Merge mozilla-central to mozilla-inbound
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Wed, 26 Apr 2017 09:04:59 +0200
changeset 403171 45c2aad0e684e5608481cccd408ae3eb1afab256
parent 403170 3f1bc119c9e47de3f5ba6ecde4bc48a8243b710f (current diff)
parent 403086 0f5ba06c4c5959030a05cb852656d854065e2226 (diff)
child 403172 d12f352d634d1a29185f01cb16552e94e2d5e9d7
push id7391
push usermtabara@mozilla.com
push dateMon, 12 Jun 2017 13:08:53 +0000
treeherdermozilla-beta@2191d7f87e2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone55.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
browser/themes/shared/reader/reader-tour.png
browser/themes/shared/reader/reader-tour@2x.png
dom/events/DataContainerEvent.cpp
dom/events/DataContainerEvent.h
dom/events/test/test_bug368835.html
dom/interfaces/events/nsIDOMDataContainerEvent.idl
dom/webidl/DataContainerEvent.webidl
js/public/ProfilingStack.h
js/src/vm/GeckoProfiler.cpp
js/src/vm/GeckoProfiler.h
layout/style/res/checkmark.svg
layout/style/res/indeterminate-checkmark.svg
layout/style/res/radio.svg
mobile/android/services/src/main/java/org/mozilla/gecko/fxa/FxAccountDevice.java
mobile/android/services/src/main/java/org/mozilla/gecko/fxa/FxAccountDeviceRegistrator.java
mobile/android/tests/background/junit4/src/org/mozilla/gecko/fxa/TestFxAccountDeviceRegistrator.java
python/mozlint/test/linters/badreturncode.lint
python/mozlint/test/linters/explicit_path.lint
python/mozlint/test/linters/external.lint
python/mozlint/test/linters/invalid_exclude.lint
python/mozlint/test/linters/invalid_include.lint
python/mozlint/test/linters/invalid_type.lint
python/mozlint/test/linters/missing_attrs.lint
python/mozlint/test/linters/missing_definition.lint
python/mozlint/test/linters/raises.lint
python/mozlint/test/linters/regex.lint
python/mozlint/test/linters/string.lint
python/mozlint/test/linters/structured.lint
tools/lint/eslint.lint
tools/lint/flake8.lint
tools/lint/wpt.lint
tools/lint/wpt_manifest.lint
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -1525,17 +1525,16 @@ pref("dom.ipc.processHangMonitor", true)
 #ifdef DEBUG
 // Don't report hangs in DEBUG builds. They're too slow and often a
 // debugger is attached.
 pref("dom.ipc.reportProcessHangs", false);
 #else
 pref("dom.ipc.reportProcessHangs", true);
 #endif
 
-pref("browser.reader.detectedFirstArticle", false);
 // Don't limit how many nodes we care about on desktop:
 pref("reader.parse-node-limit", 0);
 
 // On desktop, we want the URLs to be included here for ease of debugging,
 // and because (normally) these errors are not persisted anywhere.
 pref("reader.errors.includeURLs", true);
 
 pref("view_source.tab", true);
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -463,17 +463,17 @@ const gStoragePressureObserver = {
     }
     this._lastNotificationTime = Date.now();
 
     const BYTES_IN_GIGABYTE = 1073741824;
     const USAGE_THRESHOLD_BYTES = BYTES_IN_GIGABYTE *
       Services.prefs.getIntPref("browser.storageManager.pressureNotification.usageThresholdGB");
     let msg = "";
     let buttons = [];
-    let usage = parseInt(data);
+    let usage = subject.QueryInterface(Ci.nsISupportsPRUint64).data
     let prefStrBundle = document.getElementById("bundle_preferences");
     let brandShortName = document.getElementById("bundle_brand").getString("brandShortName");
     let notificationBox = document.getElementById("high-priority-global-notificationbox");
     buttons.push({
       label: prefStrBundle.getString("spaceAlert.learnMoreButton.label"),
       accessKey: prefStrBundle.getString("spaceAlert.learnMoreButton.accesskey"),
       callback(notificationBar, button) {
         let learnMoreURL = Services.urlFormatter.formatURLPref("app.support.baseURL") + "storage-permissions";
--- a/browser/base/content/test/general/browser_storagePressure_notification.js
+++ b/browser/base/content/test/general/browser_storagePressure_notification.js
@@ -1,15 +1,18 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
 function notifyStoragePressure(usage = 100) {
   let notifyPromise = TestUtils.topicObserved("QuotaManager::StoragePressure", () => true);
-  Services.obs.notifyObservers(null, "QuotaManager::StoragePressure", usage);
+  let usageWrapper = Cc["@mozilla.org/supports-PRUint64;1"]
+                     .createInstance(Ci.nsISupportsPRUint64);
+  usageWrapper.data = usage;
+  Services.obs.notifyObservers(usageWrapper, "QuotaManager::StoragePressure");
   return notifyPromise;
 }
 
 function privacyAboutPrefPromise() {
   let promises = [
     BrowserTestUtils.waitForLocationChange(gBrowser, "about:preferences#privacy"),
     TestUtils.topicObserved("advanced-pane-loaded", () => true)
   ];
--- a/browser/base/content/test/static/browser_all_files_referenced.js
+++ b/browser/base/content/test/static/browser_all_files_referenced.js
@@ -31,20 +31,16 @@ if (AppConstants.platform == "macosx")
 var whitelist = new Set([
   // browser/extensions/pdfjs/content/PdfStreamConverter.jsm
   {file: "chrome://pdf.js/locale/chrome.properties"},
   {file: "chrome://pdf.js/locale/viewer.properties"},
 
   // security/manager/pki/resources/content/device_manager.js
   {file: "chrome://pippki/content/load_device.xul"},
 
-  // browser/modules/ReaderParent.jsm
-  {file: "chrome://browser/skin/reader-tour.png"},
-  {file: "chrome://browser/skin/reader-tour@2x.png"},
-
   // Used by setting this url as a pref in about:config
   {file: "chrome://browser/content/newtab/alternativeDefaultSites.json"},
 
   // Add-on compat
   {file: "chrome://browser/skin/devtools/common.css"},
   {file: "chrome://global/content/XPCNativeWrapper.js"},
   {file: "chrome://global/locale/brand.dtd"},
 
@@ -217,21 +213,16 @@ var whitelist = new Set([
   {file: "chrome://mozapps/skin/extensions/themeGeneric-16.png"},
   // Bug 1348556
   {file: "chrome://mozapps/skin/plugins/pluginBlocked.png"},
   // Bug 1348558
   {file: "chrome://mozapps/skin/update/downloadButtons.png",
    platforms: ["linux"]},
   // Bug 1348559
   {file: "chrome://pippki/content/resetpassword.xul"},
-
-  // Bug 1344257
-  {file: "resource://gre-resources/checkmark.svg"},
-  {file: "resource://gre-resources/indeterminate-checkmark.svg"},
-  {file: "resource://gre-resources/radio.svg"},
   // Bug 1351078
   {file: "resource://gre/modules/Battery.jsm"},
   // Bug 1351070
   {file: "resource://gre/modules/ContentPrefInstance.jsm"},
   // Bug 1351079
   {file: "resource://gre/modules/ISO8601DateUtils.jsm"},
   // Bug 1337345
   {file: "resource://gre/modules/Manifest.jsm"},
--- a/browser/base/content/test/static/browser_parsable_css.js
+++ b/browser/base/content/test/static/browser_parsable_css.js
@@ -60,17 +60,17 @@ let whitelist = [
   {sourceName: /devtools\/skin\/animationinspector\.css$/i,
    intermittent: true,
    errorMessage: /Property contained reference to invalid variable.*color/i,
    isFromDevTools: true},
 ];
 
 if (!Services.prefs.getBoolPref("full-screen-api.unprefix.enabled")) {
   whitelist.push({
-    sourceName: /res\/(ua|html)\.css$/i,
+    sourceName: /(?:res|gre-resources)\/(ua|html)\.css$/i,
     errorMessage: /Unknown pseudo-class .*\bfullscreen\b/i,
     isFromDevTools: false
   });
 }
 
 // Platform can be "linux", "macosx" or "win". If omitted, the exception applies to all platforms.
 let allowedImageReferences = [
   // Bug 1302691
--- a/browser/components/customizableui/content/panelUI.inc.xul
+++ b/browser/components/customizableui/content/panelUI.inc.xul
@@ -494,22 +494,52 @@
        class="cui-widget-panel"
        role="group"
        type="arrow"
        hidden="true"
        flip="slide"
        position="bottomcenter topright"
        noautofocus="true">
   <panelmultiview id="appMenu-multiView" mainViewId="appMenu-mainView">
-    <panelview id="appMenu-mainView" class="cui-widget-panelview">
+    <panelview id="appMenu-mainView" class="cui-widget-panelview PanelUI-subView">
       <vbox class="panel-subview-body">
         <toolbarbutton id="appMenu-new-window-button"
                        class="subviewbutton subviewbutton-iconic"
                        label="&newNavigatorCmd.label;"
+                       key="key_newNavigator"
                        command="cmd_newNavigator"/>
         <toolbarbutton id="appMenu-private-window-button"
                        class="subviewbutton subviewbutton-iconic"
                        label="&newPrivateWindow.label;"
+                       key="key_privatebrowsing"
                        command="Tools:PrivateBrowsing"/>
+        <toolbarseparator/>
+        <toolbarbutton id="appMenu-open-file-button"
+                       class="subviewbutton"
+                       label="&openFileCmd.label;"
+                       key="openFileKb"
+                       command="Browser:OpenFile"
+                       />
+        <toolbarbutton id="appMenu-save-file-button"
+                       class="subviewbutton"
+                       label="&savePageCmd.label;"
+                       key="key_savePage"
+                       command="Browser:SavePage"
+                       />
+        <toolbarbutton id="appMenu-page-setup-button"
+                       class="subviewbutton"
+                       label="&printSetupCmd.label;"
+                       command="cmd_pageSetup"
+                       />
+        <toolbarbutton id="appMenu-print-button"
+                       class="subviewbutton subviewbutton-iconic"
+                       label="&printButton.label;"
+                       key="printKb"
+#ifdef XP_MACOSX
+                       command="cmd_print"
+#else
+                       command="cmd_printPreview"
+#endif
+                       />
       </vbox>
     </panelview>
   </panelmultiview>
 </panel>
--- a/browser/components/customizableui/content/panelUI.js
+++ b/browser/components/customizableui/content/panelUI.js
@@ -144,16 +144,19 @@ const PanelUI = {
   /**
    * Opens the menu panel. If the event target has a child with the
    * toolbarbutton-icon attribute, the panel will be anchored on that child.
    * Otherwise, the panel is anchored on the event target itself.
    *
    * @param aEvent the event (if any) that triggers showing the menu.
    */
   show(aEvent) {
+    if (gPhotonStructure) {
+      this._ensureShortcutsShown();
+    }
     return new Promise(resolve => {
       this.ensureReady().then(() => {
         if (this.panel.state == "open" ||
             document.documentElement.hasAttribute("customizing")) {
           resolve();
           return;
         }
 
@@ -865,16 +868,32 @@ const PanelUI = {
 
   _getPanelAnchor(candidate) {
     let iconAnchor =
       document.getAnonymousElementByAttribute(candidate, "class",
                                               "toolbarbutton-icon");
     return iconAnchor || candidate;
   },
 
+  _addedShortcuts: false,
+  _ensureShortcutsShown() {
+    if (this._addedShortcuts) {
+      return;
+    }
+    this._addedShortcuts = true;
+    for (let button of this.mainView.querySelectorAll("toolbarbutton[key]")) {
+      let keyId = button.getAttribute("key");
+      let key = document.getElementById(keyId);
+      if (!key) {
+        continue;
+      }
+      button.setAttribute("shortcut", ShortcutUtils.prettifyShortcut(key));
+    }
+  },
+
   _notify(status, topic) {
     Services.obs.notifyObservers(window, "panelUI-notification-" + topic, status);
   }
 };
 
 XPCOMUtils.defineConstant(this, "PanelUI", PanelUI);
 
 /**
--- a/browser/components/migration/ESEDBReader.jsm
+++ b/browser/components/migration/ESEDBReader.jsm
@@ -230,17 +230,17 @@ function unloadLibraries() {
   delete gLibs.kernel;
 }
 
 function loadLibraries() {
   Services.obs.addObserver(unloadLibraries, "xpcom-shutdown");
   gLibs.ese = ctypes.open("esent.dll");
   gLibs.kernel = ctypes.open("kernel32.dll");
   KERNEL.FileTimeToSystemTime = gLibs.kernel.declare("FileTimeToSystemTime",
-    ctypes.default_abi, ctypes.int, KERNEL.FILETIME.ptr, KERNEL.SYSTEMTIME.ptr);
+    ctypes.winapi_abi, ctypes.int, KERNEL.FILETIME.ptr, KERNEL.SYSTEMTIME.ptr);
 
   declareESEFunctions();
 }
 
 function ESEDB(rootPath, dbPath, logPath) {
   log.info("Created db");
   this.rootPath = rootPath;
   this.dbPath = dbPath;
--- a/browser/components/migration/MSMigrationUtils.jsm
+++ b/browser/components/migration/MSMigrationUtils.jsm
@@ -74,17 +74,17 @@ function CtypesKernelHelpers() {
     {dwHighDateTime: wintypes.DWORD}
   ]);
 
   try {
     this._libs.kernel32 = ctypes.open("Kernel32");
 
     this._functions.FileTimeToSystemTime =
       this._libs.kernel32.declare("FileTimeToSystemTime",
-                                  ctypes.default_abi,
+                                  ctypes.winapi_abi,
                                   wintypes.BOOL,
                                   this._structs.FILETIME.ptr,
                                   this._structs.SYSTEMTIME.ptr);
   } catch (ex) {
     this.finalize();
   }
 }
 
--- a/browser/components/migration/tests/unit/test_Edge_db_migration.js
+++ b/browser/components/migration/tests/unit/test_Edge_db_migration.js
@@ -166,17 +166,17 @@ let initializedESE = false;
 
 let eseDBWritingHelpers = {
   setupDB(dbFile, tables) {
     if (!initializedESE) {
       initializedESE = true;
       loadLibraries();
 
       KERNEL.SystemTimeToFileTime = gLibs.kernel.declare("SystemTimeToFileTime",
-          ctypes.default_abi, ctypes.bool, KERNEL.SYSTEMTIME.ptr, KERNEL.FILETIME.ptr);
+          ctypes.winapi_abi, ctypes.bool, KERNEL.SYSTEMTIME.ptr, KERNEL.FILETIME.ptr);
 
       declareESEFunction("CreateDatabaseW", ESE.JET_SESID, ESE.JET_PCWSTR,
                          ESE.JET_PCWSTR, ESE.JET_DBID.ptr, ESE.JET_GRBIT);
       declareESEFunction("CreateTableColumnIndexW", ESE.JET_SESID, ESE.JET_DBID,
                          ESE.JET_TABLECREATE_W.ptr);
       declareESEFunction("BeginTransaction", ESE.JET_SESID);
       declareESEFunction("CommitTransaction", ESE.JET_SESID, ESE.JET_GRBIT);
       declareESEFunction("PrepareUpdate", ESE.JET_SESID, ESE.JET_TABLEID,
--- a/browser/components/migration/tests/unit/test_IE_cookies.js
+++ b/browser/components/migration/tests/unit/test_IE_cookies.js
@@ -17,33 +17,39 @@ add_task(function* () {
 
   /*
   BOOL InternetSetCookieW(
     _In_  LPCTSTR lpszUrl,
     _In_  LPCTSTR lpszCookieName,
     _In_  LPCTSTR lpszCookieData
   );
   */
+  // NOTE: Even though MSDN documentation does not indicate a calling convention,
+  // InternetSetCookieW is declared in SDK headers as __stdcall but is exported
+  // from wininet.dll without name mangling, so it is effectively winapi_abi
   let setIECookie = wininet.declare("InternetSetCookieW",
-                                    ctypes.default_abi,
+                                    ctypes.winapi_abi,
                                     BOOL,
                                     LPCTSTR,
                                     LPCTSTR,
                                     LPCTSTR);
 
   /*
   BOOL InternetGetCookieW(
     _In_    LPCTSTR lpszUrl,
     _In_    LPCTSTR lpszCookieName,
     _Out_   LPCTSTR  lpszCookieData,
     _Inout_ LPDWORD lpdwSize
   );
   */
+  // NOTE: Even though MSDN documentation does not indicate a calling convention,
+  // InternetGetCookieW is declared in SDK headers as __stdcall but is exported
+  // from wininet.dll without name mangling, so it is effectively winapi_abi
   let getIECookie = wininet.declare("InternetGetCookieW",
-                                    ctypes.default_abi,
+                                    ctypes.winapi_abi,
                                     BOOL,
                                     LPCTSTR,
                                     LPCTSTR,
                                     LPCTSTR,
                                     LPDWORD);
 
   // We need to randomize the cookie to avoid clashing with other cookies
   // that might have been set by previous tests and not properly cleared.
--- a/browser/components/preferences/in-content-old/tests/browser_bug731866.js
+++ b/browser/components/preferences/in-content-old/tests/browser_bug731866.js
@@ -1,30 +1,49 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 Components.utils.import("resource://gre/modules/PlacesUtils.jsm");
 Components.utils.import("resource://gre/modules/NetUtil.jsm");
 
+const storageManagerDisabled = !SpecialPowers.getBoolPref("browser.storageManager.enabled");
+const offlineGroupDisabled = !SpecialPowers.getBoolPref("browser.preferences.offlineGroup.enabled");
+
 function test() {
   waitForExplicitFinish();
   open_preferences(runTest);
 }
 
 var gElements;
 
 function checkElements(expectedPane) {
   for (let element of gElements) {
     // keyset and preferences elements fail is_element_visible checks because they are never visible.
     // special-case the drmGroup item because its visibility depends on pref + OS version
     if (element.nodeName == "keyset" ||
         element.nodeName == "preferences" ||
         element.id === "drmGroup") {
       continue;
     }
+    // The siteDataGroup in the Storage Management project is currently only pref-on on Nightly for testing purpose.
+    // During the test and the transition period, we have to check the pref to see if the siteDataGroup
+    // should be hidden always. This would be a bit bothersome, same as the offlineGroup as below.
+    // However, this checking is necessary to make sure we don't leak the siteDataGroup into beta/release build
+    if (element.id == "siteDataGroup" && storageManagerDisabled) {
+      is_element_hidden(element, "Disabled siteDataGroup should be hidden");
+      continue;
+    }
+    // The siteDataGroup in the Storage Management project will replace the offlineGroup eventually,
+    // so during the transition period, we have to check the pref to see if the offlineGroup
+    // should be hidden always. See the bug 1354530 for the details.
+    if (element.id == "offlineGroup" && offlineGroupDisabled) {
+      is_element_hidden(element, "Disabled offlineGroup should be hidden");
+      continue;
+    }
+
     let attributeValue = element.getAttribute("data-category");
     let suffix = " (id=" + element.id + ")";
     if (attributeValue == "pane" + expectedPane) {
       is_element_visible(element, expectedPane + " elements should be visible" + suffix);
     } else {
       is_element_hidden(element, "Elements not in " + expectedPane + " should be hidden" + suffix);
     }
   }
--- a/browser/components/preferences/in-content-old/tests/browser_bug795764_cachedisabled.js
+++ b/browser/components/preferences/in-content-old/tests/browser_bug795764_cachedisabled.js
@@ -2,60 +2,45 @@
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 Components.utils.import("resource://gre/modules/PlacesUtils.jsm");
 Components.utils.import("resource://gre/modules/NetUtil.jsm");
 
 function test() {
   waitForExplicitFinish();
 
-  let prefs = [
-    "browser.cache.offline.enable",
-    "browser.cache.disk.enable",
-    "browser.cache.memory.enable",
-  ];
-  for (let pref of prefs) {
-    Services.prefs.setBoolPref(pref, false);
-  }
-
   // Adding one fake site so that the SiteDataManager would run.
   // Otherwise, without any site then it would just return so we would end up in not testing SiteDataManager.
   let principal = Services.scriptSecurityManager.createCodebasePrincipalFromOrigin("https://www.foo.com");
   Services.perms.addFromPrincipal(principal, "persistent-storage", Ci.nsIPermissionManager.ALLOW_ACTION);
-
   registerCleanupFunction(function() {
-    for (let pref of prefs) {
-      Services.prefs.clearUserPref(pref);
-    }
     Services.perms.removeFromPrincipal(principal, "persistent-storage");
   });
 
-  open_preferences(runTest);
+  SpecialPowers.pushPrefEnv({set: [
+    ["browser.cache.offline.enable", false],
+    ["browser.cache.disk.enable", false],
+    ["browser.cache.memory.enable", false],
+    ["browser.storageManager.enabled", true],
+    ["browser.preferences.offlineGroup.enabled", true]
+  ]}).then(() => open_preferences(runTest));
 }
 
 function runTest(win) {
   is(gBrowser.currentURI.spec, "about:preferences", "about:preferences loaded");
 
   let tab = win.document;
   let elements = tab.getElementById("mainPrefPane").children;
-  let offlineGroupDisabled = !SpecialPowers.getBoolPref("browser.preferences.offlineGroup.enabled");
 
   // Test if advanced pane is opened correctly
   win.gotoPref("paneAdvanced");
   for (let element of elements) {
     if (element.nodeName == "preferences") {
       continue;
     }
-    // The siteDataGroup in the Storage Management project will replace the offlineGroup eventually,
-    // so during the transition period, we have to check the pref to see if the offlineGroup
-    // should be hidden always. See the bug 1354530 for the details.
-    if (element.id == "offlineGroup" && offlineGroupDisabled) {
-      is_element_hidden(element, "Disabled offlineGroup should be hidden");
-      continue;
-    }
     let attributeValue = element.getAttribute("data-category");
     if (attributeValue == "paneAdvanced") {
       is_element_visible(element, "Advanced elements should be visible");
     } else {
       is_element_hidden(element, "Non-Advanced elements should be hidden");
     }
   }
 
--- a/browser/components/preferences/in-content/preferences.js
+++ b/browser/components/preferences/in-content/preferences.js
@@ -143,24 +143,25 @@ function telemetryBucketForCategory(cate
 function onHashChange() {
   gotoPref();
 }
 
 function gotoPref(aCategory) {
   let categories = document.getElementById("categories");
   const kDefaultCategoryInternalName = "paneGeneral";
   let hash = document.location.hash;
+
+  let category = aCategory || hash.substr(1) || kDefaultCategoryInternalName;
+  let breakIndex = category.indexOf("-");
   // Subcategories allow for selecting smaller sections of the preferences
   // until proper search support is enabled (bug 1353954).
-  let breakIndex = hash.indexOf("-");
-  let subcategory = breakIndex != -1 && hash.substring(breakIndex + 1);
+  let subcategory = breakIndex != -1 && category.substring(breakIndex + 1);
   if (subcategory) {
-    hash = hash.substring(0, breakIndex);
+    category = category.substring(0, breakIndex);
   }
-  let category = aCategory || hash.substr(1) || kDefaultCategoryInternalName;
   category = friendlyPrefCategoryNameToInternalName(category);
 
   // Updating the hash (below) or changing the selected category
   // will re-enter gotoPref.
   if (gLastHash == category && !subcategory)
     return;
   let item = categories.querySelector(".category[value=" + category + "]");
   if (!item) {
--- a/browser/components/preferences/in-content/privacy.xul
+++ b/browser/components/preferences/in-content/privacy.xul
@@ -606,16 +606,17 @@
                 label="&browserContainersEnabled.label;"
                 accesskey="&browserContainersEnabled.accesskey;"
                 preference="privacy.userContext.enabled"
                 onsyncfrompreference="return gPrivacyPane.readBrowserContainersCheckbox();"/>
       <label id="browserContainersLearnMore" class="learnMore text-link"
              value="&browserContainersLearnMore.label;"/>
       <spacer flex="1"/>
       <button id="browserContainersSettings"
+              class="accessory-button"
               label="&browserContainersSettings.label;"
               accesskey="&browserContainersSettings.accesskey;"/>
     </hbox>
   </vbox>
 </groupbox>
 
 <!-- Network -->
 <!-- Connection -->
--- a/browser/components/preferences/in-content/tests/browser_bug1020245_openPreferences_to_paneContent.js
+++ b/browser/components/preferences/in-content/tests/browser_bug1020245_openPreferences_to_paneContent.js
@@ -2,16 +2,17 @@
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 Services.prefs.setBoolPref("browser.preferences.instantApply", true);
 
 registerCleanupFunction(function() {
   Services.prefs.clearUserPref("browser.preferences.instantApply");
 });
 
+// Test opening to the differerent panes and subcategories in Preferences
 add_task(function*() {
   let prefs = yield openPreferencesViaOpenPreferencesAPI("panePrivacy");
   is(prefs.selectedPane, "panePrivacy", "Privacy pane was selected");
   prefs = yield openPreferencesViaOpenPreferencesAPI("advanced");
   is(prefs.selectedPane, "paneAdvanced", "Advanced pane was selected");
   prefs = yield openPreferencesViaHash("privacy");
   is(prefs.selectedPane, "panePrivacy", "Privacy pane is selected when hash is 'privacy'");
   prefs = yield openPreferencesViaOpenPreferencesAPI("nonexistant-category");
@@ -31,16 +32,38 @@ add_task(function*() {
   is(prefs.selectedPane, "paneGeneral", "General pane is selected by default");
   doc = gBrowser.contentDocument;
   is(doc.location.hash, "#general", "The subcategory should be removed from the URI");
   ok(doc.querySelector("#startupGroup").hidden, "Startup should be hidden when only Search is requested");
   ok(!doc.querySelector("#engineList").hidden, "The search engine list should be visible when Search is requested");
   yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
 });
 
+// Test opening Preferences with subcategory on an existing Preferences tab. See bug 1358475.
+add_task(function*() {
+  let prefs = yield openPreferencesViaOpenPreferencesAPI("general-search", {leaveOpen: true});
+  is(prefs.selectedPane, "paneGeneral", "General pane is selected by default");
+  let doc = gBrowser.contentDocument;
+  is(doc.location.hash, "#general", "The subcategory should be removed from the URI");
+  ok(doc.querySelector("#startupGroup").hidden, "Startup should be hidden when only Search is requested");
+  ok(!doc.querySelector("#engineList").hidden, "The search engine list should be visible when Search is requested");
+  // The reasons that here just call the `openPreferences` API without the helping function are
+  //   - already opened one about:preferences tab up there and
+  //   - the goal is to test on the existing tab and
+  //   - using `openPreferencesViaOpenPreferencesAPI` would introduce more handling of additional about:blank and unneccessary event
+  openPreferences("privacy-reports");
+  let selectedPane = gBrowser.contentWindow.history.state;
+  is(selectedPane, "panePrivacy", "Privacy pane should be selected");
+  is(doc.location.hash, "#privacy", "The subcategory should be removed from the URI");
+  ok(doc.querySelector("#locationBarGroup").hidden, "Location Bar prefs should be hidden when only Reports are requested");
+  ok(!doc.querySelector("#header-privacy").hidden, "The header should be visible when a subcategory is requested");
+  yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
+});
+
+
 function openPreferencesViaHash(aPane) {
   let deferred = Promise.defer();
   gBrowser.selectedTab = gBrowser.addTab("about:preferences" + (aPane ? "#" + aPane : ""));
   let newTabBrowser = gBrowser.selectedBrowser;
 
   newTabBrowser.addEventListener("Initialized", function() {
     newTabBrowser.contentWindow.addEventListener("load", function() {
       let win = gBrowser.contentWindow;
--- a/browser/components/preferences/in-content/tests/browser_bug731866.js
+++ b/browser/components/preferences/in-content/tests/browser_bug731866.js
@@ -1,37 +1,45 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 Components.utils.import("resource://gre/modules/PlacesUtils.jsm");
 Components.utils.import("resource://gre/modules/NetUtil.jsm");
 
+const storageManagerDisabled = !SpecialPowers.getBoolPref("browser.storageManager.enabled");
+const offlineGroupDisabled = !SpecialPowers.getBoolPref("browser.preferences.offlineGroup.enabled");
+
 function test() {
   waitForExplicitFinish();
-  SpecialPowers.pushPrefEnv({set: [["browser.storageManager.enabled", true]]});
   open_preferences(runTest);
 }
 
 var gElements;
 
 function checkElements(expectedPane) {
   for (let element of gElements) {
     // keyset and preferences elements fail is_element_visible checks because they are never visible.
     // special-case the drmGroup item because its visibility depends on pref + OS version
     if (element.nodeName == "keyset" ||
         element.nodeName == "preferences" ||
         element.id === "drmGroup") {
       continue;
     }
-
+    // The siteDataGroup in the Storage Management project is currently only pref-on on Nightly for testing purpose.
+    // During the test and the transition period, we have to check the pref to see if the siteDataGroup
+    // should be hidden always. This would be a bit bothersome, same as the offlineGroup as below.
+    // However, this checking is necessary to make sure we don't leak the siteDataGroup into beta/release build
+    if (element.id == "siteDataGroup" && storageManagerDisabled) {
+      is_element_hidden(element, "Disabled siteDataGroup should be hidden");
+      continue;
+    }
     // The siteDataGroup in the Storage Management project will replace the offlineGroup eventually,
     // so during the transition period, we have to check the pref to see if the offlineGroup
     // should be hidden always. See the bug 1354530 for the details.
-    if (element.id == "offlineGroup" &&
-        !SpecialPowers.getBoolPref("browser.preferences.offlineGroup.enabled")) {
+    if (element.id == "offlineGroup" && offlineGroupDisabled) {
       is_element_hidden(element, "Disabled offlineGroup should be hidden");
       continue;
     }
 
     let attributeValue = element.getAttribute("data-category");
     let suffix = " (id=" + element.id + ")";
     if (attributeValue == "pane" + expectedPane) {
       is_element_visible(element, expectedPane + " elements should be visible" + suffix);
--- a/browser/components/preferences/in-content/tests/browser_bug795764_cachedisabled.js
+++ b/browser/components/preferences/in-content/tests/browser_bug795764_cachedisabled.js
@@ -3,60 +3,45 @@
 
 const { interfaces: Ci, utils: Cu } = Components;
 Cu.import("resource://gre/modules/PlacesUtils.jsm");
 Cu.import("resource://gre/modules/NetUtil.jsm");
 
 function test() {
   waitForExplicitFinish();
 
-  let prefs = [
-    "browser.cache.offline.enable",
-    "browser.cache.disk.enable",
-    "browser.cache.memory.enable",
-  ];
-  for (let pref of prefs) {
-    Services.prefs.setBoolPref(pref, false);
-  }
-
   // Adding one fake site so that the SiteDataManager would run.
   // Otherwise, without any site then it would just return so we would end up in not testing SiteDataManager.
   let principal = Services.scriptSecurityManager.createCodebasePrincipalFromOrigin("https://www.foo.com");
   Services.perms.addFromPrincipal(principal, "persistent-storage", Ci.nsIPermissionManager.ALLOW_ACTION);
-
   registerCleanupFunction(function() {
-    for (let pref of prefs) {
-      Services.prefs.clearUserPref(pref);
-    }
     Services.perms.removeFromPrincipal(principal, "persistent-storage");
   });
 
-  open_preferences(runTest);
+  SpecialPowers.pushPrefEnv({set: [
+    ["browser.cache.offline.enable", false],
+    ["browser.cache.disk.enable", false],
+    ["browser.cache.memory.enable", false],
+    ["browser.storageManager.enabled", true],
+    ["browser.preferences.offlineGroup.enabled", true]
+  ]}).then(() => open_preferences(runTest));
 }
 
 function runTest(win) {
   is(gBrowser.currentURI.spec, "about:preferences", "about:preferences loaded");
 
   let tab = win.document;
   let elements = tab.getElementById("mainPrefPane").children;
-  let offlineGroupDisabled = !SpecialPowers.getBoolPref("browser.preferences.offlineGroup.enabled");
 
   // Test if privacy pane is opened correctly
   win.gotoPref("panePrivacy");
   for (let element of elements) {
     if (element.nodeName == "preferences") {
       continue;
     }
-    // The siteDataGroup in the Storage Management project will replace the offlineGroup eventually,
-    // so during the transition period, we have to check the pref to see if the offlineGroup
-    // should be hidden always. See the bug 1354530 for the details.
-    if (element.id == "offlineGroup" && offlineGroupDisabled) {
-      is_element_hidden(element, "Disabled offlineGroup should be hidden");
-      continue;
-    }
     let attributeValue = element.getAttribute("data-category");
     if (attributeValue == "panePrivacy") {
       is_element_visible(element, "Privacy elements should be visible");
     } else {
       is_element_hidden(element, "Non-Privacy elements should be hidden");
     }
   }
 
--- a/browser/extensions/e10srollout/bootstrap.js
+++ b/browser/extensions/e10srollout/bootstrap.js
@@ -6,23 +6,29 @@
 
 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 
 Cu.import("resource://gre/modules/Preferences.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
 Cu.import("resource://gre/modules/UpdateUtils.jsm");
 Cu.import("resource://gre/modules/AppConstants.jsm");
 
- // The amount of people to be part of e10s
+// The amount of people to be part of e10s
 const TEST_THRESHOLD = {
   "beta": 0.9,  // 90%
   "release": 1.0,  // 100%
   "esr": 1.0,  // 100%
 };
 
+// If a user qualifies for the e10s-multi experiement, this is how many
+// content processes to use.
+const MULTI_BUCKETS = {
+  "beta": { 1: .5, 4: 1, },
+};
+
 const ADDON_ROLLOUT_POLICY = {
   "beta": "50allmpc",
   "release": "50allmpc",
   "esr": "esrA", // WebExtensions and Addons with mpc=true
 };
 
 if (AppConstants.RELEASE_OR_BETA) {
   // Bug 1348576 - e10s is never enabled for non-official release builds
@@ -115,24 +121,24 @@ function defineCohort() {
 
   let cohortPrefix = "";
   if (disqualified) {
     cohortPrefix = "disqualified-";
   } else if (hasNonExemptAddon) {
     cohortPrefix = `addons-set${addonPolicy}-`;
   }
 
-  let inMultiExperiment = false;
+  let eligibleForMulti = false;
   if (userOptedOut.e10s || userOptedOut.multi) {
     // If we detected that the user opted out either for multi or e10s, then
     // the proper prefs must already be set.
     setCohort("optedOut");
   } else if (userOptedIn.e10s) {
     setCohort("optedIn");
-    inMultiExperiment = true;
+    eligibleForMulti = true;
   } else if (temporaryDisqualification != "") {
     // Users who are disqualified by the backend (from multiprocessBlockPolicy)
     // can be put into either the test or control groups, because e10s will
     // still be denied by the backend, which is useful so that the E10S_STATUS
     // telemetry probe can be correctly set.
 
     // For these volatile disqualification reasons, however, we must not try
     // to activate e10s because the backend doesn't know about it. E10S_STATUS
@@ -142,56 +148,59 @@ function defineCohort() {
     Preferences.reset(PREF_E10S_PROCESSCOUNT + ".web");
   } else if (!disqualified && testThreshold < 1.0 &&
              temporaryQualification != "") {
     // Users who are qualified for e10s and on channels where some population
     // would not receive e10s can be pushed into e10s anyway via a temporary
     // qualification which overrides the user sample value when non-empty.
     setCohort(`temp-qualified-${temporaryQualification}`);
     Preferences.set(PREF_TOGGLE_E10S, true);
-    inMultiExperiment = true;
+    eligibleForMulti = true;
   } else if (testGroup) {
     setCohort(`${cohortPrefix}test`);
     Preferences.set(PREF_TOGGLE_E10S, true);
-    inMultiExperiment = true;
+    eligibleForMulti = true;
   } else {
     setCohort(`${cohortPrefix}control`);
     Preferences.reset(PREF_TOGGLE_E10S);
     Preferences.reset(PREF_E10S_PROCESSCOUNT + ".web");
   }
 
   // Now determine if this user should be in the e10s-multi experiment.
-  // - We only run the experiment on the beta channel.
+  // - We only run the experiment on channels defined in MULTI_BUCKETS.
   // - We decided above whether this user qualifies for the experiment.
   // - If the user already opted into multi, then their prefs are already set
   //   correctly, we're done.
   // - If the user has addons that disqualify them for multi, leave them with
   //   the default number of content processes (1 on beta) but still in the
   //   test cohort.
-  if (updateChannel !== "beta" ||
-      !inMultiExperiment ||
+  if (!(updateChannel in MULTI_BUCKETS) ||
+      !eligibleForMulti ||
       userOptedIn.multi ||
+      disqualified ||
       getAddonsDisqualifyForMulti()) {
     Preferences.reset(PREF_E10S_PROCESSCOUNT + ".web");
     return;
   }
 
+  // If we got here with a cohortPrefix, it must be "addons-set50allmpc-",
+  // and we know because of getAddonsDisqualifyForMulti that the addons that
+  // are installed must be web extensions.
+  if (cohortPrefix) {
+    cohortPrefix = "webextensions-";
+  }
+
   // The user is in the multi experiment!
   // Decide how many content processes to use for this user.
-  let BUCKETS = {
-    1: .25,
-    2: .5,
-    4: .75,
-    8: 1
-  };
+  let buckets = MULTI_BUCKETS[updateChannel];
 
   let multiUserSample = getUserSample(true);
-  for (let sampleName of Object.getOwnPropertyNames(BUCKETS)) {
-    if (multiUserSample < BUCKETS[sampleName]) {
-      setCohort(`multiBucket${sampleName}`);
+  for (let sampleName of Object.getOwnPropertyNames(buckets)) {
+    if (multiUserSample < buckets[sampleName]) {
+      setCohort(`${cohortPrefix}multiBucket${sampleName}`);
       Preferences.set(PREF_E10S_PROCESSCOUNT + ".web", sampleName);
       break;
     }
   }
 }
 
 function shutdown(data, reason) {
 }
--- a/browser/extensions/e10srollout/install.rdf.in
+++ b/browser/extensions/e10srollout/install.rdf.in
@@ -5,17 +5,17 @@
 
 #filter substitution
 
 <RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
      xmlns:em="http://www.mozilla.org/2004/em-rdf#">
 
   <Description about="urn:mozilla:install-manifest">
     <em:id>e10srollout@mozilla.org</em:id>
-    <em:version>1.15</em:version>
+    <em:version>1.50</em:version>
     <em:type>2</em:type>
     <em:bootstrap>true</em:bootstrap>
     <em:multiprocessCompatible>true</em:multiprocessCompatible>
 
     <!-- Target Application this theme can install into,
         with minimum and maximum supported versions. -->
     <em:targetApplication>
       <Description>
--- a/browser/extensions/formautofill/content/manageProfiles.js
+++ b/browser/extensions/formautofill/content/manageProfiles.js
@@ -59,16 +59,18 @@ ManageProfileDialog.prototype = {
   /**
    * Load profiles and render them.
    *
    * @returns {promise}
    */
   loadProfiles() {
     return this.getProfiles().then(profiles => {
       log.debug("profiles:", profiles);
+      // Sort by last modified time starting with most recent
+      profiles.sort((a, b) => b.timeLastModified - a.timeLastModified);
       this.renderProfileElements(profiles);
       this.updateButtonsStates(this._selectedOptions.length);
     });
   },
 
   /**
    * Get profiles from storage.
    *
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -758,21 +758,16 @@ flashHang.helpButton.accesskey = L
 # be replaced with a hyperlink containing the text defined in customizeTips.tip0.learnMore.
 customizeTips.tip0 = %1$S: You can customize %2$S to work the way you do. Simply drag any of the above to the menu or toolbar. %3$S about customizing %2$S.
 customizeTips.tip0.hint = Hint
 customizeTips.tip0.learnMore = Learn more
 
 # LOCALIZATION NOTE (customizeMode.tabTitle): %S is brandShortName
 customizeMode.tabTitle = Customize %S
 
-# LOCALIZATION NOTE : FILE Reader View is a feature name and therefore typically used as a proper noun.
-
-readingList.promo.firstUse.readerView.title = Reader View
-readingList.promo.firstUse.readerView.body = Remove clutter so you can focus exactly on what you want to read.
-
 # LOCALIZATION NOTE (appMenuRemoteTabs.mobilePromo.text2):
 # %1$S will be replaced with a link, the text of which is
 # appMenuRemoteTabs.mobilePromo.android and the link will be to
 # https://www.mozilla.org/firefox/android/.
 # %2$S will be replaced with a link, the text of which is
 # appMenuRemoteTabs.mobilePromo.ios
 # and the link will be to https://www.mozilla.org/firefox/ios/.
 appMenuRemoteTabs.mobilePromo.text2 = Download %1$S or %2$S and connect them to your Firefox Account.
--- a/browser/locales/shipped-locales
+++ b/browser/locales/shipped-locales
@@ -1,15 +1,16 @@
 ach
 af
 an
 ar
 as
 ast
 az
+be
 bg
 bn-BD
 bn-IN
 br
 bs
 ca
 cak
 cs
--- a/browser/modules/ReaderParent.jsm
+++ b/browser/modules/ReaderParent.jsm
@@ -15,18 +15,16 @@ Cu.import("resource://gre/modules/Task.j
 
 XPCOMUtils.defineLazyModuleGetter(this, "PlacesUtils", "resource://gre/modules/PlacesUtils.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ReaderMode", "resource://gre/modules/ReaderMode.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "UITour", "resource:///modules/UITour.jsm");
 
 const gStringBundle = Services.strings.createBundle("chrome://global/locale/aboutReader.properties");
 
 var ReaderParent = {
-  _readerModeInfoPanelOpen: false,
-
   MESSAGES: [
     "Reader:ArticleGet",
     "Reader:FaviconRequest",
     "Reader:UpdateReaderButton",
   ],
 
   init() {
     let mm = Cc["@mozilla.org/globalmessagemanager;1"].getService(Ci.nsIMessageListenerManager);
@@ -103,30 +101,16 @@ var ReaderParent = {
       button.hidden = !browser.isArticle;
       let enterText = gStringBundle.GetStringFromName("readerView.enter");
       button.setAttribute("tooltiptext", enterText);
       command.setAttribute("label", enterText);
       command.setAttribute("hidden", !browser.isArticle);
       command.setAttribute("accesskey", gStringBundle.GetStringFromName("readerView.enter.accesskey"));
       key.setAttribute("disabled", !browser.isArticle);
     }
-
-    let currentUriHost = browser.currentURI && browser.currentURI.asciiHost;
-    if (browser.isArticle &&
-        !Services.prefs.getBoolPref("browser.reader.detectedFirstArticle") &&
-        currentUriHost && !currentUriHost.endsWith("mozilla.org")) {
-      this.showReaderModeInfoPanel(browser);
-      Services.prefs.setBoolPref("browser.reader.detectedFirstArticle", true);
-      this._readerModeInfoPanelOpen = true;
-    } else if (this._readerModeInfoPanelOpen) {
-      if (UITour.isInfoOnTarget(win, "readerMode-urlBar")) {
-        UITour.hideInfo(win);
-      }
-      this._readerModeInfoPanelOpen = false;
-    }
   },
 
   forceShowReaderIcon(browser) {
     browser.isArticle = true;
     this.updateReaderButton(browser);
   },
 
   buttonClick(event) {
@@ -138,39 +122,16 @@ var ReaderParent = {
 
   toggleReaderMode(event) {
     let win = event.target.ownerGlobal;
     let browser = win.gBrowser.selectedBrowser;
     browser.messageManager.sendAsyncMessage("Reader:ToggleReaderMode");
   },
 
   /**
-   * Shows an info panel from the UITour for Reader Mode.
-   *
-   * @param browser The <browser> that the tour should be started for.
-   */
-  showReaderModeInfoPanel(browser) {
-    let win = browser.ownerGlobal;
-    let targetPromise = UITour.getTarget(win, "readerMode-urlBar");
-    targetPromise.then(target => {
-      let browserBundle = Services.strings.createBundle("chrome://browser/locale/browser.properties");
-      let icon = "chrome://browser/skin/";
-      if (win.devicePixelRatio > 1) {
-        icon += "reader-tour@2x.png";
-      } else {
-        icon += "reader-tour.png";
-      }
-      UITour.showInfo(win, target,
-                      browserBundle.GetStringFromName("readingList.promo.firstUse.readerView.title"),
-                      browserBundle.GetStringFromName("readingList.promo.firstUse.readerView.body"),
-                      icon);
-    });
-  },
-
-  /**
    * Gets an article for a given URL. This method will download and parse a document.
    *
    * @param url The article URL.
    * @param browser The browser where the article is currently loaded.
    * @return {Promise}
    * @resolves JS object representing the article, or null if no article is found.
    */
   _getArticle: Task.async(function* (url, browser) {
--- a/browser/themes/shared/customizableui/panelUI.inc.css
+++ b/browser/themes/shared/customizableui/panelUI.inc.css
@@ -1793,16 +1793,8 @@ menuitem[checked="true"].subviewbutton >
   #PanelUI-panic-actionlist-newwindow {
     background-image: -moz-image-rect(url(chrome://browser/skin/panic-panel/icons@2x.png), 0, 128, 32, 96);
   }
 }
 
 .subviewbutton-iconic > .toolbarbutton-text {
   padding-inline-start: 5px;
 }
-
-#appMenu-new-window-button {
-  list-style-image: url(chrome://browser/skin/menu-icons/new-window.svg);
-}
-
-#appMenu-private-window-button {
-  list-style-image: url(chrome://browser/skin/menu-icons/private-window.svg);
-}
--- a/browser/themes/shared/jar.inc.mn
+++ b/browser/themes/shared/jar.inc.mn
@@ -120,18 +120,16 @@
   skin/classic/browser/warning-white.svg                       (../shared/warning-white.svg)
   skin/classic/browser/cert-error.svg                          (../shared/incontent-icons/cert-error.svg)
   skin/classic/browser/wifi.svg                                (../shared/incontent-icons/wifi.svg)
   skin/classic/browser/session-restore.svg                     (../shared/incontent-icons/session-restore.svg)
   skin/classic/browser/tab-crashed.svg                         (../shared/incontent-icons/tab-crashed.svg)
   skin/classic/browser/favicon-search-16.svg                   (../shared/favicon-search-16.svg)
   skin/classic/browser/icon-search-64.svg                      (../shared/incontent-icons/icon-search-64.svg)
   skin/classic/browser/welcome-back.svg                        (../shared/incontent-icons/welcome-back.svg)
-  skin/classic/browser/reader-tour.png                         (../shared/reader/reader-tour.png)
-  skin/classic/browser/reader-tour@2x.png                      (../shared/reader/reader-tour@2x.png)
   skin/classic/browser/readerMode.svg                          (../shared/reader/readerMode.svg)
   skin/classic/browser/panic-panel/header.png                  (../shared/panic-panel/header.png)
   skin/classic/browser/panic-panel/header@2x.png               (../shared/panic-panel/header@2x.png)
   skin/classic/browser/panic-panel/header-small.png            (../shared/panic-panel/header-small.png)
   skin/classic/browser/panic-panel/header-small@2x.png         (../shared/panic-panel/header-small@2x.png)
   skin/classic/browser/panic-panel/icons.png                   (../shared/panic-panel/icons.png)
   skin/classic/browser/panic-panel/icons@2x.png                (../shared/panic-panel/icons@2x.png)
   skin/classic/browser/privatebrowsing/aboutPrivateBrowsing.css (../shared/privatebrowsing/aboutPrivateBrowsing.css)
@@ -141,9 +139,10 @@
   skin/classic/browser/privatebrowsing/tracking-protection-off.svg (../shared/privatebrowsing/tracking-protection-off.svg)
   skin/classic/browser/privatebrowsing/tracking-protection.svg (../shared/privatebrowsing/tracking-protection.svg)
   skin/classic/browser/compacttheme/loading-inverted.png (../shared/compacttheme/loading-inverted.png)
   skin/classic/browser/compacttheme/loading-inverted@2x.png (../shared/compacttheme/loading-inverted@2x.png)
   skin/classic/browser/compacttheme/urlbar-history-dropmarker.svg (../shared/compacttheme/urlbar-history-dropmarker.svg)
   skin/classic/browser/urlbar-star.svg                         (../shared/urlbar-star.svg)
   skin/classic/browser/urlbar-tab.svg                          (../shared/urlbar-tab.svg)
   skin/classic/browser/menu-icons/new-window.svg               (../shared/menu-icons/new-window.svg)
+  skin/classic/browser/menu-icons/print.svg                    (../shared/menu-icons/print.svg)
   skin/classic/browser/menu-icons/private-window.svg           (../shared/menu-icons/private-window.svg)
--- a/browser/themes/shared/menu-icons/new-window.svg
+++ b/browser/themes/shared/menu-icons/new-window.svg
@@ -1,3 +1,6 @@
+<!-- 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/. -->
 <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16">
   <path fill="context-fill" d="M14 1H2a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h5a1 1 0 0 0 0-2H3a1 1 0 0 1-1-1V6h12v2a1 1 0 0 0 2 0V3a2 2 0 0 0-2-2zm0 4H2V4a1 1 0 0 1 1-1h10a1 1 0 0 1 1 1zm1.5 7H13V9.5a.5.5 0 1 0-1 0V12H9.5a.5.5 0 0 0 0 1H12v2.5a.5.5 0 0 0 1 0V13h2.5a.5.5 0 0 0 0-1z"/>
 </svg>
new file mode 100644
--- /dev/null
+++ b/browser/themes/shared/menu-icons/print.svg
@@ -0,0 +1,6 @@
+<!-- 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/. -->
+<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16">
+  <path fill="context-fill" d="M14 5h-1V1a1 1 0 0 0-1-1H4a1 1 0 0 0-1 1v4H2a2 2 0 0 0-2 2v5h3v3a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1v-3h3V7a2 2 0 0 0-2-2zM2.5 8a.5.5 0 1 1 .5-.5.5.5 0 0 1-.5.5zm9.5 7H4v-5h8zm0-10H4V1h8zm-6.5 7h4a.5.5 0 0 0 0-1h-4a.5.5 0 1 0 0 1zm0 2h5a.5.5 0 0 0 0-1h-5a.5.5 0 1 0 0 1z"/>
+</svg>
--- a/browser/themes/shared/menu-icons/private-window.svg
+++ b/browser/themes/shared/menu-icons/private-window.svg
@@ -1,3 +1,6 @@
+<!-- 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/. -->
 <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16">
   <path fill="context-fill" d="M12.377 11.961c-1.651 0-2.793-1.98-4.377-1.98s-2.824 1.98-4.377 1.98c-2.037 0-3.541-1.924-3.566-5.221-.015-2.047.6-2.7 3.242-2.7S6.719 5.12 8 5.12s2.056-1.08 4.7-1.08 3.257.653 3.242 2.7c-.024 3.297-1.528 5.221-3.565 5.221zM4.6 6.56c-1.607.07-2.269 1.025-2.269 1.26s1.066.9 2.107.9S6.7 8.339 6.7 8a1.889 1.889 0 0 0-2.1-1.44zm6.808 0A1.889 1.889 0 0 0 9.3 8c0 .339 1.228.72 2.269.72s2.107-.665 2.107-.9-.664-1.191-2.276-1.26z"/>
 </svg>
--- a/browser/themes/shared/menupanel.inc.css
+++ b/browser/themes/shared/menupanel.inc.css
@@ -176,8 +176,20 @@ toolbarpaletteitem[place="palette"] > #z
   -moz-image-region: rect(0px, 96px, 16px, 80px);
 }
 
 #add-share-provider {
   list-style-image: url(chrome://browser/skin/menuPanel-small.svg);
   -moz-image-region: rect(0px, 96px, 16px, 80px);
 }
 
+
+#appMenu-new-window-button {
+  list-style-image: url(chrome://browser/skin/menu-icons/new-window.svg);
+}
+
+#appMenu-private-window-button {
+  list-style-image: url(chrome://browser/skin/menu-icons/private-window.svg);
+}
+
+#appMenu-print-button {
+  list-style-image: url(chrome://browser/skin/menu-icons/print.svg);
+}
deleted file mode 100644
index be346b3847928f94d7a0bbca019f40d86edd0899..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
deleted file mode 100644
index 1a60d93ca9311f8bf9e0ce3f6f175370a69f648e..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391
GIT binary patch
literal 0
Hc$@<O00001
--- a/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/Preferences.jsm
+++ b/browser/tools/mozscreenshots/mozscreenshots/extension/configurations/Preferences.jsm
@@ -12,51 +12,72 @@ Cu.import("resource://gre/modules/Servic
 Cu.import("resource://gre/modules/Task.jsm");
 Cu.import("resource://testing-common/TestUtils.jsm");
 Cu.import("resource://testing-common/ContentTask.jsm");
 
 this.Preferences = {
 
   init(libDir) {
     let panes = [
-      ["paneGeneral", null],
-      ["paneSearch", null],
-      ["paneContent", null],
-      ["paneApplications", null],
-      ["panePrivacy", null],
-      ["panePrivacy", null, DNTDialog],
-      ["panePrivacy", null, clearRecentHistoryDialog],
-      ["paneSecurity", null],
-      ["paneSync", null],
-      ["paneAdvanced", "generalTab"],
-      ["paneAdvanced", "dataChoicesTab"],
-      ["paneAdvanced", "networkTab"],
-      ["paneAdvanced", "networkTab", connectionDialog],
-      ["paneAdvanced", "updateTab"],
-      ["paneAdvanced", "encryptionTab"],
-      ["paneAdvanced", "encryptionTab", certManager],
-      ["paneAdvanced", "encryptionTab", deviceManager],
+      /* The "new" organization */
+      ["paneGeneral"],
+      ["paneGeneral", scrollToBrowsingGroup],
+      ["paneApplications"],
+      ["paneSync"],
+      ["panePrivacy"],
+      ["panePrivacy", scrollToCacheGroup],
+      ["panePrivacy", DNTDialog],
+      ["panePrivacy", clearRecentHistoryDialog],
+      ["panePrivacy", connectionDialog],
+      ["panePrivacy", certManager],
+      ["panePrivacy", deviceManager],
+      ["paneAdvanced"],
+
+      /* The "old" organization. The third argument says to
+         set the pref to show the old organization when
+         opening the preferences. */
+      ["paneGeneral", null, true],
+      ["paneSearch", null, true],
+      ["paneContent", null, true],
+      ["paneApplications", null, true],
+      ["panePrivacy", null, true],
+      ["panePrivacy", DNTDialog, true],
+      ["panePrivacy", clearRecentHistoryDialog, true],
+      ["paneSecurity", null, true],
+      ["paneSync", null, true],
+      ["paneAdvanced", null, true, "generalTab"],
+      ["paneAdvanced", null, true, "dataChoicesTab"],
+      ["paneAdvanced", null, true, "networkTab"],
+      ["paneAdvanced", connectionDialog, true, "networkTab"],
+      ["paneAdvanced", null, true, "updateTab"],
+      ["paneAdvanced", null, true, "encryptionTab"],
+      ["paneAdvanced", certManager, true, "encryptionTab"],
+      ["paneAdvanced", deviceManager, true, "encryptionTab"],
     ];
-    for (let [primary, advanced, customFn] of panes) {
+    for (let [primary, customFn, useOldOrg, advanced] of panes) {
       let configName = primary.replace(/^pane/, "prefs") + (advanced ? "-" + advanced : "");
       if (customFn) {
         configName += "-" + customFn.name;
       }
       this.configurations[configName] = {};
-      this.configurations[configName].applyConfig = prefHelper.bind(null, primary, advanced, customFn);
+      this.configurations[configName].applyConfig = prefHelper.bind(null, primary, customFn, useOldOrg, advanced);
     }
   },
 
   configurations: {},
 };
 
-let prefHelper = Task.async(function*(primary, advanced = null, customFn = null) {
+let prefHelper = Task.async(function*(primary, customFn = null, useOldOrg = false, advanced = null) {
   let browserWindow = Services.wm.getMostRecentWindow("navigator:browser");
   let selectedBrowser = browserWindow.gBrowser.selectedBrowser;
 
+  if (useOldOrg) {
+    Services.prefs.setBoolPref("browser.preferences.useOldOrganization", !!useOldOrg);
+  }
+
   // close any dialog that might still be open
   yield ContentTask.spawn(selectedBrowser, null, function*() {
     if (!content.window.gSubDialog) {
       return;
     }
     content.window.gSubDialog.close();
   });
 
@@ -67,39 +88,53 @@ let prefHelper = Task.async(function*(pr
       readyPromise = Promise.resolve();
     } else {
       readyPromise = paintPromise(browserWindow);
     }
   } else {
     readyPromise = TestUtils.topicObserved("advanced-pane-loaded");
   }
 
-  if (primary == "paneAdvanced") {
+  if (useOldOrg && primary == "paneAdvanced") {
     browserWindow.openAdvancedPreferences(advanced);
   } else {
     browserWindow.openPreferences(primary);
   }
 
   yield readyPromise;
 
   if (customFn) {
     let customPaintPromise = paintPromise(browserWindow);
     yield* customFn(selectedBrowser);
     yield customPaintPromise;
   }
+
+  Services.prefs.clearUserPref("browser.preferences.useOldOrganization");
 });
 
 function paintPromise(browserWindow) {
   return new Promise((resolve) => {
     browserWindow.addEventListener("MozAfterPaint", function() {
       resolve();
     }, {once: true});
   });
 }
 
+function* scrollToBrowsingGroup(aBrowser) {
+  yield ContentTask.spawn(aBrowser, null, function* () {
+    content.document.getElementById("browsingGroup").scrollIntoView();
+  });
+}
+
+function* scrollToCacheGroup(aBrowser) {
+  yield ContentTask.spawn(aBrowser, null, function* () {
+    content.document.getElementById("cacheGroup").scrollIntoView();
+  });
+}
+
 function* DNTDialog(aBrowser) {
   yield ContentTask.spawn(aBrowser, null, function* () {
     content.document.getElementById("doNotTrackSettings").click();
   });
 }
 
 function* connectionDialog(aBrowser) {
   yield ContentTask.spawn(aBrowser, null, function* () {
--- a/build/moz.configure/toolchain.configure
+++ b/build/moz.configure/toolchain.configure
@@ -969,8 +969,21 @@ set_config('VISIBILITY_FLAGS', visibilit
 
 # We only want to include windows.configure when we are compiling on
 # Windows, for Windows.
 @depends(target, host)
 def is_windows(target, host):
     return host.kernel == 'WINNT' and target.kernel == 'WINNT'
 
 include('windows.configure', when=is_windows)
+
+# Security Hardening
+# ==============================================================
+
+option('--enable-hardening', env='MOZ_SECURITY_HARDENING',
+       help='Enables security hardening compiler options')
+
+@depends('--enable-hardening', c_compiler)
+def security_hardening_cflags(value, c_compiler):
+    if value and c_compiler.type in ['gcc', 'clang']:
+        return '-fstack-protector-strong'
+
+add_old_configure_assignment('HARDENING_CFLAGS', security_hardening_cflags)
--- a/chrome/nsChromeRegistryChrome.cpp
+++ b/chrome/nsChromeRegistryChrome.cpp
@@ -735,17 +735,17 @@ nsChromeRegistryChrome::ManifestLocale(M
     ChromePackageFromPackageEntry(packageName, entry, &chromePackage,
                                   mSelectedSkin);
     SendManifestEntry(chromePackage);
   }
 
   if (strcmp(package, "global") == 0) {
     // We should refresh the LocaleService, since the available
     // locales changed.
-    LocaleService::GetInstance()->Refresh();
+    LocaleService::GetInstance()->OnAvailableLocalesChanged();
   }
 }
 
 void
 nsChromeRegistryChrome::ManifestSkin(ManifestProcessingContext& cx, int lineno,
                                      char *const * argv, int flags)
 {
   char* package = argv[0];
--- a/devtools/client/framework/test/browser_toolbox_options.js
+++ b/devtools/client/framework/test/browser_toolbox_options.js
@@ -100,25 +100,16 @@ function* testOptionsShortcut() {
   is(toolbox.currentToolId, "webconsole", "webconsole is reselected (2)");
   synthesizeKeyShortcut(L10N.getStr("toolbox.help.key"));
   is(toolbox.currentToolId, "options", "Toolbox selected via shortcut key (2)");
 }
 
 function* testOptions() {
   let tool = toolbox.getPanel("options");
   panelWin = tool.panelWin;
-
-  // It's possible that the iframe for options hasn't fully loaded yet,
-  // and might be paint-suppressed, which means that clicking things
-  // might not work just yet. The "load" event is a good indication that
-  // we're ready to proceed.
-  if (tool.panelDoc.readyState != "complete") {
-    yield once(tool.panelWin, "load");
-  }
-
   let prefNodes = tool.panelDoc.querySelectorAll(
     "input[type=checkbox][data-pref]");
 
   // Store modified pref names so that they can be cleared on error.
   for (let node of tool.panelDoc.querySelectorAll("[data-pref]")) {
     let pref = node.getAttribute("data-pref");
     modifiedPrefs.push(pref);
   }
--- a/devtools/client/inspector/boxmodel/test/browser_boxmodel_pseudo-element.js
+++ b/devtools/client/inspector/boxmodel/test/browser_boxmodel_pseudo-element.js
@@ -1,15 +1,15 @@
 /* vim: set ts=2 et sw=2 tw=80: */
 /* Any copyright is dedicated to the Public Domain.
  http://creativecommons.org/publicdomain/zero/1.0/ */
 
 "use strict";
 
-// Test that the box model displays the right values for a pseduo-element.
+// Test that the box model displays the right values for a pseudo-element.
 
 const TEST_URI = `
   <style type='text/css'>
     div {
       box-sizing: border-box;
       display: block;
       float: left;
       line-height: 20px;
@@ -48,25 +48,25 @@ const res1 = [
     value: "32"
   },
   {
     selector: ".boxmodel-margin.boxmodel-top > span",
     value: 0
   },
   {
     selector: ".boxmodel-margin.boxmodel-left > span",
-    value: "auto"
+    value: 0
   },
   {
     selector: ".boxmodel-margin.boxmodel-bottom > span",
     value: 6
   },
   {
     selector: ".boxmodel-margin.boxmodel-right > span",
-    value: "auto"
+    value: 0
   },
   {
     selector: ".boxmodel-padding.boxmodel-top > span",
     value: 0
   },
   {
     selector: ".boxmodel-padding.boxmodel-left > span",
     value: 0
--- a/devtools/client/inspector/computed/test/browser_computed_pseudo-element_01.js
+++ b/devtools/client/inspector/computed/test/browser_computed_pseudo-element_01.js
@@ -28,12 +28,12 @@ function* testTopLeft(inspector, view) {
   let top = getComputedViewPropertyValue(view, "top");
   is(top, "0px", "The computed view shows the correct top");
   let left = getComputedViewPropertyValue(view, "left");
   is(left, "0px", "The computed view shows the correct left");
 
   let afterElement = children.nodes[children.nodes.length - 1];
   yield selectNode(afterElement, inspector);
   top = getComputedViewPropertyValue(view, "top");
-  is(top, "50%", "The computed view shows the correct top");
+  is(top, "96px", "The computed view shows the correct top");
   left = getComputedViewPropertyValue(view, "left");
-  is(left, "50%", "The computed view shows the correct left");
+  is(left, "96px", "The computed view shows the correct left");
 }
--- a/devtools/client/netmonitor/src/netmonitor-controller.js
+++ b/devtools/client/netmonitor/src/netmonitor-controller.js
@@ -289,16 +289,17 @@ var NetMonitorController = {
     }
   },
 };
 
 /**
  * Functions handling target network events.
  */
 function NetworkEventsHandler() {
+  this.payloadQueue = [];
   this.addRequest = this.addRequest.bind(this);
   this.updateRequest = this.updateRequest.bind(this);
   this._onNetworkEvent = this._onNetworkEvent.bind(this);
   this._onNetworkEventUpdate = this._onNetworkEventUpdate.bind(this);
   this._onDocLoadingMarker = this._onDocLoadingMarker.bind(this);
   this._onRequestHeaders = this._onRequestHeaders.bind(this);
   this._onRequestCookies = this._onRequestCookies.bind(this);
   this._onRequestPostData = this._onRequestPostData.bind(this);
@@ -522,16 +523,37 @@ NetworkEventsHandler.prototype = {
         if (reqCookies.length) {
           payload.requestCookies = reqCookies;
         }
       }
     }
     return payload;
   },
 
+  getPayloadFromQueue(id) {
+    return this.payloadQueue.find((item) => item.id === id);
+  },
+
+  // Packet order of "networkUpdateEvent" is predictable, as a result we can wait for
+  // the last one "eventTimings" packet arrives to check payload is ready
+  isQueuePayloadReady(id) {
+    let queuedPayload = this.getPayloadFromQueue(id);
+    return queuedPayload && queuedPayload.payload.eventTimings;
+  },
+
+  pushPayloadToQueue(id, payload) {
+    let queuedPayload = this.getPayloadFromQueue(id);
+    if (!queuedPayload) {
+      this.payloadQueue.push({ id, payload });
+    } else {
+      // Merge upcoming networkEventUpdate payload into existing one
+      queuedPayload.payload = Object.assign({}, queuedPayload.payload, payload);
+    }
+  },
+
   async updateRequest(id, data) {
     let {
       mimeType,
       responseContent,
       responseCookies,
       responseHeaders,
       requestCookies,
       requestHeaders,
@@ -553,17 +575,22 @@ NetworkEventsHandler.prototype = {
       this.fetchPostData(requestPostData),
       this.fetchRequestCookies(requestCookies),
       this.fetchResponseCookies(responseCookies),
     ]);
 
     let payload = Object.assign({}, data,
                                     imageObj, requestHeadersObj, responseHeadersObj,
                                     postDataObj, requestCookiesObj, responseCookiesObj);
-    await this.actions.updateRequest(id, payload, true);
+
+    this.pushPayloadToQueue(id, payload);
+
+    if (this.isQueuePayloadReady(id)) {
+      await this.actions.updateRequest(id, this.getPayloadFromQueue(id).payload, true);
+    }
   },
 
   /**
    * The "networkEventUpdate" message type handler.
    *
    * @param string type
    *        Message type.
    * @param object packet
--- a/devtools/client/netmonitor/test/browser_net_security-tab-visibility.js
+++ b/devtools/client/netmonitor/test/browser_net_security-tab-visibility.js
@@ -63,16 +63,17 @@ add_task(function* () {
        "Security state has not yet arrived.");
     is(!!document.querySelector("#security-tab"), testcase.visibleOnNewEvent,
       "Security tab is " + (testcase.visibleOnNewEvent ? "visible" : "hidden") +
       " after new request was added to the menu.");
 
     info("Waiting for security information to arrive.");
     yield onSecurityInfo;
 
+    yield waitUntil(() => !!getSelectedRequest(gStore.getState()).securityState);
     ok(getSelectedRequest(gStore.getState()).securityState,
        "Security state arrived.");
     is(!!document.querySelector("#security-tab"), testcase.visibleOnSecurityInfo,
        "Security tab is " + (testcase.visibleOnSecurityInfo ? "visible" : "hidden") +
        " after security information arrived.");
 
     info("Waiting for request to complete.");
     yield onComplete;
--- a/devtools/client/netmonitor/test/browser_net_simple-request-data.js
+++ b/devtools/client/netmonitor/test/browser_net_simple-request-data.js
@@ -86,18 +86,24 @@ function test() {
         document,
         getDisplayedRequests(gStore.getState()),
         requestItem,
         "GET",
         SIMPLE_SJS
       );
     });
 
-    monitor.panelWin.once(EVENTS.RECEIVED_REQUEST_HEADERS, () => {
+    monitor.panelWin.once(EVENTS.RECEIVED_REQUEST_HEADERS, async () => {
+      await waitUntil(() => {
+        let requestItem = getSortedRequests(gStore.getState()).get(0);
+        return requestItem.requestHeaders;
+      });
+
       let requestItem = getSortedRequests(gStore.getState()).get(0);
+
       ok(requestItem.requestHeaders,
         "There should be a requestHeaders data available.");
       is(requestItem.requestHeaders.headers.length, 10,
         "The requestHeaders data has an incorrect |headers| property.");
       isnot(requestItem.requestHeaders.headersSize, 0,
         "The requestHeaders data has an incorrect |headersSize| property.");
       // Can't test for the exact request headers size because the value may
       // vary across platforms ("User-Agent" header differs).
@@ -106,17 +112,22 @@ function test() {
         document,
         getDisplayedRequests(gStore.getState()),
         requestItem,
         "GET",
         SIMPLE_SJS
       );
     });
 
-    monitor.panelWin.once(EVENTS.RECEIVED_REQUEST_COOKIES, () => {
+    monitor.panelWin.once(EVENTS.RECEIVED_REQUEST_COOKIES, async () => {
+      await waitUntil(() => {
+        let requestItem = getSortedRequests(gStore.getState()).get(0);
+        return requestItem.requestCookies;
+      });
+
       let requestItem = getSortedRequests(gStore.getState()).get(0);
 
       ok(requestItem.requestCookies,
         "There should be a requestCookies data available.");
       is(requestItem.requestCookies.cookies.length, 2,
         "The requestCookies data has an incorrect |cookies| property.");
 
       verifyRequestItemTarget(
@@ -127,17 +138,22 @@ function test() {
         SIMPLE_SJS
       );
     });
 
     monitor.panelWin.once(EVENTS.RECEIVED_REQUEST_POST_DATA, () => {
       ok(false, "Trap listener: this request doesn't have any post data.");
     });
 
-    monitor.panelWin.once(EVENTS.RECEIVED_RESPONSE_HEADERS, () => {
+    monitor.panelWin.once(EVENTS.RECEIVED_RESPONSE_HEADERS, async () => {
+      await waitUntil(() => {
+        let requestItem = getSortedRequests(gStore.getState()).get(0);
+        return requestItem.responseHeaders;
+      });
+
       let requestItem = getSortedRequests(gStore.getState()).get(0);
 
       ok(requestItem.responseHeaders,
         "There should be a responseHeaders data available.");
       is(requestItem.responseHeaders.headers.length, 10,
         "The responseHeaders data has an incorrect |headers| property.");
       is(requestItem.responseHeaders.headersSize, 330,
         "The responseHeaders data has an incorrect |headersSize| property.");
@@ -146,34 +162,47 @@ function test() {
         document,
         getDisplayedRequests(gStore.getState()),
         requestItem,
         "GET",
         SIMPLE_SJS
       );
     });
 
-    monitor.panelWin.once(EVENTS.RECEIVED_RESPONSE_COOKIES, () => {
+    monitor.panelWin.once(EVENTS.RECEIVED_RESPONSE_COOKIES, async () => {
+      await waitUntil(() => {
+        let requestItem = getSortedRequests(gStore.getState()).get(0);
+        return requestItem.responseCookies;
+      });
+
       let requestItem = getSortedRequests(gStore.getState()).get(0);
 
       ok(requestItem.responseCookies,
         "There should be a responseCookies data available.");
       is(requestItem.responseCookies.cookies.length, 2,
         "The responseCookies data has an incorrect |cookies| property.");
 
       verifyRequestItemTarget(
         document,
         getDisplayedRequests(gStore.getState()),
         requestItem,
         "GET",
         SIMPLE_SJS
       );
     });
 
-    monitor.panelWin.once(EVENTS.STARTED_RECEIVING_RESPONSE, () => {
+    monitor.panelWin.once(EVENTS.STARTED_RECEIVING_RESPONSE, async () => {
+      await waitUntil(() => {
+        let requestItem = getSortedRequests(gStore.getState()).get(0);
+        return requestItem.httpVersion &&
+               requestItem.status &&
+               requestItem.statusText &&
+               requestItem.headersSize;
+      });
+
       let requestItem = getSortedRequests(gStore.getState()).get(0);
 
       is(requestItem.httpVersion, "HTTP/1.1",
         "The httpVersion data has an incorrect value.");
       is(requestItem.status, "200",
         "The status data has an incorrect value.");
       is(requestItem.statusText, "Och Aye",
         "The statusText data has an incorrect value.");
@@ -188,17 +217,25 @@ function test() {
         SIMPLE_SJS,
         {
           status: "200",
           statusText: "Och Aye"
         }
       );
     });
 
-    monitor.panelWin.once(EVENTS.RECEIVED_RESPONSE_CONTENT, () => {
+    monitor.panelWin.once(EVENTS.RECEIVED_RESPONSE_CONTENT, async () => {
+      await waitUntil(() => {
+        let requestItem = getSortedRequests(gStore.getState()).get(0);
+        return requestItem.transferredSize &&
+               requestItem.contentSize &&
+               requestItem.mimeType &&
+               requestItem.responseContent;
+      });
+
       let requestItem = getSortedRequests(gStore.getState()).get(0);
 
       is(requestItem.transferredSize, "12",
         "The transferredSize data has an incorrect value.");
       is(requestItem.contentSize, "12",
         "The contentSize data has an incorrect value.");
       is(requestItem.mimeType, "text/plain; charset=utf-8",
         "The mimeType data has an incorrect value.");
@@ -228,17 +265,22 @@ function test() {
           type: "plain",
           fullMimeType: "text/plain; charset=utf-8",
           transferred: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 12),
           size: L10N.getFormatStrWithNumbers("networkMenu.sizeB", 12),
         }
       );
     });
 
-    monitor.panelWin.once(EVENTS.UPDATING_EVENT_TIMINGS, () => {
+    monitor.panelWin.once(EVENTS.UPDATING_EVENT_TIMINGS, async () => {
+      await waitUntil(() => {
+        let requestItem = getSortedRequests(gStore.getState()).get(0);
+        return requestItem.eventTimings;
+      });
+
       let requestItem = getSortedRequests(gStore.getState()).get(0);
 
       is(typeof requestItem.totalTime, "number",
         "The attached totalTime is incorrect.");
       ok(requestItem.totalTime >= 0,
         "The attached totalTime should be positive.");
 
       verifyRequestItemTarget(
--- a/devtools/client/responsivedesign/test/browser.ini
+++ b/devtools/client/responsivedesign/test/browser.ini
@@ -9,13 +9,14 @@ support-files =
 
 [browser_responsive_cmd.js]
 [browser_responsivecomputedview.js]
 skip-if = e10s && debug # Bug 1252201 - Docshell leak on debug e10s
 [browser_responsiveruleview.js]
 skip-if = e10s && debug # Bug 1252201 - Docshell leak on debug e10s
 [browser_responsiveui.js]
 [browser_responsiveui_touch.js]
+skip-if = true # Bug 1358261 - Intermittent failures, mostly on Windows
 [browser_responsiveuiaddcustompreset.js]
 [browser_responsive_devicewidth.js]
 [browser_responsiveui_customuseragent.js]
 [browser_responsiveui_window_close.js]
 skip-if = (os == 'linux') && e10s && debug # Bug 1277274
--- a/devtools/client/webconsole/new-console-output/test/chrome/test_render_perf.html
+++ b/devtools/client/webconsole/new-console-output/test/chrome/test_render_perf.html
@@ -9,87 +9,90 @@
      - http://creativecommons.org/publicdomain/zero/1.0/ -->
 </head>
 <body>
 <p>Test for render perf</p>
 <div id="output"></div>
 
 <script type="text/javascript">
 "use strict";
-
-const testPackets = [];
-const numMessages = 1000;
-for (let id = 0; id < numMessages; id++) {
-  let message = "Odd text";
-  if (id % 2 === 0) {
-    message = "Even text";
+const numMessages = 2000;
+const testPackets = Array.from({length: numMessages}).map((el, id) => ({
+  "from": "server1.conn4.child1/consoleActor2",
+  "type": "consoleAPICall",
+  "message": {
+    "arguments": [
+      "foobar",
+      `${id % 2 === 0 ? "Even" : "Odd"} text`,
+      id
+    ],
+    "columnNumber": 1,
+    "counter": null,
+    "filename": "file:///test.html",
+    "functionName": "",
+    "groupName": "",
+    "level": "log",
+    "lineNumber": 1,
+    "private": false,
+    "styles": [],
+    "timeStamp": 1455064271115 + id,
+    "timer": null,
+    "workerType": "none",
+    "category": "webdev"
   }
-  testPackets.push({
-    "from": "server1.conn4.child1/consoleActor2",
-    "type": "consoleAPICall",
-    "message": {
-      "arguments": [
-        "foobar",
-        message,
-        id
-      ],
-      "columnNumber": 1,
-      "counter": null,
-      "filename": "file:///test.html",
-      "functionName": "",
-      "groupName": "",
-      "level": "log",
-      "lineNumber": 1,
-      "private": false,
-      "styles": [],
-      "timeStamp": 1455064271115 + id,
-      "timer": null,
-      "workerType": "none",
-      "category": "webdev"
-    }
-  });
+}));
+
+async function timeit(cb) {
+  // Return a Promise that resolves the number of seconds cb takes.
+  let start = performance.now();
+  await cb();
+  let elapsed = performance.now() - start;
+  return elapsed;
 }
 
-function timeit(cb) {
-  // Return a Promise that resolves the number of seconds cb takes.
-  return new Promise(resolve => {
-    let start = performance.now();
-    cb();
-    let elapsed = performance.now() - start;
-    resolve(elapsed / 1000);
-  });
-}
+window.onload = async function () {
+  try {
+    const NewConsoleOutputWrapper = browserRequire(
+      "devtools/client/webconsole/new-console-output/new-console-output-wrapper");
+    const EventEmitter = browserRequire("devtools/shared/event-emitter");
 
-window.onload = Task.async(function* () {
-  const { configureStore } = browserRequire(
-    "devtools/client/webconsole/new-console-output/store");
-  const { filterTextSet, filtersClear } = browserRequire(
-    "devtools/client/webconsole/new-console-output/actions/index");
-  const NewConsoleOutputWrapper = browserRequire(
-    "devtools/client/webconsole/new-console-output/new-console-output-wrapper");
-  const wrapper = new NewConsoleOutputWrapper(document.querySelector("#output"), {});
+    const wrapper = new NewConsoleOutputWrapper(
+      document.getElementById("output"),
+      {hud: EventEmitter.decorate({proxy: {}})},
+      {},
+      null,
+      document,
+    );
+    wrapper.init();
 
-  const store = configureStore();
-
-  let time = yield timeit(() => {
-    testPackets.forEach((message) => {
-      wrapper.dispatchMessageAdd(message);
-    });
-  });
-  info("took " + time + " seconds to render messages");
+    let times = [];
+    const iterations = 10;
+    const lastPacket = testPackets.pop();
+    for (let i = 0; i < iterations; i++) {
+      let time = await timeit(() => {
+        testPackets.forEach((packet) => wrapper.dispatchMessageAdd(packet));
+        // Only wait for the last packet to minimize work.
+        return wrapper.dispatchMessageAdd(lastPacket, true);
+      });
+      info(`took ${time} ms to render messages`);
+      times.push(time);
 
-  time = yield timeit(() => {
-    store.dispatch(filterTextSet("Odd text"));
-  });
-  info("took " + time + " seconds to search filter half the messages");
+      info("Clear the console");
+      await new Promise(resolve => requestAnimationFrame(() => resolve()));
+      wrapper.dispatchMessagesClear();
+    }
 
-  time = yield timeit(() => {
-    store.dispatch(filtersClear());
-  });
-  info("took " + time + " seconds to clear the filter");
+    let totalTime = times.reduce((sum, t) => sum + t);
+    let avg = totalTime / times.length;
+    info(`On average, it took ${avg} ms to render ${numMessages} messages`);
 
-  ok(true, "Yay, it didn't time out!");
+    ok(true, "Yay, it didn't time out!");
+  } catch (e) {
+    ok(false, `Error :  ${e.message}
+      ${e.stack}
+    `);
+  }
 
   SimpleTest.finish();
-});
+};
 </script>
 </body>
 </html>
--- a/dom/animation/EffectCompositor.cpp
+++ b/dom/animation/EffectCompositor.cpp
@@ -514,34 +514,27 @@ EffectCompositor::GetServoAnimationRule(
 /* static */ dom::Element*
 EffectCompositor::GetElementToRestyle(dom::Element* aElement,
                                       CSSPseudoElementType aPseudoType)
 {
   if (aPseudoType == CSSPseudoElementType::NotPseudo) {
     return aElement;
   }
 
-  nsIFrame* primaryFrame = aElement->GetPrimaryFrame();
-  if (!primaryFrame) {
-    return nullptr;
+  if (aPseudoType == CSSPseudoElementType::before) {
+    return nsLayoutUtils::GetBeforePseudo(aElement);
   }
-  nsIFrame* pseudoFrame;
-  if (aPseudoType == CSSPseudoElementType::before) {
-    pseudoFrame = nsLayoutUtils::GetBeforeFrame(primaryFrame);
-  } else if (aPseudoType == CSSPseudoElementType::after) {
-    pseudoFrame = nsLayoutUtils::GetAfterFrame(primaryFrame);
-  } else {
-    NS_NOTREACHED("Should not try to get the element to restyle for a pseudo "
-                  "other that :before or :after");
-    return nullptr;
+
+  if (aPseudoType == CSSPseudoElementType::after) {
+    return nsLayoutUtils::GetAfterPseudo(aElement);
   }
-  if (!pseudoFrame) {
-    return nullptr;
-  }
-  return pseudoFrame->GetContent()->AsElement();
+
+  NS_NOTREACHED("Should not try to get the element to restyle for a pseudo "
+                "other that :before or :after");
+  return nullptr;
 }
 
 bool
 EffectCompositor::HasPendingStyleUpdates() const
 {
   for (auto& elementSet : mElementsToRestyle) {
     if (elementSet.Count()) {
       return true;
--- a/dom/animation/KeyframeEffectReadOnly.cpp
+++ b/dom/animation/KeyframeEffectReadOnly.cpp
@@ -1459,29 +1459,27 @@ KeyframeEffectReadOnly::CanThrottleTrans
 
 nsIFrame*
 KeyframeEffectReadOnly::GetAnimationFrame() const
 {
   if (!mTarget) {
     return nullptr;
   }
 
-  nsIFrame* frame = mTarget->mElement->GetPrimaryFrame();
-  if (!frame) {
-    return nullptr;
-  }
-
+  nsIFrame* frame;
   if (mTarget->mPseudoType == CSSPseudoElementType::before) {
-    frame = nsLayoutUtils::GetBeforeFrame(frame);
+    frame = nsLayoutUtils::GetBeforeFrame(mTarget->mElement);
   } else if (mTarget->mPseudoType == CSSPseudoElementType::after) {
-    frame = nsLayoutUtils::GetAfterFrame(frame);
+    frame = nsLayoutUtils::GetAfterFrame(mTarget->mElement);
   } else {
+    frame = mTarget->mElement->GetPrimaryFrame();
     MOZ_ASSERT(mTarget->mPseudoType == CSSPseudoElementType::NotPseudo,
                "unknown mTarget->mPseudoType");
   }
+
   if (!frame) {
     return nullptr;
   }
 
   return nsLayoutUtils::GetStyleFrame(frame);
 }
 
 nsIDocument*
--- a/dom/base/ChildIterator.cpp
+++ b/dom/base/ChildIterator.cpp
@@ -312,57 +312,48 @@ ExplicitChildIterator::GetPreviousChild(
   return mChild;
 }
 
 nsIContent*
 AllChildrenIterator::Get() const
 {
   switch (mPhase) {
     case eAtBeforeKid: {
-      nsIFrame* frame = mOriginalContent->GetPrimaryFrame();
-      MOZ_ASSERT(frame, "No frame at eAtBeforeKid phase");
-      nsIFrame* beforeFrame = nsLayoutUtils::GetBeforeFrame(frame);
-      MOZ_ASSERT(beforeFrame, "No content before frame at eAtBeforeKid phase");
-      return beforeFrame->GetContent();
+      Element* before = nsLayoutUtils::GetBeforePseudo(mOriginalContent);
+      MOZ_ASSERT(before, "No content before frame at eAtBeforeKid phase");
+      return before;
     }
 
     case eAtExplicitKids:
       return ExplicitChildIterator::Get();
 
     case eAtAnonKids:
       return mAnonKids[mAnonKidsIdx];
 
     case eAtAfterKid: {
-      nsIFrame* frame = mOriginalContent->GetPrimaryFrame();
-      MOZ_ASSERT(frame, "No frame at eAtAfterKid phase");
-      nsIFrame* afterFrame = nsLayoutUtils::GetAfterFrame(frame);
-      MOZ_ASSERT(afterFrame, "No content before frame at eAtBeforeKid phase");
-      return afterFrame->GetContent();
+      Element* after = nsLayoutUtils::GetAfterPseudo(mOriginalContent);
+      MOZ_ASSERT(after, "No content after frame at eAtAfterKid phase");
+      return after;
     }
 
     default:
       return nullptr;
   }
 }
 
 
 bool
 AllChildrenIterator::Seek(nsIContent* aChildToFind)
 {
   if (mPhase == eAtBegin || mPhase == eAtBeforeKid) {
     mPhase = eAtExplicitKids;
-    nsIFrame* frame = mOriginalContent->GetPrimaryFrame();
-    if (frame) {
-      nsIFrame* beforeFrame = nsLayoutUtils::GetBeforeFrame(frame);
-      if (beforeFrame) {
-        if (beforeFrame->GetContent() == aChildToFind) {
-          mPhase = eAtBeforeKid;
-          return true;
-        }
-      }
+    Element* beforePseudo = nsLayoutUtils::GetBeforePseudo(mOriginalContent);
+    if (beforePseudo && beforePseudo == aChildToFind) {
+      mPhase = eAtBeforeKid;
+      return true;
     }
   }
 
   if (mPhase == eAtExplicitKids) {
     if (ExplicitChildIterator::Seek(aChildToFind)) {
       return true;
     }
     mPhase = eAtAnonKids;
@@ -399,23 +390,20 @@ AllChildrenIterator::AppendNativeAnonymo
   }
 }
 
 nsIContent*
 AllChildrenIterator::GetNextChild()
 {
   if (mPhase == eAtBegin) {
     mPhase = eAtExplicitKids;
-    nsIFrame* frame = mOriginalContent->GetPrimaryFrame();
-    if (frame) {
-      nsIFrame* beforeFrame = nsLayoutUtils::GetBeforeFrame(frame);
-      if (beforeFrame) {
-        mPhase = eAtBeforeKid;
-        return beforeFrame->GetContent();
-      }
+    Element* beforeContent = nsLayoutUtils::GetBeforePseudo(mOriginalContent);
+    if (beforeContent) {
+      mPhase = eAtBeforeKid;
+      return beforeContent;
     }
   }
 
   if (mPhase == eAtBeforeKid) {
     // Advance into our explicit kids.
     mPhase = eAtExplicitKids;
   }
 
@@ -441,43 +429,37 @@ AllChildrenIterator::GetNextChild()
         mAnonKidsIdx++;
       }
     }
 
     if (mAnonKidsIdx < mAnonKids.Length()) {
       return mAnonKids[mAnonKidsIdx];
     }
 
-    nsIFrame* frame = mOriginalContent->GetPrimaryFrame();
-    if (frame) {
-      nsIFrame* afterFrame = nsLayoutUtils::GetAfterFrame(frame);
-      if (afterFrame) {
-        mPhase = eAtAfterKid;
-        return afterFrame->GetContent();
-      }
+    Element* afterContent = nsLayoutUtils::GetAfterPseudo(mOriginalContent);
+    if (afterContent) {
+      mPhase = eAtAfterKid;
+      return afterContent;
     }
   }
 
   mPhase = eAtEnd;
   return nullptr;
 }
 
 nsIContent*
 AllChildrenIterator::GetPreviousChild()
 {
   if (mPhase == eAtEnd) {
     MOZ_ASSERT(mAnonKidsIdx == mAnonKids.Length());
     mPhase = eAtAnonKids;
-    nsIFrame* frame = mOriginalContent->GetPrimaryFrame();
-    if (frame) {
-      nsIFrame* afterFrame = nsLayoutUtils::GetAfterFrame(frame);
-      if (afterFrame) {
-        mPhase = eAtAfterKid;
-        return afterFrame->GetContent();
-      }
+    Element* afterContent = nsLayoutUtils::GetAfterPseudo(mOriginalContent);
+    if (afterContent) {
+      mPhase = eAtAfterKid;
+      return afterContent;
     }
   }
 
   if (mPhase == eAtAfterKid) {
     mPhase = eAtAnonKids;
   }
 
   if (mPhase == eAtAnonKids) {
@@ -496,23 +478,20 @@ AllChildrenIterator::GetPreviousChild()
   }
 
   if (mPhase == eAtExplicitKids) {
     nsIContent* kid = ExplicitChildIterator::GetPreviousChild();
     if (kid) {
       return kid;
     }
 
-    nsIFrame* frame = mOriginalContent->GetPrimaryFrame();
-    if (frame) {
-      nsIFrame* beforeFrame = nsLayoutUtils::GetBeforeFrame(frame);
-      if (beforeFrame) {
-        mPhase = eAtBeforeKid;
-        return beforeFrame->GetContent();
-      }
+    Element* beforeContent = nsLayoutUtils::GetBeforePseudo(mOriginalContent);
+    if (beforeContent) {
+      mPhase = eAtBeforeKid;
+      return beforeContent;
     }
   }
 
   mPhase = eAtBegin;
   return nullptr;
 }
 
 static bool
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -51,16 +51,17 @@
 #include "mozilla/dom/HTMLShadowElement.h"
 #include "mozilla/dom/IPCBlobUtils.h"
 #include "mozilla/dom/Promise.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/dom/TabParent.h"
 #include "mozilla/dom/TextDecoder.h"
 #include "mozilla/dom/TouchEvent.h"
 #include "mozilla/dom/ShadowRoot.h"
+#include "mozilla/dom/XULCommandEvent.h"
 #include "mozilla/dom/WorkerPrivate.h"
 #include "mozilla/dom/workers/ServiceWorkerManager.h"
 #include "mozilla/EventDispatcher.h"
 #include "mozilla/EventListenerManager.h"
 #include "mozilla/EventStateManager.h"
 #include "mozilla/gfx/DataSurfaceHelpers.h"
 #include "mozilla/IMEStateManager.h"
 #include "mozilla/InternalMutationEvent.h"
@@ -126,17 +127,16 @@
 #include "nsIDOMEvent.h"
 #include "nsIDOMElement.h"
 #include "nsIDOMHTMLElement.h"
 #include "nsIDOMHTMLFormElement.h"
 #include "nsIDOMHTMLInputElement.h"
 #include "nsIDOMNode.h"
 #include "nsIDOMNodeList.h"
 #include "nsIDOMWindowUtils.h"
-#include "nsIDOMXULCommandEvent.h"
 #include "nsIDragService.h"
 #include "nsIEditor.h"
 #include "nsIFormControl.h"
 #include "nsIForm.h"
 #include "nsIFragmentContentSink.h"
 #include "nsContainerFrame.h"
 #include "nsIHTMLDocument.h"
 #include "nsIIdleService.h"
@@ -6361,38 +6361,37 @@ nsContentUtils::DispatchXULCommand(nsICo
                                    nsIPresShell* aShell,
                                    bool aCtrl,
                                    bool aAlt,
                                    bool aShift,
                                    bool aMeta)
 {
   NS_ENSURE_STATE(aTarget);
   nsIDocument* doc = aTarget->OwnerDoc();
-  nsCOMPtr<nsIDOMDocument> domDoc = do_QueryInterface(doc);
-  NS_ENSURE_STATE(domDoc);
-  nsCOMPtr<nsIDOMEvent> event;
-  domDoc->CreateEvent(NS_LITERAL_STRING("xulcommandevent"),
-                      getter_AddRefs(event));
-  nsCOMPtr<nsIDOMXULCommandEvent> xulCommand = do_QueryInterface(event);
-  nsresult rv = xulCommand->InitCommandEvent(NS_LITERAL_STRING("command"),
-                                             true, true, doc->GetInnerWindow(),
-                                             0, aCtrl, aAlt, aShift, aMeta,
-                                             aSourceEvent);
-  NS_ENSURE_SUCCESS(rv, rv);
+  nsIPresShell* shell = doc->GetShell();
+  nsPresContext* presContext = nullptr;
+  if (shell) {
+    presContext = shell->GetPresContext();
+  }
+  RefPtr<XULCommandEvent> xulCommand = new XULCommandEvent(doc, presContext,
+                                                           nullptr);
+  xulCommand->InitCommandEvent(NS_LITERAL_STRING("command"), true, true,
+                               doc->GetInnerWindow(), 0, aCtrl, aAlt, aShift,
+                               aMeta, aSourceEvent);
 
   if (aShell) {
     nsEventStatus status = nsEventStatus_eIgnore;
     nsCOMPtr<nsIPresShell> kungFuDeathGrip = aShell;
-    return aShell->HandleDOMEventWithTarget(aTarget, event, &status);
+    return aShell->HandleDOMEventWithTarget(aTarget, xulCommand, &status);
   }
 
   nsCOMPtr<EventTarget> target = do_QueryInterface(aTarget);
   NS_ENSURE_STATE(target);
   bool dummy;
-  return target->DispatchEvent(event, &dummy);
+  return target->DispatchEvent(xulCommand, &dummy);
 }
 
 // static
 nsresult
 nsContentUtils::WrapNative(JSContext *cx, nsISupports *native,
                            nsWrapperCache *cache, const nsIID* aIID,
                            JS::MutableHandle<JS::Value> vp, bool aAllowWrapping)
 {
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -3751,21 +3751,21 @@ nsDOMWindowUtils::GetOMTAStyle(nsIDOMEle
 {
   nsCOMPtr<Element> element = do_QueryInterface(aElement);
   if (!element) {
     return NS_ERROR_INVALID_ARG;
   }
 
   RefPtr<nsROCSSPrimitiveValue> cssValue = nullptr;
   nsIFrame* frame = element->GetPrimaryFrame();
-  if (frame && !aPseudoElement.IsEmpty()) {
+  if (!aPseudoElement.IsEmpty()) {
     if (aPseudoElement.EqualsLiteral("::before")) {
-      frame = nsLayoutUtils::GetBeforeFrame(frame);
+      frame = nsLayoutUtils::GetBeforeFrame(element);
     } else if (aPseudoElement.EqualsLiteral("::after")) {
-      frame = nsLayoutUtils::GetAfterFrame(frame);
+      frame = nsLayoutUtils::GetAfterFrame(element);
     } else {
       return NS_ERROR_INVALID_ARG;
     }
   }
   if (frame && nsLayoutUtils::AreAsyncAnimationsEnabled()) {
     if (aProperty.EqualsLiteral("opacity")) {
       Layer* layer =
         FrameLayerBuilder::GetDedicatedLayer(frame,
--- a/dom/base/nsDeprecatedOperationList.h
+++ b/dom/base/nsDeprecatedOperationList.h
@@ -32,17 +32,16 @@ DEPRECATED_OPERATION(GetSetUserData)
 DEPRECATED_OPERATION(MozGetAsFile)
 DEPRECATED_OPERATION(UseOfCaptureEvents)
 DEPRECATED_OPERATION(UseOfReleaseEvents)
 DEPRECATED_OPERATION(UseOfDOM3LoadMethod)
 DEPRECATED_OPERATION(ChromeUseOfDOM3LoadMethod)
 DEPRECATED_OPERATION(ShowModalDialog)
 DEPRECATED_OPERATION(Window_Content)
 DEPRECATED_OPERATION(SyncXMLHttpRequest)
-DEPRECATED_OPERATION(DataContainerEvent)
 DEPRECATED_OPERATION(Window_Controllers)
 DEPRECATED_OPERATION(ImportXULIntoContent)
 DEPRECATED_OPERATION(PannerNodeDoppler)
 DEPRECATED_OPERATION(NavigatorGetUserMedia)
 DEPRECATED_OPERATION(WebrtcDeprecatedPrefix)
 DEPRECATED_OPERATION(RTCPeerConnectionGetStreams)
 DEPRECATED_OPERATION(AppCache)
 DEPRECATED_OPERATION(PrefixedImageSmoothingEnabled)
--- a/dom/base/nsDocument.cpp
+++ b/dom/base/nsDocument.cpp
@@ -8003,36 +8003,38 @@ nsDocument::GetEventTargetParent(EventCh
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDocument::CreateEvent(const nsAString& aEventType, nsIDOMEvent** aReturn)
 {
   NS_ENSURE_ARG_POINTER(aReturn);
   ErrorResult rv;
-  *aReturn = nsIDocument::CreateEvent(aEventType, rv).take();
+  *aReturn = nsIDocument::CreateEvent(aEventType, CallerType::System,
+                                      rv).take();
   return rv.StealNSResult();
 }
 
 already_AddRefed<Event>
-nsIDocument::CreateEvent(const nsAString& aEventType, ErrorResult& rv) const
+nsIDocument::CreateEvent(const nsAString& aEventType, CallerType aCallerType,
+                         ErrorResult& rv) const
 {
   nsIPresShell *shell = GetShell();
 
   nsPresContext *presContext = nullptr;
 
   if (shell) {
     // Retrieve the context
     presContext = shell->GetPresContext();
   }
 
   // Create event even without presContext.
   RefPtr<Event> ev =
     EventDispatcher::CreateEvent(const_cast<nsIDocument*>(this), presContext,
-                                 nullptr, aEventType);
+                                 nullptr, aEventType, aCallerType);
   if (!ev) {
     rv.Throw(NS_ERROR_DOM_NOT_SUPPORTED_ERR);
     return nullptr;
   }
   WidgetEvent* e = ev->WidgetEventPtr();
   e->mFlags.mBubbles = false;
   e->mFlags.mCancelable = false;
   return ev.forget();
--- a/dom/base/nsDocumentEncoder.cpp
+++ b/dom/base/nsDocumentEncoder.cpp
@@ -137,28 +137,30 @@ protected:
           return false;
       }
     }
     return true;
   }
 
   virtual bool IncludeInContext(nsINode *aNode);
 
+  void Clear();
+
   class MOZ_STACK_CLASS AutoReleaseDocumentIfNeeded final
   {
   public:
     explicit AutoReleaseDocumentIfNeeded(nsDocumentEncoder* aEncoder)
       : mEncoder(aEncoder)
     {
     }
 
     ~AutoReleaseDocumentIfNeeded()
     {
       if (mEncoder->mFlags & RequiresReinitAfterOutput) {
-        mEncoder->mDocument = nullptr;
+        mEncoder->Clear();
       }
     }
 
   private:
     nsDocumentEncoder* mEncoder;
   };
 
   nsCOMPtr<nsIDocument>          mDocument;
@@ -197,17 +199,18 @@ NS_IMPL_CYCLE_COLLECTING_ADDREF(nsDocume
 NS_IMPL_CYCLE_COLLECTING_RELEASE(nsDocumentEncoder)
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsDocumentEncoder)
    NS_INTERFACE_MAP_ENTRY(nsIDocumentEncoder)
    NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
 
 NS_IMPL_CYCLE_COLLECTION(nsDocumentEncoder,
-                         mDocument, mSelection, mRange, mNode, mCommonParent)
+                         mDocument, mSelection, mRange, mNode, mSerializer,
+                         mCommonParent)
 
 nsDocumentEncoder::nsDocumentEncoder() : mCachedBuffer(nullptr)
 {
   Initialize();
   mMimeType.AssignLiteral("text/plain");
 }
 
 void nsDocumentEncoder::Initialize(bool aClearCachedSerializer)
@@ -1010,16 +1013,29 @@ nsDocumentEncoder::SerializeRangeToStrin
     NS_ENSURE_SUCCESS(rv, rv);
   }
   rv = SerializeRangeContextEnd(mCommonAncestors, aOutputString);
   NS_ENSURE_SUCCESS(rv, rv);
 
   return rv;
 }
 
+void
+nsDocumentEncoder::Clear()
+{
+  mDocument = nullptr;
+  mSelection = nullptr;
+  mRange = nullptr;
+  mNode = nullptr;
+  mCommonParent = nullptr;
+  mNodeFixup = nullptr;
+
+  Initialize(false);
+}
+
 NS_IMETHODIMP
 nsDocumentEncoder::EncodeToString(nsAString& aOutputString)
 {
   return EncodeToStringWithMaxLength(0, aOutputString);
 }
 
 NS_IMETHODIMP
 nsDocumentEncoder::EncodeToStringWithMaxLength(uint32_t aMaxLength,
--- a/dom/base/nsGkAtomList.h
+++ b/dom/base/nsGkAtomList.h
@@ -2137,22 +2137,24 @@ GK_ATOM(onphoto, "onphoto")
 GK_ATOM(onactivestatechanged, "onactivestatechanged")
 GK_ATOM(ongamepadbuttondown, "ongamepadbuttondown")
 GK_ATOM(ongamepadbuttonup, "ongamepadbuttonup")
 GK_ATOM(ongamepadaxismove, "ongamepadaxismove")
 GK_ATOM(ongamepadconnected, "ongamepadconnected")
 GK_ATOM(ongamepaddisconnected, "ongamepaddisconnected")
 
 // Content property names
+GK_ATOM(afterPseudoProperty, "afterPseudoProperty")  // nsXMLElement*
 GK_ATOM(animationsProperty, "AnimationsProperty")        // FrameAnimations*
 GK_ATOM(animationsOfBeforeProperty, "AnimationsOfBeforeProperty") // FrameAnimations*
 GK_ATOM(animationsOfAfterProperty, "AnimationsOfAfterProperty") // FrameAnimations*
 GK_ATOM(animationEffectsProperty, "AnimationEffectsProperty") // EffectSet*
 GK_ATOM(animationEffectsForBeforeProperty, "AnimationsEffectsForBeforeProperty") // EffectSet*
 GK_ATOM(animationEffectsForAfterProperty, "AnimationsEffectsForAfterProperty") // EffectSet*
+GK_ATOM(beforePseudoProperty, "beforePseudoProperty")  // nsXMLElement*
 GK_ATOM(cssPseudoElementBeforeProperty, "CSSPseudoElementBeforeProperty") // CSSPseudoElement*
 GK_ATOM(cssPseudoElementAfterProperty, "CSSPseudoElementAfterProperty") // CSSPseudoElement*
 GK_ATOM(transitionsProperty, "TransitionsProperty")        // FrameTransitions*
 GK_ATOM(transitionsOfBeforeProperty, "TransitionsOfBeforeProperty") // FrameTransitions*
 GK_ATOM(transitionsOfAfterProperty, "TransitionsOfAfterProperty") // FrameTransitions*
 GK_ATOM(genConInitializerProperty, "QuoteNodeProperty")
 GK_ATOM(labelMouseDownPtProperty, "LabelMouseDownPtProperty")
 GK_ATOM(lockedStyleStates, "lockedStyleStates")
--- a/dom/base/nsGlobalWindow.cpp
+++ b/dom/base/nsGlobalWindow.cpp
@@ -6557,17 +6557,18 @@ nsGlobalWindow::DispatchCustomEvent(cons
 
 bool
 nsGlobalWindow::DispatchResizeEvent(const CSSIntSize& aSize)
 {
   MOZ_ASSERT(IsOuterWindow());
 
   ErrorResult res;
   RefPtr<Event> domEvent =
-    mDoc->CreateEvent(NS_LITERAL_STRING("CustomEvent"), res);
+    mDoc->CreateEvent(NS_LITERAL_STRING("CustomEvent"), CallerType::System,
+                      res);
   if (res.Failed()) {
     return false;
   }
 
   // We don't init the AutoJSAPI with ourselves because we don't want it
   // reporting errors to our onerror handlers.
   AutoJSAPI jsapi;
   jsapi.Init();
--- a/dom/base/nsIDocument.h
+++ b/dom/base/nsIDocument.h
@@ -2622,17 +2622,19 @@ public:
     CreateComment(const nsAString& aData) const;
   already_AddRefed<mozilla::dom::ProcessingInstruction>
     CreateProcessingInstruction(const nsAString& target, const nsAString& data,
                                 mozilla::ErrorResult& rv) const;
   already_AddRefed<nsINode>
     ImportNode(nsINode& aNode, bool aDeep, mozilla::ErrorResult& rv) const;
   nsINode* AdoptNode(nsINode& aNode, mozilla::ErrorResult& rv);
   already_AddRefed<mozilla::dom::Event>
-    CreateEvent(const nsAString& aEventType, mozilla::ErrorResult& rv) const;
+    CreateEvent(const nsAString& aEventType,
+                mozilla::dom::CallerType aCallerType,
+                mozilla::ErrorResult& rv) const;
   already_AddRefed<nsRange> CreateRange(mozilla::ErrorResult& rv);
   already_AddRefed<mozilla::dom::NodeIterator>
     CreateNodeIterator(nsINode& aRoot, uint32_t aWhatToShow,
                        mozilla::dom::NodeFilter* aFilter,
                        mozilla::ErrorResult& rv) const;
   already_AddRefed<mozilla::dom::NodeIterator>
     CreateNodeIterator(nsINode& aRoot, uint32_t aWhatToShow,
                        mozilla::dom::NodeFilterHolder aFilter,
--- a/dom/base/nsObjectLoadingContent.cpp
+++ b/dom/base/nsObjectLoadingContent.cpp
@@ -2669,38 +2669,42 @@ nsObjectLoadingContent::NotifyStateChang
 
   nsIDocument* doc = thisContent->GetComposedDoc();
   if (!doc) {
     return; // Nothing to do
   }
 
   EventStates newState = ObjectState();
 
+  if (newState == aOldState && mType == aOldType) {
+    return; // Also done.
+  }
+
   if (newState != aOldState) {
     NS_ASSERTION(thisContent->IsInComposedDoc(), "Something is confused");
     // This will trigger frame construction
     EventStates changedBits = aOldState ^ newState;
-
     {
       nsAutoScriptBlocker scriptBlocker;
       doc->ContentStateChanged(thisContent, changedBits);
     }
-    if (aSync) {
-      NS_ASSERTION(InActiveDocument(thisContent), "Something is confused");
-      // Make sure that frames are actually constructed immediately.
-      doc->FlushPendingNotifications(FlushType::Frames);
-    }
   } else if (aOldType != mType) {
     // If our state changed, then we already recreated frames
     // Otherwise, need to do that here
     nsCOMPtr<nsIPresShell> shell = doc->GetShell();
     if (shell) {
-      shell->RecreateFramesFor(thisContent);
+      shell->PostRecreateFramesFor(thisContent->AsElement());
     }
   }
+
+  if (aSync) {
+    NS_ASSERTION(InActiveDocument(thisContent), "Something is confused");
+    // Make sure that frames are actually constructed immediately.
+    doc->FlushPendingNotifications(FlushType::Frames);
+  }
 }
 
 nsObjectLoadingContent::ObjectType
 nsObjectLoadingContent::GetTypeOfContent(const nsCString& aMIMEType)
 {
   nsCOMPtr<nsIContent> thisContent =
     do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
   NS_ASSERTION(thisContent, "must be a content");
--- a/dom/base/nsPlainTextSerializer.cpp
+++ b/dom/base/nsPlainTextSerializer.cpp
@@ -50,16 +50,27 @@ static const  char16_t kSPACE = ' ';
 static int32_t HeaderLevel(nsIAtom* aTag);
 static int32_t GetUnicharWidth(char16_t ucs);
 static int32_t GetUnicharStringWidth(const char16_t* pwcs, int32_t n);
 
 // Someday may want to make this non-const:
 static const uint32_t TagStackSize = 500;
 static const uint32_t OLStackSize = 100;
 
+NS_IMPL_CYCLE_COLLECTING_ADDREF(nsPlainTextSerializer)
+NS_IMPL_CYCLE_COLLECTING_RELEASE(nsPlainTextSerializer)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(nsPlainTextSerializer)
+  NS_INTERFACE_MAP_ENTRY(nsIContentSerializer)
+  NS_INTERFACE_MAP_ENTRY(nsISupports)
+NS_INTERFACE_MAP_END
+
+NS_IMPL_CYCLE_COLLECTION(nsPlainTextSerializer,
+                         mElement)
+
 nsresult
 NS_NewPlainTextSerializer(nsIContentSerializer** aSerializer)
 {
   RefPtr<nsPlainTextSerializer> it = new nsPlainTextSerializer();
   it.forget(aSerializer);
   return NS_OK;
 }
 
@@ -111,20 +122,16 @@ nsPlainTextSerializer::nsPlainTextSerial
 
 nsPlainTextSerializer::~nsPlainTextSerializer()
 {
   delete[] mTagStack;
   delete[] mOLStack;
   NS_WARNING_ASSERTION(mHeadLevel == 0, "Wrong head level!");
 }
 
-NS_IMPL_ISUPPORTS(nsPlainTextSerializer,
-                  nsIContentSerializer)
-
-
 NS_IMETHODIMP 
 nsPlainTextSerializer::Init(uint32_t aFlags, uint32_t aWrapColumn,
                             const char* aCharSet, bool aIsCopying,
                             bool aIsWholeDocument)
 {
 #ifdef DEBUG
   // Check if the major control flags are set correctly.
   if (aFlags & nsIDocumentEncoder::OutputFormatFlowed) {
--- a/dom/base/nsPlainTextSerializer.h
+++ b/dom/base/nsPlainTextSerializer.h
@@ -32,17 +32,18 @@ class Element;
 } // namespace dom
 } // namespace mozilla
 
 class nsPlainTextSerializer final : public nsIContentSerializer
 {
 public:
   nsPlainTextSerializer();
 
-  NS_DECL_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTING_ISUPPORTS
+  NS_DECL_CYCLE_COLLECTION_CLASS(nsPlainTextSerializer)
 
   // nsIContentSerializer
   NS_IMETHOD Init(uint32_t flags, uint32_t aWrapColumn,
                   const char* aCharSet, bool aIsCopying,
                   bool aIsWholeDocument) override;
 
   NS_IMETHOD AppendText(nsIContent* aText, int32_t aStartOffset,
                         int32_t aEndOffset, nsAString& aStr) override;
--- a/dom/canvas/WebGLContextUtils.cpp
+++ b/dom/canvas/WebGLContextUtils.cpp
@@ -6,17 +6,16 @@
 #include "WebGLContextUtils.h"
 #include "WebGLContext.h"
 
 #include "GLContext.h"
 #include "jsapi.h"
 #include "mozilla/dom/ScriptSettings.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Sprintf.h"
-#include "nsIDOMDataContainerEvent.h"
 #include "nsIDOMEvent.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsIVariant.h"
 #include "nsPrintfCString.h"
 #include "nsServiceManagerUtils.h"
 #include <stdarg.h>
 #include "WebGLBuffer.h"
 #include "WebGLExtensions.h"
deleted file mode 100644
--- a/dom/events/DataContainerEvent.cpp
+++ /dev/null
@@ -1,98 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: set ts=8 sts=2 et sw=2 tw=80: */
-/* 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/. */
-
-#include "mozilla/dom/DataContainerEvent.h"
-#include "nsContentUtils.h"
-#include "nsIDocument.h"
-#include "nsIXPConnect.h"
-
-namespace mozilla {
-namespace dom {
-
-DataContainerEvent::DataContainerEvent(EventTarget* aOwner,
-                                       nsPresContext* aPresContext,
-                                       WidgetEvent* aEvent)
-  : Event(aOwner, aPresContext, aEvent)
-{
-  if (nsCOMPtr<nsPIDOMWindowInner> win = do_QueryInterface(aOwner)) {
-    if (nsIDocument* doc = win->GetExtantDoc()) {
-      doc->WarnOnceAbout(nsIDocument::eDataContainerEvent);
-    }
-  }
-}
-
-NS_IMPL_CYCLE_COLLECTION_CLASS(DataContainerEvent)
-
-NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(DataContainerEvent, Event)
-  NS_IMPL_CYCLE_COLLECTION_UNLINK(mData)
-NS_IMPL_CYCLE_COLLECTION_UNLINK_END
-
-NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(DataContainerEvent, Event)
-  NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mData)
-NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
-
-NS_IMPL_ADDREF_INHERITED(DataContainerEvent, Event)
-NS_IMPL_RELEASE_INHERITED(DataContainerEvent, Event)
-
-NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(DataContainerEvent)
-  NS_INTERFACE_MAP_ENTRY(nsIDOMDataContainerEvent)
-NS_INTERFACE_MAP_END_INHERITING(Event)
-
-NS_IMETHODIMP
-DataContainerEvent::GetData(const nsAString& aKey, nsIVariant** aData)
-{
-  NS_ENSURE_ARG_POINTER(aData);
-
-  mData.Get(aKey, aData);
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-DataContainerEvent::SetData(const nsAString& aKey, nsIVariant* aData)
-{
-  NS_ENSURE_ARG(aData);
-
-  // Make sure this event isn't already being dispatched.
-  NS_ENSURE_STATE(!mEvent->mFlags.mIsBeingDispatched);
-  mData.Put(aKey, aData);
-  return NS_OK;
-}
-
-void
-DataContainerEvent::SetData(JSContext* aCx, const nsAString& aKey,
-                            JS::Handle<JS::Value> aVal,
-                            ErrorResult& aRv)
-{
-  if (!nsContentUtils::XPConnect()) {
-    aRv = NS_ERROR_FAILURE;
-    return;
-  }
-  nsCOMPtr<nsIVariant> val;
-  nsresult rv =
-    nsContentUtils::XPConnect()->JSToVariant(aCx, aVal, getter_AddRefs(val));
-  if (NS_FAILED(rv)) {
-    aRv = rv;
-    return;
-  }
-  aRv = SetData(aKey, val);
-}
-
-} // namespace dom
-} // namespace mozilla
-
-using namespace mozilla;
-using namespace mozilla::dom;
-
-already_AddRefed<DataContainerEvent>
-NS_NewDOMDataContainerEvent(EventTarget* aOwner,
-                            nsPresContext* aPresContext,
-                            WidgetEvent* aEvent)
-{
-  RefPtr<DataContainerEvent> it =
-    new DataContainerEvent(aOwner, aPresContext, aEvent);
-  return it.forget();
-}
-
deleted file mode 100644
--- a/dom/events/DataContainerEvent.h
+++ /dev/null
@@ -1,65 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: set ts=8 sts=2 et sw=2 tw=80: */
-/* 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/. */
-
-#ifndef mozilla_dom_DataContainerEvent_h_
-#define mozilla_dom_DataContainerEvent_h_
-
-#include "mozilla/dom/DataContainerEventBinding.h"
-#include "mozilla/dom/Event.h"
-#include "nsIDOMDataContainerEvent.h"
-#include "nsInterfaceHashtable.h"
-
-namespace mozilla {
-namespace dom {
-
-class DataContainerEvent : public Event,
-                           public nsIDOMDataContainerEvent
-{
-public:
-  DataContainerEvent(EventTarget* aOwner,
-                     nsPresContext* aPresContext,
-                     WidgetEvent* aEvent);
-
-  NS_DECL_ISUPPORTS_INHERITED
-
-  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(DataContainerEvent, Event)
-
-  NS_FORWARD_TO_EVENT
-
-  NS_DECL_NSIDOMDATACONTAINEREVENT
-
-  virtual JSObject*
-  WrapObjectInternal(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override
-  {
-    return DataContainerEventBinding::Wrap(aCx, this, aGivenProto);
-  }
-
-  already_AddRefed<nsIVariant> GetData(const nsAString& aKey)
-  {
-    nsCOMPtr<nsIVariant> val;
-    GetData(aKey, getter_AddRefs(val));
-    return val.forget();
-  }
-
-  void SetData(JSContext* aCx, const nsAString& aKey,
-               JS::Handle<JS::Value> aVal, ErrorResult& aRv);
-
-protected:
-  ~DataContainerEvent() {}
-
-private:
-  nsInterfaceHashtable<nsStringHashKey, nsIVariant> mData;
-};
-
-} // namespace dom
-} // namespace mozilla
-
-already_AddRefed<mozilla::dom::DataContainerEvent>
-NS_NewDOMDataContainerEvent(mozilla::dom::EventTarget* aOwner,
-                            nsPresContext* aPresContext,
-                            mozilla::WidgetEvent* aEvent);
-
-#endif // mozilla_dom_DataContainerEvent_h_
--- a/dom/events/EventDispatcher.cpp
+++ b/dom/events/EventDispatcher.cpp
@@ -12,17 +12,16 @@
 #include "nsIDocument.h"
 #include "nsINode.h"
 #include "nsPIDOMWindow.h"
 #include "AnimationEvent.h"
 #include "BeforeUnloadEvent.h"
 #include "ClipboardEvent.h"
 #include "CommandEvent.h"
 #include "CompositionEvent.h"
-#include "DataContainerEvent.h"
 #include "DeviceMotionEvent.h"
 #include "DragEvent.h"
 #include "GeckoProfiler.h"
 #include "KeyboardEvent.h"
 #include "mozilla/ContentEvents.h"
 #include "mozilla/dom/CloseEvent.h"
 #include "mozilla/dom/CustomEvent.h"
 #include "mozilla/dom/DeviceOrientationEvent.h"
@@ -897,17 +896,18 @@ EventDispatcher::DispatchDOMEvent(nsISup
   }
   return NS_ERROR_ILLEGAL_VALUE;
 }
 
 /* static */ already_AddRefed<dom::Event>
 EventDispatcher::CreateEvent(EventTarget* aOwner,
                              nsPresContext* aPresContext,
                              WidgetEvent* aEvent,
-                             const nsAString& aEventType)
+                             const nsAString& aEventType,
+                             CallerType aCallerType)
 {
   if (aEvent) {
     switch(aEvent->mClass) {
     case eMutationEventClass:
       return NS_NewDOMMutationEvent(aOwner, aPresContext,
                                     aEvent->AsMutationEvent());
     case eGUIEventClass:
     case eScrollPortEventClass:
@@ -973,52 +973,40 @@ EventDispatcher::CreateEvent(EventTarget
   if (aEventType.LowerCaseEqualsLiteral("mouseevent")) {
     LOG_EVENT_CREATION(MOUSEEVENT);
     return NS_NewDOMMouseEvent(aOwner, aPresContext, nullptr);
   }
   if (aEventType.LowerCaseEqualsLiteral("mouseevents")) {
     LOG_EVENT_CREATION(MOUSEEVENTS);
     return NS_NewDOMMouseEvent(aOwner, aPresContext, nullptr);
   }
-  if (aEventType.LowerCaseEqualsLiteral("popupevents")) {
-    LOG_EVENT_CREATION(POPUPEVENTS);
-    return NS_NewDOMMouseEvent(aOwner, aPresContext, nullptr);
-  }
   if (aEventType.LowerCaseEqualsLiteral("mousescrollevents")) {
     LOG_EVENT_CREATION(MOUSESCROLLEVENTS);
     return NS_NewDOMMouseScrollEvent(aOwner, aPresContext, nullptr);
   }
   if (aEventType.LowerCaseEqualsLiteral("dragevent")) {
     LOG_EVENT_CREATION(DRAGEVENT);
     return NS_NewDOMDragEvent(aOwner, aPresContext, nullptr);
   }
-  if (aEventType.LowerCaseEqualsLiteral("dragevents")) {
-    LOG_EVENT_CREATION(DRAGEVENTS);
-    return NS_NewDOMDragEvent(aOwner, aPresContext, nullptr);
-  }
   if (aEventType.LowerCaseEqualsLiteral("keyboardevent")) {
     LOG_EVENT_CREATION(KEYBOARDEVENT);
     return NS_NewDOMKeyboardEvent(aOwner, aPresContext, nullptr);
   }
   if (aEventType.LowerCaseEqualsLiteral("keyevents")) {
     LOG_EVENT_CREATION(KEYEVENTS);
     return NS_NewDOMKeyboardEvent(aOwner, aPresContext, nullptr);
   }
   if (aEventType.LowerCaseEqualsLiteral("compositionevent")) {
     LOG_EVENT_CREATION(COMPOSITIONEVENT);
     return NS_NewDOMCompositionEvent(aOwner, aPresContext, nullptr);
   }
   if (aEventType.LowerCaseEqualsLiteral("textevent")) {
     LOG_EVENT_CREATION(TEXTEVENT);
     return NS_NewDOMCompositionEvent(aOwner, aPresContext, nullptr);
   }
-  if (aEventType.LowerCaseEqualsLiteral("textevents")) {
-    LOG_EVENT_CREATION(TEXTEVENTS);
-    return NS_NewDOMCompositionEvent(aOwner, aPresContext, nullptr);
-  }
   if (aEventType.LowerCaseEqualsLiteral("mutationevent")) {
     LOG_EVENT_CREATION(MUTATIONEVENT);
     return NS_NewDOMMutationEvent(aOwner, aPresContext, nullptr);
   }
   if (aEventType.LowerCaseEqualsLiteral("mutationevents")) {
     LOG_EVENT_CREATION(MUTATIONEVENTS);
     return NS_NewDOMMutationEvent(aOwner, aPresContext, nullptr);
   }
@@ -1049,82 +1037,33 @@ EventDispatcher::CreateEvent(EventTarget
   if (aEventType.LowerCaseEqualsLiteral("events")) {
     LOG_EVENT_CREATION(EVENTS);
     return NS_NewDOMEvent(aOwner, aPresContext, nullptr);
   }
   if (aEventType.LowerCaseEqualsLiteral("htmlevents")) {
     LOG_EVENT_CREATION(HTMLEVENTS);
     return NS_NewDOMEvent(aOwner, aPresContext, nullptr);
   }
-  if (aEventType.LowerCaseEqualsLiteral("svgevent")) {
-    LOG_EVENT_CREATION(SVGEVENT);
-    return NS_NewDOMEvent(aOwner, aPresContext, nullptr);
-  }
   if (aEventType.LowerCaseEqualsLiteral("svgevents")) {
     LOG_EVENT_CREATION(SVGEVENTS);
     return NS_NewDOMEvent(aOwner, aPresContext, nullptr);
   }
   if (aEventType.LowerCaseEqualsLiteral("timeevent")) {
     LOG_EVENT_CREATION(TIMEEVENT);
     return NS_NewDOMTimeEvent(aOwner, aPresContext, nullptr);
   }
-  if (aEventType.LowerCaseEqualsLiteral("timeevents")) {
-    LOG_EVENT_CREATION(TIMEEVENTS);
-    return NS_NewDOMTimeEvent(aOwner, aPresContext, nullptr);
-  }
-  if (aEventType.LowerCaseEqualsLiteral("xulcommandevent")) {
-    LOG_EVENT_CREATION(XULCOMMANDEVENT);
-    return NS_NewDOMXULCommandEvent(aOwner, aPresContext, nullptr);
-  }
-  if (aEventType.LowerCaseEqualsLiteral("xulcommandevents")) {
-    LOG_EVENT_CREATION(XULCOMMANDEVENTS);
-    return NS_NewDOMXULCommandEvent(aOwner, aPresContext, nullptr);
-  }
-  if (aEventType.LowerCaseEqualsLiteral("commandevent")) {
-    LOG_EVENT_CREATION(COMMANDEVENT);
-    return NS_NewDOMCommandEvent(aOwner, aPresContext, nullptr);
-  }
-  if (aEventType.LowerCaseEqualsLiteral("commandevents")) {
-    LOG_EVENT_CREATION(COMMANDEVENTS);
-    return NS_NewDOMCommandEvent(aOwner, aPresContext, nullptr);
-  }
-  if (aEventType.LowerCaseEqualsLiteral("datacontainerevent")) {
-    LOG_EVENT_CREATION(DATACONTAINEREVENT);
-    return NS_NewDOMDataContainerEvent(aOwner, aPresContext, nullptr);
-  }
-  if (aEventType.LowerCaseEqualsLiteral("datacontainerevents")) {
-    LOG_EVENT_CREATION(DATACONTAINEREVENTS);
-    return NS_NewDOMDataContainerEvent(aOwner, aPresContext, nullptr);
-  }
   if (aEventType.LowerCaseEqualsLiteral("messageevent")) {
     LOG_EVENT_CREATION(MESSAGEEVENT);
     RefPtr<Event> event = new MessageEvent(aOwner, aPresContext, nullptr);
     return event.forget();
   }
-  if (aEventType.LowerCaseEqualsLiteral("notifypaintevent")) {
-    LOG_EVENT_CREATION(NOTIFYPAINTEVENT);
-    return NS_NewDOMNotifyPaintEvent(aOwner, aPresContext, nullptr);
-  }
-  if (aEventType.LowerCaseEqualsLiteral("simplegestureevent")) {
-    LOG_EVENT_CREATION(SIMPLEGESTUREEVENT);
-    return NS_NewDOMSimpleGestureEvent(aOwner, aPresContext, nullptr);
-  }
   if (aEventType.LowerCaseEqualsLiteral("beforeunloadevent")) {
     LOG_EVENT_CREATION(BEFOREUNLOADEVENT);
     return NS_NewDOMBeforeUnloadEvent(aOwner, aPresContext, nullptr);
   }
-  // XXXkhuey this is broken
-  if (aEventType.LowerCaseEqualsLiteral("pagetransition")) {
-    LOG_EVENT_CREATION(PAGETRANSITION);
-    PageTransitionEventInit init;
-    RefPtr<Event> event =
-      PageTransitionEvent::Constructor(aOwner, EmptyString(), init);
-    event->MarkUninitialized();
-    return event.forget();
-  }
   if (aEventType.LowerCaseEqualsLiteral("scrollareaevent")) {
     LOG_EVENT_CREATION(SCROLLAREAEVENT);
     return NS_NewDOMScrollAreaEvent(aOwner, aPresContext, nullptr);
   }
   // XXXkhuey Chrome supports popstateevent here, even though it provides no
   // initPopStateEvent method.  This is nuts ... but copying it is unlikely to
   // break the web.
   if (aEventType.LowerCaseEqualsLiteral("popstateevent")) {
@@ -1164,16 +1103,31 @@ EventDispatcher::CreateEvent(EventTarget
     LOG_EVENT_CREATION(ERROREVENT);
     RootedDictionary<ErrorEventInit> init(RootingCx());
     RefPtr<Event> event =
       ErrorEvent::Constructor(aOwner, EmptyString(), init);
     event->MarkUninitialized();
     return event.forget();
   }
 
+  // Only allow these events for chrome
+  if (aCallerType == CallerType::System) {
+    if (aEventType.LowerCaseEqualsLiteral("simplegestureevent")) {
+      return NS_NewDOMSimpleGestureEvent(aOwner, aPresContext, nullptr);
+    }
+    if (aEventType.LowerCaseEqualsLiteral("xulcommandevent")) {
+      LOG_EVENT_CREATION(XULCOMMANDEVENT);
+      return NS_NewDOMXULCommandEvent(aOwner, aPresContext, nullptr);
+    }
+    if (aEventType.LowerCaseEqualsLiteral("xulcommandevents")) {
+      LOG_EVENT_CREATION(XULCOMMANDEVENTS);
+      return NS_NewDOMXULCommandEvent(aOwner, aPresContext, nullptr);
+    }
+  }
+
 #undef LOG_EVENT_CREATION
 
   // NEW EVENT TYPES SHOULD NOT BE ADDED HERE; THEY SHOULD USE ONLY EVENT
   // CONSTRUCTORS
 
   return nullptr;
 }
 
--- a/dom/events/EventDispatcher.h
+++ b/dom/events/EventDispatcher.h
@@ -3,16 +3,17 @@
 /* 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/. */
 
 #ifdef MOZILLA_INTERNAL_API
 #ifndef mozilla_EventDispatcher_h_
 #define mozilla_EventDispatcher_h_
 
+#include "mozilla/dom/BindingDeclarations.h"
 #include "mozilla/EventForwards.h"
 #include "nsCOMPtr.h"
 #include "nsTArray.h"
 
 // Microsoft's API Name hackery sucks
 #undef CreateEvent
 
 class nsIContent;
@@ -281,17 +282,19 @@ public:
                                    nsEventStatus* aEventStatus);
 
   /**
    * Creates a DOM Event.  Returns null if the event type is unsupported.
    */
   static already_AddRefed<dom::Event> CreateEvent(dom::EventTarget* aOwner,
                                                   nsPresContext* aPresContext,
                                                   WidgetEvent* aEvent,
-                                                  const nsAString& aEventType);
+                                                  const nsAString& aEventType,
+                                                  dom::CallerType aCallerType =
+                                                    dom::CallerType::System);
 
   /**
    * Called at shutting down.
    */
   static void Shutdown();
 };
 
 } // namespace mozilla
--- a/dom/events/moz.build
+++ b/dom/events/moz.build
@@ -39,17 +39,16 @@ EXPORTS.mozilla += [
 
 EXPORTS.mozilla.dom += [
     'AnimationEvent.h',
     'BeforeUnloadEvent.h',
     'ClipboardEvent.h',
     'CommandEvent.h',
     'CompositionEvent.h',
     'CustomEvent.h',
-    'DataContainerEvent.h',
     'DataTransfer.h',
     'DataTransferItem.h',
     'DataTransferItemList.h',
     'DeviceMotionEvent.h',
     'DragEvent.h',
     'Event.h',
     'EventTarget.h',
     'FocusEvent.h',
@@ -82,17 +81,16 @@ UNIFIED_SOURCES += [
     'AnimationEvent.cpp',
     'AsyncEventDispatcher.cpp',
     'BeforeUnloadEvent.cpp',
     'ClipboardEvent.cpp',
     'CommandEvent.cpp',
     'CompositionEvent.cpp',
     'ContentEventHandler.cpp',
     'CustomEvent.cpp',
-    'DataContainerEvent.cpp',
     'DataTransfer.cpp',
     'DataTransferItem.cpp',
     'DataTransferItemList.cpp',
     'DeviceMotionEvent.cpp',
     'DOMEventTargetHelper.cpp',
     'DragEvent.cpp',
     'Event.cpp',
     'EventDispatcher.cpp',
--- a/dom/events/test/chrome.ini
+++ b/dom/events/test/chrome.ini
@@ -6,17 +6,16 @@ support-files =
   bug418986-3.js
   bug591249_iframe.xul
   bug602962.xul
   file_bug679494.html
   window_bug617528.xul
   test_bug336682.js
 
 [test_bug336682_2.xul]
-[test_bug368835.html]
 [test_bug415498.xul]
 [test_bug418986-3.xul]
 [test_bug524674.xul]
 [test_bug586961.xul]
 [test_bug591249.xul]
 [test_bug602962.xul]
 [test_bug617528.xul]
 [test_bug679494.xul]
--- a/dom/events/test/mochitest.ini
+++ b/dom/events/test/mochitest.ini
@@ -23,17 +23,16 @@ support-files =
 [test_bug299673-1.html]
 [test_bug1037990.html]
 [test_bug299673-2.html]
 [test_bug322588.html]
 [test_bug328885.html]
 [test_bug336682_1.html]
 support-files = test_bug336682.js
 [test_bug367781.html]
-[test_bug368835.html]
 [test_bug379120.html]
 [test_bug391568.xhtml]
 [test_bug402089.html]
 [test_bug405632.html]
 [test_bug409604.html]
 skip-if = toolkit == 'android' #TIMED_OUT
 [test_bug412567.html]
 [test_bug418986-3.html]
--- a/dom/events/test/test_all_synthetic_events.html
+++ b/dom/events/test/test_all_synthetic_events.html
@@ -63,44 +63,31 @@ const kEventConstructors = {
   CloseEvent:                                { create: function (aName, aProps) {
                                                          return new CloseEvent(aName, aProps);
                                                        },
                                              },
   ClipboardEvent:                            { create: function (aName, aProps) {
                                                          return new ClipboardEvent(aName, aProps);
                                                        },
                                              },
-  CommandEvent:                              { create: function (aName, aProps) {
-                                                         var e = document.createEvent("commandevent");
-                                                         e.initCommandEvent(aName, aProps.bubbles, aProps.cancelable,
-                                                                            aProps.command);
-                                                         return e;
-                                                       },
-                                             },
   CompositionEvent:                          { create: function (aName, aProps) {
                                                          var e = document.createEvent("compositionevent");
                                                          e.initCompositionEvent(aName, aProps.bubbles, aProps.cancelable,
                                                                                 aProps.view, aProps.data, aProps.locale);
                                                          return e;
                                                        },
                                              },
   CustomEvent:                               { create: function (aName, aProps) {
                                                          return new CustomEvent(aName, aProps);
                                                        },
                                              },
   DataErrorEvent:                            { create: function (aName, aProps) {
                                                           return new DataErrorEvent(aName, aProps);
                                                        },
                                              },
-  DataContainerEvent:                        { create: function (aName, aProps) {
-                                                         var e = document.createEvent("datacontainerevent");
-                                                         e.initEvent(aName, aProps.bubbles, aProps.cancelable);
-                                                         return e;
-                                                       },
-                                             },
   DeviceLightEvent:                          { create: function (aName, aProps) {
                                                          return new DeviceLightEvent(aName, aProps);
                                                        },
                                              },
   DeviceMotionEvent:                         { create: function (aName, aProps) {
                                                          var e = document.createEvent("devicemotionevent");
                                                          e.initDeviceMotionEvent(aName, aProps.bubbles, aProps.cancelable, aProps.acceleration,
                                                                                  aProps.accelerationIncludingGravity, aProps.rotationRate,
@@ -266,22 +253,16 @@ const kEventConstructors = {
   MutationEvent:                             { create: function (aName, aProps) {
                                                          var e = document.createEvent("mutationevent");
                                                          e.initMutationEvent(aName, aProps.bubbles, aProps.cancelable,
                                                                              aProps.relatedNode, aProps.prevValue, aProps.newValue,
                                                                              aProps.attrName, aProps.attrChange);
                                                          return e;
                                                        },
                                              },
-  NotifyPaintEvent:                          { create: function (aName, aProps) {
-                                                         var e = document.createEvent("notifypaintevent");
-                                                         e.initEvent(aName, aProps.bubbles, aProps.cancelable);
-                                                         return e;
-                                                       },
-                                             },
   OfflineAudioCompletionEvent:               { create: "AudioContext" in self
                                                         ? function (aName, aProps) {
                                                             var ac = new AudioContext();
                                                             var ab = new AudioBuffer({ length: 42, sampleRate: ac.sampleRate });
                                                             aProps.renderedBuffer = ab;
                                                             return new OfflineAudioCompletionEvent(aName, aProps);
                                                           }
                                                         : null,
@@ -329,29 +310,16 @@ const kEventConstructors = {
                                                          var e = document.createEvent("scrollareaevent");
                                                          e.initScrollAreaEvent(aName, aProps.bubbles, aProps.cancelable,
                                                                                aProps.view, aProps.details,
                                                                                aProps.x || 0.0, aProps.y || 0.0,
                                                                                aProps.width || 0.0, aProps.height || 0.0);
                                                          return e;
                                                        },
                                              },
-  SimpleGestureEvent:                        { create: function (aName, aProps) {
-                                                         var e = document.createEvent("simplegestureevent");
-                                                         e.initSimpleGestureEvent(aName, aProps.bubbles, aProps.cancelable,
-                                                                                  aProps.view, aProps.detail,
-                                                                                  aProps.screenX, aProps.screenY,
-                                                                                  aProps.clientX, aProps.clientY,
-                                                                                  aProps.ctrlKey, aProps.altKey, aProps.shiftKey, aProps.metaKey,
-                                                                                  aProps.button, aProps.relatedTarget,
-                                                                                  aProps.allowedDirections, aProps.direction, aProps.delta || 0.0,
-                                                                                  aProps.clickCount);
-                                                         return e;
-                                                       },
-                                             },
   SpeechRecognitionError:                    { create: function (aName, aProps) {
                                                          return new SpeechRecognitionError(aName, aProps);
                                                        },
                                              },
   SpeechRecognitionEvent:                    { create: function (aName, aProps) {
                                                          return new SpeechRecognitionEvent(aName, aProps);
                                                        },
                                              },
deleted file mode 100644
--- a/dom/events/test/test_bug368835.html
+++ /dev/null
@@ -1,101 +0,0 @@
-<!DOCTYPE HTML>
-<html>
-<!--
-https://bugzilla.mozilla.org/show_bug.cgi?id=368835
--->
-  <head>
-    <title>Test for Bug 368835</title>
-
-    <link rel="stylesheet" type="text/css" href="http://mochi.test:8888/tests/SimpleTest/test.css" />
-
-    <script type="text/javascript" src="http://mochi.test:8888/tests/SimpleTest/SimpleTest.js"></script>
-  </head>
-
-  <body>
-    <a target="_blank"
-       href="https://bugzilla.mozilla.org/show_bug.cgi?id=368835">
-      Mozilla Bug 368835
-    </a>
-    <p id="display"></p>
-    <div id="content" style="display: none">
-    </div>
-    <pre id="test">
-    <script class="testbody" type="text/javascript">
-      function dataContainerEventHandler(aEvent)
-      {
-        var value = "";
-        var isPassed = true;
-        try {
-          value = aEvent.getData("data1");
-          isPassed = true;
-        } catch (e) {
-          isPassed = false;
-        }
-
-        ok(isPassed, "getData shouldn't fail.");
-        ok(value == "data1", "Wrong value of data.");
-
-        is(aEvent.getData("document"), document);
-        is(aEvent.getData("window"), window);
-        is(aEvent.getData("event"), aEvent);
-        is(aEvent.getData("null"), null);
-        is(aEvent.getData("1"), 1);
-        is(aEvent.getData("1.1"), 1.1);
-        is(aEvent.getData("true"), true);
-
-        try {
-          aEvent.setData("data3", "data3");
-          isPassed = false;
-        } catch (e) {
-          isPassed = true;
-        }
-
-        ok(isPassed, "setData should fail during event dispatching.");
-      }
-
-      function doTest()
-      {
-        var isPassed;
-        var event = null;
-
-        try {
-          event = document.createEvent("datacontainerevents");
-          isPassed = true;
-        } catch (e) {
-          isPassed = false;
-        }
-
-        ok(isPassed, "Document should know about 'datacontainerevents' event class.");
-        ok(("setData" in event), "nsIDOMDataContainerEvent isn't available.");
-
-        event.initEvent("dataContainerEvent", true, true);
-
-        try {
-          event.setData("data1", "data1");
-          event.setData("document", document);
-          event.setData("window", window);
-          event.setData("event", event);
-          event.setData("null", null);
-          event.setData("1", 1);
-          event.setData("1.1", 1.1);
-          event.setData("true", true);
-          isPassed = true;
-        } catch (e) {
-          isPassed = false;
-        }
-
-        ok(isPassed, "setData shouldn't fail when event is initialized.");
-
-        document.body.addEventListener("dataContainerEvent",
-                                       dataContainerEventHandler, true);
-        document.body.dispatchEvent(event);
-        SimpleTest.finish();
-      }
-
-      SimpleTest.waitForExplicitFinish();
-      addLoadEvent(doTest);
-    </script>
-    </pre>
-  </body>
-</html>
-
--- a/dom/interfaces/events/moz.build
+++ b/dom/interfaces/events/moz.build
@@ -8,17 +8,16 @@ with Files("**"):
     BUG_COMPONENT = ("Core", "DOM: Events")
 
 XPIDL_SOURCES += [
     'nsIDOMAnimationEvent.idl',
     'nsIDOMBeforeUnloadEvent.idl',
     'nsIDOMClipboardEvent.idl',
     'nsIDOMCommandEvent.idl',
     'nsIDOMCustomEvent.idl',
-    'nsIDOMDataContainerEvent.idl',
     'nsIDOMDataTransfer.idl',
     'nsIDOMDragEvent.idl',
     'nsIDOMEvent.idl',
     'nsIDOMEventListener.idl',
     'nsIDOMEventTarget.idl',
     'nsIDOMFocusEvent.idl',
     'nsIDOMKeyEvent.idl',
     'nsIDOMMouseEvent.idl',
deleted file mode 100644
--- a/dom/interfaces/events/nsIDOMDataContainerEvent.idl
+++ /dev/null
@@ -1,30 +0,0 @@
-/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* 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/. */
-
-#include "nsISupports.idl"
-#include "nsIVariant.idl"
-
-[builtinclass, uuid(a9f1f528-d106-4fea-8663-2d7f64b627a9)]
-interface nsIDOMDataContainerEvent : nsISupports
-{
-  /**
-   * Return the data associated with the given key.
-   *
-   * @param  key  the key
-   * @return      the data associated with the key
-   */
-  nsIVariant getData(in DOMString key);
-
-  /**
-   * Set the data for the given key.
-   *
-   * @param  key   the data key
-   * @param  data  the data
-   * @throws       NS_ERROR_UNEXPECTED if the method is called during event
-   *               dispatch
-   */
-  void setData(in DOMString key, in nsIVariant data);
-};
-
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -46,16 +46,17 @@
 #include "mozilla/psm/PSMContentListener.h"
 #include "mozilla/hal_sandbox/PHalChild.h"
 #include "mozilla/ipc/BackgroundChild.h"
 #include "mozilla/ipc/FileDescriptorSetChild.h"
 #include "mozilla/ipc/FileDescriptorUtils.h"
 #include "mozilla/ipc/GeckoChildProcessHost.h"
 #include "mozilla/ipc/ProcessChild.h"
 #include "mozilla/ipc/PChildToParentStreamChild.h"
+#include "mozilla/intl/LocaleService.h"
 #include "mozilla/ipc/TestShellChild.h"
 #include "mozilla/jsipc/CrossProcessObjectWrappers.h"
 #include "mozilla/layers/APZChild.h"
 #include "mozilla/layers/CompositorBridgeChild.h"
 #include "mozilla/layers/ContentProcessController.h"
 #include "mozilla/layers/ImageBridgeChild.h"
 #include "mozilla/layout/RenderFrameChild.h"
 #include "mozilla/net/NeckoChild.h"
@@ -218,16 +219,17 @@ using namespace mozilla;
 using namespace mozilla::docshell;
 using namespace mozilla::dom::ipc;
 using namespace mozilla::dom::workers;
 using namespace mozilla::media;
 using namespace mozilla::embedding;
 using namespace mozilla::gmp;
 using namespace mozilla::hal_sandbox;
 using namespace mozilla::ipc;
+using namespace mozilla::intl;
 using namespace mozilla::layers;
 using namespace mozilla::layout;
 using namespace mozilla::net;
 using namespace mozilla::jsipc;
 using namespace mozilla::psm;
 using namespace mozilla::widget;
 #if defined(MOZ_WIDGET_GONK)
 using namespace mozilla::system;
@@ -994,16 +996,19 @@ ContentChild::InitXPCOM(const XPCOMInitD
   mConsoleListener = new ConsoleListener(this);
   if (NS_FAILED(svc->RegisterListener(mConsoleListener)))
     NS_WARNING("Couldn't register console listener for child process");
 
   mAvailableDictionaries = aXPCOMInit.dictionaries();
 
   RecvSetOffline(aXPCOMInit.isOffline());
   RecvSetConnectivity(aXPCOMInit.isConnected());
+  LocaleService::GetInstance()->AssignAppLocales(aXPCOMInit.appLocales());
+  LocaleService::GetInstance()->AssignRequestedLocales(aXPCOMInit.requestedLocales());
+
   RecvSetCaptivePortalState(aXPCOMInit.captivePortalState());
   RecvBidiKeyboardNotify(aXPCOMInit.isLangRTL(), aXPCOMInit.haveBidiKeyboards());
 
   // Create the CPOW manager as soon as possible.
   SendPJavaScriptConstructor();
 
   if (aXPCOMInit.domainPolicy().active()) {
     nsIScriptSecurityManager* ssm = nsContentUtils::GetSecurityManager();
@@ -2268,16 +2273,30 @@ mozilla::ipc::IPCResult
 ContentChild::RecvUpdateDictionaryList(InfallibleTArray<nsString>&& aDictionaries)
 {
   mAvailableDictionaries = aDictionaries;
   mozInlineSpellChecker::UpdateCanEnableInlineSpellChecking();
   return IPC_OK();
 }
 
 mozilla::ipc::IPCResult
+ContentChild::RecvUpdateAppLocales(nsTArray<nsCString>&& aAppLocales)
+{
+  LocaleService::GetInstance()->AssignAppLocales(aAppLocales);
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
+ContentChild::RecvUpdateRequestedLocales(nsTArray<nsCString>&& aRequestedLocales)
+{
+  LocaleService::GetInstance()->AssignRequestedLocales(aRequestedLocales);
+  return IPC_OK();
+}
+
+mozilla::ipc::IPCResult
 ContentChild::RecvAddPermission(const IPC::Permission& permission)
 {
 #if MOZ_PERMISSIONS
   nsCOMPtr<nsIPermissionManager> permissionManagerIface =
     services::GetPermissionManager();
   nsPermissionManager* permissionManager =
     static_cast<nsPermissionManager*>(permissionManagerIface.get());
   MOZ_ASSERT(permissionManager,
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -368,16 +368,19 @@ public:
                                                    const ClonedMessageData& aData) override;
 
   virtual mozilla::ipc::IPCResult RecvGeolocationUpdate(const GeoPosition& somewhere) override;
 
   virtual mozilla::ipc::IPCResult RecvGeolocationError(const uint16_t& errorCode) override;
 
   virtual mozilla::ipc::IPCResult RecvUpdateDictionaryList(InfallibleTArray<nsString>&& aDictionaries) override;
 
+  virtual mozilla::ipc::IPCResult RecvUpdateAppLocales(nsTArray<nsCString>&& aAppLocales) override;
+  virtual mozilla::ipc::IPCResult RecvUpdateRequestedLocales(nsTArray<nsCString>&& aRequestedLocales) override;
+
   virtual mozilla::ipc::IPCResult RecvAddPermission(const IPC::Permission& permission) override;
 
   virtual mozilla::ipc::IPCResult RecvFlushMemory(const nsString& reason) override;
 
   virtual mozilla::ipc::IPCResult RecvActivateA11y(const uint32_t& aMsaaID) override;
   virtual mozilla::ipc::IPCResult RecvShutdownA11y() override;
 
   virtual mozilla::ipc::IPCResult RecvGarbageCollect() override;
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -73,16 +73,17 @@
 #include "mozilla/gfx/GPUProcessManager.h"
 #include "mozilla/hal_sandbox/PHalParent.h"
 #include "mozilla/ipc/BackgroundChild.h"
 #include "mozilla/ipc/BackgroundParent.h"
 #include "mozilla/ipc/FileDescriptorUtils.h"
 #include "mozilla/ipc/PChildToParentStreamParent.h"
 #include "mozilla/ipc/TestShellParent.h"
 #include "mozilla/ipc/IPCStreamUtils.h"
+#include "mozilla/intl/LocaleService.h"
 #include "mozilla/jsipc/CrossProcessObjectWrappers.h"
 #include "mozilla/layers/PAPZParent.h"
 #include "mozilla/layers/CompositorThread.h"
 #include "mozilla/layers/ImageBridgeParent.h"
 #include "mozilla/layers/LayerTreeOwnerTracker.h"
 #include "mozilla/layout/RenderFrameParent.h"
 #include "mozilla/LookAndFeel.h"
 #include "mozilla/media/MediaParent.h"
@@ -271,16 +272,17 @@ using namespace CrashReporter;
 #endif
 using namespace mozilla::dom::power;
 using namespace mozilla::media;
 using namespace mozilla::embedding;
 using namespace mozilla::gfx;
 using namespace mozilla::gmp;
 using namespace mozilla::hal;
 using namespace mozilla::ipc;
+using namespace mozilla::intl;
 using namespace mozilla::layers;
 using namespace mozilla::layout;
 using namespace mozilla::net;
 using namespace mozilla::jsipc;
 using namespace mozilla::psm;
 using namespace mozilla::widget;
 
 // XXX Workaround for bug 986973 to maintain the existing broken semantics
@@ -561,16 +563,18 @@ static const char* sObserverTopics[] = {
   "child-cc-request",
   "child-mmu-request",
   "last-pb-context-exited",
   "file-watcher-update",
 #ifdef ACCESSIBILITY
   "a11y-init-or-shutdown",
 #endif
   "cacheservice:empty-cache",
+  "intl:app-locales-changed",
+  "intl:requested-locales-changed",
 };
 
 // PreallocateProcess is called by the PreallocatedProcessManager.
 // ContentParent then takes this process back within GetNewOrUsedBrowserProcess.
 /*static*/ already_AddRefed<ContentParent>
 ContentParent::PreallocateProcess()
 {
   RefPtr<ContentParent> process =
@@ -2170,16 +2174,19 @@ ContentParent::InitInternal(ProcessPrior
     bidi->GetHaveBidiKeyboards(&xpcomInit.haveBidiKeyboards());
   }
 
   nsCOMPtr<nsISpellChecker> spellChecker(do_GetService(NS_SPELLCHECKER_CONTRACTID));
   MOZ_ASSERT(spellChecker, "No spell checker?");
 
   spellChecker->GetDictionaryList(&xpcomInit.dictionaries());
 
+  LocaleService::GetInstance()->GetAppLocalesAsLangTags(xpcomInit.appLocales());
+  LocaleService::GetInstance()->GetRequestedLocales(xpcomInit.requestedLocales());
+
   nsCOMPtr<nsIClipboard> clipboard(do_GetService("@mozilla.org/widget/clipboard;1"));
   MOZ_ASSERT(clipboard, "No clipboard?");
 
   rv = clipboard->SupportsSelectionClipboard(&xpcomInit.clipboardCaps().supportsSelectionClipboard());
   MOZ_ASSERT(NS_SUCCEEDED(rv));
 
   rv = clipboard->SupportsFindClipboard(&xpcomInit.clipboardCaps().supportsFindClipboard());
   MOZ_ASSERT(NS_SUCCEEDED(rv));
@@ -2770,16 +2777,26 @@ ContentParent::Observe(nsISupports* aSub
       // accessibility gets shutdown in chrome process.
       Unused << SendShutdownA11y();
     }
   }
 #endif
   else if (!strcmp(aTopic, "cacheservice:empty-cache")) {
     Unused << SendNotifyEmptyHTTPCache();
   }
+  else if (!strcmp(aTopic, "intl:app-locales-changed")) {
+    nsTArray<nsCString> appLocales;
+    LocaleService::GetInstance()->GetAppLocalesAsLangTags(appLocales);
+    Unused << SendUpdateAppLocales(appLocales);
+  }
+  else if (!strcmp(aTopic, "intl:requested-locales-changed")) {
+    nsTArray<nsCString> requestedLocales;
+    LocaleService::GetInstance()->GetRequestedLocales(requestedLocales);
+    Unused << SendUpdateRequestedLocales(requestedLocales);
+  }
   return NS_OK;
 }
 
 mozilla::ipc::IPCResult
 ContentParent::RecvInitBackground(Endpoint<PBackgroundParent>&& aEndpoint)
 {
   if (!BackgroundParent::Alloc(this, Move(aEndpoint))) {
     return IPC_FAIL(this, "BackgroundParent::Alloc failed");
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -265,16 +265,18 @@ struct XPCOMInitData
     DomainPolicyClone domainPolicy;
     /* used on MacOSX only */
     FontFamilyListEntry[] fontFamilies;
     OptionalURIParams userContentSheetURL;
     PrefSetting[] prefs;
     ContentDeviceData contentDeviceData;
     GfxInfoFeatureStatus[] gfxFeatureStatus;
     DataStorageEntry[] dataStorage;
+    nsCString[] appLocales;
+    nsCString[] requestedLocales;
 };
 
 /**
  * The PContent protocol is a top-level protocol between the UI process
  * and a content process. There is exactly one PContentParent/PContentChild pair
  * for each content process.
  */
 nested(upto inside_cpow) sync protocol PContent
@@ -422,16 +424,19 @@ child:
     async NotifyAlertsObserver(nsCString topic, nsString data);
 
     async GeolocationUpdate(GeoPosition somewhere);
 
     async GeolocationError(uint16_t errorCode);
 
     async UpdateDictionaryList(nsString[] dictionaries);
 
+    async UpdateAppLocales(nsCString[] appLocales);
+    async UpdateRequestedLocales(nsCString[] requestedLocales);
+
     // nsIPermissionManager messages
     async AddPermission(Permission permission);
 
     async FlushMemory(nsString reason);
 
     async GarbageCollect();
     async CycleCollect();
 
--- a/dom/locales/en-US/chrome/dom/dom.properties
+++ b/dom/locales/en-US/chrome/dom/dom.properties
@@ -178,18 +178,16 @@ UseOfReleaseEventsWarning=Use of release
 UseOfDOM3LoadMethodWarning=Use of document.load() is deprecated. To upgrade your code, use the DOM XMLHttpRequest object. For more help https://developer.mozilla.org/en/XMLHttpRequest
 # LOCALIZATION NOTE: Do not translate "window.showModalDialog()" or "window.open()"
 ShowModalDialogWarning=Use of window.showModalDialog() is deprecated. Use window.open() instead. For more help https://developer.mozilla.org/en-US/docs/Web/API/Window.open
 # LOCALIZATION NOTE: Do not translate "window._content" or "window.content"
 Window_ContentWarning=window._content is deprecated.  Please use window.content instead.
 # LOCALIZATION NOTE: Do not translate "XMLHttpRequest"
 SyncXMLHttpRequestWarning=Synchronous XMLHttpRequest on the main thread is deprecated because of its detrimental effects to the end user’s experience. For more help http://xhr.spec.whatwg.org/
 ImplicitMetaViewportTagFallback=No meta-viewport tag found. Please explicitly specify one to prevent unexpected behavioural changes in future versions. For more help https://developer.mozilla.org/en/docs/Mozilla/Mobile/Viewport_meta_tag
-# LOCALIZATION NOTE: Do not translate "DataContainerEvent" or "CustomEvent"
-DataContainerEventWarning=Use of DataContainerEvent is deprecated. Use CustomEvent instead.
 # LOCALIZATION NOTE: Do not translate "window.controllers"
 Window_ControllersWarning=window.controllers is deprecated. Do not use it for UA detection.
 ImportXULIntoContentWarning=Importing XUL nodes into a content document is deprecated. This functionality may be removed soon.
 XMLDocumentLoadPrincipalMismatch=Use of document.load forbidden on Documents that come from other Windows. Only the Window in which a Document was created is allowed to call .load on that Document. Preferably, use XMLHttpRequest instead.
 # LOCALIZATION NOTE: Do not translate "IndexedDB".
 IndexedDBTransactionAbortNavigation=An IndexedDB transaction that was not yet complete has been aborted due to page navigation.
 # LOCALIZATION NOTE: Do not translate Will-change, %1$S,%2$S are numbers.
 IgnoringWillChangeOverBudgetWarning=Will-change memory consumption is too high. Budget limit is the document surface area multiplied by %1$S (%2$S px). Occurrences of will-change over the budget will be ignored.
--- a/dom/media/DOMMediaStream.cpp
+++ b/dom/media/DOMMediaStream.cpp
@@ -109,17 +109,17 @@ DOMMediaStream::TrackPort::GetSourceTrac
 }
 
 already_AddRefed<Pledge<bool>>
 DOMMediaStream::TrackPort::BlockSourceTrackId(TrackID aTrackId, BlockingMode aBlockingMode)
 {
   if (mInputPort) {
     return mInputPort->BlockSourceTrackId(aTrackId, aBlockingMode);
   }
-  RefPtr<Pledge<bool>> rejected = new Pledge<bool>();
+  auto rejected = MakeRefPtr<Pledge<bool>>();
   rejected->Reject(NS_ERROR_FAILURE);
   return rejected.forget();
 }
 
 NS_IMPL_CYCLE_COLLECTION(DOMMediaStream::TrackPort, mTrack)
 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(DOMMediaStream::TrackPort, AddRef)
 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(DOMMediaStream::TrackPort, Release)
 
--- a/dom/media/MediaManager.cpp
+++ b/dom/media/MediaManager.cpp
@@ -1405,21 +1405,18 @@ public:
   ~GetUserMediaTask() {
   }
 
   void
   Fail(const nsAString& aName,
        const nsAString& aMessage = EmptyString(),
        const nsAString& aConstraint = EmptyString()) {
     RefPtr<MediaMgrError> error = new MediaMgrError(aName, aMessage, aConstraint);
-    RefPtr<ErrorCallbackRunnable<nsIDOMGetUserMediaSuccessCallback>> runnable =
-      new ErrorCallbackRunnable<nsIDOMGetUserMediaSuccessCallback>(mOnSuccess,
-                                                                   mOnFailure,
-                                                                   *error,
-                                                                   mWindowID);
+    auto runnable = MakeRefPtr<ErrorCallbackRunnable<nsIDOMGetUserMediaSuccessCallback>>(
+        mOnSuccess, mOnFailure, *error, mWindowID);
     // These should be empty now
     MOZ_ASSERT(!mOnSuccess);
     MOZ_ASSERT(!mOnFailure);
 
     NS_DispatchToMainThread(runnable.forget());
     // Do after ErrorCallbackRunnable Run()s, as it checks active window list
     NS_DispatchToMainThread(do_AddRef(new GetUserMediaListenerRemove(mWindowID, mListener)));
   }
@@ -2293,19 +2290,18 @@ if (privileged) {
       (!fake || Preferences::GetBool("media.navigator.permission.fake"));
 
   RefPtr<PledgeSourceSet> p = EnumerateDevicesImpl(windowID, videoType,
                                                    audioType, fake);
   RefPtr<MediaManager> self = this;
   p->Then([self, onSuccess, onFailure, windowID, c, listener, askPermission,
            prefs, isHTTPS, callID, principalInfo,
            isChrome](SourceSet*& aDevices) mutable {
-
-    RefPtr<Refcountable<UniquePtr<SourceSet>>> devices(
-         new Refcountable<UniquePtr<SourceSet>>(aDevices)); // grab result
+    // grab result
+    auto devices = MakeRefPtr<Refcountable<UniquePtr<SourceSet>>>(aDevices);
 
     // Ensure that our windowID is still good.
     if (!nsGlobalWindow::GetInnerWindowWithId(windowID)) {
       return;
     }
 
     // Apply any constraints. This modifies the passed-in list.
     RefPtr<PledgeChar> p2 = self->SelectSettings(c, isChrome, devices);
--- a/dom/media/MediaStreamGraph.cpp
+++ b/dom/media/MediaStreamGraph.cpp
@@ -3315,17 +3315,17 @@ MediaInputPort::BlockSourceTrackId(Track
     BlockingMode mBlockingMode;
     nsCOMPtr<nsIRunnable> mRunnable;
     const RefPtr<AbstractThread> mAbstractMainThread;
   };
 
   MOZ_ASSERT(IsTrackIDExplicit(aTrackId),
              "Only explicit TrackID is allowed");
 
-  RefPtr<Pledge<bool>> pledge = new Pledge<bool>();
+  auto pledge = MakeRefPtr<Pledge<bool>>();
   nsCOMPtr<nsIRunnable> runnable = NewRunnableFrom([pledge]() {
     MOZ_ASSERT(NS_IsMainThread());
     pledge->Resolve(true);
     return NS_OK;
   });
   GraphImpl()->AppendMessage(MakeUnique<Message>(this, aTrackId, aBlockingMode,
                                                  runnable.forget(),
                                                  mAbstractMainThread));
--- a/dom/smil/test/test_smilTimeEvents.xhtml
+++ b/dom/smil/test/test_smilTimeEvents.xhtml
@@ -143,17 +143,17 @@ function testBackwardsSeekToStart()
   gTimeoutID = setTimeout(timeoutFail, gTimeoutDur);
   gSvg.setCurrentTime(0);
 }
 
 function testCreateEvent()
 {
   var evt;
   try {
-    evt = document.createEvent("TimeEvents");
+    evt = document.createEvent("TimeEvent");
   } catch (e) {
     ok(false, "Failed to create TimeEvent via script: " + e);
     return;
   }
   evt.initTimeEvent("repeatEvent", null, 3);
   is(evt.type, "repeatEvent", "Unexpected type for user-generated event");
   is(evt.detail, 3, "Unexpected detail for user-generated event");
   is(evt.target, null, "Unexpected event target");
--- a/dom/tests/mochitest/general/test_interfaces.js
+++ b/dom/tests/mochitest/general/test_interfaces.js
@@ -183,17 +183,17 @@ var interfaceNamesInGlobalScope =
     {name: "ChromeNodeList", xbl: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "ChromeWindow", xbl: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "ClipboardEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "CloseEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    "CommandEvent",
+    {name: "CommandEvent", xbl: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "Comment",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "CompositionEvent",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "ConstantSourceNode",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "Controllers",
@@ -844,17 +844,17 @@ var interfaceNamesInGlobalScope =
     {name: "ScopedCredential", disabled: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "ScopedCredentialInfo", disabled: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "ShadowRoot", // Bogus, but the test harness forces it on.  See bug 1159768.
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "SharedWorker",
 // IMPORTANT: Do not change this list without review from a DOM peer!
-    "SimpleGestureEvent",
+    {name: "SimpleGestureEvent", xbl: true},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "SimpleTest", xbl: false},
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "SourceBuffer",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     "SourceBufferList",
 // IMPORTANT: Do not change this list without review from a DOM peer!
     {name: "SpeechSynthesisErrorEvent", android: false},
--- a/dom/webidl/CommandEvent.webidl
+++ b/dom/webidl/CommandEvent.webidl
@@ -1,14 +1,15 @@
 /* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* 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/.
  */
 
+[Func="IsChromeOrXBL"]
 interface CommandEvent : Event {
   readonly attribute DOMString? command;
 
   void initCommandEvent(DOMString type,
                         optional boolean canBubble = false,
                         optional boolean cancelable = false,
                         optional DOMString? command = null);
 };
deleted file mode 100644
--- a/dom/webidl/DataContainerEvent.webidl
+++ /dev/null
@@ -1,30 +0,0 @@
-/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* 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/.
- */
-
-interface nsIVariant;
-
-[ChromeOnly]
-interface DataContainerEvent : Event {
-  /**
-   * Return the data associated with the given key.
-   *
-   * @param  key  the key
-   * @return      the data associated with the key
-   */
-  nsIVariant? getData(DOMString? key);
-
-  /**
-   * Set the data for the given key.
-   *
-   * @param  key   the data key
-   * @param  data  the data
-   * @throws       NS_ERROR_UNEXPECTED if the method is called during event
-   *               dispatch
-   */
-  [Throws]
-  void setData(DOMString? key, any data);
-};
-
--- a/dom/webidl/Document.webidl
+++ b/dom/webidl/Document.webidl
@@ -68,17 +68,17 @@ interface Document : Node {
   [NewObject, Throws]
   ProcessingInstruction createProcessingInstruction(DOMString target, DOMString data);
 
   [Throws]
   Node importNode(Node node, optional boolean deep = false);
   [Throws]
   Node adoptNode(Node node);
 
-  [NewObject, Throws]
+  [NewObject, Throws, NeedsCallerType]
   Event createEvent(DOMString interface);
 
   [NewObject, Throws]
   Range createRange();
 
   // NodeFilter.SHOW_ALL = 0xFFFFFFFF
   [NewObject, Throws]
   NodeIterator createNodeIterator(Node root, optional unsigned long whatToShow = 0xFFFFFFFF, optional NodeFilter? filter = null);
--- a/dom/webidl/Selection.webidl
+++ b/dom/webidl/Selection.webidl
@@ -6,57 +6,54 @@
  * The origin of this IDL file is
  * https://dvcs.w3.org/hg/editing/raw-file/tip/editing.html#concept-selection
  *
  * Copyright © 2012 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C
  * liability, trademark and document use rules apply.
  */
 
 interface Selection {
-  readonly attribute Node? anchorNode;
+  readonly attribute Node?         anchorNode;
   readonly attribute unsigned long anchorOffset;
-  readonly attribute Node? focusNode;
+  readonly attribute Node?         focusNode;
   readonly attribute unsigned long focusOffset;
-
-  readonly attribute boolean isCollapsed;
-  [Throws, BinaryName="collapseJS"]
-  void               collapse(Node node, unsigned long offset);
-  [Throws, BinaryName="collapseToStartJS"]
-  void               collapseToStart();
-  [Throws, BinaryName="collapseToEndJS"]
-  void               collapseToEnd();
-
-  [Throws, BinaryName="extendJS"]
-  void               extend(Node node, unsigned long offset);
-
-  [Throws, BinaryName="selectAllChildrenJS"]
-  void               selectAllChildren(Node node);
+  readonly attribute boolean       isCollapsed;
+  readonly attribute unsigned long rangeCount;
+  //readonly attribute DOMString     type;
+  [Throws]
+  Range     getRangeAt(unsigned long index);
+  [Throws, BinaryName="addRangeJS"]
+  void      addRange(Range range);
+  [Throws]
+  void      removeRange(Range range);
   [Throws]
-  void               deleteFromDocument();
-
-  readonly attribute unsigned long rangeCount;
-  [Throws]
-  Range              getRangeAt(unsigned long index);
-  [Throws, BinaryName="addRangeJS"]
-  void               addRange(Range range);
-  [Throws]
-  void               removeRange(Range range);
+  void      removeAllRanges();
+  //void      empty();
+  [Throws, BinaryName="collapseJS"]
+  void      collapse(Node? node, optional unsigned long offset = 0);
+  //void      setPosition(Node? node, optional unsigned long offset = 0);
+  [Throws, BinaryName="collapseToStartJS"]
+  void      collapseToStart();
+  [Throws, BinaryName="collapseToEndJS"]
+  void      collapseToEnd();
+  [Throws, BinaryName="extendJS"]
+  void      extend(Node node, optional unsigned long offset = 0);
+  [Throws, BinaryName="setBaseAndExtentJS"]
+  void      setBaseAndExtent(Node anchorNode,
+                             unsigned long anchorOffset,
+                             Node focusNode,
+                             unsigned long focusOffset);
+  [Throws, BinaryName="selectAllChildrenJS"]
+  void      selectAllChildren(Node node);
+  [CEReactions, Throws]
+  void      deleteFromDocument();
   [Throws]
-  void               removeAllRanges();
-
-  [Throws]
-  boolean            containsNode(Node node, boolean allowPartialContainment);
-
-  [Throws, BinaryName="setBaseAndExtentJS"]
-  void               setBaseAndExtent(Node anchorNode,
-                                      unsigned long anchorOffset,
-                                      Node focusNode,
-                                      unsigned long focusOffset);
-
-  stringifier;
+  boolean   containsNode(Node node,
+                         optional boolean allowPartialContainment = false);
+  stringifier DOMString ();
 };
 
 // Additional methods not currently in the spec
 partial interface Selection {
   [Throws]
   void modify(DOMString alter, DOMString direction,
               DOMString granularity);
 };
--- a/dom/webidl/SimpleGestureEvent.webidl
+++ b/dom/webidl/SimpleGestureEvent.webidl
@@ -1,16 +1,17 @@
 /* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* 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/.
  *
  * For more information see nsIDOMSimpleGestureEvent.idl.
  */
 
+[Func="IsChromeOrXBL"]
 interface SimpleGestureEvent : MouseEvent
 {
   const unsigned long DIRECTION_UP = 1;
   const unsigned long DIRECTION_DOWN = 2;
   const unsigned long DIRECTION_LEFT = 4;
   const unsigned long DIRECTION_RIGHT = 8;
 
   const unsigned long ROTATION_COUNTERCLOCKWISE = 1;
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -470,17 +470,16 @@ WEBIDL_FILES = [
     'CSSStyleDeclaration.webidl',
     'CSSStyleRule.webidl',
     'CSSStyleSheet.webidl',
     'CSSSupportsRule.webidl',
     'CSSTransition.webidl',
     'CSSValue.webidl',
     'CSSValueList.webidl',
     'CustomElementRegistry.webidl',
-    'DataContainerEvent.webidl',
     'DataTransfer.webidl',
     'DataTransferItem.webidl',
     'DataTransferItemList.webidl',
     'DecoderDoctorNotification.webidl',
     'DedicatedWorkerGlobalScope.webidl',
     'DelayNode.webidl',
     'DesktopNotification.webidl',
     'DeviceMotionEvent.webidl',
--- a/dom/xbl/nsBindingManager.cpp
+++ b/dom/xbl/nsBindingManager.cpp
@@ -250,52 +250,53 @@ nsBindingManager::GetAnonymousNodesFor(n
 nsINodeList*
 nsBindingManager::GetAnonymousNodesFor(nsIContent* aContent)
 {
   nsXBLBinding* binding = GetBindingWithContent(aContent);
   return binding ? binding->GetAnonymousNodeList() : nullptr;
 }
 
 nsresult
-nsBindingManager::ClearBinding(nsIContent* aContent)
+nsBindingManager::ClearBinding(Element* aElement)
 {
   // Hold a ref to the binding so it won't die when we remove it from our table
   RefPtr<nsXBLBinding> binding =
-    aContent ? aContent->GetXBLBinding() : nullptr;
+    aElement ? aElement->GetXBLBinding() : nullptr;
 
   if (!binding) {
     return NS_OK;
   }
 
   // For now we can only handle removing a binding if it's the only one
   NS_ENSURE_FALSE(binding->GetBaseBinding(), NS_ERROR_FAILURE);
 
   // Hold strong ref in case removing the binding tries to close the
   // window or something.
   // XXXbz should that be ownerdoc?  Wouldn't we need a ref to the
   // currentdoc too?  What's the one that should be passed to
   // ChangeDocument?
-  nsCOMPtr<nsIDocument> doc = aContent->OwnerDoc();
+  nsCOMPtr<nsIDocument> doc = aElement->OwnerDoc();
 
   // Finally remove the binding...
   // XXXbz this doesn't remove the implementation!  Should fix!  Until
   // then we need the explicit UnhookEventHandlers here.
   binding->UnhookEventHandlers();
   binding->ChangeDocument(doc, nullptr);
-  aContent->SetXBLBinding(nullptr, this);
+  aElement->SetXBLBinding(nullptr, this);
   binding->MarkForDeath();
 
   // ...and recreate its frames. We need to do this since the frames may have
   // been removed and style may have changed due to the removal of the
   // anonymous children.
   // XXXbz this should be using the current doc (if any), not the owner doc.
   nsIPresShell *presShell = doc->GetShell();
   NS_ENSURE_TRUE(presShell, NS_ERROR_FAILURE);
 
-  return presShell->RecreateFramesFor(aContent);;
+  presShell->PostRecreateFramesFor(aElement);
+  return NS_OK;
 }
 
 nsresult
 nsBindingManager::LoadBindingDocument(nsIDocument* aBoundDoc,
                                       nsIURI* aURL,
                                       nsIPrincipal* aOriginPrincipal)
 {
   NS_PRECONDITION(aURL, "Must have a URI to load!");
--- a/dom/xbl/nsBindingManager.h
+++ b/dom/xbl/nsBindingManager.h
@@ -85,17 +85,17 @@ public:
    * Return the nodelist of "anonymous" kids for this node.  This might
    * actually include some of the nodes actual DOM kids, if there are
    * <children> tags directly as kids of <content>.  This will only end up
    * returning a non-null list for nodes which have a binding attached.
    */
   nsresult GetAnonymousNodesFor(nsIContent* aContent, nsIDOMNodeList** aResult);
   nsINodeList* GetAnonymousNodesFor(nsIContent* aContent);
 
-  nsresult ClearBinding(nsIContent* aContent);
+  nsresult ClearBinding(mozilla::dom::Element* aElement);
   nsresult LoadBindingDocument(nsIDocument* aBoundDoc, nsIURI* aURL,
                                nsIPrincipal* aOriginPrincipal);
 
   nsresult AddToAttachedQueue(nsXBLBinding* aBinding);
   void RemoveFromAttachedQueue(nsXBLBinding* aBinding);
   void ProcessAttachedQueue(uint32_t aSkipSize = 0)
   {
     if (mProcessingAttachedStack || mAttachedStack.Length() <= aSkipSize) {
--- a/dom/xbl/nsXBLResourceLoader.cpp
+++ b/dom/xbl/nsXBLResourceLoader.cpp
@@ -216,25 +216,26 @@ nsXBLResourceLoader::NotifyBoundElements
   if (!xblService)
     return;
 
   nsIURI* bindingURI = mBinding->BindingURI();
 
   uint32_t eltCount = mBoundElements.Count();
   for (uint32_t j = 0; j < eltCount; j++) {
     nsCOMPtr<nsIContent> content = mBoundElements.ObjectAt(j);
-    
+    MOZ_ASSERT(content->IsElement());
+
     bool ready = false;
     xblService->BindingReady(content, bindingURI, &ready);
 
     if (ready) {
       // We need the document to flush out frame construction and
       // such, so we want to use the current document.
       nsIDocument* doc = content->GetUncomposedDoc();
-    
+
       if (doc) {
         // Flush first to make sure we can get the frame for content
         doc->FlushPendingNotifications(FlushType::Frames);
 
         // If |content| is (in addition to having binding |mBinding|)
         // also a descendant of another element with binding |mBinding|,
         // then we might have just constructed it due to the
         // notification of its parent.  (We can know about both if the
@@ -243,21 +244,24 @@ nsXBLResourceLoader::NotifyBoundElements
         // has a primary frame and whether it's in the undisplayed map
         // before sending a ContentInserted notification, or bad things
         // will happen.
         nsIPresShell *shell = doc->GetShell();
         if (shell) {
           nsIFrame* childFrame = content->GetPrimaryFrame();
           if (!childFrame) {
             // Check to see if it's in the undisplayed content map.
+            //
+            // FIXME(emilio, bug 1359384): What about display: contents stuff?
+            // Looks like this would be inefficient in that case?
             nsStyleContext* sc =
               shell->FrameManager()->GetUndisplayedContent(content);
 
             if (!sc) {
-              shell->RecreateFramesFor(content);
+              shell->PostRecreateFramesFor(content->AsElement());
             }
           }
         }
 
         // Flush again
         // XXXbz why is this needed?
         doc->FlushPendingNotifications(FlushType::ContentAndNotify);
       }
--- a/dom/xml/nsXMLElement.cpp
+++ b/dom/xml/nsXMLElement.cpp
@@ -4,16 +4,17 @@
  * 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/. */
 
 #include "nsXMLElement.h"
 #include "mozilla/dom/ElementBinding.h"
 #include "mozilla/dom/ElementInlines.h"
 #include "nsContentUtils.h" // nsAutoScriptBlocker
 
+using namespace mozilla;
 using namespace mozilla::dom;
 
 nsresult
 NS_NewXMLElement(Element** aInstancePtrResult,
                  already_AddRefed<mozilla::dom::NodeInfo>&& aNodeInfo)
 {
   nsXMLElement* it = new nsXMLElement(aNodeInfo);
   NS_ADDREF(*aInstancePtrResult = it);
@@ -24,9 +25,31 @@ NS_IMPL_ISUPPORTS_INHERITED(nsXMLElement
                             nsIDOMNode, nsIDOMElement)
 
 JSObject*
 nsXMLElement::WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto)
 {
   return ElementBinding::Wrap(aCx, this, aGivenProto);
 }
 
+void
+nsXMLElement::UnbindFromTree(bool aDeep, bool aNullParent)
+{
+  CSSPseudoElementType pseudoType = GetPseudoElementType();
+  bool isBefore = pseudoType == CSSPseudoElementType::before;
+  nsIAtom* property = isBefore
+    ? nsGkAtoms::beforePseudoProperty : nsGkAtoms::afterPseudoProperty;
+
+  switch (pseudoType) {
+    case CSSPseudoElementType::before:
+    case CSSPseudoElementType::after: {
+      MOZ_ASSERT(GetParent());
+      MOZ_ASSERT(GetParent()->IsElement());
+      GetParent()->DeleteProperty(property);
+      break;
+    }
+    default:
+      break;
+  }
+  Element::UnbindFromTree(aDeep, aNullParent);
+}
+
 NS_IMPL_ELEMENT_CLONE(nsXMLElement)
--- a/dom/xml/nsXMLElement.h
+++ b/dom/xml/nsXMLElement.h
@@ -30,15 +30,18 @@ public:
   // nsIDOMElement
   NS_FORWARD_NSIDOMELEMENT_TO_GENERIC
 
   // nsINode interface methods
   virtual nsresult Clone(mozilla::dom::NodeInfo *aNodeInfo, nsINode **aResult) const override;
 
   virtual nsIDOMNode* AsDOMNode() override { return this; }
 
+  virtual void UnbindFromTree(bool aDeep = true,
+                              bool aNullParent = true) override;
+
 protected:
   virtual ~nsXMLElement() {}
 
   virtual JSObject* WrapNode(JSContext *aCx, JS::Handle<JSObject*> aGivenProto) override;
 };
 
 #endif // nsXMLElement_h___
--- a/editor/libeditor/HTMLAnonymousNodeEditor.cpp
+++ b/editor/libeditor/HTMLAnonymousNodeEditor.cpp
@@ -228,26 +228,26 @@ HTMLEditor::CreateAnonymousElement(nsIAt
 
   ElementDeletionObserver* observer =
     new ElementDeletionObserver(newContent, parentContent);
   NS_ADDREF(observer); // NodeWillBeDestroyed releases.
   parentContent->AddMutationObserver(observer);
   newContent->AddMutationObserver(observer);
 
 #ifdef DEBUG
-  // Editor anonymous content gets passed to RecreateFramesFor... which can't
-  // _really_ deal with anonymous content (because it can't get the frame tree
-  // ordering right).  But for us the ordering doesn't matter so this is sort of
-  // ok.
+  // Editor anonymous content gets passed to PostRecreateFramesFor... which
+  // can't _really_ deal with anonymous content (because it can't get the frame
+  // tree ordering right).  But for us the ordering doesn't matter so this is
+  // sort of ok.
   newContent->SetProperty(nsGkAtoms::restylableAnonymousNode,
 			  reinterpret_cast<void*>(true));
 #endif // DEBUG
 
   // display the element
-  ps->RecreateFramesFor(newContent);
+  ps->PostRecreateFramesFor(newContent);
 
   return newContent.forget();
 }
 
 // Removes event listener and calls DeleteRefToAnonymousNode.
 void
 HTMLEditor::RemoveListenerAndDeleteRef(const nsAString& aEvent,
                                        nsIDOMEventListener* aListener,
--- a/gfx/vr/gfxVROculus.cpp
+++ b/gfx/vr/gfxVROculus.cpp
@@ -1463,23 +1463,17 @@ VRSystemManagerOculus::ScanForController
   }
 
   if (inputState.ControllerType & ovrControllerType_RTouch) {
     activeControllerArray[newControllerCount] = ovrControllerType_RTouch;
     ++newControllerCount;
   }
 
   if (newControllerCount != mControllerCount) {
-    // controller count is changed, removing the existing gamepads first.
-    for (uint32_t i = 0; i < mOculusController.Length(); ++i) {
-      RemoveGamepad(i);
-    }
-
-    mControllerCount = 0;
-    mOculusController.Clear();
+    RemoveControllers();
 
     // Re-adding controllers to VRControllerManager.
     for (uint32_t i = 0; i < newControllerCount; ++i) {
       GamepadHand hand;
 
       switch (activeControllerArray[i]) {
         case ovrControllerType::ovrControllerType_LTouch:
           hand = GamepadHand::Left;
@@ -1496,11 +1490,16 @@ VRSystemManagerOculus::ScanForController
       ++mControllerCount;
     }
   }
 }
 
 void
 VRSystemManagerOculus::RemoveControllers()
 {
+  // controller count is changed, removing the existing gamepads first.
+  for (uint32_t i = 0; i < mOculusController.Length(); ++i) {
+    RemoveGamepad(i);
+  }
+
   mOculusController.Clear();
   mControllerCount = 0;
 }
\ No newline at end of file
--- a/gfx/vr/gfxVROpenVR.cpp
+++ b/gfx/vr/gfxVROpenVR.cpp
@@ -340,16 +340,17 @@ VRDisplayOpenVR::NotifyVSync()
   // Make sure we respond to OpenVR events even when not presenting
   PollEvents();
 }
 
 VRControllerOpenVR::VRControllerOpenVR(dom::GamepadHand aHand, uint32_t aNumButtons,
                                        uint32_t aNumAxes, ::vr::ETrackedDeviceClass aDeviceType)
   : VRControllerHost(VRDeviceType::OpenVR)
   , mTrigger(0)
+  , mAxisMove(aNumAxes)
   , mVibrateThread(nullptr)
   , mIsVibrateStopped(false)
 {
   MOZ_COUNT_CTOR_INHERITED(VRControllerOpenVR, VRControllerHost);
 
   switch (aDeviceType) {
     case ::vr::TrackedDeviceClass_Controller:
       mControllerInfo.mControllerName.AssignLiteral("OpenVR Gamepad");
@@ -357,16 +358,17 @@ VRControllerOpenVR::VRControllerOpenVR(d
     case ::vr::TrackedDeviceClass_GenericTracker:
       mControllerInfo.mControllerName.AssignLiteral("OpenVR Tracker");
       break;
     default:
       MOZ_ASSERT(false);
       break;
   }
 
+  mAxisMove.SetLengthAndRetainStorage(aNumAxes);
   mControllerInfo.mMappingType = GamepadMappingType::_empty;
   mControllerInfo.mHand = aHand;
   mControllerInfo.mNumButtons = aNumButtons;
   mControllerInfo.mNumAxes = aNumAxes;
   mControllerInfo.mNumHaptics = kNumOpenVRHaptcs;
 }
 
 VRControllerOpenVR::~VRControllerOpenVR()
@@ -386,16 +388,28 @@ VRControllerOpenVR::SetTrackedIndex(uint
 }
 
 uint32_t
 VRControllerOpenVR::GetTrackedIndex()
 {
   return mTrackedIndex;
 }
 
+float
+VRControllerOpenVR::GetAxisMove(uint32_t aAxis)
+{
+  return mAxisMove[aAxis];
+}
+
+void
+VRControllerOpenVR::SetAxisMove(uint32_t aAxis, float aValue)
+{
+  mAxisMove[aAxis] = aValue;
+}
+
 void
 VRControllerOpenVR::SetTrigger(float aValue)
 {
   mTrigger = aValue;
 }
 
 float
 VRControllerOpenVR::GetTrigger()
@@ -801,18 +815,22 @@ VRSystemManagerOpenVR::HandleTriggerPres
     controller->SetTrigger(aValue);
   }
 }
 
 void
 VRSystemManagerOpenVR::HandleAxisMove(uint32_t aControllerIdx, uint32_t aAxis,
                                       float aValue)
 {
-  if (aValue != 0.0f) {
+  RefPtr<impl::VRControllerOpenVR> controller(mOpenVRController[aControllerIdx]);
+  MOZ_ASSERT(controller);
+
+  if (controller->GetAxisMove(aAxis) != aValue) {
     NewAxisMove(aControllerIdx, aAxis, aValue);
+    controller->SetAxisMove(aAxis, aValue);
   }
 }
 
 void
 VRSystemManagerOpenVR::HandlePoseTracking(uint32_t aControllerIdx,
                                           const GamepadPoseState& aPose,
                                           VRControllerHost* aController)
 {
@@ -896,54 +914,50 @@ VRSystemManagerOpenVR::ScanForController
   // mVRSystem is available after VRDisplay is created
   // at GetHMDs().
   if (!mVRSystem) {
     return;
   }
 
   ::vr::TrackedDeviceIndex_t trackedIndexArray[::vr::k_unMaxTrackedDeviceCount];
   uint32_t newControllerCount = 0;
-  ::vr::ETrackedDeviceClass deviceType;
   // Basically, we would have HMDs in the tracked devices,
   // but we are just interested in the controllers.
   for (::vr::TrackedDeviceIndex_t trackedDevice = ::vr::k_unTrackedDeviceIndex_Hmd + 1;
        trackedDevice < ::vr::k_unMaxTrackedDeviceCount; ++trackedDevice) {
 
     if (!mVRSystem->IsTrackedDeviceConnected(trackedDevice)) {
       continue;
     }
 
-    deviceType = mVRSystem->GetTrackedDeviceClass(trackedDevice);
+    const ::vr::ETrackedDeviceClass deviceType = mVRSystem->
+                                                 GetTrackedDeviceClass(trackedDevice);
     if (deviceType != ::vr::TrackedDeviceClass_Controller
         && deviceType != ::vr::TrackedDeviceClass_GenericTracker) {
       continue;
     }
 
     trackedIndexArray[newControllerCount] = trackedDevice;
     ++newControllerCount;
   }
 
   if (newControllerCount != mControllerCount) {
-    // The controller count is changed, removing the existing gamepads first.
-    for (uint32_t i = 0; i < mOpenVRController.Length(); ++i) {
-      RemoveGamepad(i);
-    }
-    mControllerCount = 0;
-    mOpenVRController.Clear();
+    RemoveControllers();
 
     // Re-adding controllers to VRControllerManager.
     for (::vr::TrackedDeviceIndex_t i = 0; i < newControllerCount; ++i) {
       const ::vr::TrackedDeviceIndex_t trackedDevice = trackedIndexArray[i];
+      const ::vr::ETrackedDeviceClass deviceType = mVRSystem->
+                                                   GetTrackedDeviceClass(trackedDevice);
       const ::vr::ETrackedControllerRole role = mVRSystem->
-                                               GetControllerRoleForTrackedDeviceIndex(
-                                               trackedDevice);
-
+                                                GetControllerRoleForTrackedDeviceIndex(
+                                                trackedDevice);
+      const GamepadHand hand = GetGamepadHandFromControllerRole(role);
       uint32_t numButtons = 0;
       uint32_t numAxes = 0;
-      const GamepadHand hand = GetGamepadHandFromControllerRole(role);
 
       // Scan the axes that the controllers support
       for (uint32_t j = 0; j < ::vr::k_unControllerStateAxisCount; ++j) {
         const uint32_t supportAxis = mVRSystem->GetInt32TrackedDeviceProperty(trackedDevice,
                                       static_cast<vr::TrackedDeviceProperty>(
                                       ::vr::Prop_Axis0Type_Int32 + j));
         switch (supportAxis) {
           case ::vr::EVRControllerAxisType::k_eControllerAxis_Joystick:
@@ -999,12 +1013,16 @@ VRSystemManagerOpenVR::ScanForController
       ++mControllerCount;
     }
   }
 }
 
 void
 VRSystemManagerOpenVR::RemoveControllers()
 {
+  // The controller count is changed, removing the existing gamepads first.
+  for (uint32_t i = 0; i < mOpenVRController.Length(); ++i) {
+    RemoveGamepad(i);
+  }
   mOpenVRController.Clear();
   mControllerCount = 0;
 }
 
--- a/gfx/vr/gfxVROpenVR.h
+++ b/gfx/vr/gfxVROpenVR.h
@@ -64,16 +64,18 @@ protected:
 
 class VRControllerOpenVR : public VRControllerHost
 {
 public:
   explicit VRControllerOpenVR(dom::GamepadHand aHand, uint32_t aNumButtons,
                               uint32_t aNumAxes, ::vr::ETrackedDeviceClass aDeviceType);
   void SetTrackedIndex(uint32_t aTrackedIndex);
   uint32_t GetTrackedIndex();
+  float GetAxisMove(uint32_t aAxis);
+  void SetAxisMove(uint32_t aAxis, float aValue);
   void SetTrigger(float aValue);
   float GetTrigger();
   void SetHand(dom::GamepadHand aHand);
   void VibrateHaptic(::vr::IVRSystem* aVRSystem,
                      uint32_t aHapticIndex,
                      double aIntensity,
                      double aDuration,
                      uint32_t aPromiseID);
@@ -89,16 +91,17 @@ private:
                            double aDuration,
                            uint64_t aVibrateIndex,
                            uint32_t aPromiseID);
   void VibrateHapticComplete(uint32_t aPromiseID);
 
   // The index of tracked devices from ::vr::IVRSystem.
   uint32_t mTrackedIndex;
   float mTrigger;
+  nsTArray<float> mAxisMove;
   nsCOMPtr<nsIThread> mVibrateThread;
   Atomic<bool> mIsVibrateStopped;
 };
 
 } // namespace impl
 
 class VRSystemManagerOpenVR : public VRSystemManager
 {
--- a/intl/locale/LocaleService.cpp
+++ b/intl/locale/LocaleService.cpp
@@ -29,16 +29,17 @@
 static const char* kObservedPrefs[] = {
   MATCH_OS_LOCALE_PREF,
   SELECTED_LOCALE_PREF,
   ANDROID_OS_LOCALE_PREF,
   nullptr
 };
 
 using namespace mozilla::intl;
+using namespace mozilla;
 
 NS_IMPL_ISUPPORTS(LocaleService, mozILocaleService, nsIObserver)
 
 mozilla::StaticRefPtr<LocaleService> LocaleService::sInstance;
 
 /**
  * This function transforms a canonical Mozilla Language Tag, into it's
  * BCP47 compilant form.
@@ -72,102 +73,18 @@ SanitizeForBCP47(nsACString& aLocale)
   // But let's fix up the single anomalous code we actually ship,
   // just in case:
   if (aLocale.EqualsLiteral("ja-JP-mac")) {
     aLocale.AssignLiteral("ja-JP");
   }
 #endif
 }
 
-/**
- * This function performs the actual language negotiation for the API.
- *
- * Currently it collects the locale ID used by nsChromeRegistry and
- * adds hardcoded "en-US" locale as a fallback.
- */
-void
-LocaleService::NegotiateAppLocales(nsTArray<nsCString>& aRetVal)
-{
-  nsAutoCString defaultLocale;
-  GetDefaultLocale(defaultLocale);
-
-  if (XRE_IsParentProcess()) {
-    AutoTArray<nsCString, 100> availableLocales;
-    AutoTArray<nsCString, 10> requestedLocales;
-    GetAvailableLocales(availableLocales);
-    GetRequestedLocales(requestedLocales);
-
-    NegotiateLanguages(requestedLocales, availableLocales, defaultLocale,
-                       LangNegStrategy::Filtering, aRetVal);
-  } else {
-    //XXX: In bug 1348042 we're working on getting the content process
-    //     to follow the parent process negotiated locales.
-    //     Until we have it, we're going to match the behavior of following
-    //     the ChromeRegistry locale in the content process.
-
-    nsAutoCString uaLangTag;
-    nsCOMPtr<nsIToolkitChromeRegistry> cr =
-      mozilla::services::GetToolkitChromeRegistryService();
-    if (cr) {
-      cr->GetSelectedLocale(NS_LITERAL_CSTRING("global"), false, uaLangTag);
-    }
-    if (!uaLangTag.IsEmpty()) {
-      aRetVal.AppendElement(uaLangTag);
-    }
-
-    if (!uaLangTag.Equals(defaultLocale)) {
-      aRetVal.AppendElement(defaultLocale);
-    }
-  }
-}
-
-LocaleService*
-LocaleService::GetInstance()
-{
-  if (!sInstance) {
-    sInstance = new LocaleService();
-
-    // We're going to observe for requested languages changes which come
-    // from prefs.
-    DebugOnly<nsresult> rv = Preferences::AddStrongObservers(sInstance, kObservedPrefs);
-    MOZ_ASSERT(NS_SUCCEEDED(rv), "Adding observers failed.");
-    ClearOnShutdown(&sInstance);
-  }
-  return sInstance;
-}
-
-LocaleService::~LocaleService()
-{
-  Preferences::RemoveObservers(sInstance, kObservedPrefs);
-}
-
-void
-LocaleService::GetAppLocalesAsLangTags(nsTArray<nsCString>& aRetVal)
-{
-  if (mAppLocales.IsEmpty()) {
-    NegotiateAppLocales(mAppLocales);
-  }
-  aRetVal = mAppLocales;
-}
-
-void
-LocaleService::GetAppLocalesAsBCP47(nsTArray<nsCString>& aRetVal)
-{
-  if (mAppLocales.IsEmpty()) {
-    NegotiateAppLocales(mAppLocales);
-  }
-  for (uint32_t i = 0; i < mAppLocales.Length(); i++) {
-    nsAutoCString locale(mAppLocales[i]);
-    SanitizeForBCP47(locale);
-    aRetVal.AppendElement(locale);
-  }
-}
-
-bool
-LocaleService::GetRequestedLocales(nsTArray<nsCString>& aRetVal)
+static bool
+ReadRequestedLocales(nsTArray<nsCString>& aRetVal)
 {
   nsAutoCString locale;
 
   // First, we'll try to check if the user has `matchOS` pref selected
   bool matchOSLocale = Preferences::GetBool(MATCH_OS_LOCALE_PREF);
 
   if (matchOSLocale) {
     // If he has, we'll pick the locale from the system
@@ -183,21 +100,24 @@ LocaleService::GetRequestedLocales(nsTAr
   }
 
   // At the moment we just take a single locale, but in the future
   // we'll want to allow user to specify a list of requested locales.
   aRetVal.AppendElement(locale);
   return true;
 }
 
-bool
-LocaleService::GetAvailableLocales(nsTArray<nsCString>& aRetVal)
+static bool
+ReadAvailableLocales(nsTArray<nsCString>& aRetVal)
 {
   nsCOMPtr<nsIToolkitChromeRegistry> cr =
     mozilla::services::GetToolkitChromeRegistryService();
+  if (!cr) {
+    return false;
+  }
 
   nsCOMPtr<nsIUTF8StringEnumerator> localesEnum;
 
   nsresult rv =
     cr->GetLocalesForPackage(NS_LITERAL_CSTRING("global"), getter_AddRefs(localesEnum));
   if (!NS_SUCCEEDED(rv)) {
     return false;
   }
@@ -210,19 +130,179 @@ LocaleService::GetAvailableLocales(nsTAr
       return false;
     }
 
     aRetVal.AppendElement(localeStr);
   }
   return !aRetVal.IsEmpty();
 }
 
+LocaleService::LocaleService(bool aIsServer)
+  :mIsServer(aIsServer)
+{
+}
+
+/**
+ * This function performs the actual language negotiation for the API.
+ *
+ * Currently it collects the locale ID used by nsChromeRegistry and
+ * adds hardcoded "en-US" locale as a fallback.
+ */
 void
-LocaleService::Refresh()
+LocaleService::NegotiateAppLocales(nsTArray<nsCString>& aRetVal)
+{
+  nsAutoCString defaultLocale;
+  GetDefaultLocale(defaultLocale);
+
+  if (mIsServer) {
+    AutoTArray<nsCString, 100> availableLocales;
+    AutoTArray<nsCString, 10> requestedLocales;
+    GetAvailableLocales(availableLocales);
+    GetRequestedLocales(requestedLocales);
+
+    NegotiateLanguages(requestedLocales, availableLocales, defaultLocale,
+                       LangNegStrategy::Filtering, aRetVal);
+  } else {
+    // In content process, we will not do any language negotiation.
+    // Instead, the language is set manually by SetAppLocales.
+    //
+    // If this method has been called, it means that we did not fire
+    // SetAppLocales yet (happens during initialization).
+    // In that case, all we can do is return the default locale.
+    // Once SetAppLocales will be called later, it'll fire an event
+    // allowing callers to update the locale.
+    aRetVal.AppendElement(defaultLocale);
+  }
+}
+
+LocaleService*
+LocaleService::GetInstance()
+{
+  if (!sInstance) {
+    sInstance = new LocaleService(XRE_IsParentProcess());
+
+    if (sInstance->IsServer()) {
+      // We're going to observe for requested languages changes which come
+      // from prefs.
+      DebugOnly<nsresult> rv = Preferences::AddStrongObservers(sInstance, kObservedPrefs);
+      MOZ_ASSERT(NS_SUCCEEDED(rv), "Adding observers failed.");
+    }
+    ClearOnShutdown(&sInstance);
+  }
+  return sInstance;
+}
+
+LocaleService::~LocaleService()
+{
+  if (mIsServer) {
+    Preferences::RemoveObservers(sInstance, kObservedPrefs);
+  }
+}
+
+void
+LocaleService::GetAppLocalesAsLangTags(nsTArray<nsCString>& aRetVal)
+{
+  if (mAppLocales.IsEmpty()) {
+    NegotiateAppLocales(mAppLocales);
+  }
+  aRetVal = mAppLocales;
+}
+
+void
+LocaleService::GetAppLocalesAsBCP47(nsTArray<nsCString>& aRetVal)
+{
+  if (mAppLocales.IsEmpty()) {
+    NegotiateAppLocales(mAppLocales);
+  }
+  for (uint32_t i = 0; i < mAppLocales.Length(); i++) {
+    nsAutoCString locale(mAppLocales[i]);
+    SanitizeForBCP47(locale);
+    aRetVal.AppendElement(locale);
+  }
+}
+
+void
+LocaleService::AssignAppLocales(const nsTArray<nsCString>& aAppLocales)
 {
+  MOZ_ASSERT(!mIsServer, "This should only be called for LocaleService in client mode.");
+
+  mAppLocales = aAppLocales;
+  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+  if (obs) {
+    obs->NotifyObservers(nullptr, "intl:app-locales-changed", nullptr);
+  }
+}
+
+void
+LocaleService::AssignRequestedLocales(const nsTArray<nsCString>& aRequestedLocales)
+{
+  MOZ_ASSERT(!mIsServer, "This should only be called for LocaleService in client mode.");
+
+  mRequestedLocales = aRequestedLocales;
+  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+  if (obs) {
+    obs->NotifyObservers(nullptr, "intl:requested-locales-changed", nullptr);
+  }
+}
+
+bool
+LocaleService::GetRequestedLocales(nsTArray<nsCString>& aRetVal)
+{
+  if (mRequestedLocales.IsEmpty()) {
+    ReadRequestedLocales(mRequestedLocales);
+  }
+
+  aRetVal = mRequestedLocales;
+  return true;
+}
+
+bool
+LocaleService::GetAvailableLocales(nsTArray<nsCString>& aRetVal)
+{
+  if (mAvailableLocales.IsEmpty()) {
+    ReadAvailableLocales(mAvailableLocales);
+  }
+
+  aRetVal = mAvailableLocales;
+  return true;
+}
+
+
+void
+LocaleService::OnAvailableLocalesChanged()
+{
+  MOZ_ASSERT(mIsServer, "This should only be called in the server mode.");
+  mAvailableLocales.Clear();
+  // In the future we may want to trigger here intl:available-locales-changed
+  OnLocalesChanged();
+}
+
+void
+LocaleService::OnRequestedLocalesChanged()
+{
+  MOZ_ASSERT(mIsServer, "This should only be called in the server mode.");
+
+  nsTArray<nsCString> newLocales;
+  ReadRequestedLocales(newLocales);
+
+  if (mRequestedLocales != newLocales) {
+    mRequestedLocales = Move(newLocales);
+    nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
+    if (obs) {
+      obs->NotifyObservers(nullptr, "intl:requested-locales-changed", nullptr);
+    }
+    OnLocalesChanged();
+  }
+}
+
+void
+LocaleService::OnLocalesChanged()
+{
+  MOZ_ASSERT(mIsServer, "This should only be called in the server mode.");
+
   // if mAppLocales has not been initialized yet, just return
   if (mAppLocales.IsEmpty()) {
     return;
   }
 
   nsTArray<nsCString> newLocales;
   NegotiateAppLocales(newLocales);
 
@@ -431,39 +511,43 @@ LocaleService::IsAppLocaleRTL()
   return dir.EqualsLiteral("rtl");
 #endif
 }
 
 NS_IMETHODIMP
 LocaleService::Observe(nsISupports *aSubject, const char *aTopic,
                       const char16_t *aData)
 {
+  MOZ_ASSERT(mIsServer, "This should only be called in the server mode.");
+
   // At the moment the only thing we're observing are settings indicating
   // user requested locales.
   NS_ConvertUTF16toUTF8 pref(aData);
   if (pref.EqualsLiteral(MATCH_OS_LOCALE_PREF) ||
-      pref.EqualsLiteral(SELECTED_LOCALE_PREF)) {
-    Refresh();
-    nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
-    if (obs) {
-      obs->NotifyObservers(nullptr, "intl:requested-locales-changed", nullptr);
-    }
-  } else if (pref.EqualsLiteral(ANDROID_OS_LOCALE_PREF)) {
-    Refresh();
+      pref.EqualsLiteral(SELECTED_LOCALE_PREF) ||
+      pref.EqualsLiteral(ANDROID_OS_LOCALE_PREF)) {
+    OnRequestedLocalesChanged();
   }
   return NS_OK;
 }
 
 bool
 LocaleService::LanguagesMatch(const nsCString& aRequested,
                               const nsCString& aAvailable)
 {
   return Locale(aRequested, true).LanguageMatches(Locale(aAvailable, true));
 }
 
+
+bool
+LocaleService::IsServer()
+{
+  return mIsServer;
+}
+
 /**
  * mozILocaleService methods
  */
 
 static char**
 CreateOutArray(const nsTArray<nsCString>& aArray)
 {
   uint32_t n = aArray.Length();
@@ -518,16 +602,17 @@ LocaleService::GetAppLocaleAsLangTag(nsA
 
 NS_IMETHODIMP
 LocaleService::GetAppLocaleAsBCP47(nsACString& aRetVal)
 {
   if (mAppLocales.IsEmpty()) {
     NegotiateAppLocales(mAppLocales);
   }
   aRetVal = mAppLocales[0];
+
   SanitizeForBCP47(aRetVal);
   return NS_OK;
 }
 
 static LocaleService::LangNegStrategy
 ToLangNegStrategy(int32_t aStrategy)
 {
   switch (aStrategy) {
--- a/intl/locale/LocaleService.h
+++ b/intl/locale/LocaleService.h
@@ -17,16 +17,46 @@ namespace intl {
 
 /**
  * LocaleService is a manager of language negotiation in Gecko.
  *
  * It's intended to be the core place for collecting available and
  * requested languages and negotiating them to produce a fallback
  * chain of locales for the application.
  *
+ * Client / Server
+ *
+ * LocaleService may operate in one of two modes:
+ *
+ *   server
+ *     in the server mode, LocaleService is collecting and negotiating
+ *     languages. It also subscribes to relevant observers.
+ *     There should be at most one server per application instance.
+ *
+ *   client
+ *     in the client mode, LocaleService is not responsible for collecting
+ *     or reacting to any system changes. It still distributes information
+ *     about locales, but internally, it gets information from the server instance
+ *     instead of collecting it on its own.
+ *     This prevents any data desynchronization and minimizes the cost
+ *     of running the service.
+ *
+ *   In both modes, all get* methods should work the same way and all
+ *   static methods are available.
+ *
+ *   In the server mode, other components may inform LocaleService about their
+ *   status either via calls to set* methods or via observer events.
+ *   In the client mode, only the process communication should provide data
+ *   to the LocaleService.
+ *
+ *   At the moment desktop apps use the parent process in the server mode, and
+ *   content processes in the client mode.
+ *
+ * Locale / Language
+ *
  * The terms `Locale ID` and `Language ID` are used slightly differently
  * by different organizations. Mozilla uses the term `Language ID` to describe
  * a string that contains information about the language itself, script,
  * region and variant. For example "en-Latn-US-mac" is a correct Language ID.
  *
  * Locale ID contains a Language ID plus a number of extension tags that
  * contain information that go beyond language inforamation such as
  * preferred currency, date/time formatting etc.
@@ -52,16 +82,18 @@ public:
    * strategies.
    */
   enum class LangNegStrategy {
     Filtering,
     Matching,
     Lookup
   };
 
+  explicit LocaleService(bool aIsServer);
+
   /**
    * Create (if necessary) and return a raw pointer to the singleton instance.
    * Use this accessor in C++ code that just wants to call a method on the
    * instance, but does not need to hold a reference, as in
    *    nsAutoCString str;
    *    LocaleService::GetInstance()->GetAppLocaleAsLangTag(str);
    */
   static LocaleService* GetInstance();
@@ -90,16 +122,28 @@ public:
    *   LocaleService::GetInstance()->GetAppLocalesAsLangTags(appLocales);
    *
    * (See mozILocaleService.idl for a JS-callable version of this.)
    */
   void GetAppLocalesAsLangTags(nsTArray<nsCString>& aRetVal);
   void GetAppLocalesAsBCP47(nsTArray<nsCString>& aRetVal);
 
   /**
+   * This method should only be called in the client mode.
+   *
+   * It replaces all the language negotiation and is supposed to be called
+   * in order to bring the client LocaleService in sync with the server
+   * LocaleService.
+   *
+   * Currently, it's called by the IPC code.
+   */
+  void AssignAppLocales(const nsTArray<nsCString>& aAppLocales);
+  void AssignRequestedLocales(const nsTArray<nsCString>& aRequestedLocales);
+
+  /**
    * Returns a list of locales that the user requested the app to be
    * localized to.
    *
    * The result is a sorted list of valid locale IDs and it should be
    * used as a requestedLocales input list for languages negotiation.
    *
    * Example: ["en-US", "de", "pl", "sr-Cyrl", "zh-Hans-HK"]
    *
@@ -130,22 +174,31 @@ public:
    * Returns a boolean indicating if the attempt to retrieve at least
    * one locale was successful.
    *
    * (See mozILocaleService.idl for a JS-callable version of this.)
    */
   bool GetAvailableLocales(nsTArray<nsCString>& aRetVal);
 
   /**
-   * Triggers a refresh of the language negotiation process.
+   * Those three functions allow to trigger cache invalidation on one of the
+   * three cached values.
+   *
+   * In most cases, the functions will be called by the observer in
+   * LocaleService itself, but in a couple special cases, we have the
+   * other component call this manually instead of sending a global event.
    *
    * If the result differs from the previous list, it will additionally
-   * trigger a global event "intl:app-locales-changed".
+   * trigger a corresponding event
+   *
+   * This code should be called only in the server mode..
    */
-  void Refresh();
+  void OnAvailableLocalesChanged();
+  void OnRequestedLocalesChanged();
+  void OnLocalesChanged();
 
   /**
    * Negotiates the best locales out of an ordered list of requested locales and
    * a list of available locales.
    *
    * Internally it uses the following naming scheme:
    *
    *  Requested - locales requested by the user
@@ -170,16 +223,18 @@ public:
   /**
    * Returns whether the current app locale is RTL.
    */
   bool IsAppLocaleRTL();
 
   static bool LanguagesMatch(const nsCString& aRequested,
                              const nsCString& aAvailable);
 
+  bool IsServer();
+
 private:
   /**
    * Locale object, a BCP47-style tag decomposed into subtags for
    * matching purposes.
    *
    * If constructed with aRange = true, any missing subtags will be
    * set to "*".
    */
@@ -221,15 +276,18 @@ private:
                      LangNegStrategy aStrategy,
                      nsTArray<nsCString>& aRetVal);
 
   void NegotiateAppLocales(nsTArray<nsCString>& aRetVal);
 
   virtual ~LocaleService();
 
   nsTArray<nsCString> mAppLocales;
+  nsTArray<nsCString> mRequestedLocales;
+  nsTArray<nsCString> mAvailableLocales;
+  const bool mIsServer;
 
   static StaticRefPtr<LocaleService> sInstance;
 };
 } // intl
 } // namespace mozilla
 
 #endif /* mozilla_intl_LocaleService_h__ */
--- a/js/public/ProfilingStack.h
+++ b/js/public/ProfilingStack.h
@@ -194,17 +194,17 @@ class ProfileEntry
 
     static size_t offsetOfLabel() { return offsetof(ProfileEntry, string); }
     static size_t offsetOfSpOrScript() { return offsetof(ProfileEntry, spOrScript); }
     static size_t offsetOfLineOrPcOffset() { return offsetof(ProfileEntry, lineOrPcOffset); }
     static size_t offsetOfFlags() { return offsetof(ProfileEntry, flags_); }
 };
 
 JS_FRIEND_API(void)
-SetContextProfilingStack(JSContext* cx, ProfileEntry* stack, uint32_t* size,
+SetContextProfilingStack(JSContext* cx, ProfileEntry* stack, mozilla::Atomic<uint32_t>* size,
                          uint32_t max);
 
 JS_FRIEND_API(void)
 EnableContextProfilingStack(JSContext* cx, bool enabled);
 
 JS_FRIEND_API(void)
 RegisterContextProfilingEventMarker(JSContext* cx, void (*fn)(const char*));
 
--- a/js/src/jsapi-tests/testProfileStrings.cpp
+++ b/js/src/jsapi-tests/testProfileStrings.cpp
@@ -2,22 +2,24 @@
  * vim: set ts=8 sts=4 et sw=4 tw=99:
  *
  * Tests the stack-based instrumentation profiler on a JSRuntime
  */
 /* 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/. */
 
+#include "mozilla/Atomics.h"
+
 #include "jscntxt.h"
 
 #include "jsapi-tests/tests.h"
 
 static js::ProfileEntry pstack[10];
-static uint32_t psize = 0;
+static mozilla::Atomic<uint32_t> psize;
 static uint32_t max_stack = 0;
 
 static void
 reset(JSContext* cx)
 {
     psize = max_stack = 0;
     memset(pstack, 0, sizeof(pstack));
     cx->runtime()->geckoProfiler().stringsReset();
--- a/js/src/shell/js.cpp
+++ b/js/src/shell/js.cpp
@@ -338,17 +338,17 @@ struct ShellContext
     int exitCode;
     bool quitting;
 
     UniqueChars readLineBuf;
     size_t readLineBufPos;
 
     static const uint32_t GeckoProfilingMaxStackSize = 1000;
     ProfileEntry geckoProfilingStack[GeckoProfilingMaxStackSize];
-    uint32_t geckoProfilingStackSize;
+    mozilla::Atomic<uint32_t> geckoProfilingStackSize;
 
     OffThreadState offThreadState;
 
     UniqueChars moduleLoadPath;
     UniquePtr<MarkBitObservers> markObservers;
 };
 
 struct MOZ_STACK_CLASS EnvironmentPreparer : public js::ScriptEnvironmentPreparer {
--- a/js/src/vm/GeckoProfiler.cpp
+++ b/js/src/vm/GeckoProfiler.cpp
@@ -44,17 +44,17 @@ GeckoProfiler::init()
     auto locked = strings.lock();
     if (!locked->init())
         return false;
 
     return true;
 }
 
 void
-GeckoProfiler::setProfilingStack(ProfileEntry* stack, uint32_t* size, uint32_t max)
+GeckoProfiler::setProfilingStack(ProfileEntry* stack, mozilla::Atomic<uint32_t>* size, uint32_t max)
 {
     MOZ_ASSERT_IF(size_ && *size_ != 0, !enabled());
     MOZ_ASSERT(strings.lock()->initialized());
 
     stack_ = stack;
     size_  = size;
     max_   = max;
 }
@@ -231,17 +231,17 @@ GeckoProfiler::exit(JSScript* script, JS
     if (*size_ < max_) {
         const char* str = profileString(script, maybeFun);
         /* Can't fail lookup because we should already be in the set */
         MOZ_ASSERT(str != nullptr);
 
         // Bug 822041
         if (!stack_[*size_].isJs()) {
             fprintf(stderr, "--- ABOUT TO FAIL ASSERTION ---\n");
-            fprintf(stderr, " stack=%p size=%d/%d\n", (void*) stack_, *size_, max_);
+            fprintf(stderr, " stack=%p size=%d/%d\n", (void*) stack_, size(), max_);
             for (int32_t i = *size_; i >= 0; i--) {
                 if (stack_[i].isJs())
                     fprintf(stderr, "  [%d] JS %s\n", i, stack_[i].label());
                 else
                     fprintf(stderr, "  [%d] C line %d %s\n", i, stack_[i].line(), stack_[i].label());
             }
         }
 
@@ -254,39 +254,37 @@ GeckoProfiler::exit(JSScript* script, JS
 #endif
 }
 
 void
 GeckoProfiler::beginPseudoJS(const char* string, void* sp)
 {
     /* these operations cannot be re-ordered, so volatile-ize operations */
     volatile ProfileEntry* stack = stack_;
-    volatile uint32_t* size = size_;
-    uint32_t current = *size;
+    uint32_t current = *size_;
 
     MOZ_ASSERT(installed());
     if (current < max_) {
         stack[current].setLabel(string);
         stack[current].initCppFrame(sp, 0);
         stack[current].setFlag(ProfileEntry::BEGIN_PSEUDO_JS);
     }
-    *size = current + 1;
+    *size_ = current + 1;
 }
 
 void
 GeckoProfiler::push(const char* string, void* sp, JSScript* script, jsbytecode* pc, bool copy,
                   ProfileEntry::Category category)
 {
     MOZ_ASSERT_IF(sp != nullptr, script == nullptr && pc == nullptr);
     MOZ_ASSERT_IF(sp == nullptr, script != nullptr && pc != nullptr);
 
     /* these operations cannot be re-ordered, so volatile-ize operations */
     volatile ProfileEntry* stack = stack_;
-    volatile uint32_t* size = size_;
-    uint32_t current = *size;
+    uint32_t current = *size_;
 
     MOZ_ASSERT(installed());
     if (current < max_) {
         volatile ProfileEntry& entry = stack[current];
 
         if (sp != nullptr) {
             entry.initCppFrame(sp, 0);
             MOZ_ASSERT(entry.flags() == js::ProfileEntry::IS_CPP_ENTRY);
@@ -301,17 +299,17 @@ GeckoProfiler::push(const char* string, 
         entry.setCategory(category);
 
         // Track if mLabel needs a copy.
         if (copy)
             entry.setFlag(js::ProfileEntry::FRAME_LABEL_COPY);
         else
             entry.unsetFlag(js::ProfileEntry::FRAME_LABEL_COPY);
     }
-    *size = current + 1;
+    *size_ = current + 1;
 }
 
 void
 GeckoProfiler::pop()
 {
     MOZ_ASSERT(installed());
     MOZ_ASSERT(*size_ > 0);
     (*size_)--;
@@ -370,17 +368,17 @@ GeckoProfiler::allocProfileString(JSScri
 
     return cstr;
 }
 
 void
 GeckoProfiler::trace(JSTracer* trc)
 {
     if (stack_) {
-        size_t limit = Min(*size_, max_);
+        size_t limit = Min(uint32_t(*size_), max_);
         for (size_t i = 0; i < limit; i++)
             stack_[i].trace(trc);
     }
 }
 
 void
 GeckoProfiler::fixupStringsMapAfterMovingGC()
 {
@@ -549,17 +547,17 @@ ProfileEntry::setPC(jsbytecode* pc) vola
 {
     MOZ_ASSERT(isJs());
     JSScript* script = this->script();
     MOZ_ASSERT(script); // This should not be called while profiling is suppressed.
     lineOrPcOffset = pc == nullptr ? NullPCOffset : script->pcToOffset(pc);
 }
 
 JS_FRIEND_API(void)
-js::SetContextProfilingStack(JSContext* cx, ProfileEntry* stack, uint32_t* size, uint32_t max)
+js::SetContextProfilingStack(JSContext* cx, ProfileEntry* stack, mozilla::Atomic<uint32_t>* size, uint32_t max)
 {
     cx->runtime()->geckoProfiler().setProfilingStack(stack, size, max);
 }
 
 JS_FRIEND_API(void)
 js::EnableContextProfilingStack(JSContext* cx, bool enabled)
 {
     if (!cx->runtime()->geckoProfiler().enable(enabled))
--- a/js/src/vm/GeckoProfiler.h
+++ b/js/src/vm/GeckoProfiler.h
@@ -125,45 +125,40 @@ class GeckoProfiler
 {
     friend class AutoGeckoProfilerEntry;
     friend class GeckoProfilerEntryMarker;
     friend class GeckoProfilerBaselineOSRMarker;
 
     JSRuntime*           rt;
     ExclusiveData<ProfileStringMap> strings;
     ProfileEntry*        stack_;
-    uint32_t*            size_;
+    mozilla::Atomic<uint32_t>* size_;
     uint32_t             max_;
     bool                 slowAssertions;
     uint32_t             enabled_;
     void                (*eventMarker_)(const char*);
 
     UniqueChars allocProfileString(JSScript* script, JSFunction* function);
     void push(const char* string, void* sp, JSScript* script, jsbytecode* pc, bool copy,
               ProfileEntry::Category category = ProfileEntry::Category::JS);
     void pop();
 
   public:
     explicit GeckoProfiler(JSRuntime* rt);
 
     bool init();
 
-    uint32_t** addressOfSizePointer() {
-        return &size_;
-    }
-
     uint32_t* addressOfMaxSize() {
         return &max_;
     }
 
     ProfileEntry** addressOfStack() {
         return &stack_;
     }
 
-    uint32_t* sizePointer() { return size_; }
     uint32_t maxSize() { return max_; }
     uint32_t size() { MOZ_ASSERT(installed()); return *size_; }
     ProfileEntry* stack() { return stack_; }
 
     /* management of whether instrumentation is on or off */
     bool enabled() { MOZ_ASSERT_IF(enabled_, installed()); return enabled_; }
     bool installed() { return stack_ != nullptr && size_ != nullptr; }
     MOZ_MUST_USE bool enable(bool enabled);
@@ -188,17 +183,17 @@ class GeckoProfiler
             stack_[*size_ - 1].setPC(pc);
         }
     }
 
     /* Enter wasm code */
     void beginPseudoJS(const char* string, void* sp);
     void endPseudoJS() { pop(); }
 
-    void setProfilingStack(ProfileEntry* stack, uint32_t* size, uint32_t max);
+    void setProfilingStack(ProfileEntry* stack, mozilla::Atomic<uint32_t>* size, uint32_t max);
     void setEventMarker(void (*fn)(const char*));
     const char* profileString(JSScript* script, JSFunction* maybeFun);
     void onScriptFinalized(JSScript* script);
 
     void markEvent(const char* event);
 
     /* meant to be used for testing, not recommended to call in normal code */
     size_t stringsCount();
--- a/js/xpconnect/tests/mochitest/test_bug790732.html
+++ b/js/xpconnect/tests/mochitest/test_bug790732.html
@@ -27,17 +27,18 @@ https://bugzilla.mozilla.org/show_bug.cg
   is(Ci.nsIDOMCSSRule.NAMESPACE_RULE, CSSRule.NAMESPACE_RULE);
   is(Ci.nsIDOMCSSValue.CSS_PRIMITIVE_VALUE, CSSValue.CSS_PRIMITIVE_VALUE);
   is(Ci.nsIDOMEvent.FOCUS, Event.FOCUS);
   is(Ci.nsIDOMNSEvent.CLICK, Event.CLICK);
   is(Ci.nsIDOMKeyEvent, KeyEvent);
   is(Ci.nsIDOMMouseEvent, MouseEvent);
   is(Ci.nsIDOMMouseScrollEvent, MouseScrollEvent);
   is(Ci.nsIDOMMutationEvent, MutationEvent);
-  is(Ci.nsIDOMSimpleGestureEvent, SimpleGestureEvent);
+  // XXX We can't test this here because it's only exposed to chrome
+  //is(Ci.nsIDOMSimpleGestureEvent, SimpleGestureEvent);
   is(Ci.nsIDOMUIEvent, UIEvent);
   is(Ci.nsIDOMHTMLMediaElement, HTMLMediaElement);
   is(Ci.nsIDOMOfflineResourceList, OfflineResourceList);
   is(Ci.nsIDOMRange, Range);
   is(Ci.nsIDOMSVGLength, SVGLength);
   is(Ci.nsIDOMNodeFilter, NodeFilter);
   is(Ci.nsIDOMXPathResult, XPathResult);
 
--- a/layout/base/GeckoRestyleManager.cpp
+++ b/layout/base/GeckoRestyleManager.cpp
@@ -3324,24 +3324,24 @@ ElementRestyler::MustReframeForPseudo(CS
       return false;
     }
   }
 
   if (aPseudoType == CSSPseudoElementType::before) {
     // Check for a ::before pseudo style and the absence of a ::before content,
     // but only if aFrame is null or is the first continuation/ib-split.
     if ((aFrame && !nsLayoutUtils::IsFirstContinuationOrIBSplitSibling(aFrame)) ||
-        nsLayoutUtils::GetBeforeFrameForContent(aGenConParentFrame, aContent)) {
+        nsLayoutUtils::GetBeforeFrame(aContent)) {
       return false;
     }
   } else {
     // Similarly for ::after, but check for being the last continuation/
     // ib-split.
     if ((aFrame && nsLayoutUtils::GetNextContinuationOrIBSplitSibling(aFrame)) ||
-        nsLayoutUtils::GetAfterFrameForContent(aGenConParentFrame, aContent)) {
+        nsLayoutUtils::GetAfterFrame(aContent)) {
       return false;
     }
   }
 
   // Checking for a ::before frame (which we do above) is cheaper than getting
   // the ::before style context here.
   return nsLayoutUtils::HasPseudoStyle(aContent, aStyleContext, aPseudoType,
                                        mPresContext);
--- a/layout/base/PresShell.cpp
+++ b/layout/base/PresShell.cpp
@@ -2941,54 +2941,16 @@ PresShell::CreateFramesFor(nsIContent* a
   nsILayoutHistoryState* layoutState = fc->GetLastCapturedLayoutHistoryState();
   fc->BeginUpdate();
   fc->ContentInserted(aContent->GetParent(), aContent, layoutState, false);
   fc->EndUpdate();
 
   --mChangeNestCount;
 }
 
-nsresult
-PresShell::RecreateFramesFor(nsIContent* aContent)
-{
-  NS_ENSURE_TRUE(mPresContext, NS_ERROR_FAILURE);
-  if (!mDidInitialize) {
-    // Nothing to do here.  In fact, if we proceed and aContent is the
-    // root we will crash.
-    return NS_OK;
-  }
-
-  // Don't call RecreateFramesForContent since that is not exported and we want
-  // to keep the number of entrypoints down.
-
-  NS_ASSERTION(mViewManager, "Should have view manager");
-
-  // Have to make sure that the content notifications are flushed before we
-  // start messing with the frame model; otherwise we can get content doubling.
-  mDocument->FlushPendingNotifications(FlushType::ContentAndNotify);
-
-  nsAutoScriptBlocker scriptBlocker;
-
-  nsStyleChangeList changeList(mPresContext->StyleSet()->BackendType());
-  changeList.AppendChange(nullptr, aContent, nsChangeHint_ReconstructFrame);
-
-  // We might have restyles pending when we're asked to recreate frames.
-  // Record that we're OK with stale styles being returned, to avoid assertions.
-  ServoStyleSet::AutoAllowStaleStyles guard(mStyleSet->GetAsServo());
-
-  // Mark ourselves as not safe to flush while we're doing frame construction.
-  ++mChangeNestCount;
-  RestyleManager* restyleManager = mPresContext->RestyleManager();
-  restyleManager->ProcessRestyledFrames(changeList);
-  restyleManager->FlushOverflowChangedTracker();
-  --mChangeNestCount;
-
-  return NS_OK;
-}
-
 void
 nsIPresShell::PostRecreateFramesFor(Element* aElement)
 {
   mPresContext->RestyleManager()->PostRestyleEvent(aElement, nsRestyleHint(0),
                                                    nsChangeHint_ReconstructFrame);
 }
 
 void
--- a/layout/base/PresShell.h
+++ b/layout/base/PresShell.h
@@ -51,16 +51,20 @@ struct nsCallbackEventRequest;
 class ReflowCountMgr;
 #endif
 
 class nsPresShellEventCB;
 class nsAutoCauseReflowNotifier;
 
 namespace mozilla {
 
+namespace dom {
+class Element;
+}  // namespace dom
+
 class EventDispatchingCallback;
 
 // A set type for tracking visible frames, for use by the visibility code in
 // PresShell. The set contains nsIFrame* pointers.
 typedef nsTHashtable<nsPtrHashKey<nsIFrame>> VisibleFrames;
 
 // A hash table type for tracking visible regions, for use by the visibility
 // code in PresShell. The mapping is from view IDs to regions in the
@@ -128,21 +132,16 @@ public:
   virtual bool IsSafeToFlush() const override;
   virtual void DoFlushPendingNotifications(mozilla::FlushType aType) override;
   virtual void DoFlushPendingNotifications(mozilla::ChangesToFlush aType) override;
   virtual void DestroyFramesFor(nsIContent*  aContent,
                                 nsIContent** aDestroyedFramesFor) override;
   virtual void CreateFramesFor(nsIContent* aContent) override;
 
   /**
-   * Recreates the frames for a node
-   */
-  virtual nsresult RecreateFramesFor(nsIContent* aContent) override;
-
-  /**
    * Post a callback that should be handled after reflow has finished.
    */
   virtual nsresult PostReflowCallback(nsIReflowCallback* aCallback) override;
   virtual void CancelReflowCallback(nsIReflowCallback* aCallback) override;
 
   virtual void ClearFrameRefs(nsIFrame* aFrame) override;
   virtual already_AddRefed<gfxContext> CreateReferenceRenderingContext() override;
   virtual nsresult GoToAnchor(const nsAString& aAnchorName, bool aScroll,
--- a/layout/base/RestyleTracker.h
+++ b/layout/base/RestyleTracker.h
@@ -340,23 +340,22 @@ RestyleTracker::AddPendingRestyle(Elemen
       // after we deal with cur.
       //
       // As with the mRestyleRoots array, mDescendants maintains the
       // invariant that if two elements appear in the array and one
       // is an ancestor of the other, that the ancestor appears after
       // the descendant.
       RestyleData* curData;
       mPendingRestyles.Get(cur, &curData);
-      NS_ASSERTION(curData, "expected to find a RestyleData for cur");
-      // If cur has an eRestyle_ForceDescendants restyle hint, then we
-      // know that we will get to all descendants.  Don't bother
-      // recording the descendant to restyle in that case.
-      if (curData && !(curData->mRestyleHint & eRestyle_ForceDescendants)) {
-        curData->mDescendants.AppendElement(aElement);
-      }
+
+      // Even if cur has a ForceDescendants restyle hint, we're not guaranteed
+      // to reach aElement in the case the PresShell posts a restyle event from
+      // PostRecreateFramesFor, so we need to track it here.
+      MOZ_ASSERT(curData, "expected to find a RestyleData for cur");
+      curData->mDescendants.AppendElement(aElement);
     }
   }
 
   mHaveLaterSiblingRestyles =
     mHaveLaterSiblingRestyles || (aRestyleHint & eRestyle_LaterSiblings) != 0;
   return hadRestyleLaterSiblings;
 }
 
--- a/layout/base/ServoRestyleManager.cpp
+++ b/layout/base/ServoRestyleManager.cpp
@@ -330,35 +330,26 @@ ServoRestyleManager::ProcessPostTraversa
   }
 }
 
 /* static */ nsIFrame*
 ServoRestyleManager::FrameForPseudoElement(const nsIContent* aContent,
                                            nsIAtom* aPseudoTagOrNull)
 {
   MOZ_ASSERT_IF(aPseudoTagOrNull, aContent->IsElement());
-  nsIFrame* primaryFrame = aContent->GetPrimaryFrame();
-
   if (!aPseudoTagOrNull) {
-    return primaryFrame;
+    return aContent->GetPrimaryFrame();
   }
 
-  // FIXME(emilio): Need to take into account display: contents pseudos!
-  if (!primaryFrame) {
-    return nullptr;
-  }
-
-  // NOTE: we probably need to special-case display: contents here. Gecko's
-  // RestyleManager passes the primary frame of the parent instead.
   if (aPseudoTagOrNull == nsCSSPseudoElements::before) {
-    return nsLayoutUtils::GetBeforeFrameForContent(primaryFrame, aContent);
+    return nsLayoutUtils::GetBeforeFrame(aContent);
   }
 
   if (aPseudoTagOrNull == nsCSSPseudoElements::after) {
-    return nsLayoutUtils::GetAfterFrameForContent(primaryFrame, aContent);
+    return nsLayoutUtils::GetAfterFrame(aContent);
   }
 
   MOZ_CRASH("Unkown pseudo-element given to "
             "ServoRestyleManager::FrameForPseudoElement");
   return nullptr;
 }
 
 void
--- a/layout/base/nsCSSFrameConstructor.cpp
+++ b/layout/base/nsCSSFrameConstructor.cpp
@@ -1832,21 +1832,17 @@ nsCSSFrameConstructor::CreateGeneratedCo
                                                   nsStyleContext*  aStyleContext,
                                                   CSSPseudoElementType aPseudoElement,
                                                   FrameConstructionItemList& aItems)
 {
   MOZ_ASSERT(aPseudoElement == CSSPseudoElementType::before ||
              aPseudoElement == CSSPseudoElementType::after,
              "unexpected aPseudoElement");
 
-  // XXXbz is this ever true?
-  if (!aParentContent->IsElement()) {
-    NS_ERROR("Bogus generated content parent");
-    return;
-  }
+  MOZ_ASSERT(aParentContent->IsElement());
 
   StyleSetHandle styleSet = mPresShell->StyleSet();
 
   // Probe for the existence of the pseudo-element
   RefPtr<nsStyleContext> pseudoStyleContext;
   pseudoStyleContext =
     styleSet->ProbePseudoElementStyle(aParentContent->AsElement(),
                                       aPseudoElement,
@@ -1864,16 +1860,23 @@ nsCSSFrameConstructor::CreateGeneratedCo
     nsGkAtoms::mozgeneratedcontentbefore : nsGkAtoms::mozgeneratedcontentafter;
   nodeInfo = mDocument->NodeInfoManager()->GetNodeInfo(elemName, nullptr,
                                                        kNameSpaceID_None,
                                                        nsIDOMNode::ELEMENT_NODE);
   nsCOMPtr<Element> container;
   nsresult rv = NS_NewXMLElement(getter_AddRefs(container), nodeInfo.forget());
   if (NS_FAILED(rv))
     return;
+
+  // Cleared when the pseudo is unbound from the tree, so no need to store a
+  // strong reference, nor a destructor.
+  nsIAtom* property = isBefore
+    ? nsGkAtoms::beforePseudoProperty : nsGkAtoms::afterPseudoProperty;
+  aParentContent->SetProperty(property, container.get());
+
   container->SetIsNativeAnonymousRoot();
   container->SetPseudoElementType(aPseudoElement);
 
   // If the parent is in a shadow tree, make sure we don't
   // bind with a document because shadow roots and its descendants
   // are not in document.
   nsIDocument* bindDocument =
     aParentContent->HasFlag(NODE_IS_IN_SHADOW_TREE) ? nullptr : mDocument;
@@ -6137,16 +6140,19 @@ nsCSSFrameConstructor::AddFrameConstruct
 }
 
 static void
 AddGenConPseudoToFrame(nsIFrame* aOwnerFrame, nsIContent* aContent)
 {
   NS_ASSERTION(nsLayoutUtils::IsFirstContinuationOrIBSplitSibling(aOwnerFrame),
                "property should only be set on first continuation/ib-sibling");
 
+  // FIXME(emilio): Remove this property, and use the frame of the generated
+  // content itself to tear the content down? It should be quite simpler.
+
   FrameProperties props = aOwnerFrame->Properties();
   nsIFrame::ContentArray* value = props.Get(nsIFrame::GenConProperty());
   if (!value) {
     value = new nsIFrame::ContentArray;
     props.Set(nsIFrame::GenConProperty(), value);
   }
   value->AppendElement(aContent);
 }
@@ -6398,17 +6404,17 @@ AdjustAppendParentForAfterContent(nsFram
                                   nsIContent* aChild,
                                   nsIFrame** aAfterFrame)
 {
   // If the parent frame has any pseudo-elements or aContainer is a
   // display:contents node then we need to walk through the child
   // frames to find the first one that is either a ::after frame for an
   // ancestor of aChild or a frame that is for a node later in the
   // document than aChild and return that in aAfterFrame.
-  if (aParentFrame->GetGenConPseudos() ||
+  if (aParentFrame->Properties().Get(nsIFrame::GenConProperty()) ||
       nsLayoutUtils::HasPseudoStyle(aContainer, aParentFrame->StyleContext(),
                                     CSSPseudoElementType::after,
                                     aParentFrame->PresContext()) ||
       aFrameManager->GetDisplayContentsStyleFor(aContainer)) {
     nsIFrame* afterFrame = nullptr;
     nsContainerFrame* parent =
       static_cast<nsContainerFrame*>(aParentFrame->LastContinuation());
     bool done = false;
@@ -6727,32 +6733,30 @@ nsCSSFrameConstructor::FindFrameForConte
                                                   nsIContent* aTargetContent,
                                                   StyleDisplay& aTargetContentDisplay,
                                                   nsContainerFrame* aParentFrame,
                                                   bool aPrevSibling)
 {
   nsIFrame* sibling = aContent->GetPrimaryFrame();
   if (!sibling && GetDisplayContentsStyleFor(aContent)) {
     // A display:contents node - check if it has a ::before / ::after frame...
-    sibling = aPrevSibling ?
-      nsLayoutUtils::GetAfterFrameForContent(aParentFrame, aContent) :
-      nsLayoutUtils::GetBeforeFrameForContent(aParentFrame, aContent);
+    sibling = aPrevSibling ? nsLayoutUtils::GetAfterFrame(aContent)
+                           : nsLayoutUtils::GetBeforeFrame(aContent);
     if (!sibling) {
       // ... then recurse into children ...
       const bool forward = !aPrevSibling;
       FlattenedChildIterator iter(aContent, forward);
       sibling = aPrevSibling ?
         FindPreviousSibling(iter, aTargetContent, aTargetContentDisplay, aParentFrame) :
         FindNextSibling(iter, aTargetContent, aTargetContentDisplay, aParentFrame);
     }
     if (!sibling) {
       // ... then ::after / ::before on the opposite end.
-      sibling = aPrevSibling ?
-        nsLayoutUtils::GetBeforeFrameForContent(aParentFrame, aContent) :
-        nsLayoutUtils::GetAfterFrameForContent(aParentFrame, aContent);
+      sibling = aPrevSibling ? nsLayoutUtils::GetAfterFrame(aContent)
+                             : nsLayoutUtils::GetBeforeFrame(aContent);
     }
     if (!sibling) {
       return nullptr;
     }
   } else if (!sibling || sibling->GetContent() != aContent) {
     // XXX the GetContent() != aContent check is needed due to bug 135040.
     // Remove it once that's fixed.
     return nullptr;
@@ -8531,35 +8535,33 @@ nsCSSFrameConstructor::ContentRemoved(ns
   if (!childFrame || childFrame->GetContent() != aChild) {
     // XXXbz the GetContent() != aChild check is needed due to bug 135040.
     // Remove it once that's fixed.
     ClearUndisplayedContentIn(aChild, aContainer);
   }
   MOZ_ASSERT(!childFrame || !GetDisplayContentsStyleFor(aChild),
              "display:contents nodes shouldn't have a frame");
   if (!childFrame && GetDisplayContentsStyleFor(aChild)) {
-    nsIFrame* ancestorFrame = nullptr;
     nsIContent* ancestor = aContainer;
-    for (; ancestor; ancestor = ancestor->GetParent()) {
-      ancestorFrame = ancestor->GetPrimaryFrame();
-      if (ancestorFrame) {
-        break;
-      }
-    }
-    if (ancestorFrame) {
-      nsTArray<nsIContent*>* generated = ancestorFrame->GetGenConPseudos();
-      if (generated) {
-        *aDidReconstruct = true;
-        LAYOUT_PHASE_TEMP_EXIT();
-        // XXXmats Can we recreate frames only for the ::after/::before content?
-        // XXX Perhaps even only those that belong to the aChild sub-tree?
-        RecreateFramesForContent(ancestor, false, aFlags, aDestroyedFramesFor);
-        LAYOUT_PHASE_TEMP_REENTER();
-        return;
-      }
+    while (!ancestor->GetPrimaryFrame()) {
+      // FIXME(emilio): Should this use the flattened tree parent instead?
+      ancestor = ancestor->GetParent();
+      MOZ_ASSERT(ancestor, "we can't have a display: contents subtree root!");
+    }
+
+    nsIFrame* ancestorFrame = ancestor->GetPrimaryFrame();
+    if (ancestorFrame->Properties().Get(nsIFrame::GenConProperty())) {
+      *aDidReconstruct = true;
+      LAYOUT_PHASE_TEMP_EXIT();
+
+      // XXXmats Can we recreate frames only for the ::after/::before content?
+      // XXX Perhaps even only those that belong to the aChild sub-tree?
+      RecreateFramesForContent(ancestor, false, aFlags, aDestroyedFramesFor);
+      LAYOUT_PHASE_TEMP_REENTER();
+      return;
     }
 
     FlattenedChildIterator iter(aChild);
     for (nsIContent* c = iter.GetNextChild(); c; c = iter.GetNextChild()) {
       if (c->GetPrimaryFrame() || GetDisplayContentsStyleFor(c)) {
         LAYOUT_PHASE_TEMP_EXIT();
         ContentRemoved(aChild, c, nullptr, aFlags, aDidReconstruct, aDestroyedFramesFor);
         LAYOUT_PHASE_TEMP_REENTER();
--- a/layout/base/nsIPresShell.h
+++ b/layout/base/nsIPresShell.h
@@ -544,21 +544,16 @@ public:
                                 nsIContent** aDestroyedFramesFor) = 0;
   /**
    * Create new frames for aContent.  It will use the last captured layout
    * history state captured in the frame constructor to restore the state
    * in the new frame tree.
    */
   virtual void CreateFramesFor(nsIContent* aContent) = 0;
 
-  /**
-   * Recreates the frames for a node
-   */
-  virtual nsresult RecreateFramesFor(nsIContent* aContent) = 0;
-
   void PostRecreateFramesFor(mozilla::dom::Element* aElement);
   void RestyleForAnimation(mozilla::dom::Element* aElement,
                            nsRestyleHint aHint);
 
   // ShadowRoot has APIs that can change styles so we only
   // want to restyle elements in the ShadowRoot and not the whole
   // document.
   virtual void RecordShadowStyleChange(mozilla::dom::ShadowRoot* aShadowRoot) = 0;
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -1580,100 +1580,48 @@ nsLayoutUtils::GetChildListNameFor(nsIFr
     // else it's positioned and should have been on the 'id' child list.
     NS_POSTCONDITION(found, "not in child list");
   }
 #endif
 
   return id;
 }
 
-/*static*/ nsIFrame*
-nsLayoutUtils::GetBeforeFrameForContent(nsIFrame* aFrame,
-                                        const nsIContent* aContent)
-{
-  // We need to call GetGenConPseudos() on the first continuation/ib-split.
-  // Find it, for symmetry with GetAfterFrameForContent.
-  nsContainerFrame* genConParentFrame =
-    FirstContinuationOrIBSplitSibling(aFrame)->GetContentInsertionFrame();
-  if (!genConParentFrame) {
-    return nullptr;
-  }
-  nsTArray<nsIContent*>* prop = genConParentFrame->GetGenConPseudos();
-  if (prop) {
-    const nsTArray<nsIContent*>& pseudos(*prop);
-    for (uint32_t i = 0; i < pseudos.Length(); ++i) {
-      if (pseudos[i]->GetParent() == aContent &&
-          pseudos[i]->NodeInfo()->NameAtom() == nsGkAtoms::mozgeneratedcontentbefore) {
-        return pseudos[i]->GetPrimaryFrame();
-      }
-    }
-  }
-  // If the first child frame is a pseudo-frame, then try that.
-  // Note that the frame we create for the generated content is also a
-  // pseudo-frame and so don't drill down in that case.
-  nsIFrame* childFrame = genConParentFrame->PrincipalChildList().FirstChild();
-  if (childFrame &&
-      childFrame->IsPseudoFrame(aContent) &&
-      !childFrame->IsGeneratedContentFrame()) {
-    return GetBeforeFrameForContent(childFrame, aContent);
-  }
-  return nullptr;
-}
-
-/*static*/ nsIFrame*
-nsLayoutUtils::GetBeforeFrame(nsIFrame* aFrame)
-{
-  return GetBeforeFrameForContent(aFrame, aFrame->GetContent());
+static Element*
+GetPseudo(const nsIContent* aContent, nsIAtom* aPseudoProperty)
+{
+  MOZ_ASSERT(aPseudoProperty == nsGkAtoms::beforePseudoProperty ||
+             aPseudoProperty == nsGkAtoms::afterPseudoProperty);
+  return static_cast<Element*>(aContent->GetProperty(aPseudoProperty));
+}
+
+/*static*/ Element*
+nsLayoutUtils::GetBeforePseudo(const nsIContent* aContent)
+{
+  return GetPseudo(aContent, nsGkAtoms::beforePseudoProperty);
 }
 
 /*static*/ nsIFrame*
-nsLayoutUtils::GetAfterFrameForContent(nsIFrame* aFrame,
-                                       const nsIContent* aContent)
-{
-  // We need to call GetGenConPseudos() on the first continuation,
-  // but callers are likely to pass the last.
-  nsContainerFrame* genConParentFrame =
-    FirstContinuationOrIBSplitSibling(aFrame)->GetContentInsertionFrame();
-  if (!genConParentFrame) {
-    return nullptr;
-  }
-  nsTArray<nsIContent*>* prop = genConParentFrame->GetGenConPseudos();
-  if (prop) {
-    const nsTArray<nsIContent*>& pseudos(*prop);
-    for (uint32_t i = 0; i < pseudos.Length(); ++i) {
-      if (pseudos[i]->GetParent() == aContent &&
-          pseudos[i]->NodeInfo()->NameAtom() == nsGkAtoms::mozgeneratedcontentafter) {
-        return pseudos[i]->GetPrimaryFrame();
-      }
-    }
-  }
-  // If the last child frame is a pseudo-frame, then try that.
-  // Note that the frame we create for the generated content is also a
-  // pseudo-frame and so don't drill down in that case.
-  genConParentFrame = aFrame->GetContentInsertionFrame();
-  if (!genConParentFrame) {
-    return nullptr;
-  }
-  nsIFrame* lastParentContinuation =
-    LastContinuationWithChild(static_cast<nsContainerFrame*>(
-      LastContinuationOrIBSplitSibling(genConParentFrame)));
-  nsIFrame* childFrame =
-    lastParentContinuation->GetChildList(nsIFrame::kPrincipalList).LastChild();
-  if (childFrame &&
-      childFrame->IsPseudoFrame(aContent) &&
-      !childFrame->IsGeneratedContentFrame()) {
-    return GetAfterFrameForContent(childFrame->FirstContinuation(), aContent);
-  }
-  return nullptr;
+nsLayoutUtils::GetBeforeFrame(const nsIContent* aContent)
+{
+  Element* pseudo = GetBeforePseudo(aContent);
+  return pseudo ? pseudo->GetPrimaryFrame() : nullptr;
+}
+
+/*static*/ Element*
+nsLayoutUtils::GetAfterPseudo(const nsIContent* aContent)
+{
+  return GetPseudo(aContent, nsGkAtoms::afterPseudoProperty);
 }
 
 /*static*/ nsIFrame*
-nsLayoutUtils::GetAfterFrame(nsIFrame* aFrame)
-{
-  return GetAfterFrameForContent(aFrame, aFrame->GetContent());
+nsLayoutUtils::GetAfterFrame(const nsIContent* aContent)
+{
+  Element* pseudo = GetAfterPseudo(aContent);
+  return pseudo ? pseudo->GetPrimaryFrame() : nullptr;
 }
 
 // static
 nsIFrame*
 nsLayoutUtils::GetClosestFrameOfType(nsIFrame* aFrame,
                                      nsIAtom* aFrameType,
                                      nsIFrame* aStopAt)
 {
--- a/layout/base/nsLayoutUtils.h
+++ b/layout/base/nsLayoutUtils.h
@@ -281,60 +281,36 @@ public:
 
   /**
    * Use heuristics to figure out the child list that
    * aChildFrame is currently in.
    */
   static mozilla::layout::FrameChildListID GetChildListNameFor(nsIFrame* aChildFrame);
 
   /**
-   * GetBeforeFrameForContent returns the ::before frame for aContent, if
-   * one exists.  This is typically O(1).  The frame passed in must be
-   * the first-in-flow.
-   *
-   * @param aGenConParentFrame an ancestor of the ::before frame
-   * @param aContent the content whose ::before is wanted
-   * @return the ::before frame or nullptr if there isn't one
+   * Returns the ::before pseudo-element for aContent, if any.
    */
-  static nsIFrame* GetBeforeFrameForContent(nsIFrame* aGenConParentFrame,
-                                            const nsIContent* aContent);
-
-  /**
-   * GetBeforeFrame returns the outermost ::before frame of the given frame, if
-   * one exists.  This is typically O(1).  The frame passed in must be
-   * the first-in-flow.
-   *
-   * @param aFrame the frame whose ::before is wanted
-   * @return the :before frame or nullptr if there isn't one
-   */
-  static nsIFrame* GetBeforeFrame(nsIFrame* aFrame);
+  static mozilla::dom::Element* GetBeforePseudo(const nsIContent* aContent);
 
   /**
-   * GetAfterFrameForContent returns the ::after frame for aContent, if one
-   * exists.  This will walk the in-flow chain of aGenConParentFrame to the
-   * last-in-flow if needed.  This function is typically O(N) in the number
-   * of child frames, following in-flows, etc.
-   *
-   * @param aGenConParentFrame an ancestor of the ::after frame
-   * @param aContent the content whose ::after is wanted
-   * @return the ::after frame or nullptr if there isn't one
+   * Returns the frame corresponding to the ::before pseudo-element for
+   * aContent, if any.
    */
-  static nsIFrame* GetAfterFrameForContent(nsIFrame* aGenConParentFrame,
-                                           const nsIContent* aContent);
+  static nsIFrame* GetBeforeFrame(const nsIContent* aContent);
 
   /**
-   * GetAfterFrame returns the outermost ::after frame of the given frame, if one
-   * exists.  This will walk the in-flow chain to the last-in-flow if
-   * needed.  This function is typically O(N) in the number of child
-   * frames, following in-flows, etc.
-   *
-   * @param aFrame the frame whose ::after is wanted
-   * @return the :after frame or nullptr if there isn't one
+   * Returns the ::after pseudo-element for aContent, if any.
    */
-  static nsIFrame* GetAfterFrame(nsIFrame* aFrame);
+  static mozilla::dom::Element* GetAfterPseudo(const nsIContent* aContent);
+
+  /**
+   * Returns the frame corresponding to the ::after pseudo-element for aContent,
+   * if any.
+   */
+  static nsIFrame* GetAfterFrame(const nsIContent* aContent);
 
   /**
    * Given a frame, search up the frame tree until we find an
    * ancestor that (or the frame itself) is of type aFrameType, if any.
    *
    * @param aFrame the frame to start at
    * @param aFrameType the frame type to look for
    * @param aStopAt a frame to stop at after we checked it
--- a/layout/forms/nsGfxCheckboxControlFrame.cpp
+++ b/layout/forms/nsGfxCheckboxControlFrame.cpp
@@ -13,16 +13,76 @@
 #include "nsRenderingContext.h"
 #include "nsIDOMHTMLInputElement.h"
 #include "nsDisplayList.h"
 #include <algorithm>
 
 using namespace mozilla;
 using namespace mozilla::gfx;
 
+#ifdef MOZ_WIDGET_ANDROID
+
+static void
+PaintCheckMark(nsIFrame* aFrame,
+               DrawTarget* aDrawTarget,
+               const nsRect& aDirtyRect,
+               nsPoint aPt)
+{
+  nsRect rect(aPt, aFrame->GetSize());
+  rect.Deflate(aFrame->GetUsedBorderAndPadding());
+
+  // Points come from the coordinates on a 7X7 unit box centered at 0,0
+  const int32_t checkPolygonX[] = { -3, -1,  3,  3, -1, -3 };
+  const int32_t checkPolygonY[] = { -1,  1, -3, -1,  3,  1 };
+  const int32_t checkNumPoints = sizeof(checkPolygonX) / sizeof(int32_t);
+  const int32_t checkSize      = 9; // 2 units of padding on either side
+                                    // of the 7x7 unit checkmark
+
+  // Scale the checkmark based on the smallest dimension
+  nscoord paintScale = std::min(rect.width, rect.height) / checkSize;
+  nsPoint paintCenter(rect.x + rect.width  / 2,
+                      rect.y + rect.height / 2);
+
+  RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder();
+  nsPoint p = paintCenter + nsPoint(checkPolygonX[0] * paintScale,
+                                    checkPolygonY[0] * paintScale);
+
+  int32_t appUnitsPerDevPixel = aFrame->PresContext()->AppUnitsPerDevPixel();
+  builder->MoveTo(NSPointToPoint(p, appUnitsPerDevPixel));
+  for (int32_t polyIndex = 1; polyIndex < checkNumPoints; polyIndex++) {
+    p = paintCenter + nsPoint(checkPolygonX[polyIndex] * paintScale,
+                              checkPolygonY[polyIndex] * paintScale);
+    builder->LineTo(NSPointToPoint(p, appUnitsPerDevPixel));
+  }
+  RefPtr<Path> path = builder->Finish();
+  aDrawTarget->Fill(path,
+                    ColorPattern(ToDeviceColor(aFrame->StyleColor()->mColor)));
+}
+
+static void
+PaintIndeterminateMark(nsIFrame* aFrame,
+                       DrawTarget* aDrawTarget,
+                       const nsRect& aDirtyRect,
+                       nsPoint aPt)
+{
+  int32_t appUnitsPerDevPixel = aFrame->PresContext()->AppUnitsPerDevPixel();
+
+  nsRect rect(aPt, aFrame->GetSize());
+  rect.Deflate(aFrame->GetUsedBorderAndPadding());
+  rect.y += (rect.height - rect.height/4) / 2;
+  rect.height /= 4;
+
+  Rect devPxRect = NSRectToSnappedRect(rect, appUnitsPerDevPixel, *aDrawTarget);
+
+  aDrawTarget->FillRect(
+    devPxRect, ColorPattern(ToDeviceColor(aFrame->StyleColor()->mColor)));
+}
+
+#endif
+
 //------------------------------------------------------------
 nsIFrame*
 NS_NewGfxCheckboxControlFrame(nsIPresShell* aPresShell,
                               nsStyleContext* aContext)
 {
   return new (aPresShell) nsGfxCheckboxControlFrame(aContext);
 }
 
@@ -42,8 +102,53 @@ nsGfxCheckboxControlFrame::~nsGfxCheckbo
 
 #ifdef ACCESSIBILITY
 a11y::AccType
 nsGfxCheckboxControlFrame::AccessibleType()
 {
   return a11y::eHTMLCheckboxType;
 }
 #endif
+
+//------------------------------------------------------------
+#ifdef MOZ_WIDGET_ANDROID
+
+void
+nsGfxCheckboxControlFrame::BuildDisplayList(nsDisplayListBuilder*   aBuilder,
+                                            const nsRect&           aDirtyRect,
+                                            const nsDisplayListSet& aLists)
+{
+  nsFormControlFrame::BuildDisplayList(aBuilder, aDirtyRect, aLists);
+  
+  // Get current checked state through content model.
+  if ((!IsChecked() && !IsIndeterminate()) || !IsVisibleForPainting(aBuilder))
+    return;   // we're not checked or not visible, nothing to paint.
+    
+  if (IsThemed())
+    return; // No need to paint the checkmark. The theme will do it.
+
+  aLists.Content()->AppendNewToTop(new (aBuilder)
+    nsDisplayGeneric(aBuilder, this,
+                     IsIndeterminate()
+                     ? PaintIndeterminateMark : PaintCheckMark,
+                     "CheckedCheckbox",
+                     nsDisplayItem::TYPE_CHECKED_CHECKBOX));
+}
+
+#endif
+//------------------------------------------------------------
+bool
+nsGfxCheckboxControlFrame::IsChecked()
+{
+  nsCOMPtr<nsIDOMHTMLInputElement> elem(do_QueryInterface(mContent));
+  bool retval = false;
+  elem->GetChecked(&retval);
+  return retval;
+}
+
+bool
+nsGfxCheckboxControlFrame::IsIndeterminate()
+{
+  nsCOMPtr<nsIDOMHTMLInputElement> elem(do_QueryInterface(mContent));
+  bool retval = false;
+  elem->GetIndeterminate(&retval);
+  return retval;
+}
--- a/layout/forms/nsGfxCheckboxControlFrame.h
+++ b/layout/forms/nsGfxCheckboxControlFrame.h
@@ -17,15 +17,26 @@ public:
   virtual ~nsGfxCheckboxControlFrame();
 
 #ifdef DEBUG_FRAME_DUMP
   virtual nsresult GetFrameName(nsAString& aResult) const override {
     return MakeFrameName(NS_LITERAL_STRING("CheckboxControl"), aResult);
   }
 #endif
 
+#ifdef MOZ_WIDGET_ANDROID
+  virtual void BuildDisplayList(nsDisplayListBuilder*   aBuilder,
+                                const nsRect&           aDirtyRect,
+                                const nsDisplayListSet& aLists) override;
+#endif
+
 #ifdef ACCESSIBILITY
   virtual mozilla::a11y::AccType AccessibleType() override;
 #endif
+
+protected:
+
+  bool IsChecked();
+  bool IsIndeterminate();
 };
 
 #endif
 
--- a/layout/forms/nsGfxRadioControlFrame.cpp
+++ b/layout/forms/nsGfxRadioControlFrame.cpp
@@ -35,8 +35,63 @@ nsGfxRadioControlFrame::~nsGfxRadioContr
 
 #ifdef ACCESSIBILITY
 a11y::AccType
 nsGfxRadioControlFrame::AccessibleType()
 {
   return a11y::eHTMLRadioButtonType;
 }
 #endif
+
+#ifdef MOZ_WIDGET_ANDROID
+
+//--------------------------------------------------------------
+// Draw the dot for a non-native radio button in the checked state.
+static void
+PaintCheckedRadioButton(nsIFrame* aFrame,
+                        DrawTarget* aDrawTarget,
+                        const nsRect& aDirtyRect,
+                        nsPoint aPt)
+{
+  // The dot is an ellipse 2px on all sides smaller than the content-box,
+  // drawn in the foreground color.
+  nsRect rect(aPt, aFrame->GetSize());
+  rect.Deflate(aFrame->GetUsedBorderAndPadding());
+  rect.Deflate(nsPresContext::CSSPixelsToAppUnits(2),
+               nsPresContext::CSSPixelsToAppUnits(2));
+
+  Rect devPxRect =
+    ToRect(nsLayoutUtils::RectToGfxRect(rect,
+                                        aFrame->PresContext()->AppUnitsPerDevPixel()));
+
+  ColorPattern color(ToDeviceColor(aFrame->StyleColor()->mColor));
+
+  RefPtr<PathBuilder> builder = aDrawTarget->CreatePathBuilder();
+  AppendEllipseToPath(builder, devPxRect.Center(), devPxRect.Size());
+  RefPtr<Path> ellipse = builder->Finish();
+  aDrawTarget->Fill(ellipse, color);
+}
+
+void
+nsGfxRadioControlFrame::BuildDisplayList(nsDisplayListBuilder*   aBuilder,
+                                         const nsRect&           aDirtyRect,
+                                         const nsDisplayListSet& aLists)
+{
+  nsFormControlFrame::BuildDisplayList(aBuilder, aDirtyRect, aLists);
+
+  if (!IsVisibleForPainting(aBuilder))
+    return;
+  
+  if (IsThemed())
+    return; // The theme will paint the check, if any.
+
+  bool checked = true;
+  GetCurrentCheckState(&checked); // Get check state from the content model
+  if (!checked)
+    return;
+    
+  aLists.Content()->AppendNewToTop(new (aBuilder)
+    nsDisplayGeneric(aBuilder, this, PaintCheckedRadioButton,
+                     "CheckedRadioButton",
+                     nsDisplayItem::TYPE_CHECKED_RADIOBUTTON));
+}
+
+#endif
--- a/layout/forms/nsGfxRadioControlFrame.h
+++ b/layout/forms/nsGfxRadioControlFrame.h
@@ -18,11 +18,17 @@ public:
   explicit nsGfxRadioControlFrame(nsStyleContext* aContext);
   ~nsGfxRadioControlFrame();
 
   NS_DECL_FRAMEARENA_HELPERS
 
 #ifdef ACCESSIBILITY
   virtual mozilla::a11y::AccType AccessibleType() override;
 #endif
+
+#ifdef MOZ_WIDGET_ANDROID
+  virtual void BuildDisplayList(nsDisplayListBuilder*   aBuilder,
+                                const nsRect&           aDirtyRect,
+                                const nsDisplayListSet& aLists) override;
+#endif
 };
 
 #endif
--- a/layout/generic/Selection.h
+++ b/layout/generic/Selection.h
@@ -173,17 +173,17 @@ public:
   nsINode*     GetFocusNode();
   uint32_t     FocusOffset();
 
   bool IsCollapsed() const;
 
   // *JS() methods are mapped to Selection.*().
   // They may move focus only when the range represents normal selection.
   // These methods shouldn't be used by non-JS callers.
-  void CollapseJS(nsINode& aNode, uint32_t aOffset,
+  void CollapseJS(nsINode* aNode, uint32_t aOffset,
                   mozilla::ErrorResult& aRv);
   void CollapseToStartJS(mozilla::ErrorResult& aRv);
   void CollapseToEndJS(mozilla::ErrorResult& aRv);
 
   void ExtendJS(nsINode& aNode, uint32_t aOffset,
                 mozilla::ErrorResult& aRv);
 
   void SelectAllChildrenJS(nsINode& aNode, mozilla::ErrorResult& aRv);
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -10360,29 +10360,26 @@ nsIFrame::IsPseudoStackingContextFromSty
   return disp->IsAbsPosContainingBlock(this) ||
          disp->IsFloating(this) ||
          (disp->mWillChangeBitField & NS_STYLE_WILL_CHANGE_STACKING_CONTEXT);
 }
 
 Element*
 nsIFrame::GetPseudoElement(CSSPseudoElementType aType)
 {
-  nsIFrame* frame = nullptr;
+  if (!mContent) {
+    return nullptr;
+  }
 
   if (aType == CSSPseudoElementType::before) {
-    frame = nsLayoutUtils::GetBeforeFrame(this);
-  } else if (aType == CSSPseudoElementType::after) {
-    frame = nsLayoutUtils::GetAfterFrame(this);
-  }
-
-  if (frame) {
-    nsIContent* content = frame->GetContent();
-    if (content->IsElement()) {
-      return content->AsElement();
-    }
+    return nsLayoutUtils::GetBeforePseudo(mContent);
+  }
+
+  if (aType == CSSPseudoElementType::after) {
+    return nsLayoutUtils::GetAfterPseudo(mContent);
   }
 
   return nullptr;
 }
 
 static bool
 IsFrameScrolledOutOfView(nsIFrame *aFrame)
 {
--- a/layout/generic/nsIFrame.h
+++ b/layout/generic/nsIFrame.h
@@ -1175,20 +1175,16 @@ public:
     return GetBidiData().baseLevel;
   }
 
   nsBidiLevel GetEmbeddingLevel()
   {
     return GetBidiData().embeddingLevel;
   }
 
-  nsTArray<nsIContent*>* GetGenConPseudos() {
-    return Properties().Get(GenConProperty());
-  }
-
   /**
    * Return the distance between the border edge of the frame and the
    * margin edge of the frame.  Like GetRect(), returns the dimensions
    * as of the most recent reflow.
    *
    * This doesn't include any margin collapsing that may have occurred.
    *
    * It also treats 'auto' margins as zero, and treats any margins that
--- a/layout/generic/nsSelection.cpp
+++ b/layout/generic/nsSelection.cpp
@@ -5167,21 +5167,25 @@ Selection::Collapse(nsIDOMNode* aParentN
 
 NS_IMETHODIMP
 Selection::CollapseNative(nsINode* aParentNode, int32_t aOffset)
 {
   return Collapse(aParentNode, aOffset);
 }
 
 void
-Selection::CollapseJS(nsINode& aNode, uint32_t aOffset, ErrorResult& aRv)
+Selection::CollapseJS(nsINode* aNode, uint32_t aOffset, ErrorResult& aRv)
 {
   AutoRestore<bool> calledFromJSRestorer(mCalledByJS);
   mCalledByJS = true;
-  Collapse(aNode, aOffset, aRv);
+  if (!aNode) {
+    RemoveAllRanges(aRv);
+    return;
+  }
+  Collapse(*aNode, aOffset, aRv);
 }
 
 nsresult
 Selection::Collapse(nsINode* aParentNode, int32_t aOffset)
 {
   if (!aParentNode)
     return NS_ERROR_INVALID_ARG;
 
--- a/layout/painting/nsImageRenderer.cpp
+++ b/layout/painting/nsImageRenderer.cpp
@@ -143,17 +143,16 @@ nsImageRenderer::PrepareImage()
 
       if (!mImage->GetCropRect()) {
         mImageContainer.swap(srcImage);
       } else {
         nsIntRect actualCropRect;
         bool isEntireImage;
         bool success =
           mImage->ComputeActualCropRect(actualCropRect, &isEntireImage);
-        NS_ASSERTION(success, "ComputeActualCropRect() should not fail here");
         if (!success || actualCropRect.IsEmpty()) {
           // The cropped image has zero size
           mPrepareResult = DrawResult::BAD_IMAGE;
           return false;
         }
         if (isEntireImage) {
           // The cropped image is identical to the source image
           mImageContainer.swap(srcImage);
--- a/layout/reftests/flexbox/flexbox-align-self-baseline-horiz-3-ref.xhtml
+++ b/layout/reftests/flexbox/flexbox-align-self-baseline-horiz-3-ref.xhtml
@@ -55,13 +55,10 @@
       </table
       ><table cellspacing="0" cellpadding="0">
         <fieldset><legend>leg</legend>field<br/>set</fieldset>
       </table
       ><table cellspacing="0" cellpadding="0">
         <fieldset><legend>leg<br/>end</legend>field<br/>set</fieldset>
       </table>
     </div>
-    <div class="flexbox" style="font-size:0"><input type="radio"
-     /><input type="checkbox"
-     /><input type="checkbox" class="ref"/></div>
   </body>
 </html>
--- a/layout/reftests/flexbox/flexbox-align-self-baseline-horiz-3.xhtml
+++ b/layout/reftests/flexbox/flexbox-align-self-baseline-horiz-3.xhtml
@@ -39,15 +39,10 @@
       <div class="lime">text</div>
       <button>btn</button>
       <label class="pink">label</label>
       <label class="aqua">lab<br/>el</label>
       <fieldset>field<br/>set</fieldset>
       <fieldset><legend>leg</legend>field<br/>set</fieldset>
       <fieldset><legend>leg<br/>end</legend>field<br/>set</fieldset>
     </div>
-    <div class="flexbox" style="font-size:0">
-      <input type="radio"/>
-      <input type="checkbox"/>
-      <i></i>
-    </div>
   </body>
 </html>
--- a/layout/reftests/forms/input/checkbox/reftest.list
+++ b/layout/reftests/forms/input/checkbox/reftest.list
@@ -1,17 +1,17 @@
 == label-dynamic.html label-dynamic-ref.html
-fails-if(Android) == radio-stretched.html radio-stretched-ref.html # test for bug 464589
+== radio-stretched.html radio-stretched-ref.html # test for bug 464589
 != checked-native.html checked-native-notref.html
 fails-if(Android) == checked-appearance-none.html about:blank
 fails-if(Android) == unchecked-appearance-none.html about:blank
 != checked-native.html about:blank
 != checked-native-notref.html about:blank
 fails-if(Android) == indeterminate-checked.html about:blank
 fails-if(Android) == indeterminate-checked-notref.html about:blank
 fails-if(Android) == indeterminate-unchecked.html about:blank
 != indeterminate-native-checked.html indeterminate-native-checked-notref.html
 != indeterminate-native-unchecked.html indeterminate-native-unchecked-notref.html
 == indeterminate-selector.html indeterminate-selector-ref.html
 skip-if(!gtkWidget) == gtk-theme-width-height.html gtk-theme-width-height-ref.html
 skip-if(Android) == checkbox-baseline.html checkbox-baseline-ref.html # skip-if(Android) because Android use appearance:none by default for checkbox/radio.
 skip-if(Android) == checkbox-radio-color.html checkbox-radio-color-ref.html # skip-if(Android) because Android use appearance:none by default for checkbox/radio.
-skip-if(!Android) == checkbox-radio-auto-sized.html checkbox-radio-auto-sized-ref.html
+skip == checkbox-radio-auto-sized.html checkbox-radio-auto-sized-ref.html # Disabled until bug 1352238 is fixed.
--- a/layout/reftests/forms/select/reftest-stylo.list
+++ b/layout/reftests/forms/select/reftest-stylo.list
@@ -4,11 +4,11 @@ fails == multiple.html multiple.html
 fails == boguskids.html boguskids.html
 fails == dynamic-boguskids.html dynamic-boguskids.html
 fails == option-children.html option-children.html
 fuzzy(1,4) == padding-button-placement.html padding-button-placement.html
 HTTP(../..) == vertical-centering.html vertical-centering.html # Bug 1290237
 fails == 997709-2.html 997709-2.html
 needs-focus == focusring-1.html focusring-1.html
 needs-focus == focusring-2.html focusring-2.html
-fails needs-focus == focusring-3.html focusring-3.html
+needs-focus == focusring-3.html focusring-3.html
 fails == dynamic-text-indent-1.html dynamic-text-indent-1.html
 fails == dynamic-text-overflow-1.html dynamic-text-overflow-1.html
--- a/layout/reftests/image-rect/reftest-stylo.list
+++ b/layout/reftests/image-rect/reftest-stylo.list
@@ -1,15 +1,15 @@
 # DO NOT EDIT! This is a auto-generated temporary list for Stylo testing
 == background-common-usage-floating-point.html background-common-usage-floating-point.html
 == background-common-usage-percent.html background-common-usage-percent.html
 == background-common-usage-pixel.html background-common-usage-pixel.html
 == background-draw-nothing-empty-rect.html background-draw-nothing-empty-rect.html
 == background-draw-nothing-invalid-syntax.html background-draw-nothing-invalid-syntax.html
-asserts(0-8) == background-draw-nothing-malformed-images.html background-draw-nothing-malformed-images.html
+== background-draw-nothing-malformed-images.html background-draw-nothing-malformed-images.html
 == background-monster-rect.html background-monster-rect.html
 == background-over-size-rect.html background-over-size-rect.html
 fails == background-test-parser.html background-test-parser.html
 == background-with-other-properties.html background-with-other-properties.html
 == background-zoom-1.html background-zoom-1.html
 == background-zoom-2.html background-zoom-2.html
 == background-zoom-3.html background-zoom-3.html
 == background-zoom-4.html background-zoom-4.html
--- a/layout/reftests/image-rect/reftest.list
+++ b/layout/reftests/image-rect/reftest.list
@@ -1,14 +1,14 @@
 == background-common-usage-floating-point.html background-common-usage-ref.html
 == background-common-usage-percent.html background-common-usage-ref.html
 == background-common-usage-pixel.html background-common-usage-ref.html
 == background-draw-nothing-empty-rect.html background-draw-nothing-ref.html
 == background-draw-nothing-invalid-syntax.html background-draw-nothing-ref.html
-asserts(0-6) == background-draw-nothing-malformed-images.html background-draw-nothing-ref.html # Bug 576419
+== background-draw-nothing-malformed-images.html background-draw-nothing-ref.html
 == background-monster-rect.html background-monster-rect-ref.html
 == background-over-size-rect.html background-over-size-rect-ref.html
 == background-test-parser.html background-test-parser-ref.html
 fuzzy-if(Android,113,124) == background-with-other-properties.html background-with-other-properties-ref.html
 fuzzy-if(Android,16,22) == background-zoom-1.html background-zoom-1-ref.html  # Bug 1128229
 == background-zoom-2.html background-zoom-2-ref.html
 == background-zoom-3.html background-zoom-3-ref.html
 == background-zoom-4.html background-zoom-4-ref.html
--- a/layout/reftests/printing/reftest-stylo.list
+++ b/layout/reftests/printing/reftest-stylo.list
@@ -4,27 +4,27 @@
 
 # Bugs
 == 272830-1.html 272830-1.html
 == 318022-1.html 318022-1.html
 == 403669-1.html 403669-1.html
 == 381497-n.html 381497-n.html
 == test-async-print.html test-async-print.html
 == 129941-1a.html 129941-1a.html
-fails == 129941-1b.html 129941-1b.html
+== 129941-1b.html 129941-1b.html
 == 609227-1.html 609227-1.html
 == 609227-2a.html 609227-2a.html
 == 609227-2b.html 609227-2b.html
 == 577450-1.html 577450-1.html
 == 626395-1a.html 626395-1a.html
-fails == 626395-1b.html 626395-1b.html
+== 626395-1b.html 626395-1b.html
 == 626395-2a.html 626395-2a.html
-fails == 626395-2b.html 626395-2b.html
+== 626395-2b.html 626395-2b.html
 == 626395-2c.html 626395-2c.html
-fails == 626395-2d.html 626395-2d.html
+== 626395-2d.html 626395-2d.html
 == 652178-1.html 652178-1.html
 fails == 115199-1.html 115199-1.html
 == 115199-2a.html 115199-2a.html
 == 115199-2b.html 115199-2b.html
 == 652178-1.html 652178-1.html
 == 745025-1.html 745025-1.html
 fails == 820496-1.html 820496-1.html
 
--- a/layout/reftests/table-overflow/reftest-stylo.list
+++ b/layout/reftests/table-overflow/reftest-stylo.list
@@ -1,7 +1,7 @@
 # DO NOT EDIT! This is a auto-generated temporary list for Stylo testing
-fails == bug785684-x.html bug785684-x.html
-fails == bug785684-y.html bug785684-y.html
+== bug785684-x.html bug785684-x.html
+== bug785684-y.html bug785684-y.html
 == table-row-pagination.html table-row-pagination.html
 == 963441.html 963441.html
 fails == table-caption-scroll.html table-caption-scroll.html
 == table-cell-block-overflow.html table-cell-block-overflow.html
--- a/layout/reftests/text-overflow/reftest-stylo.list
+++ b/layout/reftests/text-overflow/reftest-stylo.list
@@ -19,17 +19,17 @@ HTTP(..) == marker-shadow.html marker-sh
 == aligned-baseline.html aligned-baseline.html
 == clipped-elements.html clipped-elements.html
 HTTP(..) == theme-overflow.html theme-overflow.html
 HTTP(..) == table-cell.html table-cell.html
 fails == two-value-syntax.html two-value-syntax.html
 fails HTTP(..) == single-value.html single-value.html
 == atomic-under-marker.html atomic-under-marker.html
 fuzzy(1,2616) skip-if(Android) fuzzy-if(asyncPan&&!layersGPUAccelerated,102,12352) fails-if(http.oscpu=="Linux\u0020x86_64") HTTP(..) == xulscroll.html xulscroll.html
-fails HTTP(..) == combobox-zoom.html combobox-zoom.html
+HTTP(..) == combobox-zoom.html combobox-zoom.html
 == dynamic-change-1.html dynamic-change-1.html
 
 # The vertical-text pref setting can be removed after bug 1138384 lands
 fails fails == vertical-decorations-1.html vertical-decorations-1.html
 fails fails == vertical-decorations-2.html vertical-decorations-2.html
 fails fails == vertical-decorations-1.html vertical-decorations-1.html
 fails fails == vertical-decorations-2.html vertical-decorations-2.html
 == vertical-decorations-3.html vertical-decorations-3.html
--- a/layout/reftests/writing-mode/reftest.list
+++ b/layout/reftests/writing-mode/reftest.list
@@ -100,17 +100,17 @@ HTTP(..) == 1127488-align-default-vertic
 HTTP(..) == 1127488-align-start-vertical-lr-ltr.html 1127488-align-top-left-ref.html
 HTTP(..) == 1127488-align-end-vertical-lr-ltr.html 1127488-align-bottom-left-ref.html
 HTTP(..) == 1127488-align-left-vertical-lr-ltr.html 1127488-align-top-left-ref.html
 HTTP(..) == 1127488-align-right-vertical-lr-ltr.html 1127488-align-bottom-left-ref.html
 == 1130907-intrinsic-sizing-1.html 1130907-intrinsic-sizing-1-ref.html
 == 1130907-intrinsic-sizing-2.html 1130907-intrinsic-sizing-2-ref.html
 == 1131013-vertical-bidi.html 1131013-vertical-bidi-ref.html
 == 1133945-1-vertical-align.html 1133945-1-vertical-align-ref.html
-== 1134744-radio-checkbox-baseline-1.html 1134744-radio-checkbox-baseline-1-ref.html
+skip-if(Android) == 1134744-radio-checkbox-baseline-1.html 1134744-radio-checkbox-baseline-1-ref.html # Disabled on Android until bug 1352238 is fixed.
 == 1134849-orthogonal-inline.html 1134849-orthogonal-inline-ref.html
 == 1135361-ruby-justify-1.html 1135361-ruby-justify-1-ref.html # bug 1136067
 fuzzy-if(winWidget,255,163) fuzzy-if(skiaContent,159,111) == 1136557-1-nested-spans.html 1136557-1-nested-spans-ref.html
 fuzzy-if(winWidget,255,221) fuzzy-if(skiaContent,159,111) == 1136557-2-nested-spans.html 1136557-2-nested-spans-ref.html
 fuzzy-if(winWidget,255,236) == 1136557-3-nested-spans.html 1136557-3-nested-spans-ref.html
 == 1138356-1-button-contents-alignment.html 1138356-1-button-contents-alignment-ref.html
 != 1138356-2-button-contents-alignment.html 1138356-2-button-contents-alignment-notref.html
 
--- a/layout/style/jar.mn
+++ b/layout/style/jar.mn
@@ -26,13 +26,10 @@ toolkit.jar:
    res/accessiblecaret-tilt-left@1x.png      (res/accessiblecaret-tilt-left@1x.png)
    res/accessiblecaret-tilt-left@1.5x.png    (res/accessiblecaret-tilt-left@1.5x.png)
    res/accessiblecaret-tilt-left@2x.png      (res/accessiblecaret-tilt-left@2x.png)
    res/accessiblecaret-tilt-left@2.25x.png   (res/accessiblecaret-tilt-left@2.25x.png)
    res/accessiblecaret-tilt-right@1x.png     (res/accessiblecaret-tilt-right@1x.png)
    res/accessiblecaret-tilt-right@1.5x.png   (res/accessiblecaret-tilt-right@1.5x.png)
    res/accessiblecaret-tilt-right@2x.png     (res/accessiblecaret-tilt-right@2x.png)
    res/accessiblecaret-tilt-right@2.25x.png  (res/accessiblecaret-tilt-right@2.25x.png)
-   res/checkmark.svg                         (res/checkmark.svg)
-   res/indeterminate-checkmark.svg           (res/indeterminate-checkmark.svg)
-   res/radio.svg                             (res/radio.svg)
 
 % resource gre-resources %res/
--- a/layout/style/nsCSSParser.cpp
+++ b/layout/style/nsCSSParser.cpp
@@ -10218,16 +10218,17 @@ CSSParserImpl::ParseLinearGradient(nsCSS
   // back the first token (which we may have checked for "to" above) and try to
   // parse expression as <legacy-gradient-line>? <color-stop-list>
   bool haveGradientLine = IsLegacyGradientLine(mToken.mType, mToken.mIdent);
   UngetToken();
 
   if (haveGradientLine) {
     // Parse a <legacy-gradient-line>
     cssGradient->mIsLegacySyntax = true;
+    cssGradient->mIsMozLegacySyntax = (aFlags & eGradient_MozLegacy);
     // In -webkit-linear-gradient expressions (handled below), we need to accept
     // unitless 0 for angles, to match WebKit/Blink.
     int32_t angleFlags = (aFlags & eGradient_WebkitLegacy) ?
       VARIANT_ANGLE | VARIANT_ZERO_ANGLE :
       VARIANT_ANGLE;
 
     bool haveAngle =
       ParseSingleTokenVariant(cssGradient->mAngle, angleFlags, nullptr);
@@ -10414,16 +10415,17 @@ CSSParserImpl::ParseRadialGradient(nsCSS
            !ExpectSymbol(',', true))) {
         SkipUntil(')');
         return false;
       }
     }
 
     if (cssGradient->mAngle.GetUnit() != eCSSUnit_None) {
       cssGradient->mIsLegacySyntax = true;
+      cssGradient->mIsMozLegacySyntax = (aFlags & eGradient_MozLegacy);
     }
   }
 
   // radial gradients might have a shape and size here for legacy syntax
   if (!haveShape && !haveSize) {
     haveShape =
       ParseSingleTokenVariant(cssGradient->GetRadialShape(), VARIANT_KEYWORD,
                               nsCSSProps::kRadialGradientShapeKTable);
--- a/layout/style/nsCSSValue.cpp
+++ b/layout/style/nsCSSValue.cpp
@@ -1756,17 +1756,21 @@ nsCSSValue::AppendToString(nsCSSProperty
   }
   else if (eCSSUnit_Percent < unit) {  // length unit
     aResult.AppendFloat(GetFloatValue());
   }
   else if (eCSSUnit_Gradient == unit) {
     nsCSSValueGradient* gradient = GetGradientValue();
 
     if (gradient->mIsLegacySyntax) {
-      aResult.AppendLiteral("-moz-");
+      if (gradient->mIsMozLegacySyntax) {
+        aResult.AppendLiteral("-moz-");
+      } else {
+        aResult.AppendLiteral("-webkit-");
+      }
     }
     if (gradient->mIsRepeating) {
       aResult.AppendLiteral("repeating-");
     }
     if (gradient->mIsRadial) {
       aResult.AppendLiteral("radial-gradient(");
     } else {
       aResult.AppendLiteral("linear-gradient(");
@@ -1808,31 +1812,39 @@ nsCSSValue::AppendToString(nsCSSProperty
         if (gradient->GetRadiusY().GetUnit() != eCSSUnit_None) {
           aResult.Append(' ');
           gradient->GetRadiusY().AppendToString(aProperty, aResult,
                                                 aSerialization);
         }
         needSep = true;
       }
     }
-    if (!gradient->mIsRadial && !gradient->mIsLegacySyntax) {
+    if (!gradient->mIsRadial &&
+        !(gradient->mIsLegacySyntax && gradient->mIsMozLegacySyntax)) {
       if (gradient->mBgPos.mXValue.GetUnit() != eCSSUnit_None ||
           gradient->mBgPos.mYValue.GetUnit() != eCSSUnit_None) {
         MOZ_ASSERT(gradient->mAngle.GetUnit() == eCSSUnit_None);
         MOZ_ASSERT(gradient->mBgPos.mXValue.GetUnit() == eCSSUnit_Enumerated &&
                    gradient->mBgPos.mYValue.GetUnit() == eCSSUnit_Enumerated,
                    "unexpected unit");
-        aResult.AppendLiteral("to");
+        if (!gradient->mIsLegacySyntax) {
+          aResult.AppendLiteral("to ");
+        }
+        bool didAppendX = false;
         if (!(gradient->mBgPos.mXValue.GetIntValue() & NS_STYLE_IMAGELAYER_POSITION_CENTER)) {
-          aResult.Append(' ');
           gradient->mBgPos.mXValue.AppendToString(eCSSProperty_background_position_x,
                                                   aResult, aSerialization);
+          didAppendX = true;
         }
         if (!(gradient->mBgPos.mYValue.GetIntValue() & NS_STYLE_IMAGELAYER_POSITION_CENTER)) {
-          aResult.Append(' ');
+          if (didAppendX) {
+            // We're appending both an x-keyword and a y-keyword.
+            // Add a space between them here.
+            aResult.Append(' ');
+          }
           gradient->mBgPos.mYValue.AppendToString(eCSSProperty_background_position_y,
                                                   aResult, aSerialization);
         }
         needSep = true;
       } else if (gradient->mAngle.GetUnit() != eCSSUnit_None) {
         gradient->mAngle.AppendToString(aProperty, aResult, aSerialization);
         needSep = true;
       }
@@ -3109,16 +3121,17 @@ nsCSSValueGradientStop::SizeOfExcludingT
   return n;
 }
 
 nsCSSValueGradient::nsCSSValueGradient(bool aIsRadial,
                                        bool aIsRepeating)
   : mIsRadial(aIsRadial),
     mIsRepeating(aIsRepeating),
     mIsLegacySyntax(false),
+    mIsMozLegacySyntax(false),
     mIsExplicitSize(false),
     mBgPos(eCSSUnit_None),
     mAngle(eCSSUnit_None)
 {
   mRadialValues[0].SetNoneValue();
   mRadialValues[1].SetNoneValue();
 }
 
--- a/layout/style/nsCSSValue.h
+++ b/layout/style/nsCSSValue.h
@@ -1626,17 +1626,21 @@ public:
 };
 
 struct nsCSSValueGradient final {
   nsCSSValueGradient(bool aIsRadial, bool aIsRepeating);
 
   // true if gradient is radial, false if it is linear
   bool mIsRadial;
   bool mIsRepeating;
-  bool mIsLegacySyntax;
+  bool mIsLegacySyntax; // If true, serialization should use a vendor prefix.
+  // XXXdholbert This will hopefully be going away soon, if bug 1337655 sticks:
+  bool mIsMozLegacySyntax; // (Only makes sense when mIsLegacySyntax is true.)
+                           // If true, serialization should use -moz prefix.
+                           // Else, serialization should use -webkit prefix.
   bool mIsExplicitSize;
   // line position and angle
   nsCSSValuePair mBgPos;
   nsCSSValue mAngle;
 
   // Only meaningful if mIsRadial is true
 private:
   nsCSSValue mRadialValues[2];
@@ -1684,16 +1688,17 @@ public:
 
   InfallibleTArray<nsCSSValueGradientStop> mStops;
 
   bool operator==(const nsCSSValueGradient& aOther) const
   {
     if (mIsRadial != aOther.mIsRadial ||
         mIsRepeating != aOther.mIsRepeating ||
         mIsLegacySyntax != aOther.mIsLegacySyntax ||
+        mIsMozLegacySyntax != aOther.mIsMozLegacySyntax ||
         mIsExplicitSize != aOther.mIsExplicitSize ||
         mBgPos != aOther.mBgPos ||
         mAngle != aOther.mAngle ||
         mRadialValues[0] != aOther.mRadialValues[0] ||
         mRadialValues[1] != aOther.mRadialValues[1])
       return false;
 
     if (mStops.Length() != aOther.mStops.Length())
--- a/layout/style/nsComputedDOMStyle.cpp
+++ b/layout/style/nsComputedDOMStyle.cpp
@@ -710,16 +710,44 @@ nsComputedDOMStyle::SetResolvedStyleCont
 
 void
 nsComputedDOMStyle::SetFrameStyleContext(nsStyleContext* aContext)
 {
   ClearStyleContext();
   mStyleContext = aContext;
 }
 
+/**
+ * The following function checks whether we need to explicitly resolve the style
+ * again, even though we have a style context coming from the frame.
+ *
+ * This basically checks whether the style is or may be under a ::first-line or
+ * ::first-letter frame, in which case we can't return the frame style, and we
+ * need to resolve it. See bug 505515.
+ */
+static bool
+MustReresolveStyle(const nsStyleContext* aContext)
+{
+  MOZ_ASSERT(aContext);
+
+  if (aContext->HasPseudoElementData()) {
+    if (!aContext->GetPseudo() ||
+        aContext->StyleSource().IsServoComputedValues()) {
+      // TODO(emilio): When ::first-line is supported in Servo, we may want to
+      // fix this to avoid re-resolving pseudo-element styles.
+      return true;
+    }
+
+    return aContext->GetParent() &&
+           aContext->GetParent()->HasPseudoElementData();
+  }
+
+  return false;
+}
+
 void
 nsComputedDOMStyle::UpdateCurrentStyleSources(bool aNeedsLayoutFlush)
 {
   nsCOMPtr<nsIDocument> document = do_QueryReferent(mDocumentWeak);
   if (!document) {
     ClearStyleContext();
     return;
   }
@@ -753,18 +781,31 @@ nsComputedDOMStyle::UpdateCurrentStyleSo
     // We've processed some restyles, so the cached style context might
     // be out of date.
     mStyleContext = nullptr;
   }
 
   // XXX the !mContent->IsHTMLElement(nsGkAtoms::area)
   // check is needed due to bug 135040 (to avoid using
   // mPrimaryFrame). Remove it once that's fixed.
-  if (!mPseudo && !mContent->IsHTMLElement(nsGkAtoms::area)) {
-    mOuterFrame = mContent->GetPrimaryFrame();
+  if (!mContent->IsHTMLElement(nsGkAtoms::area)) {
+    mOuterFrame = nullptr;
+
+    if (!mPseudo) {
+      mOuterFrame = mContent->GetPrimaryFrame();
+    } else if (mPseudo == nsCSSPseudoElements::before ||
+               mPseudo == nsCSSPseudoElements::after) {
+      nsIAtom* property = mPseudo == nsCSSPseudoElements::before
+                            ? nsGkAtoms::beforePseudoProperty
+                            : nsGkAtoms::afterPseudoProperty;
+
+      auto* pseudo = static_cast<Element*>(mContent->GetProperty(property));
+      mOuterFrame = pseudo ? pseudo->GetPrimaryFrame() : nullptr;
+    }
+
     mInnerFrame = mOuterFrame;
     if (mOuterFrame) {
       nsIAtom* type = mOuterFrame->GetType();
       if (type == nsGkAtoms::tableWrapperFrame) {
         // If the frame is a table wrapper frame then we should get the style
         // from the inner table frame.
         mInnerFrame = mOuterFrame->PrincipalChildList().FirstChild();
         NS_ASSERTION(mInnerFrame, "table wrapper must have an inner");
@@ -773,17 +814,17 @@ nsComputedDOMStyle::UpdateCurrentStyleSo
                      "the inner table");
       }
 
       SetFrameStyleContext(mInnerFrame->StyleContext());
       NS_ASSERTION(mStyleContext, "Frame without style context?");
     }
   }
 
-  if (!mStyleContext || mStyleContext->HasPseudoElementData()) {
+  if (!mStyleContext || MustReresolveStyle(mStyleContext)) {
 #ifdef DEBUG
     if (mStyleContext && mStyleContext->StyleSource().IsGeckoRuleNodeOrNull()) {
       // We want to check that going through this path because of
       // HasPseudoElementData is rare, because it slows us down a good
       // bit.  So check that we're really inside something associated
       // with a pseudo-element that contains elements.  (We also allow
       // the element to be NAC, just in case some chrome JS calls
       // getComputedStyle on a NAC-implemented pseudo.)
@@ -2014,54 +2055,75 @@ AppendCSSGradientLength(const nsStyleCoo
   aString.Append(tokenString);
 }
 
 static void
 AppendCSSGradientToBoxPosition(const nsStyleGradient* aGradient,
                                nsAString&             aString,
                                bool&                  aNeedSep)
 {
+  // This function only supports box position keywords. Make sure we're not
+  // calling it with inputs that would have coordinates that aren't
+  // representable with box-position keywords.
+  MOZ_ASSERT(aGradient->mShape == NS_STYLE_GRADIENT_SHAPE_LINEAR &&
+             !(aGradient->mLegacySyntax && aGradient->mMozLegacySyntax),
+             "Only call me for linear-gradient and -webkit-linear-gradient");
+
   float xValue = aGradient->mBgPosX.GetPercentValue();
   float yValue = aGradient->mBgPosY.GetPercentValue();
 
   if (yValue == 1.0f && xValue == 0.5f) {
     // omit "to bottom"
     return;
   }
   NS_ASSERTION(yValue != 0.5f || xValue != 0.5f, "invalid box position");
 
-  aString.AppendLiteral("to");
+  if (!aGradient->mLegacySyntax) {
+    // Modern syntax explicitly includes the word "to". Old syntax does not
+    // (and is implicitly "from" the given position instead).
+    aString.AppendLiteral("to ");
+  }
 
   if (yValue == 0.0f) {
-    aString.AppendLiteral(" top");
+    aString.AppendLiteral("top");
   } else if (yValue == 1.0f) {
-    aString.AppendLiteral(" bottom");
+    aString.AppendLiteral("bottom");
   } else if (yValue != 0.5f) { // do not write "center" keyword
     NS_NOTREACHED("invalid box position");
   }
 
+  if (yValue != 0.5f && xValue != 0.5f) {
+    // We're appending both a y-keyword and an x-keyword.
+    // Add a space between them here.
+    aString.AppendLiteral(" ");
+  }
+
   if (xValue == 0.0f) {
-    aString.AppendLiteral(" left");
+    aString.AppendLiteral("left");
   } else if (xValue == 1.0f) {
-    aString.AppendLiteral(" right");
+    aString.AppendLiteral("right");
   } else if (xValue != 0.5f) { // do not write "center" keyword
     NS_NOTREACHED("invalid box position");
   }
 
   aNeedSep = true;
 }
 
 void
 nsComputedDOMStyle::GetCSSGradientString(const nsStyleGradient* aGradient,
                                          nsAString& aString)
 {
   if (!aGradient->mLegacySyntax) {
     aString.Truncate();
   } else {
-    aString.AssignLiteral("-moz-");
+    if (aGradient->mMozLegacySyntax) {
+      aString.AssignLiteral("-moz-");
+    } else {
+      aString.AssignLiteral("-webkit-");
+    }
   }
   if (aGradient->mRepeating) {
     aString.AppendLiteral("repeating-");
   }
   bool isRadial = aGradient->mShape != NS_STYLE_GRADIENT_SHAPE_LINEAR;
   if (isRadial) {
     aString.AppendLiteral("radial-gradient(");
   } else {
@@ -2094,17 +2156,19 @@ nsComputedDOMStyle::GetCSSGradientString
         aString.Append(' ');
         AppendCSSGradientLength(aGradient->mRadiusY, tmpVal, aString);
       }
       needSep = true;
     }
   }
   if (aGradient->mBgPosX.GetUnit() != eStyleUnit_None) {
     MOZ_ASSERT(aGradient->mBgPosY.GetUnit() != eStyleUnit_None);
-    if (!isRadial && !aGradient->mLegacySyntax) {
+    if (!isRadial &&
+        !(aGradient->mLegacySyntax && aGradient->mMozLegacySyntax)) {
+      // linear-gradient() or -webkit-linear-gradient()
       AppendCSSGradientToBoxPosition(aGradient, aString, needSep);
     } else if (aGradient->mBgPosX.GetUnit() != eStyleUnit_Percent ||
                aGradient->mBgPosX.GetPercentValue() != 0.5f ||
                aGradient->mBgPosY.GetUnit() != eStyleUnit_Percent ||
                aGradient->mBgPosY.GetPercentValue() != (isRadial ? 0.5f : 1.0f)) {
       if (isRadial && !aGradient->mLegacySyntax) {
         if (needSep) {
           aString.Append(' ');
--- a/layout/style/nsRuleNode.cpp
+++ b/layout/style/nsRuleNode.cpp
@@ -1268,16 +1268,17 @@ static void SetGradient(const nsCSSValue
                  "bad unit for linear shape");
     NS_ASSERTION(gradient->GetRadialSize().GetUnit() == eCSSUnit_None,
                  "bad unit for linear size");
     aResult.mShape = NS_STYLE_GRADIENT_SHAPE_LINEAR;
     aResult.mSize = NS_STYLE_GRADIENT_SIZE_FARTHEST_CORNER;
   }
 
   aResult.mLegacySyntax = gradient->mIsLegacySyntax;
+  aResult.mMozLegacySyntax = gradient->mIsMozLegacySyntax;
 
   // bg-position
   SetGradientCoord(gradient->mBgPos.mXValue, aPresContext, aContext,
                    aResult.mBgPosX, aConditions);
 
   SetGradientCoord(gradient->mBgPos.mYValue, aPresContext, aContext,
                    aResult.mBgPosY, aConditions);
 
--- a/layout/style/nsStyleStruct.cpp
+++ b/layout/style/nsStyleStruct.cpp
@@ -1836,16 +1836,17 @@ nsStyleGradient::operator==(const nsStyl
   MOZ_ASSERT(aOther.mSize == NS_STYLE_GRADIENT_SIZE_FARTHEST_CORNER ||
              aOther.mShape != NS_STYLE_GRADIENT_SHAPE_LINEAR,
              "incorrect combination of shape and size");
 
   if (mShape != aOther.mShape ||
       mSize != aOther.mSize ||
       mRepeating != aOther.mRepeating ||
       mLegacySyntax != aOther.mLegacySyntax ||
+      mMozLegacySyntax != aOther.mMozLegacySyntax ||
       mBgPosX != aOther.mBgPosX ||
       mBgPosY != aOther.mBgPosY ||
       mAngle != aOther.mAngle ||
       mRadiusX != aOther.mRadiusX ||
       mRadiusY != aOther.mRadiusY) {
     return false;
   }
 
@@ -1866,16 +1867,17 @@ nsStyleGradient::operator==(const nsStyl
   return true;
 }
 
 nsStyleGradient::nsStyleGradient()
   : mShape(NS_STYLE_GRADIENT_SHAPE_LINEAR)
   , mSize(NS_STYLE_GRADIENT_SIZE_FARTHEST_CORNER)
   , mRepeating(false)
   , mLegacySyntax(false)
+  , mMozLegacySyntax(false)
 {
 }
 
 bool
 nsStyleGradient::IsOpaque()
 {
   for (uint32_t i = 0; i < mStops.Length(); i++) {
     if (NS_GET_A(mStops[i].mColor) < 255) {
@@ -2333,19 +2335,19 @@ nsStyleImageRequest::GetImageURI() const
   uri = mImageValue->GetURI();
   return uri.forget();
 }
 
 bool
 nsStyleImage::ComputeActualCropRect(nsIntRect& aActualCropRect,
                                     bool* aIsEntireImage) const
 {
-  if (mType != eStyleImageType_Image) {
-    return false;
-  }
+  MOZ_ASSERT(mType == eStyleImageType_Image,
+             "This function is designed to be used only when mType"
+             "is eStyleImageType_Image.");
 
   imgRequestProxy* req = GetImageData();
   if (!req) {
     return false;
   }
 
   nsCOMPtr<imgIContainer> imageContainer;
   req->GetImage(getter_AddRefs(imageContainer));
@@ -2416,19 +2418,17 @@ nsStyleImage::IsOpaque() const
   if (imageContainer->WillDrawOpaqueNow()) {
     if (!mCropRect) {
       return true;
     }
 
     // Must make sure if mCropRect contains at least a pixel.
     // XXX Is this optimization worth it? Maybe I should just return false.
     nsIntRect actualCropRect;
-    bool rv = ComputeActualCropRect(actualCropRect);
-    NS_ASSERTION(rv, "ComputeActualCropRect() can not fail here");
-    return rv && !actualCropRect.IsEmpty();
+    return ComputeActualCropRect(actualCropRect) && !actualCropRect.IsEmpty();
   }
 
   return false;
 }
 
 bool
 nsStyleImage::IsComplete() const
 {
--- a/layout/style/nsStyleStruct.h
+++ b/layout/style/nsStyleStruct.h
@@ -237,17 +237,21 @@ struct nsStyleGradientStop
 class nsStyleGradient final
 {
 public:
   nsStyleGradient();
   uint8_t mShape;  // NS_STYLE_GRADIENT_SHAPE_*
   uint8_t mSize;   // NS_STYLE_GRADIENT_SIZE_*;
                    // not used (must be FARTHEST_CORNER) for linear shape
   bool mRepeating;
-  bool mLegacySyntax;
+  bool mLegacySyntax; // If true, serialization should use a vendor prefix.
+  // XXXdholbert This will hopefully be going away soon, if bug 1337655 sticks:
+  bool mMozLegacySyntax; // (Only makes sense when mLegacySyntax is true.)
+                         // If true, serialization should use -moz prefix.
+                         // Else, serialization should use -webkit prefix.
 
   nsStyleCoord mBgPosX; // percent, coord, calc, none
   nsStyleCoord mBgPosY; // percent, coord, calc, none
   nsStyleCoord mAngle;  // none, angle
 
   nsStyleCoord mRadiusX; // percent, coord, calc, none
   nsStyleCoord mRadiusY; // percent, coord, calc, none
 
deleted file mode 100644
--- a/layout/style/res/checkmark.svg
+++ /dev/null
@@ -1,7 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 10 10" width="10" height="10">
-    <style type="text/css">
-        path { fill: -moz-FieldText; }
-        #disabled:target { fill: GrayText; }
-    </style>
-    <path id="disabled" d="M 2 4 L 4 6 L 8 2 L 8 4 L 4 8 L 2 6 Z"/>
-</svg>
--- a/layout/style/res/forms.css
+++ b/layout/style/res/forms.css
@@ -644,50 +644,18 @@ input[type="checkbox"]:disabled:active,
 input[type="checkbox"]:disabled:hover,
 input[type="checkbox"]:disabled:hover:active {
   padding: 1px;
   border: 1px inset ThreeDShadow;
 }
 
 input[type="checkbox"]:hover:active,
 input[type="radio"]:hover:active {
-  background-color: ThreeDFace;
-  border-style: inset;
-}
-
-input[type="radio"] {
-  background-size: calc(100% - 4px) calc(100% - 4px);
-}
-
-input[type="radio"]:checked {
-  background-image: url(radio.svg);
-}
-
-input[type="radio"]:disabled:checked {
-  background-image: url(radio.svg#disabled);
-}
-
-input[type="checkbox"] {
-  background-size: 100% 100%;
-}
-
-input[type="checkbox"]:checked {
-  background-image: url(checkmark.svg);
-}
-
-input[type="checkbox"]:disabled:checked {
-  background-image: url(checkmark.svg#disabled);
-}
-
-input[type="checkbox"]:indeterminate {
-  background-image: url(indeterminate-checkmark.svg);
-}
-
-input[type="checkbox"]:indeterminate:disabled {
-  background-image: url(indeterminate-checkmark.svg#disabled);
+  background-color: ThreeDFace ! important;
+  border-style: inset !important;
 }
 
 %endif /* defined(MOZ_WIDGET_ANDROID) */
 
 % On Mac, the native theme takes care of this.
 % See nsNativeThemeCocoa::ThemeDrawsFocusForWidget.
 %ifndef XP_MACOSX
 input[type="checkbox"]:-moz-focusring,
deleted file mode 100644
--- a/layout/style/res/indeterminate-checkmark.svg
+++ /dev/null
@@ -1,7 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 5 5" width="5" height="5">
-    <style type="text/css">
-        rect { fill: -moz-FieldText; }
-        #disabled:target { fill: GrayText; }
-    </style>
-    <rect id="disabled" x="0" y="2" width="5" height="1"/>
-</svg>
deleted file mode 100644
--- a/layout/style/res/radio.svg
+++ /dev/null
@@ -1,7 +0,0 @@
-<svg xmlns="http://www.w3.org/2000/svg" viewbox="0 0 20 20" width="20" height="20">
-    <style type="text/css">
-        circle { fill: -moz-FieldText; }
-        #disabled:target { fill: GrayText; }
-    </style>
-    <circle id="disabled" cx="10" cy="10" r="10"/>
-</svg>
--- a/layout/style/test/stylo-failures.md
+++ b/layout/style/test/stylo-failures.md
@@ -31,17 +31,18 @@ to mochitest command.
 
 * Media query support:
   * test_media_queries.html [156]
   * test_media_queries_dynamic.html [11]
   * test_media_queries_dynamic_xbl.html [2]
   * test_webkit_device_pixel_ratio.html: -webkit-device-pixel-ratio [3]
   * browser_bug453896.js [8]
 * Animation support:
-  * test_animations.html [19]
+  * test_transitions_and_reframes.html: pseudo frames bug 1331047 [4]
+  * test_animations.html: 6 of them bug 1331047 [25]
   * test_animations_dynamic_changes.html [1]
   * test_bug716226.html [1]
   * OMTA
     * test_animations_effect_timing_duration.html [1]
     * test_animations_effect_timing_enddelay.html [1]
     * test_animations_effect_timing_iterations.html [1]
     * test_animations_iterationstart.html [1]
     * test_animations_omta.html [1]
@@ -73,17 +74,17 @@ to mochitest command.
   * test_parser_diagnostics_unprintables.html [550]
 * Transition support:
   * test_compute_data_with_start_struct.html `transition` [2]
   * test_transitions.html: pseudo elements [10]
   * test_transitions_computed_value_combinations.html [145]
   * test_value_storage.html `transition` [218]
   * Events:
     * test_animations_event_order.html [2]
-* test_computed_style.html `gradient`: -moz- and -webkit-prefixed gradient values [30]
+* test_computed_style.html `gradient`: -moz- and -webkit-prefixed gradient values [35]
 * ... `mask`: mask-image isn't set properly bug 1341667 [10]
 * ... `fill`: svg paint should distinguish whether there is fallback bug 1347409 [2]
 * ... `stroke`: svg paint should distinguish whether there is fallback bug 1347409 [2]
 * character not properly escaped servo/servo#15947
   * test_parse_url.html [1]
   * test_bug829816.html [8]
 * test_compute_data_with_start_struct.html `timing-function`: incorrectly computing keywords to bezier function servo/servo#15086 [2]
 * \@counter-style support bug 1328319
@@ -104,17 +105,17 @@ to mochitest command.
     * test_font_face_parser.html `font-display` [15]
   * test_font_face_parser.html `font-language-override`: bug 1355364 [8]
   * ... `font-feature-settings`: bug 1355366 [10]
 * test_font_face_parser.html `font-weight`: keyword values should be preserved in \@font-face [4]
 * @font-face support bug 1290237
   * test_descriptor_storage.html [1]
   * test_font_face_parser.html `@font-face` [8]
 * @namespace support:
-  * test_namespace_rule.html: bug 1355715 [17]
+  * test_namespace_rule.html: bug 1355715 [16]
 * test_dont_use_document_colors.html: support of disabling document color bug 1355716 [21]
 * test_exposed_prop_accessors.html: mainly various unsupported properties [*]
 * test_font_feature_values_parsing.html: \@font-feature-values support bug 1355721 [107]
 * Grid support bug 1341802
   * test_grid_computed_values.html [4]
   * test_grid_container_shorthands.html [65]
   * test_grid_item_shorthands.html [23]
   * test_grid_shorthand_serialization.html [28]
@@ -172,30 +173,30 @@ to mochitest command.
 * Stylesheet cloning is somehow busted bug 1348481
   * test_selectors.html `cloned correctly` [157]
   * ... `matched clone` [204]
 * Unsupported prefixed values
   * moz-prefixed gradient functions bug 1337655
     * test_value_storage.html `-moz-linear-gradient` [322]
     * ... `-moz-radial-gradient` [309]
     * ... `-moz-repeating-` [298]
+    * test_specified_value_serialization.html `-moz-linear-gradient` [2]
   * webkit-prefixed gradient functions servo/servo#15441
     * test_value_storage.html `-webkit-gradient` [225]
+    * test_specified_value_serialization.html `-webkit-linear-gradient` [1]
+    * test_specified_value_serialization.html `-webkit-radial-gradient` [1]
   * moz-prefixed intrinsic width values bug 1355402
     * test_box_size_keywords.html [16]
     * test_flexbox_flex_shorthand.html `-moz-fit-content` [4]
     * test_value_storage.html `-moz-max-content` [46]
     * ... `-moz-min-content` [6]
     * ... `-moz-fit-content` [6]
     * ... `-moz-available` [4]
   * several prefixed values in cursor property bug 1356072
     * test_value_storage.html `cursor` [4]
-  * moz-prefixed values of overflow shorthand bug 1330888
-    * test_bug319381.html [8]
-    * test_value_storage.html `'overflow` [8]
   * -webkit-{flex,inline-flex} for display servo/servo#15400
     * test_webkit_flex_display.html [4]
   * test_pixel_lengths.html `mozmm`: mozmm unit bug 1356104 [3]
 * Unsupported values
   * SVG-only values of pointer-events not recognized
     * test_value_storage.html `pointer-events` [1]
   * new syntax of rgba?() and hsla?() functions servo/rust-cssparser#113
     * test_computed_style.html `css-color-4` [2]
@@ -274,17 +275,17 @@ to mochitest command.
   * test_bug511909.html `@-moz-document` [4]
   * test_condition_text.html: also \@supports [7]
 * getComputedStyle style doesn't contain custom properties bug 1336891
   * test_variable_serialization_computed.html [35]
   * test_variables.html `custom property name` [2]
 * test_css_supports.html: issues around @supports syntax servo/servo#15482 [8]
 * test_author_specified_style.html: support serializing color as author specified bug 1348165 [27]
 * browser_newtab_share_rule_processors.js: agent style sheet sharing [1]
-* test_selectors.html `this_better_be_unvisited`: visited handling [2]
+* test_selectors.html `this_better_be_unvisited`: visited handling [1]
 * test_selectors.html `:nth-child`: &lt;an+b&gt; parsing difference servo/rust-cssparser#138 [14]
 
 ## Assertions
 
 ## Need Gecko change
 
 * Servo is correct but Gecko is wrong
   * flex-basis should be 0px when omitted in flex shorthand bug 1331530
--- a/layout/style/test/test_computed_style.html
+++ b/layout/style/test/test_computed_style.html
@@ -525,12 +525,58 @@ var noframe_container = document.getElem
     var test = css_color_4[i];
     p.style.color = test[0];
     is(cs.color, test[1], "css-color-4 computed value of " + test[0]);
   }
 
   p.remove();
 })();
 
+(function test_bug_1357117() {
+  // Test that vendor-prefixed gradient styles round-trip with the same prefix,
+  // or with no prefix.
+  var backgroundImages = [
+    // [ specified style,
+    //   expected computed style,
+    //   descriptionOfTestcase ],
+    // Linear gradient with legacy-gradient-line (needs prefixed syntax):
+    [ "-moz-linear-gradient(10deg, red, blue)",
+      "-moz-linear-gradient(10deg, rgb(255, 0, 0), rgb(0, 0, 255))",
+      "-moz-linear-gradient with angled legacy-gradient-line" ],
+    [ "-webkit-linear-gradient(10deg, red, blue)",
+      "-webkit-linear-gradient(10deg, rgb(255, 0, 0), rgb(0, 0, 255))",
+      "-webkit-linear-gradient with angled legacy-gradient-line" ],
+
+    // Linear gradient with box corner (needs prefixed syntax):
+    [ "-moz-linear-gradient(top left, red, blue)",
+      "-moz-linear-gradient(0% 0%, rgb(255, 0, 0), rgb(0, 0, 255))",
+      "-moz-linear-gradient with box corner" ],
+    [ "-webkit-linear-gradient(top left, red, blue)",
+      "-webkit-linear-gradient(top left, rgb(255, 0, 0), rgb(0, 0, 255))",
+      "-webkit-linear-gradient with box corner" ],
+
+    // Radial gradients (should be serialized using modern unprefixed style):
+    [ "-moz-radial-gradient(contain, red, blue)",
+      "radial-gradient(closest-side, rgb(255, 0, 0), rgb(0, 0, 255))",
+      "-moz-radial-gradient with legacy 'contain' keyword" ],
+    [ "-webkit-radial-gradient(contain, red, blue)",
+      "radial-gradient(closest-side, rgb(255, 0, 0), rgb(0, 0, 255))",
+      "-webkit-radial-gradient with legacy 'contain' keyword" ],
+  ];
+
+  var p = document.createElement("p");
+  var cs = getComputedStyle(p, "");
+  frame_container.appendChild(p);
+
+  for (var i = 0; i < backgroundImages.length; ++i) {
+    var test = backgroundImages[i];
+    p.style.backgroundImage = test[0];
+    is(cs.backgroundImage, test[1],
+       "computed value of prefixed gradient expression (" + test[2] + ")");
+  }
+
+  p.remove();
+})();
+
 </script>
 </pre>
 </body>
 </html>
--- a/layout/style/test/test_specified_value_serialization.html
+++ b/layout/style/test/test_specified_value_serialization.html
@@ -101,12 +101,62 @@
     var test = css_color_4[i];
     p.style.color = test[0];
     is(p.style.color, test[1], "css-color-4 serialization value of " + test[0]);
   }
 
   p.remove();
 })();
 
+(function test_bug_1357117() {
+  // Test that vendor-prefixed gradient styles round-trip with the same prefix,
+  // or with no prefix.
+  var backgroundImages = [
+    // [ specified style,
+    //   expected serialization,
+    //   descriptionOfTestcase ],
+    // Linear gradient with legacy-gradient-line (needs prefixed syntax):
+    [ "-moz-linear-gradient(10deg, red, blue)",
+      "-moz-linear-gradient(10deg, red, blue)",
+      "-moz-linear-gradient with angled legacy-gradient-line" ],
+    [ "-webkit-linear-gradient(10deg, red, blue)",
+      "-webkit-linear-gradient(10deg, red, blue)",
+      "-webkit-linear-gradient with angled legacy-gradient-line" ],
+
+    // Linear gradient with box corner (needs prefixed syntax):
+    [ "-moz-linear-gradient(top left, red, blue)",
+      "-moz-linear-gradient(left top , red, blue)",
+      //                            ^
+      // (NOTE: our -moz-linear-gradient serialization inserts an extra space
+      // before the first comma in some cases. This is ugly but fine,
+      // particularly given bug 1337655).
+      "-moz-linear-gradient with box corner" ],
+    [ "-webkit-linear-gradient(top left, red, blue)",
+      "-webkit-linear-gradient(left top, red, blue)",
+      "-webkit-linear-gradient with box corner" ],
+
+    // Radial gradients (should be serialized using modern unprefixed style):
+    [ "-moz-radial-gradient(contain, red, blue)",
+      "radial-gradient(closest-side, red, blue)",
+      "-moz-radial-gradient with legacy 'contain' keyword" ],
+    [ "-webkit-radial-gradient(contain, red, blue)",
+      "radial-gradient(closest-side, red, blue)",
+      "-webkit-radial-gradient with legacy 'contain' keyword" ],
+  ];
+
+  var frame_container = document.getElementById("display");
+  var p = document.createElement("p");
+  frame_container.appendChild(p);
+
+  for (var i = 0; i < backgroundImages.length; ++i) {
+    var test = backgroundImages[i];
+    p.style.backgroundImage = test[0];
+    is(p.style.backgroundImage, test[1],
+       "serialization value of prefixed gradient expression (" + test[2] + ")");
+  }
+
+  p.remove();
+})();
+
 </script>
 </pre>
 </body>
 </html>
--- a/mfbt/RefPtr.h
+++ b/mfbt/RefPtr.h
@@ -642,11 +642,25 @@ namespace mozilla {
 template<typename T, typename... Args>
 already_AddRefed<T>
 MakeAndAddRef(Args&&... aArgs)
 {
   RefPtr<T> p(new T(Forward<Args>(aArgs)...));
   return p.forget();
 }
 
+/**
+ * Helper function to be able to conveniently write things like:
+ *
+ *   auto runnable = MakeRefPtr<ErrorCallbackRunnable<nsIDOMGetUserMediaSuccessCallback>>(
+ *       mOnSuccess, mOnFailure, *error, mWindowID);
+ */
+template<typename T, typename... Args>
+RefPtr<T>
+MakeRefPtr(Args&&... aArgs)
+{
+  RefPtr<T> p(new T(Forward<Args>(aArgs)...));
+  return p;
+}
+
 } // namespace mozilla
 
 #endif /* mozilla_RefPtr_h */
--- a/mfbt/ThreadLocal.h
+++ b/mfbt/ThreadLocal.h
@@ -6,34 +6,24 @@
 
 /* Cross-platform lightweight thread local data wrappers. */
 
 #ifndef mozilla_ThreadLocal_h
 #define mozilla_ThreadLocal_h
 
 #if !defined(XP_WIN)
 #  include <pthread.h>
-#  include <signal.h>
 #endif
 
 #include "mozilla/Assertions.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/TypeTraits.h"
 
 namespace mozilla {
 
-// sig_safe_t denotes an atomic type which can be read or stored in a single
-// instruction.  This means that data of this type is safe to be manipulated
-// from a signal handler, or other similar asynchronous execution contexts.
-#if defined(XP_WIN)
-typedef unsigned long sig_safe_t;
-#else
-typedef sig_atomic_t sig_safe_t;
-#endif
-
 namespace detail {
 
 #ifdef XP_MACOSX
 #  if defined(__has_feature)
 #    if __has_feature(cxx_thread_local)
 #      define MACOSX_HAS_THREAD_LOCAL
 #    endif
 #  endif
--- a/mobile/android/base/android-services.mozbuild
+++ b/mobile/android/base/android-services.mozbuild
@@ -826,20 +826,21 @@ sync_java_files = [TOPSRCDIR + '/mobile/
     'fxa/activities/PicassoPreferenceIconTarget.java',
     'fxa/authenticator/AccountPickler.java',
     'fxa/authenticator/AndroidFxAccount.java',
     'fxa/authenticator/FxAccountAuthenticator.java',
     'fxa/authenticator/FxAccountAuthenticatorService.java',
     'fxa/authenticator/FxAccountLoginDelegate.java',
     'fxa/authenticator/FxAccountLoginException.java',
     'fxa/authenticator/FxADefaultLoginStateMachineDelegate.java',
+    'fxa/devices/FxAccountDevice.java',
+    'fxa/devices/FxAccountDeviceListUpdater.java',
+    'fxa/devices/FxAccountDeviceRegistrator.java',
     'fxa/FirefoxAccounts.java',
     'fxa/FxAccountConstants.java',
-    'fxa/FxAccountDevice.java',
-    'fxa/FxAccountDeviceRegistrator.java',
     'fxa/FxAccountPushHandler.java',
     'fxa/login/BaseRequestDelegate.java',
     'fxa/login/Cohabiting.java',
     'fxa/login/Doghouse.java',
     'fxa/login/Engaged.java',
     'fxa/login/FxAccountLoginStateMachine.java',
     'fxa/login/FxAccountLoginTransition.java',
     'fxa/login/Married.java',
--- a/mobile/android/base/java/org/mozilla/gecko/AccountsHelper.java
+++ b/mobile/android/base/java/org/mozilla/gecko/AccountsHelper.java
@@ -14,17 +14,16 @@ import android.accounts.OperationCancele
 import android.content.Context;
 import android.content.Intent;
 import android.util.Log;
 
 import org.json.JSONException;
 import org.mozilla.gecko.background.fxa.FxAccountUtils;
 import org.mozilla.gecko.fxa.FirefoxAccounts;
 import org.mozilla.gecko.fxa.FxAccountConstants;
-import org.mozilla.gecko.fxa.FxAccountDeviceRegistrator;
 import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
 import org.mozilla.gecko.fxa.login.Engaged;
 import org.mozilla.gecko.fxa.login.State;
 import org.mozilla.gecko.restrictions.Restrictable;
 import org.mozilla.gecko.restrictions.Restrictions;
 import org.mozilla.gecko.sync.SyncConfiguration;
 import org.mozilla.gecko.sync.Utils;
 import org.mozilla.gecko.util.BundleEventListener;
--- a/mobile/android/base/java/org/mozilla/gecko/db/BrowserContract.java
+++ b/mobile/android/base/java/org/mozilla/gecko/db/BrowserContract.java
@@ -55,16 +55,17 @@ public class BrowserContract {
     public static final String PARAM_INCREMENT_VISITS = "increment_visits";
     public static final String PARAM_INCREMENT_REMOTE_AGGREGATES = "increment_remote_aggregates";
     public static final String PARAM_NON_POSITIONED_PINS = "non_positioned_pins";
     public static final String PARAM_EXPIRE_PRIORITY = "priority";
     public static final String PARAM_DATASET_ID = "dataset_id";
     public static final String PARAM_GROUP_BY = "group_by";
 
     public static final String METHOD_INSERT_HISTORY_WITH_VISITS_FROM_SYNC = "insertHistoryWithVisitsSync";
+    public static final String METHOD_REPLACE_REMOTE_CLIENTS = "replaceRemoteClients";
     public static final String METHOD_RESULT = "methodResult";
     public static final String METHOD_PARAM_OBJECT = "object";
     public static final String METHOD_PARAM_DATA = "data";
 
     static public enum ExpirePriority {
         NORMAL,
         AGGRESSIVE
     }
@@ -456,16 +457,29 @@ public class BrowserContract {
 
         // Last modified time for the client's tab record. For remote records, a server
         // timestamp provided by Sync during insertion.
         public static final String LAST_MODIFIED = "last_modified";
 
         public static final String DEVICE_TYPE = "device_type";
     }
 
+    public static final class RemoteDevices implements CommonColumns, DateSyncColumns {
+        private RemoteDevices() {}
+        public static final String TABLE_NAME = "remote_devices";
+
+        public static final String GUID = "guid"; // FxA device ID
+        public static final String NAME = "name";
+        public static final String TYPE = "type";
+        public static final String IS_CURRENT_DEVICE = "is_current_device";
+        public static final String LAST_ACCESS_TIME = "last_access_time";
+
+        public static final Uri CONTENT_URI = Uri.withAppendedPath(AUTHORITY_URI, "remote_devices");
+    }
+
     // Data storage for dynamic panels on about:home
     @RobocopTarget
     public static final class HomeItems implements CommonColumns {
         private HomeItems() {}
         public static final Uri CONTENT_FAKE_URI = Uri.withAppendedPath(HOME_AUTHORITY_URI, "items/fake");
         public static final Uri CONTENT_URI = Uri.withAppendedPath(HOME_AUTHORITY_URI, "items");
 
         public static final String CONTENT_TYPE = "vnd.android.cursor.dir/homeitem";
--- a/mobile/android/base/java/org/mozilla/gecko/db/BrowserDatabaseHelper.java
+++ b/mobile/android/base/java/org/mozilla/gecko/db/BrowserDatabaseHelper.java
@@ -19,16 +19,17 @@ import org.json.simple.JSONArray;
 import org.json.simple.JSONObject;
 import org.mozilla.gecko.GeckoProfile;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.annotation.RobocopTarget;
 import org.mozilla.gecko.db.BrowserContract.ActivityStreamBlocklist;
 import org.mozilla.gecko.db.BrowserContract.Bookmarks;
 import org.mozilla.gecko.db.BrowserContract.Combined;
 import org.mozilla.gecko.db.BrowserContract.Favicons;
+import org.mozilla.gecko.db.BrowserContract.RemoteDevices;
 import org.mozilla.gecko.db.BrowserContract.History;
 import org.mozilla.gecko.db.BrowserContract.Visits;
 import org.mozilla.gecko.db.BrowserContract.PageMetadata;
 import org.mozilla.gecko.db.BrowserContract.Numbers;
 import org.mozilla.gecko.db.BrowserContract.ReadingListItems;
 import org.mozilla.gecko.db.BrowserContract.SearchHistory;
 import org.mozilla.gecko.db.BrowserContract.Thumbnails;
 import org.mozilla.gecko.db.BrowserContract.UrlAnnotations;
@@ -55,25 +56,26 @@ import android.util.Log;
 
 
 // public for robocop testing
 public final class BrowserDatabaseHelper extends SQLiteOpenHelper {
     private static final String LOGTAG = "GeckoBrowserDBHelper";
 
     // Replace the Bug number below with your Bug that is conducting a DB upgrade, as to force a merge conflict with any
     // other patches that require a DB upgrade.
-    public static final int DATABASE_VERSION = 36; // Bug 1301717
+    public static final int DATABASE_VERSION = 37; // Bug 1351805
     public static final String DATABASE_NAME = "browser.db";
 
     final protected Context mContext;
 
     static final String TABLE_BOOKMARKS = Bookmarks.TABLE_NAME;
     static final String TABLE_HISTORY = History.TABLE_NAME;
     static final String TABLE_VISITS = Visits.TABLE_NAME;
     static final String TABLE_PAGE_METADATA = PageMetadata.TABLE_NAME;
+    static final String TABLE_REMOTE_DEVICES = RemoteDevices.TABLE_NAME;
     static final String TABLE_FAVICONS = Favicons.TABLE_NAME;
     static final String TABLE_THUMBNAILS = Thumbnails.TABLE_NAME;
     static final String TABLE_READING_LIST = ReadingListItems.TABLE_NAME;
     static final String TABLE_TABS = TabsProvider.TABLE_TABS;
     static final String TABLE_CLIENTS = TabsProvider.TABLE_CLIENTS;
     static final String TABLE_LOGINS = BrowserContract.Logins.TABLE_LOGINS;
     static final String TABLE_DELETED_LOGINS = BrowserContract.DeletedLogins.TABLE_DELETED_LOGINS;
     static final String TABLE_DISABLED_HOSTS = BrowserContract.LoginsDisabledHosts.TABLE_DISABLED_HOSTS;
@@ -231,16 +233,31 @@ public final class BrowserDatabaseHelper
         // Establish a 1-to-1 relationship with History table.
         db.execSQL("CREATE UNIQUE INDEX page_metadata_history_guid ON " + TABLE_PAGE_METADATA + "("
                 + PageMetadata.HISTORY_GUID + ")");
         // Improve performance of commonly occurring selections.
         db.execSQL("CREATE INDEX page_metadata_history_guid_and_has_image ON " + TABLE_PAGE_METADATA + "("
                 + PageMetadata.HISTORY_GUID + ", " + PageMetadata.HAS_IMAGE + ")");
     }
 
+    private void createRemoteDevicesTable(SQLiteDatabase db) {
+        debug("Creating " + TABLE_REMOTE_DEVICES + " table");
+        db.execSQL("CREATE TABLE " + TABLE_REMOTE_DEVICES + "(" +
+                RemoteDevices._ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
+                RemoteDevices.GUID + " TEXT UNIQUE NOT NULL," +
+                RemoteDevices.NAME + " TEXT NOT NULL," +
+                RemoteDevices.TYPE + " TEXT NOT NULL," +
+                RemoteDevices.IS_CURRENT_DEVICE + " INTEGER NOT NULL," +
+                RemoteDevices.DATE_CREATED + " INTEGER NOT NULL," + // Timestamp - in milliseconds.
+                RemoteDevices.DATE_MODIFIED + " INTEGER NOT NULL," +
+                RemoteDevices.LAST_ACCESS_TIME + " INTEGER NOT NULL" + // Timestamp - in milliseconds.
+                ");");
+        // Creating an index is not worth it, because most users have less than 3 devices.
+    }
+
     private void createBookmarksWithFaviconsView(SQLiteDatabase db) {
         debug("Creating " + VIEW_BOOKMARKS_WITH_FAVICONS + " view");
 
         db.execSQL("CREATE VIEW IF NOT EXISTS " + VIEW_BOOKMARKS_WITH_FAVICONS + " AS " +
                 "SELECT " + qualifyColumn(TABLE_BOOKMARKS, "*") +
                 ", " + qualifyColumn(TABLE_FAVICONS, Favicons.DATA) + " AS " + Bookmarks.FAVICON +
                 ", " + qualifyColumn(TABLE_FAVICONS, Favicons.URL) + " AS " + Bookmarks.FAVICON_URL +
                 " FROM " + TABLE_BOOKMARKS_JOIN_FAVICONS);
@@ -746,16 +763,18 @@ public final class BrowserDatabaseHelper
         createBookmarksWithAnnotationsView(db);
 
         createVisitsTable(db);
         createCombinedViewOn34(db);
 
         createActivityStreamBlocklistTable(db);
 
         createPageMetadataTable(db);
+
+        createRemoteDevicesTable(db);
     }
 
     /**
      * Copies the tabs and clients tables out of the given tabs.db file and into the destinationDB.
      *
      * @param tabsDBFile Path to existing tabs.db.
      * @param destinationDB The destination database.
      */
@@ -1975,16 +1994,20 @@ public final class BrowserDatabaseHelper
     private void upgradeDatabaseFrom34to35(final SQLiteDatabase db) {
         createActivityStreamBlocklistTable(db);
     }
 
     private void upgradeDatabaseFrom35to36(final SQLiteDatabase db) {
         createPageMetadataTable(db);
     }
 
+    private void upgradeDatabaseFrom36to37(final SQLiteDatabase db) {
+        createRemoteDevicesTable(db);
+    }
+
     private void createV33CombinedView(final SQLiteDatabase db) {
         db.execSQL("DROP VIEW IF EXISTS " + VIEW_COMBINED);
         db.execSQL("DROP VIEW IF EXISTS " + VIEW_COMBINED_WITH_FAVICONS);
 
         createCombinedViewOn33(db);
     }
 
     private void createV34CombinedView(final SQLiteDatabase db) {
@@ -2110,16 +2133,20 @@ public final class BrowserDatabaseHelper
 
                 case 35:
                     upgradeDatabaseFrom34to35(db);
                     break;
 
                 case 36:
                     upgradeDatabaseFrom35to36(db);
                     break;
+
+                case 37:
+                    upgradeDatabaseFrom36to37(db);
+                    break;
             }
         }
 
         for (Table table : BrowserProvider.sTables) {
             table.onUpgrade(db, oldVersion, newVersion);
         }
 
         // Delete the obsolete favicon database after all other upgrades complete.
--- a/mobile/android/base/java/org/mozilla/gecko/db/BrowserProvider.java
+++ b/mobile/android/base/java/org/mozilla/gecko/db/BrowserProvider.java
@@ -15,16 +15,17 @@ import java.util.Map;
 import org.mozilla.gecko.AboutPages;
 import org.mozilla.gecko.GeckoProfile;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.db.BrowserContract.ActivityStreamBlocklist;
 import org.mozilla.gecko.db.BrowserContract.Bookmarks;
 import org.mozilla.gecko.db.BrowserContract.Combined;
 import org.mozilla.gecko.db.BrowserContract.FaviconColumns;
 import org.mozilla.gecko.db.BrowserContract.Favicons;
+import org.mozilla.gecko.db.BrowserContract.RemoteDevices;
 import org.mozilla.gecko.db.BrowserContract.Highlights;
 import org.mozilla.gecko.db.BrowserContract.History;
 import org.mozilla.gecko.db.BrowserContract.Visits;
 import org.mozilla.gecko.db.BrowserContract.Schema;
 import org.mozilla.gecko.db.BrowserContract.Tabs;
 import org.mozilla.gecko.db.BrowserContract.Thumbnails;
 import org.mozilla.gecko.db.BrowserContract.TopSites;
 import org.mozilla.gecko.db.BrowserContract.UrlAnnotations;
@@ -89,16 +90,17 @@ public class BrowserProvider extends Sha
     static final String TABLE_HISTORY = History.TABLE_NAME;
     static final String TABLE_VISITS = Visits.TABLE_NAME;
     static final String TABLE_FAVICONS = Favicons.TABLE_NAME;
     static final String TABLE_THUMBNAILS = Thumbnails.TABLE_NAME;
     static final String TABLE_TABS = Tabs.TABLE_NAME;
     static final String TABLE_URL_ANNOTATIONS = UrlAnnotations.TABLE_NAME;
     static final String TABLE_ACTIVITY_STREAM_BLOCKLIST = ActivityStreamBlocklist.TABLE_NAME;
     static final String TABLE_PAGE_METADATA = PageMetadata.TABLE_NAME;
+    static final String TABLE_REMOTE_DEVICES = RemoteDevices.TABLE_NAME;
 
     static final String VIEW_COMBINED = Combined.VIEW_NAME;
     static final String VIEW_BOOKMARKS_WITH_FAVICONS = Bookmarks.VIEW_WITH_FAVICONS;
     static final String VIEW_BOOKMARKS_WITH_ANNOTATIONS = Bookmarks.VIEW_WITH_ANNOTATIONS;
     static final String VIEW_HISTORY_WITH_FAVICONS = History.VIEW_WITH_FAVICONS;
     static final String VIEW_COMBINED_WITH_FAVICONS = Combined.VIEW_WITH_FAVICONS;
 
     // Bookmark matches
@@ -142,16 +144,19 @@ public class BrowserProvider extends Sha
     static final int METADATA = 1200;
 
     static final int HIGHLIGHT_CANDIDATES = 1300;
 
     static final int ACTIVITY_STREAM_BLOCKLIST = 1400;
 
     static final int PAGE_METADATA = 1500;
 
+    static final int REMOTE_DEVICES = 1600;
+    static final int REMOTE_DEVICES_ID = 1601;
+
     static final String DEFAULT_BOOKMARKS_SORT_ORDER = Bookmarks.TYPE
             + " ASC, " + Bookmarks.POSITION + " ASC, " + Bookmarks._ID
             + " ASC";
 
     static final String DEFAULT_HISTORY_SORT_ORDER = History.DATE_LAST_VISITED + " DESC";
     static final String DEFAULT_VISITS_SORT_ORDER = Visits.DATE_VISITED + " DESC";
 
     static final UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
@@ -160,16 +165,17 @@ public class BrowserProvider extends Sha
     static final Map<String, String> HISTORY_PROJECTION_MAP;
     static final Map<String, String> COMBINED_PROJECTION_MAP;
     static final Map<String, String> SCHEMA_PROJECTION_MAP;
     static final Map<String, String> FAVICONS_PROJECTION_MAP;
     static final Map<String, String> THUMBNAILS_PROJECTION_MAP;
     static final Map<String, String> URL_ANNOTATIONS_PROJECTION_MAP;
     static final Map<String, String> VISIT_PROJECTION_MAP;
     static final Map<String, String> PAGE_METADATA_PROJECTION_MAP;
+    static final Map<String, String> REMOTE_DEVICES_PROJECTION_MAP;
     static final Table[] sTables;
 
     static {
         sTables = new Table[] {
             // See awful shortcut assumption hack in getURLImageDataTable.
             new URLImageDataTable()
         };
         // We will reuse this.
@@ -319,16 +325,31 @@ public class BrowserProvider extends Sha
         }
 
         // Combined pinned sites, top visited sites, and suggested sites
         URI_MATCHER.addURI(BrowserContract.AUTHORITY, "topsites", TOPSITES);
 
         URI_MATCHER.addURI(BrowserContract.AUTHORITY, ActivityStreamBlocklist.TABLE_NAME, ACTIVITY_STREAM_BLOCKLIST);
 
         URI_MATCHER.addURI(BrowserContract.AUTHORITY, "highlight_candidates", HIGHLIGHT_CANDIDATES);
+
+        // FxA Devices
+        URI_MATCHER.addURI(BrowserContract.AUTHORITY, "remote_devices", REMOTE_DEVICES);
+        URI_MATCHER.addURI(BrowserContract.AUTHORITY, "remote_devices/#", REMOTE_DEVICES_ID);
+
+        map = new HashMap<>();
+        map.put(RemoteDevices._ID, RemoteDevices._ID);
+        map.put(RemoteDevices.GUID, RemoteDevices.GUID);
+        map.put(RemoteDevices.NAME, RemoteDevices.NAME);
+        map.put(RemoteDevices.TYPE, RemoteDevices.TYPE);
+        map.put(RemoteDevices.IS_CURRENT_DEVICE, RemoteDevices.IS_CURRENT_DEVICE);
+        map.put(RemoteDevices.DATE_CREATED, RemoteDevices.DATE_CREATED);
+        map.put(RemoteDevices.DATE_MODIFIED, RemoteDevices.DATE_MODIFIED);
+        map.put(RemoteDevices.LAST_ACCESS_TIME, RemoteDevices.LAST_ACCESS_TIME);
+        REMOTE_DEVICES_PROJECTION_MAP = Collections.unmodifiableMap(map);
     }
 
     private static class ShrinkMemoryReceiver extends BroadcastReceiver {
         private final WeakReference<BrowserProvider> mBrowserProviderWeakReference;
 
         public ShrinkMemoryReceiver(final BrowserProvider browserProvider) {
             mBrowserProviderWeakReference = new WeakReference<>(browserProvider);
         }
@@ -644,16 +665,30 @@ public class BrowserProvider extends Sha
                 deleteUrlAnnotation(uri, selection, selectionArgs);
                 break;
 
             case PAGE_METADATA:
                 trace("Delete on PAGE_METADATA: " + uri);
                 deleted = deletePageMetadata(uri, selection, selectionArgs);
                 break;
 
+            case REMOTE_DEVICES_ID:
+                debug("Delete on REMOTE_DEVICES_ID: " + uri);
+
+                selection = DBUtils.concatenateWhere(selection, TABLE_REMOTE_DEVICES + "._ID = ?");
+                selectionArgs = DBUtils.appendSelectionArgs(selectionArgs,
+                        new String[] { Long.toString(ContentUris.parseId(uri)) });
+                // fall through
+            case REMOTE_DEVICES: {
+                trace("Deleting FxA devices: " + uri);
+                beginWrite(db);
+                deleted = deleteRemoteDevices(uri, selection, selectionArgs);
+                break;
+            }
+
             default: {
                 Table table = findTableFor(match);
                 if (table == null) {
                     throw new UnsupportedOperationException("Unknown delete URI " + uri);
                 }
                 trace("Deleting TABLE: " + uri);
                 beginWrite(db);
                 deleted = table.delete(db, uri, match, selection, selectionArgs);
@@ -716,16 +751,22 @@ public class BrowserProvider extends Sha
             }
 
             case PAGE_METADATA: {
                 trace("Insert on PAGE_METADATA: " + uri);
                 id = insertPageMetadata(uri, values);
                 break;
             }
 
+            case REMOTE_DEVICES: {
+                trace("Insert on REMOTE_DEVICES: " + uri);
+                id = insertFxADevice(uri, values);
+                break;
+            }
+
             default: {
                 Table table = findTableFor(match);
                 if (table == null) {
                     throw new UnsupportedOperationException("Unknown insert URI " + uri);
                 }
 
                 trace("Insert on TABLE: " + uri);
                 final SQLiteDatabase db = getWritableDatabase(uri);
@@ -1386,16 +1427,30 @@ public class BrowserProvider extends Sha
             case PAGE_METADATA: {
                 debug("PageMetadata query: " + uri);
 
                 qb.setProjectionMap(PAGE_METADATA_PROJECTION_MAP);
                 qb.setTables(TABLE_PAGE_METADATA);
                 break;
             }
 
+            case REMOTE_DEVICES_ID:
+                selection = DBUtils.concatenateWhere(selection, RemoteDevices._ID + " = ?");
+                selectionArgs = DBUtils.appendSelectionArgs(selectionArgs,
+                        new String[] { Long.toString(ContentUris.parseId(uri)) });
+                // fall through
+            case REMOTE_DEVICES: {
+                debug("FxA devices query: " + uri);
+
+                qb.setProjectionMap(REMOTE_DEVICES_PROJECTION_MAP);
+                qb.setTables(TABLE_REMOTE_DEVICES);
+
+                break;
+            }
+
             default: {
                 Table table = findTableFor(match);
                 if (table == null) {
                     throw new UnsupportedOperationException("Unknown query URI " + uri);
                 }
                 trace("Update TABLE: " + uri);
                 return table.query(db, uri, match, projection, selection, selectionArgs, sortOrder, groupBy, limit);
             }
@@ -1992,16 +2047,23 @@ public class BrowserProvider extends Sha
         beginWrite(db);
 
         // Perform INSERT OR REPLACE, there might be page metadata present and we want to replace it.
         // Depends on a conflict arising from unique foreign key (history_guid) constraint violation.
         return db.insertWithOnConflict(
                 TABLE_PAGE_METADATA, null, values, SQLiteDatabase.CONFLICT_REPLACE);
     }
 
+    private long insertFxADevice(final Uri uri, final ContentValues values) {
+        final SQLiteDatabase db = getWritableDatabase(uri);
+
+        beginWrite(db);
+        return db.insertOrThrow(TABLE_REMOTE_DEVICES, null, values);
+    }
+
     private long insertUrlAnnotation(final Uri uri, final ContentValues values) {
         final String url = values.getAsString(UrlAnnotations.URL);
         trace("Inserting url annotations for URL: " + url);
 
         final SQLiteDatabase db = getWritableDatabase(uri);
         beginWrite(db);
         return db.insertOrThrow(TABLE_URL_ANNOTATIONS, null, values);
     }
@@ -2015,16 +2077,23 @@ public class BrowserProvider extends Sha
 
     private int deletePageMetadata(final Uri uri, final String selection, final String[] selectionArgs) {
         trace("Deleting page metadata for URI: " + uri);
 
         final SQLiteDatabase db = getWritableDatabase(uri);
         return db.delete(TABLE_PAGE_METADATA, selection, selectionArgs);
     }
 
+    private int deleteRemoteDevices(final Uri uri, final String selection, final String[] selectionArgs) {
+        trace("Deleting FxA Devices for URI: " + uri);
+
+        final SQLiteDatabase db = getWritableDatabase(uri);
+        return db.delete(TABLE_REMOTE_DEVICES, selection, selectionArgs);
+    }
+
     private void updateUrlAnnotation(final Uri uri, final ContentValues values, final String selection, final String[] selectionArgs) {
         trace("Updating url annotation for URI: " + uri);
 
         final SQLiteDatabase db = getWritableDatabase(uri);
         db.update(TABLE_URL_ANNOTATIONS, values, selection, selectionArgs);
     }
 
     private int updateOrInsertThumbnail(Uri uri, ContentValues values, String selection,
@@ -2270,23 +2339,70 @@ public class BrowserProvider extends Sha
 
                 // If anything went wrong during insertion, we know that changes were rolled back.
                 // Inform our caller that we have failed.
                 } catch (Exception e) {
                     Log.e(LOGTAG, "Unexpected error while bulk inserting history", e);
                     result.putSerializable(BrowserContract.METHOD_RESULT, e);
                 }
                 break;
+            case BrowserContract.METHOD_REPLACE_REMOTE_CLIENTS:
+                try {
+                    final Uri uri = Uri.parse(uriArg);
+                    bulkReplaceRemoteDevices(uri, extras);
+                    result.putSerializable(BrowserContract.METHOD_RESULT, null);
+
+                    // If anything went wrong during insertion, we know that changes were rolled back.
+                    // Inform our caller that we have failed.
+                } catch (Exception e) {
+                    Log.e(LOGTAG, "Unexpected error while bulk inserting remote clients", e);
+                    result.putSerializable(BrowserContract.METHOD_RESULT, e);
+                }
+                break;
             default:
                 throw new IllegalArgumentException("Unknown method call: " + method);
         }
 
         return result;
     }
 
+    private void bulkReplaceRemoteDevices(final Uri uri, @NonNull Bundle dataBundle) {
+        final ContentValues[] values = (ContentValues[]) dataBundle.getParcelableArray(BrowserContract.METHOD_PARAM_DATA);
+
+        if (values == null) {
+            throw new IllegalArgumentException("Received null recordBundle while bulk inserting remote clients.");
+        }
+
+        final SQLiteDatabase db = getWritableDatabase(uri);
+
+        // Wrap everything in a transaction.
+        beginBatch(db);
+
+        try {
+            // First purge our list of remote devices.
+            // We pass "1" to get a count of the affected rows (see SQLiteDatabase#delete)
+            int count = deleteInTransaction(uri, "1", null);
+            Log.i(LOGTAG, "Deleted " + count + " remote devices.");
+
+            // Then insert the new ones.
+            for (int i = 0; i < values.length; i++) {
+                try {
+                    insertFxADevice(uri, values[i]);
+                } catch (Exception e) {
+                    Log.e(LOGTAG, "Could not insert device with ID " + values[i].getAsString(RemoteDevices.GUID) + ": " + e);
+                }
+            }
+            markBatchSuccessful(db);
+        } finally {
+            endBatch(db);
+        }
+
+        getContext().getContentResolver().notifyChange(uri, null, false);
+    }
+
     private void bulkInsertHistoryWithVisits(final SQLiteDatabase db, @NonNull Bundle dataBundle) {
         // NB: dataBundle structure:
         // Key METHOD_PARAM_DATA=[Bundle,...]
         // Each Bundle has keys METHOD_PARAM_OBJECT=ContentValues{HistoryRecord}, VISITS=ContentValues[]{visits}
         final Bundle[] recordBundles = (Bundle[]) dataBundle.getSerializable(BrowserContract.METHOD_PARAM_DATA);
 
         if (recordBundles == null) {
             throw new IllegalArgumentException("Received null recordBundle while bulk inserting history.");
--- a/mobile/android/base/java/org/mozilla/gecko/push/PushService.java
+++ b/mobile/android/base/java/org/mozilla/gecko/push/PushService.java
@@ -20,17 +20,17 @@ import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoProfile;
 import org.mozilla.gecko.GeckoService;
 import org.mozilla.gecko.GeckoThread;
 import org.mozilla.gecko.Telemetry;
 import org.mozilla.gecko.TelemetryContract;
 import org.mozilla.gecko.annotation.ReflectionTarget;
 import org.mozilla.gecko.db.BrowserDB;
 import org.mozilla.gecko.fxa.FxAccountConstants;
-import org.mozilla.gecko.fxa.FxAccountDeviceRegistrator;
+import org.mozilla.gecko.fxa.devices.FxAccountDeviceRegistrator;
 import org.mozilla.gecko.fxa.FxAccountPushHandler;
 import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
 import org.mozilla.gecko.fxa.login.State;
 import org.mozilla.gecko.gcm.GcmTokenClient;
 import org.mozilla.gecko.push.autopush.AutopushClientException;
 import org.mozilla.gecko.util.BundleEventListener;
 import org.mozilla.gecko.util.EventCallback;
 import org.mozilla.gecko.util.GeckoBundle;
--- a/mobile/android/base/java/org/mozilla/gecko/tabs/TabStripView.java
+++ b/mobile/android/base/java/org/mozilla/gecko/tabs/TabStripView.java
@@ -16,16 +16,17 @@ import android.animation.ObjectAnimator;
 import android.content.Context;
 import android.content.res.Resources;
 import android.graphics.Canvas;
 import android.graphics.LinearGradient;
 import android.graphics.Paint;
 import android.graphics.Shader;
 import android.support.v7.widget.LinearLayoutManager;
 import android.support.v7.widget.RecyclerView;
+import android.support.v7.widget.ViewUtils;
 import android.support.v7.widget.helper.ItemTouchHelper;
 import android.util.AttributeSet;
 import android.view.View;
 import android.view.ViewTreeObserver;
 import android.view.animation.DecelerateInterpolator;
 
 import java.util.ArrayList;
 import java.util.List;
@@ -216,49 +217,72 @@ public class TabStripView extends Recycl
     @Override
     protected void onSizeChanged(int w, int h, int oldw, int oldh) {
         super.onSizeChanged(w, h, oldw, oldh);
 
         if (w == oldw) {
             return;
         }
 
-        fadingEdgePaint.setShader(new LinearGradient(w - fadingEdgeSize, 0, w, 0,
-                new int[] { 0x0, 0x11292C29, 0xDD292C29 },
-                new float[] { 0, 0.4f, 1.0f }, Shader.TileMode.CLAMP));
+        // Gradient argb color stops.
+        final int transparent = 0x0;
+        final int inBetween = 0x11292C29;
+        final int darkest = 0xDD292C29;
+        if (ViewUtils.isLayoutRtl(this)) {
+            fadingEdgePaint.setShader(new LinearGradient(0, 0, fadingEdgeSize, 0,
+                    new int[] { darkest, inBetween, transparent },
+                    new float[] { 0, 0.6f, 1.0f }, Shader.TileMode.CLAMP));
+        } else {
+            fadingEdgePaint.setShader(new LinearGradient(w - fadingEdgeSize, 0, w, 0,
+                    new int[] { transparent, inBetween, darkest },
+                    new float[] { 0, 0.4f, 1.0f }, Shader.TileMode.CLAMP));
+        }
     }
 
-    private float getFadingEdgeStrength() {
+    private float getFadingEdgeStrength(boolean layoutIsLTR) {
         final int childCount = getChildCount();
         if (childCount == 0) {
             return 0.0f;
         } else {
             final LinearLayoutManager layoutManager = (LinearLayoutManager) getLayoutManager();
             if (layoutManager.findLastVisibleItemPosition() < adapter.getItemCount() - 1) {
                 return 1.0f;
             }
 
-            final int right = getChildAt(getChildCount() - 1).getRight();
-            final int paddingRight = getPaddingRight();
-            final int width = getWidth();
+            final float strength;
+            if (layoutIsLTR) {
+                final int right = getChildAt(getChildCount() - 1).getRight();
+                final int paddingRight = getPaddingRight();
+                final int width = getWidth();
 
-            final float strength = (right > width - paddingRight ?
-                    (float) (right - width + paddingRight) / fadingEdgeSize : 0.0f);
+                strength = (right > width - paddingRight ?
+                        (float) (right - width + paddingRight) / fadingEdgeSize : 0.0f);
+            } else {
+                final int left = getChildAt(getChildCount() - 1).getLeft();
+                final int paddingLeft = getPaddingLeft();
+
+                strength = left < paddingLeft ? (float) (paddingLeft - left) / fadingEdgeSize : 0.0f;
+            }
 
             return Math.max(0.0f, Math.min(strength, 1.0f));
         }
     }
 
     @Override
     public void draw(Canvas canvas) {
         super.draw(canvas);
-        final float strength = getFadingEdgeStrength();
+        final boolean isLTR = !ViewUtils.isLayoutRtl(this);
+        final float strength = getFadingEdgeStrength(isLTR);
         if (strength > 0.0f) {
-            final int r = getRight();
-            canvas.drawRect(r - fadingEdgeSize, getTop(), r, getBottom(), fadingEdgePaint);
+            if (isLTR) {
+                final int r = getRight();
+                canvas.drawRect(r - fadingEdgeSize, getTop(), r, getBottom(), fadingEdgePaint);
+            } else {
+                canvas.drawRect(0, getTop(), fadingEdgeSize, getBottom(), fadingEdgePaint);
+            }
             fadingEdgePaint.setAlpha((int) (strength * 255));
         }
     }
 
     private void animateRestoredTabs() {
         getViewTreeObserver().addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
             @Override
             public boolean onPreDraw() {
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -4177,17 +4177,17 @@ Tab.prototype = {
           if (!isFeed)
             return;
 
           jsonMessage = this.makeFeedMessage(target, type);
         } else if (list.indexOf("[search]") != -1 && aEvent.type == "DOMLinkAdded") {
           this.sendOpenSearchMessage(target);
         } else if (list.indexOf("[manifest]") != -1 &&
                    aEvent.type == "DOMLinkAdded" &&
-                   Services.prefs.getBoolPref("manifest.install.enabled")) {
+                   Services.prefs.getBoolPref("manifest.install.enabled", false)) {
           jsonMessage = this.makeManifestMessage(target);
         }
         if (!jsonMessage)
          return;
 
         GlobalEventDispatcher.sendRequest(jsonMessage);
         break;
       }
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/FxAccountClient.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/FxAccountClient.java
@@ -3,17 +3,17 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.background.fxa;
 
 import org.mozilla.gecko.background.fxa.FxAccountClient20.AccountStatusResponse;
 import org.mozilla.gecko.background.fxa.FxAccountClient20.RecoveryEmailStatusResponse;
 import org.mozilla.gecko.background.fxa.FxAccountClient20.RequestDelegate;
 import org.mozilla.gecko.background.fxa.FxAccountClient20.TwoKeys;
-import org.mozilla.gecko.fxa.FxAccountDevice;
+import org.mozilla.gecko.fxa.devices.FxAccountDevice;
 import org.mozilla.gecko.sync.ExtendedJSONObject;
 
 import java.util.List;
 
 public interface FxAccountClient {
   public void accountStatus(String uid, RequestDelegate<AccountStatusResponse> requestDelegate);
   public void recoveryEmailStatus(byte[] sessionToken, RequestDelegate<RecoveryEmailStatusResponse> requestDelegate);
   public void keys(byte[] keyFetchToken, RequestDelegate<TwoKeys> requestDelegate);
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/FxAccountClient20.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/background/fxa/FxAccountClient20.java
@@ -8,17 +8,17 @@ import android.support.annotation.NonNul
 
 import org.json.simple.JSONArray;
 import org.json.simple.JSONObject;
 import org.mozilla.gecko.background.common.log.Logger;
 import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientMalformedResponseException;
 import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException;
 import org.mozilla.gecko.fxa.FxAccountConstants;
 import org.mozilla.gecko.Locales;
-import org.mozilla.gecko.fxa.FxAccountDevice;
+import org.mozilla.gecko.fxa.devices.FxAccountDevice;
 import org.mozilla.gecko.sync.ExtendedJSONObject;
 import org.mozilla.gecko.sync.Utils;
 import org.mozilla.gecko.sync.crypto.HKDF;
 import org.mozilla.gecko.sync.net.AuthHeaderProvider;
 import org.mozilla.gecko.sync.net.BaseResource;
 import org.mozilla.gecko.sync.net.BaseResourceDelegate;
 import org.mozilla.gecko.sync.net.HawkAuthHeaderProvider;
 import org.mozilla.gecko.sync.net.Resource;
deleted file mode 100644
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/FxAccountDevice.java
+++ /dev/null
@@ -1,114 +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.fxa;
-
-import org.mozilla.gecko.sync.ExtendedJSONObject;
-
-public class FxAccountDevice {
-
-  public static final String JSON_KEY_NAME = "name";
-  public static final String JSON_KEY_ID = "id";
-  public static final String JSON_KEY_TYPE = "type";
-  public static final String JSON_KEY_ISCURRENTDEVICE = "isCurrentDevice";
-  public static final String JSON_KEY_PUSH_CALLBACK = "pushCallback";
-  public static final String JSON_KEY_PUSH_PUBLICKEY = "pushPublicKey";
-  public static final String JSON_KEY_PUSH_AUTHKEY = "pushAuthKey";
-
-  public final String id;
-  public final String name;
-  public final String type;
-  public final Boolean isCurrentDevice;
-  public final String pushCallback;
-  public final String pushPublicKey;
-  public final String pushAuthKey;
-
-  public FxAccountDevice(String name, String id, String type, Boolean isCurrentDevice,
-                         String pushCallback, String pushPublicKey, String pushAuthKey) {
-    this.name = name;
-    this.id = id;
-    this.type = type;
-    this.isCurrentDevice = isCurrentDevice;
-    this.pushCallback = pushCallback;
-    this.pushPublicKey = pushPublicKey;
-    this.pushAuthKey = pushAuthKey;
-  }
-
-  public static FxAccountDevice fromJson(ExtendedJSONObject json) {
-    String name = json.getString(JSON_KEY_NAME);
-    String id = json.getString(JSON_KEY_ID);
-    String type = json.getString(JSON_KEY_TYPE);
-    Boolean isCurrentDevice = json.getBoolean(JSON_KEY_ISCURRENTDEVICE);
-    String pushCallback = json.getString(JSON_KEY_PUSH_CALLBACK);
-    String pushPublicKey = json.getString(JSON_KEY_PUSH_PUBLICKEY);
-    String pushAuthKey = json.getString(JSON_KEY_PUSH_AUTHKEY);
-    return new FxAccountDevice(name, id, type, isCurrentDevice, pushCallback, pushPublicKey, pushAuthKey);
-  }
-
-  public ExtendedJSONObject toJson() {
-    final ExtendedJSONObject body = new ExtendedJSONObject();
-    if (this.name != null) {
-      body.put(JSON_KEY_NAME, this.name);
-    }
-    if (this.id != null) {
-      body.put(JSON_KEY_ID, this.id);
-    }
-    if (this.type != null) {
-      body.put(JSON_KEY_TYPE, this.type);
-    }
-    if (this.pushCallback != null) {
-      body.put(JSON_KEY_PUSH_CALLBACK, this.pushCallback);
-    }
-    if (this.pushPublicKey != null) {
-      body.put(JSON_KEY_PUSH_PUBLICKEY, this.pushPublicKey);
-    }
-    if (this.pushAuthKey != null) {
-      body.put(JSON_KEY_PUSH_AUTHKEY, this.pushAuthKey);
-    }
-    return body;
-  }
-
-  public static class Builder {
-    private String id;
-    private String name;
-    private String type;
-    private Boolean isCurrentDevice;
-    private String pushCallback;
-    private String pushPublicKey;
-    private String pushAuthKey;
-
-    public void id(String id) {
-      this.id = id;
-    }
-
-    public void name(String name) {
-      this.name = name;
-    }
-
-    public void type(String type) {
-      this.type = type;
-    }
-
-    public void isCurrentDevice() {
-      this.isCurrentDevice = Boolean.TRUE;
-    }
-
-    public void pushCallback(String pushCallback) {
-      this.pushCallback = pushCallback;
-    }
-
-    public void pushPublicKey(String pushPublicKey) {
-      this.pushPublicKey = pushPublicKey;
-    }
-
-    public void pushAuthKey(String pushAuthKey) {
-      this.pushAuthKey = pushAuthKey;
-    }
-
-    public FxAccountDevice build() {
-      return new FxAccountDevice(this.name, this.id, this.type, this.isCurrentDevice,
-                                 this.pushCallback, this.pushPublicKey, this.pushAuthKey);
-    }
-  }
-}
deleted file mode 100644
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/FxAccountDeviceRegistrator.java
+++ /dev/null
@@ -1,401 +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.fxa;
-
-import android.content.Context;
-import android.content.Intent;
-import android.support.annotation.NonNull;
-import android.support.annotation.Nullable;
-import android.support.annotation.VisibleForTesting;
-import android.text.TextUtils;
-import android.util.Log;
-
-import org.mozilla.gecko.background.common.log.Logger;
-import org.mozilla.gecko.background.fxa.FxAccountClient;
-import org.mozilla.gecko.background.fxa.FxAccountClient20;
-import org.mozilla.gecko.background.fxa.FxAccountClient20.AccountStatusResponse;
-import org.mozilla.gecko.background.fxa.FxAccountClient20.RequestDelegate;
-import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException;
-import org.mozilla.gecko.background.fxa.FxAccountRemoteError;
-import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
-import org.mozilla.gecko.fxa.login.State;
-import org.mozilla.gecko.sync.SharedPreferencesClientsDataDelegate;
-import org.mozilla.gecko.util.BundleEventListener;
-import org.mozilla.gecko.util.EventCallback;
-import org.mozilla.gecko.util.GeckoBundle;
-
-import java.io.UnsupportedEncodingException;
-import java.lang.ref.WeakReference;
-import java.lang.reflect.InvocationTargetException;
-import java.lang.reflect.Method;
-import java.security.GeneralSecurityException;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-
-/* This class provides a way to register the current device against FxA
- * and also stores the registration details in the Android FxAccount.
- * This should be used in a state where we possess a sessionToken, most likely the Engaged/Married states.
- */
-public class FxAccountDeviceRegistrator implements BundleEventListener {
-  private static final String LOG_TAG = "FxADeviceRegistrator";
-
-  // The autopush endpoint expires stale channel subscriptions every 30 days (at a set time during
-  // the month, although we don't depend on this). To avoid the FxA service channel silently
-  // expiring from underneath us, we unsubscribe and resubscribe every 21 days.
-  // Note that this simple schedule means that we might unsubscribe perfectly valid (but old)
-  // subscriptions. This will be improved as part of Bug 1345651.
-  @VisibleForTesting
-  static final long TIME_BETWEEN_CHANNEL_REGISTRATION_IN_MILLIS = 21 * 24 * 60 * 60 * 1000L;
-
-  @VisibleForTesting
-  static final long RETRY_TIME_AFTER_GCM_DISABLED_ERROR = 15 * 24 * 60 * 60 * 1000L;
-
-
-  public static final String PUSH_SUBSCRIPTION_REPLY_BUNDLE_KEY_ERROR = "error";
-  @VisibleForTesting
-  static final long ERROR_GCM_DISABLED = 2154627078L; // = NS_ERROR_DOM_PUSH_GCM_DISABLED
-
-  // The current version of the device registration, we use this to re-register
-  // devices after we update what we send on device registration.
-  @VisibleForTesting
-  static final Integer DEVICE_REGISTRATION_VERSION = 2;
-
-  private static FxAccountDeviceRegistrator instance;
-  private final WeakReference<Context> context;
-
-  private FxAccountDeviceRegistrator(Context appContext) {
-    this.context = new WeakReference<>(appContext);
-  }
-
-  private static FxAccountDeviceRegistrator getInstance(Context appContext) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
-    if (instance == null) {
-      final FxAccountDeviceRegistrator tempInstance = new FxAccountDeviceRegistrator(appContext);
-      tempInstance.setupListeners(); // Set up listener for FxAccountPush:Subscribe:Response
-      instance = tempInstance;
-    }
-    return instance;
-  }
-
-  public static boolean shouldRegister(final AndroidFxAccount fxAccount) {
-    if (fxAccount.getDeviceRegistrationVersion() != FxAccountDeviceRegistrator.DEVICE_REGISTRATION_VERSION ||
-            TextUtils.isEmpty(fxAccount.getDeviceId())) {
-      return true;
-    }
-    // At this point, we have a working up-to-date registration, but it might be a partial one
-    // (no push registration).
-    return fxAccount.getDevicePushRegistrationError() == ERROR_GCM_DISABLED &&
-           (System.currentTimeMillis() - fxAccount.getDevicePushRegistrationErrorTime()) > RETRY_TIME_AFTER_GCM_DISABLED_ERROR;
-  }
-
-  public static boolean shouldRenewRegistration(final AndroidFxAccount fxAccount) {
-    final long deviceRegistrationTimestamp = fxAccount.getDeviceRegistrationTimestamp();
-    // NB: we're comparing wall clock to wall clock, at different points in time.
-    // It's possible that wall clocks have changed, and our comparison will be meaningless.
-    // However, this happens in the context of a sync, and we won't be able to sync anyways if our
-    // wall clock deviates too much from time on the server.
-    return (System.currentTimeMillis() - deviceRegistrationTimestamp) > TIME_BETWEEN_CHANNEL_REGISTRATION_IN_MILLIS;
-  }
-
-  public static void register(Context context) {
-    final Context appContext = context.getApplicationContext();
-    try {
-      getInstance(appContext).beginRegistration(appContext);
-    } catch (Exception e) {
-      Log.e(LOG_TAG, "Could not start FxA device registration", e);
-    }
-  }
-
-  public static void renewRegistration(Context context) {
-    final Context appContext = context.getApplicationContext();
-    try {
-      getInstance(appContext).beginRegistrationRenewal(appContext);
-    } catch (Exception e) {
-      Log.e(LOG_TAG, "Could not start FxA device re-registration", e);
-    }
-  }
-
-  private void beginRegistration(Context context) {
-    // Fire up gecko and send event
-    // We create the Intent ourselves instead of using GeckoService.getIntentToCreateServices
-    // because we can't import these modules (circular dependency between browser and services)
-    final Intent geckoIntent = buildCreatePushServiceIntent(context, "android-fxa-subscribe");
-    context.startService(geckoIntent);
-    // -> handleMessage()
-  }
-
-  private void beginRegistrationRenewal(Context context) {
-    // Same as registration, but unsubscribe first to get a fresh subscription.
-    final Intent geckoIntent = buildCreatePushServiceIntent(context, "android-fxa-resubscribe");
-    context.startService(geckoIntent);
-    // -> handleMessage()
-  }
-
-  private Intent buildCreatePushServiceIntent(final Context context, final String data) {
-    final Intent intent = new Intent();
-    intent.setAction("create-services");
-    intent.setClassName(context, "org.mozilla.gecko.GeckoService");
-    intent.putExtra("category", "android-push-service");
-    intent.putExtra("data", data);
-    final AndroidFxAccount fxAccount = AndroidFxAccount.fromContext(context);
-    intent.putExtra("org.mozilla.gecko.intent.PROFILE_NAME", fxAccount.getProfile());
-    return intent;
-  }
-
-  @Override
-  public void handleMessage(String event, GeckoBundle message, EventCallback callback) {
-    if ("FxAccountsPush:Subscribe:Response".equals(event)) {
-      handlePushSubscriptionResponse(message);
-    } else {
-      Log.e(LOG_TAG, "No action defined for " + event);
-    }
-  }
-
-  private void handlePushSubscriptionResponse(final GeckoBundle message) {
-    // Make sure the context has not been gc'd during the push registration
-    // and the FxAccount still exists.
-    final Context context = this.context.get();
-    if (context == null) {
-      throw new IllegalStateException("Application context has been gc'ed");
-    }
-    final AndroidFxAccount fxAccount = AndroidFxAccount.fromContext(context);
-    if (fxAccount == null) {
-      Log.e(LOG_TAG, "AndroidFxAccount is null");
-      return;
-    }
-
-    fxAccount.resetDevicePushRegistrationError();
-    final long error = getSubscriptionReplyError(message);
-
-    final FxAccountDevice device;
-    if (error == 0L) {
-      Log.i(LOG_TAG, "Push registration succeeded. Beginning normal FxA Registration.");
-      device = buildFxAccountDevice(context, fxAccount, message.getBundle("subscription"));
-    } else {
-      fxAccount.setDevicePushRegistrationError(error, System.currentTimeMillis());
-      Log.i(LOG_TAG, "Push registration failed. Beginning degraded FxA Registration.");
-      device = buildFxAccountDevice(context, fxAccount);
-    }
-
-    doFxaRegistration(context, fxAccount, device, true);
-  }
-
-  private long getSubscriptionReplyError(final GeckoBundle message) {
-    String errorStr = message.getString(PUSH_SUBSCRIPTION_REPLY_BUNDLE_KEY_ERROR);
-    if (TextUtils.isEmpty(errorStr)) {
-      return 0L;
-    }
-    return Long.parseLong(errorStr);
-  }
-
-  private static void doFxaRegistration(final Context context, final AndroidFxAccount fxAccount,
-                                        final FxAccountDevice device, final boolean allowRecursion) {
-    final byte[] sessionToken;
-    try {
-      sessionToken = fxAccount.getState().getSessionToken();
-    } catch (State.NotASessionTokenState e) {
-      Log.e(LOG_TAG, "Could not get a session token", e);
-      return;
-    }
-
-    if (device.id == null) {
-      Log.i(LOG_TAG, "Attempting registration for a new device");
-    } else {
-      Log.i(LOG_TAG, "Attempting registration for an existing device");
-    }
-
-    final ExecutorService executor = Executors.newSingleThreadExecutor(); // Not called often, it's okay to spawn another thread
-    final FxAccountClient20 fxAccountClient =
-            new FxAccountClient20(fxAccount.getAccountServerURI(), executor);
-    fxAccountClient.registerOrUpdateDevice(sessionToken, device, new RequestDelegate<FxAccountDevice>() {
-      @Override
-      public void handleError(Exception e) {
-        Log.e(LOG_TAG, "Error while updating a device registration: ", e);
-        fxAccount.setDeviceRegistrationTimestamp(0L);
-      }
-
-      @Override
-      public void handleFailure(FxAccountClientRemoteException error) {
-        Log.e(LOG_TAG, "Error while updating a device registration: ", error);
-
-        fxAccount.setDeviceRegistrationTimestamp(0L);
-
-        if (error.httpStatusCode == 400) {
-          if (error.apiErrorNumber == FxAccountRemoteError.UNKNOWN_DEVICE) {
-            recoverFromUnknownDevice(fxAccount);
-          } else if (error.apiErrorNumber == FxAccountRemoteError.DEVICE_SESSION_CONFLICT) {
-            // This can happen if a device was already registered using our session token, and we
-            // tried to create a new one (no id field).
-            recoverFromDeviceSessionConflict(error, fxAccountClient, sessionToken, fxAccount, device,
-                    context, allowRecursion);
-          }
-        } else
-        if (error.httpStatusCode == 401
-                && error.apiErrorNumber == FxAccountRemoteError.INVALID_AUTHENTICATION_TOKEN) {
-          handleTokenError(error, fxAccountClient, fxAccount);
-        } else {
-          logErrorAndResetDeviceRegistrationVersionAndTimestamp(error, fxAccount);
-        }
-      }
-
-      @Override
-      public void handleSuccess(FxAccountDevice result) {
-        Log.i(LOG_TAG, "Device registration complete");
-        Logger.pii(LOG_TAG, "Registered device ID: " + result.id);
-        Log.i(LOG_TAG, "Setting DEVICE_REGISTRATION_VERSION to " + DEVICE_REGISTRATION_VERSION);
-        fxAccount.setFxAUserData(result.id, DEVICE_REGISTRATION_VERSION, System.currentTimeMillis());
-      }
-    });
-  }
-
-  private static FxAccountDevice buildFxAccountDevice(Context context, AndroidFxAccount fxAccount) {
-    return makeFxADeviceCommonBuilder(context, fxAccount).build();
-  }
-
-  private static FxAccountDevice buildFxAccountDevice(Context context, AndroidFxAccount fxAccount, @NonNull GeckoBundle subscription) {
-    final FxAccountDevice.Builder builder = makeFxADeviceCommonBuilder(context, fxAccount);
-    final String pushCallback = subscription.getString("pushCallback");
-    final String pushPublicKey = subscription.getString("pushPublicKey");
-    final String pushAuthKey = subscription.getString("pushAuthKey");
-    if (!TextUtils.isEmpty(pushCallback) && !TextUtils.isEmpty(pushPublicKey) &&
-        !TextUtils.isEmpty(pushAuthKey)) {
-      builder.pushCallback(pushCallback);
-      builder.pushPublicKey(pushPublicKey);
-      builder.pushAuthKey(pushAuthKey);
-    }
-    return builder.build();
-  }
-
-  // Do not call this directly, use buildFxAccountDevice instead.
-  private static FxAccountDevice.Builder makeFxADeviceCommonBuilder(Context context, AndroidFxAccount fxAccount) {
-    final String deviceId = fxAccount.getDeviceId();
-    final String clientName = getClientName(fxAccount, context);
-
-    final FxAccountDevice.Builder builder = new FxAccountDevice.Builder();
-    builder.name(clientName);
-    builder.type("mobile");
-    if (!TextUtils.isEmpty(deviceId)) {
-      builder.id(deviceId);
-    }
-    return builder;
-  }
-
-  private static void logErrorAndResetDeviceRegistrationVersionAndTimestamp(
-      final FxAccountClientRemoteException error, final AndroidFxAccount fxAccount) {
-    Log.e(LOG_TAG, "Device registration failed", error);
-    fxAccount.resetDeviceRegistrationVersion();
-    fxAccount.setDeviceRegistrationTimestamp(0L);
-  }
-
-  @Nullable
-  private static String getClientName(final AndroidFxAccount fxAccount, final Context context) {
-    try {
-      final SharedPreferencesClientsDataDelegate clientsDataDelegate =
-          new SharedPreferencesClientsDataDelegate(fxAccount.getSyncPrefs(), context);
-      return clientsDataDelegate.getClientName();
-    } catch (UnsupportedEncodingException | GeneralSecurityException e) {
-      Log.e(LOG_TAG, "Unable to get client name.", e);
-      return null;
-    }
-  }
-
-  private static void handleTokenError(final FxAccountClientRemoteException error,
-                                       final FxAccountClient fxAccountClient,
-                                       final AndroidFxAccount fxAccount) {
-    Log.i(LOG_TAG, "Recovering from invalid token error: ", error);
-    logErrorAndResetDeviceRegistrationVersionAndTimestamp(error, fxAccount);
-    fxAccountClient.accountStatus(fxAccount.getState().uid,
-        new RequestDelegate<AccountStatusResponse>() {
-      @Override
-      public void handleError(Exception e) {
-      }
-
-      @Override
-      public void handleFailure(FxAccountClientRemoteException e) {
-      }
-
-      @Override
-      public void handleSuccess(AccountStatusResponse result) {
-        final State doghouseState = fxAccount.getState().makeDoghouseState();
-        if (!result.exists) {
-          Log.i(LOG_TAG, "token invalidated because the account no longer exists");
-          // TODO: Should be in a "I have an Android account, but the FxA is gone." State.
-          // This will do for now..
-          fxAccount.setState(doghouseState);
-          return;
-        }
-        Log.e(LOG_TAG, "sessionToken invalid");
-        fxAccount.setState(doghouseState);
-      }
-    });
-  }
-
-  private static void recoverFromUnknownDevice(final AndroidFxAccount fxAccount) {
-    Log.i(LOG_TAG, "unknown device id, clearing the cached device id");
-    fxAccount.setDeviceId(null);
-  }
-
-  /**
-   * Will call delegate#complete in all cases
-   */
-  private static void recoverFromDeviceSessionConflict(final FxAccountClientRemoteException error,
-                                                       final FxAccountClient fxAccountClient,
-                                                       final byte[] sessionToken,
-                                                       final AndroidFxAccount fxAccount,
-                                                       final FxAccountDevice device,
-                                                       final Context context,
-                                                       final boolean allowRecursion) {
-    // Recovery strategy: re-try a registration, UPDATING (instead of creating) the device.
-    // We do that by finding the device ID who conflicted with us and try a registration update
-    // using that id.
-    Log.w(LOG_TAG, "device session conflict, attempting to ascertain the correct device id");
-    fxAccountClient.deviceList(sessionToken, new RequestDelegate<FxAccountDevice[]>() {
-      private void onError() {
-        Log.e(LOG_TAG, "failed to recover from device-session conflict");
-        logErrorAndResetDeviceRegistrationVersionAndTimestamp(error, fxAccount);
-      }
-
-      @Override
-      public void handleError(Exception e) {
-        onError();
-      }
-
-      @Override
-      public void handleFailure(FxAccountClientRemoteException e) {
-        onError();
-      }
-
-      @Override
-      public void handleSuccess(FxAccountDevice[] devices) {
-        for (final FxAccountDevice fxaDevice : devices) {
-          if (!fxaDevice.isCurrentDevice) {
-            continue;
-          }
-          fxAccount.setFxAUserData(fxaDevice.id, 0, 0L); // Reset device registration version/timestamp
-          if (!allowRecursion) {
-            Log.d(LOG_TAG, "Failure to register a device on the second try");
-            break;
-          }
-          final FxAccountDevice updatedDevice = new FxAccountDevice(device.name, fxaDevice.id, device.type,
-                                                                    device.isCurrentDevice, device.pushCallback,
-                                                                    device.pushPublicKey, device.pushAuthKey);
-          doFxaRegistration(context, fxAccount, updatedDevice, false);
-          return;
-        }
-        onError();
-      }
-    });
-  }
-
-  private void setupListeners() throws ClassNotFoundException, NoSuchMethodException,
-          InvocationTargetException, IllegalAccessException {
-    // We have no choice but to use reflection here, sorry :(
-    final Class<?> eventDispatcher = Class.forName("org.mozilla.gecko.EventDispatcher");
-    final Method getInstance = eventDispatcher.getMethod("getInstance");
-    final Object instance = getInstance.invoke(null);
-    final Method registerBackgroundThreadListener = eventDispatcher.getMethod("registerBackgroundThreadListener",
-            BundleEventListener.class, String[].class);
-    registerBackgroundThreadListener.invoke(instance, this, new String[] { "FxAccountsPush:Subscribe:Response" });
-  }
-}
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/authenticator/AndroidFxAccount.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/authenticator/AndroidFxAccount.java
@@ -800,49 +800,57 @@ public class AndroidFxAccount {
         intent.putExtra(FxAccountProfileService.KEY_AUTH_TOKEN, authToken);
         intent.putExtra(FxAccountProfileService.KEY_PROFILE_SERVER_URI, getProfileServerURI());
         intent.putExtra(FxAccountProfileService.KEY_RESULT_RECEIVER, new ProfileResultReceiver(new Handler()));
         context.startService(intent);
       }
     });
   }
 
-  @SuppressWarnings("unchecked")
-  private <T extends Number> T getUserDataNumber(String key, T defaultValue) {
+  private long getUserDataLong(String key, long defaultValue) {
     final String numStr = accountManager.getUserData(account, key);
     if (TextUtils.isEmpty(numStr)) {
       return defaultValue;
     }
     try {
-      return (T) NumberFormat.getInstance().parse(numStr);
-    } catch (ParseException e) {
-      Logger.warn(LOG_TAG, "Couldn't parse " + key + "; defaulting to 0L.", e);
+      return Long.parseLong(key);
+    } catch (NumberFormatException e) {
+      Logger.warn(LOG_TAG, "Couldn't parse " + key + "; defaulting to " + defaultValue, e);
       return defaultValue;
     }
   }
 
   @Nullable
   public synchronized String getDeviceId() {
     return accountManager.getUserData(account, ACCOUNT_KEY_DEVICE_ID);
   }
 
   public synchronized int getDeviceRegistrationVersion() {
-    return getUserDataNumber(ACCOUNT_KEY_DEVICE_REGISTRATION_VERSION, 0);
+    final String numStr = accountManager.getUserData(account, ACCOUNT_KEY_DEVICE_REGISTRATION_VERSION);
+    if (TextUtils.isEmpty(numStr)) {
+      return 0;
+    }
+    try {
+      return Integer.parseInt(numStr);
+    } catch (NumberFormatException e) {
+      Logger.warn(LOG_TAG, "Couldn't parse ACCOUNT_KEY_DEVICE_REGISTRATION_VERSION; defaulting to 0", e);
+      return 0;
+    }
   }
 
   public synchronized long getDeviceRegistrationTimestamp() {
-    return getUserDataNumber(ACCOUNT_KEY_DEVICE_REGISTRATION_TIMESTAMP, 0L);
+    return getUserDataLong(ACCOUNT_KEY_DEVICE_REGISTRATION_TIMESTAMP, 0L);
   }
 
   public synchronized long getDevicePushRegistrationError() {
-    return getUserDataNumber(ACCOUNT_KEY_DEVICE_PUSH_REGISTRATION_ERROR, 0L);
+    return getUserDataLong(ACCOUNT_KEY_DEVICE_PUSH_REGISTRATION_ERROR, 0L);
   }
 
   public synchronized long getDevicePushRegistrationErrorTime() {
-    return getUserDataNumber(ACCOUNT_KEY_DEVICE_PUSH_REGISTRATION_ERROR_TIME, 0L);
+    return getUserDataLong(ACCOUNT_KEY_DEVICE_PUSH_REGISTRATION_ERROR_TIME, 0L);
   }
 
   public synchronized void setDeviceId(String id) {
     accountManager.setUserData(account, ACCOUNT_KEY_DEVICE_ID, id);
   }
 
   public synchronized void setDeviceRegistrationVersion(int deviceRegistrationVersion) {
     accountManager.setUserData(account, ACCOUNT_KEY_DEVICE_REGISTRATION_VERSION,
new file mode 100644
--- /dev/null
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/devices/FxAccountDevice.java
@@ -0,0 +1,115 @@
+/* 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.fxa.devices;
+
+import org.mozilla.gecko.sync.ExtendedJSONObject;
+
+public class FxAccountDevice {
+
+  private static final String JSON_KEY_NAME = "name";
+  private static final String JSON_KEY_ID = "id";
+  private static final String JSON_KEY_TYPE = "type";
+  private static final String JSON_KEY_ISCURRENTDEVICE = "isCurrentDevice";
+  private static final String JSON_KEY_PUSH_CALLBACK = "pushCallback";
+  private static final String JSON_KEY_PUSH_PUBLICKEY = "pushPublicKey";
+  private static final String JSON_KEY_PUSH_AUTHKEY = "pushAuthKey";
+  private static final String JSON_LAST_ACCESS_TIME = "lastAccessTime";
+
+  public final String id;
+  public final String name;
+  public final String type;
+  public final Boolean isCurrentDevice;
+  public final Long lastAccessTime;
+  public final String pushCallback;
+  public final String pushPublicKey;
+  public final String pushAuthKey;
+
+  public FxAccountDevice(String name, String id, String type, Boolean isCurrentDevice,
+                         Long lastAccessTime, String pushCallback, String pushPublicKey,
+                         String pushAuthKey) {
+    this.name = name;
+    this.id = id;
+    this.type = type;
+    this.isCurrentDevice = isCurrentDevice;
+    this.lastAccessTime = lastAccessTime;
+    this.pushCallback = pushCallback;
+    this.pushPublicKey = pushPublicKey;
+    this.pushAuthKey = pushAuthKey;
+  }
+
+  public static FxAccountDevice fromJson(ExtendedJSONObject json) {
+    final String name = json.getString(JSON_KEY_NAME);
+    final String id = json.getString(JSON_KEY_ID);
+    final String type = json.getString(JSON_KEY_TYPE);
+    final Boolean isCurrentDevice = json.getBoolean(JSON_KEY_ISCURRENTDEVICE);
+    final Long lastAccessTime = json.getLong(JSON_LAST_ACCESS_TIME);
+    final String pushCallback = json.getString(JSON_KEY_PUSH_CALLBACK);
+    final String pushPublicKey = json.getString(JSON_KEY_PUSH_PUBLICKEY);
+    final String pushAuthKey = json.getString(JSON_KEY_PUSH_AUTHKEY);
+    return new FxAccountDevice(name, id, type, isCurrentDevice, lastAccessTime, pushCallback,
+                               pushPublicKey, pushAuthKey);
+  }
+
+  public ExtendedJSONObject toJson() {
+    final ExtendedJSONObject body = new ExtendedJSONObject();
+    if (this.name != null) {
+      body.put(JSON_KEY_NAME, this.name);
+    }
+    if (this.id != null) {
+      body.put(JSON_KEY_ID, this.id);
+    }
+    if (this.type != null) {
+      body.put(JSON_KEY_TYPE, this.type);
+    }
+    if (this.pushCallback != null) {
+      body.put(JSON_KEY_PUSH_CALLBACK, this.pushCallback);
+    }
+    if (this.pushPublicKey != null) {
+      body.put(JSON_KEY_PUSH_PUBLICKEY, this.pushPublicKey);
+    }
+    if (this.pushAuthKey != null) {
+      body.put(JSON_KEY_PUSH_AUTHKEY, this.pushAuthKey);
+    }
+    return body;
+  }
+
+  public static class Builder {
+    private String id;
+    private String name;
+    private String type;
+    private String pushCallback;
+    private String pushPublicKey;
+    private String pushAuthKey;
+
+    public void id(String id) {
+      this.id = id;
+    }
+
+    public void name(String name) {
+      this.name = name;
+    }
+
+    public void type(String type) {
+      this.type = type;
+    }
+
+    public void pushCallback(String pushCallback) {
+      this.pushCallback = pushCallback;
+    }
+
+    public void pushPublicKey(String pushPublicKey) {
+      this.pushPublicKey = pushPublicKey;
+    }
+
+    public void pushAuthKey(String pushAuthKey) {
+      this.pushAuthKey = pushAuthKey;
+    }
+
+    public FxAccountDevice build() {
+      return new FxAccountDevice(this.name, this.id, this.type, null, null,
+                                 this.pushCallback, this.pushPublicKey, this.pushAuthKey);
+    }
+  }
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/devices/FxAccountDeviceListUpdater.java
@@ -0,0 +1,111 @@
+/* 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.fxa.devices;
+
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.net.Uri;
+import android.os.Bundle;
+import android.support.annotation.NonNull;
+import android.support.annotation.VisibleForTesting;
+import android.util.Log;
+
+import org.mozilla.gecko.background.fxa.FxAccountClient;
+import org.mozilla.gecko.background.fxa.FxAccountClient20;
+import org.mozilla.gecko.background.fxa.FxAccountClientException;
+import org.mozilla.gecko.db.BrowserContract;
+import org.mozilla.gecko.db.BrowserContract.RemoteDevices;
+import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
+import org.mozilla.gecko.fxa.login.State;
+
+import java.util.concurrent.Executor;
+
+public class FxAccountDeviceListUpdater implements FxAccountClient20.RequestDelegate<FxAccountDevice[]> {
+    private static final String LOG_TAG = "FxADeviceListUpdater";
+
+    private final AndroidFxAccount fxAccount;
+    private final ContentResolver contentResolver;
+
+    public FxAccountDeviceListUpdater(final AndroidFxAccount fxAccount, final ContentResolver cr) {
+        this.fxAccount = fxAccount;
+        this.contentResolver = cr;
+    }
+
+    @Override
+    public void handleSuccess(final FxAccountDevice[] result) {
+        final Uri uri = RemoteDevices.CONTENT_URI
+                        .buildUpon()
+                        .appendQueryParameter(BrowserContract.PARAM_PROFILE, fxAccount.getProfile())
+                        .build();
+
+        final Bundle valuesBundle = new Bundle();
+        final ContentValues[] insertValues = new ContentValues[result.length];
+
+        final long now = System.currentTimeMillis();
+        for (int i = 0; i < result.length; i++) {
+            final FxAccountDevice fxADevice = result[i];
+            final ContentValues deviceValues = new ContentValues();
+            deviceValues.put(RemoteDevices.GUID, fxADevice.id);
+            deviceValues.put(RemoteDevices.TYPE, fxADevice.type);
+            deviceValues.put(RemoteDevices.NAME, fxADevice.name);
+            deviceValues.put(RemoteDevices.IS_CURRENT_DEVICE, fxADevice.isCurrentDevice);
+            deviceValues.put(RemoteDevices.DATE_CREATED, now);
+            deviceValues.put(RemoteDevices.DATE_MODIFIED, now);
+            // TODO: Remove that line once FxA sends lastAccessTime all the time.
+            final Long lastAccessTime = fxADevice.lastAccessTime != null ? fxADevice.lastAccessTime : 0;
+            deviceValues.put(RemoteDevices.LAST_ACCESS_TIME, lastAccessTime);
+            insertValues[i] = deviceValues;
+        }
+        valuesBundle.putParcelableArray(BrowserContract.METHOD_PARAM_DATA, insertValues);
+        try {
+            contentResolver.call(uri, BrowserContract.METHOD_REPLACE_REMOTE_CLIENTS, uri.toString(),
+                                 valuesBundle);
+            Log.i(LOG_TAG, "FxA Device list update done.");
+        } catch (Exception e) {
+            Log.e(LOG_TAG, "Error persisting the new remote device list.", e);
+        }
+    }
+
+    @Override
+    public void handleError(Exception e) {
+        onError(e);
+    }
+
+    @Override
+    public void handleFailure(FxAccountClientException.FxAccountClientRemoteException e) {
+        onError(e);
+    }
+
+    private void onError(final Exception e) {
+        Log.e(LOG_TAG, "Error while getting the FxA device list.", e);
+    }
+
+    @VisibleForTesting
+    FxAccountClient getSynchronousFxaClient() {
+        return new FxAccountClient20(fxAccount.getAccountServerURI(),
+                // Current thread executor :)
+                new Executor() {
+                    @Override
+                    public void execute(@NonNull Runnable runnable) {
+                        runnable.run();
+                    }
+                }
+        );
+    }
+
+    public void update() {
+        Log.i(LOG_TAG, "Beginning FxA device list update.");
+        final byte[] sessionToken;
+        try {
+            sessionToken = fxAccount.getState().getSessionToken();
+        } catch (State.NotASessionTokenState e) {
+            // This should never happen, because the caller (FxAccountSyncAdapter) verifies that
+            // we are in a token state before calling this method.
+            throw new IllegalStateException("Could not get a session token during Sync (?) " + e);
+        }
+        final FxAccountClient fxaClient = getSynchronousFxaClient();
+        fxaClient.deviceList(sessionToken, this);
+    }
+}
new file mode 100644
--- /dev/null
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/devices/FxAccountDeviceRegistrator.java
@@ -0,0 +1,402 @@
+/* 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.fxa.devices;
+
+import android.content.Context;
+import android.content.Intent;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.support.annotation.VisibleForTesting;
+import android.text.TextUtils;
+import android.util.Log;
+
+import org.mozilla.gecko.background.common.log.Logger;
+import org.mozilla.gecko.background.fxa.FxAccountClient;
+import org.mozilla.gecko.background.fxa.FxAccountClient20;
+import org.mozilla.gecko.background.fxa.FxAccountClient20.AccountStatusResponse;
+import org.mozilla.gecko.background.fxa.FxAccountClient20.RequestDelegate;
+import org.mozilla.gecko.background.fxa.FxAccountClientException.FxAccountClientRemoteException;
+import org.mozilla.gecko.background.fxa.FxAccountRemoteError;
+import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
+import org.mozilla.gecko.fxa.login.State;
+import org.mozilla.gecko.sync.SharedPreferencesClientsDataDelegate;
+import org.mozilla.gecko.util.BundleEventListener;
+import org.mozilla.gecko.util.EventCallback;
+import org.mozilla.gecko.util.GeckoBundle;
+
+import java.io.UnsupportedEncodingException;
+import java.lang.ref.WeakReference;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.security.GeneralSecurityException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+
+/* This class provides a way to register the current device against FxA
+ * and also stores the registration details in the Android FxAccount.
+ * This should be used in a state where we possess a sessionToken, most likely the Engaged/Married states.
+ */
+public class FxAccountDeviceRegistrator implements BundleEventListener {
+  private static final String LOG_TAG = "FxADeviceRegistrator";
+
+  // The autopush endpoint expires stale channel subscriptions every 30 days (at a set time during
+  // the month, although we don't depend on this). To avoid the FxA service channel silently
+  // expiring from underneath us, we unsubscribe and resubscribe every 21 days.
+  // Note that this simple schedule means that we might unsubscribe perfectly valid (but old)
+  // subscriptions. This will be improved as part of Bug 1345651.
+  @VisibleForTesting
+  static final long TIME_BETWEEN_CHANNEL_REGISTRATION_IN_MILLIS = 21 * 24 * 60 * 60 * 1000L;
+
+  @VisibleForTesting
+  static final long RETRY_TIME_AFTER_GCM_DISABLED_ERROR = 15 * 24 * 60 * 60 * 1000L;
+
+
+  public static final String PUSH_SUBSCRIPTION_REPLY_BUNDLE_KEY_ERROR = "error";
+  @VisibleForTesting
+  static final long ERROR_GCM_DISABLED = 2154627078L; // = NS_ERROR_DOM_PUSH_GCM_DISABLED
+
+  // The current version of the device registration, we use this to re-register
+  // devices after we update what we send on device registration.
+  @VisibleForTesting
+  static final Integer DEVICE_REGISTRATION_VERSION = 2;
+
+  private static FxAccountDeviceRegistrator instance;
+  private final WeakReference<Context> context;
+
+  private FxAccountDeviceRegistrator(Context appContext) {
+    this.context = new WeakReference<>(appContext);
+  }
+
+  private static FxAccountDeviceRegistrator getInstance(Context appContext) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
+    if (instance == null) {
+      final FxAccountDeviceRegistrator tempInstance = new FxAccountDeviceRegistrator(appContext);
+      tempInstance.setupListeners(); // Set up listener for FxAccountPush:Subscribe:Response
+      instance = tempInstance;
+    }
+    return instance;
+  }
+
+  public static boolean shouldRegister(final AndroidFxAccount fxAccount) {
+    if (fxAccount.getDeviceRegistrationVersion() != FxAccountDeviceRegistrator.DEVICE_REGISTRATION_VERSION ||
+            TextUtils.isEmpty(fxAccount.getDeviceId())) {
+      return true;
+    }
+    // At this point, we have a working up-to-date registration, but it might be a partial one
+    // (no push registration).
+    return fxAccount.getDevicePushRegistrationError() == ERROR_GCM_DISABLED &&
+           (System.currentTimeMillis() - fxAccount.getDevicePushRegistrationErrorTime()) > RETRY_TIME_AFTER_GCM_DISABLED_ERROR;
+  }
+
+  public static boolean shouldRenewRegistration(final AndroidFxAccount fxAccount) {
+    final long deviceRegistrationTimestamp = fxAccount.getDeviceRegistrationTimestamp();
+    // NB: we're comparing wall clock to wall clock, at different points in time.
+    // It's possible that wall clocks have changed, and our comparison will be meaningless.
+    // However, this happens in the context of a sync, and we won't be able to sync anyways if our
+    // wall clock deviates too much from time on the server.
+    return (System.currentTimeMillis() - deviceRegistrationTimestamp) > TIME_BETWEEN_CHANNEL_REGISTRATION_IN_MILLIS;
+  }
+
+  public static void register(Context context) {
+    final Context appContext = context.getApplicationContext();
+    try {
+      getInstance(appContext).beginRegistration(appContext);
+    } catch (Exception e) {
+      Log.e(LOG_TAG, "Could not start FxA device registration", e);
+    }
+  }
+
+  public static void renewRegistration(Context context) {
+    final Context appContext = context.getApplicationContext();
+    try {
+      getInstance(appContext).beginRegistrationRenewal(appContext);
+    } catch (Exception e) {
+      Log.e(LOG_TAG, "Could not start FxA device re-registration", e);
+    }
+  }
+
+  private void beginRegistration(Context context) {
+    // Fire up gecko and send event
+    // We create the Intent ourselves instead of using GeckoService.getIntentToCreateServices
+    // because we can't import these modules (circular dependency between browser and services)
+    final Intent geckoIntent = buildCreatePushServiceIntent(context, "android-fxa-subscribe");
+    context.startService(geckoIntent);
+    // -> handleMessage()
+  }
+
+  private void beginRegistrationRenewal(Context context) {
+    // Same as registration, but unsubscribe first to get a fresh subscription.
+    final Intent geckoIntent = buildCreatePushServiceIntent(context, "android-fxa-resubscribe");
+    context.startService(geckoIntent);
+    // -> handleMessage()
+  }
+
+  private Intent buildCreatePushServiceIntent(final Context context, final String data) {
+    final Intent intent = new Intent();
+    intent.setAction("create-services");
+    intent.setClassName(context, "org.mozilla.gecko.GeckoService");
+    intent.putExtra("category", "android-push-service");
+    intent.putExtra("data", data);
+    final AndroidFxAccount fxAccount = AndroidFxAccount.fromContext(context);
+    intent.putExtra("org.mozilla.gecko.intent.PROFILE_NAME", fxAccount.getProfile());
+    return intent;
+  }
+
+  @Override
+  public void handleMessage(String event, GeckoBundle message, EventCallback callback) {
+    if ("FxAccountsPush:Subscribe:Response".equals(event)) {
+      handlePushSubscriptionResponse(message);
+    } else {
+      Log.e(LOG_TAG, "No action defined for " + event);
+    }
+  }
+
+  private void handlePushSubscriptionResponse(final GeckoBundle message) {
+    // Make sure the context has not been gc'd during the push registration
+    // and the FxAccount still exists.
+    final Context context = this.context.get();
+    if (context == null) {
+      throw new IllegalStateException("Application context has been gc'ed");
+    }
+    final AndroidFxAccount fxAccount = AndroidFxAccount.fromContext(context);
+    if (fxAccount == null) {
+      Log.e(LOG_TAG, "AndroidFxAccount is null");
+      return;
+    }
+
+    fxAccount.resetDevicePushRegistrationError();
+    final long error = getSubscriptionReplyError(message);
+
+    final FxAccountDevice device;
+    if (error == 0L) {
+      Log.i(LOG_TAG, "Push registration succeeded. Beginning normal FxA Registration.");
+      device = buildFxAccountDevice(context, fxAccount, message.getBundle("subscription"));
+    } else {
+      fxAccount.setDevicePushRegistrationError(error, System.currentTimeMillis());
+      Log.i(LOG_TAG, "Push registration failed. Beginning degraded FxA Registration.");
+      device = buildFxAccountDevice(context, fxAccount);
+    }
+
+    doFxaRegistration(context, fxAccount, device, true);
+  }
+
+  private long getSubscriptionReplyError(final GeckoBundle message) {
+    String errorStr = message.getString(PUSH_SUBSCRIPTION_REPLY_BUNDLE_KEY_ERROR);
+    if (TextUtils.isEmpty(errorStr)) {
+      return 0L;
+    }
+    return Long.parseLong(errorStr);
+  }
+
+  private static void doFxaRegistration(final Context context, final AndroidFxAccount fxAccount,
+                                        final FxAccountDevice device, final boolean allowRecursion) {
+    final byte[] sessionToken;
+    try {
+      sessionToken = fxAccount.getState().getSessionToken();
+    } catch (State.NotASessionTokenState e) {
+      Log.e(LOG_TAG, "Could not get a session token", e);
+      return;
+    }
+
+    if (device.id == null) {
+      Log.i(LOG_TAG, "Attempting registration for a new device");
+    } else {
+      Log.i(LOG_TAG, "Attempting registration for an existing device");
+    }
+
+    final ExecutorService executor = Executors.newSingleThreadExecutor(); // Not called often, it's okay to spawn another thread
+    final FxAccountClient20 fxAccountClient =
+            new FxAccountClient20(fxAccount.getAccountServerURI(), executor);
+    fxAccountClient.registerOrUpdateDevice(sessionToken, device, new RequestDelegate<FxAccountDevice>() {
+      @Override
+      public void handleError(Exception e) {
+        Log.e(LOG_TAG, "Error while updating a device registration: ", e);
+        fxAccount.setDeviceRegistrationTimestamp(0L);
+      }
+
+      @Override
+      public void handleFailure(FxAccountClientRemoteException error) {
+        Log.e(LOG_TAG, "Error while updating a device registration: ", error);
+
+        fxAccount.setDeviceRegistrationTimestamp(0L);
+
+        if (error.httpStatusCode == 400) {
+          if (error.apiErrorNumber == FxAccountRemoteError.UNKNOWN_DEVICE) {
+            recoverFromUnknownDevice(fxAccount);
+          } else if (error.apiErrorNumber == FxAccountRemoteError.DEVICE_SESSION_CONFLICT) {
+            // This can happen if a device was already registered using our session token, and we
+            // tried to create a new one (no id field).
+            recoverFromDeviceSessionConflict(error, fxAccountClient, sessionToken, fxAccount, device,
+                    context, allowRecursion);
+          }
+        } else
+        if (error.httpStatusCode == 401
+                && error.apiErrorNumber == FxAccountRemoteError.INVALID_AUTHENTICATION_TOKEN) {
+          handleTokenError(error, fxAccountClient, fxAccount);
+        } else {
+          logErrorAndResetDeviceRegistrationVersionAndTimestamp(error, fxAccount);
+        }
+      }
+
+      @Override
+      public void handleSuccess(FxAccountDevice result) {
+        Log.i(LOG_TAG, "Device registration complete");
+        Logger.pii(LOG_TAG, "Registered device ID: " + result.id);
+        Log.i(LOG_TAG, "Setting DEVICE_REGISTRATION_VERSION to " + DEVICE_REGISTRATION_VERSION);
+        fxAccount.setFxAUserData(result.id, DEVICE_REGISTRATION_VERSION, System.currentTimeMillis());
+      }
+    });
+  }
+
+  private static FxAccountDevice buildFxAccountDevice(Context context, AndroidFxAccount fxAccount) {
+    return makeFxADeviceCommonBuilder(context, fxAccount).build();
+  }
+
+  private static FxAccountDevice buildFxAccountDevice(Context context, AndroidFxAccount fxAccount, @NonNull GeckoBundle subscription) {
+    final FxAccountDevice.Builder builder = makeFxADeviceCommonBuilder(context, fxAccount);
+    final String pushCallback = subscription.getString("pushCallback");
+    final String pushPublicKey = subscription.getString("pushPublicKey");
+    final String pushAuthKey = subscription.getString("pushAuthKey");
+    if (!TextUtils.isEmpty(pushCallback) && !TextUtils.isEmpty(pushPublicKey) &&
+        !TextUtils.isEmpty(pushAuthKey)) {
+      builder.pushCallback(pushCallback);
+      builder.pushPublicKey(pushPublicKey);
+      builder.pushAuthKey(pushAuthKey);
+    }
+    return builder.build();
+  }
+
+  // Do not call this directly, use buildFxAccountDevice instead.
+  private static FxAccountDevice.Builder makeFxADeviceCommonBuilder(Context context, AndroidFxAccount fxAccount) {
+    final String deviceId = fxAccount.getDeviceId();
+    final String clientName = getClientName(fxAccount, context);
+
+    final FxAccountDevice.Builder builder = new FxAccountDevice.Builder();
+    builder.name(clientName);
+    builder.type("mobile");
+    if (!TextUtils.isEmpty(deviceId)) {
+      builder.id(deviceId);
+    }
+    return builder;
+  }
+
+  private static void logErrorAndResetDeviceRegistrationVersionAndTimestamp(
+      final FxAccountClientRemoteException error, final AndroidFxAccount fxAccount) {
+    Log.e(LOG_TAG, "Device registration failed", error);
+    fxAccount.resetDeviceRegistrationVersion();
+    fxAccount.setDeviceRegistrationTimestamp(0L);
+  }
+
+  @Nullable
+  private static String getClientName(final AndroidFxAccount fxAccount, final Context context) {
+    try {
+      final SharedPreferencesClientsDataDelegate clientsDataDelegate =
+          new SharedPreferencesClientsDataDelegate(fxAccount.getSyncPrefs(), context);
+      return clientsDataDelegate.getClientName();
+    } catch (UnsupportedEncodingException | GeneralSecurityException e) {
+      Log.e(LOG_TAG, "Unable to get client name.", e);
+      return null;
+    }
+  }
+
+  private static void handleTokenError(final FxAccountClientRemoteException error,
+                                       final FxAccountClient fxAccountClient,
+                                       final AndroidFxAccount fxAccount) {
+    Log.i(LOG_TAG, "Recovering from invalid token error: ", error);
+    logErrorAndResetDeviceRegistrationVersionAndTimestamp(error, fxAccount);
+    fxAccountClient.accountStatus(fxAccount.getState().uid,
+        new RequestDelegate<AccountStatusResponse>() {
+      @Override
+      public void handleError(Exception e) {
+      }
+
+      @Override
+      public void handleFailure(FxAccountClientRemoteException e) {
+      }
+
+      @Override
+      public void handleSuccess(AccountStatusResponse result) {
+        final State doghouseState = fxAccount.getState().makeDoghouseState();
+        if (!result.exists) {
+          Log.i(LOG_TAG, "token invalidated because the account no longer exists");
+          // TODO: Should be in a "I have an Android account, but the FxA is gone." State.
+          // This will do for now..
+          fxAccount.setState(doghouseState);
+          return;
+        }
+        Log.e(LOG_TAG, "sessionToken invalid");
+        fxAccount.setState(doghouseState);
+      }
+    });
+  }
+
+  private static void recoverFromUnknownDevice(final AndroidFxAccount fxAccount) {
+    Log.i(LOG_TAG, "unknown device id, clearing the cached device id");
+    fxAccount.setDeviceId(null);
+  }
+
+  /**
+   * Will call delegate#complete in all cases
+   */
+  private static void recoverFromDeviceSessionConflict(final FxAccountClientRemoteException error,
+                                                       final FxAccountClient fxAccountClient,
+                                                       final byte[] sessionToken,
+                                                       final AndroidFxAccount fxAccount,
+                                                       final FxAccountDevice device,
+                                                       final Context context,
+                                                       final boolean allowRecursion) {
+    // Recovery strategy: re-try a registration, UPDATING (instead of creating) the device.
+    // We do that by finding the device ID who conflicted with us and try a registration update
+    // using that id.
+    Log.w(LOG_TAG, "device session conflict, attempting to ascertain the correct device id");
+    fxAccountClient.deviceList(sessionToken, new RequestDelegate<FxAccountDevice[]>() {
+      private void onError() {
+        Log.e(LOG_TAG, "failed to recover from device-session conflict");
+        logErrorAndResetDeviceRegistrationVersionAndTimestamp(error, fxAccount);
+      }
+
+      @Override
+      public void handleError(Exception e) {
+        onError();
+      }
+
+      @Override
+      public void handleFailure(FxAccountClientRemoteException e) {
+        onError();
+      }
+
+      @Override
+      public void handleSuccess(FxAccountDevice[] devices) {
+        for (final FxAccountDevice fxaDevice : devices) {
+          if (!fxaDevice.isCurrentDevice) {
+            continue;
+          }
+          fxAccount.setFxAUserData(fxaDevice.id, 0, 0L); // Reset device registration version/timestamp
+          if (!allowRecursion) {
+            Log.d(LOG_TAG, "Failure to register a device on the second try");
+            break;
+          }
+          final FxAccountDevice updatedDevice = new FxAccountDevice(device.name, fxaDevice.id, device.type,
+                                                                    null, null,
+                                                                    device.pushCallback, device.pushPublicKey,
+                                                                    device.pushAuthKey);
+          doFxaRegistration(context, fxAccount, updatedDevice, false);
+          return;
+        }
+        onError();
+      }
+    });
+  }
+
+  private void setupListeners() throws ClassNotFoundException, NoSuchMethodException,
+          InvocationTargetException, IllegalAccessException {
+    // We have no choice but to use reflection here, sorry :(
+    final Class<?> eventDispatcher = Class.forName("org.mozilla.gecko.EventDispatcher");
+    final Method getInstance = eventDispatcher.getMethod("getInstance");
+    final Object instance = getInstance.invoke(null);
+    final Method registerBackgroundThreadListener = eventDispatcher.getMethod("registerBackgroundThreadListener",
+            BundleEventListener.class, String[].class);
+    registerBackgroundThreadListener.invoke(instance, this, new String[] { "FxAccountsPush:Subscribe:Response" });
+  }
+}
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/receivers/FxAccountDeletedService.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/receivers/FxAccountDeletedService.java
@@ -1,25 +1,28 @@
 /* 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.fxa.receivers;
 
 import android.app.IntentService;
+import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
+import android.net.Uri;
 import android.text.TextUtils;
 
 import org.mozilla.gecko.background.common.log.Logger;
 import org.mozilla.gecko.background.fxa.FxAccountClient20;
 import org.mozilla.gecko.background.fxa.FxAccountClientException;
 import org.mozilla.gecko.background.fxa.oauth.FxAccountAbstractClient;
 import org.mozilla.gecko.background.fxa.oauth.FxAccountAbstractClientException.FxAccountAbstractClientRemoteException;
 import org.mozilla.gecko.background.fxa.oauth.FxAccountOAuthClient10;
+import org.mozilla.gecko.db.BrowserContract;
 import org.mozilla.gecko.fxa.FxAccountConstants;
 import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
 import org.mozilla.gecko.fxa.sync.FxAccountNotificationManager;
 import org.mozilla.gecko.fxa.sync.FxAccountSyncAdapter;
 import org.mozilla.gecko.sync.ExtendedJSONObject;
 import org.mozilla.gecko.sync.repositories.android.ClientsDatabase;
 import org.mozilla.gecko.sync.repositories.android.FennecTabsRepository;
 
@@ -70,16 +73,18 @@ public class FxAccountDeletedService ext
     final String accountName = intent.getStringExtra(
         FxAccountConstants.ACCOUNT_DELETED_INTENT_ACCOUNT_KEY);
     if (accountName == null) {
       Logger.warn(LOG_TAG, "Intent malformed: no account name given. Not cleaning up after " +
           "deleted Account.");
       return;
     }
 
+    clearRemoteDevicesList(intent, context);
+
     // Delete current device the from FxA devices list.
     deleteFxADevice(intent);
 
     // Fire up gecko and unsubscribe push
     final Intent geckoIntent = new Intent();
     geckoIntent.setAction("create-services");
     geckoIntent.setClassName(context, "org.mozilla.gecko.GeckoService");
     geckoIntent.putExtra("category", "android-push-service");
@@ -154,16 +159,27 @@ public class FxAccountDeletedService ext
           Logger.error(LOG_TAG, "Exception during cached OAuth token deletion; ignoring.", e);
         }
       }
     } else {
       Logger.error(LOG_TAG, "Cached OAuth server URI is null or cached OAuth tokens are null; ignoring.");
     }
   }
 
+  private void clearRemoteDevicesList(Intent intent, Context context) {
+    final Uri remoteDevicesUriWithProfile = BrowserContract.RemoteDevices.CONTENT_URI
+            .buildUpon()
+            .appendQueryParameter(BrowserContract.PARAM_PROFILE,
+                                  intent.getStringExtra(FxAccountConstants.ACCOUNT_DELETED_INTENT_ACCOUNT_PROFILE))
+            .build();
+    ContentResolver cr = context.getContentResolver();
+
+    cr.delete(remoteDevicesUriWithProfile, null, null);
+  }
+
   // Remove our current device from the FxA device list.
   private void deleteFxADevice(Intent intent) {
     final byte[] sessionToken = intent.getByteArrayExtra(FxAccountConstants.ACCOUNT_DELETED_INTENT_ACCOUNT_SESSION_TOKEN);
     if (sessionToken == null) {
       Logger.warn(LOG_TAG, "Empty session token, skipping FxA device destruction.");
       return;
     }
     final String deviceId = intent.getStringExtra(FxAccountConstants.ACCOUNT_DELETED_INTENT_ACCOUNT_DEVICE_ID);
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/sync/FxAccountSyncAdapter.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/fxa/sync/FxAccountSyncAdapter.java
@@ -1,45 +1,46 @@
 /* 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.fxa.sync;
 
 import android.accounts.Account;
 import android.content.AbstractThreadedSyncAdapter;
+import android.content.ContentProvider;
 import android.content.ContentProviderClient;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.SharedPreferences;
 import android.content.SyncResult;
 import android.os.Bundle;
 import android.os.SystemClock;
 import android.text.TextUtils;
 
 import org.mozilla.gecko.background.common.log.Logger;
 import org.mozilla.gecko.background.common.telemetry.TelemetryWrapper;
 import org.mozilla.gecko.background.fxa.FxAccountUtils;
 import org.mozilla.gecko.background.fxa.SkewHandler;
 import org.mozilla.gecko.browserid.JSONWebTokenUtils;
 import org.mozilla.gecko.fxa.FirefoxAccounts;
 import org.mozilla.gecko.fxa.FxAccountConstants;
-import org.mozilla.gecko.fxa.FxAccountDeviceRegistrator;
+import org.mozilla.gecko.fxa.devices.FxAccountDeviceListUpdater;
+import org.mozilla.gecko.fxa.devices.FxAccountDeviceRegistrator;
 import org.mozilla.gecko.fxa.authenticator.AccountPickler;
 import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
 import org.mozilla.gecko.fxa.authenticator.FxADefaultLoginStateMachineDelegate;
 import org.mozilla.gecko.fxa.authenticator.FxAccountAuthenticator;
 import org.mozilla.gecko.fxa.login.FxAccountLoginStateMachine;
 import org.mozilla.gecko.fxa.login.Married;
 import org.mozilla.gecko.fxa.login.State;
 import org.mozilla.gecko.fxa.login.State.StateLabel;
 import org.mozilla.gecko.fxa.sync.FxAccountSyncDelegate.Result;
 import org.mozilla.gecko.sync.BackoffHandler;
 import org.mozilla.gecko.sync.GlobalSession;
-import org.mozilla.gecko.sync.MetaGlobal;
 import org.mozilla.gecko.sync.PrefsBackoffHandler;
 import org.mozilla.gecko.sync.SharedPreferencesClientsDataDelegate;
 import org.mozilla.gecko.sync.SyncConfiguration;
 import org.mozilla.gecko.sync.ThreadPool;
 import org.mozilla.gecko.sync.Utils;
 import org.mozilla.gecko.sync.crypto.KeyBundle;
 import org.mozilla.gecko.sync.delegates.GlobalSessionCallback;
 import org.mozilla.gecko.sync.delegates.ClientsDataDelegate;
@@ -414,16 +415,25 @@ public class FxAccountSyncAdapter extend
       // We might need to re-register periodically to ensure our FxA push subscription is valid.
       // This involves unsubscribing, subscribing and updating remote FxA device record with
       // new push subscription information.
     } else if (FxAccountDeviceRegistrator.shouldRenewRegistration(fxAccount)) {
       FxAccountDeviceRegistrator.renewRegistration(context);
     }
   }
 
+  private void onSessionTokenStateReached(Context context, AndroidFxAccount fxAccount) {
+    // This does not block the main thread, if work has to be done it is executed in a new thread.
+    maybeRegisterDevice(context, fxAccount);
+
+    FxAccountDeviceListUpdater deviceListUpdater = new FxAccountDeviceListUpdater(fxAccount, context.getContentResolver());
+    // Since the clients stage requires a fresh list of remote devices, we update the device list synchronously.
+    deviceListUpdater.update();
+  }
+
   /**
    * A trivial Sync implementation that does not cache client keys,
    * certificates, or tokens.
    *
    * This should be replaced with a full {@link FxAccountAuthenticator}-based
    * token implementation.
    */
   @Override
@@ -541,17 +551,17 @@ public class FxAccountSyncAdapter extend
       final FxAccountLoginStateMachine stateMachine = new FxAccountLoginStateMachine();
       stateMachine.advance(state, StateLabel.Married, new FxADefaultLoginStateMachineDelegate(context, fxAccount) {
         @Override
         public void handleNotMarried(State notMarried) {
           Logger.info(LOG_TAG, "handleNotMarried: in " + notMarried.getStateLabel());
           schedulePolicy.onHandleFinal(notMarried.getNeededAction());
           syncDelegate.handleCannotSync(notMarried);
           if (notMarried.getStateLabel() == StateLabel.Engaged) {
-            maybeRegisterDevice(context, fxAccount);
+            onSessionTokenStateReached(context, fxAccount);
           }
         }
 
         private boolean shouldRequestToken(final BackoffHandler tokenBackoffHandler, final Bundle extras) {
           return shouldPerformSync(tokenBackoffHandler, "token", extras);
         }
 
         @Override
@@ -584,25 +594,25 @@ public class FxAccountSyncAdapter extend
             // in the Married state, so instead we simply do this here, once.
             final BackoffHandler tokenBackoffHandler = new PrefsBackoffHandler(sharedPrefs, "token");
             if (!shouldRequestToken(tokenBackoffHandler, extras)) {
               Logger.info(LOG_TAG, "Not syncing (token server).");
               syncDelegate.postponeSync(tokenBackoffHandler.delayMilliseconds());
               return;
             }
 
+            onSessionTokenStateReached(context, fxAccount);
+
             final SessionCallback sessionCallback = new SessionCallback(syncDelegate, schedulePolicy);
             final KeyBundle syncKeyBundle = married.getSyncKeyBundle();
             final String clientState = married.getClientState();
             syncWithAssertion(
                     assertion, tokenServerEndpointURI, tokenBackoffHandler, sharedPrefs,
                     syncKeyBundle, clientState, sessionCallback, extras, fxAccount, syncDeadline);
 
-            maybeRegisterDevice(context, fxAccount);
-
             // Force fetch the profile avatar information. (asynchronous, in another thread)
             Logger.info(LOG_TAG, "Fetching profile avatar information.");
             fxAccount.fetchProfileJSON();
           } catch (Exception e) {
             syncDelegate.handleError(e);
             return;
           }
         }
--- a/mobile/android/services/src/main/java/org/mozilla/gecko/sync/SharedPreferencesClientsDataDelegate.java
+++ b/mobile/android/services/src/main/java/org/mozilla/gecko/sync/SharedPreferencesClientsDataDelegate.java
@@ -1,17 +1,16 @@
 /* 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.sync;
 
 import org.mozilla.gecko.background.fxa.FxAccountUtils;
 import org.mozilla.gecko.fxa.FirefoxAccounts;
-import org.mozilla.gecko.fxa.FxAccountDeviceRegistrator;
 import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
 import org.mozilla.gecko.sync.delegates.ClientsDataDelegate;
 import org.mozilla.gecko.util.HardwareUtils;
 
 import android.accounts.Account;
 import android.content.Context;
 import android.content.SharedPreferences;
 
deleted file mode 100644
--- a/mobile/android/tests/background/junit4/src/org/mozilla/gecko/fxa/TestFxAccountDeviceRegistrator.java
+++ /dev/null
@@ -1,91 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
-   http://creativecommons.org/publicdomain/zero/1.0/ */
-
-package org.mozilla.gecko.fxa;
-
-import org.junit.Before;
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.mockito.Mock;
-import org.mockito.MockitoAnnotations;
-import org.mozilla.gecko.background.testhelpers.TestRunner;
-import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
-
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
-import static org.mockito.Mockito.when;
-
-@RunWith(TestRunner.class)
-public class TestFxAccountDeviceRegistrator {
-
-    @Mock
-    AndroidFxAccount fxAccount;
-
-    @Before
-    public void init() {
-        // Process Mockito annotations
-        MockitoAnnotations.initMocks(this);
-    }
-
-    @Test
-    public void shouldRegister() {
-        // Assuming there is no previous push registration errors recorded:
-        when(fxAccount.getDevicePushRegistrationError()).thenReturn(0L);
-        when(fxAccount.getDevicePushRegistrationErrorTime()).thenReturn(0L);
-
-        // Should return false if the device registration version is up-to-date and a device ID is stored.
-        // (general case after a successful device registration)
-        when(fxAccount.getDeviceRegistrationVersion()).thenReturn(FxAccountDeviceRegistrator.DEVICE_REGISTRATION_VERSION);
-        when(fxAccount.getDeviceId()).thenReturn("bogusdeviceid");
-        assertFalse(FxAccountDeviceRegistrator.shouldRegister(fxAccount));
-
-        // Should return true if the device registration version is up-to-date but no device ID is stored.
-        // (data mangling)
-        when(fxAccount.getDeviceRegistrationVersion()).thenReturn(FxAccountDeviceRegistrator.DEVICE_REGISTRATION_VERSION);
-        when(fxAccount.getDeviceId()).thenReturn(null);
-        assertTrue(FxAccountDeviceRegistrator.shouldRegister(fxAccount));
-
-        // Should return true if the device ID is stored but no device registration version can be found.
-        // (data mangling)
-        when(fxAccount.getDeviceRegistrationVersion()).thenReturn(0);
-        when(fxAccount.getDeviceId()).thenReturn("bogusid");
-        assertTrue(FxAccountDeviceRegistrator.shouldRegister(fxAccount));
-
-        // Should return true if the device registration version is too old.
-        // (code update pushed)
-        when(fxAccount.getDeviceRegistrationVersion()).thenReturn(FxAccountDeviceRegistrator.DEVICE_REGISTRATION_VERSION - 1);
-        assertTrue(FxAccountDeviceRegistrator.shouldRegister(fxAccount));
-
-        // Should return true if the device registration is OK, but we didn't get a push subscription because
-        // Google Play Services were unavailable at the time and the retry delay is passed.
-        when(fxAccount.getDeviceRegistrationVersion()).thenReturn(FxAccountDeviceRegistrator.DEVICE_REGISTRATION_VERSION);
-        when(fxAccount.getDevicePushRegistrationError()).thenReturn(FxAccountDeviceRegistrator.ERROR_GCM_DISABLED);
-        when(fxAccount.getDevicePushRegistrationErrorTime()).thenReturn(System.currentTimeMillis() -
-                                                                        FxAccountDeviceRegistrator.RETRY_TIME_AFTER_GCM_DISABLED_ERROR - 1);
-        assertTrue(FxAccountDeviceRegistrator.shouldRegister(fxAccount));
-
-        // Should return false if the device registration is OK, but we didn't get a push subscription because
-        // Google Play Services were unavailable at the time and the retry delay has not passed.
-        // We assume that RETRY_TIME_AFTER_GCM_DISABLED_ERROR is longer than the time it takes to execute this test :)
-        when(fxAccount.getDevicePushRegistrationErrorTime()).thenReturn(System.currentTimeMillis());
-        assertFalse(FxAccountDeviceRegistrator.shouldRegister(fxAccount));
-
-        // Should return false if the device registration is OK, but we didn't get a push subscription because
-        // an unknown error happened at the time.
-        when(fxAccount.getDevicePushRegistrationError()).thenReturn(12345L);
-        assertFalse(FxAccountDeviceRegistrator.shouldRegister(fxAccount));
-    }
-
-    @Test
-    public void shouldRenewRegistration() {
-        // Should return true if our last push registration was done a day before our expiration threshold.
-        when(fxAccount.getDeviceRegistrationTimestamp()).thenReturn(System.currentTimeMillis() -
-                                                                    FxAccountDeviceRegistrator.TIME_BETWEEN_CHANNEL_REGISTRATION_IN_MILLIS -
-                                                                    1 * 24 * 60 * 60 * 1000L);
-
-        // Should return false if our last push registration is recent enough.
-        // We assume that TIME_BETWEEN_CHANNEL_REGISTRATION_IN_MILLIS is longer than a day + the time it takes to run this test.
-        when(fxAccount.getDeviceRegistrationTimestamp()).thenReturn(System.currentTimeMillis() -
-                                                                    1 * 24 * 60 * 60 * 1000L);
-    }
-}
new file mode 100644
--- /dev/null
+++ b/mobile/android/tests/background/junit4/src/org/mozilla/gecko/fxa/devices/TestFxAccountDeviceListUpdater.java
@@ -0,0 +1,188 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+package org.mozilla.gecko.fxa.devices;
+
+import android.content.ContentProviderClient;
+import android.content.ContentResolver;
+import android.content.ContentValues;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Bundle;
+import android.os.RemoteException;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Mock;
+import org.mockito.MockitoAnnotations;
+import org.mozilla.gecko.background.db.DelegatingTestContentProvider;
+import org.mozilla.gecko.background.fxa.FxAccountClient;
+import org.mozilla.gecko.background.testhelpers.TestRunner;
+import org.mozilla.gecko.db.BrowserContract;
+import org.mozilla.gecko.db.BrowserProvider;
+import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
+import org.mozilla.gecko.fxa.login.State;
+import org.robolectric.shadows.ShadowContentResolver;
+
+import java.util.List;
+
+import static java.util.Objects.deepEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.fail;
+import static org.mockito.Matchers.anyString;
+import static org.mockito.Mockito.any;
+import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.when;
+
+@RunWith(TestRunner.class)
+public class TestFxAccountDeviceListUpdater {
+
+    @Before
+    public void init() {
+        // Process Mockito annotations
+        MockitoAnnotations.initMocks(this);
+        fxaDevicesUpdater = spy(new FxAccountDeviceListUpdater(fxAccount, contentResolver));
+    }
+
+    FxAccountDeviceListUpdater fxaDevicesUpdater;
+
+    @Mock
+    AndroidFxAccount fxAccount;
+
+    @Mock
+    State state;
+
+    @Mock
+    ContentResolver contentResolver;
+
+    @Mock
+    FxAccountClient fxaClient;
+
+    @Test
+    public void testUpdate() throws Throwable {
+        byte[] token = "usertoken".getBytes();
+
+        when(fxAccount.getState()).thenReturn(state);
+        when(state.getSessionToken()).thenReturn(token);
+        doReturn(fxaClient).when(fxaDevicesUpdater).getSynchronousFxaClient();
+
+        fxaDevicesUpdater.update();
+        verify(fxaClient).deviceList(token, fxaDevicesUpdater);
+    }
+
+    @Test
+    public void testSuccessHandler() throws Throwable {
+        FxAccountDevice[] result = new FxAccountDevice[2];
+        FxAccountDevice device1 = new FxAccountDevice("Current device", "deviceid1", "mobile", true, System.currentTimeMillis(),
+                "https://localhost/push/callback1", "abc123", "321cba");
+        FxAccountDevice device2 = new FxAccountDevice("Desktop PC", "deviceid2", "desktop", true, System.currentTimeMillis(),
+                "https://localhost/push/callback2", "abc123", "321cba");
+        result[0] = device1;
+        result[1] = device2;
+
+        when(fxAccount.getProfile()).thenReturn("default");
+
+        long timeBeforeCall = System.currentTimeMillis();
+        fxaDevicesUpdater.handleSuccess(result);
+
+        ArgumentCaptor<Bundle> captor = ArgumentCaptor.forClass(Bundle.class);
+        verify(contentResolver).call(any(Uri.class), eq(BrowserContract.METHOD_REPLACE_REMOTE_CLIENTS), anyString(), captor.capture());
+        List<Bundle> allArgs = captor.getAllValues();
+        assertTrue(allArgs.size() == 1);
+        ContentValues[] allValues = (ContentValues[]) allArgs.get(0).getParcelableArray(BrowserContract.METHOD_PARAM_DATA);
+
+        ContentValues firstDevice = allValues[0];
+        checkInsertDeviceContentValues(device1, firstDevice, timeBeforeCall);
+        ContentValues secondDevice = allValues[1];
+        checkInsertDeviceContentValues(device2, secondDevice, timeBeforeCall);
+    }
+
+    private void checkInsertDeviceContentValues(FxAccountDevice device, ContentValues firstDevice, long timeBeforeCall) {
+        assertEquals(firstDevice.getAsString(BrowserContract.RemoteDevices.GUID), device.id);
+        assertEquals(firstDevice.getAsString(BrowserContract.RemoteDevices.TYPE), device.type);
+        assertEquals(firstDevice.getAsString(BrowserContract.RemoteDevices.NAME), device.name);
+        assertEquals(firstDevice.getAsBoolean(BrowserContract.RemoteDevices.IS_CURRENT_DEVICE), device.isCurrentDevice);
+        deepEquals(firstDevice.getAsString(BrowserContract.RemoteDevices.LAST_ACCESS_TIME), device.lastAccessTime);
+        assertTrue(firstDevice.getAsLong(BrowserContract.RemoteDevices.DATE_CREATED) < timeBeforeCall + 10000); // Give 10 secs of leeway
+        assertTrue(firstDevice.getAsLong(BrowserContract.RemoteDevices.DATE_MODIFIED) < timeBeforeCall + 10000);
+        assertEquals(firstDevice.getAsString(BrowserContract.RemoteDevices.NAME), device.name);
+    }
+
+    @Test
+    public void testBrowserProvider() {
+        Uri uri = testUri(BrowserContract.RemoteDevices.CONTENT_URI);
+
+        BrowserProvider provider = new BrowserProvider();
+        Cursor c = null;
+        try {
+            provider.onCreate();
+            ShadowContentResolver.registerProvider(BrowserContract.AUTHORITY, new DelegatingTestContentProvider(provider));
+
+            final ShadowContentResolver cr = new ShadowContentResolver();
+            ContentProviderClient remoteDevicesClient = cr.acquireContentProviderClient(BrowserContract.RemoteDevices.CONTENT_URI);
+
+            // First let's insert a client for initial state.
+
+            Bundle bundle = new Bundle();
+            ContentValues device1 = createMockRemoteClientValues("device1");
+            bundle.putParcelableArray(BrowserContract.METHOD_PARAM_DATA, new ContentValues[] { device1 });
+
+            remoteDevicesClient.call(BrowserContract.METHOD_REPLACE_REMOTE_CLIENTS, uri.toString(), bundle);
+
+            c = remoteDevicesClient.query(uri, null, null, null, "name ASC");
+            assertEquals(c.getCount(), 1);
+            c.moveToFirst();
+            int nameCol = c.getColumnIndexOrThrow("name");
+            assertEquals(c.getString(nameCol), "device1");
+            c.close();
+
+            // Then we replace our remote clients list with a new one.
+
+            bundle = new Bundle();
+            ContentValues device2 = createMockRemoteClientValues("device2");
+            ContentValues device3 = createMockRemoteClientValues("device3");
+            bundle.putParcelableArray(BrowserContract.METHOD_PARAM_DATA, new ContentValues[] { device2, device3 });
+
+            remoteDevicesClient.call(BrowserContract.METHOD_REPLACE_REMOTE_CLIENTS, uri.toString(), bundle);
+
+            c = remoteDevicesClient.query(uri, null, null, null, "name ASC");
+            assertEquals(c.getCount(), 2);
+            c.moveToFirst();
+            nameCol = c.getColumnIndexOrThrow("name");
+            assertEquals(c.getString(nameCol), "device2");
+            c.moveToNext();
+            assertEquals(c.getString(nameCol), "device3");
+            c.close();
+        } catch (RemoteException e) {
+            fail(e.getMessage());
+        } finally {
+            if (c != null && !c.isClosed()) {
+                c.close();
+            }
+            provider.shutdown();
+        }
+    }