Merge mozilla-central to mozilla-inbound
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Fri, 22 Apr 2016 15:57:43 +0200
changeset 332424 56d0b315df5f0748becbc7a02168b53f0068d1e0
parent 332423 50dd2630d804c1a11778d8e44bced4f855338179 (current diff)
parent 332342 fc15477ce628599519cb0055f52cc195d640dc94 (diff)
child 332425 b47cf480c22f44aeb22a9e79d81fef13cb085e61
push id6048
push userkmoir@mozilla.com
push dateMon, 06 Jun 2016 19:02:08 +0000
treeherdermozilla-beta@46d72a56c57d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone48.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-central to mozilla-inbound
b2g/app/b2g.js
mobile/android/app/mobile.js
mobile/android/base/java/org/mozilla/gecko/home/HistoryHeaderListCursorAdapter.java
mobile/android/base/java/org/mozilla/gecko/home/HistoryItemAdapter.java
mobile/android/base/java/org/mozilla/gecko/home/HistoryPanel.java
mobile/android/base/java/org/mozilla/gecko/home/RemoteTabsBaseFragment.java
mobile/android/base/java/org/mozilla/gecko/home/RemoteTabsExpandableListFragment.java
mobile/android/base/java/org/mozilla/gecko/home/RemoteTabsPanel.java
mobile/android/base/java/org/mozilla/gecko/home/RemoteTabsSplitPlaneFragment.java
mobile/android/base/java/org/mozilla/gecko/home/RemoteTabsStaticFragment.java
mobile/android/base/resources/layout-sw600dp-land/home_history_panel.xml
mobile/android/base/resources/layout/home_history_panel.xml
mobile/android/base/resources/layout/home_history_range_item.xml
mobile/android/base/resources/layout/home_remote_tabs_child.xml
mobile/android/base/resources/layout/home_remote_tabs_hidden_devices_footer.xml
mobile/android/base/resources/layout/home_remote_tabs_list_panel.xml
mobile/android/base/resources/layout/home_remote_tabs_panel.xml
mobile/android/base/resources/layout/home_remote_tabs_split_plane_panel.xml
mobile/android/base/resources/layout/remote_tabs_needs_finish_migrating.xml
mobile/android/base/resources/layout/remote_tabs_needs_password.xml
mobile/android/base/resources/layout/remote_tabs_needs_upgrade.xml
mobile/android/base/resources/layout/remote_tabs_needs_verification.xml
modules/libpref/init/all.js
--- a/b2g/app/b2g.js
+++ b/b2g/app/b2g.js
@@ -341,20 +341,58 @@ pref("image.mem.surfacecache.max_size_kb
 pref("image.mem.surfacecache.size_factor", 8);  // 1/8 of main memory
 pref("image.mem.surfacecache.discard_factor", 2);  // Discard 1/2 of the surface cache at a time.
 pref("image.mem.surfacecache.min_expiration_ms", 86400000); // 24h, we rely on the out of memory hook
 
 pref("dom.w3c_touch_events.safetyX", 0); // escape borders in units of 1/240"
 pref("dom.w3c_touch_events.safetyY", 120); // escape borders in units of 1/240"
 
 #ifdef MOZ_SAFE_BROWSING
+pref("browser.safebrowsing.enabled", true);
+// Prevent loading of pages identified as malware
+pref("browser.safebrowsing.malware.enabled", true);
 pref("browser.safebrowsing.downloads.enabled", true);
 pref("browser.safebrowsing.downloads.remote.enabled", true);
+pref("browser.safebrowsing.downloads.remote.timeout_ms", 10000);
+pref("browser.safebrowsing.downloads.remote.url", "https://sb-ssl.google.com/safebrowsing/clientreport/download?key=%GOOGLE_API_KEY%");
+pref("browser.safebrowsing.downloads.remote.block_dangerous",            true);
+pref("browser.safebrowsing.downloads.remote.block_dangerous_host",       true);
+pref("browser.safebrowsing.downloads.remote.block_potentially_unwanted", false);
+pref("browser.safebrowsing.downloads.remote.block_uncommon",             false);
+pref("browser.safebrowsing.debug", false);
+
+pref("browser.safebrowsing.provider.google.lists", "goog-badbinurl-shavar,goog-downloadwhite-digest256,goog-phish-shavar,goog-malware-shavar,goog-unwanted-shavar");
+pref("browser.safebrowsing.provider.google.updateURL", "https://safebrowsing.google.com/safebrowsing/downloads?client=SAFEBROWSING_ID&appver=%VERSION%&pver=2.2&key=%GOOGLE_API_KEY%");
+pref("browser.safebrowsing.provider.google.gethashURL", "https://safebrowsing.google.com/safebrowsing/gethash?client=SAFEBROWSING_ID&appver=%VERSION%&pver=2.2");
+pref("browser.safebrowsing.provider.google.reportURL", "https://safebrowsing.google.com/safebrowsing/diagnostic?client=%NAME%&hl=%LOCALE%&site=");
+
+pref("browser.safebrowsing.reportPhishMistakeURL", "https://%LOCALE%.phish-error.mozilla.com/?hl=%LOCALE%&url=");
+pref("browser.safebrowsing.reportPhishURL", "https://%LOCALE%.phish-report.mozilla.com/?hl=%LOCALE%&url=");
+pref("browser.safebrowsing.reportMalwareMistakeURL", "https://%LOCALE%.malware-error.mozilla.com/?hl=%LOCALE%&url=");
 
 pref("browser.safebrowsing.id", "Firefox");
+
+// Tables for application reputation.
+pref("urlclassifier.downloadBlockTable", "goog-badbinurl-shavar");
+
+// The number of random entries to send with a gethash request.
+pref("urlclassifier.gethashnoise", 4);
+
+// Gethash timeout for Safebrowsing.
+pref("urlclassifier.gethash.timeout_ms", 5000);
+
+// If an urlclassifier table has not been updated in this number of seconds,
+// a gethash request will be forced to check that the result is still in
+// the database.
+pref("urlclassifier.max-complete-age", 2700);
+
+// Tracking protection
+pref("privacy.trackingprotection.enabled", false);
+pref("privacy.trackingprotection.pbmode.enabled", true);
+
 #endif
 
 // True if this is the first time we are showing about:firstrun
 pref("browser.firstrun.show.uidiscovery", true);
 pref("browser.firstrun.show.localepicker", true);
 
 // initiated by a user
 pref("content.ime.strict_policy", true);
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -781,29 +781,59 @@ pref("gecko.handlerService.schemes.ircs.
 pref("gecko.handlerService.schemes.ircs.2.uriTemplate", "chrome://browser-region/locale/region.properties");
 pref("gecko.handlerService.schemes.ircs.3.name", "chrome://browser-region/locale/region.properties");
 pref("gecko.handlerService.schemes.ircs.3.uriTemplate", "chrome://browser-region/locale/region.properties");
 
 // By default, we don't want protocol/content handlers to be registered from a different host, see bug 402287
 pref("gecko.handlerService.allowRegisterFromDifferentHost", false);
 
 #ifdef MOZ_SAFE_BROWSING
+pref("browser.safebrowsing.enabled", true);
+pref("browser.safebrowsing.malware.enabled", true);
 pref("browser.safebrowsing.downloads.enabled", true);
 pref("browser.safebrowsing.downloads.remote.enabled", true);
+pref("browser.safebrowsing.downloads.remote.timeout_ms", 10000);
+pref("browser.safebrowsing.downloads.remote.url", "https://sb-ssl.google.com/safebrowsing/clientreport/download?key=%GOOGLE_API_KEY%");
+pref("browser.safebrowsing.downloads.remote.block_dangerous",            true);
+pref("browser.safebrowsing.downloads.remote.block_dangerous_host",       true);
+pref("browser.safebrowsing.downloads.remote.block_potentially_unwanted", false);
+pref("browser.safebrowsing.downloads.remote.block_uncommon",             false);
+pref("browser.safebrowsing.debug", false);
+
+pref("browser.safebrowsing.provider.google.lists", "goog-badbinurl-shavar,goog-downloadwhite-digest256,goog-phish-shavar,goog-malware-shavar,goog-unwanted-shavar");
+pref("browser.safebrowsing.provider.google.updateURL", "https://safebrowsing.google.com/safebrowsing/downloads?client=SAFEBROWSING_ID&appver=%VERSION%&pver=2.2&key=%GOOGLE_API_KEY%");
+pref("browser.safebrowsing.provider.google.gethashURL", "https://safebrowsing.google.com/safebrowsing/gethash?client=SAFEBROWSING_ID&appver=%VERSION%&pver=2.2");
+pref("browser.safebrowsing.provider.google.reportURL", "https://safebrowsing.google.com/safebrowsing/diagnostic?client=%NAME%&hl=%LOCALE%&site=");
+
+pref("browser.safebrowsing.reportPhishMistakeURL", "https://%LOCALE%.phish-error.mozilla.com/?hl=%LOCALE%&url=");
+pref("browser.safebrowsing.reportPhishURL", "https://%LOCALE%.phish-report.mozilla.com/?hl=%LOCALE%&url=");
+pref("browser.safebrowsing.reportMalwareMistakeURL", "https://%LOCALE%.malware-error.mozilla.com/?hl=%LOCALE%&url=");
 
 #ifdef MOZILLA_OFFICIAL
 // Normally the "client ID" sent in updates is appinfo.name, but for
 // official Firefox releases from Mozilla we use a special identifier.
 pref("browser.safebrowsing.id", "navclient-auto-ffox");
 #endif
 
 // Name of the about: page contributed by safebrowsing to handle display of error
 // pages on phishing/malware hits.  (bug 399233)
 pref("urlclassifier.alternate_error_page", "blocked");
 
+// The number of random entries to send with a gethash request.
+pref("urlclassifier.gethashnoise", 4);
+
+// Gethash timeout for Safebrowsing.
+pref("urlclassifier.gethash.timeout_ms", 5000);
+
+// If an urlclassifier table has not been updated in this number of seconds,
+// a gethash request will be forced to check that the result is still in
+// the database.
+pref("urlclassifier.max-complete-age", 2700);
+// Tables for application reputation.
+pref("urlclassifier.downloadBlockTable", "goog-badbinurl-shavar");
 #ifdef XP_WIN
 // Only download the whitelist on Windows, since the whitelist is
 // only useful for suppressing remote lookups for signed binaries which we can
 // only verify on Windows (Bug 974579). Other platforms always do remote lookups.
 pref("urlclassifier.downloadAllowTable", "goog-downloadwhite-digest256");
 #endif
 #endif
 
--- a/browser/base/content/test/urlbar/browser.ini
+++ b/browser/base/content/test/urlbar/browser.ini
@@ -3,8 +3,11 @@
 support-files =
   file_blank_but_not_blank.html
 [browser_urlbar_locationchange_urlbar_edit_dos.js]
 support-files =
   file_urlbar_edit_dos.html
 [browser_urlbar_stop_pending.js]
 support-files =
   slow-page.sjs
+[browser_urlbar_remoteness_switch.js]
+run-if = e10s
+
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/urlbar/browser_urlbar_remoteness_switch.js
@@ -0,0 +1,39 @@
+"use strict";
+
+/**
+ * Verify that when loading and going back/forward through history between URLs
+ * loaded in the content process, and URLs loaded in the parent process, we
+ * don't set the URL for the tab to about:blank inbetween the loads.
+ */
+add_task(function*() {
+  let url = "http://www.example.com/foo.html";
+  yield BrowserTestUtils.withNewTab({gBrowser, url}, function*(browser) {
+    let wpl = {
+      onLocationChange(wpl, request, location, flags) {
+        if (location.schemeIs("about")) {
+          is(location.spec, "about:config", "Only about: location change should be for about:preferences");
+        } else {
+          is(location.spec, url, "Only non-about: location change should be for the http URL we're dealing with.");
+        }
+      },
+    };
+    gBrowser.addProgressListener(wpl);
+
+    let didLoad = BrowserTestUtils.browserLoaded(browser, null, function(loadedURL) {
+      return loadedURL == "about:config";
+    });
+    yield BrowserTestUtils.loadURI(browser, "about:config");
+    yield didLoad;
+
+    gBrowser.goBack();
+    yield BrowserTestUtils.browserLoaded(browser, null, function(loadedURL) {
+      return url == loadedURL;
+    });
+    gBrowser.goForward();
+    yield BrowserTestUtils.browserLoaded(browser, null, function(loadedURL) {
+      return loadedURL == "about:config";
+    });
+    gBrowser.removeProgressListener(wpl);
+  });
+});
+
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -2613,16 +2613,26 @@ ContentPermissionPrompt.prototype = {
           callback: function() {},
         },
       ];
     }
 
     var options = {
       learnMoreURL:
         Services.urlFormatter.formatURLPref("app.support.baseURL") + "push",
+      eventCallback(type) {
+        if (type == "dismissed") {
+          // Bug 1259148: Hide the doorhanger icon. Unlike other permission
+          // doorhangers, the user can't restore the doorhanger using the icon
+          // in the location bar. Instead, the site will be notified that the
+          // doorhanger was dismissed.
+          this.remove();
+          aRequest.cancel();
+        }
+      },
     };
 
     this._showPrompt(aRequest, message, "desktop-notification", actions,
                      "web-notifications",
                      "web-notifications-notification-icon", options);
   },
 
   _promptPointerLock: function CPP_promtPointerLock(aRequest, autoAllow) {
--- a/browser/components/sessionstore/ContentRestore.jsm
+++ b/browser/components/sessionstore/ContentRestore.jsm
@@ -133,17 +133,17 @@ ContentRestoreInternal.prototype = {
       webNavigation.setCurrentURI(Utils.makeURI(uri));
     }
 
     SessionHistory.restore(this.docShell, tabData);
 
     // Add a listener to watch for reloads.
     let listener = new HistoryListener(this.docShell, () => {
       // On reload, restore tab contents.
-      this.restoreTabContent(null, callbacks.onLoadFinished);
+      this.restoreTabContent(null, false, callbacks.onLoadFinished);
     });
 
     webNavigation.sessionHistory.addSHistoryListener(listener);
     this._historyListener = listener;
 
     // Make sure to reset the capabilities and attributes in case this tab gets
     // reused.
     let disallow = new Set(tabData.disallow && tabData.disallow.split(","));
@@ -170,31 +170,31 @@ ContentRestoreInternal.prototype = {
       }
     });
   },
 
   /**
    * Start loading the current page. When the data has finished loading from the
    * network, finishCallback is called. Returns true if the load was successful.
    */
-  restoreTabContent: function (loadArguments, finishCallback) {
+  restoreTabContent: function (loadArguments, isRemotenessUpdate, finishCallback) {
     let tabData = this._tabData;
     this._tabData = null;
 
     let webNavigation = this.docShell.QueryInterface(Ci.nsIWebNavigation);
     let history = webNavigation.sessionHistory;
 
     // Listen for the tab to finish loading.
     this.restoreTabContentStarted(finishCallback);
 
     // Reset the current URI to about:blank. We changed it above for
     // switch-to-tab, but now it must go back to the correct value before the
     // load happens. Don't bother doing this if we're restoring immediately
     // due to a process switch.
-    if (!loadArguments) {
+    if (!isRemotenessUpdate) {
       webNavigation.setCurrentURI(Utils.makeURI("about:blank"));
     }
 
     try {
       if (loadArguments) {
         // A load has been redirected to a new process so get history into the
         // same state it was before the load started then trigger the load.
         let referrer = loadArguments.referrer ?
--- a/browser/components/sessionstore/content/content-sessionStore.js
+++ b/browser/components/sessionstore/content/content-sessionStore.js
@@ -166,17 +166,17 @@ var MessageListener = {
     // to temporarily synchronize them.
     sendSyncMessage("SessionStore:restoreHistoryComplete", {epoch});
   },
 
   restoreTabContent({loadArguments, isRemotenessUpdate}) {
     let epoch = gCurrentEpoch;
 
     // We need to pass the value of didStartLoad back to SessionStore.jsm.
-    let didStartLoad = gContentRestore.restoreTabContent(loadArguments, () => {
+    let didStartLoad = gContentRestore.restoreTabContent(loadArguments, isRemotenessUpdate, () => {
       // Tell SessionStore.jsm that it may want to restore some more tabs,
       // since it restores a max of MAX_CONCURRENT_TAB_RESTORES at a time.
       sendAsyncMessage("SessionStore:restoreTabContentComplete", {epoch, isRemotenessUpdate});
     });
 
     sendAsyncMessage("SessionStore:restoreTabContentStarted", {epoch});
 
     if (!didStartLoad) {
--- a/browser/themes/shared/customizableui/panelUIOverlay.inc.css
+++ b/browser/themes/shared/customizableui/panelUIOverlay.inc.css
@@ -1052,17 +1052,18 @@ panelview .toolbarbutton-1,
 }
 
 .cui-widget-panelview .subviewbutton:not(.panel-subview-footer) {
   margin-left: 4px;
   margin-right: 4px;
 }
 
 panelview .toolbarbutton-1,
-.widget-overflow-list .toolbarbutton-1 {
+.widget-overflow-list .toolbarbutton-1:not(:first-child),
+.widget-overflow-list .toolbaritem-combined-buttons:not(:first-child) {
   margin-top: 6px;
 }
 
 panelview .toolbarbutton-1@buttonStateHover@,
 toolbarbutton.subviewbutton@buttonStateHover@,
 menu.subviewbutton@menuStateHover@,
 menuitem.subviewbutton@menuStateHover@,
 .share-provider-button@buttonStateHover@:not([checked="true"]),
--- a/dom/notification/Notification.cpp
+++ b/dom/notification/Notification.cpp
@@ -609,17 +609,21 @@ NotificationPermissionRequest::GetElemen
   NS_ENSURE_ARG_POINTER(aElement);
   *aElement = nullptr;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 NotificationPermissionRequest::Cancel()
 {
-  mPermission = NotificationPermission::Denied;
+  // `Cancel` is called if the user denied permission or dismissed the
+  // permission request. To distinguish between the two, we set the
+  // permission to "default" and query the permission manager in
+  // `ResolvePromise`.
+  mPermission = NotificationPermission::Default;
   return DispatchResolvePromise();
 }
 
 NS_IMETHODIMP
 NotificationPermissionRequest::Allow(JS::HandleValue aChoices)
 {
   MOZ_ASSERT(aChoices.isUndefined());
 
@@ -644,16 +648,21 @@ NotificationPermissionRequest::DispatchR
     &NotificationPermissionRequest::ResolvePromise);
   return NS_DispatchToMainThread(resolveRunnable);
 }
 
 nsresult
 NotificationPermissionRequest::ResolvePromise()
 {
   nsresult rv = NS_OK;
+  if (mPermission == NotificationPermission::Default) {
+    // This will still be "default" if the user dismissed the doorhanger,
+    // or "denied" otherwise.
+    mPermission = Notification::TestPermission(mPrincipal);
+  }
   if (mCallback) {
     ErrorResult error;
     mCallback->Call(mPermission, error);
     rv = error.StealNSResult();
   }
   Telemetry::Accumulate(
     Telemetry::WEB_NOTIFICATION_REQUEST_PERMISSION_CALLBACK, !!mCallback);
   mPromise->MaybeResolve(mPermission);
@@ -1942,20 +1951,31 @@ Notification::GetPermissionInternal(nsIP
   if (Preferences::GetBool("notification.prompt.testing", false)) {
     if (Preferences::GetBool("notification.prompt.testing.allow", true)) {
       return NotificationPermission::Granted;
     } else {
       return NotificationPermission::Denied;
     }
   }
 
+  return TestPermission(aPrincipal);
+}
+
+/* static */ NotificationPermission
+Notification::TestPermission(nsIPrincipal* aPrincipal)
+{
+  AssertIsOnMainThread();
+
   uint32_t permission = nsIPermissionManager::UNKNOWN_ACTION;
 
   nsCOMPtr<nsIPermissionManager> permissionManager =
     services::GetPermissionManager();
+  if (!permissionManager) {
+    return NotificationPermission::Default;
+  }
 
   permissionManager->TestExactPermissionFromPrincipal(aPrincipal,
                                                       "desktop-notification",
                                                       &permission);
 
   // Convert the result to one of the enum types.
   switch (permission) {
   case nsIPermissionManager::ALLOW_ACTION:
--- a/dom/notification/Notification.h
+++ b/dom/notification/Notification.h
@@ -312,16 +312,18 @@ public:
   void ReleaseObject();
 
   static NotificationPermission GetPermission(nsIGlobalObject* aGlobal,
                                               ErrorResult& aRv);
 
   static NotificationPermission GetPermissionInternal(nsIPrincipal* aPrincipal,
                                                       ErrorResult& rv);
 
+  static NotificationPermission TestPermission(nsIPrincipal* aPrincipal);
+
   bool DispatchClickEvent();
   bool DispatchNotificationClickEvent();
 
   static nsresult RemovePermission(nsIPrincipal* aPrincipal);
   static nsresult OpenSettings(nsIPrincipal* aPrincipal);
 protected:
   Notification(nsIGlobalObject* aGlobal, const nsAString& aID,
                const nsAString& aTitle, const nsAString& aBody,
--- a/dom/notification/moz.build
+++ b/dom/notification/moz.build
@@ -31,9 +31,10 @@ include('/ipc/chromium/chromium-config.m
 
 FINAL_LIBRARY = 'xul'
 LOCAL_INCLUDES += [
     '/dom/base',
     '/dom/ipc',
     '/dom/workers',
 ]
 
+BROWSER_CHROME_MANIFESTS += ['test/browser/browser.ini']
 XPCSHELL_TESTS_MANIFESTS += ['test/unit/xpcshell.ini']
new file mode 100644
--- /dev/null
+++ b/dom/notification/test/browser/browser.ini
@@ -0,0 +1,2 @@
+[browser_permission_dismiss.js]
+support-files = notification.html
new file mode 100644
--- /dev/null
+++ b/dom/notification/test/browser/browser_permission_dismiss.js
@@ -0,0 +1,113 @@
+"use strict";
+
+const ORIGIN_URI = Services.io.newURI("http://mochi.test:8888", null, null);
+const PERMISSION_NAME = "desktop-notification";
+const PROMPT_ALLOW_BUTTON = -1;
+const PROMPT_BLOCK_BUTTON = 0;
+const TEST_URL = "http://mochi.test:8888/browser/dom/notification/test/browser/notification.html";
+
+/**
+ * Clicks the specified web-notifications prompt button.
+ *
+ * @param {Number} aButtonIndex Number indicating which button to click.
+ *                              See the constants in this file.
+ * @note modified from toolkit/components/passwordmgr/test/browser/head.js
+ */
+function clickDoorhangerButton(aButtonIndex) {
+  ok(true, "Looking for action at index " + aButtonIndex);
+
+  let popup = PopupNotifications.getNotification("web-notifications");
+  let notifications = popup.owner.panel.childNodes;
+  ok(notifications.length > 0, "at least one notification displayed");
+  ok(true, notifications.length + " notification(s)");
+  let notification = notifications[0];
+
+  if (aButtonIndex == -1) {
+    ok(true, "Triggering main action");
+    notification.button.doCommand();
+  } else if (aButtonIndex <= popup.secondaryActions.length) {
+    ok(true, "Triggering secondary action " + aButtonIndex);
+    notification.childNodes[aButtonIndex].doCommand();
+  }
+}
+
+/**
+ * Opens a tab which calls `Notification.requestPermission()` with a callback
+ * argument, calls the `task` function while the permission prompt is open,
+ * and verifies that the expected permission is set.
+ *
+ * @param {Function} task Task function to run to interact with the prompt.
+ * @param {String} permission Expected permission value.
+ * @return {Promise} resolving when the task function is done and the tab
+ *                   closes.
+ */
+function tabWithRequest(task, permission) {
+  Services.perms.remove(ORIGIN_URI, PERMISSION_NAME);
+
+  return BrowserTestUtils.withNewTab({
+    gBrowser,
+    url: TEST_URL,
+  }, function*(browser) {
+    let requestPromise = ContentTask.spawn(browser, {
+      permission
+    }, function*({permission}) {
+      function requestCallback(perm) {
+        is(perm, permission,
+          "Should call the legacy callback with the permission state");
+      }
+      let perm = yield content.window.Notification
+                              .requestPermission(requestCallback);
+      is(perm, permission,
+         "Should resolve the promise with the permission state");
+    });
+
+    yield BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown");
+    yield task();
+    yield requestPromise;
+  });
+}
+
+add_task(function* setup() {
+  SimpleTest.registerCleanupFunction(() => {
+    Services.perms.remove(ORIGIN_URI, PERMISSION_NAME);
+  });
+});
+
+add_task(function* test_requestPermission_granted() {
+  yield tabWithRequest(function() {
+    clickDoorhangerButton(PROMPT_ALLOW_BUTTON);
+  }, "granted");
+
+  ok(!PopupNotifications.getNotification("web-notifications"),
+     "Should remove the doorhanger notification icon if granted");
+
+  is(Services.perms.testPermission(ORIGIN_URI, PERMISSION_NAME),
+     Services.perms.ALLOW_ACTION,
+     "Check permission in perm. manager");
+});
+
+add_task(function* test_requestPermission_denied() {
+  yield tabWithRequest(function() {
+    clickDoorhangerButton(PROMPT_BLOCK_BUTTON);
+  }, "denied");
+
+  ok(!PopupNotifications.getNotification("web-notifications"),
+     "Should remove the doorhanger notification icon if denied");
+
+  is(Services.perms.testPermission(ORIGIN_URI, PERMISSION_NAME),
+     Services.perms.DENY_ACTION,
+     "Check permission in perm. manager");
+});
+
+add_task(function* test_requestPermission_dismissed() {
+  yield tabWithRequest(function() {
+    PopupNotifications.panel.hidePopup();
+  }, "default");
+
+  ok(!PopupNotifications.getNotification("web-notifications"),
+     "Should remove the doorhanger notification icon if dismissed");
+
+  is(Services.perms.testPermission(ORIGIN_URI, PERMISSION_NAME),
+     Services.perms.UNKNOWN_ACTION,
+     "Check permission in perm. manager");
+});
new file mode 100644
--- /dev/null
+++ b/dom/notification/test/browser/notification.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <meta charset="utf-8">
+    <title>Notifications test</title>
+  </head>
+
+  <body>
+
+  </body>
+</html>
--- a/mobile/android/app/mobile.js
+++ b/mobile/android/app/mobile.js
@@ -306,16 +306,19 @@ pref("browser.search.geoSpecificDefaults
 pref("browser.search.defaultenginename.US", "chrome://browser/locale/region.properties");
 pref("browser.search.order.US.1", "chrome://browser/locale/region.properties");
 pref("browser.search.order.US.2", "chrome://browser/locale/region.properties");
 pref("browser.search.order.US.3", "chrome://browser/locale/region.properties");
 
 // disable updating
 pref("browser.search.update", false);
 
+// enable tracking protection for private browsing
+pref("privacy.trackingprotection.pbmode.enabled", true);
+
 // disable search suggestions by default
 pref("browser.search.suggest.enabled", false);
 pref("browser.search.suggest.prompted", false);
 
 // tell the search service that we don't really expose the "current engine"
 pref("browser.search.noCurrentEngine", true);
 
 // Control media casting & mirroring features
@@ -647,24 +650,56 @@ pref("media.android-media-codec.preferre
 
 // Enable MSE
 pref("media.mediasource.enabled", true);
 
 // optimize images memory usage
 pref("image.downscale-during-decode.enabled", true);
 
 #ifdef MOZ_SAFE_BROWSING
+pref("browser.safebrowsing.enabled", true);
+pref("browser.safebrowsing.malware.enabled", true);
 pref("browser.safebrowsing.downloads.enabled", false);
 pref("browser.safebrowsing.downloads.remote.enabled", false);
+pref("browser.safebrowsing.downloads.remote.timeout_ms", 10000);
+pref("browser.safebrowsing.downloads.remote.url", "https://sb-ssl.google.com/safebrowsing/clientreport/download?key=%GOOGLE_API_KEY%");
+pref("browser.safebrowsing.downloads.remote.block_dangerous",            true);
+pref("browser.safebrowsing.downloads.remote.block_dangerous_host",       true);
+pref("browser.safebrowsing.downloads.remote.block_potentially_unwanted", false);
+pref("browser.safebrowsing.downloads.remote.block_uncommon",             false);
+pref("browser.safebrowsing.debug", false);
+
+pref("browser.safebrowsing.provider.google.lists", "goog-badbinurl-shavar,goog-downloadwhite-digest256,goog-phish-shavar,goog-malware-shavar,goog-unwanted-shavar");
+pref("browser.safebrowsing.provider.google.updateURL", "https://safebrowsing.google.com/safebrowsing/downloads?client=SAFEBROWSING_ID&appver=%VERSION%&pver=2.2&key=%GOOGLE_API_KEY%");
+pref("browser.safebrowsing.provider.google.gethashURL", "https://safebrowsing.google.com/safebrowsing/gethash?client=SAFEBROWSING_ID&appver=%VERSION%&pver=2.2");
+pref("browser.safebrowsing.provider.google.reportURL", "https://safebrowsing.google.com/safebrowsing/diagnostic?client=%NAME%&hl=%LOCALE%&site=");
+
+pref("browser.safebrowsing.reportPhishMistakeURL", "https://%LOCALE%.phish-error.mozilla.com/?hl=%LOCALE%&url=");
+pref("browser.safebrowsing.reportPhishURL", "https://%LOCALE%.phish-report.mozilla.com/?hl=%LOCALE%&url=");
+pref("browser.safebrowsing.reportMalwareMistakeURL", "https://%LOCALE%.malware-error.mozilla.com/?hl=%LOCALE%&url=");
 
 pref("browser.safebrowsing.id", @MOZ_APP_UA_NAME@);
 
 // Name of the about: page contributed by safebrowsing to handle display of error
 // pages on phishing/malware hits.  (bug 399233)
 pref("urlclassifier.alternate_error_page", "blocked");
+
+// The number of random entries to send with a gethash request.
+pref("urlclassifier.gethashnoise", 4);
+
+// Gethash timeout for Safebrowsing.
+pref("urlclassifier.gethash.timeout_ms", 5000);
+
+// If an urlclassifier table has not been updated in this number of seconds,
+// a gethash request will be forced to check that the result is still in
+// the database.
+pref("urlclassifier.max-complete-age", 2700);
+
+// Tables for application reputation.
+pref("urlclassifier.downloadBlockTable", "goog-badbinurl-shavar");
 #endif
 
 // True if this is the first time we are showing about:firstrun
 pref("browser.firstrun.show.uidiscovery", true);
 pref("browser.firstrun.show.localepicker", false);
 
 // True if you always want dump() to work
 //
--- a/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/BrowserApp.java
@@ -3553,17 +3553,17 @@ public class BrowserApp extends GeckoApp
         if (itemId == R.id.bookmarks_list) {
             final String url = AboutPages.getURLForBuiltinPanelType(PanelType.BOOKMARKS);
             Tabs.getInstance().loadUrl(url);
             Telemetry.startUISession(TelemetryContract.Session.EXPERIMENT, Experiments.BOOKMARKS_HISTORY_MENU);
             return true;
         }
 
         if (itemId == R.id.history_list) {
-            final String url = AboutPages.getURLForBuiltinPanelType(PanelType.HISTORY);
+            final String url = AboutPages.getURLForBuiltinPanelType(PanelType.COMBINED_HISTORY);
             Tabs.getInstance().loadUrl(url);
             Telemetry.startUISession(TelemetryContract.Session.EXPERIMENT, Experiments.BOOKMARKS_HISTORY_MENU);
             return true;
         }
 
         if (itemId == R.id.save_as_pdf) {
             Telemetry.sendUIEvent(TelemetryContract.Event.SAVE, TelemetryContract.Method.MENU, "pdf");
             GeckoAppShell.notifyObservers("SaveAs:PDF", null);
--- a/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
+++ b/mobile/android/base/java/org/mozilla/gecko/GeckoApp.java
@@ -1265,17 +1265,17 @@ public abstract class GeckoApp
         mRootLayout = (RelativeLayout) findViewById(R.id.root_layout);
         mGeckoLayout = (RelativeLayout) findViewById(R.id.gecko_layout);
         mMainLayout = (RelativeLayout) findViewById(R.id.main_layout);
         mLayerView = (LayerView) findViewById(R.id.layer_view);
 
         // Use global layout state change to kick off additional initialization
         mMainLayout.getViewTreeObserver().addOnGlobalLayoutListener(this);
 
-        if (Versions.preMarshmallow || !AppConstants.NIGHTLY_BUILD) {
+        if (Versions.preMarshmallow) {
             mTextSelection = new ActionBarTextSelection(
                     (TextSelectionHandle) findViewById(R.id.anchor_handle),
                     (TextSelectionHandle) findViewById(R.id.caret_handle),
                     (TextSelectionHandle) findViewById(R.id.focus_handle));
         } else {
             mTextSelection = new FloatingToolbarTextSelection(this, mLayerView);
         }
         mTextSelection.create();
deleted file mode 100644
--- a/mobile/android/base/java/org/mozilla/gecko/home/HistoryHeaderListCursorAdapter.java
+++ /dev/null
@@ -1,143 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.util.SparseArray;
-import android.view.View;
-import android.widget.TextView;
-
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.db.BrowserContract;
-
-/**
- * CursorAdapter for <code>HistoryPanel</code> to partition history items by <code>MostRecentSection</code> range headers.
- */
-public class HistoryHeaderListCursorAdapter extends MultiTypeCursorAdapter implements HistoryPanel.HistoryUrlProvider {
-    private static final int ROW_HEADER = 0;
-    private static final int ROW_STANDARD = 1;
-
-    private static final int[] VIEW_TYPES = new int[] { ROW_STANDARD, ROW_HEADER };
-    private static final int[] LAYOUT_TYPES = new int[] { R.layout.home_item_row, R.layout.home_header_row };
-
-    // Maps headers in the list with their respective sections
-    private final SparseArray<HistoryPanel.MostRecentSection> mMostRecentSections;
-
-    public HistoryHeaderListCursorAdapter(Context context) {
-        super(context, null, VIEW_TYPES, LAYOUT_TYPES);
-
-        // Initialize map of history sections
-        mMostRecentSections = new SparseArray<>();
-    }
-
-    @Override
-    public Object getItem(int position) {
-        final int type = getItemViewType(position);
-
-        // Header items are not in the cursor
-        if (type == ROW_HEADER) {
-            return null;
-        }
-
-        return super.getItem(position - getMostRecentSectionsCountBefore(position));
-    }
-
-    @Override
-    public int getItemViewType(int position) {
-        if (mMostRecentSections.get(position) != null) {
-            return ROW_HEADER;
-        }
-
-        return ROW_STANDARD;
-    }
-
-    @Override
-    public boolean isEnabled(int position) {
-        return (getItemViewType(position) == ROW_STANDARD);
-    }
-
-    @Override
-    public int getCount() {
-        // Add the history section headers to the number of reported results.
-        return super.getCount() + mMostRecentSections.size();
-    }
-
-    @Override
-    public Cursor swapCursor(Cursor cursor) {
-        loadMostRecentSections(cursor);
-        Cursor oldCursor = super.swapCursor(cursor);
-        return oldCursor;
-    }
-
-    @Override
-    public void bindView(View view, Context context, int position) {
-        final int type = getItemViewType(position);
-
-        if (type == ROW_HEADER) {
-            final HistoryPanel.MostRecentSection section = mMostRecentSections.get(position);
-            final TextView row = (TextView) view;
-            row.setText(HistoryPanel.getMostRecentSectionTitle(section));
-        } else {
-            // Account for the most recent section headers
-            position -= getMostRecentSectionsCountBefore(position);
-            final Cursor c = getCursor(position);
-            final TwoLinePageRow row = (TwoLinePageRow) view;
-            row.updateFromCursor(c);
-        }
-    }
-
-    private int getMostRecentSectionsCountBefore(int position) {
-        // Account for the number headers before the given position
-        int sectionsBefore = 0;
-
-        final int historySectionsCount = mMostRecentSections.size();
-        for (int i = 0; i < historySectionsCount; i++) {
-            final int sectionPosition = mMostRecentSections.keyAt(i);
-            if (sectionPosition > position) {
-                break;
-            }
-
-            sectionsBefore++;
-        }
-
-        return sectionsBefore;
-    }
-
-    private void loadMostRecentSections(Cursor c) {
-        // Clear any history sections that may have been loaded before.
-        mMostRecentSections.clear();
-
-        if (c == null || !c.moveToFirst()) {
-            return;
-        }
-
-        HistoryPanel.MostRecentSection section = null;
-
-        do {
-            final int position = c.getPosition();
-            final long time = c.getLong(c.getColumnIndexOrThrow(BrowserContract.History.DATE_LAST_VISITED));
-            final HistoryPanel.MostRecentSection itemSection = HistoryPanel.getMostRecentSectionForTime(time);
-
-            if (section != itemSection) {
-                section = itemSection;
-                mMostRecentSections.append(position + mMostRecentSections.size(), section);
-            }
-
-            // Reached the last section, no need to continue
-            if (section == HistoryPanel.MostRecentSection.OLDER_THAN_SIX_MONTHS) {
-                break;
-            }
-        } while (c.moveToNext());
-    }
-
-    @Override
-    public String getURL(int position) {
-        position -= getMostRecentSectionsCountBefore(position);
-        final Cursor c = getCursor(position);
-        return c.getString(c.getColumnIndexOrThrow(BrowserContract.History.URL));
-    }
-}
deleted file mode 100644
--- a/mobile/android/base/java/org/mozilla/gecko/home/HistoryItemAdapter.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.support.v4.widget.CursorAdapter;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-import org.mozilla.gecko.db.BrowserContract;
-
-/**
- * A Cursor adapter that is used to populate the history list items in split plane mode.
- */
-public class HistoryItemAdapter extends CursorAdapter implements HistoryPanel.HistoryUrlProvider {
-    private final int resource;
-
-    public HistoryItemAdapter(Context context, Cursor c, int resource) {
-        super(context, c, false);
-        this.resource = resource;
-    }
-
-    @Override
-    public View newView(Context context, Cursor cursor, ViewGroup parent) {
-        return LayoutInflater.from(context).inflate(resource, parent, false);
-    }
-
-    @Override
-    public View getView(int position, View convertView, ViewGroup parent) {
-        return super.getView(position, convertView, parent);
-    }
-
-    @Override
-    public void bindView(View view, Context context, Cursor cursor) {
-        final TwoLinePageRow row = (TwoLinePageRow) view;
-        row.updateFromCursor(cursor);
-    }
-
-    @Override
-    public String getURL(int position) {
-        final Cursor cursor = getCursor();
-        if (cursor == null || !cursor.moveToPosition(position)) {
-            throw new IllegalStateException("Couldn't move cursor to position " + position);
-        }
-
-        return cursor.getString(cursor.getColumnIndexOrThrow(BrowserContract.History.URL));
-    }
-}
deleted file mode 100644
--- a/mobile/android/base/java/org/mozilla/gecko/home/HistoryPanel.java
+++ /dev/null
@@ -1,518 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import java.util.ArrayList;
-import java.util.Calendar;
-import java.util.EnumSet;
-import java.util.List;
-import java.util.Locale;
-
-import android.support.v4.content.ContextCompat;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import org.mozilla.gecko.EventDispatcher;
-import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.GeckoEvent;
-import org.mozilla.gecko.GeckoProfile;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.TelemetryContract;
-import org.mozilla.gecko.db.BrowserContract.Combined;
-import org.mozilla.gecko.db.BrowserDB;
-import org.mozilla.gecko.home.HomeContextMenuInfo.RemoveItemType;
-import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
-import org.mozilla.gecko.restrictions.Restrictable;
-import org.mozilla.gecko.restrictions.Restrictions;
-import org.mozilla.gecko.util.HardwareUtils;
-
-import android.app.AlertDialog;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.DialogInterface;
-import android.database.Cursor;
-import android.graphics.Typeface;
-import android.os.Bundle;
-import android.support.v4.app.LoaderManager;
-import android.support.v4.content.Loader;
-import android.support.v4.widget.CursorAdapter;
-import android.text.SpannableStringBuilder;
-import android.text.TextPaint;
-import android.text.method.LinkMovementMethod;
-import android.text.style.ClickableSpan;
-import android.text.style.StyleSpan;
-import android.text.style.UnderlineSpan;
-import android.util.Log;
-import android.view.ContextMenu;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewStub;
-import android.widget.AdapterView;
-import android.widget.ArrayAdapter;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-/**
- * Fragment that displays recent history in a ListView.
- */
-public class HistoryPanel extends HomeFragment {
-    // Logging tag name
-    private static final String LOGTAG = "GeckoHistoryPanel";
-
-    // For the time sections in history
-    private static final long MS_PER_DAY = 86400000;
-    private static final long MS_PER_WEEK = MS_PER_DAY * 7;
-    private static final List<MostRecentSectionRange> recentSectionTimeOffsetList = new ArrayList<>(MostRecentSection.values().length);
-
-    // Cursor loader ID for history query
-    private static final int LOADER_ID_HISTORY = 0;
-
-    // String placeholders to mark formatting.
-    private final static String FORMAT_S1 = "%1$s";
-    private final static String FORMAT_S2 = "%2$s";
-
-    // Maintain selected range state.
-    // Only accessed from the UI thread.
-    private static MostRecentSection selected;
-
-    // Adapter for the list of recent history entries.
-    private CursorAdapter mAdapter;
-
-    // Adapter for the timeline of history entries.
-    private ArrayAdapter<MostRecentSection> mRangeAdapter;
-
-    // The view shown by the fragment.
-    private HomeListView mList;
-    private HomeListView mRangeList;
-
-    // The button view for clearing browsing history.
-    private View mClearHistoryButton;
-
-    // Reference to the View to display when there are no results.
-    private View mEmptyView;
-
-    // Callbacks used for the search and favicon cursor loaders
-    private CursorLoaderCallbacks mCursorLoaderCallbacks;
-
-    // The time ranges for each section
-    public enum MostRecentSection {
-        TODAY,
-        YESTERDAY,
-        WEEK,
-        THIS_MONTH,
-        MONTH_AGO,
-        TWO_MONTHS_AGO,
-        THREE_MONTHS_AGO,
-        FOUR_MONTHS_AGO,
-        FIVE_MONTHS_AGO,
-        OLDER_THAN_SIX_MONTHS
-    };
-
-    protected interface HistoryUrlProvider {
-        public String getURL(int position);
-    }
-
-    @Override
-    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
-        return inflater.inflate(R.layout.home_history_panel, container, false);
-    }
-
-    @Override
-    public void onViewCreated(View view, Bundle savedInstanceState) {
-        super.onViewCreated(view, savedInstanceState);
-
-        mRangeList = (HomeListView) view.findViewById(R.id.range_list);
-        mList = (HomeListView) view.findViewById(R.id.list);
-        mList.setTag(HomePager.LIST_TAG_HISTORY);
-
-        if (mRangeList != null) {
-            mRangeList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
-                @Override
-                public void onItemClick(AdapterView<?> adapter, View view, int position, long id) {
-                    final MostRecentSection rangeItem = (MostRecentSection) adapter.getItemAtPosition(position);
-                    if (rangeItem != null) {
-                        // Notify data has changed for both range and item adapter.
-                        // This will update selected rangeItem item background and the tabs list.
-                        // This will also update the selected range along with cursor start and end.
-                        selected = rangeItem;
-                        mRangeAdapter.notifyDataSetChanged();
-                        getLoaderManager().getLoader(LOADER_ID_HISTORY).forceLoad();
-                        mList.smoothScrollToPosition(0);
-                    }
-                }
-            });
-        }
-
-        mList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
-            @Override
-            public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
-                final String url = ((HistoryUrlProvider) mAdapter).getURL(position);
-
-                Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.LIST_ITEM, "history");
-
-                // This item is a TwoLinePageRow, so we allow switch-to-tab.
-                mUrlOpenListener.onUrlOpen(url, EnumSet.of(OnUrlOpenListener.Flags.ALLOW_SWITCH_TO_TAB));
-            }
-        });
-
-        mList.setContextMenuInfoFactory(new HomeContextMenuInfo.Factory() {
-            @Override
-            public HomeContextMenuInfo makeInfoForCursor(View view, int position, long id, Cursor cursor) {
-                final HomeContextMenuInfo info = new HomeContextMenuInfo(view, position, id);
-                info.url = cursor.getString(cursor.getColumnIndexOrThrow(Combined.URL));
-                info.title = cursor.getString(cursor.getColumnIndexOrThrow(Combined.TITLE));
-                info.historyId = cursor.getInt(cursor.getColumnIndexOrThrow(Combined.HISTORY_ID));
-                info.itemType = RemoveItemType.HISTORY;
-                final int bookmarkIdCol = cursor.getColumnIndexOrThrow(Combined.BOOKMARK_ID);
-                if (cursor.isNull(bookmarkIdCol)) {
-                    // If this is a combined cursor, we may get a history item without a
-                    // bookmark, in which case the bookmarks ID column value will be null.
-                    info.bookmarkId =  -1;
-                } else {
-                    info.bookmarkId = cursor.getInt(bookmarkIdCol);
-                }
-                return info;
-            }
-        });
-        registerForContextMenu(mList);
-
-        mClearHistoryButton = view.findViewById(R.id.clear_history_button);
-        mClearHistoryButton.setOnClickListener(new View.OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                final Context context = getActivity();
-
-                final AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(context);
-                dialogBuilder.setMessage(R.string.home_clear_history_confirm);
-
-                dialogBuilder.setNegativeButton(R.string.button_cancel, new AlertDialog.OnClickListener() {
-                    @Override
-                    public void onClick(final DialogInterface dialog, final int which) {
-                        dialog.dismiss();
-                    }
-                });
-
-                dialogBuilder.setPositiveButton(R.string.button_ok, new AlertDialog.OnClickListener() {
-                    @Override
-                    public void onClick(final DialogInterface dialog, final int which) {
-                        dialog.dismiss();
-
-                        // Send message to Java to clear history.
-                        final JSONObject json = new JSONObject();
-                        try {
-                            json.put("history", true);
-                        } catch (JSONException e) {
-                            Log.e(LOGTAG, "JSON error", e);
-                        }
-
-                        GeckoAppShell.notifyObservers("Sanitize:ClearData", json.toString());
-
-                        Telemetry.sendUIEvent(TelemetryContract.Event.SANITIZE, TelemetryContract.Method.BUTTON, "history");
-                    }
-                });
-
-                dialogBuilder.show();
-            }
-        });
-    }
-
-    @Override
-    public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) {
-        super.onCreateContextMenu(menu, view, menuInfo);
-
-        if (!Restrictions.isAllowed(getActivity(), Restrictable.CLEAR_HISTORY)) {
-            menu.findItem(R.id.home_remove).setVisible(false);
-        }
-    }
-
-    @Override
-    public void onDestroyView() {
-        super.onDestroyView();
-
-        // Discard any additional item clicks on the list as the
-        // panel is getting destroyed (bug 1210243).
-        if (mRangeList != null) {
-            mRangeList.setOnItemClickListener(null);
-        }
-        mList.setOnItemClickListener(null);
-
-        mRangeList = null;
-        mList = null;
-        mEmptyView = null;
-        mClearHistoryButton = null;
-    }
-
-    @Override
-    public void onActivityCreated(Bundle savedInstanceState) {
-        super.onActivityCreated(savedInstanceState);
-
-        // Reset selection.
-        selected = mRangeList == null ? MostRecentSection.THIS_MONTH : MostRecentSection.TODAY;
-
-        // Initialize adapter
-        if (mRangeList != null) {
-            mAdapter = new HistoryItemAdapter(getActivity(), null, R.layout.home_item_row);
-            mRangeAdapter = new HistoryRangeAdapter(getActivity(), R.layout.home_history_range_item);
-
-            mRangeList.setAdapter(mRangeAdapter);
-            mList.setAdapter(mAdapter);
-        } else {
-            mAdapter = new HistoryHeaderListCursorAdapter(getActivity());
-            mList.setAdapter(mAdapter);
-        }
-
-        // Create callbacks before the initial loader is started
-        mCursorLoaderCallbacks = new CursorLoaderCallbacks();
-
-        // Update the section string with current time as reference.
-        updateRecentSectionOffset(getActivity());
-        loadIfVisible();
-    }
-
-    @Override
-    protected void loadIfVisible() {
-        // Force reload fragment only in tablets.
-        if (canLoad() && HardwareUtils.isTablet()) {
-            load();
-            return;
-        }
-
-         super.loadIfVisible();
-    }
-
-    @Override
-    protected void load() {
-        getLoaderManager().initLoader(LOADER_ID_HISTORY, null, mCursorLoaderCallbacks);
-    }
-
-    private void updateUiFromCursor(Cursor c) {
-        if (c != null && c.getCount() > 0) {
-            if (Restrictions.isAllowed(getActivity(), Restrictable.CLEAR_HISTORY)) {
-                mClearHistoryButton.setVisibility(View.VISIBLE);
-            }
-            return;
-        }
-
-        // Cursor is empty, so hide the "Clear browsing history" button,
-        // and set the empty view if it hasn't been set already.
-        mClearHistoryButton.setVisibility(View.GONE);
-
-        if (mEmptyView == null) {
-            // Set empty panel view. We delay this so that the empty view won't flash.
-            final ViewStub emptyViewStub = (ViewStub) getView().findViewById(R.id.home_empty_view_stub);
-            mEmptyView = emptyViewStub.inflate();
-
-            final ImageView emptyIcon = (ImageView) mEmptyView.findViewById(R.id.home_empty_image);
-            emptyIcon.setImageResource(R.drawable.icon_most_recent_empty);
-
-            final TextView emptyText = (TextView) mEmptyView.findViewById(R.id.home_empty_text);
-            if (selected == null || mRangeAdapter == null || mRangeList == null) {
-                emptyText.setText(R.string.home_most_recent_empty);
-            } else {
-                emptyText.setText(R.string.home_selected_empty);
-            }
-
-            final TextView emptyHint = (TextView) mEmptyView.findViewById(R.id.home_empty_hint);
-            final String hintText = getResources().getString(R.string.home_most_recent_emptyhint);
-
-            final SpannableStringBuilder hintBuilder = formatHintText(hintText);
-            if (hintBuilder != null) {
-                emptyHint.setText(hintBuilder);
-                emptyHint.setMovementMethod(LinkMovementMethod.getInstance());
-                emptyHint.setVisibility(View.VISIBLE);
-            }
-
-            if (!Restrictions.isAllowed(getActivity(), Restrictable.PRIVATE_BROWSING)) {
-                emptyHint.setVisibility(View.GONE);
-            }
-
-            mList.setEmptyView(mEmptyView);
-        }
-    }
-
-    /**
-     * Make Span that is clickable, and underlined
-     * between the string markers <code>FORMAT_S1</code> and
-     * <code>FORMAT_S2</code>.
-     *
-     * @param text String to format
-     * @return formatted SpannableStringBuilder, or null if there
-     * is not any text to format.
-     */
-    private SpannableStringBuilder formatHintText(String text) {
-        // Set formatting as marked by string placeholders.
-        final int underlineStart = text.indexOf(FORMAT_S1);
-        final int underlineEnd = text.indexOf(FORMAT_S2);
-
-        // Check that there is text to be formatted.
-        if (underlineStart >= underlineEnd) {
-            return null;
-        }
-
-        final SpannableStringBuilder ssb = new SpannableStringBuilder(text);
-
-        // Set clickable text.
-        final ClickableSpan clickableSpan = new ClickableSpan() {
-            @Override
-            public void onClick(View widget) {
-                Telemetry.sendUIEvent(TelemetryContract.Event.ACTION, TelemetryContract.Method.PANEL, "hint-private-browsing");
-                try {
-                    final JSONObject json = new JSONObject();
-                    json.put("type", "Menu:Open");
-                    EventDispatcher.getInstance().dispatchEvent(json, null);
-                } catch (JSONException e) {
-                    Log.e(LOGTAG, "Error forming JSON for Private Browsing contextual hint", e);
-                }
-            }
-        };
-
-        ssb.setSpan(clickableSpan, 0, text.length(), 0);
-
-        // Remove underlining set by ClickableSpan.
-        final UnderlineSpan noUnderlineSpan = new UnderlineSpan() {
-            @Override
-            public void updateDrawState(TextPaint textPaint) {
-                textPaint.setUnderlineText(false);
-            }
-        };
-
-        ssb.setSpan(noUnderlineSpan, 0, text.length(), 0);
-
-        // Add underlining for "Private Browsing".
-        ssb.setSpan(new UnderlineSpan(), underlineStart, underlineEnd, 0);
-
-        ssb.delete(underlineEnd, underlineEnd + FORMAT_S2.length());
-        ssb.delete(underlineStart, underlineStart + FORMAT_S1.length());
-
-        return ssb;
-    }
-
-    private static void updateRecentSectionOffset(final Context context) {
-        final long now = System.currentTimeMillis();
-        final Calendar cal  = Calendar.getInstance();
-        cal.set(Calendar.HOUR_OF_DAY, 0);
-        cal.set(Calendar.MINUTE, 0);
-        cal.set(Calendar.SECOND, 0);
-        cal.set(Calendar.MILLISECOND, 1);
-
-        // Calculate the start, end time and display text for the MostRecentSection range.
-        recentSectionTimeOffsetList.add(MostRecentSection.TODAY.ordinal(),
-                new MostRecentSectionRange(cal.getTimeInMillis(), now, context.getString(R.string.history_today_section)));
-        recentSectionTimeOffsetList.add(MostRecentSection.YESTERDAY.ordinal(),
-                new MostRecentSectionRange(cal.getTimeInMillis() - MS_PER_DAY, cal.getTimeInMillis(), context.getString(R.string.history_yesterday_section)));
-        recentSectionTimeOffsetList.add(MostRecentSection.WEEK.ordinal(),
-                new MostRecentSectionRange(cal.getTimeInMillis() - MS_PER_WEEK, now, context.getString(R.string.history_week_section)));
-
-        // Update the calendar to start of next month.
-        cal.add(Calendar.MONTH, 1);
-        cal.set(Calendar.DAY_OF_MONTH, cal.getMinimum(Calendar.DAY_OF_MONTH));
-
-        // Iterate over the remaining MostRecentSections, to find the start, end and display text.
-        for (int i = MostRecentSection.THIS_MONTH.ordinal(); i < MostRecentSection.OLDER_THAN_SIX_MONTHS.ordinal(); i++) {
-            final long end = cal.getTimeInMillis();
-            cal.add(Calendar.MONTH, -1);
-            final long start = cal.getTimeInMillis();
-            final String displayName = cal.getDisplayName(Calendar.MONTH, Calendar.LONG, Locale.getDefault());
-            recentSectionTimeOffsetList.add(i, new MostRecentSectionRange(start, end, displayName));
-        }
-        recentSectionTimeOffsetList.add(MostRecentSection.OLDER_THAN_SIX_MONTHS.ordinal(),
-                new MostRecentSectionRange(0L, cal.getTimeInMillis(), context.getString(R.string.history_older_section)));
-    }
-
-    private static class HistoryCursorLoader extends SimpleCursorLoader {
-        // Max number of history results
-        private static final int HISTORY_LIMIT = 100;
-        private final BrowserDB mDB;
-
-        public HistoryCursorLoader(Context context) {
-            super(context);
-            mDB = GeckoProfile.get(context).getDB();
-        }
-
-        @Override
-        public Cursor loadCursor() {
-            final ContentResolver cr = getContext().getContentResolver();
-            updateRecentSectionOffset(getContext());
-            MostRecentSectionRange mostRecentSectionRange = recentSectionTimeOffsetList.get(selected.ordinal());
-            return mDB.getRecentHistoryBetweenTime(cr, HISTORY_LIMIT, mostRecentSectionRange.start, mostRecentSectionRange.end);
-        }
-    }
-
-    protected static String getMostRecentSectionTitle(MostRecentSection section) {
-        return recentSectionTimeOffsetList.get(section.ordinal()).displayName;
-    }
-
-    protected static MostRecentSection getMostRecentSectionForTime(long time) {
-        for (int i = 0; i < MostRecentSection.OLDER_THAN_SIX_MONTHS.ordinal(); i++) {
-            if (time > recentSectionTimeOffsetList.get(i).start) {
-                return MostRecentSection.values()[i];
-            }
-        }
-
-        return MostRecentSection.OLDER_THAN_SIX_MONTHS;
-    }
-
-    private static class MostRecentSectionRange {
-        private final long start;
-        private final long end;
-        private final String displayName;
-
-        private MostRecentSectionRange(long start, long end, String displayName) {
-            this.start = start;
-            this.end = end;
-            this.displayName = displayName;
-        }
-    }
-
-    private static class HistoryRangeAdapter extends ArrayAdapter<MostRecentSection> {
-        private final Context context;
-        private final int resource;
-
-        public HistoryRangeAdapter(Context context, int resource) {
-            super(context, resource, MostRecentSection.values());
-            this.context = context;
-            this.resource = resource;
-        }
-
-        @Override
-        public View getView(int position, View convertView, ViewGroup parent) {
-            final View view;
-            if (convertView != null) {
-                view = convertView;
-            } else {
-                final LayoutInflater inflater = LayoutInflater.from(context);
-                view = inflater.inflate(resource, parent, false);
-                view.setTag(view.findViewById(R.id.range_title));
-            }
-            final MostRecentSection current = getItem(position);
-            final TextView textView = (TextView) view.getTag();
-            textView.setText(getMostRecentSectionTitle(current));
-            textView.setTextColor(ContextCompat.getColor(context, current == selected ? R.color.text_and_tabs_tray_grey : R.color.disabled_grey));
-            textView.setCompoundDrawablesWithIntrinsicBounds(0, 0, current == selected ? R.drawable.home_group_collapsed : 0, 0);
-            return view;
-        }
-    }
-
-    private class CursorLoaderCallbacks implements LoaderManager.LoaderCallbacks<Cursor> {
-        @Override
-        public Loader<Cursor> onCreateLoader(int id, Bundle args) {
-            return new HistoryCursorLoader(getActivity());
-        }
-
-        @Override
-        public void onLoadFinished(Loader<Cursor> loader, Cursor c) {
-            mAdapter.swapCursor(c);
-            updateUiFromCursor(c);
-        }
-
-        @Override
-        public void onLoaderReset(Loader<Cursor> loader) {
-            mAdapter.swapCursor(null);
-        }
-    }
-}
--- a/mobile/android/base/java/org/mozilla/gecko/home/HomeConfig.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/HomeConfig.java
@@ -14,46 +14,47 @@ import java.util.LinkedList;
 import java.util.List;
 import java.util.Map;
 
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
 import org.mozilla.gecko.annotation.RobocopTarget;
 import org.mozilla.gecko.GeckoAppShell;
-import org.mozilla.gecko.GeckoEvent;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import android.content.Context;
 import android.os.Parcel;
 import android.os.Parcelable;
 import android.text.TextUtils;
 import android.util.Pair;
 
 public final class HomeConfig {
     public static final String PREF_KEY_BOOKMARKS_PANEL_ENABLED = "bookmarksPanelEnabled";
-    public static final String PREF_KEY_HISTORY_PANEL_ENABLED = "historyPanelEnabled";
+    public static final String PREF_KEY_HISTORY_PANEL_ENABLED = "combinedHistoryPanelEnabled";
 
     /**
      * Used to determine what type of HomeFragment subclass to use when creating
      * a given panel. With the exception of DYNAMIC, all of these types correspond
      * to a default set of built-in panels. The DYNAMIC panel type is used by
      * third-party services to create panels with varying types of content.
      */
     @RobocopTarget
     public static enum PanelType implements Parcelable {
         TOP_SITES("top_sites", TopSitesPanel.class),
         BOOKMARKS("bookmarks", BookmarksPanel.class),
-        HISTORY("history", HistoryPanel.class),
         COMBINED_HISTORY("combined_history", CombinedHistoryPanel.class),
-        REMOTE_TABS("remote_tabs", RemoteTabsPanel.class),
         READING_LIST("reading_list", ReadingListPanel.class),
         RECENT_TABS("recent_tabs", RecentTabsPanel.class),
-        DYNAMIC("dynamic", DynamicPanel.class);
+        DYNAMIC("dynamic", DynamicPanel.class),
+        // Deprecated panels that should no longer exist but are kept around for
+        // migration code. Class references have been replaced with new version of the panel.
+        DEPRECATED_REMOTE_TABS("remote_tabs", CombinedHistoryPanel.class),
+        DEPRECATED_HISTORY("history", CombinedHistoryPanel.class);
 
         private final String mId;
         private final Class<?> mPanelClass;
 
         PanelType(String id, Class<?> panelClass) {
             mId = id;
             mPanelClass = panelClass;
         }
@@ -1635,23 +1636,21 @@ public final class HomeConfig {
     public static int getTitleResourceIdForBuiltinPanelType(PanelType panelType) {
         switch (panelType) {
         case TOP_SITES:
             return R.string.home_top_sites_title;
 
         case BOOKMARKS:
             return R.string.bookmarks_title;
 
+        case DEPRECATED_HISTORY:
+        case DEPRECATED_REMOTE_TABS:
         case COMBINED_HISTORY:
-        case HISTORY:
             return R.string.home_history_title;
 
-        case REMOTE_TABS:
-            return R.string.home_remote_tabs_title;
-
         case READING_LIST:
             return R.string.reading_list_title;
 
         case RECENT_TABS:
             return R.string.recent_tabs_title;
 
         default:
             throw new IllegalArgumentException("Only for built-in panel types: " + panelType);
@@ -1661,23 +1660,23 @@ public final class HomeConfig {
     public static String getIdForBuiltinPanelType(PanelType panelType) {
         switch (panelType) {
         case TOP_SITES:
             return TOP_SITES_PANEL_ID;
 
         case BOOKMARKS:
             return BOOKMARKS_PANEL_ID;
 
-        case HISTORY:
+        case DEPRECATED_HISTORY:
             return HISTORY_PANEL_ID;
 
         case COMBINED_HISTORY:
             return COMBINED_HISTORY_PANEL_ID;
 
-        case REMOTE_TABS:
+        case DEPRECATED_REMOTE_TABS:
             return REMOTE_TABS_PANEL_ID;
 
         case READING_LIST:
             return READING_LIST_PANEL_ID;
 
         case RECENT_TABS:
             return RECENT_TABS_PANEL_ID;
 
--- a/mobile/android/base/java/org/mozilla/gecko/home/HomeConfigPrefsBackend.java
+++ b/mobile/android/base/java/org/mozilla/gecko/home/HomeConfigPrefsBackend.java
@@ -161,20 +161,20 @@ public class HomeConfigPrefsBackend impl
         int historyIndex = -1;
         int syncIndex = -1;
 
         // Determine state and location of History and Sync panels.
         for (int i = 0; i < jsonPanels.length(); i++) {
             JSONObject panelObj = jsonPanels.getJSONObject(i);
             final PanelConfig panelConfig = new PanelConfig(panelObj);
             final PanelType type = panelConfig.getType();
-            if (type == PanelType.HISTORY) {
+            if (type == PanelType.DEPRECATED_HISTORY) {
                 historyIndex = i;
                 historyFlags = panelConfig.getFlags();
-            } else if (type == PanelType.REMOTE_TABS) {
+            } else if (type == PanelType.DEPRECATED_REMOTE_TABS) {
                 syncIndex = i;
                 syncFlags = panelConfig.getFlags();
             } else if (type == PanelType.COMBINED_HISTORY) {
                 // Partial landing of bug 1220928 combined the History and Sync panels of users who didn't
                 // have home panel customizations (including new users), thus they don't this migration.
                 return jsonPanels;
             }
         }
@@ -309,26 +309,26 @@ public class HomeConfigPrefsBackend impl
 
                     // Remove the old pref key.
                     prefsEditor.remove(PREFS_CONFIG_KEY_OLD);
                     break;
 
                 case 2:
                     // Add "Remote Tabs"/"Synced Tabs" panel.
                     addBuiltinPanelConfig(context, jsonPanels,
-                            PanelType.REMOTE_TABS, Position.FRONT, Position.BACK);
+                            PanelType.DEPRECATED_REMOTE_TABS, Position.FRONT, Position.BACK);
                     break;
 
                 case 3:
                     // Add the "Reading List" panel if it does not exist. At one time,
                     // the Reading List panel was shown only to devices that were not
                     // considered "low memory". Now, we expose the panel to all devices.
                     // This migration should only occur for "low memory" devices.
                     // Note: This will not agree with the default configuration, which
-                    // has REMOTE_TABS after READING_LIST on some devices.
+                    // has DEPRECATED_REMOTE_TABS after READING_LIST on some devices.
                     if (!readingListPanelExists(jsonPanels)) {
                         addBuiltinPanelConfig(context, jsonPanels,
                                 PanelType.READING_LIST, Position.BACK, Position.BACK);
                     }
                     break;
 
                 case 4:
                     // Combine the History and Sync panels. In order to minimize an unexpected reordering
@@ -485,17 +485,17 @@ public class HomeConfigPrefsBackend impl
      * used to control the visibility of the corresponding menu items.
      */
     private void updatePrefsFromConfig(JSONArray panelsArray) {
         final SharedPreferences prefs = GeckoSharedPrefs.forProfile(mContext);
         if (!prefs.contains(HomeConfig.PREF_KEY_BOOKMARKS_PANEL_ENABLED)
                 || !prefs.contains(HomeConfig.PREF_KEY_HISTORY_PANEL_ENABLED)) {
 
             final String bookmarkType = PanelType.BOOKMARKS.toString();
-            final String historyType = PanelType.HISTORY.toString();
+            final String historyType = PanelType.COMBINED_HISTORY.toString();
             try {
                 for (int i = 0; i < panelsArray.length(); i++) {
                     final JSONObject panelObj = panelsArray.getJSONObject(i);
                     final String panelType = panelObj.optString(PanelConfig.JSON_KEY_TYPE, null);
                     if (panelType == null) {
                         break;
                     }
                     final boolean isDisabled = panelObj.optBoolean(PanelConfig.JSON_KEY_DISABLED, false);
deleted file mode 100644
--- a/mobile/android/base/java/org/mozilla/gecko/home/RemoteTabsBaseFragment.java
+++ /dev/null
@@ -1,308 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import android.accounts.Account;
-import android.content.Context;
-import android.database.Cursor;
-import android.os.Bundle;
-import android.os.Handler;
-import android.support.v4.app.LoaderManager;
-import android.support.v4.content.Loader;
-import android.support.v4.widget.SwipeRefreshLayout;
-import android.util.Log;
-import android.view.ContextMenu;
-import android.view.MenuInflater;
-import android.view.MenuItem;
-import android.view.View;
-
-import org.mozilla.gecko.GeckoProfile;
-import org.mozilla.gecko.GeckoSharedPrefs;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.RemoteClientsDialogFragment.RemoteClientsListener;
-import org.mozilla.gecko.RemoteTabsExpandableListAdapter;
-import org.mozilla.gecko.db.BrowserDB;
-import org.mozilla.gecko.db.RemoteClient;
-import org.mozilla.gecko.fxa.FirefoxAccounts;
-import org.mozilla.gecko.fxa.SyncStatusListener;
-
-import java.util.ArrayList;
-import java.util.Iterator;
-import java.util.List;
-
-/**
- * Fragment backed by <code>ExpandableListAdapter<code> to displays tabs from other devices.
- */
-public abstract class RemoteTabsBaseFragment extends HomeFragment implements RemoteClientsListener {
-    // Logging tag name.
-    private static final String LOGTAG = "GeckoRemoteTabsBaseFragment";
-
-    private static final String[] STAGES_TO_SYNC_ON_REFRESH = new String[] { "clients", "tabs" };
-
-    // Update the "Last synced:" timestamps this frequently.
-    private static final long LAST_SYNCED_TIME_UPDATE_INTERVAL_IN_MILLISECONDS = 60 * 1000; // Once a minute.
-
-    // Cursor loader ID.
-    protected static final int LOADER_ID_REMOTE_TABS = 0;
-
-    // Dialog fragment TAG.
-    protected static final String DIALOG_TAG_REMOTE_TABS = "dialog_tag_remote_tabs";
-
-    // Maintain group collapsed and hidden state.
-    // Only accessed from the UI thread.
-    protected static RemoteTabsExpandableListState sState;
-
-    // Adapter for the list of remote tabs.
-    protected RemoteTabsExpandableListAdapter mAdapter;
-
-    // List of hidden remote clients.
-    // Only accessed from the UI thread.
-    protected final List<RemoteClient> mHiddenClients = new ArrayList<>();
-
-    // Callbacks used for the loader.
-    protected CursorLoaderCallbacks mCursorLoaderCallbacks;
-
-    // Child refresh layout view.
-    protected SwipeRefreshLayout mRefreshLayout;
-
-    // Sync listener that stops refreshing when a sync is completed.
-    protected RemoteTabsSyncListener mSyncStatusListener;
-
-    // Reference to the View to display when there are no results.
-    protected View mEmptyView;
-
-    // The footer view to display when there are hidden devices not shown.
-    protected View mFooterView;
-
-    // Used to post update last synced time requests.  Should always execute on the main (UI) thread.
-    protected Handler mHandler;
-
-    // Runnable to update last synced time.
-    protected Runnable mLastSyncedTimeUpdateRunnable;
-
-    @Override
-    public void onViewCreated(View view, Bundle savedInstanceState) {
-        super.onViewCreated(view, savedInstanceState);
-        mRefreshLayout = (SwipeRefreshLayout) view.findViewById(R.id.remote_tabs_refresh_layout);
-        mRefreshLayout.setColorSchemeResources(R.color.fennec_ui_orange, R.color.action_orange);
-        mRefreshLayout.setOnRefreshListener(new RemoteTabsRefreshListener());
-
-        mSyncStatusListener = new RemoteTabsSyncListener();
-        FirefoxAccounts.addSyncStatusListener(mSyncStatusListener);
-
-        mHandler = new Handler(); // Attached to current (assumed to be UI) thread.
-        mLastSyncedTimeUpdateRunnable = new LastSyncTimeUpdateRunnable();
-    }
-
-    @Override
-    public void onActivityCreated(Bundle savedInstanceState) {
-        super.onActivityCreated(savedInstanceState);
-
-        // This races when multiple Fragments are created. That's okay: one
-        // will win, and thereafter, all will be okay. If we create and then
-        // drop an instance the shared SharedPreferences backing all the
-        // instances will maintain the state for us. Since everything happens on
-        // the UI thread, this doesn't even need to be volatile.
-        if (sState == null) {
-            sState = new RemoteTabsExpandableListState(GeckoSharedPrefs.forProfile(getActivity()));
-        }
-    }
-
-    @Override
-    public void onDestroyView() {
-        super.onDestroyView();
-
-        if (mSyncStatusListener != null) {
-            FirefoxAccounts.removeSyncStatusListener(mSyncStatusListener);
-            mSyncStatusListener = null;
-        }
-
-        if (mLastSyncedTimeUpdateRunnable != null) {
-            mHandler.removeCallbacks(mLastSyncedTimeUpdateRunnable);
-            mLastSyncedTimeUpdateRunnable = null;
-            mHandler = null;
-        }
-    }
-
-    @Override
-    public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) {
-        if (!(menuInfo instanceof RemoteTabsClientContextMenuInfo)) {
-            // Long pressed item was not a RemoteTabsGroup item. Superclass
-            // can handle this.
-            super.onCreateContextMenu(menu, view, menuInfo);
-            return;
-        }
-
-        // Long pressed item was a remote client; provide the appropriate menu.
-        final MenuInflater inflater = new MenuInflater(view.getContext());
-        inflater.inflate(R.menu.home_remote_tabs_client_contextmenu, menu);
-
-        final RemoteTabsClientContextMenuInfo info = (RemoteTabsClientContextMenuInfo) menuInfo;
-        menu.setHeaderTitle(info.client.name);
-    }
-
-    @Override
-    public boolean onContextItemSelected(MenuItem item) {
-        if (super.onContextItemSelected(item)) {
-            // HomeFragment was able to handle to selected item.
-            return true;
-        }
-
-        final ContextMenu.ContextMenuInfo menuInfo = item.getMenuInfo();
-        if (!(menuInfo instanceof RemoteTabsClientContextMenuInfo)) {
-            return false;
-        }
-
-        final RemoteTabsClientContextMenuInfo info = (RemoteTabsClientContextMenuInfo) menuInfo;
-
-        final int itemId = item.getItemId();
-        if (itemId == R.id.home_remote_tabs_hide_client) {
-            sState.setClientHidden(info.client.guid, true);
-            getLoaderManager().restartLoader(LOADER_ID_REMOTE_TABS, null, mCursorLoaderCallbacks);
-            return true;
-        }
-
-        return false;
-    }
-
-    @Override
-    public void onClients(List<RemoteClient> clients) {
-        // The clients listed were hidden and have been checked by the user. We
-        // interpret that as "show these clients now".
-        for (RemoteClient client : clients) {
-            sState.setClientHidden(client.guid, false);
-            // There's no particular need to do this, but if you want to see it,
-            // let's show it all.
-            sState.setClientCollapsed(client.guid, false);
-        }
-        getLoaderManager().restartLoader(LOADER_ID_REMOTE_TABS, null, mCursorLoaderCallbacks);
-    }
-
-    @Override
-    protected void load() {
-        getLoaderManager().initLoader(LOADER_ID_REMOTE_TABS, null, mCursorLoaderCallbacks);
-    }
-
-    protected abstract void updateUiFromClients(List<RemoteClient> clients, List<RemoteClient> hiddenClients);
-
-    private static class RemoteTabsCursorLoader extends SimpleCursorLoader {
-        private final GeckoProfile mProfile;
-
-        public RemoteTabsCursorLoader(Context context) {
-            super(context);
-            mProfile = GeckoProfile.get(context);
-        }
-
-        @Override
-        public Cursor loadCursor() {
-            return mProfile.getDB().getTabsAccessor().getRemoteTabsCursor(getContext());
-        }
-    }
-
-    protected class CursorLoaderCallbacks implements LoaderManager.LoaderCallbacks<Cursor> {
-        private BrowserDB mDB;    // Pseudo-final: set in onCreateLoader.
-
-        @Override
-        public Loader<Cursor> onCreateLoader(int id, Bundle args) {
-            mDB = GeckoProfile.get(getActivity()).getDB();
-            return new RemoteTabsCursorLoader(getActivity());
-        }
-
-        @Override
-        public void onLoadFinished(Loader<Cursor> loader, Cursor c) {
-            final List<RemoteClient> clients = mDB.getTabsAccessor().getClientsFromCursor(c);
-
-            // Filter the hidden clients out of the clients list. The clients
-            // list is updated in place; the hidden clients list is built
-            // incrementally.
-            mHiddenClients.clear();
-            final Iterator<RemoteClient> it = clients.iterator();
-            while (it.hasNext()) {
-                final RemoteClient client = it.next();
-                if (sState.isClientHidden(client.guid)) {
-                    it.remove();
-                    mHiddenClients.add(client);
-                }
-            }
-
-            mAdapter.replaceClients(clients);
-            updateUiFromClients(clients, mHiddenClients);
-            scheduleLastSyncedTime();
-        }
-
-        @Override
-        public void onLoaderReset(Loader<Cursor> loader) {
-            mAdapter.replaceClients(null);
-        }
-    }
-
-    protected class RemoteTabsRefreshListener implements SwipeRefreshLayout.OnRefreshListener {
-        @Override
-        public void onRefresh() {
-            if (FirefoxAccounts.firefoxAccountsExist(getActivity())) {
-                final Account account = FirefoxAccounts.getFirefoxAccount(getActivity());
-                FirefoxAccounts.requestImmediateSync(account, STAGES_TO_SYNC_ON_REFRESH, null);
-            } else {
-                Log.wtf(LOGTAG, "No Firefox Account found; this should never happen. Ignoring.");
-                mRefreshLayout.setRefreshing(false);
-            }
-        }
-    }
-
-    protected class RemoteTabsSyncListener implements SyncStatusListener {
-        @Override
-        public Context getContext() {
-            return getActivity();
-        }
-
-        @Override
-        public Account getAccount() {
-            return FirefoxAccounts.getFirefoxAccount(getContext());
-        }
-
-        @Override
-        public void onSyncStarted() {
-        }
-
-        @Override
-        public void onSyncFinished() {
-            mRefreshLayout.setRefreshing(false);
-        }
-    }
-
-    /**
-     * Stores information regarding the creation of the context menu for a remote client.
-     */
-    protected static class RemoteTabsClientContextMenuInfo extends HomeContextMenuInfo {
-        protected final RemoteClient client;
-
-        public RemoteTabsClientContextMenuInfo(View targetView, int position, long id, RemoteClient client) {
-            super(targetView, position, id);
-            this.client = client;
-        }
-    }
-
-    /**
-     * The Runnable that schedules a future update and updates the last synced time.
-     */
-    protected class LastSyncTimeUpdateRunnable implements Runnable  {
-        @Override
-        public void run() {
-            updateAndScheduleLastSyncedTime();
-        }
-    }
-
-    protected void scheduleLastSyncedTime() {
-        // Pushes back any existing schedule callback.
-        mHandler.postDelayed(mLastSyncedTimeUpdateRunnable, LAST_SYNCED_TIME_UPDATE_INTERVAL_IN_MILLISECONDS);
-    }
-
-    protected void updateAndScheduleLastSyncedTime() {
-        // This does not hit the database; it just makes consumers update their views.  Perfect!
-        mAdapter.notifyDataSetChanged();
-        scheduleLastSyncedTime();
-    }
-}
deleted file mode 100644
--- a/mobile/android/base/java/org/mozilla/gecko/home/RemoteTabsExpandableListFragment.java
+++ /dev/null
@@ -1,251 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import java.util.ArrayList;
-import java.util.EnumSet;
-import java.util.List;
-
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.RemoteClientsDialogFragment;
-import org.mozilla.gecko.RemoteTabsExpandableListAdapter;
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.TelemetryContract;
-import org.mozilla.gecko.db.RemoteClient;
-import org.mozilla.gecko.db.RemoteTab;
-import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
-
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewStub;
-import android.widget.ExpandableListAdapter;
-import android.widget.ExpandableListView;
-import android.widget.ExpandableListView.OnChildClickListener;
-import android.widget.ExpandableListView.OnGroupClickListener;
-import android.widget.ImageView;
-import android.widget.TextView;
-
-/**
- * Fragment that displays tabs from other devices in an <code>ExpandableListView<code>.
- * <p>
- * This is intended to be used on phones, and possibly in portrait mode on tablets.
- */
-public class RemoteTabsExpandableListFragment extends RemoteTabsBaseFragment {
-    // Logging tag name.
-    private static final String LOGTAG = "GeckoRemoteTabsExpList";
-
-    // The view shown by the fragment.
-    private HomeExpandableListView mList;
-
-    public static RemoteTabsExpandableListFragment newInstance() {
-        return new RemoteTabsExpandableListFragment();
-    }
-
-    public RemoteTabsExpandableListFragment() {
-        super();
-    }
-
-    @Override
-    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
-        return inflater.inflate(R.layout.home_remote_tabs_list_panel, container, false);
-    }
-
-    @Override
-    public void onViewCreated(View view, Bundle savedInstanceState) {
-        super.onViewCreated(view, savedInstanceState);
-
-        mList = (HomeExpandableListView) view.findViewById(R.id.list);
-        mList.setTag(HomePager.LIST_TAG_REMOTE_TABS);
-
-        mList.setOnChildClickListener(new OnChildClickListener() {
-            @Override
-            public boolean onChildClick(ExpandableListView parent, View v, int groupPosition, int childPosition, long id) {
-                final ExpandableListAdapter adapter = parent.getExpandableListAdapter();
-                final RemoteTab tab = (RemoteTab) adapter.getChild(groupPosition, childPosition);
-                if (tab == null) {
-                    return false;
-                }
-
-                Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.LIST_ITEM, "remote_tabs");
-
-                // This item is a TwoLinePageRow, so we allow switch-to-tab.
-                mUrlOpenListener.onUrlOpen(tab.url, EnumSet.of(OnUrlOpenListener.Flags.ALLOW_SWITCH_TO_TAB));
-                return true;
-            }
-        });
-
-        mList.setOnGroupClickListener(new OnGroupClickListener() {
-            @Override
-            public boolean onGroupClick(ExpandableListView parent, View v, int groupPosition, long id) {
-                final ExpandableListAdapter adapter = parent.getExpandableListAdapter();
-                final RemoteClient client = (RemoteClient) adapter.getGroup(groupPosition);
-                if (client != null) {
-                    // After we process this click, the group's expanded state will have flipped.
-                    sState.setClientCollapsed(client.guid, mList.isGroupExpanded(groupPosition));
-                }
-
-                // We want the system to handle the click, expanding or collapsing as necessary.
-                return false;
-            }
-        });
-
-        // Show a context menu only for tabs (not for clients).
-        mList.setContextMenuInfoFactory(new HomeContextMenuInfo.ExpandableFactory() {
-            @Override
-            public HomeContextMenuInfo makeInfoForAdapter(View view, int position, long id, ExpandableListAdapter adapter) {
-                long packedPosition = mList.getExpandableListPosition(position);
-                final int groupPosition = ExpandableListView.getPackedPositionGroup(packedPosition);
-                final int type = ExpandableListView.getPackedPositionType(packedPosition);
-                if (type == ExpandableListView.PACKED_POSITION_TYPE_CHILD) {
-                    final int childPosition = ExpandableListView.getPackedPositionChild(packedPosition);
-                    final RemoteTab tab = (RemoteTab) adapter.getChild(groupPosition, childPosition);
-                    final HomeContextMenuInfo info = new HomeContextMenuInfo(view, position, id);
-                    info.url = tab.url;
-                    info.title = tab.title;
-                    return info;
-                }
-
-                if (type == ExpandableListView.PACKED_POSITION_TYPE_GROUP) {
-                    final RemoteClient client = (RemoteClient) adapter.getGroup(groupPosition);
-                    final RemoteTabsClientContextMenuInfo info = new RemoteTabsClientContextMenuInfo(view, position, id, client);
-                    return info;
-                }
-                return null;
-            }
-        });
-
-        registerForContextMenu(mList);
-    }
-
-    @Override
-    public void onDestroyView() {
-        super.onDestroyView();
-
-        // Discard any additional item clicks on the list as the
-        // panel is getting destroyed (bug 1210243).
-        mList.setOnChildClickListener(null);
-        mList.setOnGroupClickListener(null);
-
-        mList = null;
-        mEmptyView = null;
-    }
-
-    @Override
-    public void onActivityCreated(Bundle savedInstanceState) {
-        super.onActivityCreated(savedInstanceState);
-
-        // There is an unfortunate interaction between ExpandableListViews and
-        // footer onClick handling. The footer view itself appears to not
-        // receive click events. Its children, however, do receive click events.
-        // Therefore, we attach an onClick handler to a child of the footer view
-        // itself.
-        mFooterView = LayoutInflater.from(getActivity()).inflate(R.layout.home_remote_tabs_hidden_devices_footer, mList, false);
-        final View view = mFooterView.findViewById(R.id.hidden_devices);
-        view.setClickable(true);
-        view.setOnClickListener(new View.OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                final RemoteClientsDialogFragment dialog = RemoteClientsDialogFragment.newInstance(
-                        getResources().getString(R.string.home_remote_tabs_hidden_devices_title),
-                        getResources().getString(R.string.home_remote_tabs_unhide_selected_devices),
-                        RemoteClientsDialogFragment.ChoiceMode.MULTIPLE, new ArrayList<>(mHiddenClients));
-                dialog.setTargetFragment(RemoteTabsExpandableListFragment.this, 0);
-                dialog.show(getActivity().getSupportFragmentManager(), DIALOG_TAG_REMOTE_TABS);
-            }
-        });
-
-        // There is a delicate interaction, pre-KitKat, between
-        // {add,remove}FooterView and setAdapter. setAdapter wraps the adapter
-        // in a footer/header-managing adapter, which only happens (pre-KitKat)
-        // if a footer/header is present. Therefore, we add our footer before
-        // setting the adapter; and then we remove it afterward. From there on,
-        // we can add/remove it at will.
-        mList.addFooterView(mFooterView, null, true);
-
-        // Initialize adapter
-        mAdapter = new RemoteTabsExpandableListAdapter(R.layout.home_remote_tabs_group, R.layout.home_remote_tabs_child, null, true);
-        mList.setAdapter(mAdapter);
-
-        // Now the adapter is wrapped; we can remove our footer view.
-        mList.removeFooterView(mFooterView);
-
-        // Create callbacks before the initial loader is started
-        mCursorLoaderCallbacks = new CursorLoaderCallbacks();
-        loadIfVisible();
-    }
-
-    @Override
-    protected void updateUiFromClients(List<RemoteClient> clients, List<RemoteClient> hiddenClients) {
-        if (getView() == null) {
-            // Early abort. It is possible to get UI updates after the view is
-            // destroyed; this can happen due to asynchronous loaders or
-            // animations complete.
-            return;
-        }
-
-        // We have three states: no clients (including hidden clients) at all;
-        // all clients hidden; some clients hidden. We want to show the empty
-        // list view only when we have no clients at all. This flag
-        // differentiates the first from the latter two states.
-        boolean displayedSomeClients = false;
-
-        if (hiddenClients == null || hiddenClients.isEmpty()) {
-            mList.removeFooterView(mFooterView);
-        } else {
-            displayedSomeClients = true;
-
-            final TextView textView = (TextView) mFooterView.findViewById(R.id.hidden_devices);
-            if (hiddenClients.size() == 1) {
-                textView.setText(getResources().getString(R.string.home_remote_tabs_one_hidden_device));
-            } else {
-                textView.setText(getResources().getString(R.string.home_remote_tabs_many_hidden_devices, hiddenClients.size()));
-            }
-
-            // This is a simple, if not very future-proof, way to determine if
-            // the footer view has already been added to the list view.
-            if (mList.getFooterViewsCount() < 1) {
-                mList.addFooterView(mFooterView);
-            }
-        }
-
-        if (clients != null && !clients.isEmpty()) {
-            displayedSomeClients = true;
-
-            // No sense crashing if we've made an error.
-            int groupCount = Math.min(mList.getExpandableListAdapter().getGroupCount(), clients.size());
-            for (int i = 0; i < groupCount; i++) {
-                final RemoteClient client = clients.get(i);
-                if (sState.isClientCollapsed(client.guid)) {
-                    mList.collapseGroup(i);
-                } else {
-                    mList.expandGroup(i);
-                }
-            }
-        }
-
-        if (displayedSomeClients) {
-            return;
-        }
-
-        // No clients shown, not even hidden clients. Set the empty view if it
-        // hasn't been set already.
-        if (mEmptyView == null) {
-            // Set empty panel view. We delay this so that the empty view won't flash.
-            final ViewStub emptyViewStub = (ViewStub) getView().findViewById(R.id.home_empty_view_stub);
-            mEmptyView = emptyViewStub.inflate();
-
-            final ImageView emptyIcon = (ImageView) mEmptyView.findViewById(R.id.home_empty_image);
-            emptyIcon.setImageResource(R.drawable.icon_remote_tabs_empty);
-
-            final TextView emptyText = (TextView) mEmptyView.findViewById(R.id.home_empty_text);
-            emptyText.setText(R.string.home_remote_tabs_empty);
-
-            mList.setEmptyView(mEmptyView);
-        }
-    }
-}
deleted file mode 100644
--- a/mobile/android/base/java/org/mozilla/gecko/home/RemoteTabsPanel.java
+++ /dev/null
@@ -1,239 +0,0 @@
-/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import org.mozilla.gecko.GeckoScreenOrientation;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.fxa.AccountLoader;
-import org.mozilla.gecko.fxa.FirefoxAccounts;
-import org.mozilla.gecko.fxa.FxAccountConstants;
-import org.mozilla.gecko.fxa.login.State;
-import org.mozilla.gecko.fxa.login.State.Action;
-import org.mozilla.gecko.sync.SyncConstants;
-import org.mozilla.gecko.util.HardwareUtils;
-
-import android.accounts.Account;
-import android.content.res.Configuration;
-import android.os.Bundle;
-import android.support.v4.app.Fragment;
-import android.support.v4.app.LoaderManager.LoaderCallbacks;
-import android.support.v4.content.Loader;
-import android.util.Log;
-import android.util.Pair;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.ViewGroup;
-
-/**
- * A <code>HomeFragment</code> that, depending on the state of accounts on the
- * device:
- * <ul>
- * <li>displays remote tabs from other devices;</li>
- * <li>offers to re-connect a Firefox Account;</li>
- * <li>offers to create a new Firefox Account.</li>
- * </ul>
- */
-public class RemoteTabsPanel extends HomeFragment {
-    private static final String LOGTAG = "GeckoRemoteTabsPanel";
-
-    // Loader ID for Android Account loader.
-    private static final int LOADER_ID_ACCOUNT = 0;
-    private static final String FRAGMENT_ACTION = "FRAGMENT_ACTION";
-    private static final String FRAGMENT_ORIENTATION = "FRAGMENT_ORIENTATION";
-    private static final String FRAGMENT_TAG = "FRAGMENT_TAG";
-    private static final String NO_ACCOUNT = "NO_ACCOUNT";
-
-    // Callback for loaders.
-    private AccountLoaderCallbacks mAccountLoaderCallbacks;
-
-    @Override
-    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
-        return inflater.inflate(R.layout.home_remote_tabs_panel, container, false);
-    }
-
-    @Override
-    public void onActivityCreated(Bundle savedInstanceState) {
-        super.onActivityCreated(savedInstanceState);
-
-        // Create callbacks before the initial loader is started.
-        mAccountLoaderCallbacks = new AccountLoaderCallbacks();
-        loadIfVisible();
-    }
-
-    @Override
-    protected void loadIfVisible() {
-        // Force reload fragment only in tablets when there is valid account and the orientation has changed.
-        Pair<String, Integer> actionOrientationPair;
-        if (canLoad() && HardwareUtils.isTablet() && (actionOrientationPair = getActionAndOrientationForFragmentInBackStack()) != null) {
-            if (actionOrientationPair.first.equals(Action.None.name()) && actionOrientationPair.second != GeckoScreenOrientation.getInstance().getAndroidOrientation()) {
-                // As the fragment becomes visible only after onStart callback, we can safely remove it from the back-stack.
-                // If a portrait fragment is in the back-stack and then a landscape fragment should be shown, there can
-                // be a brief flash as the fragment as replaced.
-                getChildFragmentManager()
-                        .beginTransaction()
-                        .addToBackStack(null)
-                        .remove(getChildFragmentManager().findFragmentByTag(FRAGMENT_TAG))
-                        .commitAllowingStateLoss();
-                getChildFragmentManager().executePendingTransactions();
-
-                load();
-                return;
-            }
-        }
-        super.loadIfVisible();
-    }
-
-    @Override
-    public void load() {
-        getLoaderManager().initLoader(LOADER_ID_ACCOUNT, null, mAccountLoaderCallbacks);
-    }
-
-    private void showSubPanel(Account account) {
-        final Action actionNeeded = getActionNeeded(account);
-        final String actionString = actionNeeded != null ? actionNeeded.name() : NO_ACCOUNT;
-        final int orientation = HardwareUtils.isTablet() ? GeckoScreenOrientation.getInstance().getAndroidOrientation()
-                : Configuration.ORIENTATION_UNDEFINED;
-
-        // Check if fragment for given action and orientation is in the back-stack.
-        final Pair<String, Integer> actionOrientationPair = getActionAndOrientationForFragmentInBackStack();
-        if (actionOrientationPair != null && actionOrientationPair.first.equals(actionString) && (actionOrientationPair.second == orientation)) {
-            return;
-        }
-
-        // Instantiate the fragment for the action and update the arguments.
-        Fragment subPanel = makeFragmentForAction(actionNeeded);
-        final Bundle args = new Bundle();
-        args.putBoolean(HomePager.CAN_LOAD_ARG, getCanLoadHint());
-        args.putString(FRAGMENT_ACTION, actionString);
-        args.putInt(FRAGMENT_ORIENTATION, orientation);
-        subPanel.setArguments(args);
-
-        // Add the fragment to the back-stack.
-        getChildFragmentManager()
-            .beginTransaction()
-            .addToBackStack(null)
-            .replace(R.id.remote_tabs_container, subPanel, FRAGMENT_TAG)
-            .commitAllowingStateLoss();
-    }
-
-    private Pair<String, Integer> getActionAndOrientationForFragmentInBackStack() {
-        final Fragment currentFragment = getChildFragmentManager().findFragmentByTag(FRAGMENT_TAG);
-        if (currentFragment != null && currentFragment.getArguments() != null) {
-            final String fragmentAction  = currentFragment.getArguments().getString(FRAGMENT_ACTION);
-            final int fragmentOrientation = currentFragment.getArguments().getInt(FRAGMENT_ORIENTATION);
-            return Pair.create(fragmentAction, fragmentOrientation);
-        }
-        return null;
-    }
-
-    /**
-     * Get whatever <code>Action</code> is required to continue healthy syncing
-     * of Remote Tabs.
-     * <p>
-     * A Firefox Account can be in many states, from healthy to requiring a
-     * Fennec upgrade to continue use. If we have a Firefox Account, but the
-     * state seems corrupt, the best we can do is ask for a password, which
-     * resets most of the Account state. The health of a Sync account is
-     * essentially opaque in this respect.
-     * <p>
-     * A null Account means there is no Account (Sync or Firefox) on the device.
-     *
-     * @param account
-     *            Android Account (Sync or Firefox); may be null.
-     */
-    private Action getActionNeeded(Account account) {
-        if (account == null) {
-            return null;
-        }
-
-        if (!FxAccountConstants.ACCOUNT_TYPE.equals(account.type)) {
-            Log.wtf(LOGTAG, "Non Sync, non Firefox Android Account returned by AccountLoader; returning null.");
-            return null;
-        }
-
-        final State state = FirefoxAccounts.getFirefoxAccountState(getActivity());
-        if (state == null) {
-            Log.wtf(LOGTAG, "Firefox Account with null state found; offering needs password.");
-            return Action.NeedsPassword;
-        }
-
-        final Action actionNeeded = state.getNeededAction();
-        if (actionNeeded == null) {
-            Log.wtf(LOGTAG, "Firefox Account with non-null state but null action needed; offering needs password.");
-            return Action.NeedsPassword;
-        }
-
-        return actionNeeded;
-    }
-
-    private Fragment makeFragmentForAction(Action action) {
-        if (action == null) {
-            // This corresponds to no Account: neither Sync nor Firefox.
-            return RemoteTabsStaticFragment.newInstance(R.layout.remote_tabs_setup);
-        }
-
-        switch (action) {
-        case None:
-            if (HardwareUtils.isTablet() && GeckoScreenOrientation.getInstance().getAndroidOrientation() == Configuration.ORIENTATION_LANDSCAPE) {
-                return new RemoteTabsSplitPlaneFragment();
-            } else {
-                return new RemoteTabsExpandableListFragment();
-            }
-        case NeedsVerification:
-            return RemoteTabsStaticFragment.newInstance(R.layout.remote_tabs_needs_verification);
-        case NeedsPassword:
-            return RemoteTabsStaticFragment.newInstance(R.layout.remote_tabs_needs_password);
-        case NeedsUpgrade:
-            return RemoteTabsStaticFragment.newInstance(R.layout.remote_tabs_needs_upgrade);
-        case NeedsFinishMigrating:
-            return RemoteTabsStaticFragment.newInstance(R.layout.remote_tabs_needs_finish_migrating);
-        default:
-            // This should never happen, but we're confident we have a Firefox
-            // Account at this point, so let's show the needs password screen.
-            // That's our best hope of righting the ship.
-            Log.wtf(LOGTAG, "Got unexpected action needed; offering needs password.");
-            return RemoteTabsStaticFragment.newInstance(R.layout.remote_tabs_needs_password);
-        }
-    }
-
-    /**
-     * Update the UI to reflect the given <code>Account</code> and its state.
-     * <p>
-     * A null Account means there is no Account (Sync or Firefox) on the device.
-     *
-     * @param account
-     *            Android Account (Sync or Firefox); may be null.
-     */
-    protected void updateUiFromAccount(Account account) {
-        if (getView() == null) {
-            // Early abort. When the fragment is detached, we get a loader
-            // reset, which calls this with a null account parameter. A null
-            // account is valid (it means there is no account, either Sync or
-            // Firefox), and so we start to offer the setup flow. But this all
-            // happens after the view has been destroyed, which means inserting
-            // the setup flow fails. In this case, just abort.
-            return;
-        }
-        showSubPanel(account);
-    }
-
-    private class AccountLoaderCallbacks implements LoaderCallbacks<Account> {
-        @Override
-        public Loader<Account> onCreateLoader(int id, Bundle args) {
-            return new AccountLoader(getActivity());
-        }
-
-        @Override
-        public void onLoadFinished(Loader<Account> loader, Account account) {
-            updateUiFromAccount(account);
-        }
-
-        @Override
-        public void onLoaderReset(Loader<Account> loader) {
-            updateUiFromAccount(null);
-        }
-    }
-}
deleted file mode 100644
--- a/mobile/android/base/java/org/mozilla/gecko/home/RemoteTabsSplitPlaneFragment.java
+++ /dev/null
@@ -1,408 +0,0 @@
-package org.mozilla.gecko.home;
-
-import android.content.Context;
-import android.database.Cursor;
-import android.database.DataSetObserver;
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.MotionEvent;
-import android.view.View;
-import android.view.ViewGroup;
-import android.view.ViewStub;
-import android.widget.AbsListView;
-import android.widget.AdapterView;
-import android.widget.ArrayAdapter;
-import android.widget.ImageView;
-import android.widget.ListAdapter;
-import android.widget.TextView;
-
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.RemoteClientsDialogFragment;
-import org.mozilla.gecko.RemoteTabsExpandableListAdapter;
-import org.mozilla.gecko.RemoteTabsExpandableListAdapter.GroupViewHolder;
-import org.mozilla.gecko.Telemetry;
-import org.mozilla.gecko.TelemetryContract;
-import org.mozilla.gecko.db.RemoteClient;
-import org.mozilla.gecko.db.RemoteTab;
-
-import java.util.ArrayList;
-import java.util.EnumSet;
-import java.util.List;
-
-/**
- * Fragment that displays other devices and tabs from them in two separate <code>ListView<code> instances.
- * <p/>
- * This is intended to be used in landscape mode on tablets.
- */
-public class RemoteTabsSplitPlaneFragment extends RemoteTabsBaseFragment {
-    // Logging tag name.
-    private static final String LOGTAG = "GeckoSplitPlaneFragment";
-
-    private ArrayAdapter<RemoteTab> mTabsAdapter;
-    private ArrayAdapter<RemoteClient> mClientsAdapter;
-
-    // DataSetObserver for the expandable list adapter.
-    private DataSetObserver mObserver;
-
-    // The views shown by the fragment.
-    private HomeListView mClientList;
-    private HomeListView mTabList;
-
-    public static RemoteTabsSplitPlaneFragment newInstance() {
-        return new RemoteTabsSplitPlaneFragment();
-    }
-
-    public RemoteTabsSplitPlaneFragment() {
-        super();
-    }
-
-    @Override
-    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
-        return inflater.inflate(R.layout.home_remote_tabs_split_plane_panel, container, false);
-    }
-
-    @Override
-    public void onViewCreated(View view, Bundle savedInstanceState) {
-        super.onViewCreated(view, savedInstanceState);
-
-        mClientList = (HomeListView) view.findViewById(R.id.clients_list);
-        mTabList = (HomeListView) view.findViewById(R.id.tabs_list);
-
-        mClientList.setTag(HomePager.LIST_TAG_REMOTE_TABS);
-
-        mTabList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
-            @Override
-            public void onItemClick(AdapterView<?> adapter, View view, int position, long id) {
-                final RemoteTab tab = (RemoteTab) adapter.getItemAtPosition(position);
-                if (tab == null) {
-                    return;
-                }
-
-                Telemetry.sendUIEvent(TelemetryContract.Event.LOAD_URL, TelemetryContract.Method.LIST_ITEM, "remote_tabs");
-
-                // This item is a TwoLinePageRow, so we allow switch-to-tab.
-                mUrlOpenListener.onUrlOpen(tab.url, EnumSet.of(HomePager.OnUrlOpenListener.Flags.ALLOW_SWITCH_TO_TAB));
-            }
-        });
-
-        mClientList.setOnItemClickListener(new AdapterView.OnItemClickListener() {
-            @Override
-            public void onItemClick(AdapterView<?> adapter, View view, int position, long id) {
-                final RemoteClient client = (RemoteClient) adapter.getItemAtPosition(position);
-                if (client != null) {
-                    sState.setClientAsSelected(client.guid);
-                    mTabsAdapter.clear();
-                    for (RemoteTab tab : client.tabs) {
-                        mTabsAdapter.add(tab);
-                    }
-
-                    // Notify data has changed for both clients and tabs adapter.
-                    // This will update selected client item background and the tabs list.
-                    mClientsAdapter.notifyDataSetChanged();
-                    mTabsAdapter.notifyDataSetChanged();
-                }
-            }
-        });
-
-        mTabList.setContextMenuInfoFactory(new HomeContextMenuInfo.ListFactory() {
-            @Override
-            public HomeContextMenuInfo makeInfoForCursor(View view, int position, long id, Cursor cursor) {
-                return null;
-            }
-
-            @Override
-            public HomeContextMenuInfo makeInfoForAdapter(View view, int position, long id, ListAdapter adapter) {
-                final RemoteTab tab = (RemoteTab) adapter.getItem(position);
-                final HomeContextMenuInfo info = new HomeContextMenuInfo(view, position, id);
-                info.url = tab.url;
-                info.title = tab.title;
-                return info;
-            }
-        });
-
-        mClientList.setContextMenuInfoFactory(new HomeContextMenuInfo.ListFactory() {
-            @Override
-            public HomeContextMenuInfo makeInfoForCursor(View view, int position, long id, Cursor cursor) {
-                return null;
-            }
-
-            @Override
-            public HomeContextMenuInfo makeInfoForAdapter(View view, int position, long id, ListAdapter adapter) {
-                final RemoteClient client = (RemoteClient) adapter.getItem(position);
-                return new RemoteTabsClientContextMenuInfo(view, position, id, client);
-            }
-        });
-
-        registerForContextMenu(mClientList);
-        registerForContextMenu(mTabList);
-    }
-
-    @Override
-    public void onDestroyView() {
-        super.onDestroyView();
-
-        // Discard any additional item clicks on the list as the
-        // panel is getting destroyed (bug 1210243).
-        mClientList.setOnItemClickListener(null);
-        mTabList.setOnItemClickListener(null);
-
-        mClientList = null;
-        mTabList = null;
-        mEmptyView = null;
-        mAdapter.unregisterDataSetObserver(mObserver);
-        mObserver = null;
-    }
-
-    @Override
-    public void onActivityCreated(Bundle savedInstanceState) {
-        super.onActivityCreated(savedInstanceState);
-
-        // There is an unfortunate interaction between ListViews and
-        // footer onClick handling. The footer view itself appears to not
-        // receive click events. Its children, however, do receive click events.
-        // Therefore, we attach an onClick handler to a child of the footer view
-        // itself.
-        mFooterView = LayoutInflater.from(getActivity()).inflate(R.layout.home_remote_tabs_hidden_devices_footer, mClientList, false);
-        final View view = mFooterView.findViewById(R.id.hidden_devices);
-        view.setClickable(true);
-        view.setOnClickListener(new View.OnClickListener() {
-            @Override
-            public void onClick(View v) {
-                final RemoteClientsDialogFragment dialog = RemoteClientsDialogFragment.newInstance(
-                        getResources().getString(R.string.home_remote_tabs_hidden_devices_title),
-                        getResources().getString(R.string.home_remote_tabs_unhide_selected_devices),
-                        RemoteClientsDialogFragment.ChoiceMode.MULTIPLE, new ArrayList<>(mHiddenClients));
-                dialog.setTargetFragment(RemoteTabsSplitPlaneFragment.this, 0);
-                dialog.show(getActivity().getSupportFragmentManager(), DIALOG_TAG_REMOTE_TABS);
-            }
-        });
-
-        // There is a delicate interaction, pre-KitKat, between
-        // {add,remove}FooterView and setAdapter. setAdapter wraps the adapter
-        // in a footer/header-managing adapter, which only happens (pre-KitKat)
-        // if a footer/header is present. Therefore, we add our footer before
-        // setting the adapter; and then we remove it afterward. From there on,
-        // we can add/remove it at will.
-        mClientList.addFooterView(mFooterView, null, true);
-
-        // Initialize adapter
-        mAdapter = new RemoteTabsExpandableListAdapter(R.layout.home_remote_tabs_group, R.layout.home_remote_tabs_child, null, false);
-
-        mTabsAdapter = new RemoteTabsAdapter(getActivity(), R.layout.home_remote_tabs_child);
-        mClientsAdapter = new RemoteClientAdapter(getActivity(), R.layout.home_remote_tabs_group, mAdapter);
-
-        // ArrayAdapter.addAll() is supported only from API 11. We avoid redundant notifications while each item is added to the adapter here.
-        // ArrayAdapter notifyDataSetChanged should be called after all add operations manually.
-        mTabsAdapter.setNotifyOnChange(false);
-        mClientsAdapter.setNotifyOnChange(false);
-
-        mTabList.setAdapter(mTabsAdapter);
-        mClientList.setAdapter(mClientsAdapter);
-
-        mObserver = new RemoteTabDataSetObserver();
-        mAdapter.registerDataSetObserver(mObserver);
-
-        // Now the adapter is wrapped; we can remove our footer view.
-        mClientList.removeFooterView(mFooterView);
-
-        // Register touch handler to conditionally enable swipe refresh layout.
-        mClientList.setOnTouchListener(new ListTouchListener(mClientList));
-        mTabList.setOnTouchListener(new ListTouchListener(mTabList));
-
-        // Create callbacks before the initial loader is started
-        mCursorLoaderCallbacks = new CursorLoaderCallbacks();
-        loadIfVisible();
-    }
-
-    @Override
-    protected void updateUiFromClients(List<RemoteClient> clients, List<RemoteClient> hiddenClients) {
-        if (getView() == null) {
-            // Early abort. It is possible to get UI updates after the view is
-            // destroyed; this can happen due to asynchronous loaders or
-            // animations complete.
-            return;
-        }
-
-        // We have three states: no clients (including hidden clients) at all;
-        // all clients hidden; some clients hidden. We want to show the empty
-        // list view only when we have no clients at all. This flag
-        // differentiates the first from the latter two states.
-        boolean displayedSomeClients = false;
-
-        if (hiddenClients == null || hiddenClients.isEmpty()) {
-            mClientList.removeFooterView(mFooterView);
-        } else {
-            displayedSomeClients = true;
-
-            final TextView textView = (TextView) mFooterView.findViewById(R.id.hidden_devices);
-            if (hiddenClients.size() == 1) {
-                textView.setText(getResources().getString(R.string.home_remote_tabs_one_hidden_device));
-            } else {
-                textView.setText(getResources().getString(R.string.home_remote_tabs_many_hidden_devices, hiddenClients.size()));
-            }
-
-            // This is a simple, if not very future-proof, way to determine if
-            // the footer view has already been added to the list view.
-            if (mClientList.getFooterViewsCount() < 1) {
-                mClientList.addFooterView(mFooterView);
-            }
-        }
-
-        if (clients != null && !clients.isEmpty()) {
-            displayedSomeClients = true;
-        }
-
-        if (displayedSomeClients) {
-            return;
-        }
-
-        // No clients shown, not even hidden clients. Set the empty view if it
-        // hasn't been set already.
-        if (mEmptyView == null) {
-            // Set empty panel view. We delay this so that the empty view won't flash.
-            final ViewStub emptyViewStub = (ViewStub) getView().findViewById(R.id.home_empty_view_stub);
-            mEmptyView = emptyViewStub.inflate();
-
-            final ImageView emptyIcon = (ImageView) mEmptyView.findViewById(R.id.home_empty_image);
-            emptyIcon.setImageResource(R.drawable.icon_remote_tabs_empty);
-
-            final TextView emptyText = (TextView) mEmptyView.findViewById(R.id.home_empty_text);
-            emptyText.setText(R.string.home_remote_tabs_empty);
-
-            mClientList.setEmptyView(mEmptyView);
-        }
-    }
-
-    private class RemoteTabDataSetObserver extends DataSetObserver {
-        @Override
-        public void onChanged() {
-            super.onChanged();
-            mClientsAdapter.clear();
-            mTabsAdapter.clear();
-
-            RemoteClient selectedClient = null;
-            for (int i = 0; i < mAdapter.getGroupCount(); i++) {
-                final RemoteClient client = (RemoteClient) mAdapter.getGroup(i);
-                mClientsAdapter.add(client);
-
-                if (i == 0) {
-                    // Fallback to most recent client when selected client guid not found.
-                    selectedClient = client;
-                }
-
-                if (client.guid.equals(sState.selectedClient)) {
-                    selectedClient = client;
-                }
-            }
-
-            final List<RemoteTab> visibleTabs = (selectedClient != null) ? selectedClient.tabs : new ArrayList<RemoteTab>();
-            for (RemoteTab tab : visibleTabs) {
-                mTabsAdapter.add(tab);
-            }
-
-            // Update the selected client and notify data has changed both the list views.
-            sState.setClientAsSelected(selectedClient != null ? selectedClient.guid : null);
-            mTabsAdapter.notifyDataSetChanged();
-            mClientsAdapter.notifyDataSetChanged();
-        }
-
-        @Override
-        public void onInvalidated() {
-            super.onInvalidated();
-            mClientsAdapter.clear();
-            mTabsAdapter.clear();
-            mTabsAdapter.notifyDataSetChanged();
-            mClientsAdapter.notifyDataSetChanged();
-        }
-    }
-
-    private static class RemoteTabsAdapter extends ArrayAdapter<RemoteTab> {
-        private final Context context;
-        private final int resource;
-
-        public RemoteTabsAdapter(Context context, int resource) {
-            super(context, resource);
-            this.context = context;
-            this.resource = resource;
-        }
-
-        @Override
-        public View getView(int position, View convertView, ViewGroup parent) {
-            final TwoLinePageRow view;
-            if (convertView != null) {
-                view = (TwoLinePageRow) convertView;
-            } else {
-                final LayoutInflater inflater = LayoutInflater.from(context);
-                view = (TwoLinePageRow) inflater.inflate(resource, parent, false);
-            }
-
-            final RemoteTab tab = getItem(position);
-            view.update(tab.title, tab.url);
-
-            return view;
-        }
-    }
-
-    private class RemoteClientAdapter extends ArrayAdapter<RemoteClient> {
-        private final Context context;
-        private final int resource;
-        private final RemoteTabsExpandableListAdapter adapter;
-
-        public RemoteClientAdapter(Context context, int resource, RemoteTabsExpandableListAdapter adapter) {
-            super(context, resource);
-            this.context = context;
-            this.resource = resource;
-            this.adapter = adapter;
-        }
-
-        @Override
-        public View getView(int position, View convertView, ViewGroup parent) {
-            final View view;
-            if (convertView != null) {
-                view = convertView;
-            } else {
-                final LayoutInflater inflater = LayoutInflater.from(context);
-                view = inflater.inflate(resource, parent, false);
-                final GroupViewHolder holder = new GroupViewHolder(view);
-                view.setTag(holder);
-            }
-
-            // Update the background based on the state of the selected client.
-            final RemoteClient client = getItem(position);
-            final boolean isSelected = client.guid.equals(sState.selectedClient);
-            adapter.updateClientsItemView(isSelected, context, view, getItem(position));
-            return view;
-        }
-    }
-
-    /**
-     * OnTouchListener implementation for ListView that enables swipe to refresh on the touch down event iff list cannot scroll up.
-     * This implementation does not consume the <code>MotionEvent</code>.
-     */
-    private class ListTouchListener implements View.OnTouchListener {
-        private final AbsListView listView;
-
-        public ListTouchListener(AbsListView listView) {
-            this.listView = listView;
-        }
-
-        @Override
-        public boolean onTouch(View v, MotionEvent event) {
-            final int action = event.getAction();
-            switch (action) {
-                case MotionEvent.ACTION_DOWN:
-                    // Enable swipe to refresh iff the first item is visible and is at the top.
-                    mRefreshLayout.setEnabled(listView.getCount() <= 0
-                            || (listView.getFirstVisiblePosition() <= 0 && listView.getChildAt(0).getTop() >= 0));
-                    break;
-                case MotionEvent.ACTION_CANCEL:
-                case MotionEvent.ACTION_UP:
-                    mRefreshLayout.setEnabled(true);
-                    break;
-            }
-
-            // Event is not handled here, it will be consumed in enclosing SwipeRefreshLayout.
-            return false;
-        }
-    }
-}
deleted file mode 100644
--- a/mobile/android/base/java/org/mozilla/gecko/home/RemoteTabsStaticFragment.java
+++ /dev/null
@@ -1,124 +0,0 @@
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-package org.mozilla.gecko.home;
-
-import android.content.Intent;
-import android.os.Bundle;
-import android.view.LayoutInflater;
-import android.view.View;
-import android.view.View.OnClickListener;
-import android.view.ViewGroup;
-import org.mozilla.gecko.R;
-import org.mozilla.gecko.fxa.FxAccountConstants;
-import org.mozilla.gecko.home.HomePager.OnUrlOpenListener;
-
-import java.util.EnumSet;
-
-/**
- * A <code>HomeFragment</code> which displays one of a small set of static views
- * in response to different Firefox Account states. When the Firefox Account is
- * healthy and syncing normally, these views should not be shown.
- * <p>
- * This class exists to handle view-specific actions when buttons and links
- * shown by the different static views are clicked. For example, a static view
- * offers to set up a Firefox Account to a user who has no account (Firefox or
- * Sync) on their device.
- * <p>
- * This could be a vanilla <code>Fragment</code>, except it needs to open URLs.
- * To do so, it expects its containing <code>Activity</code> to implement
- * <code>OnUrlOpenListener<code>; to suggest this invariant at compile time, we
- * inherit from <code>HomeFragment</code>.
- */
-public class RemoteTabsStaticFragment extends HomeFragment implements OnClickListener {
-    @SuppressWarnings("unused")
-    private static final String LOGTAG = "GeckoRemoteTabsStatic";
-
-    protected static final String RESOURCE_ID = "resource_id";
-    protected static final int DEFAULT_RESOURCE_ID = R.layout.remote_tabs_setup;
-
-    private static final String CONFIRM_ACCOUNT_SUPPORT_URL =
-            "https://support.mozilla.org/kb/im-having-problems-confirming-my-firefox-account";
-
-    protected int mLayoutId;
-
-    public static RemoteTabsStaticFragment newInstance(int resourceId) {
-        final RemoteTabsStaticFragment fragment = new RemoteTabsStaticFragment();
-
-        final Bundle args = new Bundle();
-        args.putInt(RESOURCE_ID, resourceId);
-        fragment.setArguments(args);
-
-        return fragment;
-    }
-
-    @Override
-    public void onCreate(Bundle savedInstanceState) {
-        super.onCreate(savedInstanceState);
-
-        final Bundle args = getArguments();
-        if (args != null) {
-            mLayoutId = args.getInt(RESOURCE_ID, DEFAULT_RESOURCE_ID);
-        } else {
-            mLayoutId = DEFAULT_RESOURCE_ID;
-        }
-    }
-
-    @Override
-    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
-        return inflater.inflate(mLayoutId, container, false);
-    }
-
-    protected boolean maybeSetOnClickListener(View view, int resourceId) {
-        final View button = view.findViewById(resourceId);
-        if (button != null) {
-            button.setOnClickListener(this);
-            return true;
-        }
-        return false;
-    }
-
-    @Override
-    public void onViewCreated(View view, Bundle savedInstanceState) {
-        for (int resourceId : new int[] {
-                R.id.remote_tabs_setup_get_started,
-                R.id.remote_tabs_needs_verification_resend_email,
-                R.id.remote_tabs_needs_verification_help,
-                R.id.remote_tabs_needs_password_sign_in,
-                R.id.remote_tabs_needs_finish_migrating_sign_in, }) {
-            maybeSetOnClickListener(view, resourceId);
-        }
-    }
-
-    @Override
-    public void onClick(final View v) {
-        final int id = v.getId();
-        if (id == R.id.remote_tabs_setup_get_started) {
-            // This Activity will redirect to the correct Activity as needed.
-            final Intent intent = new Intent(FxAccountConstants.ACTION_FXA_GET_STARTED);
-            startActivity(intent);
-        } else if (id == R.id.remote_tabs_needs_verification_resend_email) {
-            final Intent intent = new Intent(FxAccountConstants.ACTION_FXA_CONFIRM_ACCOUNT);
-            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-            startActivity(intent);
-        } else if (id == R.id.remote_tabs_needs_verification_help) {
-            // Don't allow switch-to-tab.
-            final EnumSet<OnUrlOpenListener.Flags> flags = EnumSet.noneOf(OnUrlOpenListener.Flags.class);
-            mUrlOpenListener.onUrlOpen(CONFIRM_ACCOUNT_SUPPORT_URL, flags);
-        } else if (id == R.id.remote_tabs_needs_password_sign_in) {
-            final Intent intent = new Intent(FxAccountConstants.ACTION_FXA_UPDATE_CREDENTIALS);
-            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-            startActivity(intent);
-        } else if (id == R.id.remote_tabs_needs_finish_migrating_sign_in) {
-            final Intent intent = new Intent(FxAccountConstants.ACTION_FXA_FINISH_MIGRATING);
-            intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
-            startActivity(intent);
-        }
-    }
-
-    @Override
-    protected void load() {
-        // We're static, so nothing to do here!
-    }
-}
--- a/mobile/android/base/java/org/mozilla/gecko/preferences/PanelsPreferenceCategory.java
+++ b/mobile/android/base/java/org/mozilla/gecko/preferences/PanelsPreferenceCategory.java
@@ -158,16 +158,18 @@ public class PanelsPreferenceCategory ex
 
         final String id = pref.getKey();
 
         final String defaultPanelId = mConfigEditor.getDefaultPanelId();
         if (defaultPanelId != null && defaultPanelId.equals(id)) {
             return;
         }
 
+        updateVisibilityPrefsForPanel(id, true);
+
         mConfigEditor.setDefault(id);
         mConfigEditor.apply();
 
         Telemetry.sendUIEvent(TelemetryContract.Event.PANEL_SET_DEFAULT, Method.DIALOG, id);
     }
 
     @Override
     protected void onPrepareForRemoval() {
@@ -227,29 +229,33 @@ public class PanelsPreferenceCategory ex
         mConfigEditor.apply();
 
         if (toHide) {
             Telemetry.sendUIEvent(TelemetryContract.Event.PANEL_HIDE, Method.DIALOG, id);
         } else {
             Telemetry.sendUIEvent(TelemetryContract.Event.PANEL_SHOW, Method.DIALOG, id);
         }
 
-        if (HomeConfig.getIdForBuiltinPanelType(HomeConfig.PanelType.BOOKMARKS).equals(id)) {
-            GeckoSharedPrefs.forProfile(getContext()).edit().putBoolean(HomeConfig.PREF_KEY_BOOKMARKS_PANEL_ENABLED, !toHide).apply();
-        }
-
-        if (HomeConfig.getIdForBuiltinPanelType(HomeConfig.PanelType.HISTORY).equals(id)) {
-            GeckoSharedPrefs.forProfile(getContext()).edit().putBoolean(HomeConfig.PREF_KEY_HISTORY_PANEL_ENABLED, !toHide).apply();
-        }
+        updateVisibilityPrefsForPanel(id, !toHide);
 
         pref.setHidden(toHide);
         setDefaultFromConfig();
     }
 
     /**
      * When the default panel is removed or disabled, find an enabled panel
      * if possible and set it as mDefaultReference.
      */
     @Override
     protected void setFallbackDefault() {
         setDefaultFromConfig();
     }
+
+    private void updateVisibilityPrefsForPanel(String panelId, boolean toShow) {
+        if (HomeConfig.getIdForBuiltinPanelType(HomeConfig.PanelType.BOOKMARKS).equals(panelId)) {
+            GeckoSharedPrefs.forProfile(getContext()).edit().putBoolean(HomeConfig.PREF_KEY_BOOKMARKS_PANEL_ENABLED, toShow).apply();
+        }
+
+        if (HomeConfig.getIdForBuiltinPanelType(HomeConfig.PanelType.COMBINED_HISTORY).equals(panelId)) {
+            GeckoSharedPrefs.forProfile(getContext()).edit().putBoolean(HomeConfig.PREF_KEY_HISTORY_PANEL_ENABLED, toShow).apply();
+        }
+    }
 }
--- a/mobile/android/base/locales/en-US/android_strings.dtd
+++ b/mobile/android/base/locales/en-US/android_strings.dtd
@@ -540,39 +540,28 @@ size. -->
 <!ENTITY home_clear_history_button "Clear browsing history">
 <!ENTITY home_clear_history_confirm "Are you sure you want to clear your history?">
 <!ENTITY home_bookmarks_empty "Bookmarks you save show up here.">
 <!ENTITY home_closed_tabs_title "Recently closed tabs">
 <!ENTITY home_last_tabs_title "Tabs from last time">
 <!ENTITY home_last_tabs_empty "Your recent tabs show up here.">
 <!ENTITY home_open_all "Open all">
 <!ENTITY home_most_recent_empty "Websites you visited most recently show up here.">
-<!ENTITY home_selected_empty "Websites you visited in the selected timeframe show up here.">
 <!-- Localization note (home_most_recent_emptyhint2): "Psst" is a sound that might be used to attract someone's attention unobtrusively, and intended to hint at Private Browsing to the user.
      The placeholders &formatS1; and &formatS2; are used to mark the location of text underlining. -->
 <!ENTITY home_most_recent_emptyhint2 "Psst: using a &formatS1;New Private Tab&formatS2; won\'t save your history.">
 
 <!-- Localization note (home_default_empty): This string is used as the default text when there
      is no data to show in an about:home panel that was created by an add-on. -->
 <!ENTITY home_default_empty "No content could be found for this panel.">
 
 <!-- Localization note (home_back_up_to_filter): The variable is replaced by the name of the
      previous location in the navigation, such as the previous folder -->
 <!ENTITY home_move_back_to_filter "Back to &formatS;">
 
-<!ENTITY home_remote_tabs_title "Synced Tabs">
-<!ENTITY home_remote_tabs_empty "Your tabs from other devices show up here.">
-<!ENTITY home_remote_tabs_unable_to_connect "Unable to connect">
-<!ENTITY home_remote_tabs_need_to_sign_in "Please sign in to reconnect your Firefox Account and continue syncing.">
-<!ENTITY home_remote_tabs_need_to_finish_migrating "Your new Firefox Account is ready!">
-
-<!ENTITY home_remote_tabs_trouble_verifying "Trouble verifying your account?">
-<!ENTITY home_remote_tabs_need_to_verify "Please verify your Firefox Account to start syncing.">
-
-<!ENTITY home_remote_tabs_one_hidden_device "1 device hidden">
 <!-- Localization note (home_remote_tabs_many_hidden_devices) : The
      formatD is replaced with the number of hidden devices.  The
      number of hidden devices is always more than one.  We can't use
      Android plural forms, sadly. See Bug #753859. -->
 <!ENTITY home_remote_tabs_many_hidden_devices "&formatD; devices hidden">
 <!-- Localization note (home_remote_tabs_hidden_devices_title) : This is the
      title of a dialog; we expect more than one device. -->
 <!ENTITY home_remote_tabs_hidden_devices_title "Hidden devices">
--- a/mobile/android/base/locales/en-US/sync_strings.dtd
+++ b/mobile/android/base/locales/en-US/sync_strings.dtd
@@ -46,24 +46,16 @@
      Firefox. -->
 <!ENTITY fxaccount_back_to_browsing 'Back to browsing'>
 
 <!ENTITY fxaccount_getting_started_welcome_to_sync 'Welcome to &syncBrand.shortName.label;'>
 <!ENTITY fxaccount_getting_started_description2 'Sign in to sync your tabs, bookmarks, logins &amp; more.'>
 <!ENTITY fxaccount_getting_started_get_started 'Get started'>
 <!ENTITY fxaccount_getting_started_old_firefox 'Using an older version of &syncBrand.shortName.label;?'>
 
-<!ENTITY fxaccount_confirm_account_header 'Confirm your account'>
-<!ENTITY fxaccount_confirm_account_resend_email 'Resend email'>
-
-<!ENTITY fxaccount_sign_in_button_label 'Sign in'>
-
-<!ENTITY fxaccount_finish_migrating_header 'Sign in to finish upgrading'>
-<!ENTITY fxaccount_finish_migrating_button_label 'Finish upgrading'>
-
 <!ENTITY fxaccount_status_signed_in_as 'Signed in as'>
 <!ENTITY fxaccount_status_manage_account 'Manage account'>
 <!ENTITY fxaccount_status_auth_server 'Account server'>
 <!ENTITY fxaccount_status_sync_now 'Sync now'>
 <!ENTITY fxaccount_status_syncing2 'Syncing…'>
 <!ENTITY fxaccount_status_device_name 'Device name'>
 <!ENTITY fxaccount_status_sync_server 'Sync server'>
 <!ENTITY fxaccount_status_sync '&syncBrand.shortName.label;'>
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -384,19 +384,16 @@ gbjar.sources += ['java/org/mozilla/geck
     'home/BookmarksPanel.java',
     'home/BrowserSearch.java',
     'home/CombinedHistoryAdapter.java',
     'home/CombinedHistoryItem.java',
     'home/CombinedHistoryPanel.java',
     'home/CombinedHistoryRecyclerView.java',
     'home/DynamicPanel.java',
     'home/FramePanelLayout.java',
-    'home/HistoryHeaderListCursorAdapter.java',
-    'home/HistoryItemAdapter.java',
-    'home/HistoryPanel.java',
     'home/HistorySectionsHelper.java',
     'home/HomeAdapter.java',
     'home/HomeBanner.java',
     'home/HomeConfig.java',
     'home/HomeConfigLoader.java',
     'home/HomeConfigPrefsBackend.java',
     'home/HomeContextMenuInfo.java',
     'home/HomeExpandableListView.java',
@@ -417,22 +414,17 @@ gbjar.sources += ['java/org/mozilla/geck
     'home/PanelRecyclerView.java',
     'home/PanelRecyclerViewAdapter.java',
     'home/PanelRefreshLayout.java',
     'home/PanelViewAdapter.java',
     'home/PanelViewItemHandler.java',
     'home/PinSiteDialog.java',
     'home/ReadingListPanel.java',
     'home/RecentTabsPanel.java',
-    'home/RemoteTabsBaseFragment.java',
-    'home/RemoteTabsExpandableListFragment.java',
     'home/RemoteTabsExpandableListState.java',
-    'home/RemoteTabsPanel.java',
-    'home/RemoteTabsSplitPlaneFragment.java',
-    'home/RemoteTabsStaticFragment.java',
     'home/SearchEngine.java',
     'home/SearchEngineAdapter.java',
     'home/SearchEngineBar.java',
     'home/SearchEngineRow.java',
     'home/SearchLoader.java',
     'home/SimpleCursorLoader.java',
     'home/SpacingDecoration.java',
     'home/TabMenuStrip.java',
deleted file mode 100644
--- a/mobile/android/base/resources/layout-sw600dp-land/home_history_panel.xml
+++ /dev/null
@@ -1,46 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-              android:layout_width="match_parent"
-              android:layout_height="match_parent"
-              android:orientation="vertical">
-
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="0dp"
-        android:layout_weight="1"
-        android:orientation="horizontal">
-
-        <org.mozilla.gecko.home.HomeListView
-            android:id="@+id/range_list"
-            style="@style/Widget.HistoryListView"
-            android:layout_width="0dp"
-            android:layout_height="match_parent"
-            android:layout_weight="@dimen/split_plane_left_pane_weight"/>
-
-        <View
-            android:layout_width="1dp"
-            android:layout_height="match_parent"
-            android:background="@color/toolbar_divider_grey"/>
-
-        <ViewStub android:id="@id/home_empty_view_stub"
-                  android:layout="@layout/home_empty_panel"
-                  android:layout_height="match_parent"
-                  android:layout_width="0dp"
-                  android:layout_weight="@dimen/split_plane_right_pane_weight"/>
-
-        <org.mozilla.gecko.home.HomeListView
-            android:id="@+id/list"
-            style="@style/Widget.HistoryListView"
-            android:layout_width="0dp"
-            android:layout_height="match_parent"
-            android:layout_weight="@dimen/split_plane_right_pane_weight"/>
-
-    </LinearLayout>
-
-    <include layout="@layout/home_history_clear_button"/>
-
-</LinearLayout>
--- a/mobile/android/base/resources/layout/home_combined_history_panel.xml
+++ b/mobile/android/base/resources/layout/home_combined_history_panel.xml
@@ -8,16 +8,21 @@
               android:layout_height="match_parent"
               android:orientation="vertical">
 
     <ViewStub android:id="@+id/home_empty_view_stub"
               android:layout="@layout/home_empty_panel"
               android:layout_width="match_parent"
               android:layout_height="match_parent"/>
 
+    <ViewStub android:id="@+id/home_sync_empty_view_stub"
+              android:layout="@layout/remote_tabs_setup"
+              android:layout_width="match_parent"
+              android:layout_height="match_parent"/>
+
     <org.mozilla.gecko.home.CombinedHistoryRecyclerView
             android:id="@+id/combined_recycler_view"
             android:layout_width="match_parent"
             android:layout_height="0dp"
             android:layout_weight="1"/>
 
     <include layout="@layout/home_history_clear_button"/>
 
deleted file mode 100644
--- a/mobile/android/base/resources/layout/home_history_panel.xml
+++ /dev/null
@@ -1,15 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-              android:layout_width="match_parent"
-              android:layout_height="match_parent"
-              android:orientation="vertical">
-
-    <include layout="@layout/home_list"/>
-
-    <include layout="@layout/home_history_clear_button"/>
-
-</LinearLayout>
deleted file mode 100644
--- a/mobile/android/base/resources/layout/home_history_range_item.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
-             android:background="@color/about_page_header_grey"
-             android:layout_width="match_parent"
-             android:layout_height="@dimen/home_header_item_height">
-
-    <TextView android:id="@+id/range_title"
-              style="@style/Widget.TwoLinePageRow.Title"
-              android:paddingLeft="15dp"
-              android:paddingRight="15dp"
-              android:layout_gravity="center_vertical"
-              android:layout_width="match_parent"
-              android:layout_height="wrap_content"/>
-</FrameLayout>
deleted file mode 100644
--- a/mobile/android/base/resources/layout/home_remote_tabs_child.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<org.mozilla.gecko.home.TwoLinePageRow xmlns:android="http://schemas.android.com/apk/res/android"
-                                       style="@style/Widget.RemoteTabsItemView"
-                                       android:layout_width="match_parent"
-                                       android:layout_height="@dimen/page_row_height"
-                                       android:minHeight="@dimen/page_row_height"/>
deleted file mode 100644
--- a/mobile/android/base/resources/layout/home_remote_tabs_hidden_devices_footer.xml
+++ /dev/null
@@ -1,28 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-   This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/.
--->
-
-<!-- This layout is actually necessary because of an interaction
-     between ExpandableListView and onClick handling.  We need a child
-     to attach a click listener to. -->
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-    xmlns:gecko="http://schemas.android.com/apk/res-auto"
-    style="@style/Widget.RemoteTabsClientView"
-    android:layout_width="match_parent"
-    android:layout_height="@dimen/home_remote_tabs_hidden_footer_height"
-    android:gravity="center_vertical" >
-
-    <TextView
-        android:id="@+id/hidden_devices"
-        style="@style/Widget.Home.ActionItem"
-        android:background="@drawable/action_bar_button"
-        android:layout_width="match_parent"
-        android:layout_height="match_parent"
-        android:gravity="center"
-        android:maxLength="1024"
-        android:textColor="@color/tabs_tray_icon_grey" />
-
-</LinearLayout>
deleted file mode 100644
--- a/mobile/android/base/resources/layout/home_remote_tabs_list_panel.xml
+++ /dev/null
@@ -1,30 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-              android:layout_width="match_parent"
-              android:layout_height="match_parent"
-              android:orientation="vertical">
-
-    <ViewStub android:id="@+id/home_empty_view_stub"
-              android:layout="@layout/home_empty_panel"
-              android:layout_width="match_parent"
-              android:layout_height="match_parent"/>
-
-    <android.support.v4.widget.SwipeRefreshLayout
-                 android:id="@+id/remote_tabs_refresh_layout"
-                 android:layout_width="match_parent"
-                 android:layout_height="match_parent">
-
-        <org.mozilla.gecko.home.HomeExpandableListView
-                android:id="@+id/list"
-                style="@style/Widget.RemoteTabsListView"
-                android:groupIndicator="@android:color/transparent"
-                android:layout_width="match_parent"
-                android:layout_height="match_parent" />
-
-    </android.support.v4.widget.SwipeRefreshLayout>
-
-</LinearLayout>
deleted file mode 100644
--- a/mobile/android/base/resources/layout/home_remote_tabs_panel.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-              android:layout_width="match_parent"
-              android:layout_height="match_parent"
-              android:orientation="vertical">
-
-    <FrameLayout android:id="@+id/remote_tabs_container"
-                 android:layout_width="match_parent"
-                 android:layout_height="0dp"
-                 android:layout_weight="1" />
-
-</LinearLayout>
deleted file mode 100644
--- a/mobile/android/base/resources/layout/home_remote_tabs_split_plane_panel.xml
+++ /dev/null
@@ -1,48 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!-- This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
-
-<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
-              android:layout_width="match_parent"
-              android:layout_height="match_parent"
-              android:orientation="vertical">
-
-    <ViewStub android:id="@id/home_empty_view_stub"
-              android:layout="@layout/home_empty_panel"
-              android:layout_width="match_parent"
-              android:layout_height="match_parent"/>
-
-    <android.support.v4.widget.SwipeRefreshLayout
-            android:id="@id/remote_tabs_refresh_layout"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent">
-
-        <LinearLayout android:layout_width="match_parent"
-                      android:layout_height="match_parent"
-                      android:orientation="horizontal">
-
-            <org.mozilla.gecko.home.HomeListView
-                android:id="@+id/clients_list"
-                style="@style/Widget.RemoteTabsListView"
-                android:layout_weight="0.32"
-                android:layout_width="0dp"
-                android:layout_height="match_parent"/>
-
-            <View
-                android:layout_width="1dp"
-                android:layout_height="match_parent"
-                android:background="#D7D9DB" />
-
-            <org.mozilla.gecko.home.HomeListView
-                android:id="@+id/tabs_list"
-                style="@style/Widget.RemoteTabsListView"
-                android:layout_weight="0.68"
-                android:layout_width="0dp"
-                android:layout_height="match_parent"/>
-
-        </LinearLayout>
-
-    </android.support.v4.widget.SwipeRefreshLayout>
-
-</LinearLayout>
deleted file mode 100644
--- a/mobile/android/base/resources/layout/remote_tabs_needs_finish_migrating.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-   This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/.
--->
-
-<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent" >
-
-    <LinearLayout style="@style/RemoteTabsPanelFrame" >
-
-        <TextView
-            style="@style/RemoteTabsPanelItem.TextAppearance.Header"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:text="@string/fxaccount_finish_migrating_header" />
-
-        <TextView
-            style="@style/RemoteTabsPanelItem.TextAppearance"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:text="@string/home_remote_tabs_need_to_finish_migrating" />
-
-        <Button
-            android:id="@+id/remote_tabs_needs_finish_migrating_sign_in"
-            style="@style/RemoteTabsPanelItem.Button"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:text="@string/fxaccount_finish_migrating_button_label" />
-    </LinearLayout>
-
-</ScrollView>
deleted file mode 100644
--- a/mobile/android/base/resources/layout/remote_tabs_needs_password.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-   This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/.
--->
-
-<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent" >
-
-    <LinearLayout style="@style/RemoteTabsPanelFrame" >
-
-        <TextView
-            style="@style/RemoteTabsPanelItem.TextAppearance.Header"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:text="@string/home_remote_tabs_unable_to_connect" />
-
-        <TextView
-            style="@style/RemoteTabsPanelItem.TextAppearance"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:text="@string/home_remote_tabs_need_to_sign_in" />
-
-        <Button
-            android:id="@+id/remote_tabs_needs_password_sign_in"
-            style="@style/RemoteTabsPanelItem.Button"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:text="@string/fxaccount_sign_in_button_label" />
-    </LinearLayout>
-
-</ScrollView>
deleted file mode 100644
--- a/mobile/android/base/resources/layout/remote_tabs_needs_upgrade.xml
+++ /dev/null
@@ -1,27 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-   This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/.
--->
-
-<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent" >
-
-    <LinearLayout style="@style/RemoteTabsPanelFrame" >
-
-        <TextView
-            style="@style/RemoteTabsPanelItem.TextAppearance.Header"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:text="@string/home_remote_tabs_unable_to_connect" />
-
-        <TextView
-            style="@style/RemoteTabsPanelItem.TextAppearance"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:text="@string/fxaccount_status_needs_upgrade" />
-    </LinearLayout>
-
-</ScrollView>
deleted file mode 100644
--- a/mobile/android/base/resources/layout/remote_tabs_needs_verification.xml
+++ /dev/null
@@ -1,40 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
-   This Source Code Form is subject to the terms of the Mozilla Public
-   - License, v. 2.0. If a copy of the MPL was not distributed with this
-   - file, You can obtain one at http://mozilla.org/MPL/2.0/.
--->
-
-<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent" >
-
-    <LinearLayout style="@style/RemoteTabsPanelFrame" >
-
-        <TextView
-            style="@style/RemoteTabsPanelItem.TextAppearance.Header"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:text="@string/fxaccount_confirm_account_header" />
-
-        <TextView
-            style="@style/RemoteTabsPanelItem.TextAppearance"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:text="@string/home_remote_tabs_need_to_verify" />
-
-        <Button
-            android:id="@+id/remote_tabs_needs_verification_resend_email"
-            style="@style/RemoteTabsPanelItem.Button"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:text="@string/fxaccount_confirm_account_resend_email" />
-
-        <TextView
-            android:id="@+id/remote_tabs_needs_verification_help"
-            style="@style/RemoteTabsPanelItem.TextAppearance.Linkified"
-            android:layout_width="match_parent"
-            android:layout_height="wrap_content"
-            android:text="@string/home_remote_tabs_trouble_verifying" />
-    </LinearLayout>
-</ScrollView>
--- a/mobile/android/base/resources/values/dimens.xml
+++ b/mobile/android/base/resources/values/dimens.xml
@@ -205,12 +205,9 @@
     <dimen name="progress_bar_scroll_offset">1.5dp</dimen>
 
     <!-- http://blog.danlew.net/2015/01/06/handling-android-resources-with-non-standard-formats/ -->
     <item name="match_parent" type="dimen">-1</item>
     <item name="wrap_content" type="dimen">-2</item>
 
     <item name="tab_strip_content_start" type="dimen">12dp</item>
     <item name="firstrun_tab_strip_content_start" type="dimen">15dp</item>
-
-    <item name="split_plane_left_pane_weight" format="float" type="dimen">0.32</item>
-    <item name="split_plane_right_pane_weight" format="float" type="dimen">0.68</item>
 </resources>
--- a/mobile/android/base/strings.xml.in
+++ b/mobile/android/base/strings.xml.in
@@ -443,28 +443,19 @@
   <string name="home_clear_history_button">&home_clear_history_button;</string>
   <string name="home_clear_history_confirm">&home_clear_history_confirm;</string>
   <string name="home_bookmarks_empty">&home_bookmarks_empty;</string>
   <string name="home_closed_tabs_title">&home_closed_tabs_title;</string>
   <string name="home_last_tabs_title">&home_last_tabs_title;</string>
   <string name="home_last_tabs_empty">&home_last_tabs_empty;</string>
   <string name="home_open_all">&home_open_all;</string>
   <string name="home_most_recent_empty">&home_most_recent_empty;</string>
-  <string name="home_selected_empty">&home_selected_empty;</string>
   <string name="home_most_recent_emptyhint">&home_most_recent_emptyhint2;</string>
   <string name="home_default_empty">&home_default_empty;</string>
   <string name="home_move_back_to_filter">&home_move_back_to_filter;</string>
-  <string name="home_remote_tabs_title">&home_remote_tabs_title;</string>
-  <string name="home_remote_tabs_empty">&home_remote_tabs_empty;</string>
-  <string name="home_remote_tabs_unable_to_connect">&home_remote_tabs_unable_to_connect;</string>
-  <string name="home_remote_tabs_need_to_sign_in">&home_remote_tabs_need_to_sign_in;</string>
-  <string name="home_remote_tabs_need_to_finish_migrating">&home_remote_tabs_need_to_finish_migrating;</string>
-  <string name="home_remote_tabs_trouble_verifying">&home_remote_tabs_trouble_verifying;</string>
-  <string name="home_remote_tabs_need_to_verify">&home_remote_tabs_need_to_verify;</string>
-  <string name="home_remote_tabs_one_hidden_device">&home_remote_tabs_one_hidden_device;</string>
   <string name="home_remote_tabs_many_hidden_devices">&home_remote_tabs_many_hidden_devices;</string>
   <string name="home_remote_tabs_hidden_devices_title">&home_remote_tabs_hidden_devices_title;</string>
   <string name="home_remote_tabs_unhide_selected_devices">&home_remote_tabs_unhide_selected_devices;</string>
   <string name="pin_site_dialog_hint">&pin_site_dialog_hint;</string>
 
   <string name="remote_tabs_never_synced">&remote_tabs_never_synced;</string>
 
   <string name="filepicker_title">&filepicker_title;</string>
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -151,22 +151,22 @@ var lazilyLoadedObserverScripts = [
   ["PermissionsHelper", ["Permissions:Check", "Permissions:Get", "Permissions:Clear"], "chrome://browser/content/PermissionsHelper.js"],
   ["FeedHandler", ["Feeds:Subscribe"], "chrome://browser/content/FeedHandler.js"],
   ["Feedback", ["Feedback:Show"], "chrome://browser/content/Feedback.js"],
   ["SelectionHandler", ["TextSelection:Get"], "chrome://browser/content/SelectionHandler.js"],
   ["EmbedRT", ["GeckoView:ImportScript"], "chrome://browser/content/EmbedRT.js"],
   ["Reader", ["Reader:FetchContent", "Reader:AddToCache", "Reader:RemoveFromCache"], "chrome://browser/content/Reader.js"],
   ["PrintHelper", ["Print:PDF"], "chrome://browser/content/PrintHelper.js"],
 ];
-if (AppConstants.NIGHTLY_BUILD) {
-  lazilyLoadedObserverScripts.push(
-    ["ActionBarHandler", ["TextSelection:Get", "TextSelection:Action", "TextSelection:End"],
-      "chrome://browser/content/ActionBarHandler.js"]
-  );
-}
+
+lazilyLoadedObserverScripts.push(
+["ActionBarHandler", ["TextSelection:Get", "TextSelection:Action", "TextSelection:End"],
+  "chrome://browser/content/ActionBarHandler.js"]
+);
+
 if (AppConstants.MOZ_WEBRTC) {
   lazilyLoadedObserverScripts.push(
     ["WebrtcUI", ["getUserMedia:request",
                   "PeerConnection:request",
                   "recording-device-events"], "chrome://browser/content/WebrtcUI.js"])
 }
 
 lazilyLoadedObserverScripts.forEach(function (aScript) {
@@ -544,21 +544,19 @@ var BrowserApp = {
       }
 
       InitLater(() => Cc["@mozilla.org/login-manager;1"].getService(Ci.nsILoginManager));
       InitLater(() => LoginManagerParent.init(), window, "LoginManagerParent");
 
     }, false);
 
     // Pass caret StateChanged events to ActionBarHandler.
-    if (AppConstants.NIGHTLY_BUILD) {
-      window.addEventListener("mozcaretstatechanged", e => {
-        ActionBarHandler.caretStateChangedHandler(e);
-      }, /* useCapture = */ true, /* wantsUntrusted = */ false);
-    }
+    window.addEventListener("mozcaretstatechanged", e => {
+      ActionBarHandler.caretStateChangedHandler(e);
+    }, /* useCapture = */ true, /* wantsUntrusted = */ false);
   },
 
   get _startupStatus() {
     delete this._startupStatus;
 
     let savedMilestone = null;
     try {
       savedMilestone = Services.prefs.getCharPref("browser.startup.homepage_override.mstone");
--- a/mobile/android/services/strings.xml.in
+++ b/mobile/android/services/strings.xml.in
@@ -23,24 +23,16 @@
 <!-- Firefox Account links. -->
 <string name="fxaccount_link_tos">https://accounts.firefox.com/legal/terms</string>
 <string name="fxaccount_link_pn">https://accounts.firefox.com/legal/privacy</string>
 
 <string name="fxaccount_getting_started_welcome_to_sync">&fxaccount_getting_started_welcome_to_sync;</string>
 <string name="fxaccount_getting_started_description">&fxaccount_getting_started_description2;</string>
 <string name="fxaccount_getting_started_get_started">&fxaccount_getting_started_get_started;</string>
 
-<string name="fxaccount_confirm_account_header">&fxaccount_confirm_account_header;</string>
-<string name="fxaccount_confirm_account_resend_email">&fxaccount_confirm_account_resend_email;</string>
-
-<string name="fxaccount_sign_in_button_label">&fxaccount_sign_in_button_label;</string>
-
-<string name="fxaccount_finish_migrating_header">&fxaccount_finish_migrating_header;</string>
-<string name="fxaccount_finish_migrating_button_label">&fxaccount_finish_migrating_button_label;</string>
-
 <string name="fxaccount_status_activity_label">&syncBrand.shortName.label;</string>
 <string name="fxaccount_status_signed_in_as">&fxaccount_status_signed_in_as;</string>
 <string name="fxaccount_status_manage_account">&fxaccount_status_manage_account;</string>
 <string name="fxaccount_status_auth_server">&fxaccount_status_auth_server;</string>
 <string name="fxaccount_status_sync_now">&fxaccount_status_sync_now;</string>
 <string name="fxaccount_status_syncing">&fxaccount_status_syncing2;</string>
 <string name="fxaccount_status_last_synced">&remote_tabs_last_synced;</string>
 <string name="fxaccount_status_never_synced">&remote_tabs_never_synced;</string>
--- a/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testAboutHomePageNavigation.java
+++ b/mobile/android/tests/browser/robocop/src/org/mozilla/gecko/tests/testAboutHomePageNavigation.java
@@ -36,21 +36,21 @@ public class testAboutHomePageNavigation
             helperTestTablet();
         } else {
             helperTestPhone();
         }
     }
 
     private void helperTestTablet() {
         mAboutHome.swipeToPanelOnRight();
-        mAboutHome.assertCurrentPanel(PanelType.HISTORY);
+        mAboutHome.assertCurrentPanel(PanelType.COMBINED_HISTORY);
 
         // Edge case.
         mAboutHome.swipeToPanelOnRight();
-        mAboutHome.assertCurrentPanel(PanelType.HISTORY);
+        mAboutHome.assertCurrentPanel(PanelType.COMBINED_HISTORY);
 
         mAboutHome.swipeToPanelOnLeft();
         mAboutHome.assertCurrentPanel(PanelType.READING_LIST);
 
         mAboutHome.swipeToPanelOnLeft();
         mAboutHome.assertCurrentPanel(PanelType.BOOKMARKS);
 
         mAboutHome.swipeToPanelOnLeft();
@@ -68,21 +68,21 @@ public class testAboutHomePageNavigation
 
         mAboutHome.swipeToPanelOnLeft();
         mAboutHome.assertCurrentPanel(PanelType.BOOKMARKS);
 
         mAboutHome.swipeToPanelOnLeft();
         mAboutHome.assertCurrentPanel(PanelType.TOP_SITES);
 
         mAboutHome.swipeToPanelOnLeft();
-        mAboutHome.assertCurrentPanel(PanelType.HISTORY);
+        mAboutHome.assertCurrentPanel(PanelType.COMBINED_HISTORY);
 
         // Edge case.
         mAboutHome.swipeToPanelOnLeft();
-        mAboutHome.assertCurrentPanel(PanelType.HISTORY);
+        mAboutHome.assertCurrentPanel(PanelType.COMBINED_HISTORY);
 
         mAboutHome.swipeToPanelOnRight();
         mAboutHome.assertCurrentPanel(PanelType.TOP_SITES);
     }
 
     // TODO: bug 943706 - reimplement this old test code.
     /*
         //  Removed by Bug 896576 - [fig] Remove [getAllPagesList] from BaseTest
--- a/mobile/locales/en-US/searchplugins/bing.xml
+++ b/mobile/locales/en-US/searchplugins/bing.xml
@@ -1,15 +1,15 @@
 <!-- This Source Code Form is subject to the terms of the Mozilla Public
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
 <SearchPlugin xmlns="http://www.mozilla.org/2006/browser/search/">
 <ShortName>Bing</ShortName>
-<Image width="16" height="16">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAYAAADimHc4AAAAAXNSR0IArs4c6QAABq5JREFUeAHtXE1sFVUU/ua1pVBLJVG0CAgiCVKsLETQxJgQUhfujCILq4kCGiXqQqPxJ/4vXOhCjUa03XXHzo0CVmqEAAZ/UARCAEUbAaG08MpPW9rnd950YDJ9Nu107py5j3ua1/m/59zvm3Pm3nPvjIdhKRTgYTNWc3MNCmjgcmpwzC0TQSBPhPeypBY0odXziDLFk3+FLajHANq4a4VsOzGMgId2VKHZW45jueKd78A3jHikeLnRiblg7xU2Yy2G8HnkFLeZBgIe1uYYdiTuO9FBYI0QIA9cJxoIEPsc9brWjgb4vs6pQoATRQQcAYrgi2pHgCNAGQFl9c4DHAHKCCirdx7gCFBGQFm984CyJuDuQ0DDeuD6lUDVNcpVzaZ6r7DRHxgwYt69xTEHv+jCEJD/Gej6xv/1bAWGLhhRa1Oh6REQRWWQ4AsJQsgp/s6QHObFrzTRIyCKdH8XifjWJ0NIOX84ekZZbmeHgCi8m4qjpdG9ZbftWkHKlDoCHAHKCCirdx7gCFBGQFm98wBHgDICyuqdBzgClBFQVu88wBGgjICyeucBygRUKusfu/rGNmCgBzj5NdC9BRg8O/ZrM3ymPQSc+Aq4jSTcuI7DBn0kYatPRhcJ6d2TYYhHN82edLQ3CbjnCFBdP7JGFzpJxkYO7pAMGUu4SE+xROx5BhT6gb8/Kw3r5FnALL7msHgDsPwkcAe9Y95rQN0Snp/tcQV7PECgn8S7X7wgR28Yq/SfoFds8sOVeMkAtzMkdhEgwMnDeMbD8SDkS1nI/zRMBsPV6R18Q/FivLISuso+AuqWAnfuTKb6A6f9MWhpWcmvj8+SlMU+AgSgpbxzpy1LHqre3y97R/f39A62tgxLpeHyzRT/18dmCKhdBMhv7vNsSbGf0d1xmZDzB43UxU4PGK1JagQmFmpoloY9zdAwsKM1ScPnWbBuJwECbOd69ojZN7Bc7CWg/xhwfIPl8Nv+kt6RjxwBqgic+QHYwXRDJz91cbFX1ZS4yu0NQUGNz/wI7H0S+G6Gv5Rti8R+AgKwB+kB4gniERZ5RfkQEBAhS4u8ojwJCMgQr8jvYQaUOZ+Mip2piLGAWXc7cPPbwPT7xnK22jnlR0BtI4F/iy8G3q8G6ngUlw8BNQsI/JtA/SoOgmV7FCxMkP0ETJnH4cfXgRuaCXxFuG5WrNtLQPVsAv8qMPNx9uerrAC7lJHZI0ASbPufKWWrv0/GhW96GZjNzleu+v/Ps+RItgjoOwr88gDHarePhK/qWg6UvOjPC6qoGXnc1J7znASw/1lTpSM7BPQQdAG/nySEpXIaMIcjVHOeAyqnho+YXR8aAI58ABx6h2nvc8Z0ZYMASSHsY9iRgZZAKmp5txP0uS/wOxMkIU051UF7ngbO7jOuVZeAIN4LAYHkpjC+r2Ocf4nzgBh20pS+48ABEn60LTWtegRE471XzdltT7Bl80rp6YcmIZEPicisu4NsVaU8rVGHgHC899iEnPmYP5VwMpuWacvpXQw3TzGBx6WCpE/ApXg/yBluj7L3+gZQw85U2iJT3eWOL843pQcoSXoEXIr3XzBP8xAwn/maqxboVPufNj/W9zPmK0s6BBTj/YN8qF4H3LWbX6tu1Kl2L1s10rrp7tDRX0KreQIk3ne2ALd8CFy9pIQJKewaZDv+8LvAn++zqcv2fYbELAHnOJ1PHrK3tupV+d8v/Z7sBfZoMyhmCaiZr1flIIVwggRkWHJGbdvNh628IJGmSArhj/eAbQ1AxsEXWMxOzhUNVdOBhZ9yoIQPYdOSYgohqaqY9QCxUl4J+nUlYNIbJIXw2yPAruWp5G+SAl/KMe8BYWuT9gbFFEK4WhNZT5eAwFL5ku7CT9gvYHiKK8ophLhmR68zH4KiGmVbZjVvWwQc43K8IimEfeuAncvU8jfjNXm083U8IGzReLwhQymEcBUmsq5PgFhffDYwJNUzNJWSDKYQSpkZZ59OCIpaWmwpsc8QbSlJCuEAB+C3L85U/iZq/kS2s+EB4RoE3iAzHmQwPKMphLDJE1nPHgETqY2F12YjBFkIXFImOwKSQjJmOY6AmMAldZkjICkkY5bjCIgJXFKXOQKSQjJmOY6AmMAldZkjICkkY5bjCIgJXFKXCQH5pApz5YwbgXwOHvaO+zJ3QTIIEHvxAM6acqKEQIvHLzl62My/AlYoGXFlqvXQjiY05fhKbQFVaCYN7VcmEgq1FqyJuWDvBeqHPWE1t9eQEs5qQoovZAVWlPUyP/y8beGd31q88Vnd/wBKdfkwTW1HcgAAAABJRU5ErkJggg==</Image>
+<Image width="16" height="16">data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGAAAABgCAYAAADimHc4AAAAAXNSR0IArs4c6QAABl5JREFUeAHtnXtQVFUcx793AcEEtEEkyywfgFASk+GjIjKE7CG9p7HBMZXMqUbUIZx0YCL7QxOaxmaqUUz/oMaZTBxLwsEVM8skRVE0HiEhRhEhxkNQgdvvXGcnBWt3Ze+ec9bzm4Fl7z17fr/z/ZzfvXfPOfeiwWa6rmFV2gJ6mwINkdD1ANsu9eoCBTStDTpOUk25yMjeCE3TWa2aUfV7b96Crt48QI833qtfJiugWeFnSUb62j8s1NM1Jb7Jevernjo66/CkvRe8O16hnp/ar4zaYLYCY7Gv6AzLAHbcV8ZHgRSLccLl41x5pYsdlgHqaodXVyDtLbx8K7+XFVAAOPcEBUAB4KwAZ/cqAxQAzgpwdq8yQAHgrABn9yoDFADOCnB2rzJAAeCsAGf3KgMUAM4KcHavMuBGBXBsYRqW3z8dowOHcZaAr3sN7ywzlke4Oww9I8dwqes69tfX4rPjh/HFyTKc7ep0dyhc/XEHcGXrL/X0oLCmgmCUYkdVOTq7u6/c7ZF/e4vUKh8vL8wKu8v4ab94AfkVx7Gy+BvUt54TKUyXxiLsSdh/kC/mRN2HyOEhLm2waJUJC0A0ocyKRwEwS1kH61UAHBTKrGIKgFnKOlivAuCgUGYVUwDMUtbBehUAB4Uyq5gCYJayDtarADgolFnFFACzlHWwXgXAQaHMKqYAmKWsg/UKNRpqL+b0adPR3duLLSeOoKG91V5xKfZLlQElDaeRk5iE+iUZsCYvwvzoyRjq6yeF0P8VpFQA9tbVoKr5T1g0Cx4ZE4qNs15E47IsfPn8XDw3YSJ8aT5BNpMKABN3Q+nBqzT29fbGsxFR2PrCywaMTwlK/J2hYHcfymDSAdhc9hMu9lx7qnKo32DMo8PS7jmLcGZJJt5PSMKkkaOE5iAdgL86O4ypSnuqjgwIxNKpcTiUshSVry1HZmwCxt8cZO9jbt8vHQCm0PrSH50SKixoBLIenonqN1bg4PxUpE6ORcgQf6fqMKuwVJehNhGKf/0F1c1NCA0Ktm1y+HXybaPBfnLo8GStrcbn5aXYRpP/bbQIgIdJmQFsIdOGI85lQV9xvSwWJI4Lx+anZhsn77773fVeSgBMnP87GTsr3mAfH2c/4rLy0gJoOt+B7RXlLhOCV0XSAmCCZX5biILqn3lp5xK/UgOopBPxE1tyEb0+G1vKj6CHxolkM6kB2MQua/wds/PzEP7RarpEPYALEq0p9QgANhA1Lc14dedWjPnwXeysZs/HE988CgCTe/jgIXg77lE8Nn6C+OpThFJ+EbuWst40Qvp6zAOG+MNoTEgW8wgAiWPD8EHi04gIlm8ltdQAxtHgGhvxTAq/W5YO3y9OKQH4+wzCytgZWDolDmw+YKDWq/O7fB149ANtvROfZ1MsyRMnYU38k2DDza6w/adPYfGu7a6o6rrqkAZAzMjbsW7mM5g66o7ramjfD9X/3YJ069c0wX+07y63vhceAOvpm2iace49MfS864FPM3Z1X8LaH4qx+vs9OE9/8zbhAbA5XlcIz4TeSrfBpu3+CnXU+0Ux4QG4QvxjjQ1IpeM8W1UhmgkPYCCCNdOQdcbeQmN8qIduCBfRPBJAd28PPjl8AJkkfovgd957HABrbZVxuDnR1Chih+8Xk8cAOEUjoWlFO5BfKdcsGbfR0Je25aHu3Nl+PcLZDR20mmHlngJEfrxGOvFZW7k9rIM59/PyxpIpD+GtB+MR6OQiW/aUFfZQj+X0ZUrmldJcATAIzIJvGoIsGsNfeO80sOUi9uxQQz0WF+bjwG919ooKv18IADaVIoaPQPaMJDweGmHbdNVrY3sbVhQXYNPREvqPUJ5hQgGwSTqDlp6zlWtRIbcam9hi3HUl32HVviK0clrBZovN1a9CAmCNZMvL50XHIIEmWzL37kLV2SZXt12I+oQFIIQ6bgjC/hnPDUHcyC4UAM70FQAFgLMCnN2rDFAAOCvA2b3KAAWAswKc3asMUAA4K8DZvcoABYCzApzdqwxQADgrwNm9ygDuADStjXMMN6570t5Ck6ty3E7oiZhIe3YIyvXEtknSplwNuq5hVVoRoMdLErSHhKlZkZGdQHPfmg4/SzKt0bJ6SMskaAZpzTQn7f+95eRyJiyg6FNoQUIkZUaABC2RJ0R2sXP5fJtLPX+j0fEp+n8Aqe+mBAmGWdQAAAAASUVORK5CYII=</Image>
 <Url type="application/x-suggestions+json" template="https://www.bing.com/osjson.aspx">
   <Param name="query" value="{searchTerms}"/>
   <Param name="language" value="{moz:locale}"/>
 </Url>
 <!-- this is effectively x-moz-phonesearch, but search service expects a text/html entry -->
 <Url type="text/html" method="GET" template="https://www.bing.com/search">
   <Param name="q" value="{searchTerms}" />
   <Param name="pc" value="MOZB" />
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -4961,64 +4961,28 @@ pref("dom.voicemail.enabled", false);
 pref("dom.voicemail.defaultServiceId", 0);
 
 // DOM Inter-App Communication API.
 pref("dom.inter-app-communication-api.enabled", false);
 
 // Disable mapped array buffer by default.
 pref("dom.mapped_arraybuffer.enabled", false);
 
-#ifdef MOZ_SAFE_BROWSING
 // The tables used for Safebrowsing phishing and malware checks.
 pref("urlclassifier.malwareTable", "goog-malware-shavar,goog-unwanted-shavar,test-malware-simple,test-unwanted-simple");
 pref("urlclassifier.phishTable", "goog-phish-shavar,test-phish-simple");
 pref("urlclassifier.downloadBlockTable", "");
 pref("urlclassifier.downloadAllowTable", "");
 pref("urlclassifier.disallow_completions", "test-malware-simple,test-phish-simple,test-unwanted-simple,test-track-simple,test-trackwhite-simple,test-forbid-simple,goog-downloadwhite-digest256,mozstd-track-digest256,mozstd-trackwhite-digest256,mozfull-track-digest256,test-block-simple,mozplugin-block-digest256,mozplugin2-block-digest256");
 
 // The table and update/gethash URLs for Safebrowsing phishing and malware
 // checks.
 pref("urlclassifier.trackingTable", "test-track-simple,mozstd-track-digest256");
 pref("urlclassifier.trackingWhitelistTable", "test-trackwhite-simple,mozstd-trackwhite-digest256");
 
-// Tables for application reputation.
-pref("urlclassifier.downloadBlockTable", "goog-badbinurl-shavar");
-
-// The number of random entries to send with a gethash request.
-pref("urlclassifier.gethashnoise", 4);
-
-// Gethash timeout for Safebrowsing.	
-pref("urlclassifier.gethash.timeout_ms", 5000);
-
-// If an urlclassifier table has not been updated in this number of seconds,
-// a gethash request will be forced to check that the result is still in
-// the database.
-pref("urlclassifier.max-complete-age", 2700);
-
-pref("browser.safebrowsing.enabled", true);
-pref("browser.safebrowsing.malware.enabled", true);
-
-pref("browser.safebrowsing.downloads.remote.timeout_ms", 10000);
-pref("browser.safebrowsing.downloads.remote.url", "https://sb-ssl.google.com/safebrowsing/clientreport/download?key=%GOOGLE_API_KEY%");
-pref("browser.safebrowsing.downloads.remote.block_dangerous",            true);
-pref("browser.safebrowsing.downloads.remote.block_dangerous_host",       true);
-pref("browser.safebrowsing.downloads.remote.block_potentially_unwanted", false);
-pref("browser.safebrowsing.downloads.remote.block_uncommon",             false);
-pref("browser.safebrowsing.debug", false);
-
-pref("browser.safebrowsing.provider.google.lists", "goog-badbinurl-shavar,goog-downloadwhite-digest256,goog-phish-shavar,goog-malware-shavar,goog-unwanted-shavar");
-pref("browser.safebrowsing.provider.google.updateURL", "https://safebrowsing.google.com/safebrowsing/downloads?client=SAFEBROWSING_ID&appver=%VERSION%&pver=2.2&key=%GOOGLE_API_KEY%");
-pref("browser.safebrowsing.provider.google.gethashURL", "https://safebrowsing.google.com/safebrowsing/gethash?client=SAFEBROWSING_ID&appver=%VERSION%&pver=2.2");
-pref("browser.safebrowsing.provider.google.reportURL", "https://safebrowsing.google.com/safebrowsing/diagnostic?client=%NAME%&hl=%LOCALE%&site=");
-
-pref("browser.safebrowsing.reportPhishMistakeURL", "https://%LOCALE%.phish-error.mozilla.com/?hl=%LOCALE%&url=");
-pref("browser.safebrowsing.reportPhishURL", "https://%LOCALE%.phish-report.mozilla.com/?hl=%LOCALE%&url=");
-pref("browser.safebrowsing.reportMalwareMistakeURL", "https://%LOCALE%.malware-error.mozilla.com/?hl=%LOCALE%&url=");
-
-
 // The table and global pref for blocking access to sites forbidden by policy
 pref("browser.safebrowsing.forbiddenURIs.enabled", false);
 pref("urlclassifier.forbiddenTable", "test-forbid-simple");
 
 // The table and global pref for blocking plugin content
 pref("browser.safebrowsing.blockedURIs.enabled", false);
 pref("urlclassifier.blockedTable", "test-block-simple,mozplugin-block-digest256");
 
@@ -5031,17 +4995,16 @@ pref("browser.safebrowsing.provider.mozi
 // to lookup the localized name in preferences.properties.
 pref("browser.safebrowsing.provider.mozilla.lists.mozstd.name", "mozstdName");
 pref("browser.safebrowsing.provider.mozilla.lists.mozstd.description", "mozstdDesc");
 pref("browser.safebrowsing.provider.mozilla.lists.mozfull.name", "mozfullName");
 pref("browser.safebrowsing.provider.mozilla.lists.mozfull.description", "mozfullDesc");
 
 // Allow users to ignore Safe Browsing warnings.
 pref("browser.safebrowsing.allowOverride", true);
-#endif
 
 // Turn off Spatial navigation by default.
 pref("snav.enabled", false);
 
 // New implementation to unify touch-caret and selection-carets.
 pref("layout.accessiblecaret.enabled", false);
 
 // CSS attributes of the AccessibleCaret in CSS pixels.
--- a/services/common/kinto-updater.js
+++ b/services/common/kinto-updater.js
@@ -69,17 +69,20 @@ this.checkVersions = function() {
     // If the server is failing, the JSON response might not contain the
     // expected data (e.g. error response - Bug 1259145)
     if (!versionInfo.hasOwnProperty("data")) {
       throw new Error("Polling for changes failed.");
     }
 
     // Record new update time and the difference between local and server time
     let serverTimeMillis = Date.parse(response.headers.get("Date"));
-    let clockDifference = Math.abs(Date.now() - serverTimeMillis) / 1000;
+
+    // negative clockDifference means local time is behind server time
+    // by the absolute of that value in seconds (positive means it's ahead)
+    let clockDifference = Math.floor((Date.now() - serverTimeMillis) / 1000);
     Services.prefs.setIntPref(PREF_KINTO_CLOCK_SKEW_SECONDS, clockDifference);
     Services.prefs.setIntPref(PREF_KINTO_LAST_UPDATE, serverTimeMillis / 1000);
 
     let firstError;
     for (let collectionInfo of versionInfo.data) {
       // Skip changes that don't concern configured blocklist bucket.
       if (collectionInfo.bucket != blocklistsBucket) {
         continue;
--- a/services/common/tests/unit/test_kinto_updater.js
+++ b/services/common/tests/unit/test_kinto_updater.js
@@ -9,41 +9,41 @@ const PREF_LAST_ETAG = "services.kinto.l
 const PREF_CLOCK_SKEW_SECONDS = "services.kinto.clock_skew_seconds";
 
 // Check to ensure maybeSync is called with correct values when a changes
 // document contains information on when a collection was last modified
 add_task(function* test_check_maybeSync(){
   const changesPath = "/v1/buckets/monitor/collections/changes/records";
 
   // register a handler
-  function handleResponse (request, response) {
+  function handleResponse (serverTimeMillis, request, response) {
     try {
       const sampled = getSampleResponse(request, server.identity.primaryPort);
       if (!sampled) {
         do_throw(`unexpected ${request.method} request for ${request.path}?${request.queryString}`);
       }
 
       response.setStatusLine(null, sampled.status.status,
                              sampled.status.statusText);
       // send the headers
       for (let headerLine of sampled.sampleHeaders) {
         let headerElements = headerLine.split(':');
         response.setHeader(headerElements[0], headerElements[1].trimLeft());
       }
 
       // set the server date
-      response.setHeader("Date", (new Date(2000)).toUTCString());
+      response.setHeader("Date", (new Date(serverTimeMillis)).toUTCString());
 
       response.write(sampled.responseBody);
     } catch (e) {
       dump(`${e}\n`);
     }
   }
 
-  server.registerPathHandler(changesPath, handleResponse);
+  server.registerPathHandler(changesPath, handleResponse.bind(null, 2000));
 
   // set up prefs so the kinto updater talks to the test server
   Services.prefs.setCharPref(PREF_KINTO_BASE,
     `http://localhost:${server.identity.primaryPort}/v1`);
 
   // set some initial values so we can check these are updated appropriately
   Services.prefs.setIntPref(PREF_LAST_UPDATE, 0);
   Services.prefs.setIntPref(PREF_CLOCK_SKEW_SECONDS, 0);
@@ -72,23 +72,22 @@ add_task(function* test_check_maybeSync(
 
   // check the last_update is updated
   do_check_eq(Services.prefs.getIntPref(PREF_LAST_UPDATE), 2);
 
   // How does the clock difference look?
   let endTime = Date.now();
   let clockDifference = Services.prefs.getIntPref(PREF_CLOCK_SKEW_SECONDS);
   // we previously set the serverTime to 2 (seconds past epoch)
-  do_check_eq(clockDifference <= endTime / 1000
-              && clockDifference >= Math.floor(startTime / 1000) - 2, true);
+  do_check_true(clockDifference <= endTime / 1000
+              && clockDifference >= Math.floor(startTime / 1000) - 2);
   // Last timestamp was saved. An ETag header value is a quoted string.
   let lastEtag = Services.prefs.getCharPref(PREF_LAST_ETAG);
   do_check_eq(lastEtag, "\"1100\"");
 
-
   // Simulate a poll with up-to-date collection.
   Services.prefs.setIntPref(PREF_LAST_UPDATE, 0);
   // If server has no change, a 304 is received, maybeSync() is not called.
   updater.addTestKintoClient("test-collection", {
     maybeSync: () => {throw new Error("Should not be called");}
   });
   yield updater.checkVersions();
   // Last update is overwritten
@@ -112,16 +111,27 @@ add_task(function* test_check_maybeSync(
   try {
     yield updater.checkVersions();
   } catch (e) {
     error = e;
   }
   do_check_eq(error.message, "Polling for changes failed.");
   // When an error occurs, last update was not overwritten (see Date header above).
   do_check_eq(Services.prefs.getIntPref(PREF_LAST_UPDATE), 2);
+
+  // check negative clock skew times
+
+  // set to a time in the future
+  server.registerPathHandler(changesPath, handleResponse.bind(null, Date.now() + 10000));
+
+  yield updater.checkVersions();
+
+  clockDifference = Services.prefs.getIntPref(PREF_CLOCK_SKEW_SECONDS);
+  // we previously set the serverTime to Date.now() + 10000 ms past epoch
+  do_check_true(clockDifference <= 0 && clockDifference >= -10);
 });
 
 function run_test() {
   // Set up an HTTP Server
   server = new HttpServer();
   server.start(-1);
 
   run_next_test();
--- a/services/sync/modules/engines/clients.js
+++ b/services/sync/modules/engines/clients.js
@@ -9,24 +9,26 @@ this.EXPORTED_SYMBOLS = [
 
 var {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 
 Cu.import("resource://services-common/async.js");
 Cu.import("resource://services-common/stringbundle.js");
 Cu.import("resource://services-sync/constants.js");
 Cu.import("resource://services-sync/engines.js");
 Cu.import("resource://services-sync/record.js");
+Cu.import("resource://services-sync/resource.js");
 Cu.import("resource://services-sync/util.js");
 Cu.import("resource://gre/modules/Services.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "fxAccounts",
   "resource://gre/modules/FxAccounts.jsm");
 
 const CLIENTS_TTL = 1814400; // 21 days
 const CLIENTS_TTL_REFRESH = 604800; // 7 days
+const STALE_CLIENT_REMOTE_AGE = 604800; // 7 days
 
 const SUPPORTED_PROTOCOL_VERSIONS = ["1.1", "1.5"];
 
 function hasDupeCommand(commands, action) {
   if (!commands) {
     return false;
   }
   return commands.some(other => other.command == action.command &&
@@ -168,28 +170,46 @@ ClientEngine.prototype = {
       this.lastRecordUpload = Date.now() / 1000;
     }
     SyncEngine.prototype._syncStartup.call(this);
   },
 
   _processIncoming() {
     // Fetch all records from the server.
     this.lastSync = 0;
-    this._incomingClients = [];
+    this._incomingClients = {};
     try {
       SyncEngine.prototype._processIncoming.call(this);
       // Since clients are synced unconditionally, any records in the local store
       // that don't exist on the server must be for disconnected clients. Remove
       // them, so that we don't upload records with commands for clients that will
       // never see them. We also do this to filter out stale clients from the
       // tabs collection, since showing their list of tabs is confusing.
-      let remoteClientIDs = Object.keys(this._store._remoteClients);
-      let staleIDs = Utils.arraySub(remoteClientIDs, this._incomingClients);
-      for (let staleID of staleIDs) {
-        this._removeRemoteClient(staleID);
+      for (let id in this._store._remoteClients) {
+        if (!this._incomingClients[id]) {
+          this._log.info(`Removing local state for deleted client ${id}`);
+          this._removeRemoteClient(id);
+        }
+      }
+      // Bug 1264498: Mobile clients don't remove themselves from the clients
+      // collection when the user disconnects Sync, so we filter out clients
+      // with the same name that haven't synced in over a week.
+      delete this._incomingClients[this.localID];
+      let names = new Set([this.localName]);
+      for (let id in this._incomingClients) {
+        let record = this._store._remoteClients[id];
+        if (!names.has(record.name)) {
+          names.add(record.name);
+          continue;
+        }
+        let remoteAge = AsyncResource.serverTime - this._incomingClients[id];
+        if (remoteAge > STALE_CLIENT_REMOTE_AGE) {
+          this._log.info(`Hiding stale client ${id} with age ${remoteAge}`);
+          this._removeRemoteClient(id);
+        }
       }
     } finally {
       this._incomingClients = null;
     }
   },
 
   _uploadOutgoing() {
     this._clearedCommands = null;
@@ -214,17 +234,17 @@ ClientEngine.prototype = {
       Services.telemetry.getHistogramById(hid).add(count);
     }
     SyncEngine.prototype._syncFinish.call(this);
   },
 
   _reconcile: function _reconcile(item) {
     // Every incoming record is reconciled, so we use this to track the
     // contents of the collection on the server.
-    this._incomingClients.push(item.id);
+    this._incomingClients[item.id] = item.modified;
 
     if (!this._store.itemExists(item.id)) {
       return true;
     }
     // Clients are synced unconditionally, so we'll always have new records.
     // Unfortunately, this will cause the scheduler to use the immediate sync
     // interval for the multi-device case, instead of the active interval. We
     // work around this by updating the record during reconciliation, and
--- a/services/sync/tests/unit/test_clients_engine.js
+++ b/services/sync/tests/unit/test_clients_engine.js
@@ -490,25 +490,138 @@ add_test(function test_process_incoming_
   _("Ensures local commands are executed");
 
   engine.localCommands = [{ command: "logout", args: [] }];
 
   let ev = "weave:service:logout:finish";
 
   var handler = function() {
     Svc.Obs.remove(ev, handler);
+
+    Svc.Prefs.resetBranch("");
+    Service.recordManager.clearCache();
+    engine._resetClient();
+
     run_next_test();
   };
 
   Svc.Obs.add(ev, handler);
 
   // logout command causes processIncomingCommands to return explicit false.
   do_check_false(engine.processIncomingCommands());
 });
 
+add_test(function test_filter_duplicate_names() {
+  _("Ensure that we exclude clients with identical names that haven't synced in a week.");
+
+  let now = Date.now() / 1000;
+  let contents = {
+    meta: {global: {engines: {clients: {version: engine.version,
+                                        syncID: engine.syncID}}}},
+    clients: {},
+    crypto: {}
+  };
+  let server = serverForUsers({"foo": "password"}, contents);
+  let user   = server.user("foo");
+
+  new SyncTestingInfrastructure(server.server);
+  generateNewKeys(Service.collectionKeys);
+
+  // Synced recently.
+  let recentID = Utils.makeGUID();
+  server.insertWBO("foo", "clients", new ServerWBO(recentID, encryptPayload({
+    id: recentID,
+    name: "My Phone",
+    type: "mobile",
+    commands: [],
+    version: "48",
+    protocols: ["1.5"],
+  }), now - 10));
+
+  // Dupe of our client, synced more than 1 week ago.
+  let dupeID = Utils.makeGUID();
+  server.insertWBO("foo", "clients", new ServerWBO(dupeID, encryptPayload({
+    id: dupeID,
+    name: engine.localName,
+    type: "desktop",
+    commands: [],
+    version: "48",
+    protocols: ["1.5"],
+  }), now - 604810));
+
+  // Synced more than 1 week ago, but not a dupe.
+  let oldID = Utils.makeGUID();
+  server.insertWBO("foo", "clients", new ServerWBO(oldID, encryptPayload({
+    id: oldID,
+    name: "My old desktop",
+    type: "desktop",
+    commands: [],
+    version: "48",
+    protocols: ["1.5"],
+  }), now - 604820));
+
+  try {
+    let store = engine._store;
+
+    _("First sync");
+    strictEqual(engine.lastRecordUpload, 0);
+    engine._sync();
+    ok(engine.lastRecordUpload > 0);
+    deepEqual(user.collection("clients").keys().sort(),
+              [recentID, dupeID, oldID, engine.localID].sort(),
+              "Our record should be uploaded on first sync");
+    deepEqual(Object.keys(store.getAllIDs()).sort(),
+              [recentID, oldID, engine.localID].sort(),
+              "Fresh clients should be downloaded on first sync");
+
+    _("Broadcast logout to all clients");
+    engine.sendCommand("logout", []);
+    engine._sync();
+
+    let collection = server.getCollection("foo", "clients");
+    let recentPayload = JSON.parse(JSON.parse(collection.payload(recentID)).ciphertext);
+    deepEqual(recentPayload.commands, [{ command: "logout", args: [] }],
+              "Should send commands to the recent client");
+
+    let oldPayload = JSON.parse(JSON.parse(collection.payload(oldID)).ciphertext);
+    deepEqual(oldPayload.commands, [{ command: "logout", args: [] }],
+              "Should send commands to the week-old client");
+
+    let dupePayload = JSON.parse(JSON.parse(collection.payload(dupeID)).ciphertext);
+    deepEqual(dupePayload.commands, [],
+              "Should not send commands to the dupe client");
+
+    _("Update the dupe client's modified time");
+    server.insertWBO("foo", "clients", new ServerWBO(dupeID, encryptPayload({
+      id: dupeID,
+      name: engine.localName,
+      type: "desktop",
+      commands: [],
+      version: "48",
+      protocols: ["1.5"],
+    }), now - 10));
+
+    _("Second sync.");
+    engine._sync();
+
+    deepEqual(Object.keys(store.getAllIDs()).sort(),
+              [recentID, oldID, dupeID, engine.localID].sort(),
+              "Stale client synced, so it should no longer be marked as a dupe");
+  } finally {
+    Svc.Prefs.resetBranch("");
+    Service.recordManager.clearCache();
+
+    try {
+      server.deleteCollections("foo");
+    } finally {
+      server.stop(run_next_test);
+    }
+  }
+});
+
 add_test(function test_command_sync() {
   _("Ensure that commands are synced across clients.");
 
   engine._store.wipe();
   generateNewKeys(Service.collectionKeys);
 
   let contents = {
     meta: {global: {engines: {clients: {version: engine.version,
--- a/toolkit/xre/nsAppRunner.cpp
+++ b/toolkit/xre/nsAppRunner.cpp
@@ -3386,16 +3386,21 @@ XREMain::XRE_mainInit(bool* aExitFlag)
 #endif
     // Otherwise just warn for the moment, as most things will work.
     NS_WARNING("Failed to initialize broker services, sandboxed processes will "
                "fail to start.");
   }
 #endif
 
 #ifdef XP_MACOSX
+  // Set up ability to respond to system (Apple) events. This must occur before
+  // ProcessUpdates to ensure that links clicked in external applications aren't
+  // lost when updates are pending.
+  SetupMacApplicationDelegate();
+
   if (EnvHasValue("MOZ_LAUNCHED_CHILD")) {
     // This is needed, on relaunch, to force the OS to use the "Cocoa Dock
     // API".  Otherwise the call to ReceiveNextEvent() below will make it
     // use the "Carbon Dock API".  For more info see bmo bug 377166.
     EnsureUseCocoaDockAPI();
 
     // When the app relaunches, the original process exits.  This causes
     // the dock tile to stop bouncing, lose the "running" triangle, and
@@ -4281,20 +4286,16 @@ XREMain::XRE_mainRun()
       toolkit->SetDesktopStartupID(mDesktopStartupID);
     }
     // Clear the environment variable so it won't be inherited by
     // child processes and confuse things.
     g_unsetenv ("DESKTOP_STARTUP_ID");
 #endif
 
 #ifdef XP_MACOSX
-    // Set up ability to respond to system (Apple) events. This must be
-    // done before setting up the command line service.
-    SetupMacApplicationDelegate();
-
     // we re-initialize the command-line service and do appleevents munging
     // after we are sure that we're not restarting
     cmdLine = do_CreateInstance("@mozilla.org/toolkit/command-line;1");
     NS_ENSURE_TRUE(cmdLine, NS_ERROR_FAILURE);
 
     CommandLineServiceMac::SetupMacCommandLine(gArgc, gArgv, false);
 
     rv = cmdLine->Init(gArgc, gArgv,
--- a/toolkit/xre/nsCommandLineServiceMac.cpp
+++ b/toolkit/xre/nsCommandLineServiceMac.cpp
@@ -49,18 +49,20 @@ void SetupMacCommandLine(int& argc, char
   sArgs[0] = nullptr;
   sArgsUsed = 0;
 
   sBuildingCommandLine = true;
 
   // Copy args, stripping anything we don't want.
   for (int arg = 0; arg < argc; arg++) {
     char* flag = argv[arg];
-    // Don't pass on the psn (Process Serial Number) flag from the OS.
-    if (strncmp(flag, "-psn_", 5) != 0)
+    // Don't pass on the psn (Process Serial Number) flag from the OS, or
+    // the "-foreground" flag since it will be set below if necessary.
+    if (strncmp(flag, "-psn_", 5) != 0 &&
+        strncmp(flag, "-foreground", 11) != 0)
       AddToCommandLine(flag);
   }
 
   // Force processing of any pending Apple GetURL Events while we're building
   // the command line. The handlers will append to the command line rather than
   // act directly so there is no chance we'll process them during a XUL window
   // load and accidentally open unnecessary windows and home pages.
   ProcessPendingGetURLAppleEvents();