Merge m-c to oak DONTBUILD
authorRobert Strong <robert.bugzilla@gmail.com>
Sun, 14 May 2017 21:05:04 -0700
changeset 1476254 0563d755ca0cae4d3be76fae8c135c3d7ec17ddf
parent 1476253 c243122a4587db830e7a08c74f3eb58fac96b933 (current diff)
parent 1123860 e66dedabe582ba7b394aee4f89ed70fe389b3c46 (diff)
child 1476255 e5a18c2848cdef209d9bf561c1d65a0e7fbb214e
push id263306
push usercatlee@mozilla.com
push dateFri, 06 Apr 2018 15:43:50 +0000
treeherdertry@a0bfb6549eeb [default view] [failures only]
milestone55.0a1
Merge m-c to oak DONTBUILD
browser/app/profile/firefox.js
browser/base/content/browser.js
browser/installer/package-manifest.in
browser/locales/en-US/chrome/browser/browser.dtd
dom/webidl/AutocompleteErrorEvent.webidl
ipc/glue/GeckoChildProcessHost.cpp
old-configure.in
servo/components/script/dom/browsingcontext.rs
taskcluster/ci/test/tests.yml
testing/mozharness/configs/builds/releng_base_mac_64_builds.py
testing/mozharness/mozharness/mozilla/building/buildbase.py
testing/runcppunittests.py
toolkit/components/formautofill/FormAutofill.jsm
toolkit/components/formautofill/FormAutofillContentService.js
toolkit/components/formautofill/FormAutofillIntegration.jsm
toolkit/components/formautofill/FormAutofillStartup.js
toolkit/components/formautofill/content/RequestAutocompleteUI.jsm
toolkit/components/formautofill/content/requestAutocomplete.js
toolkit/components/formautofill/content/requestAutocomplete.xhtml
toolkit/components/formautofill/formautofill.manifest
toolkit/components/formautofill/jar.mn
toolkit/components/formautofill/moz.build
toolkit/components/formautofill/nsIFormAutofillContentService.idl
toolkit/components/formautofill/test/.eslintrc.js
toolkit/components/formautofill/test/browser/.eslintrc.js
toolkit/components/formautofill/test/browser/browser.ini
toolkit/components/formautofill/test/browser/browser_infrastructure.js
toolkit/components/formautofill/test/browser/browser_ui_requestAutocomplete.js
toolkit/components/formautofill/test/browser/head.js
toolkit/components/formautofill/test/browser/loader.js
toolkit/components/formautofill/test/chrome/.eslintrc.js
toolkit/components/formautofill/test/chrome/chrome.ini
toolkit/components/formautofill/test/chrome/head.js
toolkit/components/formautofill/test/chrome/loader.js
toolkit/components/formautofill/test/chrome/loader_parent.js
toolkit/components/formautofill/test/chrome/test_infrastructure.html
toolkit/components/formautofill/test/chrome/test_infrastructure.js
toolkit/components/formautofill/test/chrome/test_requestAutocomplete_cancel.html
toolkit/components/formautofill/test/chrome/test_requestAutocomplete_cancel.js
toolkit/components/formautofill/test/head_common.js
toolkit/components/formautofill/test/loader_common.js
toolkit/components/formautofill/test/xpcshell/.eslintrc.js
toolkit/components/formautofill/test/xpcshell/head.js
toolkit/components/formautofill/test/xpcshell/loader.js
toolkit/components/formautofill/test/xpcshell/test_infrastructure.js
toolkit/components/formautofill/test/xpcshell/test_integration.js
toolkit/components/formautofill/test/xpcshell/xpcshell.ini
toolkit/components/telemetry/Histograms.json
toolkit/mozapps/extensions/test/addons/test_bug594058/directory/file1
toolkit/mozapps/extensions/test/addons/test_bug594058/install.rdf
toolkit/mozapps/extensions/test/xpcshell/data/test_migrate.rdf
toolkit/mozapps/extensions/test/xpcshell/data/test_migrate4.rdf
toolkit/mozapps/extensions/test/xpcshell/test_bug594058.js
toolkit/mozapps/extensions/test/xpcshell/test_migrate1.js
toolkit/mozapps/extensions/test/xpcshell/test_migrate2.js
toolkit/mozapps/extensions/test/xpcshell/test_migrate3.js
toolkit/mozapps/extensions/test/xpcshell/test_migrate4.js
toolkit/mozapps/extensions/test/xpcshell/test_migrate5.js
toolkit/mozapps/extensions/test/xpcshell/test_migrateAddonRepository.js
toolkit/mozapps/extensions/test/xpcshell/test_migrate_max_version.js
toolkit/themes/shared/formautofill/requestAutocomplete.css
toolkit/xre/nsAppRunner.cpp
toolkit/xre/nsXREDirProvider.cpp
toolkit/xre/nsXREDirProvider.h
xpcom/build/XPCOMInit.cpp
--- a/addon-sdk/source/test/addons/jetpack-addon.ini
+++ b/addon-sdk/source/test/addons/jetpack-addon.ini
@@ -1,11 +1,12 @@
 [addon-manager.xpi]
 [author-email.xpi]
 [child_process.xpi]
+skip-if = true
 [chrome.xpi]
 [content-permissions.xpi]
 [content-script-messages-latency.xpi]
 [contributors.xpi]
 [curly-id.xpi]
 [developers.xpi]
 [e10s.xpi]
 skip-if = true
--- a/addon-sdk/source/test/test-xpcom.js
+++ b/addon-sdk/source/test/test-xpcom.js
@@ -140,17 +140,16 @@ function testRegister(assert, text) {
       newChannel : function(aURI, aLoadInfo) {
         var ios = Cc["@mozilla.org/network/io-service;1"].
                   getService(Ci.nsIIOService);
 
         var uri = ios.newURI("data:text/plain;charset=utf-8," + text);
         var channel = ios.newChannelFromURIWithLoadInfo(uri, aLoadInfo);
 
         channel.originalURI = aURI;
-        aLoadInfo.resultPrincipalURI = aURI;
         return channel;
       },
       getURIFlags: function(aURI) {
         return Ci.nsIAboutModule.ALLOW_SCRIPT;
       }
     })
   });
 
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -64,16 +64,18 @@ pref("extensions.hotfix.certs.2.sha1Fing
 pref("extensions.systemAddon.update.url", "https://aus5.mozilla.org/update/3/SystemAddons/%VERSION%/%BUILD_ID%/%BUILD_TARGET%/%LOCALE%/%CHANNEL%/%OS_VERSION%/%DISTRIBUTION%/%DISTRIBUTION_VERSION%/update.xml");
 
 // Disable screenshots for now, Shield will enable this.
 pref("extensions.screenshots.system-disabled", true);
 
 // Disable add-ons that are not installed by the user in all scopes by default.
 // See the SCOPE constants in AddonManager.jsm for values to use here.
 pref("extensions.autoDisableScopes", 15);
+// Scopes to scan for changes at startup.
+pref("extensions.startupScanScopes", 0);
 
 // This is where the profiler WebExtension API will look for breakpad symbols.
 // NOTE: deliberately http right now since https://symbols.mozilla.org is not supported.
 pref("extensions.geckoProfiler.symbols.url", "http://symbols.mozilla.org/");
 pref("extensions.geckoProfiler.acceptedExtensionIds", "geckoprofiler@mozilla.com");
 #if defined(XP_LINUX) || defined (XP_MACOSX)
 pref("extensions.geckoProfiler.getSymbolRules", "localBreakpad,remoteBreakpad,nm");
 #else // defined(XP_WIN)
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -1138,16 +1138,21 @@ addEventListener("DOMContentLoaded", fun
     if (tabToOpen instanceof XULElement && tabToOpen.hasAttribute("usercontextid")) {
       initBrowser.setAttribute("usercontextid", tabToOpen.getAttribute("usercontextid"));
     }
   }
 
   gBrowser.updateBrowserRemoteness(initBrowser, gMultiProcessBrowser);
 });
 
+let _resolveDelayedStartup;
+var delayedStartupPromise = new Promise(resolve => {
+  _resolveDelayedStartup = resolve;
+});
+
 var gBrowserInit = {
   delayedStartupFinished: false,
 
   onLoad() {
     gBrowser.addEventListener("DOMUpdatePageReport", gPopupBlockerObserver);
 
     Services.obs.addObserver(gPluginHandler.NPAPIPluginCrashed, "plugin-crashed");
 
@@ -1600,16 +1605,17 @@ var gBrowserInit = {
         }
       }
     });
 
     gPageActionButton.init();
 
     this.delayedStartupFinished = true;
 
+    _resolveDelayedStartup();
     Services.obs.notifyObservers(window, "browser-delayed-startup-finished");
     TelemetryTimestamps.add("delayedStartupFinished");
   },
 
   // Returns the URI(s) to load at startup.
   _getUriToLoad() {
     // window.arguments[0]: URI to load (string), or an nsIArray of
     //                      nsISupportsStrings to load, or a xul:tab of
--- a/browser/base/content/tabbrowser.xml
+++ b/browser/base/content/tabbrowser.xml
@@ -1404,23 +1404,44 @@
 
 
       <!-- TODO: remove after 57, once we know add-ons can no longer use it. -->
       <method name="setTabTitleLoading">
         <parameter name="aTab"/>
         <body/>
       </method>
 
+      <method name="setInitialTabTitle">
+        <parameter name="aTab"/>
+        <parameter name="aTitle"/>
+        <body><![CDATA[
+          if (aTitle) {
+            aTab.setAttribute("label", aTitle);
+
+            // Don't replace the set label with the empty tab label or the URL
+            // while the tab is loading.
+            aTab._suppressTransientPlaceholderLabel = true;
+          }
+        ]]></body>
+      </method>
+
       <method name="setTabTitle">
         <parameter name="aTab"/>
         <body>
           <![CDATA[
             var browser = this.getBrowserForTab(aTab);
             var title = browser.contentTitle;
 
+            if (aTab._suppressTransientPlaceholderLabel) {
+              if (!title) {
+                return false;
+              }
+              delete aTab._suppressTransientPlaceholderLabel;
+            }
+
             if (!title) {
               if (browser.currentURI.spec) {
                 try {
                   title = this.mURIFixup.createExposableURI(browser.currentURI).spec;
                 } catch (ex) {
                   title = browser.currentURI.spec;
                 }
               }
@@ -1460,32 +1481,32 @@
 
       <method name="loadOneTab">
         <parameter name="aURI"/>
         <parameter name="aReferrerURI"/>
         <parameter name="aCharset"/>
         <parameter name="aPostData"/>
         <parameter name="aLoadInBackground"/>
         <parameter name="aAllowThirdPartyFixup"/>
-        <parameter name="aIsPrerendered"/>
         <body>
           <![CDATA[
             var aTriggeringPrincipal;
             var aReferrerPolicy;
             var aFromExternal;
             var aRelatedToCurrent;
             var aAllowMixedContent;
             var aSkipAnimation;
             var aForceNotRemote;
             var aPreferredRemoteType;
             var aNoReferrer;
             var aUserContextId;
             var aSameProcessAsFrameLoader;
             var aOriginPrincipal;
             var aOpener;
+            var aIsPrerendered;
             var aCreateLazyBrowser;
             var aNextTabParentId;
             if (arguments.length == 2 &&
                 typeof arguments[1] == "object" &&
                 !(arguments[1] instanceof Ci.nsIURI)) {
               let params = arguments[1];
               aTriggeringPrincipal      = params.triggeringPrincipal
               aReferrerURI              = params.referrerURI;
@@ -2214,17 +2235,16 @@
 
       <method name="addTab">
         <parameter name="aURI"/>
         <parameter name="aReferrerURI"/>
         <parameter name="aCharset"/>
         <parameter name="aPostData"/>
         <parameter name="aOwner"/>
         <parameter name="aAllowThirdPartyFixup"/>
-        <parameter name="aIsPrerendered"/>
         <body>
           <![CDATA[
             "use strict";
 
             const NS_XUL = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
             var aTriggeringPrincipal;
             var aReferrerPolicy;
             var aFromExternal;
@@ -2235,19 +2255,21 @@
             var aPreferredRemoteType;
             var aNoReferrer;
             var aUserContextId;
             var aEventDetail;
             var aSameProcessAsFrameLoader;
             var aOriginPrincipal;
             var aDisallowInheritPrincipal;
             var aOpener;
+            var aIsPrerendered;
             var aCreateLazyBrowser;
             var aSkipBackgroundNotify;
             var aNextTabParentId;
+            var aNoInitialLabel;
             if (arguments.length == 2 &&
                 typeof arguments[1] == "object" &&
                 !(arguments[1] instanceof Ci.nsIURI)) {
               let params = arguments[1];
               aTriggeringPrincipal      = params.triggeringPrincipal;
               aReferrerURI              = params.referrerURI;
               aReferrerPolicy           = params.referrerPolicy;
               aCharset                  = params.charset;
@@ -2266,16 +2288,17 @@
               aSameProcessAsFrameLoader = params.sameProcessAsFrameLoader;
               aOriginPrincipal          = params.originPrincipal;
               aDisallowInheritPrincipal = params.disallowInheritPrincipal;
               aOpener                   = params.opener;
               aIsPrerendered            = params.isPrerendered;
               aCreateLazyBrowser        = params.createLazyBrowser;
               aSkipBackgroundNotify     = params.skipBackgroundNotify;
               aNextTabParentId          = params.nextTabParentId;
+              aNoInitialLabel           = params.noInitialLabel;
             }
 
             // if we're adding tabs, we're past interrupt mode, ditch the owner
             if (this.mCurrentTab.owner)
               this.mCurrentTab.owner = null;
 
             var t = document.createElementNS(NS_XUL, "tab");
 
@@ -2284,21 +2307,23 @@
             let lazyBrowserURI;
             if (aCreateLazyBrowser && aURI != "about:blank") {
               lazyBrowserURI = Services.io.newURI(aURI);
               aURI = "about:blank";
             }
 
             var uriIsAboutBlank = aURI == "about:blank";
 
-            if (isBlankPageURL(aURI)) {
-              t.setAttribute("label", this.mStringBundle.getString("tabs.emptyTabTitle"));
-            } else if (aURI.toLowerCase().startsWith("javascript:")) {
-              // This can go away when bug 672618 or bug 55696 are fixed.
-              t.setAttribute("label", aURI);
+            if (!aNoInitialLabel) {
+              if (isBlankPageURL(aURI)) {
+                t.setAttribute("label", this.mStringBundle.getString("tabs.emptyTabTitle"));
+              } else {
+                // Set URL as label so that the tab isn't empty initially.
+                this.setInitialTabTitle(t, aURI);
+              }
             }
 
             if (aIsPrerendered) {
               t.setAttribute("hidden", "true");
             }
 
             // Related tab inherits current tab's user context unless a different
             // usercontextid is specified
@@ -2838,17 +2863,16 @@
               aCloseWindow = false;
               aNewTab = false;
             }
 
             this._lastRelatedTab = null;
 
             // update the UI early for responsiveness
             aTab.collapsed = true;
-            this.tabContainer._fillTrailingGap();
             this._blurTab(aTab);
 
             this._removingTabs.splice(this._removingTabs.indexOf(aTab), 1);
 
             if (aCloseWindow) {
               this._windowIsClosing = true;
               while (this._removingTabs.length)
                 this._endRemoveTab(this._removingTabs[0]);
@@ -5998,31 +6022,16 @@
       <method name="_handleTabSelect">
         <parameter name="aSmoothScroll"/>
         <body><![CDATA[
           if (this.getAttribute("overflow") == "true")
             this.mTabstrip.ensureElementIsVisible(this.selectedItem, aSmoothScroll);
         ]]></body>
       </method>
 
-      <method name="_fillTrailingGap">
-        <body><![CDATA[
-          try {
-            // if we're at the right side (and not the logical end,
-            // which is why this works for both LTR and RTL)
-            // of the tabstrip, we need to ensure that we stay
-            // completely scrolled to the right side
-            var tabStrip = this.mTabstrip;
-            if (tabStrip.scrollPosition + tabStrip.scrollClientSize >
-                tabStrip.scrollSize)
-              tabStrip.scrollByPixels(-1);
-          } catch (e) {}
-        ]]></body>
-      </method>
-
       <field name="_closingTabsSpacer">
         document.getAnonymousElementByAttribute(this, "anonid", "closing-tabs-spacer");
       </field>
 
       <field name="_tabDefaultMaxWidth">NaN</field>
       <field name="_lastTabClosedByMouse">false</field>
       <field name="_hasTabTempMaxWidth">false</field>
 
@@ -6302,17 +6311,16 @@
               if (aEvent.target != window)
                 break;
 
               TabsInTitlebar.updateAppearance();
 
               var width = this.mTabstrip.boxObject.width;
               if (width != this.mTabstripWidth) {
                 this.adjustTabstrip();
-                this._fillTrailingGap();
                 this._handleTabSelect(false);
                 this.mTabstripWidth = width;
               }
               break;
             case "mouseout":
               // If the "related target" (the node to which the pointer went) is not
               // a child of the current document, the mouse just left the window.
               let relatedTarget = aEvent.relatedTarget;
@@ -6457,17 +6465,16 @@
         <body><![CDATA[
           if (tab.parentNode != this)
             return;
           tab._fullyOpen = true;
 
           this.adjustTabstrip();
 
           if (tab.getAttribute("selected") == "true") {
-            this._fillTrailingGap();
             this._handleTabSelect();
           } else if (!tab.hasAttribute("skipbackgroundnotify")) {
             this._notifyBackgroundTab(tab);
           }
 
           // XXXmano: this is a temporary workaround for bug 345399
           // We need to manually update the scroll buttons disabled state
           // if a tab was inserted to the overflow area or removed from it
--- a/browser/base/content/test/general/browser_tabopen_reflows.js
+++ b/browser/base/content/test/general/browser_tabopen_reflows.js
@@ -18,28 +18,16 @@ const EXPECTED_REFLOWS = [
     "updateCurrentBrowser@chrome://browser/content/tabbrowser.xml|" +
     "onselect@chrome://browser/content/browser.xul|",
 
   // switching focus in openLinkIn() causes reflows
   "openLinkIn@chrome://browser/content/utilityOverlay.js|" +
     "openUILinkIn@chrome://browser/content/utilityOverlay.js|" +
     "BrowserOpenTab@chrome://browser/content/browser.js|",
 
-  // accessing element.scrollPosition in _fillTrailingGap() flushes layout
-  "get_scrollPosition@chrome://global/content/bindings/scrollbox.xml|" +
-    "_fillTrailingGap@chrome://browser/content/tabbrowser.xml|" +
-    "_handleNewTab@chrome://browser/content/tabbrowser.xml|" +
-    "onxbltransitionend@chrome://browser/content/tabbrowser.xml|",
-
-  // SessionStore.getWindowDimensions()
-  "ssi_getWindowDimension@resource:///modules/sessionstore/SessionStore.jsm|" +
-    "ssi_updateWindowFeatures/<@resource:///modules/sessionstore/SessionStore.jsm|" +
-    "ssi_updateWindowFeatures@resource:///modules/sessionstore/SessionStore.jsm|" +
-    "ssi_collectWindowData@resource:///modules/sessionstore/SessionStore.jsm|",
-
   // selection change notification may cause querying the focused editor content
   // by IME and that will cause reflow.
   "select@chrome://global/content/bindings/textbox.xml|" +
     "focusAndSelectUrlBar@chrome://browser/content/browser.js|" +
     "openLinkIn@chrome://browser/content/utilityOverlay.js|" +
     "openUILinkIn@chrome://browser/content/utilityOverlay.js|" +
     "BrowserOpenTab@chrome://browser/content/browser.js|",
 
--- a/browser/base/content/test/static/browser_misused_characters_in_strings.js
+++ b/browser/base/content/test/static/browser_misused_characters_in_strings.js
@@ -181,17 +181,17 @@ add_task(async function checkAllThePrope
 });
 
 var checkDTD = async function(aURISpec) {
   let rawContents = await fetchFile(aURISpec);
   // The regular expression below is adapted from:
   // https://hg.mozilla.org/mozilla-central/file/68c0b7d6f16ce5bb023e08050102b5f2fe4aacd8/python/compare-locales/compare_locales/parser.py#l233
   let entities = rawContents.match(/<!ENTITY\s+([\w\.]*)\s+("[^"]*"|'[^']*')\s*>/g);
   if (!entities) {
-    // Some files, such as requestAutocomplete.dtd, have no entities defined.
+    // Some files have no entities defined.
     return;
   }
   for (let entity of entities) {
     let [, key, str] = entity.match(/<!ENTITY\s+([\w\.]*)\s+("[^"]*"|'[^']*')\s*>/);
     // The matched string includes the enclosing quotation marks,
     // we need to slice them off.
     str = str.slice(1, -1);
     testForErrors(aURISpec, key, str);
--- a/browser/base/content/test/webextensions/browser_extension_sideloading.js
+++ b/browser/base/content/test/webextensions/browser_extension_sideloading.js
@@ -1,160 +1,94 @@
 const {AddonManagerPrivate} = Cu.import("resource://gre/modules/AddonManager.jsm", {});
 
-// MockAddon mimics the AddonInternal interface and MockProvider implements
-// just enough of the AddonManager provider interface to make it look like
-// we have sideloaded webextensions so the sideloading flow can be tested.
+const {AddonTestUtils} = Cu.import("resource://testing-common/AddonTestUtils.jsm", {});
+
+AddonTestUtils.initMochitest(this);
 
-// MockAddon -> callback
-let setCallbacks = new Map();
-
-class MockAddon {
-  constructor(props) {
-    this._userDisabled = false;
-    this.pendingOperations = 0;
-    this.type = "extension";
+async function createWebExtension(details) {
+  let options = {
+    manifest: {
+      applications: {gecko: {id: details.id}},
 
-    for (let name in props) {
-      if (name == "userDisabled") {
-        this._userDisabled = props[name];
-      }
-      this[name] = props[name];
-    }
-  }
+      name: details.name,
 
-  markAsSeen() {
-    this.seen = true;
-  }
+      permissions: details.permissions,
+    },
+  };
 
-  get userDisabled() {
-    return this._userDisabled;
+  if (details.iconURL) {
+    options.manifest.icons = {"64": details.iconURL};
   }
 
-  set userDisabled(val) {
-    this._userDisabled = val;
-    AddonManagerPrivate.callAddonListeners(val ? "onDisabled" : "onEnabled", this);
-    let fn = setCallbacks.get(this);
-    if (fn) {
-      setCallbacks.delete(this);
-      fn(val);
-    }
-    return val;
-  }
+  let xpi = AddonTestUtils.createTempWebExtensionFile(options);
 
-  get permissions() {
-    return this._userDisabled ? AddonManager.PERM_CAN_ENABLE : AddonManager.PERM_CAN_DISABLE;
-  }
+  await AddonTestUtils.manuallyInstall(xpi);
 }
 
-class MockProvider {
-  constructor(...addons) {
-    this.addons = new Set(addons);
-  }
-
-  startup() { }
-  shutdown() { }
+async function createXULExtension(details) {
+  let xpi = AddonTestUtils.createTempXPIFile({
+    "install.rdf": {
+      id: details.id,
+      name: details.name,
+      version: "0.1",
+      targetApplications: [{
+        id: "toolkit@mozilla.org",
+        minVersion: "0",
+        maxVersion: "*",
+      }],
+    },
+  });
 
-  getAddonByID(id, callback) {
-    for (let addon of this.addons) {
-      if (addon.id == id) {
-        callback(addon);
-        return;
-      }
-    }
-    callback(null);
-  }
-
-  getAddonsByTypes(types, callback) {
-    let addons = [];
-    if (!types || types.includes("extension")) {
-      addons = [...this.addons];
-    }
-    callback(addons);
-  }
-}
-
-function promiseSetDisabled(addon) {
-  return new Promise(resolve => {
-    setCallbacks.set(addon, resolve);
-  });
+  await AddonTestUtils.manuallyInstall(xpi);
 }
 
 let cleanup;
 
 add_task(async function() {
-  // ICON_URL wouldn't ever appear as an actual webextension icon, but
-  // we're just mocking out the addon here, so all we care about is that
-  // that it propagates correctly to the popup.
-  const ICON_URL = "chrome://mozapps/skin/extensions/category-extensions.svg";
   const DEFAULT_ICON_URL = "chrome://mozapps/skin/extensions/extensionGeneric.svg";
 
+  await SpecialPowers.pushPrefEnv({
+    set: [
+      ["xpinstall.signatures.required", false],
+      ["extensions.autoDisableScopes", 15],
+      ["extensions.ui.ignoreUnsigned", true],
+    ],
+  });
+
   const ID1 = "addon1@tests.mozilla.org";
-  let mock1 = new MockAddon({
+  await createWebExtension({
     id: ID1,
     name: "Test 1",
     userDisabled: true,
-    seen: false,
-    userPermissions: {
-      permissions: ["history"],
-      origins: ["https://*/*"],
-    },
-    iconURL: ICON_URL,
+    permissions: ["history", "https://*/*"],
+    iconURL: "foo-icon.png",
   });
 
   const ID2 = "addon2@tests.mozilla.org";
-  let mock2 = new MockAddon({
+  await createXULExtension({
     id: ID2,
     name: "Test 2",
-    userDisabled: true,
-    seen: false,
-    userPermissions: {
-      permissions: [],
-      origins: [],
-    },
   });
 
   const ID3 = "addon3@tests.mozilla.org";
-  let mock3 = new MockAddon({
+  await createWebExtension({
     id: ID3,
     name: "Test 3",
-    isWebExtension: true,
-    userDisabled: true,
-    seen: false,
-    userPermissions: {
-      permissions: [],
-      origins: ["<all_urls>"],
-    }
+    permissions: ["<all_urls>"],
   });
 
   const ID4 = "addon4@tests.mozilla.org";
-  let mock4 = new MockAddon({
+  await createWebExtension({
     id: ID4,
     name: "Test 4",
-    isWebExtension: true,
-    userDisabled: true,
-    seen: false,
-    userPermissions: {
-      permissions: [],
-      origins: ["<all_urls>"],
-    }
+    permissions: ["<all_urls>"],
   });
 
-  let provider = new MockProvider(mock1, mock2, mock3, mock4);
-  AddonManagerPrivate.registerProvider(provider, [{
-    id: "extension",
-    name: "Extensions",
-    uiPriority: 4000,
-    flags: AddonManager.TYPE_UI_VIEW_LIST |
-           AddonManager.TYPE_SUPPORTS_UNDO_RESTARTLESS_UNINSTALL,
-  }]);
-
   testCleanup = async function() {
-    AddonManagerPrivate.unregisterProvider(provider);
-
     // clear out ExtensionsUI state about sideloaded extensions so
     // subsequent tests don't get confused.
     ExtensionsUI.sideloaded.clear();
     ExtensionsUI.emit("change");
   };
 
   // Navigate away from the starting page to force about:addons to load
   // in a new tab during the tests below.
@@ -198,27 +132,23 @@ add_task(async function() {
   is(gBrowser.currentURI.spec, "about:addons", "Foreground tab is at about:addons");
 
   const VIEW = "addons://list/extension";
   let win = gBrowser.selectedBrowser.contentWindow;
   ok(!win.gViewController.isLoading, "about:addons view is fully loaded");
   is(win.gViewController.currentViewId, VIEW, "about:addons is at extensions list");
 
   // Check the contents of the notification, then choose "Cancel"
-  checkNotification(panel, ICON_URL, [
+  checkNotification(panel, /\/foo-icon\.png$/, [
     ["webextPerms.hostDescription.allUrls"],
     ["webextPerms.description.history"],
   ]);
 
-  let disablePromise = promiseSetDisabled(mock1);
   panel.secondaryButton.click();
 
-  let value = await disablePromise;
-  is(value, true, "Addon should remain disabled");
-
   let [addon1, addon2, addon3, addon4] = await AddonManager.getAddonsByIDs([ID1, ID2, ID3, ID4]);
   ok(addon1.seen, "Addon should be marked as seen");
   is(addon1.userDisabled, true, "Addon 1 should still be disabled");
   is(addon2.userDisabled, true, "Addon 2 should still be disabled");
   is(addon3.userDisabled, true, "Addon 3 should still be disabled");
   is(addon4.userDisabled, true, "Addon 4 should still be disabled");
 
   await BrowserTestUtils.removeTab(gBrowser.selectedTab);
@@ -240,22 +170,18 @@ add_task(async function() {
   win = gBrowser.selectedBrowser.contentWindow;
   ok(!win.gViewController.isLoading, "about:addons view is fully loaded");
   is(win.gViewController.currentViewId, VIEW, "about:addons is at extensions list");
 
   // Check the notification contents.
   checkNotification(panel, DEFAULT_ICON_URL, []);
 
   // This time accept the install.
-  disablePromise = promiseSetDisabled(mock2);
   panel.button.click();
 
-  value = await disablePromise;
-  is(value, false, "Addon should be set to enabled");
-
   [addon1, addon2, addon3, addon4] = await AddonManager.getAddonsByIDs([ID1, ID2, ID3, ID4]);
   is(addon1.userDisabled, true, "Addon 1 should still be disabled");
   is(addon2.userDisabled, false, "Addon 2 should now be enabled");
   is(addon3.userDisabled, true, "Addon 3 should still be disabled");
   is(addon4.userDisabled, true, "Addon 4 should still be disabled");
 
   // Should still have 2 entries in the hamburger menu
   await PanelUI.show();
@@ -283,20 +209,17 @@ add_task(async function() {
   // When clicking enable we should see the permissions notification
   popupPromise = promisePopupNotificationShown("addon-webext-permissions");
   BrowserTestUtils.synthesizeMouseAtCenter(item._enableBtn, {},
                                            gBrowser.selectedBrowser);
   panel = await popupPromise;
   checkNotification(panel, DEFAULT_ICON_URL, [["webextPerms.hostDescription.allUrls"]]);
 
   // Accept the permissions
-  disablePromise = promiseSetDisabled(mock3);
   panel.button.click();
-  value = await disablePromise;
-  is(value, false, "userDisabled should be set on addon 3");
 
   addon3 = await AddonManager.getAddonByID(ID3);
   is(addon3.userDisabled, false, "Addon 3 should be enabled");
 
   // Should still have 1 entry in the hamburger menu
   await PanelUI.show();
 
   addons = PanelUI.addonNotificationContainer;
@@ -311,23 +234,26 @@ add_task(async function() {
   // When clicking enable we should see the permissions notification
   popupPromise = promisePopupNotificationShown("addon-webext-permissions");
   BrowserTestUtils.synthesizeMouseAtCenter(button, {},
                                            gBrowser.selectedBrowser);
   panel = await popupPromise;
   checkNotification(panel, DEFAULT_ICON_URL, [["webextPerms.hostDescription.allUrls"]]);
 
   // Accept the permissions
-  disablePromise = promiseSetDisabled(mock4);
   panel.button.click();
-  value = await disablePromise;
-  is(value, false, "userDisabled should be set on addon 4");
 
   addon4 = await AddonManager.getAddonByID(ID4);
   is(addon4.userDisabled, false, "Addon 4 should be enabled");
 
   // We should have recorded 1 cancelled followed by 3 accepted sideloads.
   expectTelemetry(["sideloadRejected", "sideloadAccepted", "sideloadAccepted", "sideloadAccepted"]);
 
   isnot(menuButton.getAttribute("badge-status"), "addon-alert", "Should no longer have addon alert badge");
 
+  await new Promise(resolve => setTimeout(resolve, 100));
+
+  for (let addon of [addon1, addon2, addon3, addon4]) {
+    addon.uninstall();
+  }
+
   await BrowserTestUtils.removeTab(gBrowser.selectedTab);
 });
--- a/browser/base/content/test/webextensions/head.js
+++ b/browser/base/content/test/webextensions/head.js
@@ -213,17 +213,17 @@ function checkPermissionString(string, k
  *        optional formatting parameter.
  */
 function checkNotification(panel, checkIcon, permissions) {
   let icon = panel.getAttribute("icon");
   let ul = document.getElementById("addon-webext-perm-list");
   let header = document.getElementById("addon-webext-perm-intro");
 
   if (checkIcon instanceof RegExp) {
-    ok(checkIcon.test(icon), "Notification icon is correct");
+    ok(checkIcon.test(icon), `Notification icon is correct ${JSON.stringify(icon)} ~= ${checkIcon}`);
   } else if (typeof checkIcon == "function") {
     ok(checkIcon(icon), "Notification icon is correct");
   } else {
     is(icon, checkIcon, "Notification icon is correct");
   }
 
   is(ul.childElementCount, permissions.length, `Permissions list has ${permissions.length} entries`);
   if (permissions.length == 0) {
--- a/browser/components/about/AboutRedirector.cpp
+++ b/browser/components/about/AboutRedirector.cpp
@@ -167,33 +167,37 @@ AboutRedirector::NewChannel(nsIURI* aURI
         url.AssignASCII(redir.url);
       }
 
       nsCOMPtr<nsIChannel> tempChannel;
       nsCOMPtr<nsIURI> tempURI;
       rv = NS_NewURI(getter_AddRefs(tempURI), url);
       NS_ENSURE_SUCCESS(rv, rv);
 
-      // If tempURI links to an internal URI (chrome://, resource://)
-      // then set the result principal URL on the channel's load info.
-      // Otherwise, we leave it null which forces the channel principal
-      // to reflect the displayed URL rather than being the systemPrincipal.
+      // If tempURI links to an external URI (i.e. something other than
+      // chrome:// or resource://) then set the LOAD_REPLACE flag on the
+      // channel which forces the channel owner to reflect the displayed
+      // URL rather then being the systemPrincipal.
       bool isUIResource = false;
       rv = NS_URIChainHasFlags(tempURI, nsIProtocolHandler::URI_IS_UI_RESOURCE,
                                &isUIResource);
       NS_ENSURE_SUCCESS(rv, rv);
 
+      nsLoadFlags loadFlags = isUIResource
+                    ? static_cast<nsLoadFlags>(nsIChannel::LOAD_NORMAL)
+                    : static_cast<nsLoadFlags>(nsIChannel::LOAD_REPLACE);
+
       rv = NS_NewChannelInternal(getter_AddRefs(tempChannel),
                                  tempURI,
-                                 aLoadInfo);
+                                 aLoadInfo,
+                                 nullptr, // aLoadGroup
+                                 nullptr, // aCallbacks
+                                 loadFlags);
       NS_ENSURE_SUCCESS(rv, rv);
 
-      if (isUIResource) {
-        aLoadInfo->SetResultPrincipalURI(aURI);
-      }
       tempChannel->SetOriginalURI(aURI);
 
       NS_ADDREF(*result = tempChannel);
       return rv;
     }
   }
 
   return NS_ERROR_ILLEGAL_VALUE;
--- a/browser/components/feeds/FeedConverter.js
+++ b/browser/components/feeds/FeedConverter.js
@@ -248,17 +248,16 @@ FeedConverter.prototype = {
         // Store the result in the result service so that the display
         // page can access it.
         feedService.addFeedResult(result);
 
         // Now load the actual XUL document.
         let aboutFeedsURI = ios.newURI("about:feeds");
         chromeChannel = ios.newChannelFromURIWithLoadInfo(aboutFeedsURI, loadInfo);
         chromeChannel.originalURI = result.uri;
-        loadInfo.resultPrincipalURI = result.uri;
 
         // carry the origin attributes from the channel that loaded the feed.
         chromeChannel.owner =
           Services.scriptSecurityManager.createCodebasePrincipal(aboutFeedsURI,
                                                                  loadInfo.originAttributes);
       } else {
         chromeChannel = ios.newChannelFromURIWithLoadInfo(result.uri, loadInfo);
       }
@@ -556,22 +555,20 @@ GenericProtocolHandler.prototype = {
     let inner = aUri.QueryInterface(Ci.nsINestedURI).innerURI;
     let channel = Cc["@mozilla.org/network/io-service;1"].
                   getService(Ci.nsIIOService).
                   newChannelFromURIWithLoadInfo(inner, aLoadInfo);
 
     const schemeId = this._getTelemetrySchemeId();
     Services.telemetry.getHistogramById("FEED_PROTOCOL_USAGE").add(schemeId);
 
-    if (channel instanceof Components.interfaces.nsIHttpChannel) {
+    if (channel instanceof Components.interfaces.nsIHttpChannel)
       // Set this so we know this is supposed to be a feed
       channel.setRequestHeader("X-Moz-Is-Feed", "1", false);
-    }
     channel.originalURI = aUri;
-    aLoadInfo.resultPrincipalURI = aUri;
     return channel;
   },
 
   QueryInterface(iid) {
     if (iid.equals(Ci.nsIProtocolHandler) ||
         iid.equals(Ci.nsISupports))
       return this;
     throw Cr.NS_ERROR_NO_INTERFACE;
--- a/browser/components/migration/MSMigrationUtils.jsm
+++ b/browser/components/migration/MSMigrationUtils.jsm
@@ -462,18 +462,19 @@ Cookies.prototype = {
     // If UAC is enabled, the most common destination is CookD/Low.  Though,
     // if the user runs the application in administrator mode or disables UAC,
     // cookies are stored in the original CookD destination.  Cause running the
     // browser in administrator mode is unsafe and discouraged, we just care
     // about the UAC state.
     if (!this.__cookiesFolder) {
       let cookiesFolder = Services.dirsvc.get("CookD", Ci.nsIFile);
       if (cookiesFolder.exists() && cookiesFolder.isReadable()) {
-        // Check if UAC is enabled.
-        if (Services.appinfo.QueryInterface(Ci.nsIWinAppHelper).userCanElevate) {
+        // In versions up to Windows 7, check if UAC is enabled.
+        if (AppConstants.isPlatformAndVersionAtMost("win", "6.1") &&
+            Services.appinfo.QueryInterface(Ci.nsIWinAppHelper).userCanElevate) {
           cookiesFolder.append("Low");
         }
         this.__cookiesFolder = cookiesFolder;
       }
     }
     return this.__cookiesFolder;
   },
 
@@ -507,17 +508,17 @@ Cookies.prototype = {
       let success = false;
       let folders = this._migrationType == MSMigrationUtils.MIGRATION_TYPE_EDGE ?
                       this.__cookiesFolders : [this.__cookiesFolder];
       for (let folder of folders) {
         let entries = folder.directoryEntries;
         while (entries.hasMoreElements()) {
           let entry = entries.getNext().QueryInterface(Ci.nsIFile);
           // Skip eventual bogus entries.
-          if (!entry.isFile() || !/\.txt$/.test(entry.leafName))
+          if (!entry.isFile() || !/\.(cookie|txt)$/.test(entry.leafName))
             continue;
 
           this._readCookieFile(entry, function(aSuccess) {
             // Importing even a single cookie file is considered a success.
             if (aSuccess)
               success = true;
             try {
               cookiesGenerator.next();
--- a/browser/components/preferences/in-content/findInPage.js
+++ b/browser/components/preferences/in-content/findInPage.js
@@ -7,16 +7,17 @@
 var gSearchResultsPane = {
   findSelection: null,
   searchResultsCategory: null,
   searchInput: null,
 
   init() {
     let controller = this.getSelectionController();
     this.findSelection = controller.getSelection(Ci.nsISelectionController.SELECTION_FIND);
+    this.findSelection.setColors("currentColor", "#ffe900", "currentColor", "#540ead");
     this.searchResultsCategory = document.getElementById("category-search-results");
 
     this.searchInput = document.getElementById("searchInput");
     this.searchInput.hidden = !Services.prefs.getBoolPref("browser.preferences.search");
     if (!this.searchInput.hidden) {
       this.searchInput.addEventListener("command", this);
       this.searchInput.addEventListener("focus", this);
     }
--- a/browser/components/sessionstore/SessionStore.jsm
+++ b/browser/components/sessionstore/SessionStore.jsm
@@ -2639,19 +2639,19 @@ var SessionStoreInternal = {
       }
     }
 
     let activePageData = tabData.entries[tabData.index - 1] || null;
 
     // If the page has a title, set it.
     if (activePageData) {
       if (activePageData.title) {
-        tab.label = activePageData.title;
+        win.gBrowser.setInitialTabTitle(tab, activePageData.title);
       } else if (activePageData.url != "about:blank") {
-        tab.label = activePageData.url;
+        win.gBrowser.setInitialTabTitle(tab, activePageData.url);
       }
     } else if (tab.hasAttribute("customizemode")) {
       win.gCustomizeMode.setTab(tab);
     }
 
     // Restore the tab icon.
     if ("image" in tabData) {
       // Use the serialized contentPrincipal with the new icon load.
@@ -3286,19 +3286,23 @@ var SessionStoreInternal = {
       let url = "about:blank";
       if (createLazyBrowser) {
         // Let tabbrowser know the future URI because progress listeners won't
         // get onLocationChange notification before the browser is inserted.
         let activeIndex = (tabData.index || tabData.entries.length) - 1;
         url = tabData.entries[activeIndex].url;
       }
 
+      // Setting noInitialLabel is a perf optimization. Rendering tab labels
+      // would make resizing the tabs more expensive as we're adding them.
+      // Each tab will get its initial label set in restoreTab.
       let tab = tabbrowser.addTab(url,
                                   { createLazyBrowser,
                                     skipAnimation: true,
+                                    noInitialLabel: true,
                                     userContextId,
                                     skipBackgroundNotify: true });
 
       if (select) {
         // Select a new tab first to prevent the removeTab loop from changing
         // the selected tab over and over again.
         tabbrowser.selectedTab = tab;
 
--- a/browser/components/sessionstore/test/browser.ini
+++ b/browser/components/sessionstore/test/browser.ini
@@ -107,21 +107,23 @@ skip-if = e10s # Bug 1271024
 [browser_replace_load.js]
 [browser_restore_redirect.js]
 [browser_restore_cookies_noOriginAttributes.js]
 [browser_scrollPositions.js]
 [browser_scrollPositionsReaderMode.js]
 [browser_sessionHistory.js]
 [browser_sessionStorage.js]
 [browser_sessionStorage_size.js]
+[browser_tab_label_during_restore.js]
 [browser_swapDocShells.js]
 [browser_switch_remoteness.js]
 run-if = e10s
 [browser_upgrade_backup.js]
 [browser_windowRestore_perwindowpb.js]
+
 [browser_248970_b_perwindowpb.js]
 # Disabled because of leaks.
 # Re-enabling and rewriting this test is tracked in bug 936919.
 skip-if = true
 [browser_339445.js]
 [browser_345898.js]
 [browser_350525.js]
 [browser_354894_perwindowpb.js]
@@ -218,17 +220,17 @@ skip-if = (os == 'win' && bits == 64) ||
 # Disabled for frequent intermittent failures
 [browser_464620_a.js]
 skip-if = true
 [browser_464620_b.js]
 skip-if = true
 
 # Disabled on OS X:
 [browser_625016.js]
-skip-if = os == "mac"
+skip-if = os == "mac" || (os == "linux" && debug) # linux, Bug 1348583
 
 [browser_906076_lazy_tabs.js]
 [browser_911547.js]
 [browser_send_async_message_oom.js]
 [browser_multiple_navigateAndRestore.js]
 run-if = e10s
 [browser_async_window_flushing.js]
 [browser_forget_async_closings.js]
--- a/browser/components/sessionstore/test/browser_parentProcessRestoreHash.js
+++ b/browser/components/sessionstore/test/browser_parentProcessRestoreHash.js
@@ -21,17 +21,16 @@ let TestAboutPage = {
   },
 
   newChannel(aURI, aLoadInfo) {
     // about: page inception!
     let newURI = Services.io.newURI(SELFCHROMEURL);
     let channel = Services.io.newChannelFromURIWithLoadInfo(newURI,
                                                             aLoadInfo);
     channel.originalURI = aURI;
-    aLoadInfo.resultPrincipalURI = aURI;
     return channel;
   },
 
   createInstance(outer, iid) {
     if (outer != null) {
       throw Cr.NS_ERROR_NO_AGGREGATION;
     }
     return this.QueryInterface(iid);
new file mode 100644
--- /dev/null
+++ b/browser/components/sessionstore/test/browser_tab_label_during_restore.js
@@ -0,0 +1,85 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * Test that we don't do unnecessary tab label changes while restoring a tab.
+ */
+
+add_task(async function() {
+  await SpecialPowers.pushPrefEnv({
+    "set": [
+      ["browser.sessionstore.restore_on_demand", true],
+      ["browser.sessionstore.restore_tabs_lazily", true],
+    ]
+  });
+  const BACKUP_STATE = SessionStore.getBrowserState();
+  const TEST_URL = "http://example.com/";
+
+  function observeLabelChanges(tab) {
+    info("observing tab label changes. initial label: " + tab.label);
+    let labelChangeCount = 0;
+    function TabAttrModifiedListener(event) {
+      if (event.detail.changed.some(attr => { return attr == "label" })) {
+        info("tab label change: " + tab.label);
+        labelChangeCount++;
+      }
+    }
+    tab.addEventListener("TabAttrModified", TabAttrModifiedListener);
+    return (expectedCount) => {
+      tab.removeEventListener("TabAttrModified", TabAttrModifiedListener);
+      is(labelChangeCount, expectedCount, "observed tab label changes");
+    }
+  }
+
+  info("setting test browser state");
+  let browserLoadedPromise = BrowserTestUtils.firstBrowserLoaded(window, false);
+  await promiseBrowserState({
+    windows: [{
+      tabs: [
+        { entries: [{ url: TEST_URL }] },
+        { entries: [{ url: TEST_URL }] },
+      ]
+    }]
+  });
+  let [firstTab, secondTab] = gBrowser.tabs;
+  is(gBrowser.selectedTab, firstTab, "first tab is selected");
+
+  await browserLoadedPromise;
+  const CONTENT_TITLE = firstTab.linkedBrowser.contentTitle;
+  is(firstTab.linkedBrowser.currentURI.spec, TEST_URL, "correct URL loaded in first tab");
+  is(typeof CONTENT_TITLE, "string", "content title is a string");
+  isnot(CONTENT_TITLE.length, 0, "content title isn't empty");
+  isnot(CONTENT_TITLE, TEST_URL, "content title is different from the URL");
+  is(firstTab.label, CONTENT_TITLE, "first tab displays content title");
+  ok(secondTab.hasAttribute("pending"), "second tab is pending");
+  is(secondTab.label, TEST_URL, "second tab displays URL as its title");
+
+  info("selecting the second tab");
+  let checkLabelChangeCount = observeLabelChanges(secondTab);
+  browserLoadedPromise = BrowserTestUtils.browserLoaded(secondTab.linkedBrowser, false, TEST_URL);
+  gBrowser.selectedTab = secondTab;
+  await browserLoadedPromise;
+  ok(!secondTab.hasAttribute("pending"), "second tab isn't pending anymore");
+  is(gBrowser.selectedTab.label, CONTENT_TITLE, "second tab displays content title");
+  checkLabelChangeCount(1);
+
+  info("restoring the modified browser state");
+  await TabStateFlusher.flushWindow(window);
+  await promiseBrowserState(SessionStore.getBrowserState());
+  [firstTab, secondTab] = gBrowser.tabs;
+  is(secondTab, gBrowser.selectedTab, "second tab is selected after restoring");
+  ok(firstTab.hasAttribute("pending"), "first tab is pending after restoring");
+  is(firstTab.label, CONTENT_TITLE, "first tab displays content title in pending state");
+
+  info("selecting the first tab");
+  checkLabelChangeCount = observeLabelChanges(firstTab);
+  let tabContentRestored = TestUtils.topicObserved("sessionstore-debug-tab-restored");
+  gBrowser.selectedTab = firstTab;
+  await tabContentRestored;
+  ok(!firstTab.hasAttribute("pending"), "first tab isn't pending anymore");
+  checkLabelChangeCount(0);
+  is(firstTab.label, CONTENT_TITLE, "first tab displays content title after restoring content");
+
+  await promiseBrowserState(BACKUP_STATE);
+});
+
--- a/browser/extensions/pdfjs/content/PdfStreamConverter.jsm
+++ b/browser/extensions/pdfjs/content/PdfStreamConverter.jsm
@@ -998,17 +998,16 @@ PdfStreamConverter.prototype = {
             domWindow.frameElement.className === "previewPluginContentFrame";
           PdfJsTelemetry.onEmbed(isObjectEmbed);
         }
       }
     };
 
     // Keep the URL the same so the browser sees it as the same.
     channel.originalURI = aRequest.URI;
-    channel.loadInfo.resultPrincipalURI = aRequest.loadInfo.resultPrincipalURI;
     channel.loadGroup = aRequest.loadGroup;
     channel.loadInfo.originAttributes = aRequest.loadInfo.originAttributes;
 
     // We can use the resource principal when data is fetched by the chrome,
     // e.g. useful for NoScript. Make make sure we reuse the origin attributes
     // from the request channel to keep isolation consistent.
     var ssm = Cc["@mozilla.org/scriptsecuritymanager;1"]
                 .getService(Ci.nsIScriptSecurityManager);
--- a/browser/extensions/pocket/content/AboutPocket.jsm
+++ b/browser/extensions/pocket/content/AboutPocket.jsm
@@ -35,17 +35,16 @@ AboutPage.prototype = {
     return this.uriFlags;
   },
 
   newChannel(aURI, aLoadInfo) {
     let newURI = Services.io.newURI(this.chromeURL);
     let channel = Services.io.newChannelFromURIWithLoadInfo(newURI,
                                                             aLoadInfo);
     channel.originalURI = aURI;
-    aLoadInfo.resultPrincipalURI = aURI;
 
     if (this.uriFlags & Ci.nsIAboutModule.URI_SAFE_FOR_UNTRUSTED_CONTENT) {
       let principal = Services.scriptSecurityManager.getNoAppCodebasePrincipal(aURI);
       channel.owner = principal;
     }
     return channel;
   },
 
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -307,17 +307,16 @@
 @RESPATH@/components/captivedetect.xpt
 @RESPATH@/browser/components/shellservice.xpt
 @RESPATH@/components/shistory.xpt
 @RESPATH@/components/spellchecker.xpt
 @RESPATH@/components/storage.xpt
 @RESPATH@/components/toolkit_asyncshutdown.xpt
 @RESPATH@/components/toolkit_filewatcher.xpt
 @RESPATH@/components/toolkit_finalizationwitness.xpt
-@RESPATH@/components/toolkit_formautofill.xpt
 @RESPATH@/components/toolkit_osfile.xpt
 @RESPATH@/components/toolkit_securityreporter.xpt
 @RESPATH@/components/toolkit_perfmonitoring.xpt
 @RESPATH@/components/toolkit_xulstore.xpt
 @RESPATH@/components/toolkitprofile.xpt
 #ifdef MOZ_ENABLE_XREMOTE
 @RESPATH@/components/toolkitremote.xpt
 #endif
@@ -466,19 +465,16 @@
 @RESPATH@/components/nsHandlerService.manifest
 @RESPATH@/components/nsHandlerService.js
 @RESPATH@/components/nsWebHandlerApp.manifest
 @RESPATH@/components/nsWebHandlerApp.js
 @RESPATH@/components/satchel.manifest
 @RESPATH@/components/nsFormAutoComplete.js
 @RESPATH@/components/FormHistoryStartup.js
 @RESPATH@/components/nsInputListAutoComplete.js
-@RESPATH@/components/formautofill.manifest
-@RESPATH@/components/FormAutofillContentService.js
-@RESPATH@/components/FormAutofillStartup.js
 @RESPATH@/components/contentAreaDropListener.manifest
 @RESPATH@/components/contentAreaDropListener.js
 @RESPATH@/browser/components/BrowserProfileMigrators.manifest
 @RESPATH@/browser/components/ProfileMigrator.js
 @RESPATH@/browser/components/ChromeProfileMigrator.js
 @RESPATH@/browser/components/FirefoxProfileMigrator.js
 #ifdef XP_WIN
 @RESPATH@/browser/components/360seProfileMigrator.js
--- a/browser/locales/en-US/chrome/browser/browser.dtd
+++ b/browser/locales/en-US/chrome/browser/browser.dtd
@@ -393,16 +393,19 @@ These should match what Safari and other
 <!ENTITY customizeMenu.moveToPanel.accesskey "o">
 <!ENTITY customizeMenu.removeFromToolbar.label "Remove from Toolbar">
 <!ENTITY customizeMenu.removeFromToolbar.accesskey "R">
 <!ENTITY customizeMenu.removeFromMenu.label "Remove from Menu">
 <!ENTITY customizeMenu.removeFromMenu.accesskey "R">
 <!ENTITY customizeMenu.addMoreItems.label "Add More Items…">
 <!ENTITY customizeMenu.addMoreItems.accesskey "A">
 
+<!-- LOCALIZATION NOTE (moreMenu.label) This label is used in the new Photon
+    app (hamburger) menu. When clicked, it opens a subview that contains
+    secondary commands. -->
 <!ENTITY moreMenu.label "More">
 
 <!ENTITY openCmd.commandkey           "l">
 <!ENTITY urlbar.placeholder2          "Search or enter address">
 <!ENTITY urlbar.accesskey             "d">
 <!-- LOCALIZATION NOTE (urlbar.extension.label): Used to indicate that a selected autocomplete entry is provided by an extension. -->
 <!ENTITY urlbar.extension.label       "Extension:">
 <!ENTITY urlbar.switchToTab.label     "Switch to tab:">
--- a/browser/modules/ExtensionsUI.jsm
+++ b/browser/modules/ExtensionsUI.jsm
@@ -7,16 +7,18 @@ const {classes: Cc, interfaces: Ci, resu
 
 this.EXPORTED_SYMBOLS = ["ExtensionsUI"];
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/EventEmitter.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
                                   "resource://gre/modules/AddonManager.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "AddonManagerPrivate",
+                                  "resource://gre/modules/AddonManager.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PluralForm",
                                   "resource://gre/modules/PluralForm.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "RecentWindow",
                                   "resource:///modules/RecentWindow.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Services",
                                   "resource://gre/modules/Services.jsm");
 
 XPCOMUtils.defineLazyPreferenceGetter(this, "WEBEXT_PERMISSION_PROMPTS",
@@ -30,71 +32,73 @@ const BRAND_PROPERTIES = "chrome://brand
 const HTML_NS = "http://www.w3.org/1999/xhtml";
 
 this.ExtensionsUI = {
   sideloaded: new Set(),
   updates: new Set(),
   sideloadListener: null,
   histogram: null,
 
-  init() {
+  async init() {
     this.histogram = Services.telemetry.getHistogramById("EXTENSION_INSTALL_PROMPT_RESULT");
 
     Services.obs.addObserver(this, "webextension-permission-prompt");
     Services.obs.addObserver(this, "webextension-update-permissions");
     Services.obs.addObserver(this, "webextension-install-notify");
     Services.obs.addObserver(this, "webextension-optional-permission-prompt");
 
+    await RecentWindow.getMostRecentBrowserWindow().delayedStartupPromise;
+
     this._checkForSideloaded();
   },
 
-  _checkForSideloaded() {
-    AddonManager.getAllAddons(addons => {
-      // Check for any side-loaded addons that the user is allowed
-      // to enable.
-      let sideloaded = addons.filter(
-        addon => addon.seen === false && (addon.permissions & AddonManager.PERM_CAN_ENABLE));
+  async _checkForSideloaded() {
+    let sideloaded = await AddonManagerPrivate.getNewSideloads();
+
+    if (!sideloaded.length) {
+      // No new side-loads. We're done.
+      return;
+    }
+
+    // The ordering shouldn't matter, but tests depend on notifications
+    // happening in a specific order.
+    sideloaded.sort((a, b) => a.id.localeCompare(b.id));
 
-      if (!sideloaded.length) {
-        return;
+    if (WEBEXT_PERMISSION_PROMPTS) {
+      if (!this.sideloadListener) {
+        this.sideloadListener = {
+          onEnabled: addon => {
+            if (!this.sideloaded.has(addon)) {
+              return;
+            }
+
+            this.sideloaded.delete(addon);
+            this.emit("change");
+
+            if (this.sideloaded.size == 0) {
+              AddonManager.removeAddonListener(this.sideloadListener);
+              this.sideloadListener = null;
+            }
+          },
+        };
+        AddonManager.addAddonListener(this.sideloadListener);
       }
 
-      if (WEBEXT_PERMISSION_PROMPTS) {
-        if (!this.sideloadListener) {
-          this.sideloadListener = {
-            onEnabled: addon => {
-              if (!this.sideloaded.has(addon)) {
-                return;
-              }
-
-              this.sideloaded.delete(addon);
-              this.emit("change");
-
-              if (this.sideloaded.size == 0) {
-                AddonManager.removeAddonListener(this.sideloadListener);
-                this.sideloadListener = null;
-              }
-            },
-          };
-          AddonManager.addAddonListener(this.sideloadListener);
-        }
-
-        for (let addon of sideloaded) {
-          this.sideloaded.add(addon);
-        }
-        this.emit("change");
-      } else {
-        // This and all the accompanying about:newaddon code can eventually
-        // be removed.  See bug 1331521.
-        let win = RecentWindow.getMostRecentBrowserWindow();
-        for (let addon of sideloaded) {
-          win.openUILinkIn(`about:newaddon?id=${addon.id}`, "tab");
-        }
+      for (let addon of sideloaded) {
+        this.sideloaded.add(addon);
       }
-    });
+      this.emit("change");
+    } else {
+      // This and all the accompanying about:newaddon code can eventually
+      // be removed.  See bug 1331521.
+      let win = RecentWindow.getMostRecentBrowserWindow();
+      for (let addon of sideloaded) {
+        win.openUILinkIn(`about:newaddon?id=${addon.id}`, "tab");
+      }
+    }
   },
 
   showAddonsManager(browser, strings, icon, histkey) {
     let global = browser.selectedBrowser.ownerGlobal;
     return global.BrowserOpenAddonsMgr("addons://list/extension").then(aomWin => {
       let aomBrowser = aomWin.QueryInterface(Ci.nsIInterfaceRequestor)
                              .getInterface(Ci.nsIDocShell)
                              .chromeEventHandler;
@@ -141,16 +145,21 @@ this.ExtensionsUI = {
       // there are multiple simultaneous installs happening, see
       // bug 1329884 for a longer explanation.
       let progressNotification = target.ownerGlobal.PopupNotifications.getNotification("addon-progress", target);
       if (progressNotification) {
         progressNotification.remove();
       }
 
       info.unsigned = info.addon.signedState <= AddonManager.SIGNEDSTATE_MISSING;
+      if (info.unsigned && Cu.isInAutomation &&
+          Services.prefs.getBoolPref("extensions.ui.ignoreUnsigned", false)) {
+        info.unsigned = false;
+      }
+
       let strings = this._buildStrings(info);
 
       // If this is an update with no promptable permissions, just apply it
       if (info.type == "update" && strings.msgs.length == 0) {
         info.resolve();
         return;
       }
 
--- a/build/moz.configure/checks.configure
+++ b/build/moz.configure/checks.configure
@@ -89,17 +89,17 @@ def checking(what, callback=None):
 # The simplest form is:
 #   check_prog('PROG', ('a', 'b'))
 # This will look for 'a' or 'b' in $PATH, and set_config PROG to the one
 # it can find. If PROG is already set from the environment or command line,
 # use that value instead.
 @template
 @imports(_from='mozbuild.shellutil', _import='quote')
 def check_prog(var, progs, what=None, input=None, allow_missing=False,
-               paths=None):
+               paths=None, when=None):
     if input is not None:
         # Wrap input with type checking and normalization.
         @depends(input)
         def input(value):
             if not value:
                 return
             if isinstance(value, str):
                 return (value,)
@@ -112,17 +112,17 @@ def check_prog(var, progs, what=None, in
                help='Path to %s' % (what or 'the %s program' % var.lower()))
         input = var
     what = what or var.lower()
 
     # Trick to make a @depends function out of an immediate value.
     progs = dependable(progs)
     paths = dependable(paths)
 
-    @depends_if(input, progs, paths)
+    @depends_if(input, progs, paths, when=when)
     @checking('for %s' % what, lambda x: quote(x) if x else 'not found')
     def check(value, progs, paths):
         if progs is None:
             progs = ()
 
         if not isinstance(progs, (tuple, list)):
             configure_error('progs must resolve to a list or tuple!')
 
--- a/build/moz.configure/rust.configure
+++ b/build/moz.configure/rust.configure
@@ -163,16 +163,19 @@ def rust_triple_alias(host_or_target):
             ('aarch64', 'Android'): 'aarch64-linux-android',
             ('arm', 'Android'): 'armv7-linux-androideabi',
             ('x86', 'Android'): 'i686-linux-android',
             # Windows
             ('x86', 'WINNT'): 'i686-pc-windows-msvc',
             ('x86_64', 'WINNT'): 'x86_64-pc-windows-msvc',
             ('x86', 'WINNT-MINGW'): 'i686-pc-windows-gnu',
             ('x86_64', 'WINNT-MINGW'): 'x86_64-pc-windows-gnu',
+            # Solaris
+            ('x86_64', 'SunOS'): 'x86_64-sun-solaris',
+            ('sparc64', 'SunOS'): 'sparcv9-sun-solaris',
         }.get((host_or_target.cpu, os_or_kernel), None)
 
         if rustc_target is None:
             die("Don't know how to translate {} for rustc".format(host_or_target.alias))
 
         # Check to see whether our rustc has a reasonably functional stdlib
         # for our chosen target.
         target_arg = '--target=' + rustc_target
--- a/build/moz.configure/util.configure
+++ b/build/moz.configure/util.configure
@@ -385,19 +385,24 @@ def delayed_getattr(func, key):
         return getattr(value, key, None)
 
     return result
 
 
 # Like @depends, but the decorated function is only called if one of the
 # arguments it would be called with has a positive value (bool(value) is True)
 @template
-def depends_if(*args):
+def depends_if(*args, **kwargs):
+    if kwargs:
+        assert len(kwargs) == 1
+        when = kwargs['when']
+    else:
+        when = None
     def decorator(func):
-        @depends(*args)
+        @depends(*args, when=when)
         def wrapper(*args):
             if any(arg for arg in args):
                 return func(*args)
         return wrapper
     return decorator
 
 # Hacks related to old-configure
 # ==============================
--- a/chrome/nsChromeProtocolHandler.cpp
+++ b/chrome/nsChromeProtocolHandler.cpp
@@ -163,21 +163,22 @@ nsChromeProtocolHandler::NewChannel2(nsI
             file->GetNativePath(path);
             printf("Chrome file doesn't exist: %s\n", path.get());
         }
     }
 #endif
 
     // Make sure that the channel remembers where it was
     // originally loaded from.
+    nsLoadFlags loadFlags = 0;
+    result->GetLoadFlags(&loadFlags);
+    result->SetLoadFlags(loadFlags & ~nsIChannel::LOAD_REPLACE);
     rv = result->SetOriginalURI(aURI);
     if (NS_FAILED(rv)) return rv;
 
-    aLoadInfo->SetResultPrincipalURI(aURI);
-
     // Get a system principal for content files and set the owner
     // property of the result
     nsCOMPtr<nsIURL> url = do_QueryInterface(aURI);
     nsAutoCString path;
     rv = url->GetPath(path);
     if (StringBeginsWith(path, NS_LITERAL_CSTRING("/content/")))
     {
         nsCOMPtr<nsIScriptSecurityManager> securityManager =
--- a/devtools/client/debugger/new/test/mochitest/browser.ini
+++ b/devtools/client/debugger/new/test/mochitest/browser.ini
@@ -54,16 +54,17 @@ support-files =
 [browser_dbg-console.js]
 [browser_dbg-debugger-buttons.js]
 [browser_dbg-editor-gutter.js]
 [browser_dbg-editor-select.js]
 [browser_dbg-editor-highlight.js]
 [browser_dbg-iframes.js]
 [browser_dbg_keyboard_navigation.js]
 [browser_dbg_keyboard-shortcuts.js]
+skip-if = os == "linux" # bug 1351952
 [browser_dbg-pause-exceptions.js]
 [browser_dbg-navigation.js]
 [browser_dbg-pretty-print.js]
 [browser_dbg-pretty-print-paused.js]
 [browser_dbg-scopes-mutations.js]
 [browser_dbg-searching.js]
 skip-if = true
 [browser_dbg-sourcemaps.js]
--- a/devtools/client/framework/about-devtools-toolbox.js
+++ b/devtools/client/framework/about-devtools-toolbox.js
@@ -21,20 +21,16 @@ AboutURL.prototype = {
   classID: components.ID("11342911-3135-45a8-8d71-737a2b0ad469"),
   contractID: "@mozilla.org/network/protocol/about;1?what=devtools-toolbox",
 
   QueryInterface: XPCOMUtils.generateQI([nsIAboutModule]),
 
   newChannel: function (aURI, aLoadInfo) {
     let chan = Services.io.newChannelFromURIWithLoadInfo(this.uri, aLoadInfo);
     chan.owner = Services.scriptSecurityManager.getSystemPrincipal();
-
-    // Must set the result principal URI _after_ we've created the channel
-    // since the chrome protocol would overwrite it with a chrome:// URL.
-    aLoadInfo.resultPrincipalURI = aURI;
     return chan;
   },
 
   getURIFlags: function (aURI) {
     return nsIAboutModule.ALLOW_SCRIPT || nsIAboutModule.ENABLE_INDEXED_DB;
   }
 };
 
--- a/devtools/client/inspector/rules/test/browser.ini
+++ b/devtools/client/inspector/rules/test/browser.ini
@@ -101,16 +101,17 @@ skip-if = (os == 'linux' && bits == 32 &
 [browser_rules_cssom.js]
 [browser_rules_cubicbezier-appears-on-swatch-click.js]
 [browser_rules_cubicbezier-commit-on-ENTER.js]
 [browser_rules_cubicbezier-revert-on-ESC.js]
 [browser_rules_custom.js]
 [browser_rules_cycle-angle.js]
 [browser_rules_cycle-color.js]
 [browser_rules_edit-display-grid-property.js]
+skip-if = (os == "linux") # Bug 1356214
 [browser_rules_edit-property-cancel.js]
 [browser_rules_edit-property-click.js]
 [browser_rules_edit-property-commit.js]
 [browser_rules_edit-property-computed.js]
 [browser_rules_edit-property-increments.js]
 [browser_rules_edit-property-order.js]
 [browser_rules_edit-property-remove_01.js]
 [browser_rules_edit-property-remove_02.js]
--- a/devtools/client/locales/en-US/storage.properties
+++ b/devtools/client/locales/en-US/storage.properties
@@ -82,17 +82,21 @@ storage.data.label=Data
 # LOCALIZATION NOTE (storage.parsedValue.label):
 # This is the heading displayed over the item parsed value in the sidebar
 storage.parsedValue.label=Parsed Value
 
 # LOCALIZATION NOTE (storage.popupMenu.deleteLabel):
 # Label of popup menu action to delete storage item.
 storage.popupMenu.deleteLabel=Delete “%S”
 
-# LOCALIZATION NOTE (storage.popupMenu.deleteAllLabel):
+# LOCALIZATION NOTE (storage.popupMenu.addItemLabel):
+# Label of popup menu action to add an item.
+storage.popupMenu.addItemLabel=Add Item
+
+# LOCALIZATION NOTE (storage.popupMenu.deleteAllFromLabel):
 # Label of popup menu action to delete all storage items.
 storage.popupMenu.deleteAllFromLabel=Delete All From “%S”
 
 # LOCALIZATION NOTE (storage.idb.deleteBlocked):
 # Warning notification when IndexedDB database could not be deleted immediately.
 storage.idb.deleteBlocked=Database “%S” will be deleted after all connections are closed.
 
 # LOCALIZATION NOTE (storage.idb.deleteError):
--- a/devtools/client/netmonitor/src/constants.js
+++ b/devtools/client/netmonitor/src/constants.js
@@ -164,16 +164,17 @@ const HEADER_FILTERS = HEADERS
 
 const FILTER_FLAGS = [
   ...HEADER_FILTERS,
   "set-cookie-domain",
   "set-cookie-name",
   "set-cookie-value",
   "mime-type",
   "larger-than",
+  "transferred-larger-than",
   "is",
   "has-response-header",
   "regexp",
 ];
 
 const REQUESTS_WATERFALL = {
   BACKGROUND_TICKS_MULTIPLE: 5, // ms
   BACKGROUND_TICKS_SCALES: 3,
--- a/devtools/client/netmonitor/src/utils/filter-text-utils.js
+++ b/devtools/client/netmonitor/src/utils/filter-text-utils.js
@@ -78,16 +78,17 @@ function parseFilters(query) {
 
 function processFlagFilter(type, value) {
   switch (type) {
     case "regexp":
       return value;
     case "size":
     case "transferred":
     case "larger-than":
+    case "transferred-larger-than":
       let multiplier = 1;
       if (value.endsWith("k")) {
         multiplier = 1024;
         value = value.substring(0, value.length - 1);
       } else if (value.endsWith("m")) {
         multiplier = 1024 * 1024;
         value = value.substring(0, value.length - 1);
       }
@@ -145,16 +146,23 @@ function isFlagFilterMatch(item, { type,
       }
       break;
     case "size":
       match = isSizeMatch(value, item.contentSize);
       break;
     case "larger-than":
       match = item.contentSize > value;
       break;
+    case "transferred-larger-than":
+      if (item.fromCache) {
+        match = false;
+      } else {
+        match = item.transferredSize > value;
+      }
+      break;
     case "mime-type":
       match = item.mimeType.includes(value);
       break;
     case "is":
       if (value === "from-cache" ||
           value === "cached") {
         match = item.fromCache || item.status === "304";
       } else if (value === "running") {
--- a/devtools/client/netmonitor/test/browser_net_filter-flags.js
+++ b/devtools/client/netmonitor/test/browser_net_filter-flags.js
@@ -215,16 +215,29 @@ add_task(function* () {
   testContents([0, 1, 0, 0, 0, 0, 0, 0, 0]);
 
   setFreetextFilter("set-cookie-domain:.foo.example.com");
   testContents([0, 1, 0, 0, 0, 0, 0, 0, 0]);
 
   setFreetextFilter("set-cookie-domain:.not-existing.example.com");
   testContents([0, 0, 0, 0, 0, 0, 0, 0, 0]);
 
+  // Test transferred-larger-than
+  setFreetextFilter("transferred-larger-than:-1");
+  testContents([1, 1, 1, 1, 1, 1, 1, 1, 1]);
+
+  setFreetextFilter("transferred-larger-than:0");
+  testContents([1, 1, 1, 1, 0, 0, 0, 0, 0]);
+
+  setFreetextFilter("transferred-larger-than:33");
+  testContents([0, 0, 1, 1, 0, 0, 0, 0, 0]);
+
+  setFreetextFilter("transferred-larger-than:34");
+  testContents([0, 0, 0, 0, 0, 0, 0, 0, 0]);
+
   // Test mixing flags
   setFreetextFilter("-mime-type:HtmL status-code:200");
   testContents([0, 0, 1, 1, 1, 1, 1, 1, 0]);
 
   yield teardown(monitor);
 
   function testContents(visibility) {
     const items = getSortedRequests(store.getState());
--- a/devtools/client/responsive.html/docs/browser-swap.md
+++ b/devtools/client/responsive.html/docs/browser-swap.md
@@ -49,17 +49,16 @@ involved:
       * `browser._remoteWebNavigationImpl.swapBrowser(browser)` (R)
       * `browser._remoteWebProgressManager.swapBrowser(browser)` (R)
       * `browser._remoteFinder.swapBrowser(browser)` (R)
       * Emit `EndSwapDocShells`
     * `gBrowser.mTabProgressListener`
     * `filter.addProgressListener`
     * `ourBrowser.webProgress.addProgressListener`
   * `gBrowser._endRemoveTab`
-    * `gBrowser.tabContainer._fillTrailingGap`
     * `gBrowser._blurTab`
     * `gBrowser._tabFilters.delete`
     * `gBrowser._tabListeners.delete`
     * `gBrowser._outerWindowIDBrowserMap.delete`
     * `browser.destroy`
     * `gBrowser.tabContainer.removeChild`
     * `gBrowser.tabContainer.adjustTabstrip`
     * `gBrowser.tabContainer._setPositionalAttributes`
--- a/devtools/client/shared/widgets/TableWidget.js
+++ b/devtools/client/shared/widgets/TableWidget.js
@@ -120,16 +120,18 @@ function TableWidget(node, options = {})
 
   this.document.addEventListener("keydown", this.onKeydown);
   this.document.addEventListener("mousedown", this.onMousedown);
 }
 
 TableWidget.prototype = {
 
   items: null,
+  editBookmark: null,
+  scrollIntoViewOnUpdate: null,
 
   /**
    * Getter for the headers context menu popup id.
    */
   get headersContextMenu() {
     if (this.menupopup) {
       return this.menupopup.id;
     }
@@ -1167,37 +1169,56 @@ Column.prototype = {
     }
     for (let id of itemsToHide) {
       this.cells[this.items[id]].hidden = true;
     }
     this.updateZebra();
   },
 
   /**
-   * Called when a row is updated.
+   * Called when a row is updated e.g. a cell is changed. This means that
+   * for a new row this method will be called once for each column. If a single
+   * cell is changed this method will be called just once.
    *
    * @param {string} event
    *        The event name of the event. i.e. EVENTS.ROW_UPDATED
    * @param {string} id
    *        The unique id of the object associated with the row.
    */
   onRowUpdated: function (event, id) {
     this._updateItems();
+
     if (this.highlightUpdated && this.items[id] != null) {
+      if (this.table.scrollIntoViewOnUpdate) {
+        let cell = this.cells[this.items[id]];
+
+        // When a new row is created this method is called once for each column
+        // as each cell is updated. We can only scroll to cells if they are
+        // visible. We check for visibility and once we find the first visible
+        // cell in a row we scroll it into view and reset the
+        // scrollIntoViewOnUpdate flag.
+        if (cell.label.clientHeight > 0) {
+          cell.scrollIntoView();
+
+          this.table.scrollIntoViewOnUpdate = null;
+        }
+      }
+
       if (this.table.editBookmark) {
         // A rows position in the table can change as the result of an edit. In
         // order to ensure that the correct row is highlighted after an edit we
         // save the uniqueId in editBookmark. Here we send the signal that the
         // row has been edited and that the row needs to be selected again.
         this.table.emit(EVENTS.ROW_SELECTED, this.table.editBookmark);
         this.table.editBookmark = null;
       }
 
       this.cells[this.items[id]].flash();
     }
+
     this.updateZebra();
   },
 
   destroy: function () {
     this.table.off(EVENTS.COLUMN_SORTED, this.onColumnSorted);
     this.table.off(EVENTS.HEADER_CONTEXT_MENU, this.toggleColumn);
     this.table.off(EVENTS.ROW_UPDATED, this.onRowUpdated);
     this.table.off(EVENTS.TABLE_FILTERED, this.onTableFiltered);
@@ -1602,16 +1623,20 @@ Cell.prototype = {
     this.label.addEventListener("animationend", onAnimEnd);
     this.label.classList.add("flash-out");
   },
 
   focus: function () {
     this.label.focus();
   },
 
+  scrollIntoView: function () {
+    this.label.scrollIntoView(false);
+  },
+
   destroy: function () {
     this.label.remove();
     this.label = null;
   }
 };
 
 /**
  * Simple widget to make nodes matching a CSS selector editable.
--- a/devtools/client/storage/storage.xul
+++ b/devtools/client/storage/storage.xul
@@ -25,16 +25,17 @@
 
   <popupset id="storagePopupSet">
     <menupopup id="storage-tree-popup">
       <menuitem id="storage-tree-popup-delete-all"
                 label="&storage.popupMenu.deleteAllLabel;"/>
       <menuitem id="storage-tree-popup-delete"/>
     </menupopup>
     <menupopup id="storage-table-popup">
+      <menuitem id="storage-table-popup-add"/>
       <menuitem id="storage-table-popup-delete"/>
       <menuitem id="storage-table-popup-delete-all-from"/>
       <menuitem id="storage-table-popup-delete-all"
                 label="&storage.popupMenu.deleteAllLabel;"/>
     </menupopup>
   </popupset>
 
   <box flex="1" class="devtools-responsive-container theme-body">
@@ -42,16 +43,18 @@
     <splitter class="devtools-side-splitter"/>
     <vbox flex="1">
       <hbox id="storage-toolbar" class="devtools-toolbar">
         <textbox id="storage-searchbox"
                  class="devtools-filterinput"
                  type="search"
                  timeout="200"
                  placeholder="&searchBox.placeholder;"/>
+        <button id="add-button"
+                class="devtools-button add-button"></button>
         <spacer flex="1"/>
         <button class="devtools-button sidebar-toggle" hidden="true"></button>
       </hbox>
       <vbox id="storage-table" class="theme-sidebar" flex="1"/>
     </vbox>
     <splitter class="devtools-side-splitter"/>
     <vbox id="storage-sidebar" class="devtools-sidebar-tabs" hidden="true">
       <vbox flex="1"/>
--- a/devtools/client/storage/test/browser.ini
+++ b/devtools/client/storage/test/browser.ini
@@ -24,16 +24,17 @@ support-files =
   !/devtools/client/framework/test/shared-head.js
 
 [browser_storage_basic.js]
 [browser_storage_basic_usercontextid.js]
 tags = usercontextid
 [browser_storage_basic_with_fragment.js]
 [browser_storage_cache_delete.js]
 [browser_storage_cache_error.js]
+[browser_storage_cookies_add.js]
 [browser_storage_cookies_delete_all.js]
 [browser_storage_cookies_domain.js]
 [browser_storage_cookies_domain_port.js]
 [browser_storage_cookies_edit.js]
 [browser_storage_cookies_edit_keyboard.js]
 [browser_storage_cookies_tab_navigation.js]
 [browser_storage_delete.js]
 [browser_storage_delete_all.js]
new file mode 100644
--- /dev/null
+++ b/devtools/client/storage/test/browser_storage_cookies_add.js
@@ -0,0 +1,20 @@
+/* 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/. */
+
+// Basic test to check the adding of cookies.
+
+"use strict";
+
+add_task(function* () {
+  yield openTabAndSetupStorage(MAIN_DOMAIN + "storage-cookies.html");
+  showAllColumns(true);
+
+  yield performAdd(["cookies", "http://test1.example.org"]);
+  yield performAdd(["cookies", "http://test1.example.org"]);
+  yield performAdd(["cookies", "http://test1.example.org"]);
+  yield performAdd(["cookies", "http://test1.example.org"]);
+  yield performAdd(["cookies", "http://test1.example.org"]);
+
+  yield finishTests();
+});
--- a/devtools/client/storage/test/browser_storage_indexeddb_delete_blocked.js
+++ b/devtools/client/storage/test/browser_storage_indexeddb_delete_blocked.js
@@ -13,18 +13,18 @@ add_task(function* () {
 
   info("test state before delete");
   yield checkState([
     [["indexedDB", "http://test1.example.org"], ["idb (default)"]]
   ]);
 
   info("do the delete");
   yield selectTreeItem(["indexedDB", "http://test1.example.org"]);
-  let actor = gUI.getCurrentActor();
-  let result = yield actor.removeDatabase("http://test1.example.org", "idb (default)");
+  let front = gUI.getCurrentFront();
+  let result = yield front.removeDatabase("http://test1.example.org", "idb (default)");
 
   ok(result.blocked, "removeDatabase attempt is blocked");
 
   info("test state after blocked delete");
   yield checkState([
     [["indexedDB", "http://test1.example.org"], ["idb (default)"]]
   ]);
 
@@ -42,17 +42,17 @@ add_task(function* () {
   info("test state after real delete");
   yield checkState([
     [["indexedDB", "http://test1.example.org"], []]
   ]);
 
   info("try to delete database from nonexistent host");
   let errorThrown = false;
   try {
-    result = yield actor.removeDatabase("http://test2.example.org", "idb (default)");
+    result = yield front.removeDatabase("http://test2.example.org", "idb (default)");
   } catch (ex) {
     errorThrown = true;
   }
 
   ok(errorThrown, "error was reported when trying to delete");
 
   yield finishTests();
 });
--- a/devtools/client/storage/test/head.js
+++ b/devtools/client/storage/test/head.js
@@ -958,8 +958,42 @@ function setPermission(url, permission) 
 
 function toggleSidebar() {
   gUI.sidebarToggleBtn.click();
 }
 
 function sidebarToggleVisible() {
   return !gUI.sidebarToggleBtn.hidden;
 }
+
+/**
+ * Add an item.
+ * @param  {Array} store
+ *         An array containing the path to the store to which we wish to add an
+ *         item.
+ */
+function* performAdd(store) {
+  let toolbar = gPanelWindow.document.getElementById("storage-toolbar");
+  let storeName = store.join(" > ");
+
+  yield selectTreeItem(store);
+
+  let menuAdd = toolbar.querySelector(
+    "#add-button");
+
+  if (menuAdd.hidden) {
+    is(menuAdd.hidden, false,
+       `performAdd called for ${storeName} but it is not supported`);
+    return;
+  }
+
+  let eventEdit = gUI.table.once("row-edit");
+  let eventWait = gUI.once("store-objects-updated");
+
+  menuAdd.click();
+
+  let rowId = yield eventEdit;
+  yield eventWait;
+
+  let value = getCellValue(rowId, "uniqueKey");
+
+  is(rowId, value, `Row '${rowId}' was successfully added.`);
+}
--- a/devtools/client/storage/ui.js
+++ b/devtools/client/storage/ui.js
@@ -155,21 +155,29 @@ function StorageUI(front, target, panelW
   this.onTreePopupShowing = this.onTreePopupShowing.bind(this);
   this._treePopup = this._panelDoc.getElementById("storage-tree-popup");
   this._treePopup.addEventListener("popupshowing", this.onTreePopupShowing);
 
   this.onTablePopupShowing = this.onTablePopupShowing.bind(this);
   this._tablePopup = this._panelDoc.getElementById("storage-table-popup");
   this._tablePopup.addEventListener("popupshowing", this.onTablePopupShowing);
 
+  this.onAddItem = this.onAddItem.bind(this);
   this.onRemoveItem = this.onRemoveItem.bind(this);
   this.onRemoveAllFrom = this.onRemoveAllFrom.bind(this);
   this.onRemoveAll = this.onRemoveAll.bind(this);
   this.onRemoveTreeItem = this.onRemoveTreeItem.bind(this);
 
+  this._addButton = this._panelDoc.getElementById("add-button");
+  this._addButton.addEventListener("command", this.onAddItem);
+
+  this._tablePopupAddItem = this._panelDoc.getElementById(
+    "storage-table-popup-add");
+  this._tablePopupAddItem.addEventListener("command", this.onAddItem);
+
   this._tablePopupDelete = this._panelDoc.getElementById(
     "storage-table-popup-delete");
   this._tablePopupDelete.addEventListener("command", this.onRemoveItem);
 
   this._tablePopupDeleteAllFrom = this._panelDoc.getElementById(
     "storage-table-popup-delete-all-from");
   this._tablePopupDeleteAllFrom.addEventListener("command",
     this.onRemoveAllFrom);
@@ -209,16 +217,18 @@ StorageUI.prototype = {
     this._panelDoc.removeEventListener("keypress", this.handleKeypress);
     this.searchBox.removeEventListener("input", this.filterItems);
     this.searchBox = null;
 
     this.sidebarToggleBtn.removeEventListener("click", this.onPaneToggleButtonClicked);
     this.sidebarToggleBtn = null;
 
     this._treePopup.removeEventListener("popupshowing", this.onTreePopupShowing);
+    this._addButton.removeEventListener("command", this.onAddItem);
+    this._tablePopupAddItem.removeEventListener("command", this.onAddItem);
     this._treePopupDeleteAll.removeEventListener("command", this.onRemoveAll);
     this._treePopupDelete.removeEventListener("command", this.onRemoveTreeItem);
 
     this._tablePopup.removeEventListener("popupshowing", this.onTablePopupShowing);
     this._tablePopupDelete.removeEventListener("command", this.onRemoveItem);
     this._tablePopupDeleteAllFrom.removeEventListener("command", this.onRemoveAllFrom);
     this._tablePopupDeleteAll.removeEventListener("command", this.onRemoveAll);
   },
@@ -263,17 +273,17 @@ StorageUI.prototype = {
   /**
    * Hide the object viewer sidebar
    */
   hideSidebar: function () {
     this.sidebar.hidden = true;
     this.updateSidebarToggleButton();
   },
 
-  getCurrentActor: function () {
+  getCurrentFront: function () {
     let type = this.table.datatype;
 
     return this.storageTypes[type];
   },
 
   /**
    *  Make column fields editable
    *
@@ -284,19 +294,19 @@ StorageUI.prototype = {
     if (editableFields && editableFields.length > 0) {
       this.table.makeFieldsEditable(editableFields);
     } else if (this.table._editableFieldsEngine) {
       this.table._editableFieldsEngine.destroy();
     }
   },
 
   editItem: function (eventType, data) {
-    let actor = this.getCurrentActor();
+    let front = this.getCurrentFront();
 
-    actor.editItem(data);
+    front.editItem(data);
   },
 
   /**
    * Removes the given item from the storage table. Reselects the next item in
    * the table and repopulates the sidebar with that item's data if the item
    * being removed was selected.
    */
   removeItemFromTable: function (name) {
@@ -540,39 +550,68 @@ StorageUI.prototype = {
     }
 
     try {
       if (reason === REASON.POPULATE) {
         let subType = null;
         // The indexedDB type could have sub-type data to fetch.
         // If having names specified, then it means
         // we are fetching details of specific database or of object store.
-        if (type == "indexedDB" && names) {
+        if (type === "indexedDB" && names) {
           let [ dbName, objectStoreName ] = JSON.parse(names[0]);
           if (dbName) {
             subType = "database";
           }
           if (objectStoreName) {
             subType = "object store";
           }
         }
+
+        this.actorSupportsAddItem = yield this._target.actorHasMethod(type, "addItem");
+        this.actorSupportsRemoveItem =
+          yield this._target.actorHasMethod(type, "removeItem");
+        this.actorSupportsRemoveAll =
+          yield this._target.actorHasMethod(type, "removeAll");
+
         yield this.resetColumns(type, host, subType);
       }
 
       let {data} = yield storageType.getStoreObjects(host, names, fetchOpts);
       if (data.length) {
         this.populateTable(data, reason);
       }
+      yield this.updateToolbar();
       this.emit("store-objects-updated");
     } catch (ex) {
       console.error(ex);
     }
   }),
 
   /**
+   * Updates the toolbar hiding and showing buttons as appropriate.
+   */
+  updateToolbar: Task.async(function* () {
+    let item = this.tree.selectedItem;
+    let howManyNodesIn = item ? item.length : 0;
+
+    // The first node is just a title e.g. "Cookies" so we need to be at least
+    // 2 nodes in to show the add button.
+    let canAdd = this.actorSupportsAddItem && howManyNodesIn > 1;
+
+    if (canAdd) {
+      this._addButton.hidden = false;
+      this._addButton.setAttribute("tooltiptext",
+        L10N.getFormatStr("storage.popupMenu.addItemLabel"));
+    } else {
+      this._addButton.hidden = true;
+      this._addButton.removeAttribute("tooltiptext");
+    }
+  }),
+
+  /**
    * Populates the storage tree which displays the list of storages present for
    * the page.
    *
    * @param {object} storageTypes
    *        List of storages and their corresponding hosts returned by the
    *        StorageFront.listStores call.
    */
   populateStorageTree: function (storageTypes) {
@@ -807,16 +846,21 @@ StorageUI.prototype = {
    *        the storage tree
    */
   onHostSelect: function (event, item) {
     this.table.clear();
     this.hideSidebar();
     this.searchBox.value = "";
 
     let [type, host] = item;
+    this.table.host = host;
+    this.table.datatype = type;
+
+    this.updateToolbar();
+
     let names = null;
     if (!host) {
       return;
     }
     if (item.length > 2) {
       names = [JSON.stringify(item.slice(2))];
     }
     this.fetchStorageObjects(type, host, names, REASON.POPULATE);
@@ -839,17 +883,17 @@ StorageUI.prototype = {
     this.table.host = host;
     this.table.datatype = type;
 
     let uniqueKey = null;
     let columns = {};
     let editableFields = [];
     let hiddenFields = [];
     let privateFields = [];
-    let fields = yield this.getCurrentActor().getFields(subtype);
+    let fields = yield this.getCurrentFront().getFields(subtype);
 
     fields.forEach(f => {
       if (!uniqueKey) {
         this.table.uniqueId = uniqueKey = f.name;
       }
 
       if (f.editable) {
         editableFields.push(f.name);
@@ -975,40 +1019,54 @@ StorageUI.prototype = {
     let names = null;
     if (item.length > 2) {
       names = [JSON.stringify(item.slice(2))];
     }
     this.fetchStorageObjects(type, host, names, REASON.NEXT_50_ITEMS);
   },
 
   /**
-   * Fires before a cell context menu with the "Delete" action is shown.
-   * If the currently selected storage object doesn't support removing items, prevent
-   * showing the menu.
+   * Fires before a cell context menu with the "Add" or "Delete" action is
+   * shown. If the currently selected storage object doesn't support adding or
+   * removing items, prevent showing the menu.
    */
   onTablePopupShowing: function (event) {
     let selectedItem = this.tree.selectedItem;
     let type = selectedItem[0];
-    let actor = this.getCurrentActor();
 
     // IndexedDB only supports removing items from object stores (level 4 of the tree)
-    if (!actor.removeItem || (type === "indexedDB" && selectedItem.length !== 4)) {
+    if ((!this.actorSupportsAddItem && !this.actorSupportsRemoveItem &&
+         type !== "cookies") ||
+        (type === "indexedDB" && selectedItem.length !== 4)) {
       event.preventDefault();
       return;
     }
 
     let rowId = this.table.contextMenuRowId;
     let data = this.table.items.get(rowId);
-    let name = data[this.table.uniqueId];
+
+    if (this.actorSupportsRemoveItem) {
+      let name = data[this.table.uniqueId];
+      let separatorRegex = new RegExp(SEPARATOR_GUID, "g");
+      let label = addEllipsis((name + "").replace(separatorRegex, "-"));
 
-    let separatorRegex = new RegExp(SEPARATOR_GUID, "g");
-    let label = addEllipsis((name + "").replace(separatorRegex, "-"));
+      this._tablePopupDelete.hidden = false;
+      this._tablePopupDelete.setAttribute("label",
+        L10N.getFormatStr("storage.popupMenu.deleteLabel", label));
+    } else {
+      this._tablePopupDelete.hidden = true;
+    }
 
-    this._tablePopupDelete.setAttribute("label",
-      L10N.getFormatStr("storage.popupMenu.deleteLabel", label));
+    if (this.actorSupportsAddItem) {
+      this._tablePopupAddItem.hidden = false;
+      this._tablePopupAddItem.setAttribute("label",
+        L10N.getFormatStr("storage.popupMenu.addItemLabel"));
+    } else {
+      this._tablePopupAddItem.hidden = true;
+    }
 
     if (type === "cookies") {
       let host = addEllipsis(data.host);
 
       this._tablePopupDeleteAllFrom.hidden = false;
       this._tablePopupDeleteAllFrom.setAttribute("label",
         L10N.getFormatStr("storage.popupMenu.deleteAllFromLabel", host));
     } else {
@@ -1017,23 +1075,22 @@ StorageUI.prototype = {
   },
 
   onTreePopupShowing: function (event) {
     let showMenu = false;
     let selectedItem = this.tree.selectedItem;
 
     if (selectedItem) {
       let type = selectedItem[0];
-      let actor = this.storageTypes[type];
 
       // The delete all (aka clear) action is displayed for IndexedDB object stores
       // (level 4 of tree), for Cache objects (level 3) and for the whole host (level 2)
       // for other storage types (cookies, localStorage, ...).
       let showDeleteAll = false;
-      if (actor.removeAll) {
+      if (this.actorSupportsRemoveAll) {
         let level;
         if (type == "indexedDB") {
           level = 4;
         } else if (type == "Cache") {
           level = 3;
         } else {
           level = 2;
         }
@@ -1061,70 +1118,82 @@ StorageUI.prototype = {
     }
 
     if (!showMenu) {
       event.preventDefault();
     }
   },
 
   /**
+   * Handles adding an item from the storage
+   */
+  onAddItem: function () {
+    let front = this.getCurrentFront();
+
+    // Prepare to scroll into view.
+    this.table.scrollIntoViewOnUpdate = true;
+    this.table.editBookmark = createGUID();
+    front.addItem(this.table.editBookmark);
+  },
+
+  /**
    * Handles removing an item from the storage
    */
   onRemoveItem: function () {
     let [, host, ...path] = this.tree.selectedItem;
-    let actor = this.getCurrentActor();
+    let front = this.getCurrentFront();
     let rowId = this.table.contextMenuRowId;
     let data = this.table.items.get(rowId);
     let name = data[this.table.uniqueId];
     if (path.length > 0) {
       name = JSON.stringify([...path, name]);
     }
-    actor.removeItem(host, name);
+    front.removeItem(host, name);
   },
 
   /**
    * Handles removing all items from the storage
    */
   onRemoveAll: function () {
     // Cannot use this.currentActor() if the handler is called from the
     // tree context menu: it returns correct value only after the table
     // data from server are successfully fetched (and that's async).
-    let [type, host, ...path] = this.tree.selectedItem;
-    let actor = this.storageTypes[type];
+    let [, host, ...path] = this.tree.selectedItem;
+    let front = this.getCurrentFront();
     let name = path.length > 0 ? JSON.stringify(path) : undefined;
-    actor.removeAll(host, name);
+    front.removeAll(host, name);
   },
 
   /**
    * Handles removing all cookies with exactly the same domain as the
    * cookie in the selected row.
    */
   onRemoveAllFrom: function () {
     let [, host] = this.tree.selectedItem;
-    let actor = this.getCurrentActor();
+    let front = this.getCurrentFront();
     let rowId = this.table.contextMenuRowId;
     let data = this.table.items.get(rowId);
 
-    actor.removeAll(host, data.host);
+    front.removeAll(host, data.host);
   },
 
   onRemoveTreeItem: function () {
     let [type, host, ...path] = this.tree.selectedItem;
 
     if (type == "indexedDB" && path.length == 1) {
       this.removeDatabase(host, path[0]);
     } else if (type == "Cache" && path.length == 1) {
       this.removeCache(host, path[0]);
     }
   },
 
   removeDatabase: function (host, dbName) {
-    let actor = this.storageTypes.indexedDB;
+    let front = this.getCurrentFront();
 
-    actor.removeDatabase(host, dbName).then(result => {
+    front.removeDatabase(host, dbName).then(result => {
       if (result.blocked) {
         let notificationBox = this._toolbox.getNotificationBox();
         notificationBox.appendNotification(
           L10N.getFormatStr("storage.idb.deleteBlocked", dbName),
           "storage-idb-delete-blocked",
           null,
           notificationBox.PRIORITY_WARNING_LOW);
       }
@@ -1134,13 +1203,22 @@ StorageUI.prototype = {
         L10N.getFormatStr("storage.idb.deleteError", dbName),
         "storage-idb-delete-error",
         null,
         notificationBox.PRIORITY_CRITICAL_LOW);
     });
   },
 
   removeCache: function (host, cacheName) {
-    let actor = this.storageTypes.Cache;
+    let front = this.getCurrentFront();
 
-    actor.removeItem(host, JSON.stringify([ cacheName ]));
+    front.removeItem(host, JSON.stringify([ cacheName ]));
   },
 };
+
+// Helper Functions
+
+function createGUID() {
+  return "{cccccccc-cccc-4ccc-yccc-cccccccccccc}".replace(/[cy]/g, c => {
+    let r = Math.random() * 16 | 0, v = c == "c" ? r : (r & 0x3 | 0x8);
+    return v.toString(16);
+  });
+}
--- a/devtools/client/themes/storage.css
+++ b/devtools/client/themes/storage.css
@@ -27,20 +27,26 @@
 
 /* Variables View Sidebar */
 
 #storage-sidebar {
   max-width: 500px;
   min-width: 250px;
 }
 
-#storage-toolbar .devtools-button {
-  min-width: unset;
+#storage-toolbar .add-button::before {
+  background-image: url("chrome://devtools/skin/images/add.svg");
+  -moz-user-focus: normal;
 }
 
+#storage-toolbar .devtools-button {
+  min-width: 0;
+}
+
+#storage-toolbar .devtools-button hbox,
 #storage-toolbar .sidebar-toggle[hidden] {
   display: none;
 }
 
 /* Responsive sidebar */
 @media (max-width: 700px) {
   #storage-tree,
   #storage-sidebar {
--- a/devtools/server/actors/storage.js
+++ b/devtools/server/actors/storage.js
@@ -663,16 +663,24 @@ StorageActors.createActor({
    */
   editItem: Task.async(function* (data) {
     let doc = this.storageActor.document;
     data.originAttributes = doc.nodePrincipal
                                .originAttributes;
     this.editCookie(data);
   }),
 
+  addItem: Task.async(function* (guid) {
+    let doc = this.storageActor.document;
+    let time = new Date().getTime();
+    let expiry = new Date(time + 3600 * 24 * 1000).toGMTString();
+
+    doc.cookie = `${guid}=value;expires=${expiry}`;
+  }),
+
   removeItem: Task.async(function* (host, name) {
     let doc = this.storageActor.document;
     this.removeCookie(host, name, doc.nodePrincipal
                                      .originAttributes);
   }),
 
   removeAll: Task.async(function* (host, domain) {
     let doc = this.storageActor.document;
@@ -990,16 +998,22 @@ var cookieHelpers = {
       }
       case "removeCookieObservers": {
         return cookieHelpers.removeCookieObservers();
       }
       case "editCookie": {
         let rowdata = msg.data.args[0];
         return cookieHelpers.editCookie(rowdata);
       }
+      case "createNewCookie": {
+        let host = msg.data.args[0];
+        let guid = msg.data.args[1];
+        let originAttributes = msg.data.args[2];
+        return cookieHelpers.createNewCookie(host, guid, originAttributes);
+      }
       case "removeCookie": {
         let host = msg.data.args[0];
         let name = msg.data.args[1];
         let originAttributes = msg.data.args[2];
         return cookieHelpers.removeCookie(host, name, originAttributes);
       }
       case "removeAllCookies": {
         let host = msg.data.args[0];
--- a/devtools/shared/specs/storage.js
+++ b/devtools/shared/specs/storage.js
@@ -85,16 +85,23 @@ const editRemoveMethods = {
 
 // Cookies actor spec
 createStorageSpec({
   typeName: "cookies",
   storeObjectType: "cookiestoreobject",
   methods: Object.assign({},
     editRemoveMethods,
     {
+      addItem: {
+        request: {
+          guid: Arg(0, "string"),
+        },
+        response: {}
+      }
+    }, {
       removeAll: {
         request: {
           host: Arg(0, "string"),
           domain: Arg(1, "nullable:string")
         },
         response: {}
       }
     }
--- a/docshell/base/nsAboutRedirector.cpp
+++ b/docshell/base/nsAboutRedirector.cpp
@@ -169,35 +169,39 @@ nsAboutRedirector::NewChannel(nsIURI* aU
 
   for (int i = 0; i < kRedirTotal; i++) {
     if (!strcmp(path.get(), kRedirMap[i].id)) {
       nsCOMPtr<nsIChannel> tempChannel;
       nsCOMPtr<nsIURI> tempURI;
       rv = NS_NewURI(getter_AddRefs(tempURI), kRedirMap[i].url);
       NS_ENSURE_SUCCESS(rv, rv);
 
-      // If tempURI links to an internal URI (chrome://, resource://, about:)
-      // then set the result principal URL on the channel's load info.
-      // Otherwise, we leave it null which forces the channel principal
-      // to reflect the displayed URL rather than being the systemPrincipal.
+      // If tempURI links to an external URI (i.e. something other than
+      // chrome:// or resource://) then set the LOAD_REPLACE flag on the
+      // channel which forces the channel owner to reflect the displayed
+      // URL rather then being the systemPrincipal.
       bool isUIResource = false;
       rv = NS_URIChainHasFlags(tempURI, nsIProtocolHandler::URI_IS_UI_RESOURCE,
                                &isUIResource);
       NS_ENSURE_SUCCESS(rv, rv);
 
       bool isAboutBlank = NS_IsAboutBlank(tempURI);
 
+      nsLoadFlags loadFlags = isUIResource || isAboutBlank
+                    ? static_cast<nsLoadFlags>(nsIChannel::LOAD_NORMAL)
+                    : static_cast<nsLoadFlags>(nsIChannel::LOAD_REPLACE);
+
       rv = NS_NewChannelInternal(getter_AddRefs(tempChannel),
                                  tempURI,
-                                 aLoadInfo);
+                                 aLoadInfo,
+                                 nullptr, // aLoadGroup
+                                 nullptr, // aCallbacks
+                                 loadFlags);
       NS_ENSURE_SUCCESS(rv, rv);
 
-      if (isUIResource || isAboutBlank) {
-        aLoadInfo->SetResultPrincipalURI(aURI);
-      }
       tempChannel->SetOriginalURI(aURI);
 
       tempChannel.forget(aResult);
       return rv;
     }
   }
 
   NS_ERROR("nsAboutRedirector called for unknown case");
--- a/docshell/base/nsDocShell.cpp
+++ b/docshell/base/nsDocShell.cpp
@@ -11177,22 +11177,16 @@ nsDocShell::DoURILoad(nsIURI* aURI,
   // Make sure to give the caller a channel if we managed to create one
   // This is important for correct error page/session history interaction
   if (aRequest) {
     NS_ADDREF(*aRequest = channel);
   }
 
   if (aOriginalURI) {
     channel->SetOriginalURI(aOriginalURI);
-    // The LOAD_REPLACE flag and its handling here will be removed as part
-    // of bug 1319110.  For now preserve its restoration here to not break
-    // any code expecting it being set specially on redirected channels.
-    // If the flag has originally been set to change result of
-    // NS_GetFinalChannelURI it won't have any effect and also won't cause
-    // any harm.
     if (aLoadReplace) {
       uint32_t loadFlags;
       channel->GetLoadFlags(&loadFlags);
       NS_ENSURE_SUCCESS(rv, rv);
       channel->SetLoadFlags(loadFlags | nsIChannel::LOAD_REPLACE);
     }
   } else {
     channel->SetOriginalURI(aURI);
--- a/dom/animation/ComputedTimingFunction.h
+++ b/dom/animation/ComputedTimingFunction.h
@@ -75,16 +75,30 @@ public:
            (HasSpline() ?
             mTimingFunction == aOther.mTimingFunction :
             mStepsOrFrames == aOther.mStepsOrFrames);
   }
   bool operator!=(const ComputedTimingFunction& aOther) const
   {
     return !(*this == aOther);
   }
+  bool operator==(const nsTimingFunction& aOther) const
+  {
+    return mType == aOther.mType &&
+           (HasSpline()
+            ? mTimingFunction.X1() == aOther.mFunc.mX1 &&
+              mTimingFunction.Y1() == aOther.mFunc.mY1 &&
+              mTimingFunction.X2() == aOther.mFunc.mX2 &&
+              mTimingFunction.Y2() == aOther.mFunc.mY2
+            : mStepsOrFrames == aOther.mStepsOrFrames);
+  }
+  bool operator!=(const nsTimingFunction& aOther) const
+  {
+    return !(*this == aOther);
+  }
   int32_t Compare(const ComputedTimingFunction& aRhs) const;
   void AppendToString(nsAString& aResult) const;
 
   static double GetPortion(const Maybe<ComputedTimingFunction>& aFunction,
                            double aPortion,
                            BeforeFlag aBeforeFlag)
   {
     return aFunction ? aFunction->GetValue(aPortion, aBeforeFlag) : aPortion;
@@ -100,11 +114,28 @@ private:
     : mType(aType)
     , mStepsOrFrames(aStepsOrFrames) { }
 
   nsTimingFunction::Type mType = nsTimingFunction::Type::Linear;
   nsSMILKeySpline mTimingFunction;
   uint32_t mStepsOrFrames;
 };
 
+inline bool
+operator==(const Maybe<ComputedTimingFunction>& aLHS,
+           const nsTimingFunction& aRHS)
+{
+  if (aLHS.isNothing()) {
+    return aRHS.mType == nsTimingFunction::Type::Linear;
+  }
+  return aLHS.value() == aRHS;
+}
+
+inline bool
+operator!=(const Maybe<ComputedTimingFunction>& aLHS,
+           const nsTimingFunction& aRHS)
+{
+  return !(aLHS == aRHS);
+}
+
 } // namespace mozilla
 
 #endif // mozilla_ComputedTimingFunction_h
--- a/dom/animation/KeyframeUtils.cpp
+++ b/dom/animation/KeyframeUtils.cpp
@@ -740,17 +740,17 @@ KeyframeUtils::ParseProperty(nsCSSProper
   NS_ConvertUTF16toUTF8 value(aValue);
   // FIXME this is using the wrong base uri (bug 1343919)
   RefPtr<URLExtraData> data = new URLExtraData(aDocument->GetDocumentURI(),
                                                aDocument->GetDocumentURI(),
                                                aDocument->NodePrincipal());
   return Servo_ParseProperty(aProperty,
                              &value,
                              data,
-                             LengthParsingMode::Default).Consume();
+                             ParsingMode::Default).Consume();
 }
 
 // ------------------------------------------------------------------
 //
 // Internal helpers
 //
 // ------------------------------------------------------------------
 
--- a/dom/base/nsAttrValue.cpp
+++ b/dom/base/nsAttrValue.cpp
@@ -1717,17 +1717,18 @@ nsAttrValue::ParseStyleAttribute(const n
       return true;
     }
   }
 
   RefPtr<DeclarationBlock> decl;
   if (ownerDoc->GetStyleBackendType() == StyleBackendType::Servo) {
     RefPtr<URLExtraData> data = new URLExtraData(baseURI, docURI,
                                                  aElement->NodePrincipal());
-    decl = ServoDeclarationBlock::FromCssText(aString, data);
+    decl = ServoDeclarationBlock::FromCssText(aString, data,
+                                              ownerDoc->GetCompatibilityMode());
   } else {
     css::Loader* cssLoader = ownerDoc->CSSLoader();
     nsCSSParser cssParser(cssLoader);
     decl = cssParser.ParseStyleAttribute(aString, docURI, baseURI,
                                          aElement->NodePrincipal());
   }
   if (!decl) {
     return false;
--- a/dom/base/nsContentUtils.cpp
+++ b/dom/base/nsContentUtils.cpp
@@ -10506,20 +10506,23 @@ void
 nsContentUtils::UserInteractionObserver::Init()
 {
   // Listen for the observer messages from EventStateManager which are telling
   // us whether or not the user is interacting.
   nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
   obs->AddObserver(this, kUserInteractionInactive, false);
   obs->AddObserver(this, kUserInteractionActive, false);
 
-  // Register ourselves as an annotator for the Background Hang Reporter, so
-  // that hang stacks are annotated with whether or not the user was
-  // interacting with the browser when the hang occurred.
-  HangMonitor::RegisterAnnotator(*this);
+  // We can't register ourselves as an annotator yet, as the HangMonitor hasn't
+  // started yet. It will have started by the time we have the chance to spin
+  // the event loop.
+  RefPtr<UserInteractionObserver> self = this;
+  NS_DispatchToMainThread(NS_NewRunnableFunction([=] () {
+        HangMonitor::RegisterAnnotator(*self);
+      }));
 }
 
 void
 nsContentUtils::UserInteractionObserver::Shutdown()
 {
   nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
   if (obs) {
     obs->RemoveObserver(this, kUserInteractionInactive);
--- a/dom/base/nsFrameMessageManager.cpp
+++ b/dom/base/nsFrameMessageManager.cpp
@@ -1587,19 +1587,17 @@ nsMessageManagerScriptExecutor::TryCache
   // to avoid keeping the current compartment alive.
   AutoJSAPI jsapi;
   if (!jsapi.Init(xpc::CompilationScope())) {
     return;
   }
   JSContext* cx = jsapi.cx();
   JS::Rooted<JSScript*> script(cx);
 
-  if (XRE_IsParentProcess()) {
-    script = ScriptPreloader::GetSingleton().GetCachedScript(cx, url);
-  }
+  script = ScriptPreloader::GetChildSingleton().GetCachedScript(cx, url);
 
   if (!script) {
     nsCOMPtr<nsIChannel> channel;
     NS_NewChannel(getter_AddRefs(channel),
                   uri,
                   nsContentUtils::GetSystemPrincipal(),
                   nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_DATA_IS_NULL,
                   nsIContentPolicy::TYPE_OTHER);
@@ -1652,19 +1650,17 @@ nsMessageManagerScriptExecutor::TryCache
 
   MOZ_ASSERT(script);
   aScriptp.set(script);
 
   nsAutoCString scheme;
   uri->GetScheme(scheme);
   // We don't cache data: scripts!
   if (aShouldCache && !scheme.EqualsLiteral("data")) {
-    if (XRE_IsParentProcess()) {
-      ScriptPreloader::GetSingleton().NoteScript(url, url, script);
-    }
+    ScriptPreloader::GetChildSingleton().NoteScript(url, url, script);
     // Root the object also for caching.
     auto* holder = new nsMessageManagerScriptHolder(cx, script, aRunInGlobalScope);
     sCachedScripts->Put(aURL, holder);
   }
 }
 
 void
 nsMessageManagerScriptExecutor::TryCacheLoadAndCompileScript(
--- a/dom/base/nsISelectionPrivate.idl
+++ b/dom/base/nsISelectionPrivate.idl
@@ -156,10 +156,40 @@ interface nsISelectionPrivate : nsISelec
                                            in ScrollAxis aHorizontal);
 
     /**
      * Modifies the cursor Bidi level after a change in keyboard direction
      * @param langRTL is PR_TRUE if the new language is right-to-left or
      *                PR_FALSE if the new language is left-to-right.
      */
     [noscript] void selectionLanguageChange(in boolean langRTL);
+
+    /**
+     * setColors() sets custom colors for the selection.
+     * Currently, this is supported only when the selection type is SELECTION_FIND.
+     * Otherwise, throws an exception.
+     *
+     * @param aForegroundColor     The foreground color of the selection.
+     *                             If this is "currentColor", foreground color
+     *                             isn't changed by this selection.
+     * @param aBackgroundColor     The background color of the selection.
+     *                             If this is "transparent", background color is
+     *                             never painted.
+     * @param aAltForegroundColor  The alternative foreground color of the
+     *                             selection.
+     *                             If aBackgroundColor doesn't have sufficient
+     *                             contrast with its around or foreground color
+     *                             if "currentColor" is specified, alternative
+     *                             colors are used if it have higher contrast.
+     * @param aAltBackgroundColor  The alternative background color of the
+     *                             selection.
+     */
+    void setColors(in DOMString aForegroundColor,
+                   in DOMString aBackgroundColor,
+                   in DOMString aAltForegroundColor,
+                   in DOMString aAltBackgroundColor);
+
+    /**
+     * resetColors() forget the customized colors which were set by setColors().
+     */
+    void resetColors();
 };
 
--- a/dom/bindings/Codegen.py
+++ b/dom/bindings/Codegen.py
@@ -7793,16 +7793,25 @@ class CGPerSignatureCall(CGThing):
             wrapCode += wrapForType(self.returnType, self.descriptor, resultTemplateValues)
         except MethodNotNewObjectError, err:
             assert not returnsNewObject
             raise TypeError("%s being returned from non-NewObject method or property %s.%s" %
                             (err.typename,
                              self.descriptor.interface.identifier.name,
                              self.idlNode.identifier.name))
         if setSlot:
+            if self.idlNode.isStatic():
+                raise TypeError(
+                    "Attribute %s.%s is static, so we don't have a useful slot "
+                    "to cache it in, because we don't have support for that on "
+                    "interface objects.  See "
+                    "https://bugzilla.mozilla.org/show_bug.cgi?id=1363870" %
+                    (self.descriptor.interface.identifier.name,
+                     self.idlNode.identifier.name))
+
             # When using a slot on the Xray expando, we need to make sure that
             # our initial conversion to a JS::Value is done in the caller
             # compartment.  When using a slot on our reflector, we want to do
             # the conversion in the compartment of that reflector (that is,
             # slotStorage).  In both cases we want to make sure that we finally
             # set up args.rval() to be in the caller compartment.  We also need
             # to make sure that the conversion steps happen inside a do/while
             # that they can break out of on success.
--- a/dom/canvas/CanvasRenderingContext2D.cpp
+++ b/dom/canvas/CanvasRenderingContext2D.cpp
@@ -2806,33 +2806,33 @@ CreateDeclarationForServo(nsCSSPropertyI
                      aDocument->NodePrincipal());
 
   NS_ConvertUTF16toUTF8 value(aPropertyValue);
 
   RefPtr<RawServoDeclarationBlock> servoDeclarations =
     Servo_ParseProperty(aProperty,
                         &value,
                         data,
-                        LengthParsingMode::Default).Consume();
+                        ParsingMode::Default).Consume();
 
   if (!servoDeclarations) {
     // We got a syntax error.  The spec says this value must be ignored.
     return nullptr;
   }
 
   // From canvas spec, force to set line-height property to 'normal' font
   // property.
   if (aProperty == eCSSProperty_font) {
     const nsCString normalString = NS_LITERAL_CSTRING("normal");
     Servo_DeclarationBlock_SetPropertyById(servoDeclarations,
                                            eCSSProperty_line_height,
                                            &normalString,
                                            false,
                                            data,
-                                           LengthParsingMode::Default);
+                                           ParsingMode::Default);
   }
 
   return servoDeclarations.forget();
 }
 
 static already_AddRefed<RawServoDeclarationBlock>
 CreateFontDeclarationForServo(const nsAString& aFont,
                               nsIDocument* aDocument)
--- a/dom/file/nsHostObjectProtocolHandler.cpp
+++ b/dom/file/nsHostObjectProtocolHandler.cpp
@@ -878,18 +878,16 @@ nsHostObjectProtocolHandler::NewChannel2
   }
 
   uint64_t size = blobImpl->GetSize(rv);
   if (NS_WARN_IF(rv.Failed())) {
     return rv.StealNSResult();
   }
 
   channel->SetOriginalURI(uri);
-  aLoadInfo->SetResultPrincipalURI(uri);
-
   channel->SetContentType(NS_ConvertUTF16toUTF8(contentType));
   channel->SetContentLength(size);
 
   channel.forget(result);
 
   return NS_OK;
 }
 
--- a/dom/flyweb/FlyWebService.cpp
+++ b/dom/flyweb/FlyWebService.cpp
@@ -1101,17 +1101,24 @@ FlyWebService::Observe(nsISupports* aSub
 
   nsCOMPtr<nsISupportsPRUint64> wrapper = do_QueryInterface(aSubject);
   NS_ENSURE_TRUE(wrapper, NS_ERROR_FAILURE);
 
   uint64_t innerID;
   nsresult rv = wrapper->GetData(&innerID);
   NS_ENSURE_SUCCESS(rv, rv);
 
+  // Make a copy of mServers to iterate over, because closing a server
+  // can remove entries from mServers.
+  nsCOMArray<FlyWebPublishedServer> serversCopy;
   for (FlyWebPublishedServer* server : mServers) {
+    serversCopy.AppendElement(server);
+  }
+
+  for (FlyWebPublishedServer* server : serversCopy) {
     if (server->OwnerWindowID() == innerID) {
       server->Close();
     }
   }
 
   return NS_OK;
 }
 
--- a/dom/gamepad/android/AndroidGamepad.cpp
+++ b/dom/gamepad/android/AndroidGamepad.cpp
@@ -60,17 +60,19 @@ public:
       return;
     }
 
     const auto& valid = aValid->GetElements();
     const auto& values = aValues->GetElements();
     MOZ_ASSERT(valid.Length() == values.Length());
 
     for (size_t i = 0; i < values.Length(); i++) {
-      service->NewAxisMoveEvent(aID, i, values[i]);
+      if (valid[i]) {
+        service->NewAxisMoveEvent(aID, i, values[i]);
+      }
     }
   }
 };
 
 void StartGamepadMonitoring()
 {
   AndroidGamepadManager::Init();
   java::AndroidGamepadManager::Start();
--- a/dom/html/HTMLFormElement.cpp
+++ b/dom/html/HTMLFormElement.cpp
@@ -6,17 +6,16 @@
 
 #include "mozilla/dom/HTMLFormElement.h"
 
 #include "jsapi.h"
 #include "mozilla/ContentEvents.h"
 #include "mozilla/EventDispatcher.h"
 #include "mozilla/EventStateManager.h"
 #include "mozilla/EventStates.h"
-#include "mozilla/dom/AutocompleteErrorEvent.h"
 #include "mozilla/dom/nsCSPUtils.h"
 #include "mozilla/dom/nsCSPContext.h"
 #include "mozilla/dom/nsMixedContentBlocker.h"
 #include "mozilla/dom/HTMLFormControlsCollection.h"
 #include "mozilla/dom/HTMLFormElementBinding.h"
 #include "mozilla/Move.h"
 #include "nsIHTMLDocument.h"
 #include "nsGkAtoms.h"
@@ -27,17 +26,16 @@
 #include "nsError.h"
 #include "nsContentUtils.h"
 #include "nsInterfaceHashtable.h"
 #include "nsContentList.h"
 #include "nsCOMArray.h"
 #include "nsAutoPtr.h"
 #include "nsTArray.h"
 #include "nsIMutableArray.h"
-#include "nsIFormAutofillContentService.h"
 #include "mozilla/BinarySearch.h"
 #include "nsQueryObject.h"
 
 // form submission
 #include "HTMLFormSubmissionConstants.h"
 #include "mozilla/dom/FormData.h"
 #include "mozilla/Telemetry.h"
 #include "nsIFormSubmitObserver.h"
@@ -281,41 +279,16 @@ HTMLFormElement::Reset()
 
 NS_IMETHODIMP
 HTMLFormElement::CheckValidity(bool* retVal)
 {
   *retVal = CheckValidity();
   return NS_OK;
 }
 
-void
-HTMLFormElement::RequestAutocomplete()
-{
-  bool dummy;
-  nsCOMPtr<nsIDOMWindow> window =
-    do_QueryInterface(OwnerDoc()->GetScriptHandlingObject(dummy));
-  nsCOMPtr<nsIFormAutofillContentService> formAutofillContentService =
-    do_GetService("@mozilla.org/formautofill/content-service;1");
-
-  if (!formAutofillContentService || !window) {
-    AutocompleteErrorEventInit init;
-    init.mBubbles = true;
-    init.mCancelable = false;
-    init.mReason = AutoCompleteErrorReason::Disabled;
-
-    RefPtr<AutocompleteErrorEvent> event =
-      AutocompleteErrorEvent::Constructor(this, NS_LITERAL_STRING("autocompleteerror"), init);
-
-    (new AsyncEventDispatcher(this, event))->PostDOMEvent();
-    return;
-  }
-
-  formAutofillContentService->RequestAutocomplete(this, window);
-}
-
 bool
 HTMLFormElement::ParseAttribute(int32_t aNamespaceID,
                                 nsIAtom* aAttribute,
                                 const nsAString& aValue,
                                 nsAttrValue& aResult)
 {
   if (aNamespaceID == kNameSpaceID_None) {
     if (aAttribute == nsGkAtoms::method) {
--- a/dom/html/HTMLFormElement.h
+++ b/dom/html/HTMLFormElement.h
@@ -411,18 +411,16 @@ public:
 #ifdef DEBUG
   static void
   AssertDocumentOrder(const nsTArray<nsGenericHTMLFormElement*>& aControls,
                       nsIContent* aForm);
 #endif
 
   js::ExpandoAndGeneration mExpandoAndGeneration;
 
-  void RequestAutocomplete();
-
 protected:
   virtual JSObject* WrapNode(JSContext* aCx, JS::Handle<JSObject*> aGivenProto) override;
 
   void PostPasswordEvent();
 
   RefPtr<AsyncEventDispatcher> mFormPasswordEventDispatcher;
 
   class RemoveElementRunnable;
--- a/dom/html/nsHTMLDocument.cpp
+++ b/dom/html/nsHTMLDocument.cpp
@@ -2407,17 +2407,16 @@ nsHTMLDocument::CreateAndAddWyciwygChann
     NS_ENSURE_SUCCESS(rv, rv);
 
     nsLoadFlags loadFlags = 0;
     channel->GetLoadFlags(&loadFlags);
     loadFlags |= nsIChannel::LOAD_DOCUMENT_URI;
     channel->SetLoadFlags(loadFlags);
 
     channel->SetOriginalURI(wcwgURI);
-    loadInfo->SetResultPrincipalURI(wcwgURI);
 
     rv = loadGroup->AddRequest(mWyciwygChannel, nullptr);
     NS_ASSERTION(NS_SUCCEEDED(rv), "Failed to add request to load group.");
   }
 
   return rv;
 }
 
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -54,16 +54,17 @@
 #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/loader/ScriptCacheActors.h"
 #include "mozilla/net/NeckoChild.h"
 #include "mozilla/net/CaptivePortalService.h"
 #include "mozilla/Omnijar.h"
 #include "mozilla/plugins/PluginInstanceParent.h"
 #include "mozilla/plugins/PluginModuleParent.h"
 #include "mozilla/widget/ScreenManager.h"
 #include "mozilla/widget/WidgetMessageUtils.h"
 #include "nsBaseDragService.h"
@@ -230,16 +231,17 @@ 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;
 #endif
 using namespace mozilla::widget;
+using mozilla::loader::PScriptCacheChild;
 
 namespace mozilla {
 
 namespace dom {
 
 // IPC sender for remote GC/CC logging.
 class CycleCollectWithLogsChild final
   : public PCycleCollectWithLogsChild
@@ -1815,16 +1817,41 @@ ContentChild::GetCPOWManager()
 }
 
 mozilla::ipc::IPCResult
 ContentChild::RecvPTestShellConstructor(PTestShellChild* actor)
 {
   return IPC_OK();
 }
 
+PScriptCacheChild*
+ContentChild::AllocPScriptCacheChild(const FileDescOrError& cacheFile, const bool& wantCacheData)
+{
+  return new loader::ScriptCacheChild();
+}
+
+bool
+ContentChild::DeallocPScriptCacheChild(PScriptCacheChild* cache)
+{
+  delete static_cast<loader::ScriptCacheChild*>(cache);
+  return true;
+}
+
+mozilla::ipc::IPCResult
+ContentChild::RecvPScriptCacheConstructor(PScriptCacheChild* actor, const FileDescOrError& cacheFile, const bool& wantCacheData)
+{
+  Maybe<FileDescriptor> fd;
+  if (cacheFile.type() == cacheFile.TFileDescriptor) {
+    fd.emplace(cacheFile.get_FileDescriptor());
+  }
+
+  static_cast<loader::ScriptCacheChild*>(actor)->Init(fd, wantCacheData);
+  return IPC_OK();
+}
+
 PNeckoChild*
 ContentChild::AllocPNeckoChild()
 {
   return new NeckoChild();
 }
 
 bool
 ContentChild::DeallocPNeckoChild(PNeckoChild* necko)
--- a/dom/ipc/ContentChild.h
+++ b/dom/ipc/ContentChild.h
@@ -31,16 +31,18 @@ struct SubstitutionMapping;
 struct OverrideMapping;
 class nsIDomainPolicy;
 class nsIURIClassifierCallback;
 struct LookAndFeelInt;
 
 namespace mozilla {
 class RemoteSpellcheckEngineChild;
 
+using mozilla::loader::PScriptCacheChild;
+
 namespace ipc {
 class OptionalURIParams;
 class URIParams;
 }// namespace ipc
 
 namespace dom {
 
 class AlertObserver;
@@ -236,16 +238,27 @@ public:
   DeallocPWebBrowserPersistDocumentChild(PWebBrowserPersistDocumentChild* aActor) override;
 
   virtual PTestShellChild* AllocPTestShellChild() override;
 
   virtual bool DeallocPTestShellChild(PTestShellChild*) override;
 
   virtual mozilla::ipc::IPCResult RecvPTestShellConstructor(PTestShellChild*) override;
 
+  virtual PScriptCacheChild*
+  AllocPScriptCacheChild(const FileDescOrError& cacheFile,
+                         const bool& wantCacheData) override;
+
+  virtual bool DeallocPScriptCacheChild(PScriptCacheChild*) override;
+
+  virtual mozilla::ipc::IPCResult
+  RecvPScriptCacheConstructor(PScriptCacheChild*,
+                              const FileDescOrError& cacheFile,
+                              const bool& wantCacheData) override;
+
   jsipc::CPOWManager* GetCPOWManager() override;
 
   virtual PNeckoChild* AllocPNeckoChild() override;
 
   virtual bool DeallocPNeckoChild(PNeckoChild*) override;
 
   virtual PPrintingChild* AllocPPrintingChild() override;
 
--- a/dom/ipc/ContentParent.cpp
+++ b/dom/ipc/ContentParent.cpp
@@ -80,25 +80,27 @@
 #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/loader/ScriptCacheActors.h"
 #include "mozilla/LookAndFeel.h"
 #include "mozilla/media/MediaParent.h"
 #include "mozilla/Move.h"
 #include "mozilla/net/NeckoParent.h"
 #include "mozilla/plugins/PluginBridge.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/ProcessHangMonitor.h"
 #include "mozilla/ProcessHangMonitorIPC.h"
 #include "mozilla/ScopeExit.h"
+#include "mozilla/ScriptPreloader.h"
 #include "mozilla/Services.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/TelemetryIPC.h"
 #include "mozilla/WebBrowserPersistDocumentParent.h"
 #include "mozilla/widget/ScreenManager.h"
 #include "mozilla/Unused.h"
 #include "nsAnonymousTemporaryFile.h"
@@ -279,16 +281,17 @@ 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;
+using mozilla::loader::PScriptCacheParent;
 
 // XXX Workaround for bug 986973 to maintain the existing broken semantics
 template<>
 struct nsIConsoleService::COMTypeInfo<nsConsoleService, void> {
   static const nsIID kIID;
 };
 const nsIID nsIConsoleService::COMTypeInfo<nsConsoleService, void>::kIID = NS_ICONSOLESERVICE_IID;
 
@@ -2260,25 +2263,27 @@ ContentParent::InitInternal(ProcessPrior
     nsCString UAName(gAppData->UAName);
     nsCString ID(gAppData->ID);
     nsCString vendor(gAppData->vendor);
 
     // Sending all information to content process.
     Unused << SendAppInfo(version, buildID, name, UAName, ID, vendor);
   }
 
-  // Initialize the message manager (and load delayed scripts) now that we
-  // have established communications with the child.
-  mMessageManager->InitWithCallback(this);
-
   // Send the child its remote type. On Mac, this needs to be sent prior
   // to the message we send to enable the Sandbox (SendStartProcessSandbox)
   // because different remote types require different sandbox privileges.
   Unused << SendRemoteType(mRemoteType);
 
+  ScriptPreloader::InitContentChild(*this);
+
+  // Initialize the message manager (and load delayed scripts) now that we
+  // have established communications with the child.
+  mMessageManager->InitWithCallback(this);
+
   // Set the subprocess's priority.  We do this early on because we're likely
   // /lowering/ the process's CPU and memory priority, which it has inherited
   // from this process.
   //
   // This call can cause us to send IPC messages to the child process, so it
   // must come after the Open() call above.
   ProcessPriorityManager::SetProcessPriority(this, aInitialPriority);
 
@@ -3116,16 +3121,29 @@ ContentParent::AllocPTestShellParent()
 
 bool
 ContentParent::DeallocPTestShellParent(PTestShellParent* shell)
 {
   delete shell;
   return true;
 }
 
+PScriptCacheParent*
+ContentParent::AllocPScriptCacheParent(const FileDescOrError& cacheFile, const bool& wantCacheData)
+{
+  return new loader::ScriptCacheParent(wantCacheData);
+}
+
+bool
+ContentParent::DeallocPScriptCacheParent(PScriptCacheParent* cache)
+{
+  delete static_cast<loader::ScriptCacheParent*>(cache);
+  return true;
+}
+
 PNeckoParent*
 ContentParent::AllocPNeckoParent()
 {
   return new NeckoParent();
 }
 
 bool
 ContentParent::DeallocPNeckoParent(PNeckoParent* necko)
--- a/dom/ipc/ContentParent.h
+++ b/dom/ipc/ContentParent.h
@@ -62,16 +62,18 @@ class CrossProcessProfilerController;
 
 #if defined(XP_LINUX) && defined(MOZ_CONTENT_SANDBOX)
 class SandboxBroker;
 class SandboxBrokerPolicyFactory;
 #endif
 
 class PreallocatedProcessManagerImpl;
 
+using mozilla::loader::PScriptCacheParent;
+
 namespace embedding {
 class PrintingParent;
 }
 
 namespace ipc {
 class OptionalURIParams;
 class PFileDescriptorSetParent;
 class URIParams;
@@ -906,16 +908,22 @@ private:
 
   virtual bool
   DeallocPCycleCollectWithLogsParent(PCycleCollectWithLogsParent* aActor) override;
 
   virtual PTestShellParent* AllocPTestShellParent() override;
 
   virtual bool DeallocPTestShellParent(PTestShellParent* shell) override;
 
+  virtual PScriptCacheParent*
+  AllocPScriptCacheParent(const FileDescOrError& cacheFile,
+                          const bool& wantCacheData) override;
+
+  virtual bool DeallocPScriptCacheParent(PScriptCacheParent* shell) override;
+
   virtual bool DeallocPNeckoParent(PNeckoParent* necko) override;
 
   virtual PPSMContentDownloaderParent*
   AllocPPSMContentDownloaderParent(const uint32_t& aCertType) override;
 
   virtual bool
   DeallocPPSMContentDownloaderParent(PPSMContentDownloaderParent* aDownloader) override;
 
--- a/dom/ipc/PContent.ipdl
+++ b/dom/ipc/PContent.ipdl
@@ -40,16 +40,17 @@ include protocol PRemoteSpellcheckEngine
 include protocol PWebBrowserPersistDocument;
 include protocol PWebrtcGlobal;
 include protocol PPresentation;
 include protocol PURLClassifier;
 include protocol PURLClassifierLocal;
 include protocol PVRManager;
 include protocol PVideoDecoderManager;
 include protocol PFlyWebPublishedServer;
+include protocol PScriptCache;
 include DOMTypes;
 include JavaScriptTypes;
 include IPCBlob;
 include IPCStream;
 include PTabContext;
 include URIParams;
 include PluginTypes;
 include ProtocolTypes;
@@ -306,16 +307,17 @@ nested(upto inside_cpow) sync protocol P
     manages PJavaScript;
     manages PRemoteSpellcheckEngine;
     manages PWebBrowserPersistDocument;
     manages PWebrtcGlobal;
     manages PPresentation;
     manages PFlyWebPublishedServer;
     manages PURLClassifier;
     manages PURLClassifierLocal;
+    manages PScriptCache;
 
 both:
     // Depending on exactly how the new browser is being created, it might be
     // created from either the child or parent process!
     //
     // The child creates the PBrowser as part of
     // TabChild::BrowserFrameProvideWindow (which happens when the child's
     // content calls window.open()), and the parent creates the PBrowser as part
@@ -401,16 +403,18 @@ child:
      * nsIMemoryInfoDumper.idl
      */
     async PCycleCollectWithLogs(bool dumpAllTraces,
                                 FileDescriptor gcLog,
                                 FileDescriptor ccLog);
 
     async PTestShell();
 
+    async PScriptCache(FileDescOrError cacheFile, bool wantCacheData);
+
     async RegisterChrome(ChromePackage[] packages, SubstitutionMapping[] substitutions,
                          OverrideMapping[] overrides, nsCString locale, bool reset);
     async RegisterChromeItem(ChromeRegistryItem item);
 
     async ClearImageCache(bool privateLoader, bool chrome);
 
     async SetOffline(bool offline);
     async SetConnectivity(bool connectivity);
--- a/dom/media/eme/CDMProxy.h
+++ b/dom/media/eme/CDMProxy.h
@@ -14,22 +14,26 @@
 #include "mozilla/dom/MediaKeys.h"
 
 #include "nsIThread.h"
 
 namespace mozilla {
 class MediaRawData;
 class ChromiumCDMProxy;
 
+namespace eme {
 enum DecryptStatus {
   Ok = 0,
   GenericErr = 1,
   NoKeyErr = 2,
   AbortedErr = 3,
 };
+}
+
+using eme::DecryptStatus;
 
 struct DecryptResult {
   DecryptResult(DecryptStatus aStatus, MediaRawData* aSample)
     : mStatus(aStatus)
     , mSample(aSample)
   {}
   DecryptStatus mStatus;
   RefPtr<MediaRawData> mSample;
--- a/dom/media/gmp/ChromiumCDMParent.cpp
+++ b/dom/media/gmp/ChromiumCDMParent.cpp
@@ -13,16 +13,18 @@
 #include "content_decryption_module.h"
 #include "GMPLog.h"
 #include "MediaPrefs.h"
 #include "GMPUtils.h"
 
 namespace mozilla {
 namespace gmp {
 
+using namespace eme;
+
 ChromiumCDMParent::ChromiumCDMParent(GMPContentParent* aContentParent,
                                      uint32_t aPluginId)
   : mPluginId(aPluginId)
   , mContentParent(aContentParent)
   , mVideoShmemLimit(MediaPrefs::EMEChromiumAPIVideoShmemCount())
 {
   GMP_LOG(
     "ChromiumCDMParent::ChromiumCDMParent(this=%p, contentParent=%p, id=%u)",
@@ -1079,17 +1081,17 @@ ChromiumCDMParent::Shutdown()
   }
 
   // We may be called from a task holding the last reference to the proxy, so
   // let's clear our local weak pointer to ensure it will not be used afterward
   // (including from an already-queued task, e.g.: ActorDestroy).
   mProxy = nullptr;
 
   for (RefPtr<DecryptJob>& decrypt : mDecrypts) {
-    decrypt->PostResult(AbortedErr);
+    decrypt->PostResult(eme::AbortedErr);
   }
   mDecrypts.Clear();
 
   if (mVideoDecoderInitialized && !mActorDestroyed) {
     Unused << SendDeinitializeVideoDecoder();
     mVideoDecoderInitialized = false;
   }
 
--- a/dom/media/gmp/ChromiumCDMProxy.cpp
+++ b/dom/media/gmp/ChromiumCDMProxy.cpp
@@ -552,17 +552,17 @@ ChromiumCDMProxy::Capabilites()
   return mCapabilites;
 }
 
 RefPtr<DecryptPromise>
 ChromiumCDMProxy::Decrypt(MediaRawData* aSample)
 {
   RefPtr<gmp::ChromiumCDMParent> cdm = GetCDMParent();
   if (!cdm) {
-    return DecryptPromise::CreateAndReject(DecryptResult(AbortedErr, aSample),
+    return DecryptPromise::CreateAndReject(DecryptResult(eme::AbortedErr, aSample),
                                            __func__);
   }
   RefPtr<MediaRawData> sample = aSample;
   return InvokeAsync(
     mGMPThread, __func__, [cdm, sample]() { return cdm->Decrypt(sample); });
 }
 
 void
--- a/dom/media/gmp/DecryptJob.cpp
+++ b/dom/media/gmp/DecryptJob.cpp
@@ -31,22 +31,22 @@ DecryptJob::PostResult(DecryptStatus aRe
 
 void
 DecryptJob::PostResult(DecryptStatus aResult,
                        Span<const uint8_t> aDecryptedData)
 {
   if (aDecryptedData.Length() != mSample->Size()) {
     NS_WARNING("CDM returned incorrect number of decrypted bytes");
   }
-  if (aResult == Ok) {
+  if (aResult == eme::Ok) {
     UniquePtr<MediaRawDataWriter> writer(mSample->CreateWriter());
     PodCopy(writer->Data(),
             aDecryptedData.Elements(),
             std::min<size_t>(aDecryptedData.Length(), mSample->Size()));
-  } else if (aResult == NoKeyErr) {
+  } else if (aResult == eme::NoKeyErr) {
     NS_WARNING("CDM returned NoKeyErr");
     // We still have the encrypted sample, so we can re-enqueue it to be
     // decrypted again once the key is usable again.
   } else {
     nsAutoCString str("CDM returned decode failure DecryptStatus=");
     str.AppendInt(aResult);
     NS_WARNING(str.get());
   }
--- a/dom/media/gmp/GMPCDMProxy.cpp
+++ b/dom/media/gmp/GMPCDMProxy.cpp
@@ -495,17 +495,17 @@ GMPCDMProxy::gmp_Shutdown()
 {
   MOZ_ASSERT(IsOnOwnerThread());
 
   mShutdownCalled = true;
 
   // Abort any pending decrypt jobs, to awaken any clients waiting on a job.
   for (size_t i = 0; i < mDecryptionJobs.Length(); i++) {
     DecryptJob* job = mDecryptionJobs[i];
-    job->PostResult(AbortedErr);
+    job->PostResult(eme::AbortedErr);
   }
   mDecryptionJobs.Clear();
 
   if (mCDM) {
     mCDM->Close();
     mCDM = nullptr;
   }
 }
@@ -691,17 +691,17 @@ GMPCDMProxy::Decrypt(MediaRawData* aSamp
 }
 
 void
 GMPCDMProxy::gmp_Decrypt(RefPtr<DecryptJob> aJob)
 {
   MOZ_ASSERT(IsOnOwnerThread());
 
   if (!mCDM) {
-    aJob->PostResult(AbortedErr);
+    aJob->PostResult(eme::AbortedErr);
     return;
   }
 
   nsTArray<uint8_t> data;
   data.AppendElements(aJob->mSample->Data(), aJob->mSample->Size());
   mCDM->Decrypt(aJob->mId, aJob->mSample->mCrypto, data);
   mDecryptionJobs.AppendElement(aJob.forget());
 }
--- a/dom/media/gmp/GMPDecryptorParent.cpp
+++ b/dom/media/gmp/GMPDecryptorParent.cpp
@@ -395,20 +395,20 @@ GMPDecryptorParent::RecvBatchedKeyStatus
   }
   return IPC_OK();
 }
 
 DecryptStatus
 ToDecryptStatus(GMPErr aError)
 {
   switch (aError) {
-    case GMPNoErr: return Ok;
-    case GMPNoKeyErr: return NoKeyErr;
-    case GMPAbortedErr: return AbortedErr;
-    default: return GenericErr;
+    case GMPNoErr: return eme::Ok;
+    case GMPNoKeyErr: return eme::NoKeyErr;
+    case GMPAbortedErr: return eme::AbortedErr;
+    default: return eme::GenericErr;
   }
 }
 
 mozilla::ipc::IPCResult
 GMPDecryptorParent::RecvDecrypted(const uint32_t& aId,
                                   const GMPErr& aErr,
                                   InfallibleTArray<uint8_t>&& aBuffer)
 {
--- a/dom/media/platforms/agnostic/eme/EMEDecoderModule.cpp
+++ b/dom/media/platforms/agnostic/eme/EMEDecoderModule.cpp
@@ -122,22 +122,22 @@ public:
       return;
     }
 
     if (mIsShutdown) {
       NS_WARNING("EME decrypted sample arrived after shutdown");
       return;
     }
 
-    if (aDecrypted.mStatus == NoKeyErr) {
+    if (aDecrypted.mStatus == eme::NoKeyErr) {
       // Key became unusable after we sent the sample to CDM to decrypt.
       // Call Decode() again, so that the sample is enqueued for decryption
       // if the key becomes usable again.
       AttemptDecode(aDecrypted.mSample);
-    } else if (aDecrypted.mStatus != Ok) {
+    } else if (aDecrypted.mStatus != eme::Ok) {
       mDecodePromise.RejectIfExists(
         MediaResult(
           NS_ERROR_DOM_MEDIA_FATAL_ERR,
           RESULT_DETAIL("decrypted.mStatus=%u", uint32_t(aDecrypted.mStatus))),
         __func__);
     } else {
       MOZ_ASSERT(!mIsShutdown);
       // The sample is no longer encrypted, so clear its crypto metadata.
--- a/dom/media/tests/mochitest/mochitest.ini
+++ b/dom/media/tests/mochitest/mochitest.ini
@@ -236,16 +236,18 @@ skip-if = (android_version == '18') # em
 [test_peerConnection_answererAddSecondAudioStream.html]
 skip-if = (android_version == '18') # emulator is too slow to finish a renegotiation test in under 5 minutes
 [test_peerConnection_removeAudioTrack.html]
 skip-if = (android_version == '18') # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_peerConnection_removeThenAddAudioTrack.html]
 skip-if = (android_version == '18') # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_peerConnection_addSecondVideoStream.html]
 skip-if = android_version == '18' # android(Bug 1189784, timeouts on 4.3 emulator)
+[test_peerConnection_restrictBandwidthWithTias.html]
+skip-if = android_version == '18' # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_peerConnection_removeVideoTrack.html]
 skip-if = (android_version == '18' && debug) # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_peerConnection_removeThenAddVideoTrack.html]
 skip-if = (android_version == '18' && debug) # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_peerConnection_replaceVideoThenRenegotiate.html]
 skip-if = (android_version == '18' && debug) # android(Bug 1189784, timeouts on 4.3 emulator)
 [test_peerConnection_addSecondAudioStreamNoBundle.html]
 skip-if = (android_version == '18') # android(Bug 1189784, timeouts on 4.3 emulator)
--- a/dom/media/tests/mochitest/sdpUtils.js
+++ b/dom/media/tests/mochitest/sdpUtils.js
@@ -65,16 +65,20 @@ setAllMsectionsInactive: function(sdp) {
 removeAllRtpMaps: function(sdp) {
   return sdp.replace(/a=rtpmap:.*\r\n/g, "");
 },
 
 reduceAudioMLineToDynamicPtAndOpus: function(sdp) {
   return sdp.replace(/m=audio .*\r\n/g, "m=audio 9 UDP/TLS/RTP/SAVPF 101 109\r\n");
 },
 
+addTiasBps: function(sdp, bps) {
+  return sdp.replace(/c=IN (.*)\r\n/g, "c=IN $1\r\nb=TIAS:" + bps + "\r\n");
+},
+
 removeSimulcastProperties: function(sdp) {
   return sdp.replace(/a=simulcast:.*\r\n/g, "")
             .replace(/a=rid:.*\r\n/g, "")
             .replace(/a=extmap:[^\s]* urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id.*\r\n/g, "");
 },
 
 transferSimulcastProperties: function(offer_sdp, answer_sdp) {
   if (!offer_sdp.includes("a=simulcast:")) {
copy from dom/media/tests/mochitest/test_peerConnection_basicVideo.html
copy to dom/media/tests/mochitest/test_peerConnection_restrictBandwidthWithTias.html
--- a/dom/media/tests/mochitest/test_peerConnection_basicVideo.html
+++ b/dom/media/tests/mochitest/test_peerConnection_restrictBandwidthWithTias.html
@@ -2,22 +2,30 @@
 <html>
 <head>
   <script type="application/javascript" src="pc.js"></script>
 </head>
 <body>
 <pre id="test">
 <script type="application/javascript">
   createHTML({
-    bug: "796888",
-    title: "Basic video-only peer connection"
+    bug: "1359854",
+    title: "500Kb restricted video-only peer connection"
   });
 
   var test;
   runNetworkTest(function (options) {
     test = new PeerConnectionTest(options);
     test.setMediaConstraints([{video: true}], [{video: true}]);
+    test.chain.insertAfter('PC_REMOTE_GET_OFFER', [
+      function PC_REMOTE_ADD_TIAS(test) {
+        test._local_offer.sdp = sdputils.addTiasBps(
+          test._local_offer.sdp, 250000);
+        info("Offer with TIAS: " + JSON.stringify(test._local_offer));
+      }
+    ]);
+    // TODO it would be nice to verify the used bandwidth
     test.run();
   });
 </script>
 </pre>
 </body>
 </html>
--- a/dom/smil/nsSMILCSSValueType.cpp
+++ b/dom/smil/nsSMILCSSValueType.cpp
@@ -473,43 +473,32 @@ ValueFromStringHelper(nsCSSPropertyID aP
   const ServoComputedValues* parentStyle =
     aStyleContext->GetParentAllowServo()
     ? aStyleContext->GetParentAllowServo()->StyleSource()
       .AsServoComputedValues()
     : nullptr;
   const ServoComputedValuesWithParent servoStyles =
     { currentStyle, parentStyle };
 
-  // FIXME (bug 1357295): Handle negative values properly
-#ifdef DEBUG
-  {
-    bool isNegative = false;
-    Unused << GetNonNegativePropValue(aString, aPropID, isNegative);
-    if (isNegative) {
-      NS_WARNING("stylo: Special negative value handling not yet supported"
-                 " (bug 1357295)");
-    }
-  }
-#endif // DEBUG
-
   // Parse property
   nsIDocument* doc = aTargetElement->GetUncomposedDoc();
   if (!doc) {
     return nullptr;
   }
   // FIXME this is using the wrong base uri (bug 1343919)
   RefPtr<URLExtraData> data = new URLExtraData(doc->GetDocumentURI(),
                                                doc->GetDocumentURI(),
                                                doc->NodePrincipal());
   NS_ConvertUTF16toUTF8 value(aString);
   RefPtr<RawServoDeclarationBlock> servoDeclarationBlock =
     Servo_ParseProperty(aPropID,
                         &value,
                         data,
-                        LengthParsingMode::SVG).Consume();
+                        ParsingMode::AllowUnitlessLength |
+                        ParsingMode::AllowAllNumericValues).Consume();
   if (!servoDeclarationBlock) {
     return nullptr;
   }
 
   // Compute value
   PropertyValuePair propValuePair;
   propValuePair.mProperty = aPropID;
   propValuePair.mServoDeclarationBlock = servoDeclarationBlock;
--- a/dom/svg/SVGSVGElement.h
+++ b/dom/svg/SVGSVGElement.h
@@ -428,16 +428,17 @@ private:
 
 class MOZ_RAII AutoPreserveAspectRatioOverride
 {
 public:
   AutoPreserveAspectRatioOverride(const Maybe<SVGImageContext>& aSVGContext,
                                   dom::SVGSVGElement* aRootElem
                                   MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
     : mRootElem(aRootElem)
+    , mDidOverride(false)
   {
     MOZ_GUARD_OBJECT_NOTIFIER_INIT;
     MOZ_ASSERT(mRootElem, "No SVG node to manage?");
 
     if (aSVGContext.isSome() &&
         aSVGContext->GetPreserveAspectRatio().isSome()) {
       // Override preserveAspectRatio in our helper document.
       // XXXdholbert We should technically be overriding the helper doc's clip
--- a/dom/svg/nsSVGElement.cpp
+++ b/dom/svg/nsSVGElement.cpp
@@ -1231,20 +1231,19 @@ MappedAttrParser::ParseMappedAttrValue(n
     if (mBackend == StyleBackendType::Gecko) {
       mParser.ParseProperty(propertyID, aMappedAttrValue, mDocURI, mBaseURI,
                             mElement->NodePrincipal(), mDecl->AsGecko(), &changed, false, true);
     } else {
       NS_ConvertUTF16toUTF8 value(aMappedAttrValue);
       // FIXME (bug 1343964): Figure out a better solution for sending the base uri to servo
       RefPtr<URLExtraData> data = new URLExtraData(mBaseURI, mDocURI,
                                                    mElement->NodePrincipal());
-      // FIXME (bug 1342559): Set SVG parsing mode for lengths
       changed = Servo_DeclarationBlock_SetPropertyById(
         mDecl->AsServo()->Raw(), propertyID, &value, false, data,
-        LengthParsingMode::SVG);
+        ParsingMode::AllowUnitlessLength);
     }
 
     if (changed) {
       // The normal reporting of use counters by the nsCSSParser won't happen
       // since it doesn't have a sheet.
       if (nsCSSProps::IsShorthand(propertyID)) {
         CSSPROPS_FOR_SHORTHAND_SUBPROPERTIES(subprop, propertyID,
                                              CSSEnabledState::eForAllContent) {
deleted file mode 100644
--- a/dom/webidl/AutocompleteErrorEvent.webidl
+++ /dev/null
@@ -1,23 +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/. */
-
-enum AutoCompleteErrorReason {
-  "",
-  "cancel",
-  "disabled",
-  "invalid"
-};
-
-[Pref="dom.forms.requestAutocomplete",
- Constructor(DOMString type, optional AutocompleteErrorEventInit eventInitDict)]
-interface AutocompleteErrorEvent : Event
-{
-  readonly attribute AutoCompleteErrorReason reason;
-};
-
-dictionary AutocompleteErrorEventInit : EventInit
-{
-  AutoCompleteErrorReason reason = "";
-};
--- a/dom/webidl/HTMLFormElement.webidl
+++ b/dom/webidl/HTMLFormElement.webidl
@@ -41,12 +41,9 @@ interface HTMLFormElement : HTMLElement 
   // TODO this should be: getter (RadioNodeList or HTMLInputElement or HTMLImageElement) (DOMString name);
   getter nsISupports (DOMString name);
 
   [Throws]
   void submit();
   void reset();
   boolean checkValidity();
   boolean reportValidity();
-
-  [Pref="dom.forms.requestAutocomplete"]
-  void requestAutocomplete();
 };
--- a/dom/webidl/Selection.webidl
+++ b/dom/webidl/Selection.webidl
@@ -85,9 +85,16 @@ partial interface Selection {
   readonly attribute short type;
 
   [ChromeOnly,Throws,Pref="dom.testing.selection.GetRangesForInterval"]
   sequence<Range> GetRangesForInterval(Node beginNode, long beginOffset, Node endNode, long endOffset,
                                        boolean allowAdjacent);
 
   [ChromeOnly,Throws]
   void scrollIntoView(short aRegion, boolean aIsSynchronous, short aVPercent, short aHPercent);
+
+  [ChromeOnly,Throws]
+  void setColors(DOMString aForegroundColor, DOMString aBackgroundColor,
+                 DOMString aAltForegroundColor, DOMString aAltBackgroundColor);
+
+  [ChromeOnly,Throws]
+  void resetColors();
 };
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -1049,17 +1049,16 @@ WEBIDL_FILES += [
 if CONFIG['FUZZING']:
     WEBIDL_FILES += [
         'FuzzingFunctions.webidl',
     ]
 
 GENERATED_EVENTS_WEBIDL_FILES = [
     'AddonEvent.webidl',
     'AnimationPlaybackEvent.webidl',
-    'AutocompleteErrorEvent.webidl',
     'BlobEvent.webidl',
     'CaretStateChangedEvent.webidl',
     'CloseEvent.webidl',
     'DeviceLightEvent.webidl',
     'DeviceOrientationEvent.webidl',
     'DeviceProximityEvent.webidl',
     'ErrorEvent.webidl',
     'FontFaceSetLoadEvent.webidl',
--- a/gfx/2d/2D.h
+++ b/gfx/2d/2D.h
@@ -1627,16 +1627,17 @@ public:
   /*
    * Attempts to create and install a D2D1 device from the supplied Direct3D11 device.
    * Returns true on success, or false on failure and leaves the D2D1/Direct3D11 devices unset.
    */
   static bool SetDirect3D11Device(ID3D11Device *aDevice);
   static bool SetDWriteFactory(IDWriteFactory *aFactory);
   static ID3D11Device *GetDirect3D11Device();
   static ID2D1Device *GetD2D1Device();
+  static uint32_t GetD2D1DeviceSeq();
   static IDWriteFactory *GetDWriteFactory();
   static bool SupportsD2D1();
 
   static already_AddRefed<GlyphRenderingOptions>
     CreateDWriteGlyphRenderingOptions(IDWriteRenderingParams *aParams);
 
   static uint64_t GetD2DVRAMUsageDrawTarget();
   static uint64_t GetD2DVRAMUsageSourceSurface();
--- a/gfx/2d/DrawTargetD2D1.cpp
+++ b/gfx/2d/DrawTargetD2D1.cpp
@@ -38,16 +38,17 @@ ID2D1Factory1 *D2DFactory1()
 {
   return DrawTargetD2D1::factory();
 }
 
 DrawTargetD2D1::DrawTargetD2D1()
   : mPushedLayers(1)
   , mUsedCommandListsSincePurge(0)
   , mDidComplexBlendWithListInList(false)
+  , mDeviceSeq(0)
 {
 }
 
 DrawTargetD2D1::~DrawTargetD2D1()
 {
   PopAllClips();
 
   if (mSnapshot) {
@@ -56,17 +57,17 @@ DrawTargetD2D1::~DrawTargetD2D1()
     // MarkIndependent is running.
     RefPtr<SourceSurfaceD2D1> deathGrip = mSnapshot;
     // mSnapshot can be treated as independent of this DrawTarget since we know
     // this DrawTarget won't change again.
     deathGrip->MarkIndependent();
     // mSnapshot will be cleared now.
   }
 
-  if (mDC) {
+  if (mDC && IsDeviceContextValid()) {
     // The only way mDC can be null is if Init failed, but it can happen and the
     // destructor is the only place where we need to check for it since the
     // DrawTarget will destroyed right after Init fails.
     mDC->EndDraw();
   }
 
   // Targets depending on us can break that dependency, since we're obviously not going to
   // be modified in the future.
@@ -102,28 +103,30 @@ DrawTargetD2D1::Snapshot()
 // this can cause issues with memory usage (see bug 1238328). EndDraw/BeginDraw
 // are expensive though, especially relatively when little work is done, so
 // we try to reduce the amount of times we execute these purges.
 static const uint32_t kPushedLayersBeforePurge = 25;
 
 void
 DrawTargetD2D1::Flush()
 {
-  if ((mUsedCommandListsSincePurge >= kPushedLayersBeforePurge) &&
-      mPushedLayers.size() == 1) {
-    // It's important to pop all clips as otherwise layers can forget about
-    // their clip when doing an EndDraw. When we have layers pushed we cannot
-    // easily pop all underlying clips to delay the purge until we have no
-    // layers pushed.
-    PopAllClips();
-    mUsedCommandListsSincePurge = 0;
-    mDC->EndDraw();
-    mDC->BeginDraw();
-  } else {
-    mDC->Flush();
+  if (IsDeviceContextValid()) {
+    if ((mUsedCommandListsSincePurge >= kPushedLayersBeforePurge) &&
+        mPushedLayers.size() == 1) {
+      // It's important to pop all clips as otherwise layers can forget about
+      // their clip when doing an EndDraw. When we have layers pushed we cannot
+      // easily pop all underlying clips to delay the purge until we have no
+      // layers pushed.
+      PopAllClips();
+      mUsedCommandListsSincePurge = 0;
+      mDC->EndDraw();
+      mDC->BeginDraw();
+    } else {
+      mDC->Flush();
+    }
   }
 
   // We no longer depend on any target.
   for (TargetSet::iterator iter = mDependingOnTargets.begin();
        iter != mDependingOnTargets.end(); iter++) {
     (*iter)->mDependentTargets.erase(this);
   }
   mDependingOnTargets.clear();
@@ -1002,16 +1005,18 @@ bool
 DrawTargetD2D1::Init(ID3D11Texture2D* aTexture, SurfaceFormat aFormat)
 {
   HRESULT hr;
 
   ID2D1Device* device = Factory::GetD2D1Device();
   if (!device) {
     gfxCriticalNote << "[D2D1.1] Failed to obtain a device for DrawTargetD2D1::Init(ID3D11Texture2D*, SurfaceFormat).";
     return false;
+  } else {
+    mDeviceSeq = Factory::GetD2D1DeviceSeq();
   }
 
   hr = device->CreateDeviceContext(D2D1_DEVICE_CONTEXT_OPTIONS_ENABLE_MULTITHREADED_OPTIMIZATIONS, getter_AddRefs(mDC));
 
   if (FAILED(hr)) {
     gfxCriticalError() <<"[D2D1.1] 1Failed to create a DeviceContext, code: " << hexa(hr) << " format " << (int)aFormat;
     return false;
   }
@@ -1067,16 +1072,18 @@ bool
 DrawTargetD2D1::Init(const IntSize &aSize, SurfaceFormat aFormat)
 {
   HRESULT hr;
 
   ID2D1Device* device = Factory::GetD2D1Device();
   if (!device) {
     gfxCriticalNote << "[D2D1.1] Failed to obtain a device for DrawTargetD2D1::Init(IntSize, SurfaceFormat).";
     return false;
+  } else {
+    mDeviceSeq = Factory::GetD2D1DeviceSeq();
   }
 
   hr = device->CreateDeviceContext(D2D1_DEVICE_CONTEXT_OPTIONS_ENABLE_MULTITHREADED_OPTIMIZATIONS, getter_AddRefs(mDC));
 
   if (FAILED(hr)) {
     gfxCriticalError() <<"[D2D1.1] 2Failed to create a DeviceContext, code: " << hexa(hr) << " format " << (int)aFormat;
     return false;
   }
@@ -1948,10 +1955,15 @@ DrawTargetD2D1::PushD2DLayer(ID2D1Device
 
   D2D1_ANTIALIAS_MODE antialias =
     aPixelAligned ? D2D1_ANTIALIAS_MODE_ALIASED : D2D1_ANTIALIAS_MODE_PER_PRIMITIVE;
 
   mDC->PushLayer(D2D1::LayerParameters1(aMaxRect, aGeometry, antialias, aTransform,
                                         1.0, nullptr, options), nullptr);
 }
 
+bool
+DrawTargetD2D1::IsDeviceContextValid() {
+  return (mDeviceSeq == Factory::GetD2D1DeviceSeq()) && Factory::GetD2D1Device();
+}
+
 }
 }
--- a/gfx/2d/DrawTargetD2D1.h
+++ b/gfx/2d/DrawTargetD2D1.h
@@ -220,20 +220,22 @@ private:
   already_AddRefed<ID2D1Brush> CreateBrushForPattern(const Pattern &aPattern, Float aAlpha = 1.0f);
 
   void PushClipGeometry(ID2D1Geometry* aGeometry, const D2D1_MATRIX_3X2_F& aTransform, bool aPixelAligned = false);
 
   void PushD2DLayer(ID2D1DeviceContext *aDC, ID2D1Geometry *aGeometry, const D2D1_MATRIX_3X2_F &aTransform,
                     bool aPixelAligned = false, bool aForceIgnoreAlpha = false,
                     const D2D1_RECT_F& aLayerRect = D2D1::InfiniteRect());
 
+  // This function is used to determine if the mDC is still valid; if it is
+  // stale, we should avoid using it to execute any draw commands.
+  bool IsDeviceContextValid();
+
   IntSize mSize;
 
-  RefPtr<ID3D11Device> mDevice;
-  RefPtr<ID3D11Texture2D> mTexture;
   RefPtr<ID2D1Geometry> mCurrentClippedGeometry;
   // This is only valid if mCurrentClippedGeometry is non-null. And will
   // only be the intersection of all pixel-aligned retangular clips. This is in
   // device space.
   IntRect mCurrentClipBounds;
   mutable RefPtr<ID2D1DeviceContext> mDC;
   RefPtr<ID2D1Bitmap1> mBitmap;
   RefPtr<ID2D1CommandList> mCommandList;
@@ -286,14 +288,16 @@ private:
   // subsequently used -again- as an input to a blend effect for a command list,
   // this causes an infinite recursion inside D2D as it tries to resolve the bounds.
   // If we resolve the current command list before this happens
   // we can avoid the subsequent hang. (See bug 1293586)
   bool mDidComplexBlendWithListInList;
 
   static ID2D1Factory1 *mFactory;
   static IDWriteFactory *mDWriteFactory;
+  // This value is uesed to verify if the DrawTarget is created by a stale device.
+  uint32_t mDeviceSeq;
 };
 
 }
 }
 
 #endif /* MOZILLA_GFX_DRAWTARGETD2D_H_ */
--- a/gfx/2d/Factory.cpp
+++ b/gfx/2d/Factory.cpp
@@ -161,16 +161,17 @@ namespace gfx {
 // In Gecko, this value is managed by gfx.logging.level in gfxPrefs.
 int32_t LoggingPrefs::sGfxLogLevel = LOG_DEFAULT;
 
 #ifdef MOZ_ENABLE_FREETYPE
 FT_Library Factory::mFTLibrary = nullptr;
 #endif
 
 #ifdef WIN32
+static uint32_t mDeviceSeq = 0;
 ID3D11Device *Factory::mD3D11Device = nullptr;
 ID2D1Device *Factory::mD2D1Device = nullptr;
 IDWriteFactory *Factory::mDWriteFactory = nullptr;
 #endif
 
 DrawEventRecorder *Factory::mRecorder;
 
 mozilla::gfx::Config* Factory::sConfig = nullptr;
@@ -674,16 +675,18 @@ Factory::SetDirect3D11Device(ID3D11Devic
   RefPtr<IDXGIDevice> device;
   aDevice->QueryInterface((IDXGIDevice**)getter_AddRefs(device));
   HRESULT hr = factory->CreateDevice(device, &mD2D1Device);
   if (FAILED(hr)) {
     gfxCriticalError() << "[D2D1] Failed to create gfx factory's D2D1 device, code: " << hexa(hr);
 
     mD3D11Device = nullptr;
     return false;
+  } else {
+    mDeviceSeq++;
   }
 
   return true;
 }
 
 ID3D11Device*
 Factory::GetDirect3D11Device()
 {
@@ -691,16 +694,22 @@ Factory::GetDirect3D11Device()
 }
 
 ID2D1Device*
 Factory::GetD2D1Device()
 {
   return mD2D1Device;
 }
 
+uint32_t
+Factory::GetD2D1DeviceSeq()
+{
+  return mDeviceSeq;
+}
+
 IDWriteFactory*
 Factory::GetDWriteFactory()
 {
   return mDWriteFactory;
 }
 
 bool
 Factory::SupportsD2D1()
--- a/gfx/ipc/GfxMessageUtils.h
+++ b/gfx/ipc/GfxMessageUtils.h
@@ -1282,47 +1282,35 @@ struct ParamTraits<APZStateChange>
 template<>
 struct ParamTraits<mozilla::layers::EventRegionsOverride>
   : public BitFlagsEnumSerializer<
             mozilla::layers::EventRegionsOverride,
             mozilla::layers::EventRegionsOverride::ALL_BITS>
 {};
 
 template<>
-struct ParamTraits<mozilla::layers::AsyncDragMetrics::DragDirection>
-  : public ContiguousEnumSerializer<
-             mozilla::layers::AsyncDragMetrics::DragDirection,
-             mozilla::layers::AsyncDragMetrics::DragDirection::NONE,
-             mozilla::layers::AsyncDragMetrics::DragDirection::SENTINEL>
-{};
-
-template<>
 struct ParamTraits<mozilla::layers::AsyncDragMetrics>
 {
   typedef mozilla::layers::AsyncDragMetrics paramType;
 
   static void Write(Message* aMsg, const paramType& aParam)
   {
     WriteParam(aMsg, aParam.mViewId);
     WriteParam(aMsg, aParam.mPresShellId);
     WriteParam(aMsg, aParam.mDragStartSequenceNumber);
     WriteParam(aMsg, aParam.mScrollbarDragOffset);
-    WriteParam(aMsg, aParam.mScrollTrack);
-    WriteParam(aMsg, aParam.mScrollThumbLength);
     WriteParam(aMsg, aParam.mDirection);
   }
 
   static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
   {
     return (ReadParam(aMsg, aIter, &aResult->mViewId) &&
             ReadParam(aMsg, aIter, &aResult->mPresShellId) &&
             ReadParam(aMsg, aIter, &aResult->mDragStartSequenceNumber) &&
             ReadParam(aMsg, aIter, &aResult->mScrollbarDragOffset) &&
-            ReadParam(aMsg, aIter, &aResult->mScrollTrack) &&
-            ReadParam(aMsg, aIter, &aResult->mScrollThumbLength) &&
             ReadParam(aMsg, aIter, &aResult->mDirection));
   }
 };
 
 template <>
 struct ParamTraits<mozilla::gfx::Glyph>
 {
   typedef mozilla::gfx::Glyph paramType;
@@ -1371,23 +1359,14 @@ struct ParamTraits<mozilla::layers::Comp
   static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult) {
     return ReadParam(aMsg, aIter, &aResult->mUseAPZ)
         && ReadParam(aMsg, aIter, &aResult->mUseWebRender);
   }
 };
 
 template <>
 struct ParamTraits<mozilla::layers::SimpleLayerAttributes>
-{
-  typedef mozilla::layers::SimpleLayerAttributes paramType;
-
-  static void Write(Message* aMsg, const paramType& aParam) {
-    aMsg->WriteBytes(&aParam, sizeof(aParam));
-  }
-
-  static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult) {
-    return aMsg->ReadBytesInto(aIter, aResult, sizeof(paramType));
-  }
-};
+  : public PlainOldDataSerializer<mozilla::layers::SimpleLayerAttributes>
+{ };
 
 } /* namespace IPC */
 
 #endif /* __GFXMESSAGEUTILS_H__ */
--- a/gfx/layers/LayerAttributes.h
+++ b/gfx/layers/LayerAttributes.h
@@ -12,32 +12,79 @@
 
 namespace IPC {
 template <typename T> struct ParamTraits;
 } // namespace IPC
 
 namespace mozilla {
 namespace layers {
 
+// Data stored for scroll thumb container layers.
+struct ScrollThumbData {
+  ScrollThumbData()
+    : mDirection(ScrollDirection::NONE)
+    , mThumbRatio(0.0f)
+    , mIsAsyncDraggable(false)
+  {}
+  ScrollThumbData(ScrollDirection aDirection,
+                  float aThumbRatio,
+                  CSSCoord aThumbStart,
+                  CSSCoord aThumbLength,
+                  bool aIsAsyncDraggable,
+                  CSSCoord aScrollTrackStart,
+                  CSSCoord aScrollTrackLength)
+    : mDirection(aDirection)
+    , mThumbRatio(aThumbRatio)
+    , mThumbStart(aThumbStart)
+    , mThumbLength(aThumbLength)
+    , mIsAsyncDraggable(aIsAsyncDraggable)
+    , mScrollTrackStart(aScrollTrackStart)
+    , mScrollTrackLength(aScrollTrackLength)
+  {}
+
+  ScrollDirection mDirection;
+  // The scrollbar thumb ratio is the ratio of the thumb position (in the CSS
+  // pixels of the scrollframe's parent's space) to the scroll position (in the
+  // CSS pixels of the scrollframe's space).
+  float mThumbRatio;
+  CSSCoord mThumbStart;
+  CSSCoord mThumbLength;
+  // Whether the scrollbar thumb can be dragged asynchronously.
+  bool mIsAsyncDraggable;
+  CSSCoord mScrollTrackStart;
+  CSSCoord mScrollTrackLength;
+
+  bool operator==(const ScrollThumbData& aOther) const {
+    return mDirection == aOther.mDirection &&
+           mThumbRatio == aOther.mThumbRatio &&
+           mThumbStart == aOther.mThumbStart &&
+           mThumbLength == aOther.mThumbLength &&
+           mIsAsyncDraggable == aOther.mIsAsyncDraggable &&
+           mScrollTrackStart == aOther.mScrollTrackStart &&
+           mScrollTrackLength == aOther.mScrollTrackLength;
+  }
+  bool operator!=(const ScrollThumbData& aOther) const {
+    return !(*this == aOther);
+  }
+};
+
 // Infrequently changing layer attributes that require no special
 // serialization work.
 class SimpleLayerAttributes final
 {
   friend struct IPC::ParamTraits<mozilla::layers::SimpleLayerAttributes>;
 public:
   SimpleLayerAttributes()
    : mTransformIsPerspective(false),
      mPostXScale(1.0f),
      mPostYScale(1.0f),
      mContentFlags(0),
      mOpacity(1.0f),
      mIsFixedPosition(false),
      mScrollbarTargetContainerId(FrameMetrics::NULL_SCROLL_ID),
-     mScrollbarDirection(ScrollDirection::NONE),
-     mScrollbarThumbRatio(0.0f),
      mIsScrollbarContainer(false),
      mMixBlendMode(gfx::CompositionOp::OP_OVER),
      mForceIsolatedGroup(false)
   {
   }
 
   //
   // Setters.
@@ -68,26 +115,24 @@ public:
   }
   bool SetIsFixedPosition(bool aFixedPosition) {
     if (mIsFixedPosition == aFixedPosition) {
       return false;
     }
     mIsFixedPosition = aFixedPosition;
     return true;
   }
-  bool SetScrollbarData(FrameMetrics::ViewID aScrollId, ScrollDirection aDir, float aThumbRatio) {
+  bool SetScrollThumbData(FrameMetrics::ViewID aScrollId, const ScrollThumbData& aThumbData) {
     if (mScrollbarTargetContainerId == aScrollId &&
-        mScrollbarDirection == aDir &&
-        mScrollbarThumbRatio == aThumbRatio)
+        mThumbData == aThumbData)
     {
       return false;
     }
     mScrollbarTargetContainerId = aScrollId;
-    mScrollbarDirection = aDir;
-    mScrollbarThumbRatio = aThumbRatio;
+    mThumbData = aThumbData;
     return true;
   }
   bool SetIsScrollbarContainer(FrameMetrics::ViewID aScrollId) {
     if (mIsScrollbarContainer && mScrollbarTargetContainerId == aScrollId) {
       return false;
     }
     mIsScrollbarContainer = true;
     mScrollbarTargetContainerId = aScrollId;
@@ -167,17 +212,17 @@ public:
   // APZ hit testing.
   bool HitTestingInfoIsEqual(const SimpleLayerAttributes& aOther) const {
     if (mIsScrollbarContainer != aOther.mIsScrollbarContainer) {
       return false;
     }
     if (mScrollbarTargetContainerId != aOther.mScrollbarTargetContainerId) {
       return false;
     }
-    if (mScrollbarDirection != aOther.mScrollbarDirection) {
+    if (mThumbData != aOther.mThumbData) {
       return false;
     }
     if (FixedPositionScrollContainerId() != aOther.FixedPositionScrollContainerId()) {
       return false;
     }
     if (mTransform != aOther.mTransform) {
       return false;
     }
@@ -201,21 +246,18 @@ public:
     return mOpacity;
   }
   bool IsFixedPosition() const {
     return mIsFixedPosition;
   }
   FrameMetrics::ViewID ScrollbarTargetContainerId() const {
     return mScrollbarTargetContainerId;
   }
-  ScrollDirection ScrollbarDirection() const {
-    return mScrollbarDirection;
-  }
-  float ScrollbarThumbRatio() const {
-    return mScrollbarThumbRatio;
+  const ScrollThumbData& ThumbData() const {
+    return mThumbData;
   }
   float IsScrollbarContainer() const {
     return mIsScrollbarContainer;
   }
   gfx::CompositionOp MixBlendMode() const {
     return mMixBlendMode;
   }
   bool ForceIsolatedGroup() const {
@@ -259,38 +301,33 @@ public:
            mTransformIsPerspective == aOther.mTransformIsPerspective &&
            mScrolledClip == aOther.mScrolledClip &&
            mPostXScale == aOther.mPostXScale &&
            mPostYScale == aOther.mPostYScale &&
            mContentFlags == aOther.mContentFlags &&
            mOpacity == aOther.mOpacity &&
            mIsFixedPosition == aOther.mIsFixedPosition &&
            mScrollbarTargetContainerId == aOther.mScrollbarTargetContainerId &&
-           mScrollbarDirection == aOther.mScrollbarDirection &&
-           mScrollbarThumbRatio == aOther.mScrollbarThumbRatio &&
+           mThumbData == aOther.mThumbData &&
            mIsScrollbarContainer == aOther.mIsScrollbarContainer &&
            mMixBlendMode == aOther.mMixBlendMode &&
            mForceIsolatedGroup == aOther.mForceIsolatedGroup;
   }
 
 private:
   gfx::Matrix4x4 mTransform;
   bool mTransformIsPerspective;
   Maybe<LayerClip> mScrolledClip;
   float mPostXScale;
   float mPostYScale;
   uint32_t mContentFlags;
   float mOpacity;
   bool mIsFixedPosition;
   uint64_t mScrollbarTargetContainerId;
-  ScrollDirection mScrollbarDirection;
-  // The scrollbar thumb ratio is the ratio of the thumb position (in the CSS
-  // pixels of the scrollframe's parent's space) to the scroll position (in the
-  // CSS pixels of the scrollframe's space).
-  float mScrollbarThumbRatio;
+  ScrollThumbData mThumbData;
   bool mIsScrollbarContainer;
   gfx::CompositionOp mMixBlendMode;
   bool mForceIsolatedGroup;
 
   struct FixedPositionData {
     FrameMetrics::ViewID mScrollId;
     LayerPoint mAnchor;
     int32_t mSides;
--- a/gfx/layers/LayerMetricsWrapper.h
+++ b/gfx/layers/LayerMetricsWrapper.h
@@ -404,21 +404,21 @@ public:
     MOZ_ASSERT(IsValid());
 
     if (mLayer->AsContainerLayer()) {
       return mLayer->AsContainerLayer()->GetEventRegionsOverride();
     }
     return EventRegionsOverride::NoOverride;
   }
 
-  ScrollDirection GetScrollbarDirection() const
+  const ScrollThumbData& GetScrollThumbData() const
   {
     MOZ_ASSERT(IsValid());
 
-    return mLayer->GetScrollbarDirection();
+    return mLayer->GetScrollThumbData();
   }
 
   FrameMetrics::ViewID GetScrollbarTargetContainerId() const
   {
     MOZ_ASSERT(IsValid());
 
     return mLayer->GetScrollbarTargetContainerId();
   }
--- a/gfx/layers/Layers.cpp
+++ b/gfx/layers/Layers.cpp
@@ -1884,20 +1884,21 @@ Layer::PrintInfo(std::stringstream& aStr
     aStream << " [combines3DTransformWithAncestors]";
   }
   if (Is3DContextLeaf()) {
     aStream << " [is3DContextLeaf]";
   }
   if (IsScrollbarContainer()) {
     aStream << " [scrollbar]";
   }
-  if (GetScrollbarDirection() == ScrollDirection::VERTICAL) {
+  ScrollDirection thumbDirection = GetScrollThumbData().mDirection;
+  if (thumbDirection == ScrollDirection::VERTICAL) {
     aStream << nsPrintfCString(" [vscrollbar=%" PRIu64 "]", GetScrollbarTargetContainerId()).get();
   }
-  if (GetScrollbarDirection() == ScrollDirection::HORIZONTAL) {
+  if (thumbDirection == ScrollDirection::HORIZONTAL) {
     aStream << nsPrintfCString(" [hscrollbar=%" PRIu64 "]", GetScrollbarTargetContainerId()).get();
   }
   if (GetIsFixedPosition()) {
     LayerPoint anchor = GetFixedPositionAnchor();
     aStream << nsPrintfCString(" [isFixedPosition scrollId=%" PRIu64 " sides=0x%x anchor=%s]",
                      GetFixedPositionScrollContainerId(),
                      GetFixedPositionSides(),
                      ToString(anchor).c_str()).get();
@@ -2033,18 +2034,19 @@ Layer::DumpPacket(layerscope::LayersPack
   }
   // Opacity
   layer->set_opacity(GetOpacity());
   // Content opaque
   layer->set_copaque(static_cast<bool>(GetContentFlags() & CONTENT_OPAQUE));
   // Component alpha
   layer->set_calpha(static_cast<bool>(GetContentFlags() & CONTENT_COMPONENT_ALPHA));
   // Vertical or horizontal bar
-  if (GetScrollbarDirection() != ScrollDirection::NONE) {
-    layer->set_direct(GetScrollbarDirection() == ScrollDirection::VERTICAL ?
+  ScrollDirection thumbDirection = GetScrollThumbData().mDirection;
+  if (thumbDirection != ScrollDirection::NONE) {
+    layer->set_direct(thumbDirection == ScrollDirection::VERTICAL ?
                       LayersPacket::Layer::VERTICAL :
                       LayersPacket::Layer::HORIZONTAL);
     layer->set_barid(GetScrollbarTargetContainerId());
   }
 
   // Mask layer
   if (mMaskLayer) {
     layer->set_mask(reinterpret_cast<uint64_t>(mMaskLayer.get()));
--- a/gfx/layers/Layers.h
+++ b/gfx/layers/Layers.h
@@ -1283,22 +1283,23 @@ public:
     if (mSimpleAttrs.SetStickyPositionData(aScrollId, aOuter, aInner)) {
       MOZ_LAYERS_LOG_IF_SHADOWABLE(this, ("Layer::Mutated(%p) StickyPositionData", this));
       MutatedSimple();
     }
   }
 
   /**
    * CONSTRUCTION PHASE ONLY
-   * If a layer is a scrollbar layer, |aScrollId| holds the scroll identifier
-   * of the scrollable content that the scrollbar is for.
+   * If a layer is a scroll thumb container layer, set the scroll identifier
+   * of the scroll frame scrolled by the thumb, and other data related to the
+   * thumb.
    */
-  void SetScrollbarData(FrameMetrics::ViewID aScrollId, ScrollDirection aDir, float aThumbRatio)
+  void SetScrollThumbData(FrameMetrics::ViewID aScrollId, const ScrollThumbData& aThumbData)
   {
-    if (mSimpleAttrs.SetScrollbarData(aScrollId, aDir, aThumbRatio)) {
+    if (mSimpleAttrs.SetScrollThumbData(aScrollId, aThumbData)) {
       MOZ_LAYERS_LOG_IF_SHADOWABLE(this, ("Layer::Mutated(%p) ScrollbarData", this));
       MutatedSimple();
     }
   }
 
   // Set during construction for the container layer of scrollbar components.
   // |aScrollId| holds the scroll identifier of the scrollable content that
   // the scrollbar is for.
@@ -1362,18 +1363,17 @@ public:
   bool GetIsStickyPosition() { return mSimpleAttrs.IsStickyPosition(); }
   FrameMetrics::ViewID GetFixedPositionScrollContainerId() { return mSimpleAttrs.FixedPositionScrollContainerId(); }
   LayerPoint GetFixedPositionAnchor() { return mSimpleAttrs.FixedPositionAnchor(); }
   int32_t GetFixedPositionSides() { return mSimpleAttrs.FixedPositionSides(); }
   FrameMetrics::ViewID GetStickyScrollContainerId() { return mSimpleAttrs.StickyScrollContainerId(); }
   const LayerRect& GetStickyScrollRangeOuter() { return mSimpleAttrs.StickyScrollRangeOuter(); }
   const LayerRect& GetStickyScrollRangeInner() { return mSimpleAttrs.StickyScrollRangeInner(); }
   FrameMetrics::ViewID GetScrollbarTargetContainerId() { return mSimpleAttrs.ScrollbarTargetContainerId(); }
-  ScrollDirection GetScrollbarDirection() { return mSimpleAttrs.ScrollbarDirection(); }
-  float GetScrollbarThumbRatio() { return mSimpleAttrs.ScrollbarThumbRatio(); }
+  const ScrollThumbData& GetScrollThumbData() const { return mSimpleAttrs.ThumbData(); }
   bool IsScrollbarContainer() { return mSimpleAttrs.IsScrollbarContainer(); }
   Layer* GetMaskLayer() const { return mMaskLayer; }
   void CheckCanary() const { mCanary.Check(); }
 
   // Ancestor mask layers are associated with FrameMetrics, but for simplicity
   // in maintaining the layer tree structure we attach them to the layer.
   size_t GetAncestorMaskLayerCount() const {
     return mAncestorMaskLayers.Length();
--- a/gfx/layers/apz/src/APZCTreeManager.cpp
+++ b/gfx/layers/apz/src/APZCTreeManager.cpp
@@ -513,17 +513,17 @@ APZCTreeManager::PrepareNodeForLayer(con
     node = RecycleOrCreateNode(aState, nullptr, aLayersId);
     AttachNodeToTree(node, aParent, aNextSibling);
     node->SetHitTestData(
         GetEventRegions(aLayer),
         aLayer.GetTransformTyped(),
         aLayer.GetClipRect() ? Some(ParentLayerIntRegion(*aLayer.GetClipRect())) : Nothing(),
         GetEventRegionsOverride(aParent, aLayer));
     node->SetScrollbarData(aLayer.GetScrollbarTargetContainerId(),
-                           aLayer.GetScrollbarDirection(),
+                           aLayer.GetScrollThumbData(),
                            aLayer.IsScrollbarContainer());
     node->SetFixedPosData(aLayer.GetFixedPositionScrollContainerId());
     return node;
   }
 
   AsyncPanZoomController* apzc = nullptr;
   // If we get here, aLayer is a scrollable layer and somebody
   // has registered a GeckoContentController for it, so we need to ensure
@@ -702,17 +702,17 @@ APZCTreeManager::PrepareNodeForLayer(con
         Some(clipRegion),
         GetEventRegionsOverride(aParent, aLayer));
   }
 
   // Note: if layer properties must be propagated to nodes, RecvUpdate in
   // LayerTransactionParent.cpp must ensure that APZ will be notified
   // when those properties change.
   node->SetScrollbarData(aLayer.GetScrollbarTargetContainerId(),
-                         aLayer.GetScrollbarDirection(),
+                         aLayer.GetScrollThumbData(),
                          aLayer.IsScrollbarContainer());
   node->SetFixedPosData(aLayer.GetFixedPositionScrollContainerId());
   return node;
 }
 
 template<typename PanGestureOrScrollWheelInput>
 static bool
 WillHandleInput(const PanGestureOrScrollWheelInput& aPanInput)
@@ -776,49 +776,85 @@ APZCTreeManager::ReceiveInputEvent(Input
     case MULTITOUCH_INPUT: {
       MultiTouchInput& touchInput = aEvent.AsMultiTouchInput();
       result = ProcessTouchInput(touchInput, aOutTargetGuid, aOutInputBlockId);
       break;
     } case MOUSE_INPUT: {
       MouseInput& mouseInput = aEvent.AsMouseInput();
       mouseInput.mHandledByAPZ = true;
 
-      if (DragTracker::StartsDrag(mouseInput)) {
+      bool startsDrag = DragTracker::StartsDrag(mouseInput);
+      if (startsDrag) {
         // If this is the start of a drag we need to unambiguously know if it's
         // going to land on a scrollbar or not. We can't apply an untransform
         // here without knowing that, so we need to ensure the untransform is
         // a no-op.
         FlushRepaintsToClearScreenToGeckoTransform();
       }
 
-      bool hitScrollbar = false;
+      HitTestingTreeNode* hitScrollbarNode = nullptr;
       RefPtr<AsyncPanZoomController> apzc = GetTargetAPZC(mouseInput.mOrigin,
-            &hitResult, &hitScrollbar);
+            &hitResult, &hitScrollbarNode);
+      bool hitScrollbar = hitScrollbarNode;
 
       // When the mouse is outside the window we still want to handle dragging
       // but we won't find an APZC. Fallback to root APZC then.
       { // scope lock
         MutexAutoLock lock(mTreeLock);
         if (!apzc && mRootNode) {
           apzc = mRootNode->GetApzc();
         }
       }
 
       if (apzc) {
         bool targetConfirmed = (hitResult != HitNothing && hitResult != HitDispatchToContentRegion);
-        if (gfxPrefs::APZDragEnabled() && hitScrollbar) {
+        bool apzDragEnabled = gfxPrefs::APZDragEnabled();
+        if (apzDragEnabled && hitScrollbar) {
           // If scrollbar dragging is enabled and we hit a scrollbar, wait
           // for the main-thread confirmation because it contains drag metrics
           // that we need.
           targetConfirmed = false;
         }
         result = mInputQueue->ReceiveInputEvent(
           apzc, targetConfirmed,
           mouseInput, aOutInputBlockId);
 
+        // Under some conditions, we can confirm the drag block right away.
+        // Otherwise, we have to wait for a main-thread confirmation.
+        if (apzDragEnabled && gfxPrefs::APZDragInitiationEnabled() &&
+            startsDrag && hitScrollbarNode &&
+            hitScrollbarNode->IsScrollThumbNode() &&
+            hitScrollbarNode->GetScrollThumbData().mIsAsyncDraggable &&
+            // check that the scrollbar's target scroll frame is layerized
+            hitScrollbarNode->GetScrollTargetId() == apzc->GetGuid().mScrollId &&
+            !apzc->IsScrollInfoLayer() && mInputQueue->GetCurrentDragBlock()) {
+          DragBlockState* dragBlock = mInputQueue->GetCurrentDragBlock();
+          uint64_t dragBlockId = dragBlock->GetBlockId();
+          const ScrollThumbData& thumbData = hitScrollbarNode->GetScrollThumbData();
+          // AsyncPanZoomController::HandleInputEvent() will call
+          // TransformToLocal() on the event, but we need its mLocalOrigin now
+          // to compute a drag start offset for the AsyncDragMetrics.
+          mouseInput.TransformToLocal(apzc->GetTransformToThis());
+          CSSCoord dragStart = apzc->ConvertScrollbarPoint(
+              mouseInput.mLocalOrigin, thumbData);
+          // ConvertScrollbarPoint() got the drag start offset relative to
+          // the scroll track. Now get it relative to the thumb.
+          dragStart -= thumbData.mThumbStart;
+          mInputQueue->ConfirmDragBlock(
+              dragBlockId, apzc,
+              AsyncDragMetrics(apzc->GetGuid().mScrollId,
+                               apzc->GetGuid().mPresShellId,
+                               dragBlockId,
+                               dragStart,
+                               thumbData.mDirection));
+          // Content can't prevent scrollbar dragging with preventDefault(),
+          // so we don't need to wait for a content response.
+          dragBlock->SetContentResponse(false);
+        }
+
         if (result == nsEventStatus_eConsumeDoDefault) {
           // This input event is part of a drag block, so whether or not it is
           // directed at a scrollbar depends on whether the drag block started
           // on a scrollbar.
           hitScrollbar = mInputQueue->IsDragOnScrollbar(hitScrollbar);
         }
 
         // Update the out-parameters so they are what the caller expects.
@@ -1646,17 +1682,17 @@ APZCTreeManager::GetTargetNode(const Scr
       }
   );
   return target.forget();
 }
 
 already_AddRefed<AsyncPanZoomController>
 APZCTreeManager::GetTargetAPZC(const ScreenPoint& aPoint,
                                HitTestResult* aOutHitResult,
-                               bool* aOutHitScrollbar)
+                               HitTestingTreeNode** aOutHitScrollbar)
 {
   MutexAutoLock lock(mTreeLock);
   HitTestResult hitResult = HitNothing;
   ParentLayerPoint point = ViewAs<ParentLayerPixel>(aPoint,
     PixelCastJustification::ScreenIsParentLayerForRoot);
   RefPtr<AsyncPanZoomController> target = GetAPZCAtPoint(mRootNode, point,
       &hitResult, aOutHitScrollbar);
 
@@ -1787,17 +1823,17 @@ APZCTreeManager::GetTargetApzcForNode(Hi
   }
   return nullptr;
 }
 
 AsyncPanZoomController*
 APZCTreeManager::GetAPZCAtPoint(HitTestingTreeNode* aNode,
                                 const ParentLayerPoint& aHitTestPoint,
                                 HitTestResult* aOutHitResult,
-                                bool* aOutHitScrollbar)
+                                HitTestingTreeNode** aOutScrollbarNode)
 {
   mTreeLock.AssertCurrentThreadOwns();
 
   // This walks the tree in depth-first, reverse order, so that it encounters
   // APZCs front-to-back on the screen.
   HitTestingTreeNode* resultNode;
   HitTestingTreeNode* root = aNode;
   std::stack<ParentLayerPoint> hitTestPoints;
@@ -1840,18 +1876,18 @@ APZCTreeManager::GetAPZCAtPoint(HitTesti
         return TraversalFlag::Continue;
       }
   );
 
   if (*aOutHitResult != HitNothing) {
       MOZ_ASSERT(resultNode);
       for (HitTestingTreeNode* n = resultNode; n; n = n->GetParent()) {
         if (n->IsScrollbarNode()) {
-          if (aOutHitScrollbar) {
-            *aOutHitScrollbar = true;
+          if (aOutScrollbarNode) {
+            *aOutScrollbarNode = n;
           }
           // If we hit a scrollbar, target the APZC for the content scrolled
           // by the scrollbar. (The scrollbar itself doesn't scroll with the
           // scrolled content, so it doesn't carry the scrolled content's
           // scroll metadata).
           ScrollableLayerGuid guid(n->GetLayersId(), 0, n->GetScrollTargetId());
           if (RefPtr<HitTestingTreeNode> scrollTarget = GetTargetNode(guid, &GuidComparatorIgnoringPresShell)) {
             MOZ_ASSERT(scrollTarget->GetApzc());
--- a/gfx/layers/apz/src/APZCTreeManager.h
+++ b/gfx/layers/apz/src/APZCTreeManager.h
@@ -433,17 +433,17 @@ public:
      lock the tree of APZCs while they find the right one, and then return an addref'd
      pointer to it. This allows caller code to just use the target APZC without worrying
      about it going away. These are public for testing code and generally should not be
      used by other production code.
   */
   RefPtr<HitTestingTreeNode> GetRootNode() const;
   already_AddRefed<AsyncPanZoomController> GetTargetAPZC(const ScreenPoint& aPoint,
                                                          HitTestResult* aOutHitResult,
-                                                         bool* aOutHitScrollbar = nullptr);
+                                                         HitTestingTreeNode** aOutScrollbarNode = nullptr);
   ScreenToParentLayerMatrix4x4 GetScreenToApzcTransform(const AsyncPanZoomController *aApzc) const;
   ParentLayerToScreenMatrix4x4 GetApzcToGeckoTransform(const AsyncPanZoomController *aApzc) const;
 
   /**
    * Process touch velocity.
    * Sometimes the touch move event will have a velocity even though no scrolling
    * is occurring such as when the toolbar is being hidden/shown in Fennec.
    * This function can be called to have the y axis' velocity queue updated.
@@ -468,17 +468,17 @@ private:
                                                      GuidComparator aComparator);
   HitTestingTreeNode* FindTargetNode(HitTestingTreeNode* aNode,
                                      const ScrollableLayerGuid& aGuid,
                                      GuidComparator aComparator);
   AsyncPanZoomController* GetTargetApzcForNode(HitTestingTreeNode* aNode);
   AsyncPanZoomController* GetAPZCAtPoint(HitTestingTreeNode* aNode,
                                          const ParentLayerPoint& aHitTestPoint,
                                          HitTestResult* aOutHitResult,
-                                         bool* aOutHitScrollbar);
+                                         HitTestingTreeNode** aOutScrollbarNode);
   AsyncPanZoomController* FindRootApzcForLayersId(uint64_t aLayersId) const;
   AsyncPanZoomController* FindRootContentApzcForLayersId(uint64_t aLayersId) const;
   AsyncPanZoomController* FindRootContentOrRootApzc() const;
   already_AddRefed<AsyncPanZoomController> GetMultitouchTarget(AsyncPanZoomController* aApzc1, AsyncPanZoomController* aApzc2) const;
   already_AddRefed<AsyncPanZoomController> CommonAncestor(AsyncPanZoomController* aApzc1, AsyncPanZoomController* aApzc2) const;
   already_AddRefed<AsyncPanZoomController> GetTouchInputBlockAPZC(const MultiTouchInput& aEvent,
                                                                   nsTArray<TouchBehaviorFlags>* aOutTouchBehaviors,
                                                                   HitTestResult* aOutHitResult);
--- a/gfx/layers/apz/src/AsyncDragMetrics.h
+++ b/gfx/layers/apz/src/AsyncDragMetrics.h
@@ -3,67 +3,54 @@
 /* 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_layers_DragMetrics_h
 #define mozilla_layers_DragMetrics_h
 
 #include "FrameMetrics.h"
+#include "LayersTypes.h"
 
 namespace IPC {
 template <typename T> struct ParamTraits;
 } // namespace IPC
 
 namespace mozilla {
 
 namespace layers {
 
 class AsyncDragMetrics {
   friend struct IPC::ParamTraits<mozilla::layers::AsyncDragMetrics>;
 
 public:
-  enum DragDirection {
-    NONE,
-    VERTICAL,
-    HORIZONTAL,
-    SENTINEL,
-  };
-
   // IPC constructor
   AsyncDragMetrics()
     : mViewId(0)
     , mPresShellId(0)
     , mDragStartSequenceNumber(0)
     , mScrollbarDragOffset(0)
-    , mScrollThumbLength(0)
-    , mDirection(NONE)
+    , mDirection(ScrollDirection::NONE)
   {}
 
   AsyncDragMetrics(const FrameMetrics::ViewID& aViewId,
                    uint32_t aPresShellId,
                    uint64_t aDragStartSequenceNumber,
                    CSSCoord aScrollbarDragOffset,
-                   const CSSRect& aScrollTrack,
-                   CSSCoord aScrollThumbLength,
-                   DragDirection aDirection)
+                   ScrollDirection aDirection)
     : mViewId(aViewId)
     , mPresShellId(aPresShellId)
     , mDragStartSequenceNumber(aDragStartSequenceNumber)
     , mScrollbarDragOffset(aScrollbarDragOffset)
-    , mScrollTrack(aScrollTrack)
-    , mScrollThumbLength(aScrollThumbLength)
     , mDirection(aDirection)
   {}
 
   FrameMetrics::ViewID mViewId;
   uint32_t mPresShellId;
   uint64_t mDragStartSequenceNumber;
-  CSSCoord mScrollbarDragOffset;
-  CSSRect mScrollTrack;
-  CSSCoord mScrollThumbLength;
-  DragDirection mDirection;
+  CSSCoord mScrollbarDragOffset;  // relative to the thumb's start offset
+  ScrollDirection mDirection;
 };
 
 }
 }
 
 #endif
--- a/gfx/layers/apz/src/AsyncPanZoomController.cpp
+++ b/gfx/layers/apz/src/AsyncPanZoomController.cpp
@@ -828,63 +828,63 @@ AsyncPanZoomController::ArePointerEvents
   if (!consumable) {
     return false;
   }
 
   return true;
 }
 
 template <typename Units>
-static CoordTyped<Units> GetAxisStart(AsyncDragMetrics::DragDirection aDir, const PointTyped<Units>& aValue) {
-  if (aDir == AsyncDragMetrics::HORIZONTAL) {
+static CoordTyped<Units> GetAxisStart(ScrollDirection aDir, const PointTyped<Units>& aValue) {
+  if (aDir == ScrollDirection::HORIZONTAL) {
     return aValue.x;
   } else {
     return aValue.y;
   }
 }
 
 template <typename Units>
-static CoordTyped<Units> GetAxisStart(AsyncDragMetrics::DragDirection aDir, const RectTyped<Units>& aValue) {
-  if (aDir == AsyncDragMetrics::HORIZONTAL) {
+static CoordTyped<Units> GetAxisStart(ScrollDirection aDir, const RectTyped<Units>& aValue) {
+  if (aDir == ScrollDirection::HORIZONTAL) {
     return aValue.x;
   } else {
     return aValue.y;
   }
 }
 
 template <typename Units>
-static IntCoordTyped<Units> GetAxisStart(AsyncDragMetrics::DragDirection aDir, const IntRectTyped<Units>& aValue) {
-  if (aDir == AsyncDragMetrics::HORIZONTAL) {
+static IntCoordTyped<Units> GetAxisStart(ScrollDirection aDir, const IntRectTyped<Units>& aValue) {
+  if (aDir == ScrollDirection::HORIZONTAL) {
     return aValue.x;
   } else {
     return aValue.y;
   }
 }
 
 template <typename Units>
-static CoordTyped<Units> GetAxisEnd(AsyncDragMetrics::DragDirection aDir, const RectTyped<Units>& aValue) {
-  if (aDir == AsyncDragMetrics::HORIZONTAL) {
+static CoordTyped<Units> GetAxisEnd(ScrollDirection aDir, const RectTyped<Units>& aValue) {
+  if (aDir == ScrollDirection::HORIZONTAL) {
     return aValue.x + aValue.width;
   } else {
     return aValue.y + aValue.height;
   }
 }
 
 template <typename Units>
-static CoordTyped<Units> GetAxisLength(AsyncDragMetrics::DragDirection aDir, const RectTyped<Units>& aValue) {
-  if (aDir == AsyncDragMetrics::HORIZONTAL) {
+static CoordTyped<Units> GetAxisLength(ScrollDirection aDir, const RectTyped<Units>& aValue) {
+  if (aDir == ScrollDirection::HORIZONTAL) {
     return aValue.width;
   } else {
     return aValue.height;
   }
 }
 
 template <typename FromUnits, typename ToUnits>
-static float GetAxisScale(AsyncDragMetrics::DragDirection aDir, const ScaleFactors2D<FromUnits, ToUnits>& aValue) {
-  if (aDir == AsyncDragMetrics::HORIZONTAL) {
+static float GetAxisScale(ScrollDirection aDir, const ScaleFactors2D<FromUnits, ToUnits>& aValue) {
+  if (aDir == ScrollDirection::HORIZONTAL) {
     return aValue.xScale;
   } else {
     return aValue.yScale;
   }
 }
 
 nsEventStatus AsyncPanZoomController::HandleDragEvent(const MouseInput& aEvent,
                                                       const AsyncDragMetrics& aDragMetrics)
@@ -906,48 +906,42 @@ nsEventStatus AsyncPanZoomController::Ha
   }
 
   RefPtr<HitTestingTreeNode> node =
     GetApzcTreeManager()->FindScrollNode(aDragMetrics);
   if (!node) {
     return nsEventStatus_eConsumeNoDefault;
   }
 
+  const ScrollThumbData& thumbData = node->GetScrollThumbData();
+
   mozilla::Telemetry::Accumulate(mozilla::Telemetry::SCROLL_INPUT_METHODS,
       (uint32_t) ScrollInputMethod::ApzScrollbarDrag);
 
   ReentrantMonitorAutoEnter lock(mMonitor);
-  CSSPoint scrollFramePoint = aEvent.mLocalOrigin / GetFrameMetrics().GetZoom();
-  // The scrollbar can be transformed with the frame but the pres shell
-  // resolution is only applied to the scroll frame.
-  CSSPoint scrollbarPoint = scrollFramePoint * mFrameMetrics.GetPresShellResolution();
-  CSSRect cssCompositionBound = mFrameMetrics.CalculateCompositedRectInCssPixels();
-
-  CSSCoord mousePosition = GetAxisStart(aDragMetrics.mDirection, scrollbarPoint) -
-                        aDragMetrics.mScrollbarDragOffset -
-                        GetAxisStart(aDragMetrics.mDirection, cssCompositionBound) -
-                        GetAxisStart(aDragMetrics.mDirection, aDragMetrics.mScrollTrack);
-
-  CSSCoord scrollMax = GetAxisLength(aDragMetrics.mDirection, aDragMetrics.mScrollTrack);
-  scrollMax -= aDragMetrics.mScrollThumbLength;
+  CSSCoord mousePosition = ConvertScrollbarPoint(aEvent.mLocalOrigin, thumbData) -
+                           aDragMetrics.mScrollbarDragOffset;
+
+  CSSCoord scrollMax = thumbData.mScrollTrackLength;
+  scrollMax -= thumbData.mThumbLength;
 
   float scrollPercent = mousePosition / scrollMax;
 
   CSSCoord minScrollPosition =
     GetAxisStart(aDragMetrics.mDirection, mFrameMetrics.GetScrollableRect().TopLeft());
   CSSCoord maxScrollPosition =
     GetAxisLength(aDragMetrics.mDirection, mFrameMetrics.GetScrollableRect()) -
-    GetAxisLength(aDragMetrics.mDirection, cssCompositionBound);
+    GetAxisLength(aDragMetrics.mDirection, mFrameMetrics.CalculateCompositedRectInCssPixels());
   CSSCoord scrollPosition = scrollPercent * maxScrollPosition;
 
   scrollPosition = std::max(scrollPosition, minScrollPosition);
   scrollPosition = std::min(scrollPosition, maxScrollPosition);
 
   CSSPoint scrollOffset = mFrameMetrics.GetScrollOffset();
-  if (aDragMetrics.mDirection == AsyncDragMetrics::HORIZONTAL) {
+  if (aDragMetrics.mDirection == ScrollDirection::HORIZONTAL) {
     scrollOffset.x = scrollPosition;
   } else {
     scrollOffset.y = scrollPosition;
   }
   mFrameMetrics.SetScrollOffset(scrollOffset);
   ScheduleCompositeAndMaybeRepaint();
   UpdateSharedCompositorFrameMetrics();
 
@@ -1568,16 +1562,35 @@ AsyncPanZoomController::ConvertToGecko(c
 
     *aOut = LayoutDevicePoint(ViewAs<LayoutDevicePixel>(*layoutPoint,
                 PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent));
     return true;
   }
   return false;
 }
 
+CSSCoord
+AsyncPanZoomController::ConvertScrollbarPoint(const ParentLayerPoint& aScrollbarPoint,
+                                              const ScrollThumbData& aThumbData) const
+{
+  ReentrantMonitorAutoEnter lock(mMonitor);
+
+  // First, get it into the right coordinate space.
+  CSSPoint scrollbarPoint = aScrollbarPoint / mFrameMetrics.GetZoom();
+  // The scrollbar can be transformed with the frame but the pres shell
+  // resolution is only applied to the scroll frame.
+  scrollbarPoint = scrollbarPoint * mFrameMetrics.GetPresShellResolution();
+
+  // Now, get it to be relative to the beginning of the scroll track.
+  CSSRect cssCompositionBound = mFrameMetrics.CalculateCompositedRectInCssPixels();
+  return GetAxisStart(aThumbData.mDirection, scrollbarPoint)
+      - GetAxisStart(aThumbData.mDirection, cssCompositionBound)
+      - aThumbData.mScrollTrackStart;
+}
+
 static bool
 AllowsScrollingMoreThanOnePage(double aMultiplier)
 {
   const int32_t kMinAllowPageScroll =
     EventStateManager::MIN_MULTIPLIER_VALUE_ALLOWING_OVER_ONE_PAGE_SCROLL;
   return Abs(aMultiplier) >= kMinAllowPageScroll;
 }
 
@@ -2947,16 +2960,21 @@ bool AsyncPanZoomController::IsFlingingF
   return false;
 }
 
 bool AsyncPanZoomController::IsPannable() const {
   ReentrantMonitorAutoEnter lock(mMonitor);
   return mX.CanScroll() || mY.CanScroll();
 }
 
+bool AsyncPanZoomController::IsScrollInfoLayer() const {
+  ReentrantMonitorAutoEnter lock(mMonitor);
+  return mFrameMetrics.IsScrollInfoLayer();
+}
+
 int32_t AsyncPanZoomController::GetLastTouchIdentifier() const {
   RefPtr<GestureEventListener> listener = GetGestureEventListener();
   return listener ? listener->GetLastTouchIdentifier() : -1;
 }
 
 void AsyncPanZoomController::RequestContentRepaint(bool aUserAction) {
   // Reinvoke this method on the repaint thread if it's not there already. It's
   // important to do this before the call to CalculatePendingDisplayPort, so
--- a/gfx/layers/apz/src/AsyncPanZoomController.h
+++ b/gfx/layers/apz/src/AsyncPanZoomController.h
@@ -339,16 +339,21 @@ public:
   bool HasScrollgrab() const { return mScrollMetadata.GetHasScrollgrab(); }
 
   /**
    * Returns whether this APZC has room to be panned (in any direction).
    */
   bool IsPannable() const;
 
   /**
+   * Returns whether this APZC represents a scroll info layer.
+   */
+  bool IsScrollInfoLayer() const;
+
+  /**
    * Returns true if the APZC has been flung with a velocity greater than the
    * stop-on-tap fling velocity threshold (which is pref-controlled).
    */
   bool IsFlingingFast() const;
 
   /**
    * Returns the identifier of the touch in the last touch event processed by
    * this APZC. This should only be called when the last touch event contained
@@ -393,16 +398,24 @@ public:
   // Return whether or not a scroll delta will be able to scroll in either
   // direction.
   bool CanScrollWithWheel(const ParentLayerPoint& aDelta) const;
 
   // Return whether or not there is room to scroll this APZC
   // in the given direction.
   bool CanScroll(ScrollDirection aDirection) const;
 
+  /**
+   * Convert a point on the scrollbar from this APZC's ParentLayer coordinates
+   * to CSS coordinates relative to the beginning of the scroll track.
+   * Only the component in the direction of scrolling is returned.
+   */
+  CSSCoord ConvertScrollbarPoint(const ParentLayerPoint& aScrollbarPoint,
+                                 const ScrollThumbData& aThumbData) const;
+
   void NotifyMozMouseScrollEvent(const nsString& aString) const;
 
 protected:
   // Protected destructor, to discourage deletion outside of Release():
   virtual ~AsyncPanZoomController();
 
   // Returns the cached current frame time.
   TimeStamp GetFrameTime() const;
--- a/gfx/layers/apz/src/HitTestingTreeNode.cpp
+++ b/gfx/layers/apz/src/HitTestingTreeNode.cpp
@@ -22,17 +22,16 @@ namespace layers {
 
 HitTestingTreeNode::HitTestingTreeNode(AsyncPanZoomController* aApzc,
                                        bool aIsPrimaryHolder,
                                        uint64_t aLayersId)
   : mApzc(aApzc)
   , mIsPrimaryApzcHolder(aIsPrimaryHolder)
   , mLayersId(aLayersId)
   , mScrollViewId(FrameMetrics::NULL_SCROLL_ID)
-  , mScrollDir(ScrollDirection::NONE)
   , mIsScrollbarContainer(false)
   , mFixedPosTarget(FrameMetrics::NULL_SCROLL_ID)
   , mOverride(EventRegionsOverride::NoOverride)
 {
 if (mIsPrimaryApzcHolder) {
     MOZ_ASSERT(mApzc);
   }
   MOZ_ASSERT(!mApzc || mApzc->GetLayersId() == mLayersId);
@@ -90,46 +89,56 @@ HitTestingTreeNode::SetLastChild(HitTest
       MOZ_ASSERT(aChild->GetApzc() != parent);
       aChild->SetApzcParent(parent);
     }
   }
 }
 
 void
 HitTestingTreeNode::SetScrollbarData(FrameMetrics::ViewID aScrollViewId,
-                                     ScrollDirection aDir,
+                                     const ScrollThumbData& aThumbData,
                                      bool aIsScrollContainer)
 {
   mScrollViewId = aScrollViewId;
-  mScrollDir = aDir;
+  mScrollThumbData = aThumbData;
   mIsScrollbarContainer = aIsScrollContainer;
 }
 
 bool
 HitTestingTreeNode::MatchesScrollDragMetrics(const AsyncDragMetrics& aDragMetrics) const
 {
-  return ((mScrollDir == ScrollDirection::HORIZONTAL &&
-           aDragMetrics.mDirection == AsyncDragMetrics::HORIZONTAL) ||
-          (mScrollDir == ScrollDirection::VERTICAL &&
-           aDragMetrics.mDirection == AsyncDragMetrics::VERTICAL)) &&
+  return IsScrollThumbNode() &&
+         mScrollThumbData.mDirection == aDragMetrics.mDirection &&
          mScrollViewId == aDragMetrics.mViewId;
 }
 
 bool
+HitTestingTreeNode::IsScrollThumbNode() const
+{
+  return mScrollThumbData.mDirection != ScrollDirection::NONE;
+}
+
+bool
 HitTestingTreeNode::IsScrollbarNode() const
 {
-  return mIsScrollbarContainer || (mScrollDir != ScrollDirection::NONE);
+  return mIsScrollbarContainer || IsScrollThumbNode();
 }
 
 FrameMetrics::ViewID
 HitTestingTreeNode::GetScrollTargetId() const
 {
   return mScrollViewId;
 }
 
+const ScrollThumbData&
+HitTestingTreeNode::GetScrollThumbData() const
+{
+  return mScrollThumbData;
+}
+
 void
 HitTestingTreeNode::SetFixedPosData(FrameMetrics::ViewID aFixedPosTarget)
 {
   mFixedPosTarget = aFixedPosTarget;
 }
 
 FrameMetrics::ViewID
 HitTestingTreeNode::GetFixedPosTarget() const
--- a/gfx/layers/apz/src/HitTestingTreeNode.h
+++ b/gfx/layers/apz/src/HitTestingTreeNode.h
@@ -87,22 +87,23 @@ public:
                       const CSSTransformMatrix& aTransform,
                       const Maybe<ParentLayerIntRegion>& aClipRegion,
                       const EventRegionsOverride& aOverride);
   bool IsOutsideClip(const ParentLayerPoint& aPoint) const;
 
   /* Scrollbar info */
 
   void SetScrollbarData(FrameMetrics::ViewID aScrollViewId,
-                        ScrollDirection aDir,
+                        const ScrollThumbData& aThumbData,
                         bool aIsScrollContainer);
   bool MatchesScrollDragMetrics(const AsyncDragMetrics& aDragMetrics) const;
-  LayerIntCoord GetScrollThumbLength() const;
-  bool IsScrollbarNode() const;
+  bool IsScrollbarNode() const;  // Scroll thumb or scrollbar container layer.
+  bool IsScrollThumbNode() const;  // Scroll thumb container layer.
   FrameMetrics::ViewID GetScrollTargetId() const;
+  const ScrollThumbData& GetScrollThumbData() const;
 
   /* Fixed pos info */
 
   void SetFixedPosData(FrameMetrics::ViewID aFixedPosTarget);
   FrameMetrics::ViewID GetFixedPosTarget() const;
 
   /* Convert aPoint into the LayerPixel space for the layer corresponding to
    * this node. */
@@ -128,17 +129,17 @@ private:
 
   uint64_t mLayersId;
 
   // This is set for both scroll track and scroll thumb Container layers, and
   // represents the scroll id of the scroll frame scrolled by the scrollbar.
   FrameMetrics::ViewID mScrollViewId;
 
   // This is set for scroll thumb Container layers only.
-  ScrollDirection mScrollDir;
+  ScrollThumbData mScrollThumbData;
 
   // This is set for scroll track Container layers only.
   bool mIsScrollbarContainer;
 
   FrameMetrics::ViewID mFixedPosTarget;
 
   /* Let {L,M} be the {layer, scrollable metrics} pair that this node
    * corresponds to in the layer tree. mEventRegions contains the event regions
--- a/gfx/layers/composite/AsyncCompositionManager.cpp
+++ b/gfx/layers/composite/AsyncCompositionManager.cpp
@@ -799,17 +799,17 @@ MoveScrollbarForLayerMargin(Layer* aRoot
                             const ScreenMargin& aFixedLayerMargins)
 {
   // See bug 1223928 comment 9 - once we can detect the RCD with just the
   // isRootContent flag on the metrics, we can probably move this code into
   // ApplyAsyncTransformToScrollbar rather than having it as a separate
   // adjustment on the layer tree.
   Layer* scrollbar = BreadthFirstSearch<ReverseIterator>(aRoot,
     [aRootScrollId](Layer* aNode) {
-      return (aNode->GetScrollbarDirection() == ScrollDirection::HORIZONTAL &&
+      return (aNode->GetScrollThumbData().mDirection == ScrollDirection::HORIZONTAL &&
               aNode->GetScrollbarTargetContainerId() == aRootScrollId);
     });
   if (scrollbar) {
     // Shift the horizontal scrollbar down into the new space exposed by the
     // dynamic toolbar hiding. Technically we should also scale the vertical
     // scrollbar a bit to expand into the new space but it's not as noticeable
     // and it would add a lot more complexity, so we're going with the "it's not
     // worth it" justification.
@@ -1075,17 +1075,17 @@ AsyncCompositionManager::ApplyAsyncConte
                 maskLayer->GetLocalTransformTyped() * combinedAsyncTransform);
           }
 
           appliedTransform = true;
         }
 
         ExpandRootClipRect(layer, fixedLayerMargins);
 
-        if (layer->GetScrollbarDirection() != ScrollDirection::NONE) {
+        if (layer->GetScrollThumbData().mDirection != ScrollDirection::NONE) {
           ApplyAsyncTransformToScrollbar(layer);
         }
       });
 
   return appliedTransform;
 }
 
 static bool
@@ -1123,32 +1123,33 @@ ApplyAsyncTransformToScrollbarForContent
   AsyncTransformComponentMatrix asyncTransform =
     apzc->GetCurrentAsyncTransform(AsyncPanZoomController::RESPECT_FORCE_DISABLE);
 
   // |asyncTransform| represents the amount by which we have scrolled and
   // zoomed since the last paint. Because the scrollbar was sized and positioned based
   // on the painted content, we need to adjust it based on asyncTransform so that
   // it reflects what the user is actually seeing now.
   AsyncTransformComponentMatrix scrollbarTransform;
-  if (aScrollbar->GetScrollbarDirection() == ScrollDirection::VERTICAL) {
+  const ScrollThumbData& thumbData = aScrollbar->GetScrollThumbData();
+  if (thumbData.mDirection == ScrollDirection::VERTICAL) {
     const ParentLayerCoord asyncScrollY = asyncTransform._42;
     const float asyncZoomY = asyncTransform._22;
 
     // The scroll thumb needs to be scaled in the direction of scrolling by the
     // inverse of the async zoom. This is because zooming in decreases the
     // fraction of the whole srollable rect that is in view.
     const float yScale = 1.f / asyncZoomY;
 
     // Note: |metrics.GetZoom()| doesn't yet include the async zoom.
     const CSSToParentLayerScale effectiveZoom(metrics.GetZoom().yScale * asyncZoomY);
 
     // Here we convert the scrollbar thumb ratio into a true unitless ratio by
     // dividing out the conversion factor from the scrollframe's parent's space
     // to the scrollframe's space.
-    const float ratio = aScrollbar->GetScrollbarThumbRatio() /
+    const float ratio = thumbData.mThumbRatio /
         (metrics.GetPresShellResolution() * asyncZoomY);
     // The scroll thumb needs to be translated in opposite direction of the
     // async scroll. This is because scrolling down, which translates the layer
     // content up, should result in moving the scroll thumb down.
     ParentLayerCoord yTranslation = -asyncScrollY * ratio;
 
     // The scroll thumb additionally needs to be translated to compensate for
     // the scale applied above. The origin with respect to which the scale is
@@ -1175,27 +1176,27 @@ ApplyAsyncTransformToScrollbarForContent
       // resolution-cancelling transform which ensures the scroll thumb isn't
       // actually rendered at a larger scale.
       yTranslation *= metrics.GetPresShellResolution();
     }
 
     scrollbarTransform.PostScale(1.f, yScale, 1.f);
     scrollbarTransform.PostTranslate(0, yTranslation, 0);
   }
-  if (aScrollbar->GetScrollbarDirection() == ScrollDirection::HORIZONTAL) {
+  if (thumbData.mDirection == ScrollDirection::HORIZONTAL) {
     // See detailed comments under the VERTICAL case.
 
     const ParentLayerCoord asyncScrollX = asyncTransform._41;
     const float asyncZoomX = asyncTransform._11;
 
     const float xScale = 1.f / asyncZoomX;
 
     const CSSToParentLayerScale effectiveZoom(metrics.GetZoom().xScale * asyncZoomX);
 
-    const float ratio = aScrollbar->GetScrollbarThumbRatio() /
+    const float ratio = thumbData.mThumbRatio /
         (metrics.GetPresShellResolution() * asyncZoomX);
     ParentLayerCoord xTranslation = -asyncScrollX * ratio;
 
     const CSSCoord thumbOrigin = (metrics.GetScrollOffset().x * ratio);
     const CSSCoord thumbOriginScaled = thumbOrigin * xScale;
     const CSSCoord thumbOriginDelta = thumbOriginScaled - thumbOrigin;
     const ParentLayerCoord thumbOriginDeltaPL = thumbOriginDelta * effectiveZoom;
     xTranslation -= thumbOriginDeltaPL;
--- a/gfx/layers/ipc/CompositorBridgeParent.cpp
+++ b/gfx/layers/ipc/CompositorBridgeParent.cpp
@@ -467,16 +467,20 @@ CompositorBridgeParent::StopAndClearReso
   // This must be destroyed now since it accesses the widget.
   if (mCompositorScheduler) {
     mCompositorScheduler->Destroy();
     mCompositorScheduler = nullptr;
   }
 
   // After this point, it is no longer legal to access the widget.
   mWidget = nullptr;
+
+  // Clear mAnimationStorage here to ensure that the compositor thread
+  // still exists when we destroy it.
+  mAnimationStorage = nullptr;
 }
 
 mozilla::ipc::IPCResult
 CompositorBridgeParent::RecvWillClose()
 {
   StopAndClearResources();
   return IPC_OK();
 }
@@ -620,17 +624,16 @@ CompositorBridgeParent::RecvNotifyApprox
 void
 CompositorBridgeParent::ActorDestroy(ActorDestroyReason why)
 {
   StopAndClearResources();
 
   RemoveCompositor(mCompositorID);
 
   mCompositionManager = nullptr;
-  mAnimationStorage = nullptr;
 
   if (mApzcTreeManager) {
     mApzcTreeManager->ClearTree();
     mApzcTreeManager = nullptr;
   }
 
   { // scope lock
     MonitorAutoLock lock(*sIndirectLayerTreesLock);
--- a/gfx/layers/wr/WebRenderScrollData.cpp
+++ b/gfx/layers/wr/WebRenderScrollData.cpp
@@ -38,21 +38,18 @@ WebRenderLayerScrollData::Initialize(Web
   mTransformIsPerspective = aLayer->GetTransformIsPerspective();
   mEventRegions = aLayer->GetEventRegions();
   mReferentId = aLayer->AsRefLayer()
       ? Some(aLayer->AsRefLayer()->GetReferentId())
       : Nothing();
   mEventRegionsOverride = aLayer->AsContainerLayer()
       ? aLayer->AsContainerLayer()->GetEventRegionsOverride()
       : EventRegionsOverride::NoOverride;
-  mScrollbarDirection = aLayer->GetScrollbarDirection();
+  mScrollThumbData = aLayer->GetScrollThumbData();
   mScrollbarTargetContainerId = aLayer->GetScrollbarTargetContainerId();
-  mScrollThumbLength = mScrollbarDirection == ScrollDirection::VERTICAL
-      ? aLayer->GetVisibleRegion().GetBounds().height
-      : aLayer->GetVisibleRegion().GetBounds().width;
   mIsScrollbarContainer = aLayer->IsScrollbarContainer();
   mFixedPosScrollContainerId = aLayer->GetFixedPositionScrollContainerId();
 }
 
 int32_t
 WebRenderLayerScrollData::GetDescendantCount() const
 {
   MOZ_ASSERT(mDescendantCount >= 0); // check that it was set
--- a/gfx/layers/wr/WebRenderScrollData.h
+++ b/gfx/layers/wr/WebRenderScrollData.h
@@ -48,19 +48,18 @@ public:
                                           size_t aIndex) const;
 
   bool IsScrollInfoLayer() const { return mIsScrollInfoLayer; }
   gfx::Matrix4x4 GetTransform() const { return mTransform; }
   bool GetTransformIsPerspective() const { return mTransformIsPerspective; }
   EventRegions GetEventRegions() const { return mEventRegions; }
   Maybe<uint64_t> GetReferentId() const { return mReferentId; }
   EventRegionsOverride GetEventRegionsOverride() const { return mEventRegionsOverride; }
-  ScrollDirection GetScrollbarDirection() const { return mScrollbarDirection; }
+  const ScrollThumbData& GetScrollThumbData() const { return mScrollThumbData; }
   FrameMetrics::ViewID GetScrollbarTargetContainerId() const { return mScrollbarTargetContainerId; }
-  int32_t GetScrollThumbLength() const { return mScrollThumbLength; }
   bool IsScrollbarContainer() const { return mIsScrollbarContainer; }
   FrameMetrics::ViewID GetFixedPositionScrollContainerId() const { return mFixedPosScrollContainerId; }
 
   friend struct IPC::ParamTraits<WebRenderLayerScrollData>;
 
 private:
   // The number of descendants this layer has (not including the layer itself).
   // This is needed to reconstruct the depth-first layer tree traversal
@@ -78,19 +77,18 @@ private:
   // over IPC, and use on the parent side in APZ.
 
   bool mIsScrollInfoLayer;
   gfx::Matrix4x4 mTransform;
   bool mTransformIsPerspective;
   EventRegions mEventRegions;
   Maybe<uint64_t> mReferentId;
   EventRegionsOverride mEventRegionsOverride;
-  ScrollDirection mScrollbarDirection;
+  ScrollThumbData mScrollThumbData;
   FrameMetrics::ViewID mScrollbarTargetContainerId;
-  int32_t mScrollThumbLength;
   bool mIsScrollbarContainer;
   FrameMetrics::ViewID mFixedPosScrollContainerId;
 };
 
 // Data needed by APZ, for the whole layer tree. One instance of this class
 // is created for each transaction sent over PWebRenderBridge. It is populated
 // with information from the WebRender layer tree on the client side and the
 // information is used by APZ on the parent side.
@@ -145,53 +143,62 @@ private:
   bool mIsFirstPaint;
 };
 
 } // namespace layers
 } // namespace mozilla
 
 namespace IPC {
 
+// When ScrollThumbData is stored on the layer tree, it's part of
+// SimpleAttributes which itself uses PlainOldDataSerializer, so
+// we don't need a ParamTraits specialization for ScrollThumbData
+// separately. Here, however, ScrollThumbData is stored as part
+// of WebRenderLayerScrollData whose fields are serialized
+// individually, so we do.
+template<>
+struct ParamTraits<mozilla::layers::ScrollThumbData>
+  : public PlainOldDataSerializer<mozilla::layers::ScrollThumbData>
+{ };
+
 template<>
 struct ParamTraits<mozilla::layers::WebRenderLayerScrollData>
 {
   typedef mozilla::layers::WebRenderLayerScrollData paramType;
 
   static void
   Write(Message* aMsg, const paramType& aParam)
   {
     WriteParam(aMsg, aParam.mDescendantCount);
     WriteParam(aMsg, aParam.mScrollIds);
     WriteParam(aMsg, aParam.mIsScrollInfoLayer);
     WriteParam(aMsg, aParam.mTransform);
     WriteParam(aMsg, aParam.mTransformIsPerspective);
     WriteParam(aMsg, aParam.mEventRegions);
     WriteParam(aMsg, aParam.mReferentId);
     WriteParam(aMsg, aParam.mEventRegionsOverride);
-    WriteParam(aMsg, aParam.mScrollbarDirection);
+    WriteParam(aMsg, aParam.mScrollThumbData);
     WriteParam(aMsg, aParam.mScrollbarTargetContainerId);
-    WriteParam(aMsg, aParam.mScrollThumbLength);
     WriteParam(aMsg, aParam.mIsScrollbarContainer);
     WriteParam(aMsg, aParam.mFixedPosScrollContainerId);
   }
 
   static bool
   Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult)
   {
     return ReadParam(aMsg, aIter, &aResult->mDescendantCount)
         && ReadParam(aMsg, aIter, &aResult->mScrollIds)
         && ReadParam(aMsg, aIter, &aResult->mIsScrollInfoLayer)
         && ReadParam(aMsg, aIter, &aResult->mTransform)
         && ReadParam(aMsg, aIter, &aResult->mTransformIsPerspective)
         && ReadParam(aMsg, aIter, &aResult->mEventRegions)
         && ReadParam(aMsg, aIter, &aResult->mReferentId)
         && ReadParam(aMsg, aIter, &aResult->mEventRegionsOverride)
-        && ReadParam(aMsg, aIter, &aResult->mScrollbarDirection)
+        && ReadParam(aMsg, aIter, &aResult->mScrollThumbData)
         && ReadParam(aMsg, aIter, &aResult->mScrollbarTargetContainerId)
-        && ReadParam(aMsg, aIter, &aResult->mScrollThumbLength)
         && ReadParam(aMsg, aIter, &aResult->mIsScrollbarContainer)
         && ReadParam(aMsg, aIter, &aResult->mFixedPosScrollContainerId);
   }
 };
 
 template<>
 struct ParamTraits<mozilla::layers::WebRenderScrollData>
 {
--- a/gfx/layers/wr/WebRenderScrollDataWrapper.h
+++ b/gfx/layers/wr/WebRenderScrollDataWrapper.h
@@ -274,34 +274,28 @@ public:
   }
 
   EventRegionsOverride GetEventRegionsOverride() const
   {
     MOZ_ASSERT(IsValid());
     return mLayer->GetEventRegionsOverride();
   }
 
-  ScrollDirection GetScrollbarDirection() const
+  const ScrollThumbData& GetScrollThumbData() const
   {
     MOZ_ASSERT(IsValid());
-    return mLayer->GetScrollbarDirection();
+    return mLayer->GetScrollThumbData();
   }
 
   FrameMetrics::ViewID GetScrollbarTargetContainerId() const
   {
     MOZ_ASSERT(IsValid());
     return mLayer->GetScrollbarTargetContainerId();
   }
 
-  int32_t GetScrollThumbLength() const
-  {
-    MOZ_ASSERT(IsValid());
-    return mLayer->GetScrollThumbLength();
-  }
-
   bool IsScrollbarContainer() const
   {
     MOZ_ASSERT(IsValid());
     return mLayer->IsScrollbarContainer();
   }
 
   FrameMetrics::ViewID GetFixedPositionScrollContainerId() const
   {
--- a/gfx/thebes/gfxPlatform.cpp
+++ b/gfx/thebes/gfxPlatform.cpp
@@ -2335,16 +2335,21 @@ gfxPlatform::InitWebRenderConfig()
 
   featureWebRender.DisableByDefault(
       FeatureStatus::OptIn,
       "WebRender is an opt-in feature",
       NS_LITERAL_CSTRING("FEATURE_FAILURE_DEFAULT_OFF"));
 
   if (prefEnabled) {
     featureWebRender.UserEnable("Enabled by pref");
+  } else {
+    const char* env = PR_GetEnv("MOZ_WEBRENDER");
+    if (env && *env == '1') {
+      featureWebRender.UserEnable("Enabled by envvar");
+    }
   }
 
   // WebRender relies on the GPU process when on Windows
 #ifdef XP_WIN
   if (!gfxConfig::IsEnabled(Feature::GPU_PROCESS)) {
     featureWebRender.ForceDisable(
       FeatureStatus::Unavailable,
       "GPU Process is disabled",
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -290,16 +290,17 @@ private:
   DECL_GFX_PREF(Live, "apz.axis_lock.lock_angle",              APZAxisLockAngle, float, float(M_PI / 6.0) /* 30 degrees */);
   DECL_GFX_PREF(Live, "apz.axis_lock.mode",                    APZAxisLockMode, int32_t, 0);
   DECL_GFX_PREF(Live, "apz.content_response_timeout",          APZContentResponseTimeout, int32_t, 400);
   DECL_GFX_PREF(Live, "apz.danger_zone_x",                     APZDangerZoneX, int32_t, 50);
   DECL_GFX_PREF(Live, "apz.danger_zone_y",                     APZDangerZoneY, int32_t, 100);
   DECL_GFX_PREF(Live, "apz.disable_for_scroll_linked_effects", APZDisableForScrollLinkedEffects, bool, false);
   DECL_GFX_PREF(Live, "apz.displayport_expiry_ms",             APZDisplayPortExpiryTime, uint32_t, 15000);
   DECL_GFX_PREF(Live, "apz.drag.enabled",                      APZDragEnabled, bool, false);
+  DECL_GFX_PREF(Live, "apz.drag.initial.enabled",              APZDragInitiationEnabled, bool, false);
   DECL_GFX_PREF(Live, "apz.enlarge_displayport_when_clipped",  APZEnlargeDisplayPortWhenClipped, bool, false);
   DECL_GFX_PREF(Live, "apz.fling_accel_base_mult",             APZFlingAccelBaseMultiplier, float, 1.0f);
   DECL_GFX_PREF(Live, "apz.fling_accel_interval_ms",           APZFlingAccelInterval, int32_t, 500);
   DECL_GFX_PREF(Live, "apz.fling_accel_supplemental_mult",     APZFlingAccelSupplementalMultiplier, float, 1.0f);
   DECL_GFX_PREF(Live, "apz.fling_accel_min_velocity",          APZFlingAccelMinVelocity, float, 1.5f);
   DECL_GFX_PREF(Once, "apz.fling_curve_function_x1",           APZCurveFunctionX1, float, 0.0f);
   DECL_GFX_PREF(Once, "apz.fling_curve_function_x2",           APZCurveFunctionX2, float, 1.0f);
   DECL_GFX_PREF(Once, "apz.fling_curve_function_y1",           APZCurveFunctionY1, float, 0.0f);
--- a/gfx/thebes/gfxQuartzNativeDrawing.cpp
+++ b/gfx/thebes/gfxQuartzNativeDrawing.cpp
@@ -26,49 +26,61 @@ gfxQuartzNativeDrawing::BeginNativeDrawi
   DrawTarget *dt = mDrawTarget;
   if (dt->IsDualDrawTarget() || dt->IsTiledDrawTarget() ||
       dt->GetBackendType() != BackendType::SKIA) {
     // We need a DrawTarget that we can get a CGContextRef from:
     Matrix transform = dt->GetTransform();
 
     mNativeRect = transform.TransformBounds(mNativeRect);
     mNativeRect.RoundOut();
-    // Quartz theme drawing often adjusts drawing rects, so make
-    // sure our surface is big enough for that.
-    mNativeRect.Inflate(5);
     if (mNativeRect.IsEmpty()) {
       return nullptr;
     }
 
     mTempDrawTarget =
       Factory::CreateDrawTarget(BackendType::SKIA,
                                 IntSize::Truncate(mNativeRect.width, mNativeRect.height),
                                 SurfaceFormat::B8G8R8A8);
+    if (!mTempDrawTarget) {
+      return nullptr;
+    }
 
-    if (mTempDrawTarget) {
-        transform.PostTranslate(-mNativeRect.x, -mNativeRect.y);
-        mTempDrawTarget->SetTransform(transform);
-    }
+    transform.PostTranslate(-mNativeRect.x, -mNativeRect.y);
+    mTempDrawTarget->SetTransform(transform);
+
     dt = mTempDrawTarget;
+  } else {
+    // Clip the DT in case BorrowedCGContext needs to create a new layer.
+    // This prevents it from creating a new layer the size of the window.
+    mDrawTarget->PushClipRect(mNativeRect);
   }
-  if (dt) {
-    MOZ_ASSERT(dt->GetBackendType() == BackendType::SKIA);
-    mCGContext = mBorrowedContext.Init(dt);
-    MOZ_ASSERT(mCGContext);
+
+  MOZ_ASSERT(dt->GetBackendType() == BackendType::SKIA);
+  mCGContext = mBorrowedContext.Init(dt);
+
+  if (NS_WARN_IF(!mCGContext)) {
+    // Failed borrowing CG context, so we need to clean up.
+    if (!mTempDrawTarget) {
+      mDrawTarget->PopClip();
+    }
+    return nullptr;
   }
+
   return mCGContext;
 }
 
 void
 gfxQuartzNativeDrawing::EndNativeDrawing()
 {
   NS_ASSERTION(mCGContext, "EndNativeDrawing called without BeginNativeDrawing");
 
   mBorrowedContext.Finish();
   if (mTempDrawTarget) {
     RefPtr<SourceSurface> source = mTempDrawTarget->Snapshot();
 
     AutoRestoreTransform autoRestore(mDrawTarget);
     mDrawTarget->SetTransform(Matrix());
     mDrawTarget->DrawSurface(source, mNativeRect,
                              Rect(0, 0, mNativeRect.width, mNativeRect.height));
+  } else {
+    mDrawTarget->PopClip();
   }
 }
--- a/gfx/ycbcr/ycbcr_to_rgb565.h
+++ b/gfx/ycbcr/ycbcr_to_rgb565.h
@@ -2,17 +2,17 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 #ifndef MEDIA_BASE_YCBCR_TO_RGB565_H_
 #define MEDIA_BASE_YCBCR_TO_RGB565_H_
 #include "yuv_convert.h"
 #include "mozilla/arm.h"
 
 // It's currently only worth including this if we have NEON support.
-#ifdef MOZILLA_MAY_SUPPORT_NEON
+#if defined(__arm__) && defined(MOZILLA_MAY_SUPPORT_NEON)
 #define HAVE_YCBCR_TO_RGB565 1
 #endif
 
 namespace mozilla {
 
 namespace gfx {
 
 #ifdef HAVE_YCBCR_TO_RGB565
--- a/image/decoders/nsGIFDecoder2.cpp
+++ b/image/decoders/nsGIFDecoder2.cpp
@@ -86,19 +86,16 @@ nsGIFDecoder2::nsGIFDecoder2(RasterImage
   , mOldColor(0)
   , mCurrentFrameIndex(-1)
   , mColorTablePos(0)
   , mGIFOpen(false)
   , mSawTransparency(false)
 {
   // Clear out the structure, excluding the arrays.
   memset(&mGIFStruct, 0, sizeof(mGIFStruct));
-
-  // Initialize as "animate once" in case no NETSCAPE2.0 extension is found.
-  mGIFStruct.loop_count = 1;
 }
 
 nsGIFDecoder2::~nsGIFDecoder2()
 {
   free(mGIFStruct.local_colormap);
 }
 
 nsresult
@@ -106,17 +103,17 @@ nsGIFDecoder2::FinishInternal()
 {
   MOZ_ASSERT(!HasError(), "Shouldn't call FinishInternal after error!");
 
   // If the GIF got cut off, handle it anyway
   if (!IsMetadataDecode() && mGIFOpen) {
     if (mCurrentFrameIndex == mGIFStruct.images_decoded) {
       EndImageFrame();
     }
-    PostDecodeDone(mGIFStruct.loop_count - 1);
+    PostDecodeDone(mGIFStruct.loop_count);
     mGIFOpen = false;
   }
 
   return NS_OK;
 }
 
 void
 nsGIFDecoder2::FlushImageData()
@@ -741,16 +738,21 @@ nsGIFDecoder2::ReadNetscapeExtensionData
   static const uint8_t NETSCAPE_LOOPING_EXTENSION_SUB_BLOCK_ID = 1;
   static const uint8_t NETSCAPE_BUFFERING_EXTENSION_SUB_BLOCK_ID = 2;
 
   const uint8_t subBlockID = aData[0] & 7;
   switch (subBlockID) {
     case NETSCAPE_LOOPING_EXTENSION_SUB_BLOCK_ID:
       // This is looping extension.
       mGIFStruct.loop_count = LittleEndian::readUint16(aData + 1);
+      // Zero loop count is infinite animation loop request.
+      if (mGIFStruct.loop_count == 0) {
+        mGIFStruct.loop_count = -1;
+      }
+
       return Transition::To(State::NETSCAPE_EXTENSION_SUB_BLOCK,
                             SUB_BLOCK_HEADER_LEN);
 
     case NETSCAPE_BUFFERING_EXTENSION_SUB_BLOCK_ID:
       // We allow, but ignore, this extension.
       return Transition::To(State::NETSCAPE_EXTENSION_SUB_BLOCK,
                             SUB_BLOCK_HEADER_LEN);
 
--- a/ipc/glue/GeckoChildProcessHost.cpp
+++ b/ipc/glue/GeckoChildProcessHost.cpp
@@ -318,16 +318,88 @@ uint32_t GeckoChildProcessHost::sNextUni
 
 /* static */
 uint32_t
 GeckoChildProcessHost::GetUniqueID()
 {
   return sNextUniqueID++;
 }
 
+#if defined(XP_WIN) && defined(MOZ_SANDBOX)
+
+// This is pretty much a duplicate of the function in PluginProcessParent.
+// Simply copying for now due to uplift. I will address this duplication and for
+// example the similar code in the Constructor in bug 1339105.
+static void
+AddSandboxAllowedFile(std::vector<std::wstring>& aAllowedFiles,
+                      nsIProperties* aDirSvc, const char* aDirKey,
+                      const nsAString& aSuffix = EmptyString())
+{
+  nsCOMPtr<nsIFile> ruleDir;
+  nsresult rv =
+    aDirSvc->Get(aDirKey, NS_GET_IID(nsIFile), getter_AddRefs(ruleDir));
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return;
+  }
+
+  nsAutoString rulePath;
+  rv = ruleDir->GetPath(rulePath);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return;
+  }
+
+  // Convert network share path to format for sandbox policy.
+  if (Substring(rulePath, 0, 2).Equals(L"\\\\")) {
+    rulePath.InsertLiteral(u"??\\UNC", 1);
+  }
+
+  if (!aSuffix.IsEmpty()) {
+    rulePath.Append(aSuffix);
+  }
+
+  aAllowedFiles.push_back(std::wstring(rulePath.get()));
+  return;
+}
+
+static void
+AddContentSandboxAllowedFiles(int32_t aSandboxLevel,
+                              std::vector<std::wstring>& aAllowedFilesRead,
+                              std::vector<std::wstring>& aAllowedFilesReadWrite)
+{
+  if (aSandboxLevel < 1) {
+    return;
+  }
+
+  nsresult rv;
+  nsCOMPtr<nsIProperties> dirSvc =
+    do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return;
+  }
+
+  // Add rule to allow read / write access to content temp dir. If for some
+  // reason the addition of the content temp failed, this will give write access
+  // to the normal TEMP dir. However such failures should be pretty rare and
+  // without this printing will not currently work.
+  AddSandboxAllowedFile(aAllowedFilesReadWrite, dirSvc,
+                        NS_APP_CONTENT_PROCESS_TEMP_DIR,
+                        NS_LITERAL_STRING("\\*"));
+
+  if (aSandboxLevel < 2) {
+    return;
+  }
+
+  // Add rule to allow read access to installation directory. At less than
+  // level 2 we already add a global read rule.
+  AddSandboxAllowedFile(aAllowedFilesRead, dirSvc, NS_GRE_DIR,
+                        NS_LITERAL_STRING("\\*"));
+}
+
+#endif
+
 void
 GeckoChildProcessHost::PrepareLaunch()
 {
 #ifdef MOZ_CRASHREPORTER
   if (CrashReporter::GetEnabled()) {
     CrashReporter::OOPInit();
   }
 #endif
@@ -338,16 +410,21 @@ GeckoChildProcessHost::PrepareLaunch()
   }
 
 #if defined(MOZ_CONTENT_SANDBOX)
   // We need to get the pref here as the process is launched off main thread.
   if (mProcessType == GeckoProcessType_Content) {
     mSandboxLevel = Preferences::GetInt("security.sandbox.content.level");
     mEnableSandboxLogging =
       Preferences::GetBool("security.sandbox.logging.enabled");
+
+    // This calls the directory service, which can also cause issues if called
+    // off main thread.
+    AddContentSandboxAllowedFiles(mSandboxLevel, mAllowedFilesRead,
+                                  mAllowedFilesReadWrite);
   }
 #endif
 
 #if defined(MOZ_SANDBOX)
   // For other process types we can't rely on them being launched on main
   // thread and they may not have access to prefs in the child process, so allow
   // them to turn on logging via an environment variable.
   mEnableSandboxLogging = mEnableSandboxLogging
@@ -658,60 +735,16 @@ AddAppDirToCommandLine(std::vector<std::
         aCmdLine.push_back("-profile");
         aCmdLine.push_back(path.get());
       }
 #endif
     }
   }
 }
 
-#if defined(XP_WIN) && defined(MOZ_SANDBOX)
-
-static void
-AddContentSandboxAllowedFiles(int32_t aSandboxLevel,
-                              std::vector<std::wstring>& aAllowedFilesRead)
-{
-  if (aSandboxLevel < 1) {
-    return;
-  }
-
-  nsCOMPtr<nsIFile> binDir;
-  nsresult rv = NS_GetSpecialDirectory(NS_GRE_DIR, getter_AddRefs(binDir));
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return;
-  }
-
-  nsAutoString binDirPath;
-  rv = binDir->GetPath(binDirPath);
-  if (NS_WARN_IF(NS_FAILED(rv))) {
-    return;
-  }
-
-  // If bin directory is on a remote drive add read access.
-  wchar_t volPath[MAX_PATH];
-  if (!::GetVolumePathNameW(binDirPath.get(), volPath, MAX_PATH)) {
-    return;
-  }
-
-  if (::GetDriveTypeW(volPath) != DRIVE_REMOTE) {
-    return;
-  }
-
-  // Convert network share path to format for sandbox policy.
-  if (Substring(binDirPath, 0, 2).Equals(L"\\\\")) {
-    binDirPath.InsertLiteral(u"??\\UNC", 1);
-  }
-
-  binDirPath.AppendLiteral(u"\\*");
-
-  aAllowedFilesRead.push_back(std::wstring(binDirPath.get()));
-}
-
-#endif
-
 bool
 GeckoChildProcessHost::PerformAsyncLaunchInternal(std::vector<std::string>& aExtraOpts, base::ProcessArchitecture arch)
 {
   // We rely on the fact that InitializeChannel() has already been processed
   // on the IO thread before this point is reached.
   if (!GetChannel()) {
     return false;
   }
@@ -1043,17 +1076,16 @@ GeckoChildProcessHost::PerformAsyncLaunc
       if (mSandboxLevel > 0 &&
           !PR_GetEnv("MOZ_DISABLE_CONTENT_SANDBOX")) {
         // For now we treat every failure as fatal in SetSecurityLevelForContentProcess
         // and just crash there right away. Should this change in the future then we
         // should also handle the error here.
         mSandboxBroker.SetSecurityLevelForContentProcess(mSandboxLevel,
                                                          mPrivileges);
         shouldSandboxCurrentProcess = true;
-        AddContentSandboxAllowedFiles(mSandboxLevel, mAllowedFilesRead);
       }
 #endif // MOZ_CONTENT_SANDBOX
       break;
     case GeckoProcessType_Plugin:
       if (mSandboxLevel > 0 &&
           !PR_GetEnv("MOZ_DISABLE_NPAPI_SANDBOX")) {
         bool ok = mSandboxBroker.SetSecurityLevelForPluginProcess(mSandboxLevel);
         if (!ok) {
--- a/ipc/glue/IPCMessageUtils.h
+++ b/ipc/glue/IPCMessageUtils.h
@@ -225,16 +225,40 @@ struct BitFlagsEnumSerializer
 
 template <>
 struct ParamTraits<base::ChildPrivileges>
   : public ContiguousEnumSerializer<base::ChildPrivileges,
                                     base::PRIVILEGES_DEFAULT,
                                     base::PRIVILEGES_LAST>
 { };
 
+/**
+ * A helper class for serializing plain-old data (POD) structures.
+ * The memory representation of the structure is written to and read from
+ * the serialized stream directly, without individual processing of the
+ * structure's members.
+ *
+ * Derive ParamTraits<T> from PlainOldDataSerializer<T> if T is POD.
+ */
+template <typename T>
+struct PlainOldDataSerializer
+{
+  // TODO: Once the mozilla::IsPod trait is in good enough shape (bug 900042),
+  //       static_assert that mozilla::IsPod<T>::value is true.
+  typedef T paramType;
+
+  static void Write(Message* aMsg, const paramType& aParam) {
+    aMsg->WriteBytes(&aParam, sizeof(aParam));
+  }
+
+  static bool Read(const Message* aMsg, PickleIterator* aIter, paramType* aResult) {
+    return aMsg->ReadBytesInto(aIter, aResult, sizeof(paramType));
+  }
+};
+
 template<>
 struct ParamTraits<int8_t>
 {
   typedef int8_t paramType;
 
   static void Write(Message* aMsg, const paramType& aParam)
   {
     aMsg->WriteBytes(&aParam, sizeof(aParam));
--- a/ipc/glue/IPCStreamDestination.cpp
+++ b/ipc/glue/IPCStreamDestination.cpp
@@ -139,16 +139,22 @@ public:
   Init(nsIInputStream* aStream, uint32_t aBufferSize) override
   {
     MaybeStartReading();
     nsCOMPtr<nsIBufferedInputStream> stream = do_QueryInterface(mStream);
     MOZ_ASSERT(stream);
     return stream->Init(aStream, aBufferSize);
   }
 
+  NS_IMETHODIMP
+  GetData(nsIInputStream **aResult) override
+  {
+    return NS_ERROR_NOT_IMPLEMENTED;
+  }
+
   void
   MaybeStartReading();
 
   void
   MaybeCloseDestination();
 
 private:
   ~DelayedStartInputStream() = default;
--- a/js/src/devtools/rootAnalysis/analyzeHeapWrites.js
+++ b/js/src/devtools/rootAnalysis/analyzeHeapWrites.js
@@ -161,21 +161,24 @@ function treatAsSafeArgument(entry, varN
         ["Gecko_GetAtomAsUTF16", "aLength", null],
         ["Gecko_CopyMozBindingFrom", "aDest", null],
         ["Gecko_SetNullImageValue", "aImage", null],
         ["Gecko_SetGradientImageValue", "aImage", null],
         ["Gecko_SetImageOrientation", "aVisibility", null],
         ["Gecko_SetImageOrientationAsFromImage", "aVisibility", null],
         ["Gecko_CopyImageOrientationFrom", "aDst", null],
         ["Gecko_SetImageElement", "aImage", null],
+        ["Gecko_SetLayerImageImageValue", "aImage", null],
         ["Gecko_SetUrlImageValue", "aImage", null],
         ["Gecko_CopyImageValueFrom", "aImage", null],
         ["Gecko_SetCursorArrayLength", "aStyleUI", null],
         ["Gecko_CopyCursorArrayFrom", "aDest", null],
+        ["Gecko_SetCursorImageValue", "aCursor", null],
         ["Gecko_SetCursorImage", "aCursor", null],
+        ["Gecko_SetListStyleImageImageValue", "aList", null],
         ["Gecko_SetListStyleImageNone", "aList", null],
         ["Gecko_SetListStyleImage", "aList", null],
         ["Gecko_CopyListStyleImageFrom", "aList", null],
         ["Gecko_ClearStyleContents", "aContent", null],
         ["Gecko_CopyStyleContentsFrom", "aContent", null],
         ["Gecko_CopyStyleGridTemplateValues", "aGridTemplate", null],
         ["Gecko_ResetStyleCoord", null, null],
         ["Gecko_CopyClipPathValueFrom", "aDst", null],
@@ -185,19 +188,22 @@ function treatAsSafeArgument(entry, varN
         [/Gecko_CSSValue_Set/, "aCSSValue", null],
         ["Gecko_CSSValue_Drop", "aCSSValue", null],
         ["Gecko_CSSFontFaceRule_GetCssText", "aResult", null],
         ["Gecko_EnsureTArrayCapacity", "aArray", null],
         ["Gecko_ClearPODTArray", "aArray", null],
         ["Gecko_ClearAndResizeStyleContents", "aContent", null],
         [/Gecko_ClearAndResizeCounter/, "aContent", null],
         [/Gecko_CopyCounter.*?From/, "aContent", null],
+        [/Gecko_SetContentDataImageValue/, "aList", null],
         [/Gecko_SetContentData/, "aContent", null],
         [/Gecko_EnsureStyle.*?ArrayLength/, "aArray", null],
-        ["Gecko_AnimationAppendKeyframe", "aKeyframes", null],
+        ["Gecko_GetOrCreateKeyframeAtStart", "aKeyframes", null],
+        ["Gecko_GetOrCreateInitialKeyframe", "aKeyframes", null],
+        ["Gecko_GetOrCreateFinalKeyframe", "aKeyframes", null],
         ["Gecko_SetStyleCoordCalcValue", null, null],
         ["Gecko_StyleClipPath_SetURLValue", "aClip", null],
         ["Gecko_nsStyleFilter_SetURLValue", "aEffects", null],
         ["Gecko_nsStyleSVGPaint_CopyFrom", "aDest", null],
         ["Gecko_nsStyleSVGPaint_SetURLValue", "aPaint", null],
         ["Gecko_nsStyleSVGPaint_Reset", "aPaint", null],
         ["Gecko_nsStyleSVG_SetDashArrayLength", "aSvg", null],
         ["Gecko_nsStyleSVG_CopyDashArray", "aDst", null],
@@ -368,29 +374,32 @@ function ignoreContents(entry)
         // to do but not understood by the analysis yet.
         / EmptyString\(\)/,
         /nsCSSProps::LookupPropertyValue/,
         /nsCSSProps::ValueToKeyword/,
         /nsCSSKeywords::GetStringValue/,
 
         // The analysis can't cope with the indirection used for the objects
         // being initialized here.
-        "Gecko_AnimationAppendKeyframe",
+        "Gecko_GetOrCreateKeyframeAtStart",
+        "Gecko_GetOrCreateInitialKeyframe",
+        "Gecko_GetOrCreateFinalKeyframe",
         "Gecko_NewStyleQuoteValues",
         "Gecko_NewCSSValueSharedList",
         "Gecko_NewGridTemplateAreasValue",
         /nsCSSValue::SetCalcValue/,
         /CSSValueSerializeCalcOps::Append/,
         "Gecko_CSSValue_SetFunction",
         "Gecko_CSSValue_SetArray",
         "Gecko_EnsureMozBorderColors",
         "Gecko_ClearMozBorderColors",
         "Gecko_AppendMozBorderColors",
         "Gecko_CopyMozBorderColors",
         "Gecko_SetJemallocThreadLocalArena",
+        "Gecko_SetNullImageValue",
 
         // Needs main thread assertions or other fixes.
         /UndisplayedMap::GetEntryFor/,
         /nsStyleContext::CalcStyleDifferenceInternal/,
         /EffectCompositor::GetServoAnimationRule/,
         /LookAndFeel::GetColor/,
         "Gecko_CopyStyleContentsFrom",
         "Gecko_CSSValue_SetAbsoluteLength",
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/SIMD/anyall.js
@@ -0,0 +1,38 @@
+load(libdir + 'simd.js');
+
+setJitCompilerOption("ion.warmup.trigger", 50);
+
+function all(B, n) {
+    var a = B.splat(true);
+    for (var i = 0; i < n; i++) {
+        var b = B.replaceLane(a, i, false);
+        assertEq(B.allTrue(b), false);
+        var c = B.replaceLane(b, i, true);
+        assertEq(B.allTrue(c), true);
+    }
+}
+
+function any(B, n) {
+    var a = B.splat(false);
+    for (var i = 0; i < n; i++) {
+        var b = B.replaceLane(a, i, true);
+        assertEq(B.anyTrue(b), true);
+        var c = B.replaceLane(b, i, false);
+        assertEq(B.anyTrue(c), false);
+    }
+}
+
+function f() {
+    for (var j = 0; j < 200; j++) {
+        all(SIMD.Bool64x2, 2)
+        any(SIMD.Bool64x2, 2)
+        all(SIMD.Bool32x4, 4)
+        any(SIMD.Bool32x4, 4)
+        all(SIMD.Bool16x8, 8)
+        any(SIMD.Bool16x8, 8)
+        all(SIMD.Bool8x16, 16)
+        any(SIMD.Bool8x16, 16)
+    }
+}
+
+f()
--- a/js/src/jit/BaselineCompiler.cpp
+++ b/js/src/jit/BaselineCompiler.cpp
@@ -794,27 +794,25 @@ bool
 BaselineCompiler::emitArgumentTypeChecks()
 {
     if (!function())
         return true;
 
     frame.pushThis();
     frame.popRegsAndSync(1);
 
-    ICTypeMonitor_Fallback::Compiler compiler(cx, ICStubCompiler::Engine::Baseline,
-                                              (uint32_t) 0);
+    ICTypeMonitor_Fallback::Compiler compiler(cx, uint32_t(0));
     if (!emitNonOpIC(compiler.getStub(&stubSpace_)))
         return false;
 
     for (size_t i = 0; i < function()->nargs(); i++) {
         frame.pushArg(i);
         frame.popRegsAndSync(1);
 
-        ICTypeMonitor_Fallback::Compiler compiler(cx, ICStubCompiler::Engine::Baseline,
-                                                  i + 1);
+        ICTypeMonitor_Fallback::Compiler compiler(cx, i + 1);
         if (!emitNonOpIC(compiler.getStub(&stubSpace_)))
             return false;
     }
 
     return true;
 }
 
 bool
@@ -2609,18 +2607,17 @@ BaselineCompiler::emit_JSOP_GETALIASEDVA
 {
     frame.syncStack(0);
 
     Address address = getEnvironmentCoordinateAddress(R0.scratchReg());
     masm.loadValue(address, R0);
 
     if (ionCompileable_) {
         // No need to monitor types if we know Ion can't compile this script.
-        ICTypeMonitor_Fallback::Compiler compiler(cx, ICStubCompiler::Engine::Baseline,
-                                                  (ICMonitoredFallbackStub*) nullptr);
+        ICTypeMonitor_Fallback::Compiler compiler(cx, nullptr);
         if (!emitOpIC(compiler.getStub(&stubSpace_)))
             return false;
     }
 
     frame.push(R0);
     return true;
 }
 
@@ -2759,18 +2756,17 @@ BaselineCompiler::emit_JSOP_GETIMPORT()
     // Imports are initialized by this point except in rare circumstances, so
     // don't emit a check unless we have to.
     if (targetEnv->getSlot(shape->slot()).isMagic(JS_UNINITIALIZED_LEXICAL))
         if (!emitUninitializedLexicalCheck(R0))
             return false;
 
     if (ionCompileable_) {
         // No need to monitor types if we know Ion can't compile this script.
-        ICTypeMonitor_Fallback::Compiler compiler(cx, ICStubCompiler::Engine::Baseline,
-                                                  (ICMonitoredFallbackStub*) nullptr);
+        ICTypeMonitor_Fallback::Compiler compiler(cx, nullptr);
         if (!emitOpIC(compiler.getStub(&stubSpace_)))
             return false;
     }
 
     frame.push(R0);
     return true;
 }
 
--- a/js/src/jit/BaselineIC.cpp
+++ b/js/src/jit/BaselineIC.cpp
@@ -773,18 +773,16 @@ TypedThingRequiresFloatingPoint(JSObject
            type == Scalar::Float32 ||
            type == Scalar::Float64;
 }
 
 static bool
 DoGetElemFallback(JSContext* cx, BaselineFrame* frame, ICGetElem_Fallback* stub_, HandleValue lhs,
                   HandleValue rhs, MutableHandleValue res)
 {
-    SharedStubInfo info(cx, frame, stub_->icEntry());
-
     // This fallback stub may trigger debug mode toggling.
     DebugModeOSRVolatileStub<ICGetElem_Fallback*> stub(frame, stub_);
 
     RootedScript script(cx, frame->script());
     jsbytecode* pc = stub->icEntry()->pc(frame->script());
     JSOp op = JSOp(*pc);
     FallbackICSpew(cx, stub, "GetElem(%s)", CodeName[op]);
 
@@ -809,18 +807,17 @@ DoGetElemFallback(JSContext* cx, Baselin
         stub->discardStubs(cx);
 
     if (stub->state().canAttachStub()) {
         ICStubEngine engine = ICStubEngine::Baseline;
         GetPropIRGenerator gen(cx, script, pc, CacheKind::GetElem, stub->state().mode(),
                                &isTemporarilyUnoptimizable, lhs, rhs, CanAttachGetter::Yes);
         if (gen.tryAttachStub()) {
             ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
-                                                        engine, info.outerScript(cx), stub,
-                                                        &attached);
+                                                        engine, script, stub, &attached);
             if (newStub) {
                 JitSpew(JitSpew_BaselineIC, "  Attached CacheIR stub");
                 if (gen.shouldNotePreliminaryObjectStub())
                     newStub->toCacheIR_Monitored()->notePreliminaryObject();
                 else if (gen.shouldUnlinkPreliminaryObjectStubs())
                     StripPreliminaryObjectStubs(cx, stub);
             }
         }
@@ -834,20 +831,18 @@ DoGetElemFallback(JSContext* cx, Baselin
         TypeScript::Monitor(cx, frame->script(), pc, res);
     }
 
     // Check if debug mode toggling made the stub invalid.
     if (stub.invalid())
         return true;
 
     // Add a type monitor stub for the resulting value.
-    if (!stub->addMonitorStubForValue(cx, &info, res))
-    {
+    if (!stub->addMonitorStubForValue(cx, frame, res))
         return false;
-    }
 
     if (attached)
         return true;
 
     // GetElem operations which could access negative indexes generally can't
     // be optimized without the potential for bailouts, as we can't statically
     // determine that an object has no properties on such indexes.
     if (rhs.isNumber() && rhs.toNumber() < 0)
@@ -1333,18 +1328,16 @@ ICHasOwn_Fallback::Compiler::generateStu
 //
 // GetName_Fallback
 //
 
 static bool
 DoGetNameFallback(JSContext* cx, BaselineFrame* frame, ICGetName_Fallback* stub_,
                   HandleObject envChain, MutableHandleValue res)
 {
-    SharedStubInfo info(cx, frame, stub_->icEntry());
-
     // This fallback stub may trigger debug mode toggling.
     DebugModeOSRVolatileStub<ICGetName_Fallback*> stub(frame, stub_);
 
     RootedScript script(cx, frame->script());
     jsbytecode* pc = stub->icEntry()->pc(script);
     mozilla::DebugOnly<JSOp> op = JSOp(*pc);
     FallbackICSpew(cx, stub, "GetName(%s)", CodeName[JSOp(*pc)]);
 
@@ -1356,18 +1349,17 @@ DoGetNameFallback(JSContext* cx, Baselin
     if (stub->state().maybeTransition())
         stub->discardStubs(cx);
 
     if (stub->state().canAttachStub()) {
         ICStubEngine engine = ICStubEngine::Baseline;
         GetNameIRGenerator gen(cx, script, pc, stub->state().mode(), envChain, name);
         if (gen.tryAttachStub()) {
             ICStub* newStub = AttachBaselineCacheIRStub(cx, gen.writerRef(), gen.cacheKind(),
-                                                        engine, info.outerScript(cx), stub,
-                                                        &attached);
+                                                        engine, script, stub, &attached);
             if (newStub)
                 JitSpew(JitSpew_BaselineIC, "  Attached CacheIR stub");
         }
         if (!attached)
             stub->state().trackNotAttached();
     }
 
     static_assert(JSOP_GETGNAME_LENGTH == JSOP_GETNAME_LENGTH,
@@ -1382,17 +1374,17 @@ DoGetNameFallback(JSContext* cx, Baselin
 
     TypeScript::Monitor(cx, script, pc, res);
 
     // Check if debug mode toggling made the stub invalid.
     if (stub.invalid())
         return true;
 
     // Add a type monitor stub for the resulting value.
-    if (!stub->addMonitorStubForValue(cx, &info, res))
+    if (!stub->addMonitorStubForValue(cx, frame, res))
         return false;
 
     if (!attached)
         stub->noteUnoptimizableAccess();
     return true;
 }
 
 typedef bool (*DoGetNameFallbackFn)(JSContext*, BaselineFrame*, ICGetName_Fallback*,
@@ -2388,18 +2380,16 @@ TryAttachStringSplit(JSContext* cx, ICCa
     *attached = true;
     return true;
 }
 
 static bool
 DoCallFallback(JSContext* cx, BaselineFrame* frame, ICCall_Fallback* stub_, uint32_t argc,
                Value* vp, MutableHandleValue res)
 {
-    SharedStubInfo info(cx, frame, stub_->icEntry());
-
     // This fallback stub may trigger debug mode toggling.
     DebugModeOSRVolatileStub<ICCall_Fallback*> stub(frame, stub_);
 
     RootedScript script(cx, frame->script());
     jsbytecode* pc = stub->icEntry()->pc(script);
     JSOp op = JSOp(*pc);
     FallbackICSpew(cx, stub, "Call(%s)", CodeName[op]);
 
@@ -2461,25 +2451,18 @@ DoCallFallback(JSContext* cx, BaselineFr
     }
 
     TypeScript::Monitor(cx, script, pc, res);
 
     // Check if debug mode toggling made the stub invalid.
     if (stub.invalid())
         return true;
 
-    // Attach a new TypeMonitor stub for this value.
-    ICTypeMonitor_Fallback* typeMonFbStub = stub->fallbackMonitorStub();
-    if (!typeMonFbStub->addMonitorStubForValue(cx, &info, res))
-    {
-        return false;
-    }
-
     // Add a type monitor stub for the resulting value.
-    if (!stub->addMonitorStubForValue(cx, &info, res))
+    if (!stub->addMonitorStubForValue(cx, frame, res))
         return false;
 
     // If 'callee' is a potential Call_StringSplit, try to attach an
     // optimized StringSplit stub. Note that vp[0] now holds the return value
     // instead of the callee, so we pass the callee as well.
     if (!TryAttachStringSplit(cx, stub, script, argc, callee, vp, pc, res, &handled))
         return false;
 
@@ -2487,18 +2470,16 @@ DoCallFallback(JSContext* cx, BaselineFr
         stub->noteUnoptimizableCall();
     return true;
 }
 
 static bool
 DoSpreadCallFallback(JSContext* cx, BaselineFrame* frame, ICCall_Fallback* stub_, Value* vp,
                      MutableHandleValue res)
 {
-    SharedStubInfo info(cx, frame, stub_->icEntry());
-
     // This fallback stub may trigger debug mode toggling.
     DebugModeOSRVolatileStub<ICCall_Fallback*> stub(frame, stub_);
 
     RootedScript script(cx, frame->script());
     jsbytecode* pc = stub->icEntry()->pc(script);
     JSOp op = JSOp(*pc);
     bool constructing = (op == JSOP_SPREADNEW);
     FallbackICSpew(cx, stub, "SpreadCall(%s)", CodeName[op]);
@@ -2522,25 +2503,18 @@ DoSpreadCallFallback(JSContext* cx, Base
 
     if (!SpreadCallOperation(cx, script, pc, thisv, callee, arr, newTarget, res))
         return false;
 
     // Check if debug mode toggling made the stub invalid.
     if (stub.invalid())
         return true;
 
-    // Attach a new TypeMonitor stub for this value.
-    ICTypeMonitor_Fallback* typeMonFbStub = stub->fallbackMonitorStub();
-    if (!typeMonFbStub->addMonitorStubForValue(cx, &info, res))
-    {
-        return false;
-    }
-
     // Add a type monitor stub for the resulting value.
-    if (!stub->addMonitorStubForValue(cx, &info, res))
+    if (!stub->addMonitorStubForValue(cx, frame, res))
         return false;
 
     if (!handled)
         stub->noteUnoptimizableCall();
     return true;
 }
 
 void
--- a/js/src/jit/BaselineIC.h
+++ b/js/src/jit/BaselineIC.h
@@ -98,17 +98,17 @@ class ICTypeUpdate_PrimitiveSet : public
   public:
     class Compiler : public TypeCheckPrimitiveSetStub::Compiler {
       protected:
         MOZ_MUST_USE bool generateStubCode(MacroAssembler& masm);
 
       public:
         Compiler(JSContext* cx, ICTypeUpdate_PrimitiveSet* existingStub, JSValueType type)
           : TypeCheckPrimitiveSetStub::Compiler(cx, TypeUpdate_PrimitiveSet,
-                                                Engine::Baseline, existingStub, type)
+                                                existingStub, type)
         {}
 
         ICTypeUpdate_PrimitiveSet* updateStub() {
             TypeCheckPrimitiveSetStub* stub =
                 this->TypeCheckPrimitiveSetStub::Compiler::updateStub();
             if (!stub)
                 return nullptr;
             return stub->toUpdateStub();
@@ -397,17 +397,17 @@ class ICGetElem_Fallback : public ICMoni
         explicit Compiler(JSContext* cx)
           : ICStubCompiler(cx, ICStub::GetElem_Fallback, Engine::Baseline)
         { }
 
         ICStub* getStub(ICStubSpace* space) {
             ICGetElem_Fallback* stub = newStub<ICGetElem_Fallback>(space, getStubCode());
             if (!stub)
                 return nullptr;
-            if (!stub->initMonitoringChain(cx, space, engine_))
+            if (!stub->initMonitoringChain(cx, space))
                 return nullptr;
             return stub;
         }
     };
 };
 
 // SetElem
 //      JSOP_SETELEM
@@ -526,17 +526,17 @@ class ICGetName_Fallback : public ICMoni
 
       public:
         explicit Compiler(JSContext* cx)
           : ICStubCompiler(cx, ICStub::GetName_Fallback, Engine::Baseline)
         { }
 
         ICStub* getStub(ICStubSpace* space) {
             ICGetName_Fallback* stub = newStub<ICGetName_Fallback>(space, getStubCode());
-            if (!stub || !stub->initMonitoringChain(cx, space, engine_))
+            if (!stub || !stub->initMonitoringChain(cx, space))
                 return nullptr;
             return stub;
         }
     };
 };
 
 // BindName
 //      JSOP_BINDNAME
@@ -582,17 +582,17 @@ class ICGetIntrinsic_Fallback : public I
       public:
         explicit Compiler(JSContext* cx)
           : ICStubCompiler(cx, ICStub::GetIntrinsic_Fallback, Engine::Baseline)
         { }
 
         ICStub* getStub(ICStubSpace* space) {
             ICGetIntrinsic_Fallback* stub =
                 newStub<ICGetIntrinsic_Fallback>(space, getStubCode());
-            if (!stub || !stub->initMonitoringChain(cx, space, engine_))
+            if (!stub || !stub->initMonitoringChain(cx, space))
                 return nullptr;
             return stub;
         }
     };
 };
 
 // Stub that loads the constant result of a GETINTRINSIC operation.
 class ICGetIntrinsic_Constant : public ICStub
@@ -763,17 +763,17 @@ class ICCall_Fallback : public ICMonitor
         Compiler(JSContext* cx, bool isConstructing, bool isSpread)
           : ICCallStubCompiler(cx, ICStub::Call_Fallback),
             isConstructing_(isConstructing),
             isSpread_(isSpread)
         { }
 
         ICStub* getStub(ICStubSpace* space) {
             ICCall_Fallback* stub = newStub<ICCall_Fallback>(space, getStubCode());
-            if (!stub || !stub->initMonitoringChain(cx, space, engine_))
+            if (!stub || !stub->initMonitoringChain(cx, space))
                 return nullptr;
             return stub;
         }
     };
 };
 
 class ICCall_Scripted : public ICMonitoredStub
 {
--- a/js/src/jit/MoveResolver.cpp
+++ b/js/src/jit/MoveResolver.cpp
@@ -101,22 +101,121 @@ MoveResolver::findCycledMove(PendingMove
             (*iter)++;
             return other;
         }
     }
     // No blocking moves found.
     return nullptr;
 }
 
+#ifdef JS_CODEGEN_ARM
+static inline bool
+MoveIsDouble(const MoveOperand& move)
+{
+    if (!move.isFloatReg())
+        return false;
+    return move.floatReg().isDouble();
+}
+#endif
+
+#ifdef JS_CODEGEN_ARM
+static inline bool
+MoveIsSingle(const MoveOperand& move)
+{
+    if (!move.isFloatReg())
+        return false;
+    return move.floatReg().isSingle();
+}
+#endif
+
+#ifdef JS_CODEGEN_ARM
+bool
+MoveResolver::isDoubleAliasedAsSingle(const MoveOperand& move)
+{
+    if (!MoveIsDouble(move))
+        return false;
+
+    for (auto iter = pending_.begin(); iter != pending_.end(); ++iter) {
+        PendingMove* other = *iter;
+        if (other->from().aliases(move) && MoveIsSingle(other->from()))
+            return true;
+        if (other->to().aliases(move) && MoveIsSingle(other->to()))
+            return true;
+    }
+    return false;
+}
+#endif
+
+#ifdef JS_CODEGEN_ARM
+static MoveOperand
+SplitIntoLowerHalf(const MoveOperand& move)
+{
+    if (MoveIsDouble(move)) {
+        FloatRegister lowerSingle = move.floatReg().asSingle();
+        return MoveOperand(lowerSingle);
+    }
+
+    MOZ_ASSERT(move.isMemoryOrEffectiveAddress());
+    return move;
+}
+#endif
+
+#ifdef JS_CODEGEN_ARM
+static MoveOperand
+SplitIntoUpperHalf(const MoveOperand& move)
+{
+    if (MoveIsDouble(move)) {
+        FloatRegister lowerSingle = move.floatReg().asSingle();
+        FloatRegister upperSingle = VFPRegister(lowerSingle.code() + 1, VFPRegister::Single);
+        return MoveOperand(upperSingle);
+    }
+
+    MOZ_ASSERT(move.isMemoryOrEffectiveAddress());
+    return MoveOperand(move.base(), move.disp() + sizeof(float));
+}
+#endif
+
 bool
 MoveResolver::resolve()
 {
     resetState();
     orderedMoves_.clear();
 
+#ifdef JS_CODEGEN_ARM
+    // Some of ARM's double registers alias two of its single registers,
+    // but the algorithm below assumes that every register can participate
+    // in at most one cycle. To satisfy the algorithm, any double registers
+    // that may conflict are split into their single-register halves.
+    //
+    // This logic is only applicable because ARM only uses registers d0-d15,
+    // all of which alias s0-s31. Double registers d16-d31 are unused.
+    // Therefore there is never a double move that cannot be split.
+    // If this changes in the future, the algorithm will have to be fixed.
+    for (auto iter = pending_.begin(); iter != pending_.end(); ++iter) {
+        PendingMove* pm = *iter;
+
+        if (isDoubleAliasedAsSingle(pm->from()) || isDoubleAliasedAsSingle(pm->to())) {
+            PendingMove* lower = movePool_.allocate();
+            if (!lower)
+                return false;
+
+            // Insert the new node before the current position to not affect iteration.
+            MoveOperand fromLower = SplitIntoLowerHalf(pm->from());
+            MoveOperand toLower = SplitIntoLowerHalf(pm->to());
+            new (lower) PendingMove(fromLower, toLower, MoveOp::FLOAT32);
+            pending_.insertBefore(pm, lower);
+
+            // Overwrite pm in place for the upper move. Iteration proceeds as normal.
+            MoveOperand fromUpper = SplitIntoUpperHalf(pm->from());
+            MoveOperand toUpper = SplitIntoUpperHalf(pm->to());
+            pm->overwrite(fromUpper, toUpper, MoveOp::FLOAT32);
+        }
+    }
+#endif
+
     InlineList<PendingMove> stack;
 
     // This is a depth-first-search without recursion, which tries to find
     // cycles in a list of moves.
     //
     // Algorithm.
     //
     // S = Traversal stack.
--- a/js/src/jit/MoveResolver.h
+++ b/js/src/jit/MoveResolver.h
@@ -247,16 +247,23 @@ class MoveOp
         return endCycleType_;
     }
     bool aliases(const MoveOperand& op) const {
         return from().aliases(op) || to().aliases(op);
     }
     bool aliases(const MoveOp& other) const {
         return aliases(other.from()) || aliases(other.to());
     }
+#ifdef JS_CODEGEN_ARM
+    void overwrite(MoveOperand& from, MoveOperand& to, Type type) {
+        from_ = from;
+        to_ = to;
+        type_ = type;
+    }
+#endif
 };
 
 class MoveResolver
 {
   private:
     struct PendingMove
       : public MoveOp,
         public TempObject,
@@ -293,16 +300,20 @@ class MoveResolver
     PendingMove* findBlockingMove(const PendingMove* last);
     PendingMove* findCycledMove(PendingMoveIterator* stack, PendingMoveIterator end, const PendingMove* first);
     MOZ_MUST_USE bool addOrderedMove(const MoveOp& move);
     void reorderMove(size_t from, size_t to);
 
     // Internal reset function. Does not clear lists.
     void resetState();
 
+#ifdef JS_CODEGEN_ARM
+    bool isDoubleAliasedAsSingle(const MoveOperand& move);
+#endif
+
   public:
     MoveResolver();
 
     // Resolves a move group into two lists of ordered moves. These moves must
     // be executed in the order provided. Some moves may indicate that they
     // participate in a cycle. For every cycle there are two such moves, and it
     // is guaranteed that cycles do not nest inside each other in the list.
     //
--- a/js/src/jit/SharedIC.cpp
+++ b/js/src/jit/SharedIC.cpp
@@ -437,34 +437,33 @@ ICMonitoredStub::ICMonitoredStub(Kind ki
     // If the first monitored stub is a ICTypeMonitor_Fallback stub, then
     // double check that _its_ firstMonitorStub is the same as this one.
     MOZ_ASSERT_IF(firstMonitorStub_->isTypeMonitor_Fallback(),
                   firstMonitorStub_->toTypeMonitor_Fallback()->firstMonitorStub() ==
                      firstMonitorStub_);
 }
 
 bool
-ICMonitoredFallbackStub::initMonitoringChain(JSContext* cx, ICStubSpace* space,
-                                             ICStubCompiler::Engine engine)
+ICMonitoredFallbackStub::initMonitoringChain(JSContext* cx, ICStubSpace* space)
 {
     MOZ_ASSERT(fallbackMonitorStub_ == nullptr);
 
-    ICTypeMonitor_Fallback::Compiler compiler(cx, engine, this);
+    ICTypeMonitor_Fallback::Compiler compiler(cx, this);
     ICTypeMonitor_Fallback* stub = compiler.getStub(space);
     if (!stub)
         return false;
     fallbackMonitorStub_ = stub;
     return true;
 }
 
 bool
-ICMonitoredFallbackStub::addMonitorStubForValue(JSContext* cx, SharedStubInfo* stub,
+ICMonitoredFallbackStub::addMonitorStubForValue(JSContext* cx, BaselineFrame* frame,
                                                 HandleValue val)
 {
-    return fallbackMonitorStub_->addMonitorStubForValue(cx, stub, val);
+    return fallbackMonitorStub_->addMonitorStubForValue(cx, frame, val);
 }
 
 bool
 ICUpdatedStub::initUpdatingChain(JSContext* cx, ICStubSpace* space)
 {
     MOZ_ASSERT(firstUpdateStub_ == nullptr);
 
     ICTypeUpdate_Fallback::Compiler compiler(cx);
@@ -2007,22 +2006,20 @@ ComputeGetPropResult(JSContext* cx, Base
 
     return true;
 }
 
 static bool
 DoGetPropFallback(JSContext* cx, BaselineFrame* frame, ICGetProp_Fallback* stub_,
                   MutableHandleValue val, MutableHandleValue res)
 {
-    SharedStubInfo info(cx, frame, stub_->icEntry());
-    HandleScript script = info.innerScript();
-
     // This fallback stub may trigger debug mode toggling.
     DebugModeOSRVolatileStub<ICGetProp_Fallback*> stub(frame, stub_);
 
+    RootedScript script(cx, frame->script());
     jsbytecode* pc = stub_->icEntry()->pc(script);
     JSOp op = JSOp(*pc);
     FallbackICSpew(cx, stub, "GetProp(%s)", CodeName[op]);
 
     MOZ_ASSERT(op == JSOP_GETPROP ||
                op == JSOP_CALLPROP ||
                op == JSOP_LENGTH ||
                op == JSOP_GETBOUNDNAME);
@@ -2064,17 +2061,17 @@ DoGetPropFallback(JSContext* cx, Baselin
 
     TypeScript::Monitor(cx, script, pc, res);
 
     // Check if debug mode toggling made the stub invalid.
     if (stub.invalid())
         return true;
 
     // Add a type monitor stub for the resulting value.
-    if (!stub->addMonitorStubForValue(cx, &info, res))
+    if (!stub->addMonitorStubForValue(cx, frame, res))
         return false;
 
     if (attached)
         return true;
 
     MOZ_ASSERT(!attached);
     if (!isTemporarilyUnoptimizable)
         stub->noteUnoptimizableAccess();
@@ -2171,17 +2168,17 @@ BaselineScript::noteAccessedGetter(uint3
     if (stub->isGetProp_Fallback())
         stub->toGetProp_Fallback()->noteAccessedGetter();
 }
 
 // TypeMonitor_Fallback
 //
 
 bool
-ICTypeMonitor_Fallback::addMonitorStubForValue(JSContext* cx, SharedStubInfo* info, HandleValue val)
+ICTypeMonitor_Fallback::addMonitorStubForValue(JSContext* cx, BaselineFrame* frame, HandleValue val)
 {
     bool wasDetachedMonitorChain = lastMonitorStubPtrAddr_ == nullptr;
     MOZ_ASSERT_IF(wasDetachedMonitorChain, numOptimizedMonitorStubs_ == 0);
 
     if (numOptimizedMonitorStubs_ >= MAX_OPTIMIZED_STUBS) {
         // TODO: if the TypeSet becomes unknown or has the AnyObject type,
         // replace stubs with a single stub to handle these.
         return true;
@@ -2198,19 +2195,20 @@ ICTypeMonitor_Fallback::addMonitorStubFo
         for (ICStubConstIterator iter(firstMonitorStub()); !iter.atEnd(); iter++) {
             if (iter->isTypeMonitor_PrimitiveSet()) {
                 existingStub = iter->toTypeMonitor_PrimitiveSet();
                 if (existingStub->containsType(type))
                     return true;
             }
         }
 
-        ICTypeMonitor_PrimitiveSet::Compiler compiler(cx, info->engine(), existingStub, type);
-        ICStub* stub = existingStub ? compiler.updateStub()
-                                    : compiler.getStub(compiler.getStubSpace(info->outerScript(cx)));
+        ICTypeMonitor_PrimitiveSet::Compiler compiler(cx, existingStub, type);
+        ICStub* stub = existingStub
+                       ? compiler.updateStub()
+                       : compiler.getStub(compiler.getStubSpace(frame->script()));
         if (!stub) {
             ReportOutOfMemory(cx);
             return false;
         }
 
         JitSpew(JitSpew_BaselineIC, "  %s TypeMonitor stub %p for primitive type %d",
                 existingStub ? "Modified existing" : "Created new", stub, type);
 
@@ -2227,17 +2225,17 @@ ICTypeMonitor_Fallback::addMonitorStubFo
             if (iter->isTypeMonitor_SingleObject() &&
                 iter->toTypeMonitor_SingleObject()->object() == obj)
             {
                 return true;
             }
         }
 
         ICTypeMonitor_SingleObject::Compiler compiler(cx, obj);
-        ICStub* stub = compiler.getStub(compiler.getStubSpace(info->outerScript(cx)));
+        ICStub* stub = compiler.getStub(compiler.getStubSpace(frame->script()));
         if (!stub) {
             ReportOutOfMemory(cx);
             return false;
         }
 
         JitSpew(JitSpew_BaselineIC, "  Added TypeMonitor stub %p for singleton %p",
                 stub, obj.get());
 
@@ -2251,17 +2249,17 @@ ICTypeMonitor_Fallback::addMonitorStubFo
             if (iter->isTypeMonitor_ObjectGroup() &&
                 iter->toTypeMonitor_ObjectGroup()->group() == group)
             {
                 return true;
             }
         }
 
         ICTypeMonitor_ObjectGroup::Compiler compiler(cx, group);
-        ICStub* stub = compiler.getStub(compiler.getStubSpace(info->outerScript(cx)));
+        ICStub* stub = compiler.getStub(compiler.getStubSpace(frame->script()));
         if (!stub) {
             ReportOutOfMemory(cx);
             return false;
         }
 
         JitSpew(JitSpew_BaselineIC, "  Added TypeMonitor stub %p for ObjectGroup %p",
                 stub, group.get());
 
@@ -2288,21 +2286,20 @@ ICTypeMonitor_Fallback::addMonitorStubFo
             iter->toMonitoredStub()->updateFirstMonitorStub(firstMonitorStub_);
         }
     }
 
     return true;
 }
 
 static bool
-DoTypeMonitorFallback(JSContext* cx, void* payload, ICTypeMonitor_Fallback* stub,
+DoTypeMonitorFallback(JSContext* cx, BaselineFrame* frame, ICTypeMonitor_Fallback* stub,
                       HandleValue value, MutableHandleValue res)
 {
-    SharedStubInfo info(cx, payload, stub->icEntry());
-    HandleScript script = info.innerScript();
+    JSScript* script = frame->script();
     jsbytecode* pc = stub->icEntry()->pc(script);
     TypeFallbackICSpew(cx, stub, "TypeMonitor");
 
     if (value.isMagic()) {
         // It's possible that we arrived here from bailing out of Ion, and that
         // Ion proved that the value is dead and optimized out. In such cases,
         // do nothing. However, it's also possible that we have an uninitialized
         // this, in which case we should not look for other magic values.
@@ -2311,17 +2308,17 @@ DoTypeMonitorFallback(JSContext* cx, voi
             MOZ_ASSERT(!stub->monitorsThis());
             res.set(value);
             return true;
         }
 
         // In derived class constructors (including nested arrows/eval), the
         // |this| argument or GETALIASEDVAR can return the magic TDZ value.
         MOZ_ASSERT(value.isMagic(JS_UNINITIALIZED_LEXICAL));
-        MOZ_ASSERT(info.frame()->isFunctionFrame() || info.frame()->isEvalFrame());
+        MOZ_ASSERT(frame->isFunctionFrame() || frame->isEvalFrame());
         MOZ_ASSERT(stub->monitorsThis() ||
                    *GetNextPc(pc) == JSOP_CHECKTHIS ||
                    *GetNextPc(pc) == JSOP_CHECKRETURN);
     }
 
     uint32_t argument;
     if (stub->monitorsThis()) {
         MOZ_ASSERT(pc == script->code());
@@ -2335,41 +2332,41 @@ DoTypeMonitorFallback(JSContext* cx, voi
         TypeScript::SetArgument(cx, script, argument, value);
     } else {
         if (value.isMagic(JS_UNINITIALIZED_LEXICAL))
             TypeScript::Monitor(cx, script, pc, TypeSet::UnknownType());
         else
             TypeScript::Monitor(cx, script, pc, value);
     }
 
-    if (!stub->invalid() && !stub->addMonitorStubForValue(cx, &info, value))
+    if (!stub->invalid() && !stub->addMonitorStubForValue(cx, frame, value))
         return false;
 
     // Copy input value to res.
     res.set(value);
     return true;
 }
 
-typedef bool (*DoTypeMonitorFallbackFn)(JSContext*, void*, ICTypeMonitor_Fallback*,
+typedef bool (*DoTypeMonitorFallbackFn)(JSContext*, BaselineFrame*, ICTypeMonitor_Fallback*,
                                         HandleValue, MutableHandleValue);
 static const VMFunction DoTypeMonitorFallbackInfo =
     FunctionInfo<DoTypeMonitorFallbackFn>(DoTypeMonitorFallback, "DoTypeMonitorFallback",
                                           TailCall);
 
 bool
 ICTypeMonitor_Fallback::Compiler::generateStubCode(MacroAssembler& masm)
 {
     MOZ_ASSERT(R0 == JSReturnOperand);
 
     // Restore the tail call register.
     EmitRestoreTailCallReg(masm);
 
     masm.pushValue(R0);
     masm.push(ICStubReg);
-    pushStubPayload(masm, R0.scratchReg());
+    masm.pushBaselineFramePtr(BaselineFrameReg, R0.scratchReg());
 
     return tailCallVM(DoTypeMonitorFallbackInfo, masm);
 }
 
 bool
 ICTypeMonitor_PrimitiveSet::Compiler::generateStubCode(MacroAssembler& masm)
 {
     Label success;
--- a/js/src/jit/SharedIC.h
+++ b/js/src/jit/SharedIC.h
@@ -1202,19 +1202,18 @@ class ICMonitoredFallbackStub : public I
     // Pointer to the fallback monitor stub.
     ICTypeMonitor_Fallback* fallbackMonitorStub_;
 
     ICMonitoredFallbackStub(Kind kind, JitCode* stubCode)
       : ICFallbackStub(kind, ICStub::MonitoredFallback, stubCode),
         fallbackMonitorStub_(nullptr) {}
 
   public:
-    MOZ_MUST_USE bool initMonitoringChain(JSContext* cx, ICStubSpace* space,
-                                          ICStubCompiler::Engine engine);
-    MOZ_MUST_USE bool addMonitorStubForValue(JSContext* cx, SharedStubInfo* info, HandleValue val);
+    MOZ_MUST_USE bool initMonitoringChain(JSContext* cx, ICStubSpace* space);
+    MOZ_MUST_USE bool addMonitorStubForValue(JSContext* cx, BaselineFrame* frame, HandleValue val);
 
     inline ICTypeMonitor_Fallback* fallbackMonitorStub() const {
         return fallbackMonitorStub_;
     }
 
     static inline size_t offsetOfFallbackMonitorStub() {
         return offsetof(ICMonitoredFallbackStub, fallbackMonitorStub_);
     }
@@ -1299,19 +1298,19 @@ class TypeCheckPrimitiveSetStub : public
 
         virtual int32_t getKey() const {
             return static_cast<int32_t>(engine_) |
                   (static_cast<int32_t>(kind) << 1) |
                   (static_cast<int32_t>(flags_) << 17);
         }
 
       public:
-        Compiler(JSContext* cx, Kind kind, Engine engine_, TypeCheckPrimitiveSetStub* existingStub,
+        Compiler(JSContext* cx, Kind kind, TypeCheckPrimitiveSetStub* existingStub,
                  JSValueType type)
-          : ICStubCompiler(cx, kind, engine_),
+          : ICStubCompiler(cx, kind, Engine::Baseline),
             existingStub_(existingStub),
             flags_((existingStub ? existingStub->typeFlags() : 0) | TypeToFlag(type))
         {
             MOZ_ASSERT_IF(existingStub_, flags_ != existingStub_->typeFlags());
         }
 
         TypeCheckPrimitiveSetStub* updateStub() {
             MOZ_ASSERT(existingStub_);
@@ -1464,37 +1463,37 @@ class ICTypeMonitor_Fallback : public IC
         MOZ_ASSERT(icEntry_ == nullptr);
         MOZ_ASSERT(lastMonitorStubPtrAddr_ == nullptr);
         icEntry_ = icEntry;
         lastMonitorStubPtrAddr_ = icEntry_->addressOfFirstStub();
     }
 
     // Create a new monitor stub for the type of the given value, and
     // add it to this chain.
-    MOZ_MUST_USE bool addMonitorStubForValue(JSContext* cx, SharedStubInfo* info, HandleValue val);
+    MOZ_MUST_USE bool addMonitorStubForValue(JSContext* cx, BaselineFrame* frame, HandleValue val);
 
     void resetMonitorStubChain(Zone* zone);
 
     // Compiler for this stub kind.
     class Compiler : public ICStubCompiler {
         ICMonitoredFallbackStub* mainFallbackStub_;
         uint32_t argumentIndex_;
 
       protected:
         MOZ_MUST_USE bool generateStubCode(MacroAssembler& masm);
 
       public:
-        Compiler(JSContext* cx, Engine engine, ICMonitoredFallbackStub* mainFallbackStub)
-            : ICStubCompiler(cx, ICStub::TypeMonitor_Fallback, engine),
+        Compiler(JSContext* cx, ICMonitoredFallbackStub* mainFallbackStub)
+          : ICStubCompiler(cx, ICStub::TypeMonitor_Fallback, Engine::Baseline),
             mainFallbackStub_(mainFallbackStub),
             argumentIndex_(BYTECODE_INDEX)
         { }
 
-        Compiler(JSContext* cx, Engine engine, uint32_t argumentIndex)
-          : ICStubCompiler(cx, ICStub::TypeMonitor_Fallback, engine),
+        Compiler(JSContext* cx, uint32_t argumentIndex)
+          : ICStubCompiler(cx, ICStub::TypeMonitor_Fallback, Engine::Baseline),
             mainFallbackStub_(nullptr),
             argumentIndex_(argumentIndex)
         { }
 
         ICTypeMonitor_Fallback* getStub(ICStubSpace* space) {
             return newStub<ICTypeMonitor_Fallback>(space, getStubCode(), mainFallbackStub_,
                                                        argumentIndex_);
         }
@@ -1510,19 +1509,19 @@ class ICTypeMonitor_PrimitiveSet : publi
     {}
 
   public:
     class Compiler : public TypeCheckPrimitiveSetStub::Compiler {
       protected:
         MOZ_MUST_USE bool generateStubCode(MacroAssembler& masm);
 
       public:
-        Compiler(JSContext* cx, Engine engine, ICTypeMonitor_PrimitiveSet* existingStub,
+        Compiler(JSContext* cx, ICTypeMonitor_PrimitiveSet* existingStub,
                  JSValueType type)
-          : TypeCheckPrimitiveSetStub::Compiler(cx, TypeMonitor_PrimitiveSet, engine, existingStub,
+          : TypeCheckPrimitiveSetStub::Compiler(cx, TypeMonitor_PrimitiveSet, existingStub,
                                                 type)
         {}
 
         ICTypeMonitor_PrimitiveSet* updateStub() {
             TypeCheckPrimitiveSetStub* stub =
                 this->TypeCheckPrimitiveSetStub::Compiler::updateStub();
             if (!stub)
                 return nullptr;
@@ -2357,17 +2356,17 @@ class ICGetProp_Fallback : public ICMoni
 
       public:
         explicit Compiler(JSContext* cx, Engine engine)
           : ICStubCompiler(cx, ICStub::GetProp_Fallback, engine)
         { }
 
         ICStub* getStub(ICStubSpace* space) {
             ICGetProp_Fallback* stub = newStub<ICGetProp_Fallback>(space, getStubCode());
-            if (!stub || !stub->initMonitoringChain(cx, space, engine_))
+            if (!stub || !stub->initMonitoringChain(cx, space))
                 return nullptr;
             return stub;
         }
     };
 };
 
 static inline uint32_t
 SimpleTypeDescrKey(SimpleTypeDescr* descr)
--- a/js/src/jit/x86-shared/Assembler-x86-shared.h
+++ b/js/src/jit/x86-shared/Assembler-x86-shared.h
@@ -2291,16 +2291,20 @@ class AssemblerX86Shared : public Assemb
     void vmovmskpd(FloatRegister src, Register dest) {
         MOZ_ASSERT(HasSSE2());
         masm.vmovmskpd_rr(src.encoding(), dest.encoding());
     }
     void vmovmskps(FloatRegister src, Register dest) {
         MOZ_ASSERT(HasSSE2());
         masm.vmovmskps_rr(src.encoding(), dest.encoding());
     }
+    void vpmovmskb(FloatRegister src, Register dest) {
+        MOZ_ASSERT(HasSSE2());
+        masm.vpmovmskb_rr(src.encoding(), dest.encoding());
+    }
     void vptest(FloatRegister rhs, FloatRegister lhs) {
         MOZ_ASSERT(HasSSE41());
         masm.vptest_rr(rhs.encoding(), lhs.encoding());
     }
     void vucomisd(FloatRegister rhs, FloatRegister lhs) {
         MOZ_ASSERT(HasSSE2());
         masm.vucomisd_rr(rhs.encoding(), lhs.encoding());
     }
--- a/js/src/jit/x86-shared/BaseAssembler-x86-shared.h
+++ b/js/src/jit/x86-shared/BaseAssembler-x86-shared.h
@@ -3085,16 +3085,21 @@ public:
         twoByteOpSimdInt32("vmovmskpd", VEX_PD, OP2_MOVMSKPD_EdVd, src, dst);
     }
 
     void vmovmskps_rr(XMMRegisterID src, RegisterID dst)
     {
         twoByteOpSimdInt32("vmovmskps", VEX_PS, OP2_MOVMSKPD_EdVd, src, dst);
     }
 
+    void vpmovmskb_rr(XMMRegisterID src, RegisterID dst)
+    {
+        twoByteOpSimdInt32("vpmovmskb", VEX_PD, OP2_PMOVMSKB_EdVd, src, dst);
+    }
+
     void vptest_rr(XMMRegisterID rhs, XMMRegisterID lhs) {
         threeByteOpSimd("vptest", VEX_PD, OP3_PTEST_VdVd, ESCAPE_38, rhs, invalid_xmm, lhs);
     }
 
     void vmovd_rr(XMMRegisterID src, RegisterID dst)
     {
         twoByteOpSimdInt32("vmovd", VEX_PD, OP2_MOVD_EdVd, (XMMRegisterID)dst, (RegisterID)src);
     }
--- a/js/src/jit/x86-shared/CodeGenerator-x86-shared.cpp
+++ b/js/src/jit/x86-shared/CodeGenerator-x86-shared.cpp
@@ -2958,28 +2958,30 @@ CodeGeneratorX86Shared::visitSimdInsertE
 }
 
 void
 CodeGeneratorX86Shared::visitSimdAllTrue(LSimdAllTrue* ins)
 {
     FloatRegister input = ToFloatRegister(ins->input());
     Register output = ToRegister(ins->output());
 
-    masm.vmovmskps(input, output);
-    masm.cmp32(output, Imm32(0xf));
+    // We know that the input lanes are boolean, so they are either 0 or -1.
+    // The all-true vector has all 128 bits set, no matter the lane geometry.
+    masm.vpmovmskb(input, output);
+    masm.cmp32(output, Imm32(0xffff));
     masm.emitSet(Assembler::Zero, output);
 }
 
 void
 CodeGeneratorX86Shared::visitSimdAnyTrue(LSimdAnyTrue* ins)
 {
     FloatRegister input = ToFloatRegister(ins->input());
     Register output = ToRegister(ins->output());
 
-    masm.vmovmskps(input, output);
+    masm.vpmovmskb(input, output);
     masm.cmp32(output, Imm32(0x0));
     masm.emitSet(Assembler::NonZero, output);
 }
 
 template <class T, class Reg> void
 CodeGeneratorX86Shared::visitSimdGeneralShuffle(LSimdGeneralShuffleBase* ins, Reg tempRegister)
 {
     MSimdGeneralShuffle* mir = ins->mir();
--- a/js/src/jit/x86-shared/Encoding-x86-shared.h
+++ b/js/src/jit/x86-shared/Encoding-x86-shared.h
@@ -261,16 +261,17 @@ enum TwoByteOpcodeID {
     OP2_CMPPS_VpsWps    = 0xC2,
     OP2_PINSRW          = 0xC4,
     OP2_PEXTRW_GdUdIb   = 0xC5,
     OP2_SHUFPS_VpsWpsIb = 0xC6,
     OP2_PSRLW_VdqWdq    = 0xD1,
     OP2_PSRLD_VdqWdq    = 0xD2,
     OP2_PMULLW_VdqWdq   = 0xD5,
     OP2_MOVQ_WdVd       = 0xD6,
+    OP2_PMOVMSKB_EdVd   = 0xD7,
     OP2_PSUBUSB_VdqWdq  = 0xD8,
     OP2_PSUBUSW_VdqWdq  = 0xD9,
     OP2_PANDDQ_VdqWdq   = 0xDB,
     OP2_PADDUSB_VdqWdq  = 0xDC,
     OP2_PADDUSW_VdqWdq  = 0xDD,
     OP2_PANDNDQ_VdqWdq  = 0xDF,
     OP2_PSRAW_VdqWdq    = 0xE1,
     OP2_PSRAD_VdqWdq    = 0xE2,
--- a/js/src/jsapi-tests/testJitMoveEmitterCycles.cpp
+++ b/js/src/jsapi-tests/testJitMoveEmitterCycles.cpp
@@ -1,16 +1,17 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
  * vim: set ts=8 sts=4 et sw=4 tw=99:
  */
 /* 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/. */
 
 #if defined(JS_SIMULATOR_ARM)
+
 #include "jit/arm/Assembler-arm.h"
 #include "jit/arm/MoveEmitter-arm.h"
 #include "jit/arm/Simulator-arm.h"
 #include "jit/Linker.h"
 #include "jit/MacroAssembler.h"
 #include "jit/MoveResolver.h"
 
 #include "jsapi-tests/tests.h"
@@ -523,10 +524,101 @@ BEGIN_TEST(testJitMoveEmitterCycles_auto
     CHECK(int(f) == 29);
     sim->get_float_from_s_register(15, &f);
     CHECK(int(f) == 29);
     sim->get_float_from_s_register(23, &f);
     CHECK(int(f) == 16);
     return true;
 }
 END_TEST(testJitMoveEmitterCycles_autogen3)
+BEGIN_TEST(testJitMoveEmitterCycles_bug1299147_1)
+{
+    using namespace js;
+    using namespace js::jit;
+    LifoAlloc lifo(LIFO_ALLOC_PRIMARY_CHUNK_SIZE);
+    TempAllocator alloc(&lifo);
+    JitContext jc(cx, &alloc);
+    cx->runtime()->getJitRuntime(cx);
+    MacroAssembler masm;
+    MoveEmitter mover(masm);
+    MoveResolver mr;
+    mr.setAllocator(alloc);
+    Simulator* sim = Simulator::Current();
+    // S2 -> S0
+    // S2 -> S6
+    // S3 -> S1
+    // S3 -> S7
+    // D0 -> D1
+    // D0 -> D2
+    TRY(mr.addMove(MoveOperand(s2), MoveOperand(s0), MoveOp::FLOAT32));
+    TRY(mr.addMove(MoveOperand(s2), MoveOperand(s6), MoveOp::FLOAT32));
+    sim->set_s_register_from_float(2, 2);
+    TRY(mr.addMove(MoveOperand(s3), MoveOperand(s1), MoveOp::FLOAT32));
+    TRY(mr.addMove(MoveOperand(s3), MoveOperand(s7), MoveOp::FLOAT32));
+    sim->set_s_register_from_float(3, 4);
+    TRY(mr.addMove(MoveOperand(d0), MoveOperand(d1), MoveOp::FLOAT32));
+    TRY(mr.addMove(MoveOperand(d0), MoveOperand(d2), MoveOp::FLOAT32));
+    sim->set_d_register_from_double(0, 1);
+    // don't explode!
+    TRY(mr.resolve());
+    mover.emit(mr);
+    mover.finish();
+    masm.abiret();
+    JitCode* code = linkAndAllocate(cx, &masm);
+    sim->call(code->raw(), 1, 1);
+    float f;
+    double d;
+    sim->get_double_from_d_register(1, &d);
+    CHECK(d == 1);
+    sim->get_double_from_d_register(2, &d);
+    CHECK(d == 1);
+    sim->get_float_from_s_register(0, &f);
+    CHECK(int(f) == 2);
+    sim->get_float_from_s_register(6, &f);
+    CHECK(int(f) == 2);
+    sim->get_float_from_s_register(1, &f);
+    CHECK(int(f) == 4);
+    sim->get_float_from_s_register(7, &f);
+    CHECK(int(f) == 4);
+    return true;
+}
+END_TEST(testJitMoveEmitterCycles_bug1299147_1)
+BEGIN_TEST(testJitMoveEmitterCycles_bug1299147)
+{
+    using namespace js;
+    using namespace js::jit;
+    LifoAlloc lifo(LIFO_ALLOC_PRIMARY_CHUNK_SIZE);
+    TempAllocator alloc(&lifo);
+    JitContext jc(cx, &alloc);
+    cx->runtime()->getJitRuntime(cx);
+    MacroAssembler masm;
+    MoveEmitter mover(masm);
+    MoveResolver mr;
+    mr.setAllocator(alloc);
+    Simulator* sim = Simulator::Current();
+    // S2 -> S5
+    // S2 -> S6
+    // D0 -> D1
+    TRY(mr.addMove(MoveOperand(s2), MoveOperand(s5), MoveOp::FLOAT32));
+    TRY(mr.addMove(MoveOperand(s2), MoveOperand(s6), MoveOp::FLOAT32));
+    sim->set_s_register_from_float(2, 2);
+    TRY(mr.addMove(MoveOperand(d0), MoveOperand(d1), MoveOp::FLOAT32));
+    sim->set_d_register_from_double(0, 1);
+    // don't explode!
+    TRY(mr.resolve());
+    mover.emit(mr);
+    mover.finish();
+    masm.abiret();
+    JitCode* code = linkAndAllocate(cx, &masm);
+    sim->call(code->raw(), 1, 1);
+    float f;
+    double d;
+    sim->get_double_from_d_register(1, &d);
+    CHECK(d == 1);
+    sim->get_float_from_s_register(5, &f);
+    CHECK(int(f) == 2);
+    sim->get_float_from_s_register(6, &f);
+    CHECK(int(f) == 2);
+    return true;
+}
+END_TEST(testJitMoveEmitterCycles_bug1299147)
 
-#endif
+#endif // JS_SIMULATOR_ARM
--- a/js/xpconnect/idl/xpccomponents.idl
+++ b/js/xpconnect/idl/xpccomponents.idl
@@ -524,16 +524,22 @@ interface nsIXPCComponents_Utils : nsISu
     attribute boolean werror;
 
     [implicit_jscontext]
     attribute boolean strict_mode;
 
     [implicit_jscontext]
     attribute boolean ion;
 
+    // Returns true if we're running in automation and certain security
+    // restrictions can be eased.
+    readonly attribute boolean isInAutomation;
+
+    void crashIfNotInAutomation();
+
     [implicit_jscontext]
     void setGCZeal(in long zeal);
 
     [implicit_jscontext]
     void nukeSandbox(in jsval obj);
 
     /*
      * API to dynamically block script for a given global. This takes effect
--- a/js/xpconnect/loader/AutoMemMap.cpp
+++ b/js/xpconnect/loader/AutoMemMap.cpp
@@ -5,52 +5,92 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "AutoMemMap.h"
 #include "ScriptPreloader-inl.h"
 
 #include "mozilla/Unused.h"
 #include "nsIFile.h"
 
+#include <private/pprio.h>
+
 namespace mozilla {
 namespace loader {
 
+using namespace mozilla::ipc;
+
 AutoMemMap::~AutoMemMap()
 {
     if (fileMap) {
         if (addr) {
             Unused << NS_WARN_IF(PR_MemUnmap(addr, size()) != PR_SUCCESS);
             addr = nullptr;
         }
 
         Unused << NS_WARN_IF(PR_CloseFileMap(fileMap) != PR_SUCCESS);
         fileMap = nullptr;
     }
 }
 
+FileDescriptor
+AutoMemMap::cloneFileDescriptor()
+{
+    if (fd.get()) {
+        auto handle = FileDescriptor::PlatformHandleType(PR_FileDesc2NativeHandle(fd.get()));
+        return FileDescriptor(handle);
+    }
+    return FileDescriptor();
+}
+
 Result<Ok, nsresult>
 AutoMemMap::init(nsIFile* file, int flags, int mode, PRFileMapProtect prot)
 {
     MOZ_ASSERT(!fd);
+
+    NS_TRY(file->OpenNSPRFileDesc(flags, mode, &fd.rwget()));
+
+    return initInternal(prot);
+}
+
+Result<Ok, nsresult>
+AutoMemMap::init(const FileDescriptor& file)
+{
+    MOZ_ASSERT(!fd);
+    if (!file.IsValid()) {
+        return Err(NS_ERROR_INVALID_ARG);
+    }
+
+    auto handle = file.ClonePlatformHandle();
+
+    fd = PR_ImportFile(PROsfd(handle.get()));
+    if (!fd) {
+        return Err(NS_ERROR_FAILURE);
+    }
+    Unused << handle.release();
+
+    return initInternal();
+}
+
+Result<Ok, nsresult>
+AutoMemMap::initInternal(PRFileMapProtect prot)
+{
     MOZ_ASSERT(!fileMap);
     MOZ_ASSERT(!addr);
 
-    int64_t fileSize;
-    NS_TRY(file->GetFileSize(&fileSize));
+    PRFileInfo64 fileInfo;
+    NS_TRY(PR_GetOpenFileInfo64(fd.get(), &fileInfo));
 
-    if (fileSize > UINT32_MAX)
+    if (fileInfo.size > UINT32_MAX)
         return Err(NS_ERROR_INVALID_ARG);
 
-    NS_TRY(file->OpenNSPRFileDesc(flags, mode, &fd.rwget()));
-
     fileMap = PR_CreateFileMap(fd, 0, prot);
     if (!fileMap)
         return Err(NS_ERROR_FAILURE);
 
-    size_ = fileSize;
+    size_ = fileInfo.size;
     addr = PR_MemMap(fileMap, 0, size_);
     if (!addr)
         return Err(NS_ERROR_FAILURE);
 
     return Ok();
 }
 
 } // namespace loader
--- a/js/xpconnect/loader/AutoMemMap.h
+++ b/js/xpconnect/loader/AutoMemMap.h
@@ -5,36 +5,42 @@
 
 #ifndef loader_AutoMemMap_h
 #define loader_AutoMemMap_h
 
 #include "mozilla/FileUtils.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/RangedPtr.h"
 #include "mozilla/Result.h"
+#include "mozilla/ipc/FileDescriptor.h"
 #include "nsIMemoryReporter.h"
 
 #include <prio.h>
 
 class nsIFile;
 
 namespace mozilla {
 namespace loader {
 
+using mozilla::ipc::FileDescriptor;
+
 class AutoMemMap
 {
     public:
         AutoMemMap() = default;
 
         ~AutoMemMap();
 
         Result<Ok, nsresult>
         init(nsIFile* file, int flags = PR_RDONLY, int mode = 0,
              PRFileMapProtect prot = PR_PROT_READONLY);
 
+        Result<Ok, nsresult>
+        init(const ipc::FileDescriptor& file);
+
         bool initialized() { return addr; }
 
         uint32_t size() const { MOZ_ASSERT(fd); return size_; }
 
         template<typename T = void>
         const RangedPtr<T> get()
         {
             MOZ_ASSERT(addr);
@@ -45,17 +51,21 @@ class AutoMemMap
         const RangedPtr<T> get() const
         {
             MOZ_ASSERT(addr);
             return { static_cast<T*>(addr), size_ };
         }
 
         size_t nonHeapSizeOfExcludingThis() { return size_; }
 
+        FileDescriptor cloneFileDescriptor();
+
     private:
+        Result<Ok, nsresult> initInternal(PRFileMapProtect prot = PR_PROT_READONLY);
+
         AutoFDClose fd;
         PRFileMap* fileMap = nullptr;
 
         uint32_t size_ = 0;
         void* addr = nullptr;
 
         AutoMemMap(const AutoMemMap&) = delete;
         void operator=(const AutoMemMap&) = delete;
new file mode 100644
--- /dev/null
+++ b/js/xpconnect/loader/PScriptCache.ipdl
@@ -0,0 +1,33 @@
+/* -*- Mode: C++; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 8 -*- */
+/* vim: set sw=4 ts=8 et tw=80 ft=cpp : */
+/* 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 protocol PContent;
+
+using class mozilla::TimeStamp from "mozilla/TimeStamp.h";
+using mozilla::void_t from "ipc/IPCMessageUtils.h";
+
+namespace mozilla {
+namespace loader {
+
+struct ScriptData {
+    nsCString url;
+    nsCString cachePath;
+    TimeStamp loadTime;
+    // This will be an empty array if script data is present in the previous
+    // session's cache.
+    uint8_t[] xdrData;
+};
+
+protocol PScriptCache
+{
+    manager PContent;
+
+parent:
+    async __delete__(ScriptData[] scripts);
+};
+
+} // namespace loader
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/js/xpconnect/loader/ScriptCacheActors.cpp
@@ -0,0 +1,100 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=4 et sw=4 tw=99: */
+/* 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/ScriptPreloader.h"
+#include "ScriptPreloader-inl.h"
+#include "mozilla/loader/ScriptCacheActors.h"
+
+#include "mozilla/dom/ContentParent.h"
+
+namespace mozilla {
+namespace loader {
+
+void
+ScriptCacheChild::Init(const Maybe<FileDescriptor>& cacheFile, bool wantCacheData)
+{
+    mWantCacheData = wantCacheData;
+
+    auto& cache = ScriptPreloader::GetChildSingleton();
+    Unused << cache.InitCache(cacheFile, this);
+
+    if (!wantCacheData) {
+        // If the parent process isn't expecting any cache data from us, we're
+        // done.
+        Send__delete__(this, AutoTArray<ScriptData, 0>());
+    }
+}
+
+// Finalize the script cache for the content process, and send back data about
+// any scripts executed up to this point.
+void
+ScriptCacheChild::SendScriptsAndFinalize(ScriptPreloader::ScriptHash& scripts)
+{
+    MOZ_ASSERT(mWantCacheData);
+
+    AutoSafeJSAPI jsapi;
+
+    auto matcher = ScriptPreloader::Match<ScriptPreloader::ScriptStatus::Saved>();
+
+    nsTArray<ScriptData> dataArray;
+    for (auto& script : IterHash(scripts, matcher)) {
+        if (!script->mSize && !script->XDREncode(jsapi.cx())) {
+            continue;
+        }
+
+        auto data = dataArray.AppendElement();
+
+        data->url() = script->mURL;
+        data->cachePath() = script->mCachePath;
+        data->loadTime() = script->mLoadTime;
+
+        if (script->HasBuffer()) {
+            auto& xdrData = script->Buffer();
+            data->xdrData().AppendElements(xdrData.begin(), xdrData.length());
+            script->FreeData();
+        }
+    }
+
+    Send__delete__(this, dataArray);
+}
+
+void
+ScriptCacheChild::ActorDestroy(ActorDestroyReason aWhy)
+{
+    auto& cache = ScriptPreloader::GetChildSingleton();
+    cache.mChildActor = nullptr;
+}
+
+
+IPCResult
+ScriptCacheParent::Recv__delete__(nsTArray<ScriptData>&& scripts)
+{
+    if (!mWantCacheData && scripts.Length()) {
+        return IPC_FAIL(this, "UnexpectedScriptData");
+    }
+
+    // We don't want any more data from the process at this point.
+    mWantCacheData = false;
+
+    // Merge the child's script data with the parent's.
+    auto parent = static_cast<dom::ContentParent*>(Manager());
+    auto processType = ScriptPreloader::GetChildProcessType(parent->GetRemoteType());
+
+    auto& cache = ScriptPreloader::GetChildSingleton();
+    for (auto& script : scripts) {
+        cache.NoteScript(script.url(), script.cachePath(), processType,
+                         Move(script.xdrData()), script.loadTime());
+    }
+
+    return IPC_OK();
+}
+
+void
+ScriptCacheParent::ActorDestroy(ActorDestroyReason aWhy)
+{}
+
+} // namespace loader
+} // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/js/xpconnect/loader/ScriptCacheActors.h
@@ -0,0 +1,62 @@
+/* -*-  Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; -*- */
+/* 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 ScriptCache_h
+#define ScriptCache_h
+
+#include "mozilla/ScriptPreloader.h"
+#include "mozilla/loader/PScriptCacheChild.h"
+#include "mozilla/loader/PScriptCacheParent.h"
+
+namespace mozilla {
+namespace ipc {
+    class FileDescriptor;
+}
+
+
+namespace loader {
+
+using mozilla::ipc::FileDescriptor;
+using mozilla::ipc::IPCResult;
+
+class ScriptCacheParent final : public PScriptCacheParent
+{
+public:
+    explicit ScriptCacheParent(bool wantCacheData)
+        : mWantCacheData(wantCacheData)
+    {}
+
+protected:
+    virtual IPCResult Recv__delete__(nsTArray<ScriptData>&& scripts) override;
+
+    virtual void ActorDestroy(ActorDestroyReason aWhy) override;
+
+private:
+    bool mWantCacheData;
+};
+
+class ScriptCacheChild final : public PScriptCacheChild
+{
+    friend class mozilla::ScriptPreloader;
+
+public:
+    ScriptCacheChild() = default;
+
+    void Init(const Maybe<FileDescriptor>& cacheFile, bool wantCacheData);
+
+protected:
+    virtual void ActorDestroy(ActorDestroyReason aWhy) override;
+
+    void SendScriptsAndFinalize(ScriptPreloader::ScriptHash& scripts);
+
+private:
+    bool mWantCacheData = false;
+};
+
+
+} // namespace loader
+} // namespace mozilla
+
+#endif // ScriptCache_h
--- a/js/xpconnect/loader/ScriptPreloader-inl.h
+++ b/js/xpconnect/loader/ScriptPreloader-inl.h
@@ -4,27 +4,36 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef ScriptPreloader_inl_h
 #define ScriptPreloader_inl_h
 
 #include "mozilla/Attributes.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/CheckedInt.h"
+#include "mozilla/EnumSet.h"
 #include "mozilla/Range.h"
 #include "mozilla/Result.h"
 #include "mozilla/Unused.h"
+#include "mozilla/dom/ScriptSettings.h"
 #include "nsString.h"
 #include "nsTArray.h"
 
 #include <prio.h>
 
 namespace mozilla {
 namespace loader {
 
+using mozilla::dom::AutoJSAPI;
+
+struct MOZ_RAII AutoSafeJSAPI : public AutoJSAPI
+{
+    AutoSafeJSAPI() { Init(); }
+};
+
 static inline Result<Ok, nsresult>
 WrapNSResult(PRStatus aRv)
 {
     if (aRv != PR_SUCCESS) {
         return Err(NS_ERROR_FAILURE);
     }
     return Ok();
 }
@@ -51,16 +60,32 @@ public:
     write(size_t size)
     {
         auto buf = data.AppendElements(size);
         cursor_ += size;
         return buf;
     }
 
     void
+    codeUint8(const uint8_t& val)
+    {
+        *write(sizeof val) = val;
+    }
+
+    template<typename T>
+    void
+    codeUint8(const EnumSet<T>& val)
+    {
+        // EnumSets are always represented as uint32_t values, so we need to
+        // assert that the value actually fits in a uint8 before writing it.
+        uint32_t value = val.serialize();
+        codeUint8(CheckedUint8(value).value());
+    }
+
+    void
     codeUint16(const uint16_t& val)
     {
         LittleEndian::writeUint16(write(sizeof val), val);
     }
 
     void
     codeUint32(const uint32_t& val)
     {
@@ -101,16 +126,36 @@ public:
         MOZ_ASSERT(checkCapacity(size));
 
         auto buf = &data[cursor_];
         cursor_ += size;
         return buf;
     }
 
     bool
+    codeUint8(uint8_t& val)
+    {
+        if (checkCapacity(sizeof val)) {
+            val = *read(sizeof val);
+        }
+        return !error_;
+    }
+
+    template<typename T>
+    bool
+    codeUint8(EnumSet<T>& val)
+    {
+        uint8_t value;
+        if (codeUint8(value)) {
+            val.deserialize(value);
+        }
+        return !error_;
+    }
+
+    bool
     codeUint16(uint16_t& val)
     {
         if (checkCapacity(sizeof val)) {
             val = LittleEndian::readUint16(read(sizeof val));
         }
         return !error_;
     }
 
@@ -158,12 +203,128 @@ private:
 
     bool error_ = false;
 
 public:
     const Range<uint8_t>& data;
     size_t cursor_ = 0;
 };
 
+
+template <typename T> struct Matcher;
+
+// Wraps the iterator for a nsTHashTable so that it may be used as a range
+// iterator. Each iterator result acts as a smart pointer to the hash element,
+// and has a Remove() method which will remove the element from the hash.
+//
+// It also accepts an optional Matcher instance against which to filter the
+// elements which should be iterated over.
+//
+// Example:
+//
+//    for (auto& elem : HashElemIter<HashType>(hash)) {
+//        if (elem->IsDead()) {
+//            elem.Remove();
+//        }
+//    }
+template <typename T>
+class HashElemIter
+{
+    using Iterator = typename T::Iterator;
+    using ElemType = typename T::UserDataType;
+
+    T& hash_;
+    Matcher<ElemType>* matcher_;
+    Maybe<Iterator> iter_;
+
+public:
+    explicit HashElemIter(T& hash, Matcher<ElemType>* matcher = nullptr)
+        : hash_(hash), matcher_(matcher)
+    {
+        iter_.emplace(Move(hash.Iter()));
+    }
+
+    class Elem
+    {
+        friend class HashElemIter<T>;
+
+        HashElemIter<T>& iter_;
+        bool done_;
+
+        Elem(HashElemIter& iter, bool done)
+            : iter_(iter), done_(done)
+        {
+            skipNonMatching();
+        }
+
+        Iterator& iter() { return iter_.iter_.ref(); }
+
+        void skipNonMatching()
+        {
+            if (iter_.matcher_) {
+                while (!done_ && !iter_.matcher_->Matches(get())) {
+                    iter().Next();
+                    done_ = iter().Done();
+                }
+            }
+        }
+
+    public:
+        Elem& operator*() { return *this; }
+
+        ElemType get()
+        {
+          if (done_) {
+            return nullptr;
+          }
+          return iter().Data();
+        }
+
+        ElemType operator->() { return get(); }
+
+        operator ElemType() { return get(); }
+
+        void Remove() { iter().Remove(); }
+
+        Elem& operator++()
+        {
+            MOZ_ASSERT(!done_);
+
+            iter().Next();
+            done_ = iter().Done();
+
+            skipNonMatching();
+            return *this;
+        }
+
+        bool operator!=(Elem& other)
+        {
+            return done_ != other.done_ || this->get() != other.get();
+        }
+    };
+
+    Elem begin() { return Elem(*this, iter_->Done()); }
+
+    Elem end() { return Elem(*this, true); }
+};
+
+template <typename T>
+HashElemIter<T> IterHash(T& hash, Matcher<typename T::UserDataType>* matcher = nullptr)
+{
+    return HashElemIter<T>(hash, matcher);
+}
+
+template <typename T, typename F>
+bool
+Find(T&& iter, F&& match)
+{
+    for (auto& elem : iter) {
+        if (match(elem)) {
+            return true;
+        }
+    }
+    return false;
+}
+
 }; // namespace loader
 }; // namespace mozilla
 
 #endif // ScriptPreloader_inl_h
--- a/js/xpconnect/loader/ScriptPreloader.cpp
+++ b/js/xpconnect/loader/ScriptPreloader.cpp
@@ -1,64 +1,73 @@
 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
 /* vim: set ts=8 sts=4 et sw=4 tw=99: */
 /* 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/ScriptPreloader.h"
 #include "ScriptPreloader-inl.h"
+#include "mozilla/loader/ScriptCacheActors.h"
 
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/FileUtils.h"
 #include "mozilla/Logging.h"
+#include "mozilla/ScopeExit.h"
 #include "mozilla/Services.h"
 #include "mozilla/Unused.h"
-#include "mozilla/dom/ScriptSettings.h"
+#include "mozilla/dom/ContentChild.h"
+#include "mozilla/dom/ContentParent.h"
 
 #include "MainThreadUtils.h"
 #include "nsDebug.h"
 #include "nsDirectoryServiceUtils.h"
 #include "nsIFile.h"
 #include "nsIObserverService.h"
 #include "nsJSUtils.h"
 #include "nsProxyRelease.h"
 #include "nsThreadUtils.h"
 #include "nsXULAppAPI.h"
 #include "xpcpublic.h"
 
 #define DELAYED_STARTUP_TOPIC "browser-delayed-startup-finished"
+#define DOC_ELEM_INSERTED_TOPIC "document-element-inserted"
 #define CLEANUP_TOPIC "xpcom-shutdown"
 #define SHUTDOWN_TOPIC "quit-application-granted"
 #define CACHE_FLUSH_TOPIC "startupcache-invalidate"
 
 namespace mozilla {
 namespace {
 static LazyLogModule gLog("ScriptPreloader");
 
 #define LOG(level, ...) MOZ_LOG(gLog, LogLevel::level, (__VA_ARGS__))
 }
 
 using mozilla::dom::AutoJSAPI;
+using mozilla::dom::ContentChild;
+using mozilla::dom::ContentParent;
 using namespace mozilla::loader;
 
+ProcessType ScriptPreloader::sProcessType;
+
+
 nsresult
 ScriptPreloader::CollectReports(nsIHandleReportCallback* aHandleReport,
                                 nsISupports* aData, bool aAnonymize)
 {
     MOZ_COLLECT_REPORT(
         "explicit/script-preloader/heap/saved-scripts", KIND_HEAP, UNITS_BYTES,
-        SizeOfLinkedList(mSavedScripts, MallocSizeOf),
+        SizeOfHashEntries<ScriptStatus::Saved>(mScripts, MallocSizeOf),
         "Memory used to hold the scripts which have been executed in this "
         "session, and will be written to the startup script cache file.");
 
     MOZ_COLLECT_REPORT(
         "explicit/script-preloader/heap/restored-scripts", KIND_HEAP, UNITS_BYTES,
-        SizeOfLinkedList(mRestoredScripts, MallocSizeOf),
+        SizeOfHashEntries<ScriptStatus::Restored>(mScripts, MallocSizeOf),
         "Memory used to hold the scripts which have been restored from the "
         "startup script cache file, but have not been executed in this session.");
 
     MOZ_COLLECT_REPORT(
         "explicit/script-preloader/heap/other", KIND_HEAP, UNITS_BYTES,
         ShallowHeapSizeOfIncludingThis(MallocSizeOf),
         "Memory used by the script cache service itself.");
 
@@ -72,62 +81,155 @@ ScriptPreloader::CollectReports(nsIHandl
 
 
 ScriptPreloader&
 ScriptPreloader::GetSingleton()
 {
     static RefPtr<ScriptPreloader> singleton;
 
     if (!singleton) {
-        singleton = new ScriptPreloader();
+        if (XRE_IsParentProcess()) {
+            singleton = new ScriptPreloader();
+            singleton->mChildCache = &GetChildSingleton();
+            Unused << singleton->InitCache();
+        } else {
+            singleton = &GetChildSingleton();
+        }
+
         ClearOnShutdown(&singleton);
     }
 
     return *singleton;
 }
 
+// The child singleton is available in all processes, including the parent, and
+// is used for scripts which are expected to be loaded into child processes
+// (such as process and frame scripts), or scripts that have already been loaded
+// into a child. The child caches are managed as follows:
+//
+// - Every startup, we open the cache file from the last session, move it to a
+//  new location, and begin pre-loading the scripts that are stored in it. There
+//  is a separate cache file for parent and content processes, but the parent
+//  process opens both the parent and content cache files.
+//
+// - Once startup is complete, we write a new cache file for the next session,
+//   containing only the scripts that were used during early startup, so we don't
+//   waste pre-loading scripts that may not be needed.
+//
+// - For content processes, opening and writing the cache file is handled in the
+//  parent process. The first content process of each type sends back the data
+//  for scripts that were loaded in early startup, and the parent merges them and
+//  writes them to a cache file.
+//
+// - Currently, content processes only benefit from the cache data written
+//  during the *previous* session. Ideally, new content processes should probably
+//  use the cache data written during this session if there was no previous cache
+//  file, but I'd rather do that as a follow-up.
+ScriptPreloader&
+ScriptPreloader::GetChildSingleton()
+{
+    static RefPtr<ScriptPreloader> singleton;
+
+    if (!singleton) {
+        singleton = new ScriptPreloader();
+        if (XRE_IsParentProcess()) {
+            Unused << singleton->InitCache(NS_LITERAL_STRING("scriptCache-child"));
+        }
+        ClearOnShutdown(&singleton);
+    }
+
+    return *singleton;
+}
+
+void
+ScriptPreloader::InitContentChild(ContentParent& parent)
+{
+    auto& cache = GetChildSingleton();
+
+    // We want startup script data from the first process of a given type.
+    // That process sends back its script data before it executes any
+    // untrusted code, and then we never accept further script data for that
+    // type of process for the rest of the session.
+    //
+    // The script data from each process type is merged with the data from the
+    // parent process's frame and process scripts, and shared between all
+    // content process types in the next session.
+    //
+    // Note that if the first process of a given type crashes or shuts down
+    // before sending us its script data, we silently ignore it, and data for
+    // that process type is not included in the next session's cache. This
+    // should be a sufficiently rare occurrence that it's not worth trying to
+    // handle specially.
+    auto processType = GetChildProcessType(parent.GetRemoteType());
+    bool wantScriptData = !cache.mInitializedProcesses.contains(processType);
+    cache.mInitializedProcesses += processType;
+
+    auto fd = cache.mCacheData.cloneFileDescriptor();
+    if (fd.IsValid()) {
+        Unused << parent.SendPScriptCacheConstructor(fd, wantScriptData);
+    } else {
+        Unused << parent.SendPScriptCacheConstructor(NS_ERROR_FILE_NOT_FOUND, wantScriptData);
+    }
+}
+
+ProcessType
+ScriptPreloader::GetChildProcessType(const nsAString& remoteType)
+{
+    if (remoteType.EqualsLiteral(EXTENSION_REMOTE_TYPE)) {
+        return ProcessType::Extension;
+    }
+    return ProcessType::Web;
+}
+
 
 namespace {
 
-struct MOZ_RAII AutoSafeJSAPI : public AutoJSAPI
-{
-    AutoSafeJSAPI() { Init(); }
-};
-
-
 static void
 TraceOp(JSTracer* trc, void* data)
 {
     auto preloader = static_cast<ScriptPreloader*>(data);
 
     preloader->Trace(trc);
 }
 
 } // anonymous namespace
 
 void
 ScriptPreloader::Trace(JSTracer* trc)
 {
-    for (auto script : mSavedScripts) {
-        JS::TraceEdge(trc, &script->mScript, "ScriptPreloader::CachedScript.mScript");
-    }
-
-    for (auto script : mRestoredScripts) {
+    for (auto& script : IterHash(mScripts)) {
         JS::TraceEdge(trc, &script->mScript, "ScriptPreloader::CachedScript.mScript");
     }
 }
 
 
 ScriptPreloader::ScriptPreloader()
   : mMonitor("[ScriptPreloader.mMonitor]")
   , mSaveMonitor("[ScriptPreloader.mSaveMonitor]")
 {
+    if (XRE_IsParentProcess()) {
+        sProcessType = ProcessType::Parent;
+    } else {
+        sProcessType = GetChildProcessType(dom::ContentChild::GetSingleton()->GetRemoteType());
+    }
+
     nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
     MOZ_RELEASE_ASSERT(obs);
-    obs->AddObserver(this, DELAYED_STARTUP_TOPIC, false);
+
+    if (XRE_IsParentProcess()) {
+        // In the parent process, we want to freeze the script cache as soon
+        // as delayed startup for the first browser window has completed.
+        obs->AddObserver(this, DELAYED_STARTUP_TOPIC, false);
+    } else {
+        // In the child process, we need to freeze the script cache before any
+        // untrusted code has been executed. The insertion of the first DOM
+        // document element may sometimes be earlier than is ideal, but at
+        // least it should always be safe.
+        obs->AddObserver(this, DOC_ELEM_INSERTED_TOPIC, false);
+    }
     obs->AddObserver(this, SHUTDOWN_TOPIC, false);
     obs->AddObserver(this, CLEANUP_TOPIC, false);
     obs->AddObserver(this, CACHE_FLUSH_TOPIC, false);
 
     AutoSafeJSAPI jsapi;
     JS_AddExtraGCRootsTracer(jsapi.cx(), TraceOp, this);
 }
 
@@ -149,145 +251,171 @@ ScriptPreloader::Cleanup()
     if (mSaveThread) {
         MonitorAutoLock mal(mSaveMonitor);
 
         while (!mSaveComplete && mSaveThread) {
             mal.Wait();
         }
     }
 
-    mSavedScripts.clear();
-    mRestoredScripts.clear();
+    mScripts.Clear();
 
     AutoSafeJSAPI jsapi;
     JS_RemoveExtraGCRootsTracer(jsapi.cx(), TraceOp, this);
 
     UnregisterWeakMemoryReporter(this);
 }
 
 void
-ScriptPreloader::FlushScripts(LinkedList<CachedScript>& scripts)
+ScriptPreloader::FlushCache()
 {
-    for (auto next = scripts.getFirst(); next; ) {
-        auto script = next;
-        next = script->getNext();
+    MonitorAutoLock mal(mMonitor);
 
+    for (auto& script : IterHash(mScripts)) {
         // We can only purge finished scripts here. Async scripts that are
         // still being parsed off-thread have a non-refcounted reference to
         // this script, which needs to stay alive until they finish parsing.
         if (script->mReadyToExecute) {
             script->Cancel();
-            script->remove();
-            delete script;
+            script.Remove();
         }
     }
-}
-
-void
-ScriptPreloader::FlushCache()
-{
-    MonitorAutoLock mal(mMonitor);
-
-    FlushScripts(mSavedScripts);
-    FlushScripts(mRestoredScripts);
 
     // If we've already finished saving the cache at this point, start a new
     // delayed save operation. This will write out an empty cache file in place
     // of any cache file we've already written out this session, which will
     // prevent us from falling back to the current session's cache file on the
     // next startup.
-    if (mSaveComplete) {
+    if (mSaveComplete && mChildCache) {
         mSaveComplete = false;
 
         Unused << NS_NewNamedThread("SaveScripts",
                                     getter_AddRefs(mSaveThread), this);
     }
 }
 
 nsresult
 ScriptPreloader::Observe(nsISupports* subject, const char* topic, const char16_t* data)
 {
+    nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
     if (!strcmp(topic, DELAYED_STARTUP_TOPIC)) {
-        nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
         obs->RemoveObserver(this, DELAYED_STARTUP_TOPIC);
 
+        MOZ_ASSERT(XRE_IsParentProcess());
+
         mStartupFinished = true;
 
-        if (XRE_IsParentProcess()) {
+        if (mChildCache) {
             Unused << NS_NewNamedThread("SaveScripts",
                                         getter_AddRefs(mSaveThread), this);
         }
+    } else if (!strcmp(topic, DOC_ELEM_INSERTED_TOPIC)) {
+        obs->RemoveObserver(this, DOC_ELEM_INSERTED_TOPIC);
+
+        MOZ_ASSERT(XRE_IsContentProcess());
+
+        mStartupFinished = true;
+
+        if (mChildActor) {
+            mChildActor->SendScriptsAndFinalize(mScripts);
+        }
     } else if (!strcmp(topic, SHUTDOWN_TOPIC)) {
         ForceWriteCacheFile();
     } else if (!strcmp(topic, CLEANUP_TOPIC)) {
         Cleanup();
     } else if (!strcmp(topic, CACHE_FLUSH_TOPIC)) {
         FlushCache();
     }
 
     return NS_OK;
 }
 
 
 Result<nsCOMPtr<nsIFile>, nsresult>
-ScriptPreloader::GetCacheFile(const char* leafName)
+ScriptPreloader::GetCacheFile(const nsAString& suffix)
 {
     nsCOMPtr<nsIFile> cacheFile;
     NS_TRY(mProfD->Clone(getter_AddRefs(cacheFile)));
 
     NS_TRY(cacheFile->AppendNative(NS_LITERAL_CSTRING("startupCache")));
     Unused << cacheFile->Create(nsIFile::DIRECTORY_TYPE, 0777);
 
-    NS_TRY(cacheFile->AppendNative(nsDependentCString(leafName)));
+    NS_TRY(cacheFile->Append(mBaseName + suffix));
 
     return Move(cacheFile);
 }
 
-static const uint8_t MAGIC[] = "mozXDRcache";
+static const uint8_t MAGIC[] = "mozXDRcachev001";
 
 Result<Ok, nsresult>
 ScriptPreloader::OpenCache()
 {
     NS_TRY(NS_GetSpecialDirectory("ProfLDS", getter_AddRefs(mProfD)));
 
     nsCOMPtr<nsIFile> cacheFile;
-    MOZ_TRY_VAR(cacheFile, GetCacheFile("scriptCache.bin"));
+    MOZ_TRY_VAR(cacheFile, GetCacheFile(NS_LITERAL_STRING(".bin")));
 
     bool exists;
     NS_TRY(cacheFile->Exists(&exists));
     if (exists) {
-        NS_TRY(cacheFile->MoveTo(nullptr, NS_LITERAL_STRING("scriptCache-current.bin")));
+        NS_TRY(cacheFile->MoveTo(nullptr, mBaseName + NS_LITERAL_STRING("-current.bin")));
     } else {
-        NS_TRY(cacheFile->SetLeafName(NS_LITERAL_STRING("scriptCache-current.bin")));
+        NS_TRY(cacheFile->SetLeafName(mBaseName + NS_LITERAL_STRING("-current.bin")));
         NS_TRY(cacheFile->Exists(&exists));
         if (!exists) {
             return Err(NS_ERROR_FILE_NOT_FOUND);
         }
     }
 
     MOZ_TRY(mCacheData.init(cacheFile));
 
     return Ok();
 }
 
 // Opens the script cache file for this session, and initializes the script
 // cache based on its contents. See WriteCache for details of the cache file.
 Result<Ok, nsresult>
-ScriptPreloader::InitCache()
+ScriptPreloader::InitCache(const nsAString& basePath)
 {
     mCacheInitialized = true;
+    mBaseName = basePath;
 
     RegisterWeakMemoryReporter(this);
 
     if (!XRE_IsParentProcess()) {
         return Ok();
     }
 
     MOZ_TRY(OpenCache());
 
+    return InitCacheInternal();
+}
+
+Result<Ok, nsresult>
+ScriptPreloader::InitCache(const Maybe<ipc::FileDescriptor>& cacheFile, ScriptCacheChild* cacheChild)
+{
+    MOZ_ASSERT(XRE_IsContentProcess());
+
+    mCacheInitialized = true;
+    mChildActor = cacheChild;
+
+    RegisterWeakMemoryReporter(this);
+
+    if (cacheFile.isNothing()){
+        return Ok();
+    }
+
+    MOZ_TRY(mCacheData.init(cacheFile.ref()));
+
+    return InitCacheInternal();
+}
+
+Result<Ok, nsresult>
+ScriptPreloader::InitCacheInternal()
+{
     auto size = mCacheData.size();
 
     uint32_t headerSize;
     if (size < sizeof(MAGIC) + sizeof(headerSize)) {
         return Err(NS_ERROR_UNEXPECTED);
     }
 
     auto data = mCacheData.get<uint8_t>();
@@ -300,65 +428,72 @@ ScriptPreloader::InitCache()
 
     headerSize = LittleEndian::readUint32(data.get());
     data += sizeof(headerSize);
 
     if (data + headerSize > end) {
         return Err(NS_ERROR_UNEXPECTED);
     }
 
+    AutoTArray<CachedScript*, 256> scripts;
+
     {
-        AutoCleanLinkedList<CachedScript> scripts;
+        auto cleanup = MakeScopeExit([&] () {
+            mScripts.Clear();
+        });
 
         Range<uint8_t> header(data, data + headerSize);
         data += headerSize;
 
         InputBuffer buf(header);
 
         size_t offset = 0;
         while (!buf.finished()) {
-            auto script = MakeUnique<CachedScript>(buf);
+            auto script = MakeUnique<CachedScript>(*this, buf);
+            MOZ_RELEASE_ASSERT(script);
 
             auto scriptData = data + script->mOffset;
             if (scriptData + script->mSize > end) {
                 return Err(NS_ERROR_UNEXPECTED);
             }
 
             // Make sure offsets match what we'd expect based on script ordering and
             // size, as a basic sanity check.
             if (script->mOffset != offset) {
                 return Err(NS_ERROR_UNEXPECTED);
             }
             offset += script->mSize;
 
             script->mXDRRange.emplace(scriptData, scriptData + script->mSize);
 
-            scripts.insertBack(script.release());
+            scripts.AppendElement(script.get());
+            mScripts.Put(script->mCachePath, script.get());
+            Unused << script.release();
         }
 
         if (buf.error()) {
             return Err(NS_ERROR_UNEXPECTED);
         }
 
-        for (auto script : scripts) {
-            mScripts.Put(script->mCachePath, script);
-        }
-        mRestoredScripts = Move(scripts);
+        cleanup.release();
     }
 
     AutoJSAPI jsapi;
     MOZ_RELEASE_ASSERT(jsapi.Init(xpc::CompilationScope()));
     JSContext* cx = jsapi.cx();
 
     auto start = TimeStamp::Now();
     LOG(Info, "Off-thread decoding scripts...\n");
 
     JS::CompileOptions options(cx, JSVERSION_LATEST);
-    for (auto script : mRestoredScripts) {
-        if (script->mSize > MIN_OFFTHREAD_SIZE &&
+
+    for (auto& script : scripts) {
+        // Only async decode scripts which have been used in this process type.
+        if (script->mProcessTypes.contains(CurrentProcessType()) &&
+            script->AsyncDecodable() &&
             JS::CanCompileOffThread(cx, options, script->mSize)) {
             DecodeScriptOffThread(cx, script);
         } else {
             script->mReadyToExecute = true;
         }
     }
 
     LOG(Info, "Initialized decoding in %fms\n",
@@ -376,78 +511,74 @@ Write(PRFileDesc* fd, const void* data, 
     return Ok();
 }
 
 void
 ScriptPreloader::PrepareCacheWrite()
 {
     MOZ_ASSERT(NS_IsMainThread());
 
+    auto cleanup = MakeScopeExit([&] () {
+        if (mChildCache) {
+            mChildCache->PrepareCacheWrite();
+        }
+    });
+
     if (mDataPrepared) {
         return;
     }
 
-    if (mRestoredScripts.isEmpty()) {
-        // Check for any new scripts that we need to save. If there aren't
-        // any, and there aren't any saved scripts that we need to remove,
-        // don't bother writing out a new cache file.
-        bool found = false;
-        for (auto script : mSavedScripts) {
-            if (script->mXDRRange.isNothing()) {
-                found = true;
-                break;
-            }
-        }
-        if (!found) {
-            mSaveComplete = true;
-            return;
-        }
+    bool found = Find(IterHash(mScripts), [] (CachedScript* script) {
+        return (script->mStatus == ScriptStatus::Restored ||
+                !script->HasRange() || script->HasArray());
+    });
+
+    if (!found) {
+        mSaveComplete = true;
+        return;
     }
 
     AutoSafeJSAPI jsapi;
 
-    LinkedList<CachedScript> asyncScripts;
+    for (auto& script : IterHash(mScripts, Match<ScriptStatus::Saved>())) {
+        // Don't write any scripts that are also in the child cache. They'll be
+        // loaded from the child cache in that case, so there's no need to write
+        // them twice.
+        CachedScript* childScript = mChildCache ? mChildCache->mScripts.Get(script->mCachePath) : nullptr;
+        if (childScript) {
+            if (childScript->mStatus == ScriptStatus::Saved) {
+                childScript->UpdateLoadTime(script->mLoadTime);
+                childScript->mProcessTypes += script->mProcessTypes;
+            } else {
+                childScript = nullptr;
+            }
+        }
 
-    for (CachedScript* next = mSavedScripts.getFirst(); next; ) {
-        CachedScript* script = next;
-        next = script->getNext();
-
-        if (!script->mSize && !script->XDREncode(jsapi.cx())) {
-            script->remove();
-            delete script;
+        if (childScript || (!script->mSize && !script->XDREncode(jsapi.cx()))) {
+            script.Remove();
         } else {
             script->mSize = script->Range().length();
-
-            if (script->mSize > MIN_OFFTHREAD_SIZE) {
-                script->remove();
-                asyncScripts.insertBack(script);
-            }
         }
     }
 
-    // Store async-decoded scripts contiguously, since they're loaded
-    // immediately at startup.
-    while (CachedScript* s = asyncScripts.popLast()) {
-        mSavedScripts.insertFront(s);
-    }
-
     mDataPrepared = true;
 }
 
 // Writes out a script cache file for the scripts accessed during early
 // startup in this session. The cache file is a little-endian binary file with
 // the following format:
 //
 // - A uint32 containing the size of the header block.
 //
 // - A header entry for each file stored in the cache containing:
 //   - The URL that the script was originally read from.
 //   - Its cache key.
 //   - The offset of its XDR data within the XDR data block.
 //   - The size of its XDR data in the XDR data block.
+//   - A bit field describing which process types the script is used in.
 //
 // - A block of XDR data for the encoded scripts, with each script's data at
 //   an offset from the start of the block, as specified above.
 Result<Ok, nsresult>
 ScriptPreloader::WriteCache()
 {
     MOZ_ASSERT(!NS_IsMainThread());
 
@@ -460,127 +591,160 @@ ScriptPreloader::WriteCache()
     }
 
     if (mSaveComplete) {
         // If we don't have anything we need to save, we're done.
         return Ok();
     }
 
     nsCOMPtr<nsIFile> cacheFile;
-    MOZ_TRY_VAR(cacheFile, GetCacheFile("scriptCache-new.bin"));
+    MOZ_TRY_VAR(cacheFile, GetCacheFile(NS_LITERAL_STRING("-new.bin")));
 
     bool exists;
     NS_TRY(cacheFile->Exists(&exists));
     if (exists) {
         NS_TRY(cacheFile->Remove(false));
     }
 
-    AutoFDClose fd;
-    NS_TRY(cacheFile->OpenNSPRFileDesc(PR_WRONLY | PR_CREATE_FILE, 0644, &fd.rwget()));
+    {
+        AutoFDClose fd;
+        NS_TRY(cacheFile->OpenNSPRFileDesc(PR_WRONLY | PR_CREATE_FILE, 0644, &fd.rwget()));
+
+        nsTArray<CachedScript*> scripts;
+        for (auto& script : IterHash(mScripts, Match<ScriptStatus::Saved>())) {
+            scripts.AppendElement(script);
+        }
+
+        // Sort scripts by load time, with async loaded scripts before sync scripts.
+        // Since async scripts are always loaded immediately at startup, it helps to
+        // have them stored contiguously.
+        scripts.Sort(CachedScript::Comparator());
 
-    OutputBuffer buf;
-    size_t offset = 0;
-    for (auto script : mSavedScripts) {
-        script->mOffset = offset;
-        script->Code(buf);
+        OutputBuffer buf;
+        size_t offset = 0;
+        for (auto script : scripts) {
+            script->mOffset = offset;
+            script->Code(buf);
+
+            offset += script->mSize;
+        }
 
-        offset += script->mSize;
+        uint8_t headerSize[4];
+        LittleEndian::writeUint32(headerSize, buf.cursor());
+
+        MOZ_TRY(Write(fd, MAGIC, sizeof(MAGIC)));
+        MOZ_TRY(Write(fd, headerSize, sizeof(headerSize)));
+        MOZ_TRY(Write(fd, buf.Get(), buf.cursor()));
+        for (auto script : scripts) {
+            MOZ_TRY(Write(fd, script->Range().begin().get(), script->mSize));
+
+            if (script->mScript) {
+                script->FreeData();
+            }
+        }
     }
 
-    uint8_t headerSize[4];
-    LittleEndian::writeUint32(headerSize, buf.cursor());
-
-    MOZ_TRY(Write(fd, MAGIC, sizeof(MAGIC)));
-    MOZ_TRY(Write(fd, headerSize, sizeof(headerSize)));
-    MOZ_TRY(Write(fd, buf.Get(), buf.cursor()));
-
-    for (auto script : mSavedScripts) {
-        MOZ_TRY(Write(fd, script->Range().begin().get(), script->mSize));
-        script->mXDRData.reset();
-    }
-
-    NS_TRY(cacheFile->MoveTo(nullptr, NS_LITERAL_STRING("scriptCache.bin")));
+    NS_TRY(cacheFile->MoveTo(nullptr, mBaseName + NS_LITERAL_STRING(".bin")));
 
     return Ok();
 }
 
 // Runs in the mSaveThread thread, and writes out the cache file for the next
 // session after a reasonable delay.
 nsresult
 ScriptPreloader::Run()
 {
     MonitorAutoLock mal(mSaveMonitor);
 
     // Ideally wait about 10 seconds before saving, to avoid unnecessary IO
     // during early startup.
     mal.Wait(10000);
 
-    Unused << WriteCache();
+    auto result = WriteCache();
+    Unused << NS_WARN_IF(result.isErr());
+
+    result = mChildCache->WriteCache();
+    Unused << NS_WARN_IF(result.isErr());
 
     mSaveComplete = true;
     NS_ReleaseOnMainThread(mSaveThread.forget());
 
     mal.NotifyAll();
     return NS_OK;
 }
 
-/* static */ ScriptPreloader::CachedScript*
-ScriptPreloader::FindScript(LinkedList<CachedScript>& scripts, const nsCString& cachePath)
-{
-    for (auto script : scripts) {
-        if (script->mCachePath == cachePath) {
-            return script;
-        }
-    }
-    return nullptr;
-}
-
 void
 ScriptPreloader::NoteScript(const nsCString& url, const nsCString& cachePath,
-                            JS::HandleScript script)
+                            JS::HandleScript jsscript)
 {
-    if (mStartupFinished || !mCacheInitialized) {
-        return;
-    }
     // Don't bother trying to cache any URLs with cache-busting query
     // parameters.
-    if (cachePath.FindChar('?') >= 0) {
+    if (mStartupFinished || !mCacheInitialized || cachePath.FindChar('?') >= 0) {
         return;
     }
 
     // Don't bother caching files that belong to the mochitest harness.
     NS_NAMED_LITERAL_CSTRING(mochikitPrefix, "chrome://mochikit/");
     if (StringHead(url, mochikitPrefix.Length()) == mochikitPrefix) {
         return;
     }
 
-    bool exists = mScripts.Get(cachePath);
+    auto script = mScripts.LookupOrAdd(cachePath, *this, url, cachePath, jsscript);
 
-    CachedScript* restored = nullptr;
-    if (exists) {
-        restored = FindScript(mRestoredScripts, cachePath);
+    if (script->mStatus == ScriptStatus::Restored) {
+        script->mStatus = ScriptStatus::Saved;
+
+        MOZ_ASSERT(jsscript);
+        script->mScript = jsscript;
+        script->mReadyToExecute = true;
     }
 
-    if (restored) {
-        restored->remove();
-        mSavedScripts.insertBack(restored);
+    script->UpdateLoadTime(TimeStamp::Now());
+    script->mProcessTypes += CurrentProcessType();
+}
+
+void
+ScriptPreloader::NoteScript(const nsCString& url, const nsCString& cachePath,
+                            ProcessType processType, nsTArray<uint8_t>&& xdrData,
+                            TimeStamp loadTime)
+{
+    auto script = mScripts.LookupOrAdd(cachePath, *this, url, cachePath, nullptr);
+
+    if (script->mStatus == ScriptStatus::Restored) {
+        script->mStatus = ScriptStatus::Saved;
 
-        MOZ_ASSERT(script);
-        restored->mScript = script;
-        restored->mReadyToExecute = true;
-    } else if (!exists) {
-        auto cachedScript = new CachedScript(url, cachePath, script);
-        mSavedScripts.insertBack(cachedScript);
-        mScripts.Put(cachePath, cachedScript);
+        script->mReadyToExecute = true;
+    } else {
+        if (!script->HasRange()) {
+            MOZ_ASSERT(!script->HasArray());
+
+            script->mSize = xdrData.Length();
+            script->mXDRData.construct<nsTArray<uint8_t>>(Forward<nsTArray<uint8_t>>(xdrData));
+
+            auto& data = script->Array();
+            script->mXDRRange.emplace(data.Elements(), data.Length());
+        }
     }
+
+    script->UpdateLoadTime(loadTime);
+    script->mProcessTypes += processType;
 }
 
 JSScript*
 ScriptPreloader::GetCachedScript(JSContext* cx, const nsCString& path)
 {
+    // If a script is used by both the parent and the child, it's stored only
+    // in the child cache.
+    if (mChildCache) {
+        auto script = mChildCache->GetCachedScript(cx, path);
+        if (script) {
+            return script;
+        }
+    }
+
     auto script = mScripts.Get(path);
     if (script) {
         return WaitForCachedScript(cx, script);
     }
 
     return nullptr;
 }
 
@@ -632,64 +796,65 @@ ScriptPreloader::CancelOffThreadParse(vo
     JS::CancelOffThreadScriptDecoder(jsapi.cx(), token);
 }
 
 /* static */ void
 ScriptPreloader::OffThreadDecodeCallback(void* token, void* context)
 {
     auto script = static_cast<CachedScript*>(context);
 
-    MonitorAutoLock mal(GetSingleton().mMonitor);
+    MonitorAutoLock mal(script->mCache.mMonitor);
 
     if (script->mReadyToExecute) {
         // We've already executed this script on the main thread, and opted to
         // main thread decode it rather waiting for off-thread decoding to
         // finish. So just cancel the off-thread parse rather than completing
         // it.
         NS_DispatchToMainThread(
-            NewRunnableMethod<void*>(&GetSingleton(),
+            NewRunnableMethod<void*>(&script->mCache,
                                      &ScriptPreloader::CancelOffThreadParse,
                                      token));
         return;
     }
 
     script->mToken = token;
     script->mReadyToExecute = true;
 
     mal.NotifyAll();
 }
 
-inline
-ScriptPreloader::CachedScript::CachedScript(InputBuffer& buf)
+ScriptPreloader::CachedScript::CachedScript(ScriptPreloader& cache, InputBuffer& buf)
+    : mCache(cache)
+    , mStatus(ScriptStatus::Restored)
 {
     Code(buf);
 }
 
 bool
 ScriptPreloader::CachedScript::XDREncode(JSContext* cx)
 {
     JSAutoCompartment ac(cx, mScript);
     JS::RootedScript jsscript(cx, mScript);
 
-    mXDRData.emplace();
+    mXDRData.construct<JS::TranscodeBuffer>();
 
-    JS::TranscodeResult code = JS::EncodeScript(cx, Data(), jsscript);
+    JS::TranscodeResult code = JS::EncodeScript(cx, Buffer(), jsscript);
     if (code == JS::TranscodeResult_Ok) {
-        mXDRRange.emplace(Data().begin(), Data().length());
+        mXDRRange.emplace(Buffer().begin(), Buffer().length());
         return true;
     }
     JS_ClearPendingException(cx);
     return false;
 }
 
 void
 ScriptPreloader::CachedScript::Cancel()
 {
     if (mToken) {
-        GetSingleton().mMonitor.AssertCurrentThreadOwns();
+        mCache.mMonitor.AssertCurrentThreadOwns();
 
         AutoSafeJSAPI jsapi;
         JS::CancelOffThreadScriptDecoder(jsapi.cx(), mToken);
 
         mReadyToExecute = true;
         mToken = nullptr;
     }
 }
@@ -703,21 +868,25 @@ ScriptPreloader::CachedScript::GetJSScri
     }
 
     // If we have no token at this point, the script was too small to decode
     // off-thread, or it was needed before the off-thread compilation was
     // finished, and is small enough to decode on the main thread rather than
     // wait for the off-thread decoding to finish. In either case, we decode
     // it synchronously the first time it's needed.
     if (!mToken) {
-        MOZ_ASSERT(mXDRRange.isSome());
+        MOZ_ASSERT(HasRange());
 
         JS::RootedScript script(cx);
         if (JS::DecodeScript(cx, Range(), &script)) {
             mScript = script;
+
+            if (mCache.mSaveComplete) {
+                FreeData();
+            }
         }
 
         return mScript;
     }
 
     Maybe<JSAutoCompartment> ac;
     if (JS::CompartmentCreationOptionsRef(cx).addonIdOrNull()) {
         // Make sure we never try to finish the parse in a compartment with an
--- a/js/xpconnect/loader/ScriptPreloader.h
+++ b/js/xpconnect/loader/ScriptPreloader.h
@@ -1,73 +1,123 @@
 /* -*-  Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4; -*- */
 /* 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 ScriptPreloader_h
 #define ScriptPreloader_h
 
+#include "mozilla/CheckedInt.h"
+#include "mozilla/EnumSet.h"
 #include "mozilla/LinkedList.h"
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/Maybe.h"
+#include "mozilla/MaybeOneOf.h"
 #include "mozilla/Monitor.h"
 #include "mozilla/Range.h"
 #include "mozilla/Vector.h"
 #include "mozilla/Result.h"
 #include "mozilla/loader/AutoMemMap.h"
-#include "nsDataHashtable.h"
+#include "nsClassHashtable.h"
 #include "nsIFile.h"
 #include "nsIMemoryReporter.h"
 #include "nsIObserver.h"
 #include "nsIThread.h"
 
 #include "jsapi.h"
+#include "js/GCAnnotations.h"
 
 #include <prio.h>
 
 namespace mozilla {
+namespace dom {
+    class ContentParent;
+}
+namespace ipc {
+    class FileDescriptor;
+}
 namespace loader {
     class InputBuffer;
+    class ScriptCacheChild;
+
+    enum class ProcessType : uint8_t {
+        Parent,
+        Web,
+        Extension,
+    };
+
+    template <typename T>
+    struct Matcher
+    {
+        virtual bool Matches(T) = 0;
+    };
 }
 
 using namespace mozilla::loader;
 
 class ScriptPreloader : public nsIObserver
                       , public nsIMemoryReporter
                       , public nsIRunnable
 {
     MOZ_DEFINE_MALLOC_SIZE_OF(MallocSizeOf)
 
+    friend class mozilla::loader::ScriptCacheChild;
+
 public:
     NS_DECL_THREADSAFE_ISUPPORTS
     NS_DECL_NSIOBSERVER
     NS_DECL_NSIMEMORYREPORTER
     NS_DECL_NSIRUNNABLE
 
     static ScriptPreloader& GetSingleton();
+    static ScriptPreloader& GetChildSingleton();
+
+    static ProcessType GetChildProcessType(const nsAString& remoteType);
 
     // Retrieves the script with the given cache key from the script cache.
     // Returns null if the script is not cached.
     JSScript* GetCachedScript(JSContext* cx, const nsCString& name);
 
     // Notes the execution of a script with the given URL and cache key.
     // Depending on the stage of startup, the script may be serialized and
     // stored to the startup script cache.
     void NoteScript(const nsCString& url, const nsCString& cachePath, JS::HandleScript script);
 
+    void NoteScript(const nsCString& url, const nsCString& cachePath,
+                    ProcessType processType, nsTArray<uint8_t>&& xdrData,
+                    TimeStamp loadTime);
+
     // Initializes the script cache from the startup script cache file.
-    Result<Ok, nsresult> InitCache();
+    Result<Ok, nsresult> InitCache(const nsAString& = NS_LITERAL_STRING("scriptCache"));
+
+    Result<Ok, nsresult> InitCache(const Maybe<ipc::FileDescriptor>& cacheFile, ScriptCacheChild* cacheChild);
+
+private:
+    Result<Ok, nsresult> InitCacheInternal();
 
+public:
     void Trace(JSTracer* trc);
 
+    static ProcessType CurrentProcessType()
+    {
+        return sProcessType;
+    }
+
+    static void InitContentChild(dom::ContentParent& parent);
+
 protected:
     virtual ~ScriptPreloader() = default;
 
 private:
+    enum class ScriptStatus {
+      Restored,
+      Saved,
+    };
+
     // Represents a cached JS script, either initially read from the script
     // cache file, to be added to the next session's script cache file, or
     // both.
     //
     // A script which was read from the cache file may be in any of the
     // following states:
     //
     //  - Read from the cache, and being compiled off thread. In this case,
@@ -82,118 +132,206 @@ private:
     //    file. In this case, mReadyToExecute is true, and mScript is non-null.
     //
     // A script to be added to the next session's cache file always has a
     // non-null mScript value. If it was read from the last session's cache
     // file, it also has a non-empty mXDRRange range, which will be stored in
     // the next session's cache file. If it was compiled in this session, its
     // mXDRRange will initially be empty, and its mXDRData buffer will be
     // populated just before it is written to the cache file.
-    class CachedScript : public LinkedListElement<CachedScript>
+    class CachedScript
     {
     public:
         CachedScript(CachedScript&&) = default;
 
-        CachedScript(const nsCString& url, const nsCString& cachePath, JSScript* script)
-            : mURL(url)
+        CachedScript(ScriptPreloader& cache, const nsCString& url, const nsCString& cachePath, JSScript* script)
+            : mCache(cache)
+            , mURL(url)
             , mCachePath(cachePath)
+            , mStatus(ScriptStatus::Saved)
             , mScript(script)
             , mReadyToExecute(true)
         {}
 
-        explicit inline CachedScript(InputBuffer& buf);
+        inline CachedScript(ScriptPreloader& cache, InputBuffer& buf);
+
+        ~CachedScript() = default;
 
-        ~CachedScript()
+        // For use with nsTArray::Sort.
+        //
+        // Orders scripts by:
+        //
+        // 1) Async-decoded scripts before sync-decoded scripts, since the
+        //    former are needed immediately at startup, and should be stored
+        //    contiguously.
+        // 2) Script load time, so that scripts which are needed earlier are
+        //    stored earlier, and scripts needed at approximately the same
+        //    time are stored approximately contiguously.
+        struct Comparator
         {
-            auto& cache = GetSingleton();
-#ifdef DEBUG
-            auto hashValue = cache.mScripts.Get(mCachePath);
-            MOZ_ASSERT_IF(hashValue, hashValue == this);
-#endif
-            cache.mScripts.Remove(mCachePath);
+            bool Equals(const CachedScript* a, const CachedScript* b) const
+            {
+              return (a->AsyncDecodable() == b->AsyncDecodable() &&
+                      a->mLoadTime == b->mLoadTime);
+            }
+
+            bool LessThan(const CachedScript* a, const CachedScript* b) const
+            {
+              if (a->AsyncDecodable() != b->AsyncDecodable()) {
+                return a->AsyncDecodable();
+              }
+              return a->mLoadTime < b->mLoadTime;
+            }
+        };
+
+        struct StatusMatcher final : public Matcher<CachedScript*>
+        {
+            explicit StatusMatcher(ScriptStatus status) : mStatus(status) {}
+
+            virtual bool Matches(CachedScript* script)
+            {
+                return script->mStatus == mStatus;
+            }
+
+            const ScriptStatus mStatus;
+        };
+
+        void Cancel();
+
+        void FreeData()
+        {
+            // If the script data isn't mmapped, we need to release both it
+            // and the Range that points to it at the same time.
+            if (!mXDRData.empty()) {
+                mXDRRange.reset();
+                mXDRData.destroy();
+            }
         }
 
-        void Cancel();
+        void UpdateLoadTime(const TimeStamp& loadTime)
+        {
+          if (mLoadTime.IsNull() || loadTime < mLoadTime) {
+            mLoadTime = loadTime;
+          }
+        }
+
+        bool AsyncDecodable() const { return mSize > MIN_OFFTHREAD_SIZE; }
 
         // Encodes this script into XDR data, and stores the result in mXDRData.
         // Returns true on success, false on failure.
         bool XDREncode(JSContext* cx);
 
         // Encodes or decodes this script, in the storage format required by the
         // script cache file.
         template<typename Buffer>
         void Code(Buffer& buffer)
         {
             buffer.codeString(mURL);
             buffer.codeString(mCachePath);
             buffer.codeUint32(mOffset);
             buffer.codeUint32(mSize);
+            buffer.codeUint8(mProcessTypes);
         }
 
         // Returns the XDR data generated for this script during this session. See
         // mXDRData.
-        JS::TranscodeBuffer& Data()
+        JS::TranscodeBuffer& Buffer()
         {
-            MOZ_ASSERT(mXDRData.isSome());
-            return mXDRData.ref();
+            MOZ_ASSERT(HasBuffer());
+            return mXDRData.ref<JS::TranscodeBuffer>();
         }
 
+        bool HasBuffer() { return mXDRData.constructed<JS::TranscodeBuffer>(); }
+
         // Returns the read-only XDR data for this script. See mXDRRange.
         const JS::TranscodeRange& Range()
         {
-            MOZ_ASSERT(mXDRRange.isSome());
+            MOZ_ASSERT(HasRange());
             return mXDRRange.ref();
         }
 
+        bool HasRange() { return mXDRRange.isSome(); }
+
+        nsTArray<uint8_t>& Array()
+        {
+            MOZ_ASSERT(HasArray());
+            return mXDRData.ref<nsTArray<uint8_t>>();
+        }
+
+        bool HasArray() { return mXDRData.constructed<nsTArray<uint8_t>>(); }
+
+
         JSScript* GetJSScript(JSContext* cx);
 
         size_t HeapSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf)
         {
             auto size = mallocSizeOf(this);
-            if (mXDRData.isSome()) {
-                size += (mXDRData->sizeOfExcludingThis(mallocSizeOf) +
-                         mURL.SizeOfExcludingThisEvenIfShared(mallocSizeOf) +
-                         mCachePath.SizeOfExcludingThisEvenIfShared(mallocSizeOf));
+
+            if (HasArray()) {
+                size += Array().ShallowSizeOfExcludingThis(mallocSizeOf);
+            } else if (HasBuffer()) {
+                size += Buffer().sizeOfExcludingThis(mallocSizeOf);
+            } else {
+                return size;
             }
+
+            size += (mURL.SizeOfExcludingThisEvenIfShared(mallocSizeOf) +
+                     mCachePath.SizeOfExcludingThisEvenIfShared(mallocSizeOf));
             return size;
         }
 
+        ScriptPreloader& mCache;
+
         // The URL from which this script was initially read and compiled.
         nsCString mURL;
         // A unique identifier for this script's filesystem location, used as a
         // primary cache lookup value.
         nsCString mCachePath;
 
         // The offset of this script in the cache file, from the start of the XDR
         // data block.
         uint32_t mOffset = 0;
         // The size of this script's encoded XDR data.
         uint32_t mSize = 0;
 
+        ScriptStatus mStatus;
+
+        TimeStamp mLoadTime{};
+
         JS::Heap<JSScript*> mScript;
 
         // True if this script is ready to be executed. This means that either the
         // off-thread portion of an off-thread decode has finished, or the script
         // is too small to be decoded off-thread, and may be immediately decoded
         // whenever it is first executed.
         bool mReadyToExecute = false;
 
         // The off-thread decode token for a completed off-thread decode, which
         // has not yet been finalized on the main thread.
         void* mToken = nullptr;
 
+        // The set of processes in which this script has been used.
+        EnumSet<ProcessType> mProcessTypes{};
+
         // The read-only XDR data for this script, which was either read from an
         // existing cache file, or generated by encoding a script which was
         // compiled during this session.
         Maybe<JS::TranscodeRange> mXDRRange;
 
         // XDR data which was generated from a script compiled during this
         // session, and will be written to the cache file.
-        Maybe<JS::TranscodeBuffer> mXDRData;
-    };
+        MaybeOneOf<JS::TranscodeBuffer, nsTArray<uint8_t>> mXDRData;
+    } JS_HAZ_NON_GC_POINTER;
+
+    template <ScriptStatus status>
+    static Matcher<CachedScript*>* Match()
+    {
+        static CachedScript::StatusMatcher matcher{status};
+        return &matcher;
+    }
 
     // There's a trade-off between the time it takes to setup an off-thread
     // decode and the time we save by doing the decode off-thread. At this
     // point, the setup is quite expensive, and 20K is about where we start to
     // see an improvement rather than a regression.
     //
     // This also means that we get much better performance loading one big
     // script than several small scripts, since the setup is per-script, and the
@@ -208,34 +346,31 @@ private:
     static constexpr int MAX_MAINTHREAD_DECODE_SIZE = 50 * 1024;
 
     ScriptPreloader();
 
     void ForceWriteCacheFile();
     void Cleanup();
 
     void FlushCache();
-    void FlushScripts(LinkedList<CachedScript>& scripts);
 
     // Opens the cache file for reading.
     Result<Ok, nsresult> OpenCache();
 
     // Writes a new cache file to disk. Must not be called on the main thread.
     Result<Ok, nsresult> WriteCache();
 
     // Prepares scripts for writing to the cache, serializing new scripts to
     // XDR, and calculating their size-based offsets.
     void PrepareCacheWrite();
 
     // Returns a file pointer for the cache file with the given name in the
     // current profile.
     Result<nsCOMPtr<nsIFile>, nsresult>
-    GetCacheFile(const char* leafName);
-
-    static CachedScript* FindScript(LinkedList<CachedScript>& scripts, const nsCString& cachePath);
+    GetCacheFile(const nsAString& suffix);
 
     // Waits for the given cached script to finish compiling off-thread, or
     // decodes it synchronously on the main thread, as appropriate.
     JSScript* WaitForCachedScript(JSContext* cx, CachedScript* script);
 
     // Begins decoding the given script in a background thread.
     void DecodeScriptOffThread(JSContext* cx, CachedScript* script);
 
@@ -243,45 +378,50 @@ private:
     void CancelOffThreadParse(void* token);
 
     size_t ShallowHeapSizeOfIncludingThis(mozilla::MallocSizeOf mallocSizeOf)
     {
         return (mallocSizeOf(this) + mScripts.ShallowSizeOfExcludingThis(mallocSizeOf) +
                 mallocSizeOf(mSaveThread.get()) + mallocSizeOf(mProfD.get()));
     }
 
-    template<typename T>
-    static size_t SizeOfLinkedList(LinkedList<T>& list, mozilla::MallocSizeOf mallocSizeOf)
+    using ScriptHash = nsClassHashtable<nsCStringHashKey, CachedScript>;
+
+    template<ScriptStatus status>
+    static size_t SizeOfHashEntries(ScriptHash& scripts, mozilla::MallocSizeOf mallocSizeOf)
     {
         size_t size = 0;
-        for (auto elem : list) {
+        for (auto elem : IterHash(scripts, Match<status>())) {
             size += elem->HeapSizeOfIncludingThis(mallocSizeOf);
         }
         return size;
     }
 
-    // The list of scripts executed during this session, and being saved for
-    // potential reuse, and to be written to the next session's cache file.
-    AutoCleanLinkedList<CachedScript> mSavedScripts;
-
-    // The list of scripts restored from the cache file at the start of this
-    // session. Scripts are removed from this list and moved to mSavedScripts
-    // the first time they're used during this session.
-    AutoCleanLinkedList<CachedScript> mRestoredScripts;
-
-    nsDataHashtable<nsCStringHashKey, CachedScript*> mScripts;
+    ScriptHash mScripts;
 
     // True after we've shown the first window, and are no longer adding new
     // scripts to the cache.
     bool mStartupFinished = false;
 
     bool mCacheInitialized = false;
     bool mSaveComplete = false;
     bool mDataPrepared = false;
 
+    // The process type of the current process.
+    static ProcessType sProcessType;
+
+    // The process types for which remote processes have been initialized, and
+    // are expected to send back script data.
+    EnumSet<ProcessType> mInitializedProcesses{};
+
+    RefPtr<ScriptPreloader> mChildCache;
+    ScriptCacheChild* mChildActor = nullptr;
+
+    nsString mBaseName;
+
     nsCOMPtr<nsIFile> mProfD;
     nsCOMPtr<nsIThread> mSaveThread;
 
     // The mmapped cache data from this session's cache file.
     AutoMemMap mCacheData;
 
     Monitor mMonitor;
     Monitor mSaveMonitor;
--- a/js/xpconnect/loader/moz.build
+++ b/js/xpconnect/loader/moz.build
@@ -4,44 +4,52 @@
 # 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/.
 
 UNIFIED_SOURCES += [
     'AutoMemMap.cpp',
     'ChromeScriptLoader.cpp',
     'mozJSLoaderUtils.cpp',
     'mozJSSubScriptLoader.cpp',
+    'ScriptCacheActors.cpp',
     'ScriptPreloader.cpp',
 ]
 
 # mozJSComponentLoader.cpp cannot be built in unified mode because it uses
 # windows.h
 SOURCES += [
     'mozJSComponentLoader.cpp'
 ]
 
+IPDL_SOURCES += [
+    'PScriptCache.ipdl',
+]
+
 EXPORTS.mozilla += [
     'ScriptPreloader.h',
 ]
 
 EXPORTS.mozilla.dom += [
     'PrecompiledScript.h',
 ]
 
 EXPORTS.mozilla.loader += [
     'AutoMemMap.h',
+    'ScriptCacheActors.h',
 ]
 
 EXTRA_JS_MODULES += [
     'ISO8601DateUtils.jsm',
     'XPCOMUtils.jsm',
 ]
 
 FINAL_LIBRARY = 'xul'
 
 LOCAL_INCLUDES += [
     '../src',
     '../wrappers',
     '/dom/base',
 ]
 
+include('/ipc/chromium/chromium-config.mozbuild')
+
 if CONFIG['GNU_CXX']:
     CXXFLAGS += ['-Wno-shadow']
--- a/js/xpconnect/loader/mozJSComponentLoader.cpp
+++ b/js/xpconnect/loader/mozJSComponentLoader.cpp
@@ -678,38 +678,36 @@ mozJSComponentLoader::ObjectForLocation(
 
     bool writeToCache = false;
     StartupCache* cache = StartupCache::GetSingleton();
 
     nsAutoCString cachePath(kJSCachePrefix);
     rv = PathifyURI(aInfo.URI(), cachePath);
     NS_ENSURE_SUCCESS(rv, rv);
 
-    if (cache) {
-        if (!mReuseLoaderGlobal) {
-            script = ScriptPreloader::GetSingleton().GetCachedScript(cx, cachePath);
-            if (!script) {
-                rv = ReadCachedScript(cache, cachePath, cx, mSystemPrincipal, &script);
-            }
-        } else {
-            rv = ReadCachedFunction(cache, cachePath, cx, mSystemPrincipal,
-                                    function.address());
+    if (!mReuseLoaderGlobal) {
+        script = ScriptPreloader::GetSingleton().GetCachedScript(cx, cachePath);
+        if (!script && cache) {
+            ReadCachedScript(cache, cachePath, cx, mSystemPrincipal, &script);
         }
+    } else if (cache) {
+        ReadCachedFunction(cache, cachePath, cx, mSystemPrincipal,
+                           function.address());
+    }
 
-        if (NS_SUCCEEDED(rv)) {
-            LOG(("Successfully loaded %s from startupcache\n", nativePath.get()));
-        } else {
-            // This is ok, it just means the script is not yet in the
-            // cache. Could mean that the cache was corrupted and got removed,
-            // but either way we're going to write this out.
-            writeToCache = true;
-            // ReadCachedScript and ReadCachedFunction may have set a pending
-            // exception.
-            JS_ClearPendingException(cx);
-        }
+    if (script || function) {
+        LOG(("Successfully loaded %s from startupcache\n", nativePath.get()));
+    } else if (cache) {
+        // This is ok, it just means the script is not yet in the
+        // cache. Could mean that the cache was corrupted and got removed,
+        // but either way we're going to write this out.
+        writeToCache = true;
+        // ReadCachedScript and ReadCachedFunction may have set a pending
+        // exception.
+        JS_ClearPendingException(cx);
     }
 
     if (!script && !function) {
         // The script wasn't in the cache , so compile it now.
         LOG(("Slow loading %s\n", nativePath.get()));
 
         // Use lazy source if both of these conditions hold:
         //
--- a/js/xpconnect/loader/mozJSSubScriptLoader.cpp
+++ b/js/xpconnect/loader/mozJSSubScriptLoader.cpp
@@ -692,20 +692,20 @@ mozJSSubScriptLoader::DoLoadSubScriptWit
 
     JSVersion version = JS_GetVersion(cx);
     nsAutoCString cachePath;
     cachePath.AppendPrintf("jssubloader/%d", version);
     PathifyURI(uri, cachePath);
 
     RootedFunction function(cx);
     RootedScript script(cx);
-    if (cache && !options.ignoreCache) {
+    if (!options.ignoreCache) {
         if (!options.wantReturnValue)
             script = ScriptPreloader::GetSingleton().GetCachedScript(cx, cachePath);
-        if (!script)
+        if (!script && cache)
             rv = ReadCachedScript(cache, cachePath, cx, mSystemPrincipal, &script);
         if (NS_FAILED(rv) || !script) {
             // ReadCachedScript may have set a pending exception.
             JS_ClearPendingException(cx);
         }
     }
 
     // If we are doing an async load, trigger it and bail out.
new file mode 100755
--- /dev/null
+++ b/js/xpconnect/loader/script_cache.py
@@ -0,0 +1,81 @@
+#!/usr/bin/env python
+import io
+import os
+import struct
+import sys
+
+MAGIC = b'mozXDRcachev001\0'
+
+
+def usage():
+    print("""Usage: script_cache.py <file.bin> ...
+
+        Decodes and prints out the contents of a startup script cache file
+        (e.g., startupCache/scriptCache.bin) in human-readable form.""")
+
+    sys.exit(1)
+
+
+class ProcessTypes:
+    Default = 0
+    Web = 1
+    Extension = 2
+
+    def __init__(self, val):
+        self.val = val
+
+    def __str__(self):
+        res = []
+        if self.val & (1 << self.Default):
+            res.append('Parent')
+        if self.val & (1 << self.Web):
+            res.append('Web')
+        if self.val & (1 << self.Extension):
+            res.append('Extension')
+        return '|'.join(res)
+
+
+class InputBuffer(object):
+
+    def __init__(self, data):
+        self.data = data
+        self.offset = 0
+
+    @property
+    def remaining(self):
+        return len(self.data) - self.offset
+
+    def unpack(self, fmt):
+        res = struct.unpack_from(fmt, self.data, self.offset)
+        self.offset += struct.calcsize(fmt)
+        return res
+
+    def unpack_str(self):
+        size, = self.unpack('<H')
+        res = self.data[self.offset:self.offset + size].decode('utf-8')
+        self.offset += size
+        return res
+
+
+if len(sys.argv) < 2 or not os.path.exists(sys.argv[1]):
+    usage()
+
+for filename in sys.argv[1:]:
+    with io.open(filename, 'rb') as f:
+        magic = f.read(len(MAGIC))
+        if magic != MAGIC:
+            raise Exception('Bad magic number')
+
+        hdrSize, = struct.unpack('<I', f.read(4))
+
+        hdr = InputBuffer(f.read(hdrSize))
+
+        i = 0
+        while hdr.remaining:
+            i += 1
+            print('{}: {}'.format(i, hdr.unpack_str()))
+            print('  Key:       {}'.format(hdr.unpack_str()))
+            print('  Offset:    {:>9,}'.format(*hdr.unpack('<I')))
+            print('  Size:      {:>9,}'.format(*hdr.unpack('<I')))
+            print('  Processes: {}'.format(ProcessTypes(*hdr.unpack('B'))))
+            print('')
--- a/js/xpconnect/src/XPCComponents.cpp
+++ b/js/xpconnect/src/XPCComponents.cpp
@@ -2911,28 +2911,28 @@ nsXPCComponents_Utils::SetWantXrays(Hand
                                     js::AllCompartments());
     NS_ENSURE_TRUE(ok, NS_ERROR_FAILURE);
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsXPCComponents_Utils::ForcePermissiveCOWs(JSContext* cx)
 {
-    CrashIfNotInAutomation();
+    xpc::CrashIfNotInAutomation();
     CompartmentPrivate::Get(CurrentGlobalOrNull(cx))->forcePermissiveCOWs = true;
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsXPCComponents_Utils::ForcePrivilegedComponentsForScope(HandleValue vscope,
                                                          JSContext* cx)
 {
     if (!vscope.isObject())
         return NS_ERROR_INVALID_ARG;
-    CrashIfNotInAutomation();
+    xpc::CrashIfNotInAutomation();
     JSObject* scopeObj = js::UncheckedUnwrap(&vscope.toObject());
     XPCWrappedNativeScope* scope = ObjectScope(scopeObj);
     scope->ForcePrivilegedComponents();
     return NS_OK;
 }
 
 NS_IMETHODIMP
 nsXPCComponents_Utils::GetComponentsForScope(HandleValue vscope, JSContext* cx,
@@ -3008,16 +3008,32 @@ nsXPCComponents_Utils::SetGCZeal(int32_t
 {
 #ifdef JS_GC_ZEAL
     JS_SetGCZeal(cx, uint8_t(aValue), JS_DEFAULT_ZEAL_FREQ);
 #endif
     return NS_OK;
 }
 
 NS_IMETHODIMP
+nsXPCComponents_Utils::GetIsInAutomation(bool* aResult)
+{
+    NS_ENSURE_ARG_POINTER(aResult);
+
+    *aResult = xpc::IsInAutomation();
+    return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXPCComponents_Utils::CrashIfNotInAutomation()
+{
+    xpc::CrashIfNotInAutomation();
+    return NS_OK;
+}
+
+NS_IMETHODIMP
 nsXPCComponents_Utils::NukeSandbox(HandleValue obj, JSContext* cx)
 {
     PROFILER_LABEL_FUNC(js::ProfileEntry::Category::JS);
     NS_ENSURE_TRUE(obj.isObject(), NS_ERROR_INVALID_ARG);
     JSObject* wrapper = &obj.toObject();
     NS_ENSURE_TRUE(IsWrapper(wrapper), NS_ERROR_INVALID_ARG);
     RootedObject sb(cx, UncheckedUnwrap(wrapper));
     NS_ENSURE_TRUE(IsSandbox(sb), NS_ERROR_INVALID_ARG);
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -9318,16 +9318,32 @@ nsLayoutUtils::GetCumulativeApzCallbackT
     }
     frame = GetCrossDocParentFrame(frame);
     lastContent = content;
     content = frame ? frame->GetContent() : nullptr;
   }
   return delta;
 }
 
+/* static */ nsRect
+nsLayoutUtils::ComputePartialPrerenderArea(const nsRect& aDirtyRect,
+                                           const nsRect& aOverflow,
+                                           const nsSize& aPrerenderSize)
+{
+  // Simple calculation for now: center the pre-render area on the dirty rect,
+  // and clamp to the overflow area. Later we can do more advanced things like
+  // redistributing from one axis to another, or from one side to another.
+  nscoord xExcess = aPrerenderSize.width - aDirtyRect.width;
+  nscoord yExcess = aPrerenderSize.height - aDirtyRect.height;
+  nsRect result = aDirtyRect;
+  result.Inflate(xExcess / 2, yExcess / 2);
+  return result.MoveInsideAndClamp(aOverflow);
+}
+
+
 /* static */ bool
 nsLayoutUtils::SupportsServoStyleBackend(nsIDocument* aDocument)
 {
   return StyloEnabled() &&
          (aDocument->IsHTMLOrXHTML() || aDocument->IsSVGDocument()) &&
          static_cast<nsDocument*>(aDocument)->IsContentDocument();
 }
 
--- a/layout/base/nsLayoutUtils.h
+++ b/layout/base/nsLayoutUtils.h
@@ -2886,16 +2886,29 @@ public:
    * transforms on the content elements encountered along the way. Return the
    * accumulated value.
    * XXX: Note that this does not take into account CSS transforms, nor
    * differences in structure between the frame tree and the layer tree (which
    * is probably what we *want* to be computing).
    */
   static CSSPoint GetCumulativeApzCallbackTransform(nsIFrame* aFrame);
 
+  /**
+   * Compute a rect to pre-render in cases where we want to render more of
+   * something than what is visible (usually to support async transformation).
+   * @param aDirtyRect the area that's visible
+   * @param aOverflow the total size of the thing we're rendering
+   * @param aPrerenderSize how large of an area we're willing to render
+   * @return A rectangle that includes |aDirtyRect|, is clamped to |aOverflow|,
+   *         and is no larger than |aPrerenderSize|.
+   */
+  static nsRect ComputePartialPrerenderArea(const nsRect& aDirtyRect,
+                                            const nsRect& aOverflow,
+                                            const nsSize& aPrerenderSize);
+
   /*
    * Returns whether the given document supports being rendered with a
    * Servo-backed style system.  This checks whether Stylo is enabled
    * globally, that the document is an HTML document, and that it is
    * being presented in a content docshell.
    */
   static bool SupportsServoStyleBackend(nsIDocument* aDocument);
 
new file mode 100644
--- /dev/null
+++ b/layout/base/tests/bug1359411-ref.html
@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html><head><style>
+  i:before{content:"X"}
+</style></head><body onload="runTest()"><div><button type="button"><i></i></button><p></p></div><editor contenteditable="true">focus me, then press the UP key</editor>
+<script>
+function runTest() {
+  document.body.offsetHeight;
+  var e = document.querySelector('editor');
+  e.focus()
+}
+</script>
new file mode 100644
--- /dev/null
+++ b/layout/base/tests/bug1359411.html
@@ -0,0 +1,12 @@
+<!DOCTYPE html>
+<html><head><script src="/tests/SimpleTest/EventUtils.js"></script><style>
+  i:before{content:"X"}
+</style></head><body onload="runTest()"><div><button type="button"><i></i></button><p></p></div><editor contenteditable="true" onfocus="sendKey('UP')">focus me, then press the UP key</editor>
+<script>var e = document.querySelector('editor'); e.focus()</script>
+<script>
+function runTest() {
+  document.body.offsetHeight;
+  var e = document.querySelector('editor');
+  e.focus()
+}
+</script>
--- a/layout/base/tests/mochitest.ini
+++ b/layout/base/tests/mochitest.ini
@@ -282,16 +282,18 @@ support-files =
   bug1354478-3.html
   bug1354478-3-ref.html
   bug1354478-4.html
   bug1354478-4-ref.html
   bug1354478-5.html
   bug1354478-5-ref.html
   bug1354478-6.html
   bug1354478-6-ref.html
+  bug1359411.html
+  bug1359411-ref.html
   image_rgrg-256x256.png
   input-invalid-ref.html
   input-maxlength-invalid-change.html
   input-maxlength-ui-invalid-change.html
   input-maxlength-ui-valid-change.html
   input-maxlength-valid-before-change.html
   input-maxlength-valid-change.html
   input-minlength-invalid-change.html
--- a/layout/base/tests/test_reftests_with_caret.html
+++ b/layout/base/tests/test_reftests_with_caret.html
@@ -187,16 +187,17 @@ var tests = [
     [ 'bug1263357-4.html' , 'bug1263357-4-ref.html'] ,
     [ 'bug1263357-5.html' , 'bug1263357-5-ref.html'] ,
     [ 'bug1354478-1.html' , 'bug1354478-1-ref.html'] ,
     [ 'bug1354478-2.html' , 'bug1354478-2-ref.html'] ,
     [ 'bug1354478-3.html' , 'bug1354478-3-ref.html'] ,
     [ 'bug1354478-4.html' , 'bug1354478-4-ref.html'] ,
     [ 'bug1354478-5.html' , 'bug1354478-5-ref.html'] ,
     [ 'bug1354478-6.html' , 'bug1354478-6-ref.html'] ,
+    [ 'bug1359411.html'   , 'bug1359411-ref.html' ] ,
     function() {SpecialPowers.pushPrefEnv({'clear': [['layout.accessiblecaret.enabled']]}, nextTest);} ,
 ];
 
 if (navigator.appVersion.indexOf("Android") == -1) {
   tests.push([ 'bug512295-1.html' , 'bug512295-1-ref.html' ]);
   tests.push([ 'bug512295-2.html' , 'bug512295-2-ref.html' ]);
   tests.push([ 'bug923376.html'   , 'bug923376-ref.html'   ]);
   tests.push(function() {SpecialPowers.pushPrefEnv({'set': [['layout.css.overflow-clip-box.enabled', true]]}, nextTest);});
--- a/layout/generic/Selection.h
+++ b/layout/generic/Selection.h
@@ -25,16 +25,17 @@ class nsAutoScrollTimer;
 class nsIContentIterator;
 class nsIDocument;
 class nsIEditor;
 class nsIFrame;
 class nsIHTMLEditor;
 class nsFrameSelection;
 class nsPIDOMWindowOuter;
 struct SelectionDetails;
+struct SelectionCustomColors;
 class nsCopySupport;
 class nsHTMLCopyEncoder;
 
 namespace mozilla {
 class ErrorResult;
 struct AutoPrepareFocusRange;
 } // namespace mozilla
 
@@ -246,16 +247,22 @@ public:
                             bool aAllowAdjacent,
                             nsTArray<RefPtr<nsRange>>& aReturn,
                             mozilla::ErrorResult& aRv);
 
   void ScrollIntoView(int16_t aRegion, bool aIsSynchronous,
                       int16_t aVPercent, int16_t aHPercent,
                       mozilla::ErrorResult& aRv);
 
+  void SetColors(const nsAString& aForeColor, const nsAString& aBackColor,
+                 const nsAString& aAltForeColor, const nsAString& aAltBackColor,
+                 mozilla::ErrorResult& aRv);
+
+  void ResetColors(mozilla::ErrorResult& aRv);
+
   // Non-JS callers should use the following methods.
   void Collapse(nsINode& aNode, uint32_t aOffset, mozilla::ErrorResult& aRv);
   void CollapseToStart(mozilla::ErrorResult& aRv);
   void CollapseToEnd(mozilla::ErrorResult& aRv);
   void Extend(nsINode& aNode, uint32_t aOffset, mozilla::ErrorResult& aRv);
   void AddRange(nsRange& aRange, mozilla::ErrorResult& aRv);
   void SelectAllChildren(nsINode& aNode, mozilla::ErrorResult& aRv);
   void SetBaseAndExtent(nsINode& aAnchorNode, uint32_t aAnchorOffset,
@@ -279,16 +286,18 @@ private:
 
 public:
   SelectionType GetType() const { return mSelectionType; }
   void SetType(SelectionType aSelectionType)
   {
     mSelectionType = aSelectionType;
   }
 
+  SelectionCustomColors* GetCustomColors() const { return mCustomColors.get(); }
+
   nsresult NotifySelectionListeners(bool aCalledByJS);
   nsresult NotifySelectionListeners();
 
   friend struct AutoUserInitiated;
   struct MOZ_RAII AutoUserInitiated
   {
     explicit AutoUserInitiated(Selection* aSelection
                                MOZ_GUARD_OBJECT_NOTIFIER_PARAM)
@@ -413,19 +422,21 @@ private:
   // O(log n) time, though this would require rebalancing and other overhead.
   nsTArray<RangeData> mRanges;
 
   RefPtr<nsRange> mAnchorFocusRange;
   RefPtr<nsFrameSelection> mFrameSelection;
   RefPtr<nsAutoScrollTimer> mAutoScrollTimer;
   nsCOMArray<nsISelectionListener> mSelectionListeners;
   nsRevocableEventPtr<ScrollSelectionIntoViewEvent> mScrollEvent;
-  CachedOffsetForFrame *mCachedOffsetForFrame;
+  CachedOffsetForFrame* mCachedOffsetForFrame;
   nsDirection mDirection;
   SelectionType mSelectionType;
+  UniquePtr<SelectionCustomColors> mCustomColors;
+
   /**
    * True if the current selection operation was initiated by user action.
    * It determines whether we exclude -moz-user-select:none nodes or not,
    * as well as whether selectstart events will be fired.
    */
   bool mUserInitiated;
 
   /**
--- a/layout/generic/nsFlexContainerFrame.h
+++ b/layout/generic/nsFlexContainerFrame.h
@@ -325,17 +325,14 @@ protected:
    *                           reflow methods to interpret positions correctly).
    */
   void ReflowPlaceholders(nsPresContext* aPresContext,
                           const ReflowInput& aReflowInput,
                           nsTArray<nsIFrame*>& aPlaceholders,
                           const mozilla::LogicalPoint& aContentBoxOrigin,
                           const nsSize& aContainerSize);
 
-  bool mChildrenHaveBeenReordered; // Have we ever had to reorder our kids
-                                   // to satisfy their 'order' values?
-
   nscoord mBaselineFromLastReflow;
   // Note: the last baseline is a distance from our border-box end edge.
   nscoord mLastBaselineFromLastReflow;
 };
 
 #endif /* nsFlexContainerFrame_h___ */
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -2787,17 +2787,17 @@ nsIFrame::BuildDisplayListForStackingCon
   }
 
   if (clipCapturedBy == ContainerItemType::eOwnLayerForTransformWithRoundedClip) {
     clipState.Restore();
     resultList.AppendNewToTop(
       new (aBuilder) nsDisplayOwnLayer(aBuilder, this, &resultList,
                                        aBuilder->CurrentActiveScrolledRoot(), 0,
                                        mozilla::layers::FrameMetrics::NULL_SCROLL_ID,
-                                       0.0f, /* aForceActive = */ false));
+                                       ScrollThumbData{}, /* aForceActive = */ false));
   }
 
   /* If we have sticky positioning, wrap it in a sticky position item.
    */
   if (useFixedPosition) {
     if (clipCapturedBy == ContainerItemType::eFixedPosition) {
       clipState.Restore();
     }
--- a/layout/generic/nsFrameSelection.h
+++ b/layout/generic/nsFrameSelection.h
@@ -40,16 +40,34 @@ struct SelectionDetails
 #endif
   int32_t mStart;
   int32_t mEnd;
   mozilla::SelectionType mSelectionType;
   mozilla::TextRangeStyle mTextRangeStyle;
   mozilla::UniquePtr<SelectionDetails> mNext;
 };
 
+struct SelectionCustomColors
+{
+#ifdef NS_BUILD_REFCNT_LOGGING
+  SelectionCustomColors()
+  {
+    MOZ_COUNT_CTOR(SelectionCustomColors);
+  }
+  ~SelectionCustomColors()
+  {
+    MOZ_COUNT_DTOR(SelectionCustomColors);
+  }
+#endif
+  mozilla::Maybe<nscolor> mForegroundColor;
+  mozilla::Maybe<nscolor> mBackgroundColor;
+  mozilla::Maybe<nscolor> mAltForegroundColor;
+  mozilla::Maybe<nscolor> mAltBackgroundColor;
+};
+
 class nsIPresShell;
 
 /** PeekOffsetStruct is used to group various arguments (both input and output)
  *  that are passed to nsFrame::PeekOffset(). See below for the description of
  *  individual arguments.
  */
 struct MOZ_STACK_CLASS nsPeekOffsetStruct
 {
--- a/layout/generic/nsGfxScrollFrame.cpp
+++ b/layout/generic/nsGfxScrollFrame.cpp
@@ -3088,23 +3088,43 @@ ScrollFrameHelper::AppendScrollPartsTo(n
       flags |= nsDisplayOwnLayer::VERTICAL_SCROLLBAR;
       appendToTopFlags |= APPEND_SCROLLBAR_CONTAINER;
     }
     if (scrollParts[i] == mHScrollbarBox) {
       flags |= nsDisplayOwnLayer::HORIZONTAL_SCROLLBAR;
       appendToTopFlags |= APPEND_SCROLLBAR_CONTAINER;
     }
 
-    // The display port doesn't necessarily include the scrollbars, so just
-    // include all of the scrollbars if we are in a RCD-RSF. We only do
-    // this for the root scrollframe of the root content document, which is
-    // zoomable, and where the scrollbar sizes are bounded by the widget.
-    nsRect dirty = mIsRoot && mOuter->PresContext()->IsRootContentDocument()
-                   ? scrollParts[i]->GetVisualOverflowRectRelativeToParent()
-                   : aDirtyRect;
+    // The display port doesn't necessarily include the scrollbars.
+    bool thumbGetsLayer = (scrollTargetId != FrameMetrics::NULL_SCROLL_ID);
+    bool isRcdRsf = mIsRoot && mOuter->PresContext()->IsRootContentDocument();
+    nsRect dirty = aDirtyRect;
+    if (isRcdRsf || thumbGetsLayer) {
+      nsRect overflow = scrollParts[i]->GetVisualOverflowRectRelativeToParent();
+      if (isRcdRsf) {
+        // For the root content document's root scroll frame (RCD-RSF), include
+        // all of the scrollbars. We only do this for the RCD-RSF, which is
+        // zoomable, and where the scrollbar sizes are bounded by the widget.
+        dirty = overflow;
+      } else {
+        // For subframes, we still try to prerender parts of the scrollbar that
+        // are not currently visible, because they might be brought into view
+        // by async scrolling, but we bound the area to render by the size of
+        // the root reference frame (because subframe scrollbars can be
+        // arbitrary large).
+        nsSize refSize = aBuilder->RootReferenceFrame()->GetSize();
+        gfxSize scale = nsLayoutUtils::GetTransformToAncestorScale(mOuter);
+        if (scale.width != 0 && scale.height != 0) {
+          refSize.width /= scale.width;
+          refSize.height /= scale.height;
+        }
+        dirty = nsLayoutUtils::ComputePartialPrerenderArea(dirty, overflow, refSize);
+      }
+    }
+
     nsDisplayListBuilder::AutoBuildingDisplayList
       buildingForChild(aBuilder, scrollParts[i],
                        dirty + mOuter->GetOffsetTo(scrollParts[i]), true);
 
     // Always create layers for overlay scrollbars so that we don't create a
     // giant layer covering the whole scrollport if both scrollbars are visible.
     bool isOverlayScrollbar = (flags != 0) && overlayScrollbars;
     bool createLayer = aCreateLayer || isOverlayScrollbar ||
--- a/layout/generic/nsSelection.cpp
+++ b/layout/generic/nsSelection.cpp
@@ -3485,27 +3485,29 @@ nsFrameSelection::DisconnectFromPresShel
 // mozilla::dom::Selection implementation
 
 // note: this can return a nil anchor node
 
 Selection::Selection()
   : mCachedOffsetForFrame(nullptr)
   , mDirection(eDirNext)
   , mSelectionType(SelectionType::eNormal)
+  , mCustomColors(nullptr)
   , mUserInitiated(false)
   , mCalledByJS(false)
   , mSelectionChangeBlockerCount(0)
 {
 }
 
 Selection::Selection(nsFrameSelection* aList)
   : mFrameSelection(aList)
   , mCachedOffsetForFrame(nullptr)
   , mDirection(eDirNext)
   , mSelectionType(SelectionType::eNormal)
+  , mCustomColors(nullptr)
   , mUserInitiated(false)
   , mCalledByJS(false)
   , mSelectionChangeBlockerCount(0)
 {
 }
 
 Selection::~Selection()
 {
@@ -6816,16 +6818,112 @@ Selection::SelectionLanguageChange(bool 
   
   // The caret might have moved, so invalidate the desired position
   // for future usages of up-arrow or down-arrow
   frameSelection->InvalidateDesiredPos();
   
   return NS_OK;
 }
 
+NS_IMETHODIMP
+Selection::SetColors(const nsAString& aForegroundColor,
+                     const nsAString& aBackgroundColor,
+                     const nsAString& aAltForegroundColor,
+                     const nsAString& aAltBackgroundColor)
+{
+  ErrorResult result;
+  SetColors(aForegroundColor, aBackgroundColor,
+            aAltForegroundColor, aAltBackgroundColor, result);
+  return result.StealNSResult();
+}
+
+void
+Selection::SetColors(const nsAString& aForegroundColor,
+                     const nsAString& aBackgroundColor,
+                     const nsAString& aAltForegroundColor,
+                     const nsAString& aAltBackgroundColor,
+                     ErrorResult& aRv)
+{
+  if (mSelectionType != SelectionType::eFind) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return;
+  }
+
+  mCustomColors.reset(new SelectionCustomColors);
+
+  NS_NAMED_LITERAL_STRING(currentColorStr, "currentColor");
+  NS_NAMED_LITERAL_STRING(transparentStr, "transparent");
+
+  if (!aForegroundColor.Equals(currentColorStr)) {
+    nscolor foregroundColor;
+    nsAttrValue aForegroundColorValue;
+    aForegroundColorValue.ParseColor(aForegroundColor);
+    if (!aForegroundColorValue.GetColorValue(foregroundColor)) {
+      aRv.Throw(NS_ERROR_INVALID_ARG);
+      return;
+    }
+    mCustomColors->mForegroundColor = Some(foregroundColor);
+  } else {
+    mCustomColors->mForegroundColor = Nothing();
+  }
+
+  if (!aBackgroundColor.Equals(transparentStr)) {
+    nscolor backgroundColor;
+    nsAttrValue aBackgroundColorValue;
+    aBackgroundColorValue.ParseColor(aBackgroundColor);
+    if (!aBackgroundColorValue.GetColorValue(backgroundColor)) {
+      aRv.Throw(NS_ERROR_INVALID_ARG);
+      return;
+    }
+    mCustomColors->mBackgroundColor = Some(backgroundColor);
+  } else {
+    mCustomColors->mBackgroundColor = Nothing();
+  }
+
+  if (!aAltForegroundColor.Equals(currentColorStr)) {
+    nscolor altForegroundColor;
+    nsAttrValue aAltForegroundColorValue;
+    aAltForegroundColorValue.ParseColor(aAltForegroundColor);
+    if (!aAltForegroundColorValue.GetColorValue(altForegroundColor)) {
+      aRv.Throw(NS_ERROR_INVALID_ARG);
+      return;
+    }
+    mCustomColors->mAltForegroundColor = Some(altForegroundColor);
+  } else {
+    mCustomColors->mAltForegroundColor = Nothing();
+  }
+
+  if (!aAltBackgroundColor.Equals(transparentStr)) {
+    nscolor altBackgroundColor;
+    nsAttrValue aAltBackgroundColorValue;
+    aAltBackgroundColorValue.ParseColor(aAltBackgroundColor);
+    if (!aAltBackgroundColorValue.GetColorValue(altBackgroundColor)) {
+      aRv.Throw(NS_ERROR_INVALID_ARG);
+      return;
+    }
+    mCustomColors->mAltBackgroundColor = Some(altBackgroundColor);
+  } else {
+    mCustomColors->mAltBackgroundColor = Nothing();
+  }
+}
+
+NS_IMETHODIMP
+Selection::ResetColors()
+{
+  ErrorResult result;
+  ResetColors(result);
+  return result.StealNSResult();
+}
+
+void
+Selection::ResetColors(ErrorResult& aRv)
+{
+  mCustomColors = nullptr;
+}
+
 NS_IMETHODIMP_(nsDirection)
 Selection::GetSelectionDirection() {
   return mDirection;
 }
 
 NS_IMETHODIMP_(void)
 Selection::SetSelectionDirection(nsDirection aDirection) {
   mDirection = aDirection;
--- a/layout/generic/nsTextFrame.cpp
+++ b/layout/generic/nsTextFrame.cpp
@@ -3823,23 +3823,96 @@ nsTextPaintStyle::GetSelectionColors(nsc
 
 void
 nsTextPaintStyle::GetHighlightColors(nscolor* aForeColor,
                                      nscolor* aBackColor)
 {
   NS_ASSERTION(aForeColor, "aForeColor is null");
   NS_ASSERTION(aBackColor, "aBackColor is null");
 
-  nscolor backColor =
-    LookAndFeel::GetColor(LookAndFeel::eColorID_TextHighlightBackground);
-  nscolor foreColor =
-    LookAndFeel::GetColor(LookAndFeel::eColorID_TextHighlightForeground);
-  EnsureSufficientContrast(&foreColor, &backColor);
-  *aForeColor = foreColor;
-  *aBackColor = backColor;
+  const nsFrameSelection* frameSelection = mFrame->GetConstFrameSelection();
+  const Selection* selection =
+    frameSelection->GetSelection(SelectionType::eFind);
+  const SelectionCustomColors* customColors = nullptr;
+  if (selection) {
+    customColors = selection->GetCustomColors();
+  }
+
+  if (!customColors) {
+    nscolor backColor =
+      LookAndFeel::GetColor(LookAndFeel::eColorID_TextHighlightBackground);
+    nscolor foreColor =
+      LookAndFeel::GetColor(LookAndFeel::eColorID_TextHighlightForeground);
+    EnsureSufficientContrast(&foreColor, &backColor);
+    *aForeColor = foreColor;
+    *aBackColor = backColor;
+
+    return;
+  }
+
+  if (customColors->mForegroundColor && customColors->mBackgroundColor) {
+    nscolor foreColor = *customColors->mForegroundColor;
+    nscolor backColor = *customColors->mBackgroundColor;
+
+    if (EnsureSufficientContrast(&foreColor, &backColor) &&
+        customColors->mAltForegroundColor &&
+        customColors->mAltBackgroundColor) {
+      foreColor = *customColors->mAltForegroundColor;
+      backColor = *customColors->mAltBackgroundColor;
+    }
+
+    *aForeColor = foreColor;
+    *aBackColor = backColor;
+    return;
+  }
+
+  if (customColors->mBackgroundColor) {
+    // !mForegroundColor means "currentColor"; the current color of the text.
+    nscolor foreColor = GetTextColor();
+    nscolor backColor = *customColors->mBackgroundColor;
+
+    if (customColors->mAltBackgroundColor) {
+      int32_t foreLuminosityDifference =
+                NS_LUMINOSITY_DIFFERENCE(foreColor, backColor);
+
+      // The sufficient luminosity difference is based on the link color of
+      // about:preferences, so we don't invert the background color on these text.
+      // XXX: Make this more generic.
+      int32_t sufficientLuminosityDifference =
+                NS_LUMINOSITY_DIFFERENCE(NS_RGBA(23, 140, 229, 255), backColor);
+
+      if (foreLuminosityDifference < sufficientLuminosityDifference) {
+        backColor = *customColors->mAltBackgroundColor;
+      }
+    }
+
+    *aForeColor = foreColor;
+    *aBackColor = backColor;
+    return;
+  }
+
+  if (customColors->mForegroundColor) {
+    nscolor foreColor = *customColors->mForegroundColor;
+    // !mBackgroundColor means "transparent"; the current color of the background.
+    nscolor backColor = mFrameBackgroundColor;
+
+    if (customColors->mAltForegroundColor &&
+        EnsureSufficientContrast(&foreColor, &backColor)) {
+      foreColor = *customColors->mAltForegroundColor;
+      backColor = mFrameBackgroundColor;
+    }
+
+    *aForeColor = foreColor;
+    *aBackColor = backColor;
+    return;
+  }
+
+  // There are neither mForegroundColor nor mBackgroundColor.
+  *aForeColor = GetTextColor();
+  *aBackColor = NS_TRANSPARENT;
 }
 
 void
 nsTextPaintStyle::GetURLSecondaryColor(nscolor* aForeColor)
 {
   NS_ASSERTION(aForeColor, "aForeColor is null");
 
   nscolor textColor = GetTextColor();
--- a/layout/generic/nsTextFrame.h
+++ b/layout/generic/nsTextFrame.h
@@ -6,16 +6,17 @@
 #ifndef nsTextFrame_h__
 #define nsTextFrame_h__
 
 #include "mozilla/Attributes.h"
 #include "mozilla/EventForwards.h"
 #include "mozilla/gfx/2D.h"
 #include "mozilla/UniquePtr.h"
 #include "nsFrame.h"
+#include "nsFrameSelection.h"
 #include "nsSplittableFrame.h"
 #include "nsLineBox.h"
 #include "gfxSkipChars.h"
 #include "gfxTextRun.h"
 #include "nsDisplayList.h"
 #include "JustificationUtils.h"
 #include "RubyUtils.h"
 
--- a/layout/mathml/nsMathMLSelectedFrame.cpp
+++ b/layout/mathml/nsMathMLSelectedFrame.cpp
@@ -7,29 +7,16 @@
 #include "nsDisplayList.h"
 
 using namespace mozilla;
 
 nsMathMLSelectedFrame::~nsMathMLSelectedFrame()
 {
 }
 
-void
-nsMathMLSelectedFrame::Init(nsIContent*       aContent,
-                            nsContainerFrame* aParent,
-                            nsIFrame*         aPrevInFlow)
-{
-  // Init our local attributes
-  mInvalidMarkup = false;
-  mSelectedFrame = nullptr;
-
-  // Let the base class do the rest
-  nsMathMLContainerFrame::Init(aContent, aParent, aPrevInFlow);
-}
-
 NS_IMETHODIMP
 nsMathMLSelectedFrame::TransmitAutomaticData()
 {
   // Note that to determine space-like and embellished op properties:
   //   - <semantics> behaves the same as <maction>
   //   - <annotation-xml> behaves the same as <mrow>
 
   // The REC defines the following element to be space-like:
--- a/layout/mathml/nsMathMLSelectedFrame.h
+++ b/layout/mathml/nsMathMLSelectedFrame.h
@@ -5,21 +5,16 @@
 
 #ifndef nsMathMLSelectedFrame_h___
 #define nsMathMLSelectedFrame_h___
 
 #include "nsMathMLContainerFrame.h"
 
 class nsMathMLSelectedFrame : public nsMathMLContainerFrame {
 public:
-  virtual void
-  Init(nsIContent*       aContent,
-       nsContainerFrame* aParent,
-       nsIFrame*         aPrevInFlow) override;
-
   NS_IMETHOD
   TransmitAutomaticData() override;
 
   virtual void
   SetInitialChildList(ChildListID     aListID,
                       nsFrameList&    aChildList) override;
 
   virtual nsresult
@@ -49,17 +44,19 @@ public:
          ReflowOutput&     aDesiredSize,
          const ReflowInput& aReflowInput,
          nsReflowStatus&          aStatus) override;
 
   virtual nsQueryFrame::FrameIID GetFrameId() override = 0;
 
 protected:
   explicit nsMathMLSelectedFrame(nsStyleContext* aContext) :
-    nsMathMLContainerFrame(aContext) {}
+    nsMathMLContainerFrame(aContext),
+    mSelectedFrame(nullptr),
+    mInvalidMarkup(false) {}
   virtual ~nsMathMLSelectedFrame();
   
   virtual nsIFrame* GetSelectedFrame() = 0;
   nsIFrame*       mSelectedFrame;
 
   bool            mInvalidMarkup;
   
 private:
--- a/layout/painting/FrameLayerBuilder.cpp
+++ b/layout/painting/FrameLayerBuilder.cpp
@@ -6286,25 +6286,25 @@ FrameLayerBuilder::GetMostRecentGeometry
     if (data->GetDisplayItemKey() == itemPerFrameKey) {
       return data->GetGeometry();
     }
   }
 
   return nullptr;
 }
 
-gfx::Rect
-CalculateBounds(const nsTArray<DisplayItemClip::RoundedRect>& aRects, int32_t A2D)
+static gfx::Rect
+CalculateBounds(const nsTArray<DisplayItemClip::RoundedRect>& aRects, int32_t aAppUnitsPerDevPixel)
 {
   nsRect bounds = aRects[0].mRect;
   for (uint32_t i = 1; i < aRects.Length(); ++i) {
     bounds.UnionRect(bounds, aRects[i].mRect);
    }
 
-  return gfx::ToRect(nsLayoutUtils::RectToGfxRect(bounds, A2D));
+  return gfx::Rect(bounds.ToNearestPixels(aAppUnitsPerDevPixel));
 }
 
 static void
 SetClipCount(PaintedDisplayItemLayerUserData* apaintedData,
              uint32_t aClipCount)
 {
   if (apaintedData) {
     apaintedData->mMaskClipCount = aClipCount;
@@ -6371,17 +6371,16 @@ ContainerState::CreateMaskLayer(Layer *a
   MaskLayerUserData* userData = GetMaskLayerUserData(maskLayer);
 
   int32_t A2D = mContainerFrame->PresContext()->AppUnitsPerDevPixel();
   MaskLayerUserData newData(aClip, aRoundedRectClipCount, A2D, mParameters);
   if (*userData == newData) {
     return maskLayer.forget();
   }
 
-  // calculate a more precise bounding rect
   gfx::Rect boundingRect = CalculateBounds(newData.mRoundedClipRects,
                                            newData.mAppUnitsPerDevPixel);
   boundingRect.Scale(mParameters.mXScale, mParameters.mYScale);
 
   uint32_t maxSize = mManager->GetMaxTextureSize();
   NS_ASSERTION(maxSize > 0, "Invalid max texture size");
 #ifdef MOZ_GFX_OPTIMIZE_MOBILE
   // Make mask image width aligned to 4. See Bug 1245552.
--- a/layout/painting/nsDisplayList.cpp
+++ b/layout/painting/nsDisplayList.cpp
@@ -6146,22 +6146,22 @@ bool nsDisplayBlendContainer::TryMerge(n
   MergeFromTrackingMergedFrames(static_cast<nsDisplayBlendContainer*>(aItem));
   return true;
 }
 
 nsDisplayOwnLayer::nsDisplayOwnLayer(nsDisplayListBuilder* aBuilder,
                                      nsIFrame* aFrame, nsDisplayList* aList,
                                      const ActiveScrolledRoot* aActiveScrolledRoot,
                                      uint32_t aFlags, ViewID aScrollTarget,
-                                     float aScrollbarThumbRatio,
+                                     const ScrollThumbData& aThumbData,
                                      bool aForceActive)
     : nsDisplayWrapList(aBuilder, aFrame, aList, aActiveScrolledRoot)
     , mFlags(aFlags)
     , mScrollTarget(aScrollTarget)
-    , mScrollbarThumbRatio(aScrollbarThumbRatio)
+    , mThumbData(aThumbData)
     , mForceActive(aForceActive)
 {
   MOZ_COUNT_CTOR(nsDisplayOwnLayer);
 }
 
 #ifdef NS_BUILD_REFCNT_LOGGING
 nsDisplayOwnLayer::~nsDisplayOwnLayer() {
   MOZ_COUNT_DTOR(nsDisplayOwnLayer);
@@ -6185,21 +6185,18 @@ already_AddRefed<Layer>
 nsDisplayOwnLayer::BuildLayer(nsDisplayListBuilder* aBuilder,
                               LayerManager* aManager,
                               const ContainerLayerParameters& aContainerParameters)
 {
   RefPtr<ContainerLayer> layer = aManager->GetLayerBuilder()->
     BuildContainerLayerFor(aBuilder, aManager, mFrame, this, &mList,
                            aContainerParameters, nullptr,
                            FrameLayerBuilder::CONTAINER_ALLOW_PULL_BACKGROUND_COLOR);
-  if (mFlags & VERTICAL_SCROLLBAR) {
-    layer->SetScrollbarData(mScrollTarget, ScrollDirection::VERTICAL, mScrollbarThumbRatio);
-  }
-  if (mFlags & HORIZONTAL_SCROLLBAR) {
-    layer->SetScrollbarData(mScrollTarget, ScrollDirection::HORIZONTAL, mScrollbarThumbRatio);
+  if (mThumbData.mDirection != ScrollDirection::NONE) {
+    layer->SetScrollThumbData(mScrollTarget, mThumbData);
   }
   if (mFlags & SCROLLBAR_CONTAINER) {
     layer->SetIsScrollbarContainer(mScrollTarget);
   }
 
   if (mFlags & GENERATE_SUBDOC_INVALIDATIONS) {
     mFrame->PresContext()->SetNotifySubDocInvalidationData(layer);
   }
@@ -7315,30 +7312,17 @@ nsDisplayOpacity::CanUseAsyncAnimations(
 
   return false;
 }
 
 bool
 nsDisplayTransform::CanUseAsyncAnimations(nsDisplayListBuilder* aBuilder)
 {
   return mAllowAsyncAnimation;
-}
-
-static nsRect ComputePartialPrerenderArea(const nsRect& aDirtyRect,
-                                          const nsRect& aOverflow,
-                                          const nsSize& aPrerenderSize)
-{
-  // Simple calculation for now: center the pre-render area on the dirty rect,
-  // and clamp to the overflow area. Later we can do more advanced things like
-  // redistributing from one axis to another, or from one side to another.
-  nscoord xExcess = aPrerenderSize.width - aDirtyRect.width;
-  nscoord yExcess = aPrerenderSize.height - aDirtyRect.height;
-  nsRect result = aDirtyRect;
-  result.Inflate(xExcess / 2, yExcess / 2);
-  return result.MoveInsideAndClamp(aOverflow);
+
 }
 
 static void
 RecordAnimationFrameSizeTelemetry(nsIFrame* aFrame, const nsSize& overflow)
 {
   gfxSize scale = nsLayoutUtils::GetTransformToAncestorScale(aFrame);
   nsSize frameSize = nsSize(overflow.width * scale.width,
                             overflow.height * scale.height);
@@ -7401,17 +7385,17 @@ nsDisplayTransform::ShouldPrerenderTrans
   nsSize maxSize = Min(relativeLimit, absoluteLimit);
   gfxSize scale = nsLayoutUtils::GetTransformToAncestorScale(aFrame);
   nsSize frameSize = nsSize(overflow.Size().width * scale.width,
                             overflow.Size().height * scale.height);
   if (frameSize <= maxSize) {
     *aDirtyRect = overflow;
     return FullPrerender;
   } else if (gfxPrefs::PartiallyPrerenderAnimatedContent()) {
-    *aDirtyRect = ComputePartialPrerenderArea(*aDirtyRect, overflow, maxSize);
+    *aDirtyRect = nsLayoutUtils::ComputePartialPrerenderArea(*aDirtyRect, overflow, maxSize);
     return PartialPrerender;
   }
 
   EffectCompositor::SetPerformanceWarning(
     aFrame, eCSSProperty_transform,
     AnimationPerformanceWarning(
       AnimationPerformanceWarning::Type::ContentTooLarge,
       {
--- a/layout/painting/nsDisplayList.h
+++ b/layout/painting/nsDisplayList.h
@@ -30,16 +30,17 @@
 #include "DisplayListClipState.h"
 #include "LayerState.h"
 #include "FrameMetrics.h"
 #include "mozilla/EnumeratedArray.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/UniquePtr.h"
 #include "mozilla/TimeStamp.h"
 #include "mozilla/gfx/UserData.h"
+#include "mozilla/layers/LayerAttributes.h"
 #include "nsCSSRenderingBorders.h"
 
 #include <stdint.h>
 #include "nsTHashtable.h"
 
 #include <stdlib.h>
 #include <algorithm>
 
@@ -4036,16 +4037,17 @@ private:
 };
 
 /**
  * A display item that has no purpose but to ensure its contents get
  * their own layer.
  */
 class nsDisplayOwnLayer : public nsDisplayWrapList {
 public:
+  typedef mozilla::layers::ScrollThumbData ScrollThumbData;
 
   /**
    * nsDisplayOwnLayer constructor flags
    */
   enum {
     GENERATE_SUBDOC_INVALIDATIONS = 0x0