Merge m-c to graphics
authorKartikaya Gupta <kgupta@mozilla.com>
Mon, 15 May 2017 09:36:37 -0400
changeset 359348 e377209ad3e8246c20ab53c4fbb4e7a4c802a24b
parent 359347 ba9b2448c357b5ea8827bcb77233c6af1d24a2fb (current diff)
parent 358392 df404e72597d2754290a95a18782ec31bc0027b3 (diff)
child 359349 af0bdad77eaccc484b2d4d55d41e6f08e87af1fe
push id90494
push userkwierso@gmail.com
push dateFri, 19 May 2017 22:19:13 +0000
treeherdermozilla-inbound@cd9b1ab819e8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone55.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-c to graphics MozReview-Commit-ID: BYH3uvvu4jz
browser/extensions/formautofill/test/unit/test_profileStorage.js
dom/webidl/AutocompleteErrorEvent.webidl
gfx/layers/apz/src/APZCTreeManager.cpp
gfx/layers/apz/src/APZCTreeManager.h
gfx/layers/ipc/CompositorBridgeParent.cpp
gfx/thebes/gfxPrefs.h
layout/generic/nsBulletFrame.cpp
layout/painting/nsDisplayList.cpp
servo/components/script/dom/browsingcontext.rs
taskcluster/ci/test/tests.yml
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/url-classifier/tests/gtest/TestCachingV4.cpp
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
--- 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/extensions/test/browser/browser_ext_tabs_executeScript_runAt.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_executeScript_runAt.js
@@ -47,16 +47,20 @@ add_task(async function testExecuteScrip
         await loadingPromise;
 
         let states = await Promise.all([
           // Send the executeScript requests in the reverse order that we expect
           // them to execute in, to avoid them passing only because of timing
           // races.
           browser.tabs.executeScript({
             code: "document.readyState",
+            // Testing default `runAt`.
+          }),
+          browser.tabs.executeScript({
+            code: "document.readyState",
             runAt: "document_idle",
           }),
           browser.tabs.executeScript({
             code: "document.readyState",
             runAt: "document_end",
           }),
           browser.tabs.executeScript({
             code: "document.readyState",
@@ -72,17 +76,18 @@ add_task(async function testExecuteScrip
                                 `document_end state is valid: ${states[1]}`);
         browser.test.assertTrue(states[2] == "complete",
                                 `document_idle state is valid: ${states[2]}`);
 
         // If we have the earliest valid states for each script, we're done.
         // Otherwise, try again.
         success = (states[0] == "loading" &&
                    states[1] == "interactive" &&
-                   states[2] == "complete");
+                   states[2] == "complete" &&
+                   states[3] == "complete");
       }
 
       browser.test.assertTrue(success, "Got the earliest expected states at least once");
 
       browser.test.notifyPass("executeScript-runAt");
     } catch (e) {
       browser.test.fail(`Error: ${e} :: ${e.stack}`);
       browser.test.notifyFail("executeScript-runAt");
--- 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/formautofill/FormAutofillParent.jsm
+++ b/browser/extensions/formautofill/FormAutofillParent.jsm
@@ -148,17 +148,17 @@ FormAutofillParent.prototype = {
    *
    * @returns {boolean} status of form autofill feature
    */
   _getStatus() {
     if (!Services.prefs.getBoolPref(ENABLED_PREF)) {
       return false;
     }
 
-    return profileStorage.getAll().length > 0;
+    return profileStorage.addresses.getAll({noComputedFields: true}).length > 0;
   },
 
   /**
    * Set status and trigger _onStatusChanged.
    *
    * @param {boolean} newStatus The latest status we want to set for _enabled
    */
   _setStatus(newStatus) {
@@ -176,24 +176,24 @@ FormAutofillParent.prototype = {
   receiveMessage({name, data, target}) {
     switch (name) {
       case "FormAutofill:GetAddresses": {
         this._getAddresses(data, target);
         break;
       }
       case "FormAutofill:SaveAddress": {
         if (data.guid) {
-          profileStorage.update(data.guid, data.address);
+          profileStorage.addresses.update(data.guid, data.address);
         } else {
-          profileStorage.add(data.address);
+          profileStorage.addresses.add(data.address);
         }
         break;
       }
       case "FormAutofill:RemoveAddresses": {
-        data.guids.forEach(guid => profileStorage.remove(guid));
+        data.guids.forEach(guid => profileStorage.addresses.remove(guid));
         break;
       }
     }
   },
 
   /**
    * Uninitializes FormAutofillParent. This is for testing only.
    *
@@ -220,32 +220,32 @@ FormAutofillParent.prototype = {
    *         The input autocomplete property's information.
    * @param  {nsIFrameMessageManager} target
    *         Content's message manager.
    */
   _getAddresses({searchString, info}, target) {
     let addresses = [];
 
     if (info && info.fieldName) {
-      addresses = profileStorage.getByFilter({searchString, info});
+      addresses = profileStorage.addresses.getByFilter({searchString, info});
     } else {
-      addresses = profileStorage.getAll();
+      addresses = profileStorage.addresses.getAll();
     }
 
     target.sendAsyncMessage("FormAutofill:Addresses", addresses);
   },
 
   _updateSavedFieldNames() {
     if (!Services.ppmm.initialProcessData.autofillSavedFieldNames) {
       Services.ppmm.initialProcessData.autofillSavedFieldNames = new Set();
     } else {
       Services.ppmm.initialProcessData.autofillSavedFieldNames.clear();
     }
 
-    profileStorage.getAll().forEach((address) => {
+    profileStorage.addresses.getAll().forEach((address) => {
       Object.keys(address).forEach((fieldName) => {
         if (!address[fieldName]) {
           return;
         }
         Services.ppmm.initialProcessData.autofillSavedFieldNames.add(fieldName);
       });
     });
 
--- a/browser/extensions/formautofill/ProfileStorage.jsm
+++ b/browser/extensions/formautofill/ProfileStorage.jsm
@@ -8,46 +8,68 @@
  * The data is stored in JSON format, without indentation and the computed
  * fields, using UTF-8 encoding. With indentation and computed fields applied,
  * the schema would look like this:
  *
  * {
  *   version: 1,
  *   addresses: [
  *     {
- *       guid,             // 12 characters
+ *       guid,                 // 12 characters
  *
  *       // address fields
  *       given-name,
  *       additional-name,
  *       family-name,
- *       organization,     // Company
- *       street-address,   // (Multiline)
- *       address-level2,   // City/Town
- *       address-level1,   // Province (Standardized code if possible)
+ *       organization,         // Company
+ *       street-address,       // (Multiline)
+ *       address-level2,       // City/Town
+ *       address-level1,       // Province (Standardized code if possible)
  *       postal-code,
- *       country,          // ISO 3166
+ *       country,              // ISO 3166
  *       tel,
  *       email,
  *
  *       // computed fields (These fields are not stored in the file as they are
  *       // generated at runtime.)
  *       name,
  *       address-line1,
  *       address-line2,
  *       address-line3,
  *
  *       // metadata
- *       timeCreated,      // in ms
- *       timeLastUsed,     // in ms
- *       timeLastModified, // in ms
+ *       timeCreated,          // in ms
+ *       timeLastUsed,         // in ms
+ *       timeLastModified,     // in ms
  *       timesUsed
- *     },
+ *     }
+ *   ],
+ *   creditCards: [
  *     {
- *       // ...
+ *       guid,                 // 12 characters
+ *
+ *       // credit card fields
+ *       cc-name,
+ *       cc-number-encrypted,
+ *       cc-number-masked,     // e.g. ************1234
+ *       cc-exp-month,
+ *       cc-exp-year,          // 2-digit year will be converted to 4 digits
+ *                             // upon saving
+ *
+ *       // computed fields (These fields are not stored in the file as they are
+ *       // generated at runtime.)
+ *       cc-given-name,
+ *       cc-additional-name,
+ *       cc-family-name,
+ *
+ *       // metadata
+ *       timeCreated,          // in ms
+ *       timeLastUsed,         // in ms
+ *       timeLastModified,     // in ms
+ *       timesUsed
  *     }
  *   ]
  * }
  */
 
 "use strict";
 
 // We expose a singleton from this module. Some tests may import the
@@ -66,46 +88,477 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource://gre/modules/JSONFile.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "FormAutofillNameUtils",
                                   "resource://formautofill/FormAutofillNameUtils.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "gUUIDGenerator",
                                    "@mozilla.org/uuid-generator;1",
                                    "nsIUUIDGenerator");
 
-this.log = null;
-FormAutofillUtils.defineLazyLogGetter(this, this.EXPORTED_SYMBOLS[0]);
-
 const PROFILE_JSON_FILE_NAME = "autofill-profiles.json";
 
 const SCHEMA_VERSION = 1;
 
-const VALID_FIELDS = [
+const VALID_PROFILE_FIELDS = [
   "given-name",
   "additional-name",
   "family-name",
   "organization",
   "street-address",
   "address-level2",
   "address-level1",
   "postal-code",
   "country",
   "tel",
   "email",
 ];
 
+const VALID_CREDIT_CARD_FIELDS = [
+  "cc-name",
+  "cc-number-encrypted",
+  "cc-number-masked",
+  "cc-exp-month",
+  "cc-exp-year",
+];
+
+const INTERNAL_FIELDS = [
+  "guid",
+  "timeCreated",
+  "timeLastUsed",
+  "timeLastModified",
+  "timesUsed",
+];
+
+/**
+ * Class that manipulates records in a specified collection.
+ *
+ * Note that it is responsible for converting incoming data to a consistent
+ * format in the storage. For example, computed fields will be transformed to
+ * the original fields and 2-digit years will be calculated into 4 digits.
+ */
+class AutofillRecords {
+  /**
+   * Creates an AutofillRecords.
+   *
+   * @param {JSONFile} store
+   *        An instance of JSONFile.
+   * @param {string} collectionName
+   *        A key of "store.data".
+   * @param {Array.<string>} validFields
+   *        A list containing non-metadata field names.
+   */
+  constructor(store, collectionName, validFields) {
+    FormAutofillUtils.defineLazyLogGetter(this, "AutofillRecords:" + collectionName);
+
+    this.VALID_FIELDS = validFields;
+
+    this._store = store;
+    this._collectionName = collectionName;
+  }
+
+  /**
+   * Gets the schema version number.
+   *
+   * @returns {number}
+   *          The current schema version number.
+   */
+  get version() {
+    return SCHEMA_VERSION;
+  }
+
+  /**
+   * Adds a new record.
+   *
+   * @param {Object} record
+   *        The new record for saving.
+   */
+  add(record) {
+    this.log.debug("add:", record);
+
+    let recordToSave = this._clone(record);
+    this._normalizeRecord(recordToSave);
+
+    let guid;
+    while (!guid || this._findByGUID(guid)) {
+      guid = gUUIDGenerator.generateUUID().toString()
+                           .replace(/[{}-]/g, "").substring(0, 12);
+    }
+    recordToSave.guid = guid;
+
+    // Metadata
+    let now = Date.now();
+    recordToSave.timeCreated = now;
+    recordToSave.timeLastModified = now;
+    recordToSave.timeLastUsed = 0;
+    recordToSave.timesUsed = 0;
+
+    this._store.data[this._collectionName].push(recordToSave);
+    this._store.saveSoon();
+
+    Services.obs.notifyObservers(null, "formautofill-storage-changed", "add");
+  }
+
+  /**
+   * Update the specified record.
+   *
+   * @param  {string} guid
+   *         Indicates which record to update.
+   * @param  {Object} record
+   *         The new record used to overwrite the old one.
+   */
+  update(guid, record) {
+    this.log.debug("update:", guid, record);
+
+    let recordFound = this._findByGUID(guid);
+    if (!recordFound) {
+      throw new Error("No matching record.");
+    }
+
+    let recordToUpdate = this._clone(record);
+    this._normalizeRecord(recordToUpdate);
+    for (let field of this.VALID_FIELDS) {
+      if (recordToUpdate[field] !== undefined) {
+        recordFound[field] = recordToUpdate[field];
+      } else {
+        delete recordFound[field];
+      }
+    }
+
+    recordFound.timeLastModified = Date.now();
+
+    this._store.saveSoon();
+
+    Services.obs.notifyObservers(null, "formautofill-storage-changed", "update");
+  }
+
+  /**
+   * Notifies the stroage of the use of the specified record, so we can update
+   * the metadata accordingly.
+   *
+   * @param  {string} guid
+   *         Indicates which record to be notified.
+   */
+  notifyUsed(guid) {
+    this.log.debug("notifyUsed:", guid);
+
+    let recordFound = this._findByGUID(guid);
+    if (!recordFound) {
+      throw new Error("No matching record.");
+    }
+
+    recordFound.timesUsed++;
+    recordFound.timeLastUsed = Date.now();
+
+    this._store.saveSoon();
+    Services.obs.notifyObservers(null, "formautofill-storage-changed", "notifyUsed");
+  }
+
+  /**
+   * Removes the specified record. No error occurs if the record isn't found.
+   *
+   * @param  {string} guid
+   *         Indicates which record to remove.
+   */
+  remove(guid) {
+    this.log.debug("remove:", guid);
+
+    this._store.data[this._collectionName] =
+      this._store.data[this._collectionName].filter(record => record.guid != guid);
+    this._store.saveSoon();
+
+    Services.obs.notifyObservers(null, "formautofill-storage-changed", "remove");
+  }
+
+  /**
+   * Returns the record with the specified GUID.
+   *
+   * @param   {string} guid
+   *          Indicates which record to retrieve.
+   * @returns {Object}
+   *          A clone of the record.
+   */
+  get(guid) {
+    this.log.debug("get:", guid);
+
+    let recordFound = this._findByGUID(guid);
+    if (!recordFound) {
+      throw new Error("No matching record.");
+    }
+
+    // The record is cloned to avoid accidental modifications from outside.
+    let clonedRecord = this._clone(recordFound);
+    this._recordReadProcessor(clonedRecord);
+    return clonedRecord;
+  }
+
+  /**
+   * Returns all records.
+   *
+   * @param   {Object} config
+   *          Specifies how data will be retrieved.
+   * @param   {boolean} config.noComputedFields
+   *          Returns raw record without those computed fields.
+   * @returns {Array.<Object>}
+   *          An array containing clones of all records.
+   */
+  getAll(config = {}) {
+    this.log.debug("getAll", config);
+
+    // Records are cloned to avoid accidental modifications from outside.
+    let clonedRecords = this._store.data[this._collectionName].map(this._clone);
+    clonedRecords.forEach(record => this._recordReadProcessor(record, config));
+    return clonedRecords;
+  }
+
+  /**
+   * Returns the filtered records based on input's information and searchString.
+   *
+   * @returns {Array.<Object>}
+   *          An array containing clones of matched record.
+   */
+  getByFilter({info, searchString}) {
+    this.log.debug("getByFilter:", info, searchString);
+
+    let lcSearchString = searchString.toLowerCase();
+    let result = this.getAll().filter(record => {
+      // Return true if string is not provided and field exists.
+      // TODO: We'll need to check if the address is for billing or shipping.
+      //       (Bug 1358941)
+      let name = record[info.fieldName];
+
+      if (!searchString) {
+        return !!name;
+      }
+
+      return name && name.toLowerCase().startsWith(lcSearchString);
+    });
+
+    this.log.debug("getByFilter:", "Returning", result.length, "result(s)");
+    return result;
+  }
+
+  _clone(record) {
+    return Object.assign({}, record);
+  }
+
+  _findByGUID(guid) {
+    let found = this._findIndexByGUID(guid);
+    return found < 0 ? undefined : this._store.data[this._collectionName][found];
+  }
+
+  _findIndexByGUID(guid) {
+    return this._store.data[this._collectionName].findIndex(record => record.guid == guid);
+  }
+
+  _normalizeRecord(record) {
+    this._recordWriteProcessor(record);
+
+    for (let key in record) {
+      if (!this.VALID_FIELDS.includes(key)) {
+        throw new Error(`"${key}" is not a valid field.`);
+      }
+      if (typeof record[key] !== "string" &&
+          typeof record[key] !== "number") {
+        throw new Error(`"${key}" contains invalid data type.`);
+      }
+    }
+  }
+
+  // An interface to be inherited.
+  _recordReadProcessor(record, config) {}
+
+  // An interface to be inherited.
+  _recordWriteProcessor(record) {}
+}
+
+class Addresses extends AutofillRecords {
+  constructor(store) {
+    super(store, "addresses", VALID_PROFILE_FIELDS);
+  }
+
+  _recordReadProcessor(profile, {noComputedFields} = {}) {
+    if (noComputedFields) {
+      return;
+    }
+
+    // Compute name
+    let name = FormAutofillNameUtils.joinNameParts({
+      given: profile["given-name"],
+      middle: profile["additional-name"],
+      family: profile["family-name"],
+    });
+    if (name) {
+      profile.name = name;
+    }
+
+    // Compute address
+    if (profile["street-address"]) {
+      let streetAddress = profile["street-address"].split("\n");
+      // TODO: we should prevent the dataloss by concatenating the rest of lines
+      //       with a locale-specific character in the future (bug 1360114).
+      for (let i = 0; i < 3; i++) {
+        if (streetAddress[i]) {
+          profile["address-line" + (i + 1)] = streetAddress[i];
+        }
+      }
+    }
+  }
+
+  _recordWriteProcessor(profile) {
+    // Normalize name
+    if (profile.name) {
+      let nameParts = FormAutofillNameUtils.splitName(profile.name);
+      if (!profile["given-name"] && nameParts.given) {
+        profile["given-name"] = nameParts.given;
+      }
+      if (!profile["additional-name"] && nameParts.middle) {
+        profile["additional-name"] = nameParts.middle;
+      }
+      if (!profile["family-name"] && nameParts.family) {
+        profile["family-name"] = nameParts.family;
+      }
+      delete profile.name;
+    }
+
+    // Normalize address
+    if (profile["address-line1"] || profile["address-line2"] ||
+        profile["address-line3"]) {
+      // Treat "street-address" as "address-line1" if it contains only one line
+      // and "address-line1" is omitted.
+      if (!profile["address-line1"] && profile["street-address"] &&
+          !profile["street-address"].includes("\n")) {
+        profile["address-line1"] = profile["street-address"];
+        delete profile["street-address"];
+      }
+
+      // Remove "address-line*" but keep the values.
+      let addressLines = [1, 2, 3].map(i => {
+        let value = profile["address-line" + i];
+        delete profile["address-line" + i];
+        return value;
+      });
+
+      // Concatenate "address-line*" if "street-address" is omitted.
+      if (!profile["street-address"]) {
+        profile["street-address"] = addressLines.join("\n");
+      }
+    }
+  }
+}
+
+class CreditCards extends AutofillRecords {
+  constructor(store) {
+    super(store, "creditCards", VALID_CREDIT_CARD_FIELDS);
+  }
+
+  _recordReadProcessor(creditCard, {noComputedFields} = {}) {
+    if (noComputedFields) {
+      return;
+    }
+
+    // Compute split names
+    if (creditCard["cc-name"]) {
+      let nameParts = FormAutofillNameUtils.splitName(creditCard["cc-name"]);
+      if (nameParts.given) {
+        creditCard["cc-given-name"] = nameParts.given;
+      }
+      if (nameParts.middle) {
+        creditCard["cc-additional-name"] = nameParts.middle;
+      }
+      if (nameParts.family) {
+        creditCard["cc-family-name"] = nameParts.family;
+      }
+    }
+  }
+
+  _recordWriteProcessor(creditCard) {
+    // Fields that should not be set by content.
+    delete creditCard["cc-number-encrypted"];
+    delete creditCard["cc-number-masked"];
+
+    // Validate and encrypt credit card numbers, and calculate the masked numbers
+    if (creditCard["cc-number"]) {
+      let ccNumber = creditCard["cc-number"].replace(/\s/g, "");
+      delete creditCard["cc-number"];
+
+      if (!/^\d+$/.test(ccNumber)) {
+        throw new Error("Credit card number contains invalid characters.");
+      }
+
+      // TODO: Encrypt cc-number here (bug 1337314).
+      // e.g. creditCard["cc-number-encrypted"] = Encrypt(creditCard["cc-number"]);
+
+      if (ccNumber.length > 4) {
+        creditCard["cc-number-masked"] = "*".repeat(ccNumber.length - 4) + ccNumber.substr(-4);
+      } else {
+        creditCard["cc-number-masked"] = ccNumber;
+      }
+    }
+
+    // Normalize name
+    if (creditCard["cc-given-name"] || creditCard["cc-additional-name"] || creditCard["cc-family-name"]) {
+      if (!creditCard["cc-name"]) {
+        creditCard["cc-name"] = FormAutofillNameUtils.joinNameParts({
+          given: creditCard["cc-given-name"],
+          middle: creditCard["cc-additional-name"],
+          family: creditCard["cc-family-name"],
+        });
+      }
+
+      delete creditCard["cc-given-name"];
+      delete creditCard["cc-additional-name"];
+      delete creditCard["cc-family-name"];
+    }
+
+    // Validate expiry date
+    if (creditCard["cc-exp-month"]) {
+      let expMonth = parseInt(creditCard["cc-exp-month"], 10);
+      if (isNaN(expMonth) || expMonth < 1 || expMonth > 12) {
+        delete creditCard["cc-exp-month"];
+      } else {
+        creditCard["cc-exp-month"] = expMonth;
+      }
+    }
+    if (creditCard["cc-exp-year"]) {
+      let expYear = parseInt(creditCard["cc-exp-year"], 10);
+      if (isNaN(expYear) || expYear < 0) {
+        delete creditCard["cc-exp-year"];
+      } else if (expYear < 100) {
+        // Enforce 4 digits years.
+        creditCard["cc-exp-year"] = expYear + 2000;
+      } else {
+        creditCard["cc-exp-year"] = expYear;
+      }
+    }
+  }
+}
+
 function ProfileStorage(path) {
   this._path = path;
   this._initializePromise = null;
+  this.INTERNAL_FIELDS = INTERNAL_FIELDS;
 }
 
 ProfileStorage.prototype = {
-  // These fields are defined internally for each record.
-  INTERNAL_FIELDS:
-    ["guid", "timeCreated", "timeLastUsed", "timeLastModified", "timesUsed"],
+  get addresses() {
+    if (!this._addresses) {
+      this._store.ensureDataReady();
+      this._addresses = new Addresses(this._store);
+    }
+    return this._addresses;
+  },
+
+  get creditCards() {
+    if (!this._creditCards) {
+      this._store.ensureDataReady();
+      this._creditCards = new CreditCards(this._store);
+    }
+    return this._creditCards;
+  },
+
   /**
    * Loads the profile data from file to memory.
    *
    * @returns {Promise}
    * @resolves When the operation finished successfully.
    * @rejects  JavaScript exception.
    */
   initialize() {
@@ -114,275 +567,24 @@ ProfileStorage.prototype = {
         path: this._path,
         dataPostProcessor: this._dataPostProcessor.bind(this),
       });
       this._initializePromise = this._store.load();
     }
     return this._initializePromise;
   },
 
-  /**
-   * Adds a new address.
-   *
-   * @param {Address} address
-   *        The new address for saving.
-   */
-  add(address) {
-    log.debug("add:", address);
-    this._store.ensureDataReady();
-
-    let addressToSave = this._clone(address);
-    this._normalizeAddress(addressToSave);
-
-    addressToSave.guid = gUUIDGenerator.generateUUID().toString()
-                                       .replace(/[{}-]/g, "").substring(0, 12);
-
-    // Metadata
-    let now = Date.now();
-    addressToSave.timeCreated = now;
-    addressToSave.timeLastModified = now;
-    addressToSave.timeLastUsed = 0;
-    addressToSave.timesUsed = 0;
-
-    this._store.data.addresses.push(addressToSave);
-
-    this._store.saveSoon();
-    Services.obs.notifyObservers(null, "formautofill-storage-changed", "add");
-  },
-
-  /**
-   * Update the specified address.
-   *
-   * @param  {string} guid
-   *         Indicates which address to update.
-   * @param  {Address} address
-   *         The new address used to overwrite the old one.
-   */
-  update(guid, address) {
-    log.debug("update:", guid, address);
-    this._store.ensureDataReady();
-
-    let addressFound = this._findByGUID(guid);
-    if (!addressFound) {
-      throw new Error("No matching record.");
-    }
-
-    let addressToUpdate = this._clone(address);
-    this._normalizeAddress(addressToUpdate);
-
-    for (let field of VALID_FIELDS) {
-      if (addressToUpdate[field] !== undefined) {
-        addressFound[field] = addressToUpdate[field];
-      } else {
-        delete addressFound[field];
-      }
-    }
-
-    addressFound.timeLastModified = Date.now();
-
-    this._store.saveSoon();
-    Services.obs.notifyObservers(null, "formautofill-storage-changed", "update");
-  },
-
-  /**
-   * Notifies the stroage of the use of the specified address, so we can update
-   * the metadata accordingly.
-   *
-   * @param  {string} guid
-   *         Indicates which address to be notified.
-   */
-  notifyUsed(guid) {
-    this._store.ensureDataReady();
-
-    let addressFound = this._findByGUID(guid);
-    if (!addressFound) {
-      throw new Error("No matching record.");
-    }
-
-    addressFound.timesUsed++;
-    addressFound.timeLastUsed = Date.now();
-
-    this._store.saveSoon();
-    Services.obs.notifyObservers(null, "formautofill-storage-changed", "notifyUsed");
-  },
-
-  /**
-   * Removes the specified address. No error occurs if the address isn't found.
-   *
-   * @param  {string} guid
-   *         Indicates which address to remove.
-   */
-  remove(guid) {
-    log.debug("remove:", guid);
-    this._store.ensureDataReady();
-
-    this._store.data.addresses =
-      this._store.data.addresses.filter(address => address.guid != guid);
-    this._store.saveSoon();
-    Services.obs.notifyObservers(null, "formautofill-storage-changed", "remove");
-  },
-
-  /**
-   * Returns the address with the specified GUID.
-   *
-   * @param   {string} guid
-   *          Indicates which address to retrieve.
-   * @returns {Address}
-   *          A clone of the address.
-   */
-  get(guid) {
-    log.debug("get:", guid);
-    this._store.ensureDataReady();
-
-    let addressFound = this._findByGUID(guid);
-    if (!addressFound) {
-      throw new Error("No matching record.");
-    }
-
-    // The record is cloned to avoid accidental modifications from outside.
-    let clonedAddress = this._clone(addressFound);
-    this._computeFields(clonedAddress);
-    return clonedAddress;
-  },
-
-  /**
-   * Returns all addresses.
-   *
-   * @returns {Array.<Address>}
-   *          An array containing clones of all addresses.
-   */
-  getAll() {
-    log.debug("getAll");
-    this._store.ensureDataReady();
-
-    // Records are cloned to avoid accidental modifications from outside.
-    let clonedAddresses = this._store.data.addresses.map(this._clone);
-    clonedAddresses.forEach(this._computeFields);
-    return clonedAddresses;
-  },
-
-  /**
-   * Returns the filtered addresses based on input's information and searchString.
-   *
-   * @returns {Array.<Address>}
-   *          An array containing clones of matched addresses.
-   */
-  getByFilter({info, searchString}) {
-    log.debug("getByFilter:", info, searchString);
-
-    let lcSearchString = searchString.toLowerCase();
-    let result = this.getAll().filter(address => {
-      // Return true if string is not provided and field exists.
-      // TODO: We'll need to check if the address is for billing or shipping.
-      //       (Bug 1358941)
-      let name = address[info.fieldName];
-
-      if (!searchString) {
-        return !!name;
-      }
-
-      return name && name.toLowerCase().startsWith(lcSearchString);
-    });
-
-    log.debug("getByFilter: Returning", result.length, "result(s)");
-    return result;
-  },
-
-  _clone(record) {
-    return Object.assign({}, record);
-  },
-
-  _findByGUID(guid) {
-    return this._store.data.addresses.find(address => address.guid == guid);
-  },
-
-  _computeFields(address) {
-    // Compute name
-    address.name = FormAutofillNameUtils.joinNameParts({
-      given: address["given-name"],
-      middle: address["additional-name"],
-      family: address["family-name"],
-    });
-
-    // Compute address
-    if (address["street-address"]) {
-      let streetAddress = address["street-address"].split("\n");
-      // TODO: we should prevent the dataloss by concatenating the rest of lines
-      //       with a locale-specific character in the future (bug 1360114).
-      for (let i = 0; i < 3; i++) {
-        if (streetAddress[i]) {
-          address["address-line" + (i + 1)] = streetAddress[i];
-        }
-      }
-    }
-  },
-
-  _normalizeAddressLines(address) {
-    if (address["address-line1"] || address["address-line2"] ||
-        address["address-line3"]) {
-      // Treat "street-address" as "address-line1" if it contains only one line
-      // and "address-line1" is omitted.
-      if (!address["address-line1"] && address["street-address"] &&
-          !address["street-address"].includes("\n")) {
-        address["address-line1"] = address["street-address"];
-        delete address["street-address"];
-      }
-
-      // Remove "address-line*" but keep the values.
-      let addressLines = [1, 2, 3].map(i => {
-        let value = address["address-line" + i];
-        delete address["address-line" + i];
-        return value;
-      });
-
-      // Concatenate "address-line*" if "street-address" is omitted.
-      if (!address["street-address"]) {
-        address["street-address"] = addressLines.join("\n");
-      }
-    }
-  },
-
-  _normalizeName(address) {
-    if (!address.name) {
-      return;
-    }
-
-    let nameParts = FormAutofillNameUtils.splitName(address.name);
-    if (!address["given-name"] && nameParts.given) {
-      address["given-name"] = nameParts.given;
-    }
-    if (!address["additional-name"] && nameParts.middle) {
-      address["additional-name"] = nameParts.middle;
-    }
-    if (!address["family-name"] && nameParts.family) {
-      address["family-name"] = nameParts.family;
-    }
-    delete address.name;
-  },
-
-  _normalizeAddress(address) {
-    this._normalizeName(address);
-    this._normalizeAddressLines(address);
-
-    for (let key in address) {
-      if (!VALID_FIELDS.includes(key)) {
-        throw new Error(`"${key}" is not a valid field.`);
-      }
-      if (typeof address[key] !== "string" &&
-          typeof address[key] !== "number") {
-        throw new Error(`"${key}" contains invalid data type.`);
-      }
-    }
-  },
-
   _dataPostProcessor(data) {
     data.version = SCHEMA_VERSION;
     if (!data.addresses) {
       data.addresses = [];
     }
+    if (!data.creditCards) {
+      data.creditCards = [];
+    }
     return data;
   },
 
   // For test only.
   _saveImmediately() {
     return this._store._save();
   },
 };
new file mode 100644
--- /dev/null
+++ b/browser/extensions/formautofill/test/unit/test_addressRecords.js
@@ -0,0 +1,286 @@
+/**
+ * Tests ProfileStorage object with addresses records.
+ */
+
+"use strict";
+
+const {ProfileStorage} = Cu.import("resource://formautofill/ProfileStorage.jsm", {});
+
+const TEST_STORE_FILE_NAME = "test-profile.json";
+
+const TEST_ADDRESS_1 = {
+  "given-name": "Timothy",
+  "additional-name": "John",
+  "family-name": "Berners-Lee",
+  organization: "World Wide Web Consortium",
+  "street-address": "32 Vassar Street\nMIT Room 32-G524",
+  "address-level2": "Cambridge",
+  "address-level1": "MA",
+  "postal-code": "02139",
+  country: "US",
+  tel: "+1 617 253 5702",
+  email: "timbl@w3.org",
+};
+
+const TEST_ADDRESS_2 = {
+  "street-address": "Some Address",
+  country: "US",
+};
+
+const TEST_ADDRESS_3 = {
+  "street-address": "Other Address",
+  "postal-code": "12345",
+};
+
+const TEST_ADDRESS_WITH_INVALID_FIELD = {
+  "street-address": "Another Address",
+  invalidField: "INVALID",
+};
+
+let prepareTestRecords = async function(path) {
+  let profileStorage = new ProfileStorage(path);
+  await profileStorage.initialize();
+
+  let onChanged = TestUtils.topicObserved("formautofill-storage-changed",
+                                          (subject, data) => data == "add");
+  profileStorage.addresses.add(TEST_ADDRESS_1);
+  await onChanged;
+  profileStorage.addresses.add(TEST_ADDRESS_2);
+  await profileStorage._saveImmediately();
+};
+
+let do_check_record_matches = (recordWithMeta, record) => {
+  for (let key in record) {
+    do_check_eq(recordWithMeta[key], record[key]);
+  }
+};
+
+add_task(async function test_initialize() {
+  let path = getTempFile(TEST_STORE_FILE_NAME).path;
+  let profileStorage = new ProfileStorage(path);
+  await profileStorage.initialize();
+
+  do_check_eq(profileStorage._store.data.version, 1);
+  do_check_eq(profileStorage._store.data.addresses.length, 0);
+
+  let data = profileStorage._store.data;
+  Assert.deepEqual(data.addresses, []);
+
+  await profileStorage._saveImmediately();
+
+  profileStorage = new ProfileStorage(path);
+  await profileStorage.initialize();
+
+  Assert.deepEqual(profileStorage._store.data, data);
+});
+
+add_task(async function test_getAll() {
+  let path = getTempFile(TEST_STORE_FILE_NAME).path;
+  await prepareTestRecords(path);
+
+  let profileStorage = new ProfileStorage(path);
+  await profileStorage.initialize();
+
+  let addresses = profileStorage.addresses.getAll();
+
+  do_check_eq(addresses.length, 2);
+  do_check_record_matches(addresses[0], TEST_ADDRESS_1);
+  do_check_record_matches(addresses[1], TEST_ADDRESS_2);
+
+  // Check computed fields.
+  do_check_eq(addresses[0].name, "Timothy John Berners-Lee");
+  do_check_eq(addresses[0]["address-line1"], "32 Vassar Street");
+  do_check_eq(addresses[0]["address-line2"], "MIT Room 32-G524");
+
+  // Test with noComputedFields set.
+  addresses = profileStorage.addresses.getAll({noComputedFields: true});
+  do_check_eq(addresses[0].name, undefined);
+  do_check_eq(addresses[0]["address-line1"], undefined);
+  do_check_eq(addresses[0]["address-line2"], undefined);
+
+  // Modifying output shouldn't affect the storage.
+  addresses[0].organization = "test";
+  do_check_record_matches(profileStorage.addresses.getAll()[0], TEST_ADDRESS_1);
+});
+
+add_task(async function test_get() {
+  let path = getTempFile(TEST_STORE_FILE_NAME).path;
+  await prepareTestRecords(path);
+
+  let profileStorage = new ProfileStorage(path);
+  await profileStorage.initialize();
+
+  let addresses = profileStorage.addresses.getAll();
+  let guid = addresses[0].guid;
+
+  let address = profileStorage.addresses.get(guid);
+  do_check_record_matches(address, TEST_ADDRESS_1);
+
+  // Modifying output shouldn't affect the storage.
+  address.organization = "test";
+  do_check_record_matches(profileStorage.addresses.get(guid), TEST_ADDRESS_1);
+
+  Assert.throws(() => profileStorage.addresses.get("INVALID_GUID"),
+    /No matching record\./);
+});
+
+add_task(async function test_getByFilter() {
+  let path = getTempFile(TEST_STORE_FILE_NAME).path;
+  await prepareTestRecords(path);
+
+  let profileStorage = new ProfileStorage(path);
+  await profileStorage.initialize();
+
+  let filter = {info: {fieldName: "street-address"}, searchString: "Some"};
+  let addresses = profileStorage.addresses.getByFilter(filter);
+  do_check_eq(addresses.length, 1);
+  do_check_record_matches(addresses[0], TEST_ADDRESS_2);
+
+  filter = {info: {fieldName: "country"}, searchString: "u"};
+  addresses = profileStorage.addresses.getByFilter(filter);
+  do_check_eq(addresses.length, 2);
+  do_check_record_matches(addresses[0], TEST_ADDRESS_1);
+  do_check_record_matches(addresses[1], TEST_ADDRESS_2);
+
+  filter = {info: {fieldName: "street-address"}, searchString: "test"};
+  addresses = profileStorage.addresses.getByFilter(filter);
+  do_check_eq(addresses.length, 0);
+
+  filter = {info: {fieldName: "street-address"}, searchString: ""};
+  addresses = profileStorage.addresses.getByFilter(filter);
+  do_check_eq(addresses.length, 2);
+
+  // Check if the filtering logic is free from searching special chars.
+  filter = {info: {fieldName: "street-address"}, searchString: ".*"};
+  addresses = profileStorage.addresses.getByFilter(filter);
+  do_check_eq(addresses.length, 0);
+
+  // Prevent broken while searching the property that does not exist.
+  filter = {info: {fieldName: "tel"}, searchString: "1"};
+  addresses = profileStorage.addresses.getByFilter(filter);
+  do_check_eq(addresses.length, 0);
+});
+
+add_task(async function test_add() {
+  let path = getTempFile(TEST_STORE_FILE_NAME).path;
+  await prepareTestRecords(path);
+
+  let profileStorage = new ProfileStorage(path);
+  await profileStorage.initialize();
+
+  let addresses = profileStorage.addresses.getAll();
+
+  do_check_eq(addresses.length, 2);
+
+  do_check_record_matches(addresses[0], TEST_ADDRESS_1);
+  do_check_record_matches(addresses[1], TEST_ADDRESS_2);
+
+  do_check_neq(addresses[0].guid, undefined);
+  do_check_neq(addresses[0].timeCreated, undefined);
+  do_check_eq(addresses[0].timeLastModified, addresses[0].timeCreated);
+  do_check_eq(addresses[0].timeLastUsed, 0);
+  do_check_eq(addresses[0].timesUsed, 0);
+
+  Assert.throws(() => profileStorage.addresses.add(TEST_ADDRESS_WITH_INVALID_FIELD),
+    /"invalidField" is not a valid field\./);
+});
+
+add_task(async function test_update() {
+  let path = getTempFile(TEST_STORE_FILE_NAME).path;
+  await prepareTestRecords(path);
+
+  let profileStorage = new ProfileStorage(path);
+  await profileStorage.initialize();
+
+  let addresses = profileStorage.addresses.getAll();
+  let guid = addresses[1].guid;
+  let timeLastModified = addresses[1].timeLastModified;
+
+  let onChanged = TestUtils.topicObserved("formautofill-storage-changed",
+                                          (subject, data) => data == "update");
+
+  do_check_neq(addresses[1].country, undefined);
+
+  profileStorage.addresses.update(guid, TEST_ADDRESS_3);
+  await onChanged;
+  await profileStorage._saveImmediately();
+
+  profileStorage = new ProfileStorage(path);
+  await profileStorage.initialize();
+
+  let address = profileStorage.addresses.get(guid);
+
+  do_check_eq(address.country, undefined);
+  do_check_neq(address.timeLastModified, timeLastModified);
+  do_check_record_matches(address, TEST_ADDRESS_3);
+
+  Assert.throws(
+    () => profileStorage.addresses.update("INVALID_GUID", TEST_ADDRESS_3),
+    /No matching record\./
+  );
+
+  Assert.throws(
+    () => profileStorage.addresses.update(guid, TEST_ADDRESS_WITH_INVALID_FIELD),
+    /"invalidField" is not a valid field\./
+  );
+});
+
+add_task(async function test_notifyUsed() {
+  let path = getTempFile(TEST_STORE_FILE_NAME).path;
+  await prepareTestRecords(path);
+
+  let profileStorage = new ProfileStorage(path);
+  await profileStorage.initialize();
+
+  let addresses = profileStorage.addresses.getAll();
+  let guid = addresses[1].guid;
+  let timeLastUsed = addresses[1].timeLastUsed;
+  let timesUsed = addresses[1].timesUsed;
+
+  let onChanged = TestUtils.topicObserved("formautofill-storage-changed",
+                                          (subject, data) => data == "notifyUsed");
+
+  profileStorage.addresses.notifyUsed(guid);
+  await onChanged;
+  await profileStorage._saveImmediately();
+
+  profileStorage = new ProfileStorage(path);
+  await profileStorage.initialize();
+
+  let address = profileStorage.addresses.get(guid);
+
+  do_check_eq(address.timesUsed, timesUsed + 1);
+  do_check_neq(address.timeLastUsed, timeLastUsed);
+
+  Assert.throws(() => profileStorage.addresses.notifyUsed("INVALID_GUID"),
+    /No matching record\./);
+});
+
+add_task(async function test_remove() {
+  let path = getTempFile(TEST_STORE_FILE_NAME).path;
+  await prepareTestRecords(path);
+
+  let profileStorage = new ProfileStorage(path);
+  await profileStorage.initialize();
+
+  let addresses = profileStorage.addresses.getAll();
+  let guid = addresses[1].guid;
+
+  let onChanged = TestUtils.topicObserved("formautofill-storage-changed",
+                                          (subject, data) => data == "remove");
+
+  do_check_eq(addresses.length, 2);
+
+  profileStorage.addresses.remove(guid);
+  await onChanged;
+  await profileStorage._saveImmediately();
+
+  profileStorage = new ProfileStorage(path);
+  await profileStorage.initialize();
+
+  addresses = profileStorage.addresses.getAll();
+
+  do_check_eq(addresses.length, 1);
+
+  Assert.throws(() => profileStorage.addresses.get(guid), /No matching record\./);
+});
new file mode 100644
--- /dev/null
+++ b/browser/extensions/formautofill/test/unit/test_creditCardRecords.js
@@ -0,0 +1,325 @@
+/**
+ * Tests ProfileStorage object with creditCards records.
+ */
+
+"use strict";
+
+const {ProfileStorage} = Cu.import("resource://formautofill/ProfileStorage.jsm", {});
+
+const TEST_STORE_FILE_NAME = "test-credit-card.json";
+
+const TEST_CREDIT_CARD_1 = {
+  "cc-name": "John Doe",
+  "cc-number": "1234567812345678",
+  "cc-exp-month": 4,
+  "cc-exp-year": 2017,
+};
+
+const TEST_CREDIT_CARD_2 = {
+  "cc-name": "Timothy Berners-Lee",
+  "cc-number": "1111222233334444",
+  "cc-exp-month": 12,
+  "cc-exp-year": 2022,
+};
+
+const TEST_CREDIT_CARD_3 = {
+  "cc-number": "9999888877776666",
+  "cc-exp-month": 1,
+  "cc-exp-year": 2000,
+};
+
+const TEST_CREDIT_CARD_WITH_2_DIGITS_YEAR = {
+  "cc-number": "1234123412341234",
+  "cc-exp-month": 1,
+  "cc-exp-year": 12,
+};
+
+const TEST_CREDIT_CARD_WITH_INVALID_FIELD = {
+  "cc-name": "John Doe",
+  invalidField: "INVALID",
+};
+
+const TEST_CREDIT_CARD_WITH_INVALID_EXPIRY_DATE = {
+  "cc-name": "John Doe",
+  "cc-number": "1111222233334444",
+  "cc-exp-month": 13,
+  "cc-exp-year": -3,
+};
+
+const TEST_CREDIT_CARD_WITH_SPACES_BETWEEN_DIGITS = {
+  "cc-name": "John Doe",
+  "cc-number": "1111 2222 3333 4444",
+};
+
+const TEST_CREDIT_CARD_WITH_INVALID_NUMBERS = {
+  "cc-name": "John Doe",
+  "cc-number": "abcdefg",
+};
+
+let prepareTestCreditCards = async function(path) {
+  let profileStorage = new ProfileStorage(path);
+  await profileStorage.initialize();
+
+  let onChanged = TestUtils.topicObserved("formautofill-storage-changed",
+                                          (subject, data) => data == "add");
+  profileStorage.creditCards.add(TEST_CREDIT_CARD_1);
+  await onChanged;
+  profileStorage.creditCards.add(TEST_CREDIT_CARD_2);
+  await profileStorage._saveImmediately();
+};
+
+let reCCNumber = /^(\*+)(.{4})$/;
+
+let do_check_credit_card_matches = (creditCardWithMeta, creditCard) => {
+  for (let key in creditCard) {
+    if (key == "cc-number") {
+      do_check_eq(creditCardWithMeta["cc-number"], undefined);
+
+      // check "cc-number-encrypted" after encryption lands (bug 1337314).
+
+      let matches = reCCNumber.exec(creditCardWithMeta["cc-number-masked"]);
+      do_check_neq(matches, null);
+      do_check_eq(creditCardWithMeta["cc-number-masked"].length, creditCard["cc-number"].length);
+      do_check_eq(creditCard["cc-number"].endsWith(matches[2]), true);
+    } else {
+      do_check_eq(creditCardWithMeta[key], creditCard[key]);
+    }
+  }
+};
+
+add_task(async function test_initialize() {
+  let path = getTempFile(TEST_STORE_FILE_NAME).path;
+  let profileStorage = new ProfileStorage(path);
+  await profileStorage.initialize();
+
+  do_check_eq(profileStorage._store.data.version, 1);
+  do_check_eq(profileStorage._store.data.creditCards.length, 0);
+
+  let data = profileStorage._store.data;
+  Assert.deepEqual(data.creditCards, []);
+
+  await profileStorage._saveImmediately();
+
+  profileStorage = new ProfileStorage(path);
+  await profileStorage.initialize();
+
+  Assert.deepEqual(profileStorage._store.data, data);
+});
+
+add_task(async function test_getAll() {
+  let path = getTempFile(TEST_STORE_FILE_NAME).path;
+  await prepareTestCreditCards(path);
+
+  let profileStorage = new ProfileStorage(path);
+  await profileStorage.initialize();
+
+  let creditCards = profileStorage.creditCards.getAll();
+
+  do_check_eq(creditCards.length, 2);
+  do_check_credit_card_matches(creditCards[0], TEST_CREDIT_CARD_1);
+  do_check_credit_card_matches(creditCards[1], TEST_CREDIT_CARD_2);
+
+  // Check computed fields.
+  do_check_eq(creditCards[0]["cc-given-name"], "John");
+  do_check_eq(creditCards[0]["cc-family-name"], "Doe");
+
+  // Test with noComputedFields set.
+  creditCards = profileStorage.creditCards.getAll({noComputedFields: true});
+  do_check_eq(creditCards[0]["cc-given-name"], undefined);
+  do_check_eq(creditCards[0]["cc-family-name"], undefined);
+
+  // Modifying output shouldn't affect the storage.
+  creditCards[0]["cc-name"] = "test";
+  do_check_credit_card_matches(profileStorage.creditCards.getAll()[0], TEST_CREDIT_CARD_1);
+});
+
+add_task(async function test_get() {
+  let path = getTempFile(TEST_STORE_FILE_NAME).path;
+  await prepareTestCreditCards(path);
+
+  let profileStorage = new ProfileStorage(path);
+  await profileStorage.initialize();
+
+  let creditCards = profileStorage.creditCards.getAll();
+  let guid = creditCards[0].guid;
+
+  let creditCard = profileStorage.creditCards.get(guid);
+  do_check_credit_card_matches(creditCard, TEST_CREDIT_CARD_1);
+
+  // Modifying output shouldn't affect the storage.
+  creditCards[0]["cc-name"] = "test";
+  do_check_credit_card_matches(profileStorage.creditCards.get(guid), TEST_CREDIT_CARD_1);
+
+  Assert.throws(() => profileStorage.creditCards.get("INVALID_GUID"),
+    /No matching record\./);
+});
+
+add_task(async function test_getByFilter() {
+  let path = getTempFile(TEST_STORE_FILE_NAME).path;
+  await prepareTestCreditCards(path);
+
+  let profileStorage = new ProfileStorage(path);
+  await profileStorage.initialize();
+
+  let filter = {info: {fieldName: "cc-name"}, searchString: "Tim"};
+  let creditCards = profileStorage.creditCards.getByFilter(filter);
+  do_check_eq(creditCards.length, 1);
+  do_check_credit_card_matches(creditCards[0], TEST_CREDIT_CARD_2);
+
+  // TODO: Uncomment this after decryption lands (bug 1337314).
+  // filter = {info: {fieldName: "cc-number"}, searchString: "11"};
+  // creditCards = profileStorage.creditCards.getByFilter(filter);
+  // do_check_eq(creditCards.length, 1);
+  // do_check_credit_card_matches(creditCards[0], TEST_CREDIT_CARD_2);
+});
+
+add_task(async function test_add() {
+  let path = getTempFile(TEST_STORE_FILE_NAME).path;
+  await prepareTestCreditCards(path);
+
+  let profileStorage = new ProfileStorage(path);
+  await profileStorage.initialize();
+
+  let creditCards = profileStorage.creditCards.getAll();
+
+  do_check_eq(creditCards.length, 2);
+
+  do_check_credit_card_matches(creditCards[0], TEST_CREDIT_CARD_1);
+  do_check_credit_card_matches(creditCards[1], TEST_CREDIT_CARD_2);
+
+  do_check_neq(creditCards[0].guid, undefined);
+  do_check_neq(creditCards[0].timeCreated, undefined);
+  do_check_eq(creditCards[0].timeLastModified, creditCards[0].timeCreated);
+  do_check_eq(creditCards[0].timeLastUsed, 0);
+  do_check_eq(creditCards[0].timesUsed, 0);
+
+  Assert.throws(() => profileStorage.creditCards.add(TEST_CREDIT_CARD_WITH_INVALID_FIELD),
+    /"invalidField" is not a valid field\./);
+});
+
+add_task(async function test_update() {
+  let path = getTempFile(TEST_STORE_FILE_NAME).path;
+  await prepareTestCreditCards(path);
+
+  let profileStorage = new ProfileStorage(path);
+  await profileStorage.initialize();
+
+  let creditCards = profileStorage.creditCards.getAll();
+  let guid = creditCards[1].guid;
+  let timeLastModified = creditCards[1].timeLastModified;
+
+  let onChanged = TestUtils.topicObserved("formautofill-storage-changed",
+                                          (subject, data) => data == "update");
+
+  do_check_neq(creditCards[1]["cc-name"], undefined);
+
+  profileStorage.creditCards.update(guid, TEST_CREDIT_CARD_3);
+  await onChanged;
+  await profileStorage._saveImmediately();
+
+  profileStorage = new ProfileStorage(path);
+  await profileStorage.initialize();
+
+  let creditCard = profileStorage.creditCards.get(guid);
+
+  do_check_eq(creditCard["cc-name"], undefined);
+  do_check_neq(creditCard.timeLastModified, timeLastModified);
+  do_check_credit_card_matches(creditCard, TEST_CREDIT_CARD_3);
+
+  Assert.throws(
+    () => profileStorage.creditCards.update("INVALID_GUID", TEST_CREDIT_CARD_3),
+    /No matching record\./
+  );
+
+  Assert.throws(
+    () => profileStorage.creditCards.update(guid, TEST_CREDIT_CARD_WITH_INVALID_FIELD),
+    /"invalidField" is not a valid field\./
+  );
+});
+
+add_task(async function test_validate() {
+  let path = getTempFile(TEST_STORE_FILE_NAME).path;
+
+  let profileStorage = new ProfileStorage(path);
+  await profileStorage.initialize();
+
+  profileStorage.creditCards.add(TEST_CREDIT_CARD_WITH_INVALID_EXPIRY_DATE);
+  profileStorage.creditCards.add(TEST_CREDIT_CARD_WITH_2_DIGITS_YEAR);
+  profileStorage.creditCards.add(TEST_CREDIT_CARD_WITH_SPACES_BETWEEN_DIGITS);
+
+  let creditCards = profileStorage.creditCards.getAll();
+
+  do_check_eq(creditCards[0]["cc-exp-month"], undefined);
+  do_check_eq(creditCards[0]["cc-exp-year"], undefined);
+
+  do_check_eq(creditCards[1]["cc-exp-month"], TEST_CREDIT_CARD_WITH_2_DIGITS_YEAR["cc-exp-month"]);
+  do_check_eq(creditCards[1]["cc-exp-year"],
+    parseInt(TEST_CREDIT_CARD_WITH_2_DIGITS_YEAR["cc-exp-year"], 10) + 2000);
+
+  do_check_eq(creditCards[2]["cc-number-masked"].length, 16);
+  // TODO: Check the decrypted numbers should not contain spaces after
+  //       decryption lands (bug 1337314).
+
+  Assert.throws(() => profileStorage.creditCards.add(TEST_CREDIT_CARD_WITH_INVALID_NUMBERS),
+    /Credit card number contains invalid characters\./);
+});
+
+add_task(async function test_notifyUsed() {
+  let path = getTempFile(TEST_STORE_FILE_NAME).path;
+  await prepareTestCreditCards(path);
+
+  let profileStorage = new ProfileStorage(path);
+  await profileStorage.initialize();
+
+  let creditCards = profileStorage.creditCards.getAll();
+  let guid = creditCards[1].guid;
+  let timeLastUsed = creditCards[1].timeLastUsed;
+  let timesUsed = creditCards[1].timesUsed;
+
+  let onChanged = TestUtils.topicObserved("formautofill-storage-changed",
+                                          (subject, data) => data == "notifyUsed");
+
+  profileStorage.creditCards.notifyUsed(guid);
+  await onChanged;
+  await profileStorage._saveImmediately();
+
+  profileStorage = new ProfileStorage(path);
+  await profileStorage.initialize();
+
+  let creditCard = profileStorage.creditCards.get(guid);
+
+  do_check_eq(creditCard.timesUsed, timesUsed + 1);
+  do_check_neq(creditCard.timeLastUsed, timeLastUsed);
+
+  Assert.throws(() => profileStorage.creditCards.notifyUsed("INVALID_GUID"),
+    /No matching record\./);
+});
+
+add_task(async function test_remove() {
+  let path = getTempFile(TEST_STORE_FILE_NAME).path;
+  await prepareTestCreditCards(path);
+
+  let profileStorage = new ProfileStorage(path);
+  await profileStorage.initialize();
+
+  let creditCards = profileStorage.creditCards.getAll();
+  let guid = creditCards[1].guid;
+
+  let onChanged = TestUtils.topicObserved("formautofill-storage-changed",
+                                          (subject, data) => data == "remove");
+
+  do_check_eq(creditCards.length, 2);
+
+  profileStorage.creditCards.remove(guid);
+  await onChanged;
+  await profileStorage._saveImmediately();
+
+  profileStorage = new ProfileStorage(path);
+  await profileStorage.initialize();
+
+  creditCards = profileStorage.creditCards.getAll();
+
+  do_check_eq(creditCards.length, 1);
+
+  Assert.throws(() => profileStorage.creditCards.get(guid), /No matching record\./);
+});
--- a/browser/extensions/formautofill/test/unit/test_enabledStatus.js
+++ b/browser/extensions/formautofill/test/unit/test_enabledStatus.js
@@ -59,28 +59,28 @@ add_task(function* test_enabledStatus_ob
 });
 
 add_task(function* test_enabledStatus_getStatus() {
   let formAutofillParent = new FormAutofillParent();
   do_register_cleanup(function cleanup() {
     Services.prefs.clearUserPref("extensions.formautofill.addresses.enabled");
   });
 
-  sinon.stub(profileStorage, "getAll");
-  profileStorage.getAll.returns([]);
+  sinon.stub(profileStorage.addresses, "getAll");
+  profileStorage.addresses.getAll.returns([]);
 
   // pref is enabled and profile is empty.
   Services.prefs.setBoolPref("extensions.formautofill.addresses.enabled", true);
   do_check_eq(formAutofillParent._getStatus(), false);
 
   // pref is disabled and profile is empty.
   Services.prefs.setBoolPref("extensions.formautofill.addresses.enabled", false);
   do_check_eq(formAutofillParent._getStatus(), false);
 
-  profileStorage.getAll.returns(["test-profile"]);
+  profileStorage.addresses.getAll.returns(["test-profile"]);
   // pref is enabled and profile is not empty.
   Services.prefs.setBoolPref("extensions.formautofill.addresses.enabled", true);
   do_check_eq(formAutofillParent._getStatus(), true);
 
   // pref is disabled and profile is not empty.
   Services.prefs.setBoolPref("extensions.formautofill.addresses.enabled", false);
   do_check_eq(formAutofillParent._getStatus(), false);
 });
deleted file mode 100644
--- a/browser/extensions/formautofill/test/unit/test_profileStorage.js
+++ /dev/null
@@ -1,274 +0,0 @@
-/**
- * Tests ProfileStorage object.
- */
-
-"use strict";
-
-const {ProfileStorage} = Cu.import("resource://formautofill/ProfileStorage.jsm", {});
-
-const TEST_STORE_FILE_NAME = "test-profile.json";
-
-const TEST_ADDRESS_1 = {
-  "given-name": "Timothy",
-  "additional-name": "John",
-  "family-name": "Berners-Lee",
-  organization: "World Wide Web Consortium",
-  "street-address": "32 Vassar Street\nMIT Room 32-G524",
-  "address-level2": "Cambridge",
-  "address-level1": "MA",
-  "postal-code": "02139",
-  country: "US",
-  tel: "+1 617 253 5702",
-  email: "timbl@w3.org",
-};
-
-const TEST_ADDRESS_2 = {
-  "street-address": "Some Address",
-  country: "US",
-};
-
-const TEST_ADDRESS_3 = {
-  "street-address": "Other Address",
-  "postal-code": "12345",
-};
-
-const TEST_ADDRESS_WITH_INVALID_FIELD = {
-  "street-address": "Another Address",
-  invalidField: "INVALID",
-};
-
-let prepareTestRecords = async function(path) {
-  let profileStorage = new ProfileStorage(path);
-  await profileStorage.initialize();
-
-  let onChanged = TestUtils.topicObserved("formautofill-storage-changed",
-                                          (subject, data) => data == "add");
-  profileStorage.add(TEST_ADDRESS_1);
-  await onChanged;
-  profileStorage.add(TEST_ADDRESS_2);
-  await profileStorage._saveImmediately();
-};
-
-let do_check_record_matches = (recordWithMeta, record) => {
-  for (let key in record) {
-    do_check_eq(recordWithMeta[key], record[key]);
-  }
-};
-
-add_task(async function test_initialize() {
-  let path = getTempFile(TEST_STORE_FILE_NAME).path;
-  let profileStorage = new ProfileStorage(path);
-  await profileStorage.initialize();
-
-  do_check_eq(profileStorage._store.data.version, 1);
-  do_check_eq(profileStorage._store.data.addresses.length, 0);
-
-  let data = profileStorage._store.data;
-
-  await profileStorage._saveImmediately();
-
-  profileStorage = new ProfileStorage(path);
-  await profileStorage.initialize();
-
-  Assert.deepEqual(profileStorage._store.data, data);
-});
-
-add_task(async function test_getAll() {
-  let path = getTempFile(TEST_STORE_FILE_NAME).path;
-  await prepareTestRecords(path);
-
-  let profileStorage = new ProfileStorage(path);
-  await profileStorage.initialize();
-
-  let addresses = profileStorage.getAll();
-
-  do_check_eq(addresses.length, 2);
-  do_check_record_matches(addresses[0], TEST_ADDRESS_1);
-  do_check_record_matches(addresses[1], TEST_ADDRESS_2);
-
-  // Modifying output shouldn't affect the storage.
-  addresses[0].organization = "test";
-  do_check_record_matches(profileStorage.getAll()[0], TEST_ADDRESS_1);
-});
-
-add_task(async function test_get() {
-  let path = getTempFile(TEST_STORE_FILE_NAME).path;
-  await prepareTestRecords(path);
-
-  let profileStorage = new ProfileStorage(path);
-  await profileStorage.initialize();
-
-  let addresses = profileStorage.getAll();
-  let guid = addresses[0].guid;
-
-  let address = profileStorage.get(guid);
-  do_check_record_matches(address, TEST_ADDRESS_1);
-
-  // Modifying output shouldn't affect the storage.
-  address.organization = "test";
-  do_check_record_matches(profileStorage.get(guid), TEST_ADDRESS_1);
-
-  Assert.throws(() => profileStorage.get("INVALID_GUID"),
-    /No matching record\./);
-});
-
-add_task(async function test_getByFilter() {
-  let path = getTempFile(TEST_STORE_FILE_NAME).path;
-  await prepareTestRecords(path);
-
-  let profileStorage = new ProfileStorage(path);
-  await profileStorage.initialize();
-
-  let filter = {info: {fieldName: "street-address"}, searchString: "Some"};
-  let addresses = profileStorage.getByFilter(filter);
-  do_check_eq(addresses.length, 1);
-  do_check_record_matches(addresses[0], TEST_ADDRESS_2);
-
-  filter = {info: {fieldName: "country"}, searchString: "u"};
-  addresses = profileStorage.getByFilter(filter);
-  do_check_eq(addresses.length, 2);
-  do_check_record_matches(addresses[0], TEST_ADDRESS_1);
-  do_check_record_matches(addresses[1], TEST_ADDRESS_2);
-
-  filter = {info: {fieldName: "street-address"}, searchString: "test"};
-  addresses = profileStorage.getByFilter(filter);
-  do_check_eq(addresses.length, 0);
-
-  filter = {info: {fieldName: "street-address"}, searchString: ""};
-  addresses = profileStorage.getByFilter(filter);
-  do_check_eq(addresses.length, 2);
-
-  // Check if the filtering logic is free from searching special chars.
-  filter = {info: {fieldName: "street-address"}, searchString: ".*"};
-  addresses = profileStorage.getByFilter(filter);
-  do_check_eq(addresses.length, 0);
-
-  // Prevent broken while searching the property that does not exist.
-  filter = {info: {fieldName: "tel"}, searchString: "1"};
-  addresses = profileStorage.getByFilter(filter);
-  do_check_eq(addresses.length, 0);
-});
-
-add_task(async function test_add() {
-  let path = getTempFile(TEST_STORE_FILE_NAME).path;
-  await prepareTestRecords(path);
-
-  let profileStorage = new ProfileStorage(path);
-  await profileStorage.initialize();
-
-  let addresses = profileStorage.getAll();
-
-  do_check_eq(addresses.length, 2);
-
-  do_check_record_matches(addresses[0], TEST_ADDRESS_1);
-  do_check_record_matches(addresses[1], TEST_ADDRESS_2);
-
-  do_check_neq(addresses[0].guid, undefined);
-  do_check_neq(addresses[0].timeCreated, undefined);
-  do_check_eq(addresses[0].timeLastModified, addresses[0].timeCreated);
-  do_check_eq(addresses[0].timeLastUsed, 0);
-  do_check_eq(addresses[0].timesUsed, 0);
-
-  Assert.throws(() => profileStorage.add(TEST_ADDRESS_WITH_INVALID_FIELD),
-    /"invalidField" is not a valid field\./);
-});
-
-add_task(async function test_update() {
-  let path = getTempFile(TEST_STORE_FILE_NAME).path;
-  await prepareTestRecords(path);
-
-  let profileStorage = new ProfileStorage(path);
-  await profileStorage.initialize();
-
-  let addresses = profileStorage.getAll();
-  let guid = addresses[1].guid;
-  let timeLastModified = addresses[1].timeLastModified;
-
-  let onChanged = TestUtils.topicObserved("formautofill-storage-changed",
-                                          (subject, data) => data == "update");
-
-  do_check_neq(addresses[1].country, undefined);
-
-  profileStorage.update(guid, TEST_ADDRESS_3);
-  await onChanged;
-  await profileStorage._saveImmediately();
-
-  profileStorage = new ProfileStorage(path);
-  await profileStorage.initialize();
-
-  let address = profileStorage.get(guid);
-
-  do_check_eq(address.country, undefined);
-  do_check_neq(address.timeLastModified, timeLastModified);
-  do_check_record_matches(address, TEST_ADDRESS_3);
-
-  Assert.throws(
-    () => profileStorage.update("INVALID_GUID", TEST_ADDRESS_3),
-    /No matching record\./
-  );
-
-  Assert.throws(
-    () => profileStorage.update(guid, TEST_ADDRESS_WITH_INVALID_FIELD),
-    /"invalidField" is not a valid field\./
-  );
-});
-
-add_task(async function test_notifyUsed() {
-  let path = getTempFile(TEST_STORE_FILE_NAME).path;
-  await prepareTestRecords(path);
-
-  let profileStorage = new ProfileStorage(path);
-  await profileStorage.initialize();
-
-  let addresses = profileStorage.getAll();
-  let guid = addresses[1].guid;
-  let timeLastUsed = addresses[1].timeLastUsed;
-  let timesUsed = addresses[1].timesUsed;
-
-  let onChanged = TestUtils.topicObserved("formautofill-storage-changed",
-                                          (subject, data) => data == "notifyUsed");
-
-  profileStorage.notifyUsed(guid);
-  await onChanged;
-  await profileStorage._saveImmediately();
-
-  profileStorage = new ProfileStorage(path);
-  await profileStorage.initialize();
-
-  let address = profileStorage.get(guid);
-
-  do_check_eq(address.timesUsed, timesUsed + 1);
-  do_check_neq(address.timeLastUsed, timeLastUsed);
-
-  Assert.throws(() => profileStorage.notifyUsed("INVALID_GUID"),
-    /No matching record\./);
-});
-
-add_task(async function test_remove() {
-  let path = getTempFile(TEST_STORE_FILE_NAME).path;
-  await prepareTestRecords(path);
-
-  let profileStorage = new ProfileStorage(path);
-  await profileStorage.initialize();
-
-  let addresses = profileStorage.getAll();
-  let guid = addresses[1].guid;
-
-  let onChanged = TestUtils.topicObserved("formautofill-storage-changed",
-                                          (subject, data) => data == "remove");
-
-  do_check_eq(addresses.length, 2);
-
-  profileStorage.remove(guid);
-  await onChanged;
-  await profileStorage._saveImmediately();
-
-  profileStorage = new ProfileStorage(path);
-  await profileStorage.initialize();
-
-  addresses = profileStorage.getAll();
-
-  do_check_eq(addresses.length, 1);
-
-  Assert.throws(() => profileStorage.get(guid), /No matching record\./);
-});
--- a/browser/extensions/formautofill/test/unit/test_savedFieldNames.js
+++ b/browser/extensions/formautofill/test/unit/test_savedFieldNames.js
@@ -40,18 +40,18 @@ add_task(async function test_profileSave
 
 add_task(async function test_profileSavedFieldNames_update() {
   let formAutofillParent = new FormAutofillParent();
   await formAutofillParent.init();
   do_register_cleanup(function cleanup() {
     Services.prefs.clearUserPref("extensions.formautofill.addresses.enabled");
   });
 
-  sinon.stub(profileStorage, "getAll");
-  profileStorage.getAll.returns([]);
+  sinon.stub(profileStorage.addresses, "getAll");
+  profileStorage.addresses.getAll.returns([]);
 
   // The set is empty if there's no profile in the store.
   formAutofillParent._updateSavedFieldNames();
   do_check_eq(Services.ppmm.initialProcessData.autofillSavedFieldNames.size, 0);
 
   // 2 profiles with 4 valid fields.
   let fakeStorage = [{
     guid: "test-guid-1",
@@ -69,17 +69,17 @@ add_task(async function test_profileSave
     "street-address": "331 E. Evelyn Avenue",
     tel: "1-650-903-0800",
     country: "US",
     timeCreated: 0,
     timeLastUsed: 0,
     timeLastModified: 0,
     timesUsed: 0,
   }];
-  profileStorage.getAll.returns(fakeStorage);
+  profileStorage.addresses.getAll.returns(fakeStorage);
   formAutofillParent._updateSavedFieldNames();
 
   let autofillSavedFieldNames = Services.ppmm.initialProcessData.autofillSavedFieldNames;
   do_check_eq(autofillSavedFieldNames.size, 4);
   do_check_eq(autofillSavedFieldNames.has("organization"), true);
   do_check_eq(autofillSavedFieldNames.has("street-address"), true);
   do_check_eq(autofillSavedFieldNames.has("tel"), true);
   do_check_eq(autofillSavedFieldNames.has("email"), false);
--- a/browser/extensions/formautofill/test/unit/test_transformFields.js
+++ b/browser/extensions/formautofill/test/unit/test_transformFields.js
@@ -3,17 +3,17 @@
  */
 
 "use strict";
 
 const {ProfileStorage} = Cu.import("resource://formautofill/ProfileStorage.jsm", {});
 
 const TEST_STORE_FILE_NAME = "test-profile.json";
 
-const COMPUTE_TESTCASES = [
+const ADDRESS_COMPUTE_TESTCASES = [
   // Empty
   {
     description: "Empty address",
     address: {
     },
     expectedResult: {
     },
   },
@@ -78,17 +78,17 @@ const COMPUTE_TESTCASES = [
       "street-address": "line1\nline2\nline3\nline4",
       "address-line1": "line1",
       "address-line2": "line2",
       "address-line3": "line3",
     },
   },
 ];
 
-const NORMALIZE_TESTCASES = [
+const ADDRESS_NORMALIZE_TESTCASES = [
   // Empty
   {
     description: "Empty address",
     address: {
     },
     expectedResult: {
     },
   },
@@ -175,53 +175,152 @@ const NORMALIZE_TESTCASES = [
       "address-line3": "line3",
     },
     expectedResult: {
       "street-address": "street address\nstreet address line 2",
     },
   },
 ];
 
+const CREDIT_CARD_COMPUTE_TESTCASES = [
+  // Empty
+  {
+    description: "Empty credit card",
+    creditCard: {
+    },
+    expectedResult: {
+    },
+  },
+
+  // Name
+  {
+    description: "Has \"cc-name\"",
+    creditCard: {
+      "cc-name": "Timothy John Berners-Lee",
+    },
+    expectedResult: {
+      "cc-name": "Timothy John Berners-Lee",
+      "cc-given-name": "Timothy",
+      "cc-additional-name": "John",
+      "cc-family-name": "Berners-Lee",
+    },
+  },
+];
+
+const CREDIT_CARD_NORMALIZE_TESTCASES = [
+  // Empty
+  {
+    description: "Empty credit card",
+    creditCard: {
+    },
+    expectedResult: {
+    },
+  },
+
+  // Name
+  {
+    description: "Has both \"cc-name\" and the split name fields",
+    creditCard: {
+      "cc-name": "Timothy John Berners-Lee",
+      "cc-given-name": "John",
+      "cc-family-name": "Doe",
+    },
+    expectedResult: {
+      "cc-name": "Timothy John Berners-Lee",
+    },
+  },
+  {
+    description: "Has only the split name fields",
+    creditCard: {
+      "cc-given-name": "John",
+      "cc-family-name": "Doe",
+    },
+    expectedResult: {
+      "cc-name": "John Doe",
+    },
+  },
+];
+
 let do_check_record_matches = (expectedRecord, record) => {
   for (let key in expectedRecord) {
     do_check_eq(expectedRecord[key], record[key] || "");
   }
 };
 
-add_task(async function test_computeFields() {
+add_task(async function test_computeAddressFields() {
   let path = getTempFile(TEST_STORE_FILE_NAME).path;
 
   let profileStorage = new ProfileStorage(path);
   await profileStorage.initialize();
 
-  COMPUTE_TESTCASES.forEach(testcase => profileStorage.add(testcase.address));
+  ADDRESS_COMPUTE_TESTCASES.forEach(testcase => profileStorage.addresses.add(testcase.address));
+  await profileStorage._saveImmediately();
+
+  profileStorage = new ProfileStorage(path);
+  await profileStorage.initialize();
+
+  let addresses = profileStorage.addresses.getAll();
+
+  for (let i in addresses) {
+    do_print("Verify testcase: " + ADDRESS_COMPUTE_TESTCASES[i].description);
+    do_check_record_matches(ADDRESS_COMPUTE_TESTCASES[i].expectedResult, addresses[i]);
+  }
+});
+
+add_task(async function test_normalizeAddressFields() {
+  let path = getTempFile(TEST_STORE_FILE_NAME).path;
+
+  let profileStorage = new ProfileStorage(path);
+  await profileStorage.initialize();
+
+  ADDRESS_NORMALIZE_TESTCASES.forEach(testcase => profileStorage.addresses.add(testcase.address));
   await profileStorage._saveImmediately();
 
   profileStorage = new ProfileStorage(path);
   await profileStorage.initialize();
 
-  let addresses = profileStorage.getAll();
+  let addresses = profileStorage.addresses.getAll();
 
   for (let i in addresses) {
-    do_print("Verify testcase: " + COMPUTE_TESTCASES[i].description);
-    do_check_record_matches(COMPUTE_TESTCASES[i].expectedResult, addresses[i]);
+    do_print("Verify testcase: " + ADDRESS_NORMALIZE_TESTCASES[i].description);
+    do_check_record_matches(ADDRESS_NORMALIZE_TESTCASES[i].expectedResult, addresses[i]);
   }
 });
 
-add_task(async function test_normalizeFields() {
+add_task(async function test_computeCreditCardFields() {
   let path = getTempFile(TEST_STORE_FILE_NAME).path;
 
   let profileStorage = new ProfileStorage(path);
   await profileStorage.initialize();
 
-  NORMALIZE_TESTCASES.forEach(testcase => profileStorage.add(testcase.address));
+  CREDIT_CARD_COMPUTE_TESTCASES.forEach(testcase => profileStorage.creditCards.add(testcase.creditCard));
   await profileStorage._saveImmediately();
 
   profileStorage = new ProfileStorage(path);
   await profileStorage.initialize();
 
-  let addresses = profileStorage.getAll();
+  let creditCards = profileStorage.creditCards.getAll();
 
-  for (let i in addresses) {
-    do_print("Verify testcase: " + NORMALIZE_TESTCASES[i].description);
-    do_check_record_matches(NORMALIZE_TESTCASES[i].expectedResult, addresses[i]);
+  for (let i in creditCards) {
+    do_print("Verify testcase: " + CREDIT_CARD_COMPUTE_TESTCASES[i].description);
+    do_check_record_matches(CREDIT_CARD_COMPUTE_TESTCASES[i].expectedResult, creditCards[i]);
   }
 });
+
+add_task(async function test_normalizeCreditCardFields() {
+  let path = getTempFile(TEST_STORE_FILE_NAME).path;
+
+  let profileStorage = new ProfileStorage(path);
+  await profileStorage.initialize();
+
+  CREDIT_CARD_NORMALIZE_TESTCASES.forEach(testcase => profileStorage.creditCards.add(testcase.creditCard));
+  await profileStorage._saveImmediately();
+
+  profileStorage = new ProfileStorage(path);
+  await profileStorage.initialize();
+
+  let creditCards = profileStorage.creditCards.getAll();
+
+  for (let i in creditCards) {
+    do_print("Verify testcase: " + CREDIT_CARD_NORMALIZE_TESTCASES[i].description);
+    do_check_record_matches(CREDIT_CARD_NORMALIZE_TESTCASES[i].expectedResult, creditCards[i]);
+  }
+});
--- a/browser/extensions/formautofill/test/unit/xpcshell.ini
+++ b/browser/extensions/formautofill/test/unit/xpcshell.ini
@@ -11,21 +11,22 @@ support-files =
 [heuristics/third_party/test_HomeDepot.js]
 [heuristics/third_party/test_Macys.js]
 [heuristics/third_party/test_NewEgg.js]
 [heuristics/third_party/test_OfficeDepot.js]
 [heuristics/third_party/test_QVC.js]
 [heuristics/third_party/test_Sears.js]
 [heuristics/third_party/test_Staples.js]
 [heuristics/third_party/test_Walmart.js]
+[test_addressRecords.js]
 [test_autofillFormFields.js]
 [test_collectFormFields.js]
+[test_creditCardRecords.js]
 [test_enabledStatus.js]
 [test_findLabelElements.js]
 [test_getFormInputDetails.js]
 [test_isCJKName.js]
 [test_markAsAutofillField.js]
 [test_nameUtils.js]
 [test_onFormSubmitted.js]
 [test_profileAutocompleteResult.js]
-[test_profileStorage.js]
 [test_savedFieldNames.js]
 [test_transformFields.js]
--- a/browser/extensions/mortar/host/common/ppapi-runtime.jsm
+++ b/browser/extensions/mortar/host/common/ppapi-runtime.jsm
@@ -53,18 +53,17 @@ const PP_ERROR_ADDRESS_IN_USE = -108;
 const PP_ERROR_MESSAGE_TOO_BIG = -109;
 const PP_ERROR_NAME_NOT_RESOLVED = -110;
 
 // Point is defined as 1/72 of an inch (25.4mm)
 const POINT_PER_INCH = 72;
 const POINT_PER_MILLIMETER = POINT_PER_INCH / 25.4;
 
 const PRINT_FILE_NAME = "print.pdf";
-const PRINT_CONTENT_TEMP_KEY =
-  (Services.appinfo.OS == "Linux") ? "TmpD" : "ContentTmpD";
+const PRINT_TEMP_KEY = "TmpD";
 
 const PP_Bool = {
   PP_FALSE: 0,
   PP_TRUE: 1,
 };
 
 const PP_AudioFrameSize = {
   PP_AUDIOMINSAMPLEFRAMECOUNT: 64,
@@ -5006,17 +5005,17 @@ dump(`callFromJSON: < ${JSON.stringify(c
           instance, page_ranges: pageRanges, page_range_count: pageRangeCount
         }), true);
       if (!bufferId) {
         return;
       }
       let buffer = PP_Resource.lookup(bufferId);
 
       // Save PDF to file
-      let file = Services.dirsvc.get(PRINT_CONTENT_TEMP_KEY, Ci.nsIFile);
+      let file = Services.dirsvc.get(PRINT_TEMP_KEY, Ci.nsIFile);
       file.append(PRINT_FILE_NAME);
       file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, PR_IRUSR | PR_IWUSR);
       let stream = Cc["@mozilla.org/network/file-output-stream;1"].
                    createInstance(Ci.nsIFileOutputStream);
       stream.init(file, PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE,
                   PR_IRUSR | PR_IWUSR, 0);
       let binaryStream = Cc["@mozilla.org/binaryoutputstream;1"].
                          createInstance(Ci.nsIBinaryOutputStream);
@@ -5028,17 +5027,17 @@ dump(`callFromJSON: < ${JSON.stringify(c
       stream.flush();
       stream.close();
       buffer.unmap();
       buffer.release();
       instance.rt.call(new InterfaceMemberCall(
         "PPP_Printing(Dev);0.6", "End", { instance }), true);
       // We need permission for printing PDF file
       instance.mm.sendAsyncMessage("ppapipdf.js:printPDF", {
-        contentTempKey: PRINT_CONTENT_TEMP_KEY, fileName: PRINT_FILE_NAME });
+        filePath: file.path });
     },
 
     /**
      * void SearchString(
      *   [in] PP_Instance instance,
      *   [in] mem_t str,
      *   [in] mem_t term,
      *   [in] PP_Bool case_sensitive,
--- a/browser/extensions/mortar/host/pdf/ppapi-content-sandbox.js
+++ b/browser/extensions/mortar/host/pdf/ppapi-content-sandbox.js
@@ -8,16 +8,17 @@
  * This code runs in the sandbox in the content process where the page that
  * loaded the plugin lives. It communicates with the process where the PPAPI
  * implementation lives.
  */
 const { classes: Cc, interfaces: Ci, results: Cr, utils: Cu } = Components;
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/FileUtils.jsm");
 
 XPCOMUtils.defineLazyModuleGetter(this, "NetUtil",
                                           "resource://gre/modules/NetUtil.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
                              "resource://gre/modules/PrivateBrowsingUtils.jsm");
 
 let mm = pluginElement.frameLoader.messageManager;
 let containerWindow = pluginElement.ownerGlobal;
@@ -152,18 +153,17 @@ mm.addMessageListener("ppapipdf.js:getPr
   trimPrintSettings.startPageRange = printSettings.startPageRange;
   trimPrintSettings.endPageRange = printSettings.endPageRange;
 
   mm.sendAsyncMessage("ppapipdf.js:printsettingschanged", {
     trimPrintSettings });
 });
 
 mm.addMessageListener("ppapipdf.js:printPDF", ({ data }) => {
-  let file = Services.dirsvc.get(data.contentTempKey, Ci.nsIFile);
-  file.append(data.fileName);
+  let file = new FileUtils.File(data.filePath);
   if (!file.exists()) {
     return;
   }
 
   let webBrowserPrint =
     containerWindow.QueryInterface(Ci.nsIInterfaceRequestor).
     getInterface(Ci.nsIWebBrowserPrint);
   if (!webBrowserPrint || !webBrowserPrint.printPDF) {
--- 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/ipc/TabChild.cpp
+++ b/dom/ipc/TabChild.cpp
@@ -1681,22 +1681,26 @@ TabChild::MaybeCoalesceWheelEvent(const 
             *aIsNextWheelEvent = true;
           }
           return false; // Stop peeking.
         });
     // We only coalesce the current event when
     // 1. It's eWheel (we don't coalesce eOperationStart and eWheelOperationEnd)
     // 2. It's not the first wheel event.
     // 3. It's not the last wheel event.
-    // 4. It's dispatched before the last wheel event was processed.
+    // 4. It's dispatched before the last wheel event was processed +
+    //    the processing time of the last event.
+    //    This way pages spending lots of time in wheel listeners get wheel
+    //    events coalesced more aggressively.
     // 5. It has same attributes as the coalesced wheel event which is not yet
     //    fired.
     if (!mLastWheelProcessedTimeFromParent.IsNull() &&
         *aIsNextWheelEvent &&
-        aEvent.mTimeStamp < mLastWheelProcessedTimeFromParent &&
+        aEvent.mTimeStamp < (mLastWheelProcessedTimeFromParent +
+                             mLastWheelProcessingDuration) &&
         (mCoalescedWheelData.IsEmpty() ||
          mCoalescedWheelData.CanCoalesce(aEvent, aGuid, aInputBlockId))) {
       mCoalescedWheelData.Coalesce(aEvent, aGuid, aInputBlockId);
       return true;
     }
   }
   return false;
 }
@@ -1754,18 +1758,18 @@ TabChild::RecvMouseWheelEvent(const Widg
   }
   if (isNextWheelEvent) {
     // Update mLastWheelProcessedTimeFromParent so that we can compare the end
     // time of the current event with the dispatched time of the next event.
     mLastWheelProcessedTimeFromParent = aEvent.mTimeStamp;
     mozilla::TimeStamp beforeDispatchingTime = TimeStamp::Now();
     MaybeDispatchCoalescedWheelEvent();
     DispatchWheelEvent(aEvent, aGuid, aInputBlockId);
-    mLastWheelProcessedTimeFromParent +=
-      (TimeStamp::Now() - beforeDispatchingTime);
+    mLastWheelProcessingDuration = (TimeStamp::Now() - beforeDispatchingTime);
+    mLastWheelProcessedTimeFromParent += mLastWheelProcessingDuration;
   } else {
     // This is the last wheel event. Set mLastWheelProcessedTimeFromParent to
     // null moment to avoid coalesce the next incoming wheel event.
     mLastWheelProcessedTimeFromParent = TimeStamp();
     MaybeDispatchCoalescedWheelEvent();
     DispatchWheelEvent(aEvent, aGuid, aInputBlockId);
   }
   return IPC_OK();
--- a/dom/ipc/TabChild.h
+++ b/dom/ipc/TabChild.h
@@ -837,16 +837,17 @@ private:
   // event so that in case event handling takes time, some repeated events can
   // be skipped to not flood child process.
   mozilla::TimeStamp mRepeatedKeyEventTime;
 
   // Similar to mRepeatedKeyEventTime, store the end time (from parent process)
   // of handling the last repeated wheel event so that in case event handling
   // takes time, some repeated events can be skipped to not flood child process.
   mozilla::TimeStamp mLastWheelProcessedTimeFromParent;
+  mozilla::TimeDuration mLastWheelProcessingDuration;
   CoalescedWheelData mCoalescedWheelData;
 
   AutoTArray<bool, NUMBER_OF_AUDIO_CHANNELS> mAudioChannelsActive;
 
   RefPtr<layers::IAPZCTreeManager> mApzcTreeManager;
 
   // The most recently seen layer observer epoch in RecvSetDocShellIsActive.
   uint64_t mLayerObserverEpoch;
--- a/dom/media/MediaCache.cpp
+++ b/dom/media/MediaCache.cpp
@@ -19,18 +19,20 @@
 #include "nsIPrincipal.h"
 #include "mozilla/Attributes.h"
 #include "mozilla/Services.h"
 #include <algorithm>
 
 namespace mozilla {
 
 #undef LOG
+#undef LOGI
 LazyLogModule gMediaCacheLog("MediaCache");
 #define LOG(...) MOZ_LOG(gMediaCacheLog, LogLevel::Debug, (__VA_ARGS__))
+#define LOGI(...) MOZ_LOG(gMediaCacheLog, LogLevel::Info, (__VA_ARGS__))
 
 
 // Readahead blocks for non-seekable streams will be limited to this
 // fraction of the cache space. We don't normally evict such blocks
 // because replacing them requires a seek, but we need to make sure
 // they don't monopolize the cache.
 static const double NONSEEKABLE_READAHEAD_MAX = 0.5;
 
@@ -1238,22 +1240,24 @@ MediaCache::Update()
       } else if (mIndex.Length() > uint32_t(maxBlocks)) {
         // We're in the process of bringing the cache size back to the
         // desired limit, so don't bring in more data yet
         LOG("Stream %p throttling to reduce cache size", stream);
         enableReading = false;
       } else {
         TimeDuration predictedNewDataUse = PredictNextUseForIncomingData(stream);
 
-        if (stream->mCacheSuspended &&
+        if (stream->mThrottleReadahead &&
+            stream->mCacheSuspended &&
             predictedNewDataUse.ToSeconds() > resumeThreshold) {
           // Don't need data for a while, so don't bother waking up the stream
           LOG("Stream %p avoiding wakeup since more data is not needed", stream);
           enableReading = false;
-        } else if (predictedNewDataUse.ToSeconds() > readaheadLimit) {
+        } else if (stream->mThrottleReadahead &&
+                   predictedNewDataUse.ToSeconds() > readaheadLimit) {
           // Don't read ahead more than this much
           LOG("Stream %p throttling to avoid reading ahead too far", stream);
           enableReading = false;
         } else if (freeBlockCount > 0) {
           // Free blocks in the cache, so keep reading
           LOG("Stream %p reading since there are free blocks", stream);
           enableReading = true;
         } else if (latestNextUse <= TimeDuration(0)) {
@@ -2203,16 +2207,28 @@ MediaCacheStream::Seek(int32_t aWhence, 
 
   LOG("Stream %p Seek to %" PRId64, this, mStreamOffset);
   gMediaCache->NoteSeek(this, oldOffset);
 
   gMediaCache->QueueUpdate();
   return NS_OK;
 }
 
+void
+MediaCacheStream::ThrottleReadahead(bool bThrottle)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  if (mThrottleReadahead != bThrottle) {
+    LOGI("Stream %p ThrottleReadahead %d", this, bThrottle);
+    mThrottleReadahead = bThrottle;
+    ReentrantMonitorAutoEnter mon(gMediaCache->GetReentrantMonitor());
+    gMediaCache->QueueUpdate();
+  }
+}
+
 int64_t
 MediaCacheStream::Tell()
 {
   ReentrantMonitorAutoEnter mon(gMediaCache->GetReentrantMonitor());
   return mStreamOffset;
 }
 
 nsresult
@@ -2485,8 +2501,9 @@ nsresult MediaCacheStream::GetCachedRang
   }
   return NS_OK;
 }
 
 } // namespace mozilla
 
 // avoid redefined macro in unified build
 #undef LOG
+#undef LOGI
--- a/dom/media/MediaCache.h
+++ b/dom/media/MediaCache.h
@@ -343,16 +343,18 @@ public:
   // this will block until the data is available or the stream is
   // closed, otherwise it won't block.
   nsresult Read(char* aBuffer, uint32_t aCount, uint32_t* aBytes);
   // Seeks to aOffset in the stream then performs a Read operation. See
   // 'Read' for argument and return details.
   nsresult ReadAt(int64_t aOffset, char* aBuffer,
                   uint32_t aCount, uint32_t* aBytes);
 
+  void ThrottleReadahead(bool bThrottle);
+
   size_t SizeOfExcludingThis(MallocSizeOf aMallocSizeOf) const;
 
 private:
   friend class MediaCache;
 
   /**
    * A doubly-linked list of blocks. Add/Remove/Get methods are all
    * constant time. We declare this here so that a stream can contain a
@@ -509,13 +511,15 @@ private:
   // mChannelOffset%BLOCK_SIZE bytes have been filled in with good data,
   // the rest are garbage.
   // Heap allocate this buffer since the exact power-of-2 will cause allocation
   // slop when combined with the rest of the object members.
   UniquePtr<uint8_t[]> mPartialBlockBuffer = MakeUnique<uint8_t[]>(BLOCK_SIZE);
 
   // True if associated with a private browsing window.
   const bool mIsPrivateBrowsing;
+
+  bool mThrottleReadahead = false;
 };
 
 } // namespace mozilla
 
 #endif
--- a/dom/media/MediaDecoder.cpp
+++ b/dom/media/MediaDecoder.cpp
@@ -1054,16 +1054,17 @@ MediaDecoder::NotifySuspendedStatusChang
 
 void
 MediaDecoder::NotifyBytesDownloaded()
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_DIAGNOSTIC_ASSERT(!IsShutdown());
   UpdatePlaybackRate();
   GetOwner()->DownloadProgressed();
+  mResource->ThrottleReadahead(CanPlayThrough());
 }
 
 void
 MediaDecoder::NotifyDownloadEnded(nsresult aStatus)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_DIAGNOSTIC_ASSERT(!IsShutdown());
 
--- a/dom/media/MediaResource.cpp
+++ b/dom/media/MediaResource.cpp
@@ -711,16 +711,22 @@ ChannelMediaResource::MediaReadAt(int64_
     aOffset += bytesRead;
     aCount -= bytesRead;
     curr += bytesRead;
   }
   bytes->SetLength(curr - start);
   return bytes.forget();
 }
 
+void
+ChannelMediaResource::ThrottleReadahead(bool bThrottle)
+{
+  mCacheStream.ThrottleReadahead(bThrottle);
+}
+
 int64_t ChannelMediaResource::Tell()
 {
   return mCacheStream.Tell();
 }
 
 nsresult ChannelMediaResource::GetCachedRanges(MediaByteRangeSet& aRanges)
 {
   return mCacheStream.GetCachedRanges(aRanges);
--- a/dom/media/MediaResource.h
+++ b/dom/media/MediaResource.h
@@ -248,16 +248,21 @@ public:
     bool ok = bytes->SetLength(aCount, fallible);
     NS_ENSURE_TRUE(ok, nullptr);
     char* curr = reinterpret_cast<char*>(bytes->Elements());
     nsresult rv = ReadFromCache(curr, aOffset, aCount);
     NS_ENSURE_SUCCESS(rv, nullptr);
     return bytes.forget();
   }
 
+  // Pass true to limit the amount of readahead data (specified by
+  // "media.cache_readahead_limit") or false to read as much as the
+  // cache size allows.
+  virtual void ThrottleReadahead(bool bThrottle) { }
+
   // Report the current offset in bytes from the start of the stream.
   // This is used to approximate where we currently are in the playback of a
   // media.
   // A call to ReadAt will update this position.
   virtual int64_t Tell() = 0;
   // Moves any existing channel loads into or out of background. Background
   // loads don't block the load event. This also determines whether or not any
   // new loads initiated (for example to seek) will be in the background.
@@ -551,16 +556,18 @@ public:
   // MediaCacheStream::NotifyDataReceived/Ended.
   // This can fail.
   nsresult CacheClientSeek(int64_t aOffset, bool aResume);
   // Suspend the current load since data is currently not wanted
   nsresult CacheClientSuspend();
   // Resume the current load since data is wanted again
   nsresult CacheClientResume();
 
+  void ThrottleReadahead(bool bThrottle) override;
+
   // Ensure that the media cache writes any data held in its partial block.
   // Called on the main thread.
   void FlushCache() override;
 
   // Notify that the last data byte range was loaded.
   void NotifyLastByteRange() override;
 
   // Main thread
--- 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/platforms/ffmpeg/FFmpegLibWrapper.cpp
+++ b/dom/media/platforms/ffmpeg/FFmpegLibWrapper.cpp
@@ -2,16 +2,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "FFmpegLibWrapper.h"
 #include "FFmpegLog.h"
 #include "MediaPrefs.h"
 #include "mozilla/PodOperations.h"
 #include "mozilla/Types.h"
+#include "PlatformDecoderModule.h"
 #include "prlink.h"
 
 #define AV_LOG_DEBUG    48
 
 namespace mozilla
 {
 
 FFmpegLibWrapper::LinkResult
--- a/dom/media/platforms/ffmpeg/FFmpegLibWrapper.h
+++ b/dom/media/platforms/ffmpeg/FFmpegLibWrapper.h
@@ -1,15 +1,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef __FFmpegLibWrapper_h__
 #define __FFmpegLibWrapper_h__
 
+#include "mozilla/Attributes.h"
 #include "mozilla/Types.h"
 
 struct AVCodec;
 struct AVCodecContext;
 struct AVFrame;
 struct AVPacket;
 struct AVDictionary;
 struct AVCodecParserContext;
--- 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
@@ -568,17 +568,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
@@ -757,17 +757,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)
@@ -831,49 +831,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.
@@ -1701,17 +1737,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);
 
@@ -1842,17 +1878,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;
@@ -1895,18 +1931,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
@@ -449,17 +449,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.
@@ -484,17 +484,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/DeviceManagerDx.cpp
+++ b/gfx/thebes/DeviceManagerDx.cpp
@@ -11,22 +11,25 @@
 #include "gfxWindowsPlatform.h"
 #include "mozilla/D3DMessageUtils.h"
 #include "mozilla/Telemetry.h"
 #include "mozilla/WindowsVersion.h"
 #include "mozilla/gfx/GraphicsMessages.h"
 #include "mozilla/gfx/Logging.h"
 #include "mozilla/layers/CompositorThread.h"
 #include "mozilla/layers/DeviceAttachmentsD3D11.h"
-#include "nsExceptionHandler.h"
 #include "nsIGfxInfo.h"
-#include "nsPrintfCString.h"
 #include <d3d11.h>
 #include <ddraw.h>
 
+#ifdef MOZ_CRASHREPORTER
+#include "nsExceptionHandler.h"
+#include "nsPrintfCString.h"
+#endif
+
 namespace mozilla {
 namespace gfx {
 
 using namespace mozilla::widget;
 
 StaticAutoPtr<DeviceManagerDx> DeviceManagerDx::sInstance;
 
 // We don't have access to the D3D11CreateDevice type in gfxWindowsPlatform.h,
--- 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
@@ -149,33 +149,36 @@ function treatAsSafeArgument(entry, varN
         // RawGeckoBorrowedNode thread-mutable parameters.
         ["Gecko_SetNodeFlags", "aNode", null],
         ["Gecko_UnsetNodeFlags", "aNode", null],
 
         // Various Servo binding out parameters. This is a mess and there needs
         // to be a way to indicate which params are out parameters, either using
         // an attribute or a naming convention.
         ["Gecko_CopyFontFamilyFrom", "dst", null],
-        ["Gecko_SetListStyleType", "style_struct", null],
-        ["Gecko_CopyListStyleTypeFrom", "dst", null],
+        ["Gecko_SetListStyleType", "aList", null],
+        ["Gecko_CopyListStyleTypeFrom", "aDst", null],
         ["Gecko_SetMozBinding", "aDisplay", null],
         [/ClassOrClassList/, /aClass/, null],
         ["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