merge autoland to mozilla-central a=merge
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Fri, 03 Mar 2017 13:01:04 +0100
changeset 345723 31c09bb63b697089f9866b7cbfa57b8900df2b1d
parent 345643 9732cd019a8b94c49a275661320c1b742635a3d6 (current diff)
parent 345722 a4fedf44e1db492420baa4af6fc9bf125c6d34f4 (diff)
child 345771 a793136c90bc5d32f2c82aa7dea4bc300c4f1836
push id31443
push usercbook@mozilla.com
push dateFri, 03 Mar 2017 12:01:25 +0000
treeherdermozilla-central@31c09bb63b69 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone54.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 autoland to mozilla-central a=merge
devtools/client/framework/source-map-util.js
devtools/client/framework/source-map-worker.js
devtools/client/framework/source-map.js
release/docker/beet-mover/Dockerfile
release/docker/beet-mover/requirements.txt
release/docker/firefox-snap/Dockerfile
release/docker/firefox-snap/Makefile
release/docker/firefox-snap/distribution.ini
release/docker/firefox-snap/runme.sh
release/docker/firefox-snap/snapcraft.yaml.in
release/docker/funsize-balrog-submitter/Dockerfile
release/docker/funsize-balrog-submitter/Makefile
release/docker/funsize-balrog-submitter/dep.pubkey
release/docker/funsize-balrog-submitter/nightly.pubkey
release/docker/funsize-balrog-submitter/release.pubkey
release/docker/funsize-balrog-submitter/requirements.txt
release/docker/funsize-balrog-submitter/runme.sh
release/docker/funsize-balrog-submitter/scripts/funsize-balrog-submitter.py
release/docker/funsize-update-generator/Dockerfile
release/docker/funsize-update-generator/Makefile
release/docker/funsize-update-generator/dep.pubkey
release/docker/funsize-update-generator/nightly.pubkey
release/docker/funsize-update-generator/release.pubkey
release/docker/funsize-update-generator/requirements.txt
release/docker/funsize-update-generator/runme.sh
release/docker/funsize-update-generator/scripts/funsize.py
release/docker/funsize-update-generator/scripts/mbsdiff_hook.sh
testing/mozharness/configs/beetmover/en_us.yml.tmpl
toolkit/components/mozintl/MozIntl.cpp
toolkit/components/mozintl/MozIntl.h
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -180,18 +180,16 @@ pref("browser.eme.ui.enabled", false);
 #endif
 
 // UI tour experience.
 pref("browser.uitour.enabled", true);
 pref("browser.uitour.loglevel", "Error");
 pref("browser.uitour.requireSecure", true);
 pref("browser.uitour.themeOrigin", "https://addons.mozilla.org/%LOCALE%/firefox/themes/");
 pref("browser.uitour.url", "https://www.mozilla.org/%LOCALE%/firefox/%VERSION%/tour/");
-// This is used as a regexp match against the page's URL.
-pref("browser.uitour.readerViewTrigger", "^https:\\/\\/www\\.mozilla\\.org\\/[^\\/]+\\/firefox\\/reading\\/start");
 // How long to show a Hearbeat survey (two hours, in seconds)
 pref("browser.uitour.surveyDuration", 7200);
 
 pref("browser.customizemode.tip0.shown", false);
 pref("browser.customizemode.tip0.learnMoreUrl", "https://support.mozilla.org/1/firefox/%VERSION%/%OS%/%LOCALE%/customize");
 
 pref("keyword.enabled", true);
 pref("browser.fixup.domainwhitelist.localhost", true);
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -4642,18 +4642,16 @@ var XULBrowserWindow = {
       URLBarSetURI(aLocationURI);
 
       BookmarkingUI.onLocationChange();
 
       gIdentityHandler.onLocationChange();
 
       SocialUI.updateState();
 
-      UITour.onLocationChange(location);
-
       gTabletModePageCounter.inc();
 
       // Utility functions for disabling find
       var shouldDisableFind = function(aDocument) {
         let docElt = aDocument.documentElement;
         return docElt && docElt.getAttribute("disablefastfind") == "true";
       }
 
--- a/browser/base/content/test/webextensions/browser_extension_sideloading.js
+++ b/browser/base/content/test/webextensions/browser_extension_sideloading.js
@@ -201,18 +201,20 @@ add_task(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"
-  let icon = panel.getAttribute("icon");
-  is(icon, ICON_URL, "Permissions notification has the addon icon");
+  checkNotification(panel, ICON_URL, [
+    ["webextPerms.hostDescription.allUrls"],
+    ["webextPerms.description.history"],
+  ]);
 
   let disablePromise = promiseSetDisabled(mock1);
   panel.secondaryButton.click();
 
   let value = yield disablePromise;
   is(value, true, "Addon should remain disabled");
 
   let [addon1, addon2, addon3, addon4] = yield AddonManager.getAddonsByIDs([ID1, ID2, ID3, ID4]);
@@ -237,19 +239,20 @@ add_task(function* () {
 
   // Again we should be at the extentions list in about:addons
   is(gBrowser.currentURI.spec, "about:addons", "Foreground tab is at about:addons");
 
   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, this time accept the install
-  icon = panel.getAttribute("icon");
-  is(icon, DEFAULT_ICON_URL, "Permissions notification has the default icon");
+  // Check the notification contents.
+  checkNotification(panel, DEFAULT_ICON_URL, []);
+
+  // This time accept the install.
   disablePromise = promiseSetDisabled(mock2);
   panel.button.click();
 
   value = yield disablePromise;
   is(value, false, "Addon should be set to enabled");
 
   [addon1, addon2, addon3, addon4] = yield AddonManager.getAddonsByIDs([ID1, ID2, ID3, ID4]);
   is(addon1.userDisabled, true, "Addon 1 should still be disabled");
@@ -280,16 +283,17 @@ add_task(function* () {
   ok(is_visible(item._enableBtn), "Enable button is visible for sideloaded extension");
   ok(is_hidden(item._disableBtn), "Disable button is not visible for sideloaded extension");
 
   // When clicking enable we should see the permissions notification
   popupPromise = promisePopupNotificationShown("addon-webext-permissions");
   BrowserTestUtils.synthesizeMouseAtCenter(item._enableBtn, {},
                                            gBrowser.selectedBrowser);
   panel = yield popupPromise;
+  checkNotification(panel, DEFAULT_ICON_URL, [["webextPerms.hostDescription.allUrls"]]);
 
   // Accept the permissions
   disablePromise = promiseSetDisabled(mock3);
   panel.button.click();
   value = yield disablePromise;
   is(value, false, "userDisabled should be set on addon 3");
 
   addon3 = yield AddonManager.getAddonByID(ID3);
@@ -307,16 +311,17 @@ add_task(function* () {
   win = yield BrowserOpenAddonsMgr(`addons://detail/${encodeURIComponent(ID4)}`);
   let button = win.document.getElementById("detail-enable-btn");
 
   // When clicking enable we should see the permissions notification
   popupPromise = promisePopupNotificationShown("addon-webext-permissions");
   BrowserTestUtils.synthesizeMouseAtCenter(button, {},
                                            gBrowser.selectedBrowser);
   panel = yield popupPromise;
+  checkNotification(panel, DEFAULT_ICON_URL, [["webextPerms.hostDescription.allUrls"]]);
 
   // Accept the permissions
   disablePromise = promiseSetDisabled(mock4);
   panel.button.click();
   value = yield disablePromise;
   is(value, false, "userDisabled should be set on addon 4");
 
   addon4 = yield AddonManager.getAddonByID(ID4);
--- a/browser/base/content/test/webextensions/browser_extension_update_interactive.js
+++ b/browser/base/content/test/webextensions/browser_extension_update_interactive.js
@@ -24,90 +24,106 @@ function* interactiveUpdateTest(autoUpda
     ["extensions.update.autoUpdateDefault", autoUpdate],
 
     // Point updates to the local mochitest server
     ["extensions.update.url", `${BASE}/browser_webext_update.json`],
   ]});
 
   // Trigger an update check, manually applying the update if we're testing
   // without auto-update.
-  function* triggerUpdate(win, addon) {
+  async function triggerUpdate(win, addon) {
     let manualUpdatePromise;
     if (!autoUpdate) {
       manualUpdatePromise = new Promise(resolve => {
         let listener = {
           onNewInstall() {
             AddonManager.removeInstallListener(listener);
             resolve();
           },
         };
         AddonManager.addInstallListener(listener);
       });
     }
 
-    checkFn(win, addon);
+    let promise = checkFn(win, addon);
 
     if (manualUpdatePromise) {
-      yield manualUpdatePromise;
+      await manualUpdatePromise;
 
       let list = win.document.getElementById("addon-list");
 
       // Make sure we have XBL bindings
       list.clientHeight;
 
       let item = list.children.find(_item => _item.value == ID);
       EventUtils.synthesizeMouseAtCenter(item._updateBtn, {}, win);
     }
+
+    return {promise};
   }
 
   // Navigate away from the starting page to force about:addons to load
   // in a new tab during the tests below.
   gBrowser.selectedBrowser.loadURI("about:robots");
   yield BrowserTestUtils.browserLoaded(gBrowser.selectedBrowser);
 
   // Install version 1.0 of the test extension
   let addon = yield promiseInstallAddon(`${BASE}/browser_webext_update1.xpi`);
   ok(addon, "Addon was installed");
   is(addon.version, "1.0", "Version 1 of the addon is installed");
 
   let win = yield BrowserOpenAddonsMgr("addons://list/extension");
 
   // Trigger an update check
   let popupPromise = promisePopupNotificationShown("addon-webext-permissions");
-  yield triggerUpdate(win, addon);
+  let {promise: checkPromise} = yield triggerUpdate(win, addon);
   let panel = yield popupPromise;
 
   // Click the cancel button, wait to see the cancel event
   let cancelPromise = promiseInstallEvent(addon, "onInstallCancelled");
   panel.secondaryButton.click();
   yield cancelPromise;
 
   addon = yield AddonManager.getAddonByID(ID);
   is(addon.version, "1.0", "Should still be running the old version");
 
+  // Make sure the update check is completely finished.
+  yield checkPromise;
+
   // Trigger a new update check
   popupPromise = promisePopupNotificationShown("addon-webext-permissions");
-  yield triggerUpdate(win, addon);
+  checkPromise = (yield triggerUpdate(win, addon)).promise;
 
   // This time, accept the upgrade
   let updatePromise = promiseInstallEvent(addon, "onInstallEnded");
   panel = yield popupPromise;
   panel.button.click();
 
   addon = yield updatePromise;
   is(addon.version, "2.0", "Should have upgraded");
 
+  yield checkPromise;
+
   yield BrowserTestUtils.removeTab(gBrowser.selectedTab);
   addon.uninstall();
   yield SpecialPowers.popPrefEnv();
 }
 
 // Invoke the "Check for Updates" menu item
 function checkAll(win) {
   win.gViewController.doCommand("cmd_findAllUpdates");
+  return new Promise(resolve => {
+    let observer = {
+      observe(subject, topic, data) {
+        Services.obs.removeObserver(observer, "EM-update-check-finished");
+        resolve();
+      },
+    };
+    Services.obs.addObserver(observer, "EM-update-check-finished", false);
+  });
 }
 
 // Test "Check for Updates" with both auto-update settings
 add_task(() => interactiveUpdateTest(true, checkAll));
 add_task(() => interactiveUpdateTest(false, checkAll));
 
 
 // Invoke an invidual extension's "Find Updates" menu item
--- a/browser/base/content/test/webextensions/head.js
+++ b/browser/base/content/test/webextensions/head.js
@@ -145,16 +145,60 @@ function checkPermissionString(string, k
     ok(string.startsWith(localizedString.slice(0, i)), msg);
     ok(string.endsWith(localizedString.slice(i + 2)), msg);
   } else {
     is(string, localizedString, msg);
   }
 }
 
 /**
+ * Check the contents of a permission popup notification
+ *
+ * @param {Window} panel
+ *        The popup window.
+ * @param {string|regexp|function} checkIcon
+ *        The icon expected to appear in the notification.  If this is a
+ *        string, it must match the icon url exactly.  If it is a
+ *        regular expression it is tested against the icon url, and if
+ *        it is a function, it is called with the icon url and returns
+ *        true if the url is correct.
+ * @param {array} permissions
+ *        The expected entries in the permissions list.  Each element
+ *        in this array is itself a 2-element array with the string key
+ *        for the item (e.g., "webextPerms.description.foo") and an
+ *        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");
+  } 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) {
+    is(header.getAttribute("hidden"), "true", "Permissions header is hidden");
+  } else {
+    is(header.getAttribute("hidden"), "", "Permissions header is visible");
+  }
+
+  for (let i in permissions) {
+    let [key, param] = permissions[i];
+    checkPermissionString(ul.children[i].textContent, key, param,
+                          `Permission number ${i + 1} is correct`);
+  }
+}
+
+/**
  * Test that install-time permission prompts work for a given
  * installation method.
  *
  * @param {Function} installFn
  *        Callable that takes the name of an xpi file to install and
  *        starts to install it.  Should return a Promise that resolves
  *        when the install is finished or rejects if the install is canceled.
  *
@@ -208,54 +252,29 @@ async function testInstallMethod(install
         },
       };
       AddonManager.addInstallListener(listener);
     });
 
     let installMethodPromise = installFn(filename);
 
     let panel = await promisePopupNotificationShown("addon-webext-permissions");
-    let icon = panel.getAttribute("icon");
-
-    let ul = document.getElementById("addon-webext-perm-list");
-    let header = document.getElementById("addon-webext-perm-intro");
-
     if (filename == PERMS_XPI) {
       // The icon should come from the extension, don't bother with the precise
       // path, just make sure we've got a jar url pointing to the right path
       // inside the jar.
-      ok(icon.startsWith("jar:file://"), "Icon is a jar url");
-      ok(icon.endsWith("/icon.png"), "Icon is icon.png inside a jar");
-
-      is(header.getAttribute("hidden"), "", "Permission list header is visible");
-      is(ul.childElementCount, 5, "Permissions list has 5 entries");
-
-      checkPermissionString(ul.children[0].textContent,
-                            "webextPerms.hostDescription.wildcard",
-                            "wildcard.domain",
-                            "First permission is domain permission");
-      checkPermissionString(ul.children[1].textContent,
-                            "webextPerms.hostDescription.oneSite",
-                            "singlehost.domain",
-                            "Second permission is single host permission");
-      checkPermissionString(ul.children[2].textContent,
-                            "webextPerms.description.nativeMessaging", null,
-                            "Third permission is nativeMessaging");
-      checkPermissionString(ul.children[3].textContent,
-                            "webextPerms.description.tabs", null,
-                            "Fourth permission is tabs");
-      checkPermissionString(ul.children[4].textContent,
-                            "webextPerms.description.history", null,
-                            "Fifth permission is history");
+      checkNotification(panel, /^jar:file:\/\/.*\/icon\.png$/, [
+        ["webextPerms.hostDescription.wildcard", "wildcard.domain"],
+        ["webextPerms.hostDescription.oneSite", "singlehost.domain"],
+        ["webextPerms.description.nativeMessaging"],
+        ["webextPerms.description.tabs"],
+        ["webextPerms.description.history"],
+      ]);
     } else if (filename == NO_PERMS_XPI) {
-      // This extension has no icon, it should have the default
-      ok(isDefaultIcon(icon), "Icon is the default extension icon");
-
-      is(header.getAttribute("hidden"), "true", "Permission list header is hidden");
-      is(ul.childElementCount, 0, "Permissions list has 0 entries");
+      checkNotification(panel, isDefaultIcon, []);
     }
 
     if (cancel) {
       panel.secondaryButton.click();
       try {
         await installMethodPromise;
       } catch (err) {}
     } else {
--- a/browser/components/extensions/ext-commands.js
+++ b/browser/components/extensions/ext-commands.js
@@ -69,40 +69,41 @@ CommandList.prototype = {
    * @param {Object} manifest The manifest JSON object.
    * @returns {Map<string, object>}
    */
   loadCommandsFromManifest(manifest) {
     let commands = new Map();
     // For Windows, chrome.runtime expects 'win' while chrome.commands
     // expects 'windows'.  We can special case this for now.
     let os = PlatformInfo.os == "win" ? "windows" : PlatformInfo.os;
-    for (let name of Object.keys(manifest.commands)) {
-      let command = manifest.commands[name];
-      let shortcut = command.suggested_key[os] || command.suggested_key.default;
-      if (shortcut) {
-        commands.set(name, {
-          description: command.description,
-          shortcut: shortcut.replace(/\s+/g, ""),
-        });
-      }
+    for (let [name, command] of Object.entries(manifest.commands)) {
+      let suggested_key = command.suggested_key || {};
+      let shortcut = suggested_key[os] || suggested_key.default;
+      shortcut = shortcut ? shortcut.replace(/\s+/g, "") : null;
+      commands.set(name, {
+        description: command.description,
+        shortcut,
+      });
     }
     return commands;
   },
 
   /**
    * Registers the commands to a document.
    * @param {ChromeWindow} window The XUL window to insert the Keyset.
    */
   registerKeysToDocument(window) {
     let doc = window.document;
     let keyset = doc.createElementNS(XUL_NS, "keyset");
     keyset.id = `ext-keyset-id-${this.id}`;
     this.commands.forEach((command, name) => {
-      let keyElement = this.buildKey(doc, name, command.shortcut);
-      keyset.appendChild(keyElement);
+      if (command.shortcut) {
+        let keyElement = this.buildKey(doc, name, command.shortcut);
+        keyset.appendChild(keyElement);
+      }
     });
     doc.documentElement.appendChild(keyset);
     this.keysetsMap.set(window, keyset);
   },
 
   /**
    * Builds a XUL Key element and attaches an onCommand listener which
    * emits a command event with the provided name when fired.
--- a/browser/components/extensions/test/browser/browser_ext_browserAction_context.js
+++ b/browser/components/extensions/test/browser/browser_ext_browserAction_context.js
@@ -170,17 +170,17 @@ add_task(function* testTabSwitchContext(
       },
 
       "default.png": imageBuffer,
       "default-2.png": imageBuffer,
       "1.png": imageBuffer,
       "2.png": imageBuffer,
     },
 
-    getTests(tabs, expectDefaults) {
+    getTests: function(tabs, expectDefaults) {
       const DEFAULT_BADGE_COLOR = [0xd9, 0, 0, 255];
 
       let details = [
         {"icon": browser.runtime.getURL("default.png"),
          "popup": browser.runtime.getURL("default.html"),
          "title": "Default Title",
          "badge": "",
          "badgeBackgroundColor": DEFAULT_BADGE_COLOR},
@@ -326,17 +326,17 @@ add_task(function* testDefaultTitle() {
 
       "permissions": ["tabs"],
     },
 
     files: {
       "icon.png": imageBuffer,
     },
 
-    getTests(tabs, expectDefaults) {
+    getTests: function(tabs, expectDefaults) {
       const DEFAULT_BADGE_COLOR = [0xd9, 0, 0, 255];
 
       let details = [
         {"title": "Foo Extension",
          "popup": "",
          "badge": "",
          "badgeBackgroundColor": DEFAULT_BADGE_COLOR,
          "icon": browser.runtime.getURL("icon.png")},
--- a/browser/components/extensions/test/browser/browser_ext_commands_getAll.js
+++ b/browser/components/extensions/test/browser/browser_ext_commands_getAll.js
@@ -21,24 +21,29 @@ add_task(function* () {
         "with-platform-info": {
           "suggested_key": {
             "mac": "Ctrl+Shift+M",
             "linux": "Ctrl+Shift+L",
             "windows": "Ctrl+Shift+W",
             "android": "Ctrl+Shift+A",
           },
         },
+        "without-suggested-key": {
+          "description": "has no suggested_key",
+        },
+        "without-suggested-key-nor-description": {
+        },
       },
     },
 
     background: function() {
       browser.test.onMessage.addListener((message, additionalScope) => {
         browser.commands.getAll((commands) => {
           let errorMessage = "getAll should return an array of commands";
-          browser.test.assertEq(commands.length, 3, errorMessage);
+          browser.test.assertEq(commands.length, 5, errorMessage);
 
           let command = commands.find(c => c.name == "with-desciption");
 
           errorMessage = "The description should match what is provided in the manifest";
           browser.test.assertEq("should have a description", command.description, errorMessage);
 
           errorMessage = "The shortcut should match the default shortcut provided in the manifest";
           browser.test.assertEq("Ctrl+Shift+Y", command.shortcut, errorMessage);
@@ -59,16 +64,28 @@ add_task(function* () {
           };
 
           command = commands.find(c => c.name == "with-platform-info");
           let platformKey = platformKeys[additionalScope.platform];
           let shortcut = `Ctrl+Shift+${platformKey}`;
           errorMessage = `The shortcut should match the one provided in the manifest for OS='${additionalScope.platform}'`;
           browser.test.assertEq(shortcut, command.shortcut, errorMessage);
 
+          command = commands.find(c => c.name == "without-suggested-key");
+
+          browser.test.assertEq("has no suggested_key", command.description, "The description should match what is provided in the manifest");
+
+          browser.test.assertEq(null, command.shortcut, "The shortcut should be empty if not provided");
+
+          command = commands.find(c => c.name == "without-suggested-key-nor-description");
+
+          browser.test.assertEq(null, command.description, "The description should be empty when it is not provided");
+
+          browser.test.assertEq(null, command.shortcut, "The shortcut should be empty if not provided");
+
           browser.test.notifyPass("commands");
         });
       });
       browser.test.sendMessage("ready");
     },
   });
 
   yield extension.startup();
--- a/browser/components/extensions/test/browser/browser_ext_pageAction_context.js
+++ b/browser/components/extensions/test/browser/browser_ext_pageAction_context.js
@@ -48,17 +48,17 @@ add_task(function* testTabSwitchContext(
         },
       },
 
       "default.png": imageBuffer,
       "1.png": imageBuffer,
       "2.png": imageBuffer,
     },
 
-    getTests(tabs) {
+    getTests: function(tabs) {
       let details = [
         {"icon": browser.runtime.getURL("default.png"),
          "popup": browser.runtime.getURL("default.html"),
          "title": "Default T\u00edtulo \u263a"},
         {"icon": browser.runtime.getURL("1.png"),
          "popup": browser.runtime.getURL("default.html"),
          "title": "Default T\u00edtulo \u263a"},
         {"icon": browser.runtime.getURL("2.png"),
--- a/browser/components/extensions/test/browser/browser_ext_pageAction_title.js
+++ b/browser/components/extensions/test/browser/browser_ext_pageAction_title.js
@@ -48,17 +48,17 @@ add_task(function* testTabSwitchContext(
         },
       },
 
       "default.png": imageBuffer,
       "1.png": imageBuffer,
       "2.png": imageBuffer,
     },
 
-    getTests(tabs) {
+    getTests: function(tabs) {
       let details = [
         {"icon": browser.runtime.getURL("default.png"),
          "popup": browser.runtime.getURL("default.html"),
          "title": "Default T\u00edtulo \u263a"},
         {"icon": browser.runtime.getURL("1.png"),
          "popup": browser.runtime.getURL("default.html"),
          "title": "Default T\u00edtulo \u263a"},
         {"icon": browser.runtime.getURL("2.png"),
@@ -185,17 +185,17 @@ add_task(function* testDefaultTitle() {
 
       "permissions": ["tabs"],
     },
 
     files: {
       "icon.png": imageBuffer,
     },
 
-    getTests(tabs) {
+    getTests: function(tabs) {
       let details = [
         {"title": "Foo Extension",
          "popup": "",
          "icon": browser.runtime.getURL("icon.png")},
         {"title": "Foo Title",
          "popup": "",
          "icon": browser.runtime.getURL("icon.png")},
       ];
--- a/browser/components/extensions/test/browser/browser_ext_sidebarAction_context.js
+++ b/browser/components/extensions/test/browser/browser_ext_sidebarAction_context.js
@@ -166,17 +166,17 @@ add_task(function* testTabSwitchContext(
       },
 
       "default.png": imageBuffer,
       "default-2.png": imageBuffer,
       "1.png": imageBuffer,
       "2.png": imageBuffer,
     },
 
-    getTests(tabs, expectDefaults) {
+    getTests: function(tabs, expectDefaults) {
       let details = [
         {"icon": browser.runtime.getURL("default.png"),
          "panel": browser.runtime.getURL("default.html"),
          "title": "Default Title",
         },
         {"icon": browser.runtime.getURL("1.png"),
          "panel": browser.runtime.getURL("default.html"),
          "title": "Default Title",
@@ -319,17 +319,17 @@ add_task(function* testDefaultTitle() {
       "permissions": ["tabs"],
     },
 
     files: {
       "sidebar.html": sidebar,
       "icon.png": imageBuffer,
     },
 
-    getTests(tabs, expectDefaults) {
+    getTests: function(tabs, expectDefaults) {
       let details = [
         {"title": "Foo Extension",
          "panel": browser.runtime.getURL("sidebar.html"),
          "icon": browser.runtime.getURL("icon.png")},
         {"title": "Foo Title",
          "panel": browser.runtime.getURL("sidebar.html"),
          "icon": browser.runtime.getURL("icon.png")},
         {"title": "Bar Title",
--- a/browser/components/extensions/test/browser/browser_ext_tabs_executeScript_bad.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_executeScript_bad.js
@@ -85,17 +85,17 @@ add_task(function* testBadPermissions() 
       "commands": {
         "test-tabs-executeScript": {
           "suggested_key": {
             "default": "Alt+Shift+K",
           },
         },
       },
     },
-    contentSetup() {
+    contentSetup: function() {
       browser.commands.onCommand.addListener(function(command) {
         if (command == "test-tabs-executeScript") {
           browser.test.sendMessage("tabs-command-key-pressed");
         }
       });
       return Promise.resolve();
     },
     setup: function* (extension) {
@@ -127,17 +127,17 @@ add_task(function* testBadPermissions() 
   });
 
   info("Test active tab, page action, no click");
   yield testHasNoPermission({
     manifest: {
       "permissions": ["http://example.com/", "activeTab"],
       "page_action": {},
     },
-    async contentSetup() {
+    contentSetup: async function() {
       let [tab] = await browser.tabs.query({active: true, currentWindow: true});
       await browser.pageAction.show(tab.id);
     },
   });
 
   yield BrowserTestUtils.removeTab(tab2);
   yield BrowserTestUtils.removeTab(tab1);
 });
--- a/browser/components/extensions/test/browser/browser_ext_tabs_executeScript_good.js
+++ b/browser/components/extensions/test/browser/browser_ext_tabs_executeScript_good.js
@@ -81,17 +81,17 @@ add_task(function* testGoodPermissions()
       "commands": {
         "test-tabs-executeScript": {
           "suggested_key": {
             "default": "Alt+Shift+K",
           },
         },
       },
     },
-    contentSetup() {
+    contentSetup: function() {
       browser.commands.onCommand.addListener(function(command) {
         if (command == "test-tabs-executeScript") {
           browser.test.sendMessage("tabs-command-key-pressed");
         }
       });
       return Promise.resolve();
     },
     setup: function* (extension) {
@@ -101,17 +101,17 @@ add_task(function* testGoodPermissions()
   });
 
   info("Test activeTab permission with a browser action click");
   yield testHasPermission({
     manifest: {
       "permissions": ["activeTab"],
       "browser_action": {},
     },
-    contentSetup() {
+    contentSetup: function() {
       browser.browserAction.onClicked.addListener(() => {
         browser.test.log("Clicked.");
       });
       return Promise.resolve();
     },
     setup: clickBrowserAction,
     tearDown: closeBrowserAction,
   });
@@ -157,17 +157,17 @@ add_task(function* testGoodPermissions()
     tearDown: closePageAction,
   });
 
   info("Test activeTab permission with a context menu click");
   yield testHasPermission({
     manifest: {
       "permissions": ["activeTab", "contextMenus"],
     },
-    contentSetup() {
+    contentSetup: function() {
       browser.contextMenus.create({title: "activeTab", contexts: ["all"]});
       return Promise.resolve();
     },
     setup: function* (extension) {
       let contextMenu = document.getElementById("contentAreaContextMenu");
       let awaitPopupShown = BrowserTestUtils.waitForEvent(contextMenu, "popupshown");
       let awaitPopupHidden = BrowserTestUtils.waitForEvent(contextMenu, "popuphidden");
 
--- a/browser/components/uitour/UITour.jsm
+++ b/browser/components/uitour/UITour.jsm
@@ -27,25 +27,22 @@ XPCOMUtils.defineLazyModuleGetter(this, 
 XPCOMUtils.defineLazyModuleGetter(this, "CustomizableUI",
   "resource:///modules/CustomizableUI.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "UITelemetry",
   "resource://gre/modules/UITelemetry.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "BrowserUITelemetry",
   "resource:///modules/BrowserUITelemetry.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "PrivateBrowsingUtils",
   "resource://gre/modules/PrivateBrowsingUtils.jsm");
-XPCOMUtils.defineLazyModuleGetter(this, "ReaderMode",
-  "resource://gre/modules/ReaderMode.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "ReaderParent",
   "resource:///modules/ReaderParent.jsm");
 
 // See LOG_LEVELS in Console.jsm. Common examples: "All", "Info", "Warn", & "Error".
 const PREF_LOG_LEVEL      = "browser.uitour.loglevel";
 const PREF_SEENPAGEIDS    = "browser.uitour.seenPageIDs";
-const PREF_READERVIEW_TRIGGER = "browser.uitour.readerViewTrigger";
 const PREF_SURVEY_DURATION = "browser.uitour.surveyDuration";
 
 const BACKGROUND_PAGE_ACTIONS_ALLOWED = new Set([
   "forceShowReaderIcon",
   "getConfiguration",
   "getTreatmentTag",
   "hideHighlight",
   "hideInfo",
@@ -292,33 +289,16 @@ this.UITour = {
       Services.prefs.clearUserPref(PREF_SEENPAGEIDS);
       return;
     }
 
     Services.prefs.setCharPref(PREF_SEENPAGEIDS,
                                JSON.stringify([...this.seenPageIDs]));
   },
 
-  get _readerViewTriggerRegEx() {
-    delete this._readerViewTriggerRegEx;
-    let readerViewUITourTrigger = Services.prefs.getCharPref(PREF_READERVIEW_TRIGGER);
-    return this._readerViewTriggerRegEx = new RegExp(readerViewUITourTrigger, "i");
-  },
-
-  onLocationChange(aLocation) {
-    // The ReaderView tour page is expected to run in Reader View,
-    // which disables JavaScript on the page. To get around that, we
-    // automatically start a pre-defined tour on page load (for hysterical
-    // raisins the ReaderView tour is known as "readinglist")
-    let originalUrl = ReaderMode.getOriginalUrl(aLocation);
-    if (this._readerViewTriggerRegEx.test(originalUrl)) {
-      this.startSubTour("readinglist");
-    }
-  },
-
   onPageEvent(aMessage, aEvent) {
     let browser = aMessage.target;
     let window = browser.ownerGlobal;
 
     // Does the window have tabs? We need to make sure since windowless browsers do
     // not have tabs.
     if (!window.gBrowser) {
       // When using windowless browsers we don't have a valid |window|. If that's the case,
@@ -1933,29 +1913,16 @@ this.UITour = {
     }.bind(this)).catch(err => {
       log.error(err);
       this.sendPageCallback(aMessageManager, aCallbackID, {
         targets: [],
       });
     });
   },
 
-  startSubTour(aFeature) {
-    if (aFeature != "string") {
-      log.error("startSubTour: No feature option specified");
-      return;
-    }
-
-    if (aFeature == "readinglist") {
-      ReaderParent.showReaderModeInfoPanel(browser);
-    } else {
-      log.error("startSubTour: Unknown feature option specified");
-    }
-  },
-
   addNavBarWidget(aTarget, aMessageManager, aCallbackID) {
     if (aTarget.node) {
       log.error("addNavBarWidget: can't add a widget already present:", aTarget);
       return;
     }
     if (!aTarget.allowAdd) {
       log.error("addNavBarWidget: not allowed to add this widget:", aTarget);
       return;
--- a/browser/config/mozconfigs/linux32/valgrind
+++ b/browser/config/mozconfigs/linux32/valgrind
@@ -1,9 +1,10 @@
 . $topsrcdir/browser/config/mozconfigs/linux32/nightly
 
 ac_add_options --enable-valgrind
 ac_add_options --disable-jemalloc
 ac_add_options --disable-install-strip
+ac_add_options --disable-gtest-in-build
 
 # Include the override mozconfig again (even though the above includes it)
 # since it's supposed to override everything.
 . "$topsrcdir/build/mozconfig.common.override"
--- a/browser/config/mozconfigs/linux64/valgrind
+++ b/browser/config/mozconfigs/linux64/valgrind
@@ -1,9 +1,10 @@
 . $topsrcdir/browser/config/mozconfigs/linux64/nightly
 
 ac_add_options --enable-valgrind
 ac_add_options --disable-jemalloc
 ac_add_options --disable-install-strip
+ac_add_options --disable-gtest-in-build
 
 # Include the override mozconfig again (even though the above includes it)
 # since it's supposed to override everything.
 . "$topsrcdir/build/mozconfig.common.override"
--- a/browser/extensions/e10srollout/bootstrap.js
+++ b/browser/extensions/e10srollout/bootstrap.js
@@ -68,29 +68,28 @@ function defineCohort() {
   if (updateChannel in ADDON_ROLLOUT_POLICY) {
     addonPolicy = ADDON_ROLLOUT_POLICY[updateChannel];
     Preferences.set(PREF_E10S_ADDON_POLICY, addonPolicy);
     // This is also the proper place to set the blocklist pref
     // in case it is necessary.
 
     Preferences.set(PREF_E10S_ADDON_BLOCKLIST,
                     // bug 1185672 - Tab Mix Plus
-                    "{dc572301-7619-498c-a57d-39143191b318};" +
-                    // bug 1332692 - LastPass
-                    "support@lastpass.com;");
+                    "{dc572301-7619-498c-a57d-39143191b318};");
   } else {
     Preferences.reset(PREF_E10S_ADDON_POLICY);
   }
 
   let userOptedOut = optedOut();
   let userOptedIn = optedIn();
   let disqualified = (Services.appinfo.multiprocessBlockPolicy != 0);
   let testGroup = (getUserSample() < TEST_THRESHOLD[updateChannel]);
   let hasNonExemptAddon = Preferences.get(PREF_E10S_HAS_NONEXEMPT_ADDON, false);
   let temporaryDisqualification = getTemporaryDisqualification();
+  let temporaryQualification = getTemporaryQualification();
 
   let cohortPrefix = "";
   if (disqualified) {
     cohortPrefix = "disqualified-";
   } else if (hasNonExemptAddon) {
     cohortPrefix = `addons-set${addonPolicy}-`;
   }
 
@@ -104,16 +103,19 @@ function defineCohort() {
     // still be denied by the backend, which is useful so that the E10S_STATUS
     // telemetry probe can be correctly set.
 
     // For these volatile disqualification reasons, however, we must not try
     // to activate e10s because the backend doesn't know about it. E10S_STATUS
     // here will be accumulated as "2 - Disabled", which is fine too.
     setCohort(`temp-disqualified-${temporaryDisqualification}`);
     Preferences.reset(PREF_TOGGLE_E10S);
+  } else if (!disqualified && temporaryQualification != "") {
+    setCohort(`temp-qualified-${temporaryQualification}`);
+    Preferences.set(PREF_TOGGLE_E10S, true);
   } else if (testGroup) {
     setCohort(`${cohortPrefix}test`);
     Preferences.set(PREF_TOGGLE_E10S, true);
   } else {
     setCohort(`${cohortPrefix}control`);
     Preferences.reset(PREF_TOGGLE_E10S);
   }
 }
@@ -171,8 +173,28 @@ function optedOut() {
  * means that this particular user should be temporarily
  * disqualified due to some particular reason.
  * If a user shouldn't be disqualified, then an empty
  * string must be returned.
  */
 function getTemporaryDisqualification() {
   return "";
 }
+
+/* If this function returns a non-empty string, it
+ * means that this particular user should be temporarily
+ * qualified due to some particular reason.
+ * If a user shouldn't be qualified, then an empty
+ * string must be returned.
+ */
+function getTemporaryQualification() {
+  // Whenever the DevTools toolbox is opened for the first time in a release, it
+  // records this fact in the following pref as part of the DevTools telemetry
+  // system.  If this pref is set, then it means the user has opened DevTools at
+  // some point in time.
+  const PREF_OPENED_DEVTOOLS = "devtools.telemetry.tools.opened.version";
+  let hasOpenedDevTools = Preferences.isSet(PREF_OPENED_DEVTOOLS);
+  if (hasOpenedDevTools) {
+    return "devtools";
+  }
+
+  return "";
+}
--- a/browser/extensions/e10srollout/install.rdf.in
+++ b/browser/extensions/e10srollout/install.rdf.in
@@ -5,17 +5,17 @@
 
 #filter substitution
 
 <RDF xmlns="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
      xmlns:em="http://www.mozilla.org/2004/em-rdf#">
 
   <Description about="urn:mozilla:install-manifest">
     <em:id>e10srollout@mozilla.org</em:id>
-    <em:version>1.9</em:version>
+    <em:version>1.11</em:version>
     <em:type>2</em:type>
     <em:bootstrap>true</em:bootstrap>
     <em:multiprocessCompatible>true</em:multiprocessCompatible>
 
     <!-- Target Application this theme can install into,
         with minimum and maximum supported versions. -->
     <em:targetApplication>
       <Description>
--- a/browser/extensions/moz.build
+++ b/browser/extensions/moz.build
@@ -5,25 +5,25 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 DIRS += [
     'aushelper',
     'e10srollout',
     'pdfjs',
     'pocket',
     'webcompat',
-    'shield-recipe-client',
 ]
 
 # Only include the following system add-ons if building Aurora or Nightly
 if not CONFIG['RELEASE_OR_BETA']:
     DIRS += [
         'flyweb',
         'formautofill',
         'presentation',
+        'shield-recipe-client',
     ]
 
 # Only include mortar system add-ons if we locally enable it
 if CONFIG['MOZ_MORTAR']:
     DIRS += [
         'mortar',
     ]
 
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -545,16 +545,21 @@
 
 @RESPATH@/components/PresentationDeviceInfoManager.manifest
 @RESPATH@/components/PresentationDeviceInfoManager.js
 @RESPATH@/components/BuiltinProviders.manifest
 @RESPATH@/components/PresentationControlService.js
 @RESPATH@/components/PresentationDataChannelSessionTransport.js
 @RESPATH@/components/PresentationDataChannelSessionTransport.manifest
 
+#ifdef ENABLE_INTL_API
+@RESPATH@/components/mozIntl.manifest
+@RESPATH@/components/mozIntl.js
+#endif
+
 #if defined(ENABLE_TESTS) && defined(MOZ_DEBUG)
 @RESPATH@/components/TestInterfaceJS.js
 @RESPATH@/components/TestInterfaceJS.manifest
 @RESPATH@/components/TestInterfaceJSMaplike.js
 #endif
 
 ; [Extensions]
 @RESPATH@/components/extensions-toolkit.manifest
--- a/browser/installer/windows/nsis/installer.nsi
+++ b/browser/installer/windows/nsis/installer.nsi
@@ -551,16 +551,17 @@ SectionEnd
 
 ; Cleanup operations to perform at the end of the installation.
 Section "-InstallEndCleanup"
   SetDetailsPrint both
   DetailPrint "$(STATUS_CLEANUP)"
   SetDetailsPrint none
 
   ${Unless} ${Silent}
+    ClearErrors
     ${MUI_INSTALLOPTIONS_READ} $0 "summary.ini" "Field 4" "State"
     ${If} "$0" == "1"
       ; NB: this code is duplicated in stub.nsi. Please keep in sync.
       ; For data migration in the app, we want to know what the default browser
       ; value was before we changed it. To do so, we read it here and store it
       ; in our own registry key.
       StrCpy $0 ""
       AppAssocReg::QueryCurrentDefault "http" "protocol" "effective"
@@ -587,17 +588,17 @@ Section "-InstallEndCleanup"
       ${GetParameters} $0
       ${GetOptions} "$0" "/UAC:" $0
       ${If} ${Errors}
         Call SetAsDefaultAppUserHKCU
       ${Else}
         GetFunctionAddress $0 SetAsDefaultAppUserHKCU
         UAC::ExecCodeSegment $0
       ${EndIf}
-    ${Else}
+    ${ElseIfNot} ${Errors}
       ${LogHeader} "Writing default-browser opt-out"
       ClearErrors
       WriteRegStr HKCU "Software\Mozilla\Firefox" "DefaultBrowserOptOut" "True"
       ${If} ${Errors}
         ${LogMsg} "Error writing default-browser opt-out"
       ${EndIf}
     ${EndIf}
   ${EndUnless}
--- a/browser/locales/en-US/chrome/browser/browser.properties
+++ b/browser/locales/en-US/chrome/browser/browser.properties
@@ -57,16 +57,17 @@ webextPerms.sideloadMenuItem=%1$S added 
 
 # LOCALIZATION NOTE (webextPerms.sideloadHeader)
 # This string is used as a header in the webextension permissions dialog
 # when the extension is side-loaded.
 # %S is replaced with the localized name of the extension being installed.
 # Note, this string will be used as raw markup. Avoid characters like <, >, &
 webextPerms.sideloadHeader=%S added
 webextPerms.sideloadText2=Another program on your computer installed an add-on that may affect your browser. Please review this add-on’s permissions requests and choose to Enable or Cancel (to leave it disabled).
+webextPerms.sideloadTextNoPerms=Another program on your computer installed an add-on that may affect your browser. Please choose to Enable or Cancel (to leave it disabled).
 
 webextPerms.sideloadEnable.label=Enable
 webextPerms.sideloadEnable.accessKey=E
 webextPerms.sideloadCancel.label=Cancel
 webextPerms.sideloadCancel.accessKey=C
 
 # LOCALIZATION NOTE (webextPerms.updateMenuItem)
 # %S will be replaced with the localized name of the extension which
--- a/browser/modules/ExtensionsUI.jsm
+++ b/browser/modules/ExtensionsUI.jsm
@@ -187,42 +187,16 @@ this.ExtensionsUI = {
   },
 
   // Create a set of formatted strings for a permission prompt
   _buildStrings(info) {
     let result = {};
 
     let bundle = Services.strings.createBundle(BROWSER_PROPERTIES);
 
-    let name = this._sanitizeName(info.addon.name);
-    let addonName = `<span class="addon-webext-name">${name}</span>`;
-
-    result.header = bundle.formatStringFromName("webextPerms.header", [addonName], 1);
-    result.text = "";
-    result.listIntro = bundle.GetStringFromName("webextPerms.listIntro");
-
-    result.acceptText = bundle.GetStringFromName("webextPerms.add.label");
-    result.acceptKey = bundle.GetStringFromName("webextPerms.add.accessKey");
-    result.cancelText = bundle.GetStringFromName("webextPerms.cancel.label");
-    result.cancelKey = bundle.GetStringFromName("webextPerms.cancel.accessKey");
-
-    if (info.type == "sideload") {
-      result.header = bundle.formatStringFromName("webextPerms.sideloadHeader", [addonName], 1);
-      result.text = bundle.GetStringFromName("webextPerms.sideloadText2");
-      result.acceptText = bundle.GetStringFromName("webextPerms.sideloadEnable.label");
-      result.acceptKey = bundle.GetStringFromName("webextPerms.sideloadEnable.accessKey");
-      result.cancelText = bundle.GetStringFromName("webextPerms.sideloadCancel.label");
-      result.cancelKey = bundle.GetStringFromName("webextPerms.sideloadCancel.accessKey");
-    } else if (info.type == "update") {
-      result.header = "";
-      result.text = bundle.formatStringFromName("webextPerms.updateText", [addonName], 1);
-      result.acceptText = bundle.GetStringFromName("webextPerms.updateAccept.label");
-      result.acceptKey = bundle.GetStringFromName("webextPerms.updateAccept.accessKey");
-    }
-
     let perms = info.permissions || {hosts: [], permissions: []};
 
     // First classify our host permissions
     let allUrls = false, wildcards = [], sites = [];
     for (let permission of perms.hosts) {
       if (permission == "<all_urls>") {
         allUrls = true;
         break;
@@ -290,16 +264,45 @@ this.ExtensionsUI = {
       try {
         result.msgs.push(bundle.GetStringFromName(permissionKey(permission)));
       } catch (err) {
         // We deliberately do not include all permissions in the prompt.
         // So if we don't find one then just skip it.
       }
     }
 
+    // Now figure out all the rest of the notification text.
+    let name = this._sanitizeName(info.addon.name);
+    let addonName = `<span class="addon-webext-name">${name}</span>`;
+
+    result.header = bundle.formatStringFromName("webextPerms.header", [addonName], 1);
+    result.text = "";
+    result.listIntro = bundle.GetStringFromName("webextPerms.listIntro");
+
+    result.acceptText = bundle.GetStringFromName("webextPerms.add.label");
+    result.acceptKey = bundle.GetStringFromName("webextPerms.add.accessKey");
+    result.cancelText = bundle.GetStringFromName("webextPerms.cancel.label");
+    result.cancelKey = bundle.GetStringFromName("webextPerms.cancel.accessKey");
+
+    if (info.type == "sideload") {
+      result.header = bundle.formatStringFromName("webextPerms.sideloadHeader", [addonName], 1);
+      let key = result.msgs.length == 0 ?
+                "webextPerms.sideloadTextNoPerms" : "webextPerms.sideloadText2";
+      result.text = bundle.GetStringFromName(key);
+      result.acceptText = bundle.GetStringFromName("webextPerms.sideloadEnable.label");
+      result.acceptKey = bundle.GetStringFromName("webextPerms.sideloadEnable.accessKey");
+      result.cancelText = bundle.GetStringFromName("webextPerms.sideloadCancel.label");
+      result.cancelKey = bundle.GetStringFromName("webextPerms.sideloadCancel.accessKey");
+    } else if (info.type == "update") {
+      result.header = "";
+      result.text = bundle.formatStringFromName("webextPerms.updateText", [addonName], 1);
+      result.acceptText = bundle.GetStringFromName("webextPerms.updateAccept.label");
+      result.acceptKey = bundle.GetStringFromName("webextPerms.updateAccept.accessKey");
+    }
+
     return result;
   },
 
   showPermissionsPrompt(browser, strings, icon) {
     function eventCallback(topic) {
       if (topic == "showing") {
         let doc = this.browser.ownerDocument;
         doc.getElementById("addon-webext-perm-header").innerHTML = strings.header;
--- a/config/rules.mk
+++ b/config/rules.mk
@@ -596,22 +596,24 @@ ifeq ($(OS_ARCH)_$(GNU_CC), WINNT_)
 else
 ifdef GNU_CC
 	-$(RM) *.gcda
 endif
 endif
 endif
 
 ifneq (,$(MOZ_PROFILE_GENERATE)$(MOZ_PROFILE_USE))
+ifneq (,$(filter target,$(MAKECMDGOALS)))
 ifdef GNU_CC
 # Force rebuilding libraries and programs in both passes because each
 # pass uses different object files.
 $(PROGRAM) $(SHARED_LIBRARY) $(LIBRARY): FORCE
 endif
 endif
+endif
 
 endif # NO_PROFILE_GUIDED_OPTIMIZE
 
 ##############################################
 
 checkout:
 	$(MAKE) -C $(topsrcdir) -f client.mk checkout
 
--- a/devtools/client/framework/ToolboxProcess.jsm
+++ b/devtools/client/framework/ToolboxProcess.jsm
@@ -156,17 +156,20 @@ BrowserToolboxProcess.prototype = {
   },
 
   /**
    * Initializes a profile for the remote debugger process.
    */
   _initProfile: function () {
     dumpn("Initializing the chrome toolbox user profile.");
 
-    let debuggingProfileDir = Services.dirsvc.get("ProfLD", Ci.nsIFile);
+    // We used to use `ProfLD` instead of `ProfD`, so migrate old profiles if they exist.
+    this._migrateProfileDir();
+
+    let debuggingProfileDir = Services.dirsvc.get("ProfD", Ci.nsIFile);
     debuggingProfileDir.append(CHROME_DEBUGGER_PROFILE_NAME);
     try {
       debuggingProfileDir.create(Ci.nsIFile.DIRECTORY_TYPE, 0o755);
     } catch (ex) {
       // Don't re-copy over the prefs again if this profile already exists
       if (ex.result === Cr.NS_ERROR_FILE_ALREADY_EXISTS) {
         this._dbgProfilePath = debuggingProfileDir.path;
       } else {
@@ -190,16 +193,45 @@ BrowserToolboxProcess.prototype = {
     // always works:
     Services.prefs.savePrefFile(prefsFile);
 
     dumpn("Finished creating the chrome toolbox user profile at: " +
           this._dbgProfilePath);
   },
 
   /**
+   * Originally, the profile was placed in `ProfLD` instead of `ProfD`.  On some systems,
+   * such as macOS, `ProfLD` is in the user's Caches directory, which is not an
+   * appropriate place to store supposedly persistent profile data.
+   */
+  _migrateProfileDir() {
+    let oldDebuggingProfileDir = Services.dirsvc.get("ProfLD", Ci.nsIFile);
+    oldDebuggingProfileDir.append(CHROME_DEBUGGER_PROFILE_NAME);
+    if (!oldDebuggingProfileDir.exists()) {
+      return;
+    }
+    dumpn(`Old debugging profile exists: ${oldDebuggingProfileDir.path}`);
+    try {
+      // Remove the directory from the target location, if it exists
+      let newDebuggingProfileDir = Services.dirsvc.get("ProfD", Ci.nsIFile);
+      newDebuggingProfileDir.append(CHROME_DEBUGGER_PROFILE_NAME);
+      if (newDebuggingProfileDir.exists()) {
+        dumpn(`Removing folder at destination: ${newDebuggingProfileDir.path}`);
+        newDebuggingProfileDir.remove(true);
+      }
+      // Move profile from old to new location
+      let newDebuggingProfileParent = Services.dirsvc.get("ProfD", Ci.nsIFile);
+      oldDebuggingProfileDir.moveTo(newDebuggingProfileParent, null);
+      dumpn("Debugging profile migrated successfully");
+    } catch (e) {
+      dumpn(`Debugging profile migration failed: ${e}`);
+    }
+  },
+
+  /**
    * Creates and initializes the profile & process for the remote debugger.
    */
   _create: function () {
     dumpn("Initializing chrome debugging process.");
     let process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess);
     this._dbgProcess = process;
     process.init(Services.dirsvc.get("XREExeF", Ci.nsIFile));
 
deleted file mode 100644
--- a/devtools/client/framework/source-map-util.js
+++ /dev/null
@@ -1,20 +0,0 @@
-function originalToGeneratedId(originalId) {
-  const match = originalId.match(/(.*)\/originalSource/);
-  return match ? match[1] : "";
-}
-
-function generatedToOriginalId(generatedId, url) {
-  return generatedId + "/originalSource-" + url.replace(/ \//, '-');
-}
-
-function isOriginalId(id) {
-  return !!id.match(/\/originalSource/);
-}
-
-function isGeneratedId(id) {
-  return !isOriginalId(id);
-}
-
-module.exports = {
-  originalToGeneratedId, generatedToOriginalId, isOriginalId, isGeneratedId
-};
deleted file mode 100644
--- a/devtools/client/framework/source-map-worker.js
+++ /dev/null
@@ -1,220 +0,0 @@
-const { fetch, assert } = require("devtools/shared/DevToolsUtils");
-const { joinURI } = require("devtools/shared/path");
-const path = require("sdk/fs/path");
-const { SourceMapConsumer, SourceMapGenerator } = require("source-map");
-const { isJavaScript } = require("./source");
-const {
-  originalToGeneratedId,
-  generatedToOriginalId,
-  isGeneratedId,
-  isOriginalId
-} = require("./source-map-util");
-
-let sourceMapRequests = new Map();
-let sourceMapsEnabled = false;
-
-function clearSourceMaps() {
-  sourceMapRequests.clear();
-}
-
-function enableSourceMaps() {
-  sourceMapsEnabled = true;
-}
-
-function _resolveSourceMapURL(source) {
-  const { url = "", sourceMapURL = "" } = source;
-  if (path.isURL(sourceMapURL) || url == "") {
-    // If it's already a full URL or the source doesn't have a URL,
-    // don't resolve anything.
-    return sourceMapURL;
-  } else if (path.isAbsolute(sourceMapURL)) {
-    // If it's an absolute path, it should be resolved relative to the
-    // host of the source.
-    const { protocol = "", host = "" } = parse(url);
-    return `${protocol}//${host}${sourceMapURL}`;
-  }
-  // Otherwise, it's a relative path and should be resolved relative
-  // to the source.
-  return dirname(url) + "/" + sourceMapURL;
-}
-
-/**
- * Sets the source map's sourceRoot to be relative to the source map url.
- * @memberof utils/source-map-worker
- * @static
- */
-function _setSourceMapRoot(sourceMap, absSourceMapURL, source) {
-  // No need to do this fiddling if we won't be fetching any sources over the
-  // wire.
-  if (sourceMap.hasContentsOfAllSources()) {
-    return;
-  }
-
-  const base = dirname(
-    (absSourceMapURL.indexOf("data:") === 0 && source.url) ?
-      source.url :
-      absSourceMapURL
-  );
-
-  if (sourceMap.sourceRoot) {
-    sourceMap.sourceRoot = joinURI(base, sourceMap.sourceRoot);
-  } else {
-    sourceMap.sourceRoot = base;
-  }
-
-  return sourceMap;
-}
-
-function _getSourceMap(generatedSourceId)
-    : ?Promise<SourceMapConsumer> {
-  return sourceMapRequests.get(generatedSourceId);
-}
-
-async function _resolveAndFetch(generatedSource) : SourceMapConsumer {
-  // Fetch the sourcemap over the network and create it.
-  const sourceMapURL = _resolveSourceMapURL(generatedSource);
-  const fetched = await fetch(
-    sourceMapURL, { loadFromCache: false }
-  );
-
-  // Create the source map and fix it up.
-  const map = new SourceMapConsumer(fetched.content);
-  _setSourceMapRoot(map, sourceMapURL, generatedSource);
-  return map;
-}
-
-function _fetchSourceMap(generatedSource) {
-  const existingRequest = sourceMapRequests.get(generatedSource.id);
-  if (existingRequest) {
-    // If it has already been requested, return the request. Make sure
-    // to do this even if sourcemapping is turned off, because
-    // pretty-printing uses sourcemaps.
-    //
-    // An important behavior here is that if it's in the middle of
-    // requesting it, all subsequent calls will block on the initial
-    // request.
-    return existingRequest;
-  } else if (!generatedSource.sourceMapURL || !sourceMapsEnabled) {
-    return Promise.resolve(null);
-  }
-
-  // Fire off the request, set it in the cache, and return it.
-  // Suppress any errors and just return null (ignores bogus
-  // sourcemaps).
-  const req = _resolveAndFetch(generatedSource).catch(() => null);
-  sourceMapRequests.set(generatedSource.id, req);
-  return req;
-}
-
-async function getOriginalURLs(generatedSource) {
-  const map = await _fetchSourceMap(generatedSource);
-  return map && map.sources;
-}
-
-async function getGeneratedLocation(location: Location, originalSource: Source)
-    : Promise<Location> {
-  if (!isOriginalId(location.sourceId)) {
-    return location;
-  }
-
-  const generatedSourceId = originalToGeneratedId(location.sourceId);
-  const map = await _getSourceMap(generatedSourceId);
-  if (!map) {
-    return location;
-  }
-
-  const { line, column } = map.generatedPositionFor({
-    source: originalSource.url,
-    line: location.line,
-    column: location.column == null ? 0 : location.column
-  });
-
-  return {
-    sourceId: generatedSourceId,
-    line: line,
-    // Treat 0 as no column so that line breakpoints work correctly.
-    column: column === 0 ? undefined : column
-  };
-}
-
-async function getOriginalLocation(location) {
-  if (!isGeneratedId(location.sourceId)) {
-    return location;
-  }
-
-  const map = await _getSourceMap(location.sourceId);
-  if (!map) {
-    return location;
-  }
-
-  const { source: url, line, column } = map.originalPositionFor({
-    line: location.line,
-    column: location.column == null ? Infinity : location.column
-  });
-
-  if (url == null) {
-    // No url means the location didn't map.
-    return location;
-  }
-
-  return {
-    sourceId: generatedToOriginalId(location.sourceId, url),
-    line,
-    column
-  };
-}
-
-async function getOriginalSourceText(originalSource) {
-  assert(isOriginalId(originalSource.id),
-         "Source is not an original source");
-
-  const generatedSourceId = originalToGeneratedId(originalSource.id);
-  const map = await _getSourceMap(generatedSourceId);
-  if (!map) {
-    return null;
-  }
-
-  let text = map.sourceContentFor(originalSource.url);
-  if (!text) {
-    text = (await fetch(
-      originalSource.url, { loadFromCache: false }
-    )).content;
-  }
-
-  return {
-    text,
-    contentType: isJavaScript(originalSource.url || "") ?
-      "text/javascript" :
-      "text/plain"
-  };
-}
-
-function applySourceMap(generatedId, url, code, mappings) {
-  const generator = new SourceMapGenerator({ file: url });
-  mappings.forEach(mapping => generator.addMapping(mapping));
-  generator.setSourceContent(url, code);
-
-  const map = SourceMapConsumer(generator.toJSON());
-  sourceMapRequests.set(generatedId, Promise.resolve(map));
-}
-
-const publicInterface = {
-  getOriginalURLs,
-  getGeneratedLocation,
-  getOriginalLocation,
-  getOriginalSourceText,
-  enableSourceMaps,
-  applySourceMap,
-  clearSourceMaps
-};
-
-self.onmessage = function(msg) {
-  const { id, method, args } = msg.data;
-  const response = publicInterface[method].apply(undefined, args);
-  if (response instanceof Promise) {
-    response.then(val => self.postMessage({ id, response: val }),
-                  err => self.postMessage({ id, error: err }));
-  } else {
-    self.postMessage({ id, response });
-  }
-};
deleted file mode 100644
--- a/devtools/client/framework/source-map.js
+++ /dev/null
@@ -1,84 +0,0 @@
-// @flow
-
-const {
-  originalToGeneratedId,
-  generatedToOriginalId,
-  isGeneratedId,
-  isOriginalId
-} = require("./source-map-util");
-
-function workerTask(worker, method) {
-  return function(...args: any) {
-    return new Promise((resolve, reject) => {
-      const id = msgId++;
-      worker.postMessage({ id, method, args });
-
-      const listener = ({ data: result }) => {
-        if (result.id !== id) {
-          return;
-        }
-
-        worker.removeEventListener("message", listener);
-        if (result.error) {
-          reject(result.error);
-        } else {
-          resolve(result.response);
-        }
-      };
-
-      worker.addEventListener("message", listener);
-    });
-  };
-}
-
-let sourceMapWorker;
-function restartWorker() {
-  if (sourceMapWorker) {
-    sourceMapWorker.terminate();
-  }
-  sourceMapWorker = new Worker(
-    "resource://devtools/client/framework/source-map-worker.js"
-  );
-
-  if (Services.prefs.getBoolPref("devtools.debugger.client-source-maps-enabled")) {
-    sourceMapWorker.postMessage({ id: 0, method: "enableSourceMaps" });
-  }
-}
-restartWorker();
-
-function destroyWorker() {
-  if (sourceMapWorker) {
-    sourceMapWorker.terminate();
-    sourceMapWorker = null;
-  }
-}
-
-function shouldSourceMap() {
-  return Services.prefs.getBoolPref("devtools.debugger.client-source-maps-enabled");
-}
-
-const getOriginalURLs = workerTask(sourceMapWorker, "getOriginalURLs");
-const getGeneratedLocation = workerTask(sourceMapWorker,
-                                        "getGeneratedLocation");
-const getOriginalLocation = workerTask(sourceMapWorker,
-                                       "getOriginalLocation");
-const getOriginalSourceText = workerTask(sourceMapWorker,
-                                         "getOriginalSourceText");
-const applySourceMap = workerTask(sourceMapWorker, "applySourceMap");
-const clearSourceMaps = workerTask(sourceMapWorker, "clearSourceMaps");
-
-module.exports = {
-  originalToGeneratedId,
-  generatedToOriginalId,
-  isGeneratedId,
-  isOriginalId,
-
-  getOriginalURLs,
-  getGeneratedLocation,
-  getOriginalLocation,
-  getOriginalSourceText,
-  applySourceMap,
-  clearSourceMaps,
-  destroyWorker,
-  shouldSourceMap
-};
--- a/devtools/client/responsive.html/browser/swap.js
+++ b/devtools/client/responsive.html/browser/swap.js
@@ -50,16 +50,20 @@ function swapToInnerBrowser({ tab, conta
     from.dispatchEvent(event);
   };
 
   return {
 
     start: Task.async(function* () {
       tab.isResponsiveDesignMode = true;
 
+      // Hide the browser content temporarily while things move around to avoid displaying
+      // strange intermediate states.
+      tab.linkedBrowser.style.visibility = "hidden";
+
       // Freeze navigation temporarily to avoid "blinking" in the location bar.
       freezeNavigationState(tab);
 
       // 1. Create a temporary, hidden tab to load the tool UI.
       let containerTab = gBrowser.addTab("about:blank", {
         skipAnimation: true,
         forceNotRemote: true,
       });
@@ -130,19 +134,26 @@ function swapToInnerBrowser({ tab, conta
           findBar.onFindCommand();
         }
       }
 
       // Force the browser UI to match the new state of the tab and browser.
       thawNavigationState(tab);
       gBrowser.setTabTitle(tab);
       gBrowser.updateCurrentBrowser(true);
+
+      // Show the browser content again now that the move is done.
+      tab.linkedBrowser.style.visibility = "";
     }),
 
     stop() {
+      // Hide the browser content temporarily while things move around to avoid displaying
+      // strange intermediate states.
+      tab.linkedBrowser.style.visibility = "hidden";
+
       // 1. Stop the tunnel between outer and inner browsers.
       tunnel.stop();
       tunnel = null;
 
       // 2. Create a temporary, hidden tab to hold the content.
       let contentTab = gBrowser.addTab("about:blank", {
         skipAnimation: true,
       });
@@ -192,16 +203,19 @@ function swapToInnerBrowser({ tab, conta
       gBrowser = null;
 
       // The focus manager seems to get a little dizzy after all this swapping.  If a
       // content element had been focused inside the viewport before stopping, it will
       // have lost focus.  Activate the frame to restore expected focus.
       tab.linkedBrowser.frameLoader.activateRemoteFrame();
 
       delete tab.isResponsiveDesignMode;
+
+      // Show the browser content again now that the move is done.
+      tab.linkedBrowser.style.visibility = "";
     },
 
   };
 }
 
 /**
  * Browser navigation properties we'll freeze temporarily to avoid "blinking" in the
  * location bar, etc. caused by the containerURL peeking through before the swap is
--- a/devtools/server/actors/utils/TabSources.js
+++ b/devtools/server/actors/utils/TabSources.js
@@ -796,18 +796,17 @@ TabSources.prototype = {
   }
 };
 
 /*
  * Checks if a source should never be displayed to the user because
  * it's either internal or we don't support in the UI yet.
  */
 function isHiddenSource(source) {
-  // Ignore the internal Function.prototype script
-  return source.text === "() {\n}";
+  return source.introductionType === "Function.prototype";
 }
 
 /**
  * Returns true if its argument is not null.
  */
 function isNotNull(thing) {
   return thing !== null;
 }
--- a/docshell/shistory/nsSHEntry.cpp
+++ b/docshell/shistory/nsSHEntry.cpp
@@ -54,29 +54,24 @@ nsSHEntry::nsSHEntry(const nsSHEntry& aO
   , mStateData(aOther.mStateData)
   , mIsSrcdocEntry(aOther.mIsSrcdocEntry)
   , mScrollRestorationIsManual(false)
   , mSrcdocData(aOther.mSrcdocData)
   , mBaseURI(aOther.mBaseURI)
 {
 }
 
-static bool
-ClearParentPtr(nsISHEntry* aEntry, void* /* aData */)
-{
-  if (aEntry) {
-    aEntry->SetParent(nullptr);
-  }
-  return true;
-}
-
 nsSHEntry::~nsSHEntry()
 {
   // Null out the mParent pointers on all our kids.
-  mChildren.EnumerateForwards(ClearParentPtr, nullptr);
+  for (nsISHEntry* entry : mChildren) {
+    if (entry) {
+      entry->SetParent(nullptr);
+    }
+  }
 }
 
 NS_IMPL_ISUPPORTS(nsSHEntry, nsISHContainer, nsISHEntry, nsISHEntryInternal)
 
 NS_IMETHODIMP
 nsSHEntry::SetScrollPosition(int32_t aX, int32_t aY)
 {
   mScrollPositionX = aX;
--- a/dom/base/FragmentOrElement.cpp
+++ b/dom/base/FragmentOrElement.cpp
@@ -385,16 +385,19 @@ nsIContent::GetBaseURI(bool aTryUseXHRDo
     if (!attr.IsEmpty()) {
       baseAttrs.AppendElement(attr);
     }
     elem = elem->GetParent();
   } while(elem);
 
   if (!baseAttrs.IsEmpty()) {
     doc->WarnOnceAbout(nsIDocument::eXMLBaseAttribute);
+    if (IsHTMLElement() || IsSVGElement() || IsXULElement()) {
+      doc->WarnOnceAbout(nsIDocument::eXMLBaseAttributeWithStyledElement);
+    }
     // Now resolve against all xml:base attrs
     for (uint32_t i = baseAttrs.Length() - 1; i != uint32_t(-1); --i) {
       nsCOMPtr<nsIURI> newBase;
       nsresult rv = NS_NewURI(getter_AddRefs(newBase), baseAttrs[i],
                               doc->GetDocumentCharacterSet().get(), base);
       // Do a security check, almost the same as nsDocument::SetBaseURL()
       // Only need to do this on the final uri
       if (NS_SUCCEEDED(rv) && i == 0) {
--- a/dom/base/nsDeprecatedOperationList.h
+++ b/dom/base/nsDeprecatedOperationList.h
@@ -47,8 +47,9 @@ DEPRECATED_OPERATION(RTCPeerConnectionGe
 DEPRECATED_OPERATION(AppCache)
 DEPRECATED_OPERATION(PrefixedImageSmoothingEnabled)
 DEPRECATED_OPERATION(PrefixedFullscreenAPI)
 DEPRECATED_OPERATION(LenientSetter)
 DEPRECATED_OPERATION(FileLastModifiedDate)
 DEPRECATED_OPERATION(ImageBitmapRenderingContext_TransferImageBitmap)
 DEPRECATED_OPERATION(URLCreateObjectURL_MediaStream)
 DEPRECATED_OPERATION(XMLBaseAttribute)
+DEPRECATED_OPERATION(XMLBaseAttributeWithStyledElement)
--- a/dom/base/nsRange.cpp
+++ b/dom/base/nsRange.cpp
@@ -2894,17 +2894,17 @@ GetTextFrameForContent(nsIContent* aCont
     if (frame && frame->GetType() == nsGkAtoms::textFrame) {
       return static_cast<nsTextFrame*>(frame);
     }
   }
   return nullptr;
 }
 
 static nsresult GetPartialTextRect(nsLayoutUtils::RectCallback* aCallback,
-                                   mozilla::dom::DOMStringList* aTextList,
+                                   Sequence<nsString>* aTextList,
                                    nsIContent* aContent, int32_t aStartOffset,
                                    int32_t aEndOffset, bool aClampToEdge,
                                    bool aFlushLayout)
 {
   nsTextFrame* textFrame = GetTextFrameForContent(aContent, aFlushLayout);
   if (textFrame) {
     // If we'll need it later, collect the full content text now.
     nsAutoString textContent;
@@ -2942,26 +2942,26 @@ static nsresult GetPartialTextRect(nsLay
       aCallback->AddRect(r);
 
       // Finally capture the text, if requested.
       if (aTextList) {
         const nsAString& textSubstring =
           Substring(textContent,
                     textContentStart,
                     (textContentEnd - textContentStart));
-        aTextList->Add(textSubstring);
+        aTextList->AppendElement(textSubstring, fallible);
       }
     }
   }
   return NS_OK;
 }
 
 /* static */ void
 nsRange::CollectClientRectsAndText(nsLayoutUtils::RectCallback* aCollector,
-                                   mozilla::dom::DOMStringList* aTextList,
+                                   Sequence<nsString>* aTextList,
                                    nsRange* aRange,
                                    nsINode* aStartParent, int32_t aStartOffset,
                                    nsINode* aEndParent, int32_t aEndOffset,
                                    bool aClampToEdge, bool aFlushLayout)
 {
   // Hold strong pointers across the flush
   nsCOMPtr<nsINode> startContainer = aStartParent;
   nsCOMPtr<nsINode> endContainer = aEndParent;
@@ -3092,21 +3092,20 @@ nsRange::GetClientRectsAndTexts(
   mozilla::dom::ClientRectsAndTexts& aResult,
   ErrorResult& aErr)
 {
   if (!mStartParent) {
     return;
   }
 
   aResult.mRectList = new DOMRectList(static_cast<nsIDOMRange*>(this));
-  aResult.mTextList = new DOMStringList();
 
   nsLayoutUtils::RectListBuilder builder(aResult.mRectList);
 
-  CollectClientRectsAndText(&builder, aResult.mTextList, this,
+  CollectClientRectsAndText(&builder, &aResult.mTextList, this,
     mStartParent, mStartOffset, mEndParent, mEndOffset, true, true);
 }
 
 NS_IMETHODIMP
 nsRange::GetUsedFontFaces(nsIDOMFontFaceList** aResult)
 {
   *aResult = nullptr;
 
--- a/dom/base/nsRange.h
+++ b/dom/base/nsRange.h
@@ -267,17 +267,17 @@ public:
   static bool IsNodeSelected(nsINode* aNode, uint32_t aStartOffset,
                              uint32_t aEndOffset);
 
   /**
    * This helper function gets rects and correlated text for the given range.
    * @param aTextList optional where nullptr = don't retrieve text
    */
   static void CollectClientRectsAndText(nsLayoutUtils::RectCallback* aCollector,
-                                        mozilla::dom::DOMStringList* aTextList,
+                                        mozilla::dom::Sequence<nsString>* aTextList,
                                         nsRange* aRange,
                                         nsINode* aStartParent, int32_t aStartOffset,
                                         nsINode* aEndParent, int32_t aEndOffset,
                                         bool aClampToEdge, bool aFlushLayout);
 
   /**
    * Scan this range for -moz-user-select:none nodes and split it up into
    * multiple ranges to exclude those nodes.  The resulting ranges are put
--- a/dom/canvas/WebGLContext.cpp
+++ b/dom/canvas/WebGLContext.cpp
@@ -705,16 +705,32 @@ WebGLContext::CreateAndInitGLWith(FnCrea
 
     return true;
 }
 
 bool
 WebGLContext::CreateAndInitGL(bool forceEnabled,
                               std::vector<FailureReason>* const out_failReasons)
 {
+    // WebGL2 is separately blocked:
+    if (IsWebGL2()) {
+        const nsCOMPtr<nsIGfxInfo> gfxInfo = services::GetGfxInfo();
+        const auto feature = nsIGfxInfo::FEATURE_WEBGL2;
+
+        FailureReason reason;
+        if (IsFeatureInBlacklist(gfxInfo, feature, &reason.key)) {
+            reason.info = "Refused to create WebGL2 context because of blacklist"
+                          " entry: ";
+            reason.info.Append(reason.key);
+            out_failReasons->push_back(reason);
+            GenerateWarning("%s", reason.info.BeginReading());
+            return false;
+        }
+    }
+
     const gl::SurfaceCaps baseCaps = BaseCaps(mOptions, this);
     gl::CreateContextFlags flags = gl::CreateContextFlags::NO_VALIDATION;
     bool tryNativeGL = true;
     bool tryANGLE = false;
 
     if (forceEnabled) {
         flags |= gl::CreateContextFlags::FORCE_ENABLE_HARDWARE;
     }
--- a/dom/html/HTMLMediaElement.h
+++ b/dom/html/HTMLMediaElement.h
@@ -744,16 +744,18 @@ public:
 
   // These are used for testing only
   float ComputedVolume() const;
   bool ComputedMuted() const;
   nsSuspendedTypes ComputedSuspended() const;
 
   void SetMediaInfo(const MediaInfo& aInfo);
 
+  virtual AbstractThread* AbstractMainThread() const final override;
+
   // Telemetry: to record the usage of a {visible / invisible} video element as
   // the source of {drawImage(), createPattern(), createImageBitmap() and
   // captureStream()} APIs.
   enum class CallerAPI {
     DRAW_IMAGE,
     CREATE_PATTERN,
     CREATE_IMAGEBITMAP,
     CAPTURE_STREAM,
@@ -1183,18 +1185,16 @@ protected:
 
   // Get the HTMLMediaElement object if the decoder is being used from an
   // HTML media element, and null otherwise.
   virtual HTMLMediaElement* GetMediaElement() final override
   {
     return this;
   }
 
-  virtual AbstractThread* AbstractMainThread() const final override;
-
   // Return true if decoding should be paused
   virtual bool GetPaused() final override
   {
     bool isPaused = false;
     GetPaused(&isPaused);
     return isPaused;
   }
 
--- a/dom/media/MediaDecoder.cpp
+++ b/dom/media/MediaDecoder.cpp
@@ -58,16 +58,18 @@ static const uint64_t ESTIMATED_DURATION
 
 LazyLogModule gMediaDecoderLog("MediaDecoder");
 #define DECODER_LOG(x, ...) \
   MOZ_LOG(gMediaDecoderLog, LogLevel::Debug, ("Decoder=%p " x, this, ##__VA_ARGS__))
 
 #define DUMP_LOG(x, ...) \
   NS_DebugBreak(NS_DEBUG_WARNING, nsPrintfCString("Decoder=%p " x, this, ##__VA_ARGS__).get(), nullptr, nullptr, -1)
 
+#define NS_DispatchToMainThread(...) CompileError_UseAbstractMainThreadInstead
+
 static const char*
 ToPlayStateStr(MediaDecoder::PlayState aState)
 {
   switch (aState) {
     case MediaDecoder::PLAY_STATE_START:    return "START";
     case MediaDecoder::PLAY_STATE_LOADING:  return "LOADING";
     case MediaDecoder::PLAY_STATE_PAUSED:   return "PAUSED";
     case MediaDecoder::PLAY_STATE_PLAYING:  return "PLAYING";
@@ -149,16 +151,17 @@ MediaDecoder::ResourceCallback::Resource
 }
 
 void
 MediaDecoder::ResourceCallback::Connect(MediaDecoder* aDecoder)
 {
   MOZ_ASSERT(NS_IsMainThread());
   mDecoder = aDecoder;
   mTimer = do_CreateInstance("@mozilla.org/timer;1");
+  mTimer->SetTarget(mAbstractMainThread->AsEventTarget());
 }
 
 void
 MediaDecoder::ResourceCallback::Disconnect()
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (mDecoder) {
     mDecoder = nullptr;
@@ -460,17 +463,16 @@ MediaDecoder::MediaDecoder(MediaDecoderO
 
   // mIgnoreProgressData
   mWatchManager.Watch(mLogicallySeeking, &MediaDecoder::SeekingChanged);
 
   mWatchManager.Watch(mIsAudioDataAudible,
                       &MediaDecoder::NotifyAudibleStateChanged);
 
   MediaShutdownManager::InitStatics();
-  MediaShutdownManager::Instance().Register(this);
 }
 
 #undef INIT_MIRROR
 #undef INIT_CANONICAL
 
 void
 MediaDecoder::Shutdown()
 {
@@ -628,17 +630,22 @@ MediaDecoder::OpenResource(nsIStreamList
 }
 
 nsresult
 MediaDecoder::Load(nsIStreamListener** aStreamListener)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(mResource, "Can't load without a MediaResource");
 
-  nsresult rv = OpenResource(aStreamListener);
+  nsresult rv = MediaShutdownManager::Instance().Register(this);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
+  rv = OpenResource(aStreamListener);
   NS_ENSURE_SUCCESS(rv, rv);
 
   SetStateMachine(CreateStateMachine());
   NS_ENSURE_TRUE(GetStateMachine(), NS_ERROR_FAILURE);
 
   return InitializeStateMachine();
 }
 
@@ -1650,18 +1657,18 @@ MediaMemoryTracker::CollectReports(nsIHa
   //     resources memory and finish the asynchronous memory report.
   RefPtr<MediaDecoder::ResourceSizes> resourceSizes =
       new MediaDecoder::ResourceSizes(MediaMemoryTracker::MallocSizeOf);
 
   nsCOMPtr<nsIHandleReportCallback> handleReport = aHandleReport;
   nsCOMPtr<nsISupports> data = aData;
 
   resourceSizes->Promise()->Then(
-      // Non-DocGroup version of AbstractThread::MainThread is fine for memory
-      // report.
+      // Don't use SystemGroup::AbstractMainThreadFor() for
+      // handleReport->Callback() will run scripts.
       AbstractThread::MainThread(),
       __func__,
       [handleReport, data] (size_t size) {
         handleReport->Callback(
             EmptyCString(), NS_LITERAL_CSTRING("explicit/media/resources"),
             KIND_HEAP, UNITS_BYTES, size,
             NS_LITERAL_CSTRING("Memory used by media resources including "
                                "streaming buffers, caches, etc."),
@@ -1810,17 +1817,17 @@ MediaDecoder::DumpDebugInfo()
 
   if (!GetStateMachine()) {
     DUMP_LOG("%s", str.get());
     return;
   }
 
   RefPtr<MediaDecoder> self = this;
   GetStateMachine()->RequestDebugInfo()->Then(
-    AbstractThread::MainThread(), __func__,
+    SystemGroup::AbstractMainThreadFor(TaskCategory::Other), __func__,
     [this, self, str] (const nsACString& aString) {
       DUMP_LOG("%s", str.get());
       DUMP_LOG("%s", aString.Data());
     },
     [this, self, str] () {
       DUMP_LOG("%s", str.get());
     });
 }
@@ -1831,17 +1838,17 @@ MediaDecoder::RequestDebugInfo()
   MOZ_DIAGNOSTIC_ASSERT(!IsShutdown());
 
   auto str = GetDebugInfo();
   if (!GetStateMachine()) {
     return DebugInfoPromise::CreateAndResolve(str, __func__);
   }
 
   return GetStateMachine()->RequestDebugInfo()->Then(
-    AbstractThread::MainThread(), __func__,
+    SystemGroup::AbstractMainThreadFor(TaskCategory::Other), __func__,
     [str] (const nsACString& aString) {
       nsCString result = str + nsCString("\n") + aString;
       return DebugInfoPromise::CreateAndResolve(result, __func__);
     },
     [str] () {
       return DebugInfoPromise::CreateAndResolve(str, __func__);
     });
 }
@@ -1867,8 +1874,9 @@ MediaMemoryTracker::~MediaMemoryTracker(
 {
   UnregisterWeakMemoryReporter(this);
 }
 
 } // namespace mozilla
 
 // avoid redefined macro in unified build
 #undef DECODER_LOG
+#undef NS_DispatchToMainThread
--- a/dom/media/MediaFormatReader.cpp
+++ b/dom/media/MediaFormatReader.cpp
@@ -38,16 +38,18 @@ using mozilla::layers::LayerManager;
 using mozilla::layers::LayersBackend;
 
 static mozilla::LazyLogModule sFormatDecoderLog("MediaFormatReader");
 mozilla::LazyLogModule gMediaDemuxerLog("MediaDemuxer");
 
 #define LOG(arg, ...) MOZ_LOG(sFormatDecoderLog, mozilla::LogLevel::Debug, ("MediaFormatReader(%p)::%s: " arg, this, __func__, ##__VA_ARGS__))
 #define LOGV(arg, ...) MOZ_LOG(sFormatDecoderLog, mozilla::LogLevel::Verbose, ("MediaFormatReader(%p)::%s: " arg, this, __func__, ##__VA_ARGS__))
 
+#define NS_DispatchToMainThread(...) CompileError_UseAbstractMainThreadInstead
+
 namespace mozilla {
 
 /**
  * This is a singleton which controls the number of decoders that can be
  * created concurrently. Before calling PDMFactory::CreateDecoder(), Alloc()
  * must be called to get a token object as a permission to create a decoder.
  * The token should stay alive until Shutdown() is called on the decoder.
  * The destructor of the token will restore the decoder count so it is available
@@ -111,21 +113,23 @@ private:
 
   GlobalAllocPolicy& mPolicy; // reference to a singleton object.
 };
 
 GlobalAllocPolicy::GlobalAllocPolicy()
   : mMonitor("DecoderAllocPolicy::mMonitor")
   , mDecoderLimit(MediaPrefs::MediaDecoderLimit())
 {
-  // Non DocGroup-version AbstractThread::MainThread is fine for
-  // ClearOnShutdown().
-  AbstractThread::MainThread()->Dispatch(NS_NewRunnableFunction([this] () {
-    ClearOnShutdown(this, ShutdownPhase::ShutdownThreads);
-  }));
+  SystemGroup::Dispatch(
+    "GlobalAllocPolicy::ClearOnShutdown",
+    TaskCategory::Other,
+    NS_NewRunnableFunction([this] () {
+      ClearOnShutdown(this, ShutdownPhase::ShutdownThreads);
+    })
+  );
 }
 
 GlobalAllocPolicy::~GlobalAllocPolicy()
 {
   while (!mPromises.empty()) {
     RefPtr<PromisePrivate> p = mPromises.front().forget();
     mPromises.pop();
     p->Reject(true, __func__);
@@ -647,20 +651,19 @@ MediaFormatReader::DecoderFactory::DoIni
 // This ensure that the reader's taskqueue will never blocked while a demuxer
 // is itself blocked attempting to access the MediaCache or the MediaResource.
 class MediaFormatReader::DemuxerProxy
 {
   using TrackType = TrackInfo::TrackType;
   class Wrapper;
 
 public:
-  explicit DemuxerProxy(MediaDataDemuxer* aDemuxer, AbstractThread* aMainThread)
+  explicit DemuxerProxy(MediaDataDemuxer* aDemuxer)
     : mTaskQueue(new AutoTaskQueue(
-                   GetMediaThreadPool(MediaThreadType::PLATFORM_DECODER),
-                   aMainThread))
+                   GetMediaThreadPool(MediaThreadType::PLATFORM_DECODER)))
     , mData(new Data(aDemuxer))
   {
     MOZ_COUNT_CTOR(DemuxerProxy);
   }
 
   ~DemuxerProxy()
   {
     MOZ_COUNT_DTOR(DemuxerProxy);
@@ -987,19 +990,17 @@ MediaFormatReader::DemuxerProxy::NotifyD
 MediaFormatReader::MediaFormatReader(AbstractMediaDecoder* aDecoder,
                                      MediaDataDemuxer* aDemuxer,
                                      VideoFrameContainer* aVideoFrameContainer)
   : MediaDecoderReader(aDecoder)
   , mAudio(this, MediaData::AUDIO_DATA,
            Preferences::GetUint("media.audio-max-decode-error", 3))
   , mVideo(this, MediaData::VIDEO_DATA,
            Preferences::GetUint("media.video-max-decode-error", 2))
-  , mDemuxer(new DemuxerProxy(aDemuxer, aDecoder
-                                        ? aDecoder->AbstractMainThread()
-                                        : AbstractThread::MainThread()))
+  , mDemuxer(new DemuxerProxy(aDemuxer))
   , mDemuxerInitDone(false)
   , mLastReportedNumDecodedFrames(0)
   , mPreviousDecodedKeyframeTime_us(sNoPreviousDecodedKeyframe)
   , mInitDone(false)
   , mTrackDemuxersMayBlock(false)
   , mSeekScheduled(false)
   , mVideoFrameContainer(aVideoFrameContainer)
   , mDecoderFactory(new DecoderFactory(this))
@@ -1334,19 +1335,20 @@ MediaFormatReader::OnDemuxerInitDone(nsr
       mAudio.mTrackDemuxer = nullptr;
     }
   }
 
   UniquePtr<EncryptionInfo> crypto = mDemuxer->GetCrypto();
   if (mDecoder && crypto && crypto->IsEncrypted()) {
     // Try and dispatch 'encrypted'. Won't go if ready state still HAVE_NOTHING.
     for (uint32_t i = 0; i < crypto->mInitDatas.Length(); i++) {
-      NS_DispatchToMainThread(
+      nsCOMPtr<nsIRunnable> r =
         new DispatchKeyNeededEvent(mDecoder, crypto->mInitDatas[i].mInitData,
-                                   crypto->mInitDatas[i].mType));
+                                   crypto->mInitDatas[i].mType);
+      mDecoder->AbstractMainThread()->Dispatch(r.forget());
     }
     mInfo.mCrypto = *crypto;
   }
 
   int64_t videoDuration = HasVideo() ? mInfo.mVideo.mDuration : 0;
   int64_t audioDuration = HasAudio() ? mInfo.mAudio.mDuration : 0;
 
   int64_t duration = std::max(videoDuration, audioDuration);
@@ -1699,16 +1701,17 @@ MediaFormatReader::NotifyWaitingForKey(T
 {
   MOZ_ASSERT(OnTaskQueue());
   auto& decoder = GetDecoderData(aTrack);
   if (mDecoder) {
     mDecoder->NotifyWaitingForKey();
   }
   if (!decoder.mDecodeRequest.Exists()) {
     LOGV("WaitingForKey received while no pending decode. Ignoring");
+    return;
   }
   decoder.mWaitingForKey = true;
   ScheduleUpdate(aTrack);
 }
 
 void
 MediaFormatReader::NotifyEndOfStream(TrackType aTrack)
 {
@@ -2256,41 +2259,43 @@ MediaFormatReader::Update(TrackType aTra
     } else if (aTrack == TrackType::kAudioTrack) {
       decoder.Flush();
     }
     return;
   }
 
   bool needInput = NeedInput(decoder);
 
-  LOGV("Update(%s) ni=%d no=%d in:%" PRIu64 " out:%" PRIu64
-       " qs=%u decoding:%d flushing:%d "
-       "shutdown:%d pending:%u waiting:%d promise:%d sid:%u",
-       TrackTypeToStr(aTrack), needInput, needOutput, decoder.mNumSamplesInput,
-       decoder.mNumSamplesOutput, uint32_t(size_t(decoder.mSizeOfQueue)),
-       decoder.mDecodeRequest.Exists(), decoder.mFlushRequest.Exists(),
-       decoder.mShutdownRequest.Exists(), uint32_t(decoder.mOutput.Length()),
-       decoder.mWaitingForData, decoder.HasPromise(),
-       decoder.mLastStreamSourceID);
+  LOGV(
+    "Update(%s) ni=%d no=%d in:%" PRIu64 " out:%" PRIu64
+    " qs=%u decoding:%d flushing:%d shutdown:%d pending:%u waiting:%d sid:%u",
+    TrackTypeToStr(aTrack),
+    needInput,
+    needOutput,
+    decoder.mNumSamplesInput,
+    decoder.mNumSamplesOutput,
+    uint32_t(size_t(decoder.mSizeOfQueue)),
+    decoder.mDecodeRequest.Exists(),
+    decoder.mFlushRequest.Exists(),
+    decoder.mShutdownRequest.Exists(),
+    uint32_t(decoder.mOutput.Length()),
+    decoder.mWaitingForData,
+    decoder.mLastStreamSourceID);
 
   if ((decoder.mWaitingForData
        && (!decoder.mTimeThreshold || decoder.mTimeThreshold.ref().mWaiting))
       || (decoder.mWaitingForKey && decoder.mDecodeRequest.Exists())) {
     // Nothing more we can do at present.
     LOGV("Still waiting for data or key.");
     return;
   }
 
-  if (decoder.mWaitingForKey) {
-    decoder.mWaitingForKey = false;
-    if (decoder.HasWaitingPromise() && !decoder.IsWaiting()) {
-      LOGV("No longer waiting for key. Resolving waiting promise");
-      decoder.mWaitingPromise.Resolve(decoder.mType, __func__);
-      return;
-    }
+  if (decoder.CancelWaitingForKey()) {
+    LOGV("No longer waiting for key. Resolving waiting promise");
+    return;
   }
 
   if (!needInput) {
     LOGV("No need for additional input (pending:%u)",
          uint32_t(decoder.mOutput.Length()));
     return;
   }
 
@@ -2933,46 +2938,65 @@ MediaFormatReader::GetMozDebugReaderData
     videoName = mVideo.mDescription;
   }
 
   result += nsPrintfCString("audio decoder: %s\n", audioName);
   result += nsPrintfCString("audio frames decoded: %" PRIu64 "\n",
                             mAudio.mNumSamplesOutputTotal);
   if (HasAudio()) {
     result += nsPrintfCString(
-      "audio state: ni=%d no=%d demuxr:%d demuxq:%d tt:%f tths:%d in:%" PRIu64
-      " out:%" PRIu64 " qs=%u pending:%u waiting:%d sid:%u\n",
-      NeedInput(mAudio), mAudio.HasPromise(), mAudio.mDemuxRequest.Exists(),
-      int(mAudio.mQueuedSamples.Length()),
+      "audio state: ni=%d no=%d wp:%d demuxr:%d demuxq:%u decoder:%d tt:%.1f "
+      "tths:%d in:%" PRIu64 " out:%" PRIu64
+      " qs=%u pending:%u wfd:%d wfk:%d sid:%u\n",
+      NeedInput(mAudio),
+      mAudio.HasPromise(),
+      !mAudio.mWaitingPromise.IsEmpty(),
+      mAudio.mDemuxRequest.Exists(),
+      uint32_t(mAudio.mQueuedSamples.Length()),
+      mAudio.mDecodeRequest.Exists(),
       mAudio.mTimeThreshold ? mAudio.mTimeThreshold.ref().Time().ToSeconds()
                             : -1.0,
       mAudio.mTimeThreshold ? mAudio.mTimeThreshold.ref().mHasSeeked : -1,
-      mAudio.mNumSamplesInput, mAudio.mNumSamplesOutput,
-      unsigned(size_t(mAudio.mSizeOfQueue)), unsigned(mAudio.mOutput.Length()),
-      mAudio.mWaitingForData, mAudio.mLastStreamSourceID);
+      mAudio.mNumSamplesInput,
+      mAudio.mNumSamplesOutput,
+      unsigned(size_t(mAudio.mSizeOfQueue)),
+      unsigned(mAudio.mOutput.Length()),
+      mAudio.mWaitingForData,
+      mAudio.mWaitingForKey,
+      mAudio.mLastStreamSourceID);
   }
   result += nsPrintfCString("video decoder: %s\n", videoName);
   result +=
     nsPrintfCString("hardware video decoding: %s\n",
                     VideoIsHardwareAccelerated() ? "enabled" : "disabled");
-  result += nsPrintfCString("video frames decoded: %" PRIu64 " (skipped:%" PRIu64 ")\n",
-                            mVideo.mNumSamplesOutputTotal,
-                            mVideo.mNumSamplesSkippedTotal);
+  result +=
+    nsPrintfCString("video frames decoded: %" PRIu64 " (skipped:%" PRIu64 ")\n",
+                    mVideo.mNumSamplesOutputTotal,
+                    mVideo.mNumSamplesSkippedTotal);
   if (HasVideo()) {
     result += nsPrintfCString(
-      "video state: ni=%d no=%d demuxr:%d demuxq:%d tt:%f tths:%d in:%" PRIu64
-      " out:%" PRIu64 " qs=%u pending:%u waiting:%d sid:%u\n",
-      NeedInput(mVideo), mVideo.HasPromise(), mVideo.mDemuxRequest.Exists(),
-      int(mVideo.mQueuedSamples.Length()),
+      "video state: ni=%d no=%d wp:%d demuxr:%d demuxq:%u decoder:%d tt:%.1f "
+      "tths:%d in:%" PRIu64 " out:%" PRIu64
+      " qs=%u pending:%u wfd:%d wfk:%d sid:%u\n",
+      NeedInput(mVideo),
+      mVideo.HasPromise(),
+      !mVideo.mWaitingPromise.IsEmpty(),
+      mVideo.mDemuxRequest.Exists(),
+      uint32_t(mVideo.mQueuedSamples.Length()),
+      mVideo.mDecodeRequest.Exists(),
       mVideo.mTimeThreshold ? mVideo.mTimeThreshold.ref().Time().ToSeconds()
                             : -1.0,
       mVideo.mTimeThreshold ? mVideo.mTimeThreshold.ref().mHasSeeked : -1,
-      mVideo.mNumSamplesInput, mVideo.mNumSamplesOutput,
-      unsigned(size_t(mVideo.mSizeOfQueue)), unsigned(mVideo.mOutput.Length()),
-      mVideo.mWaitingForData, mVideo.mLastStreamSourceID);
+      mVideo.mNumSamplesInput,
+      mVideo.mNumSamplesOutput,
+      unsigned(size_t(mVideo.mSizeOfQueue)),
+      unsigned(mVideo.mOutput.Length()),
+      mVideo.mWaitingForData,
+      mVideo.mWaitingForKey,
+      mVideo.mLastStreamSourceID);
   }
   aString += result;
 }
 
 void
 MediaFormatReader::SetVideoBlankDecode(bool aIsBlankDecode)
 {
   MOZ_ASSERT(OnTaskQueue());
@@ -3025,8 +3049,10 @@ MediaFormatReader::OnFirstDemuxFailed(Tr
 
   auto& decoder = GetDecoderData(aType);
   MOZ_ASSERT(decoder.mFirstDemuxedSampleTime.isNothing());
   decoder.mFirstDemuxedSampleTime.emplace(TimeUnit::FromInfinity());
   MaybeResolveMetadataPromise();
 }
 
 } // namespace mozilla
+
+#undef NS_DispatchToMainThread
--- a/dom/media/MediaFormatReader.h
+++ b/dom/media/MediaFormatReader.h
@@ -344,16 +344,17 @@ private:
     {
       if (mFlushRequest.Exists() || mFlushed) {
         // Flush still pending or already flushed, nothing more to do.
         return;
       }
       mDecodeRequest.DisconnectIfExists();
       mDrainRequest.DisconnectIfExists();
       mDrainState = DrainState::None;
+      CancelWaitingForKey();
       mOutput.Clear();
       mNumSamplesInput = 0;
       mNumSamplesOutput = 0;
       mSizeOfQueue = 0;
       if (mDecoder && !mFlushed) {
         RefPtr<MediaFormatReader> owner = mOwner;
         TrackType type = mType == MediaData::AUDIO_DATA
                          ? TrackType::kAudioTrack
@@ -376,29 +377,42 @@ private:
                    }
                    owner->NotifyError(type, aError);
                  })
           ->Track(mFlushRequest);
       }
       mFlushed = true;
     }
 
+    bool CancelWaitingForKey()
+    {
+      if (!mWaitingForKey) {
+        return false;
+      }
+      mWaitingForKey = false;
+      if (IsWaiting() || !HasWaitingPromise()) {
+        return false;
+      }
+      mWaitingPromise.Resolve(mType, __func__);
+      return true;
+    }
+
     // Reset the state of the DecoderData, clearing all queued frames
     // (pending demuxed and decoded).
     // The track demuxer is *not* reset.
     void ResetState()
     {
       MOZ_ASSERT(mOwner->OnTaskQueue());
       mDemuxEOS = false;
       mWaitingForData = false;
-      mWaitingForKey = false;
       mQueuedSamples.Clear();
       mDecodeRequest.DisconnectIfExists();
       mDrainRequest.DisconnectIfExists();
       mDrainState = DrainState::None;
+      CancelWaitingForKey();
       mTimeThreshold.reset();
       mLastSampleTime.reset();
       mOutput.Clear();
       mNumSamplesInput = 0;
       mNumSamplesOutput = 0;
       mSizeOfQueue = 0;
       mNextStreamSourceID.reset();
       if (!HasFatalError()) {
--- a/dom/media/MediaShutdownManager.cpp
+++ b/dom/media/MediaShutdownManager.cpp
@@ -86,34 +86,39 @@ MediaShutdownManager::RemoveBlocker()
   MOZ_ASSERT(mDecoders.Count() == 0);
   GetShutdownBarrier()->RemoveBlocker(this);
   // Clear our singleton reference. This will probably delete
   // this instance, so don't deref |this| clearing sInstance.
   sInstance = nullptr;
   DECODER_LOG(LogLevel::Debug, ("MediaShutdownManager::BlockShutdown() end."));
 }
 
-void
+nsresult
 MediaShutdownManager::Register(MediaDecoder* aDecoder)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  MOZ_RELEASE_ASSERT(!mIsDoingXPCOMShutDown);
+  if (mIsDoingXPCOMShutDown) {
+    return NS_ERROR_ABORT;
+  }
   // Don't call Register() after you've Unregistered() all the decoders,
   // that's not going to work.
   MOZ_ASSERT(!mDecoders.Contains(aDecoder));
   mDecoders.PutEntry(aDecoder);
   MOZ_ASSERT(mDecoders.Contains(aDecoder));
   MOZ_ASSERT(mDecoders.Count() > 0);
+  return NS_OK;
 }
 
 void
 MediaShutdownManager::Unregister(MediaDecoder* aDecoder)
 {
   MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(mDecoders.Contains(aDecoder));
+  if (!mDecoders.Contains(aDecoder)) {
+    return;
+  }
   mDecoders.RemoveEntry(aDecoder);
   if (mIsDoingXPCOMShutDown && mDecoders.Count() == 0) {
     RemoveBlocker();
   }
 }
 
 NS_IMETHODIMP
 MediaShutdownManager::GetName(nsAString& aName)
--- a/dom/media/MediaShutdownManager.h
+++ b/dom/media/MediaShutdownManager.h
@@ -57,17 +57,17 @@ public:
   static void InitStatics();
 
   // The MediaShutdownManager is a singleton, access its instance with
   // this accessor.
   static MediaShutdownManager& Instance();
 
   // Notifies the MediaShutdownManager that it needs to track the shutdown
   // of this MediaDecoder.
-  void Register(MediaDecoder* aDecoder);
+  nsresult Register(MediaDecoder* aDecoder);
 
   // Notifies the MediaShutdownManager that a MediaDecoder that it was
   // tracking has shutdown, and it no longer needs to be shutdown in the
   // xpcom-shutdown listener.
   void Unregister(MediaDecoder* aDecoder);
 
 private:
 
--- a/dom/media/VideoFrameContainer.cpp
+++ b/dom/media/VideoFrameContainer.cpp
@@ -12,24 +12,28 @@
 #include "nsSVGEffects.h"
 
 using namespace mozilla::layers;
 
 namespace mozilla {
 static LazyLogModule gVideoFrameContainerLog("VideoFrameContainer");
 #define CONTAINER_LOG(type, msg) MOZ_LOG(gVideoFrameContainerLog, type, msg)
 
+#define NS_DispatchToMainThread(...) CompileError_UseAbstractMainThreadInstead
+
 VideoFrameContainer::VideoFrameContainer(dom::HTMLMediaElement* aElement,
                                          already_AddRefed<ImageContainer> aContainer)
   : mElement(aElement),
     mImageContainer(aContainer), mMutex("nsVideoFrameContainer"),
     mBlackImage(nullptr),
     mFrameID(0),
     mIntrinsicSizeChanged(false), mImageSizeChanged(false),
-    mPendingPrincipalHandle(PRINCIPAL_HANDLE_NONE), mFrameIDForPendingPrincipalHandle(0)
+    mPendingPrincipalHandle(PRINCIPAL_HANDLE_NONE),
+    mFrameIDForPendingPrincipalHandle(0),
+    mMainThread(aElement->AbstractMainThread())
 {
   NS_ASSERTION(aElement, "aElement must not be null");
   NS_ASSERTION(mImageContainer, "aContainer must not be null");
 }
 
 VideoFrameContainer::~VideoFrameContainer()
 {}
 
@@ -73,17 +77,18 @@ SetImageToBlackPixel(PlanarYCbCrImage* a
   data.mYStride = data.mCbCrStride = 1;
   data.mPicSize = data.mYSize = data.mCbCrSize = gfx::IntSize(1, 1);
   aImage->CopyData(data);
 }
 
 class VideoFrameContainerInvalidateRunnable : public Runnable {
 public:
   explicit VideoFrameContainerInvalidateRunnable(VideoFrameContainer* aVideoFrameContainer)
-    : mVideoFrameContainer(aVideoFrameContainer)
+    : Runnable("VideoFrameContainerInvalidateRunnable")
+    , mVideoFrameContainer(aVideoFrameContainer)
   {}
   NS_IMETHOD Run()
   {
     MOZ_ASSERT(NS_IsMainThread());
 
     mVideoFrameContainer->Invalidate();
 
     return NS_OK;
@@ -166,17 +171,17 @@ void VideoFrameContainer::SetCurrentFram
   if (principalHandleChanged) {
     UpdatePrincipalHandleForFrameIDLocked(lastPrincipalHandle,
                                           newImages.LastElement().mFrameID);
   }
 
   SetCurrentFramesLocked(mLastPlayedVideoFrame.GetIntrinsicSize(), images);
   nsCOMPtr<nsIRunnable> event =
     new VideoFrameContainerInvalidateRunnable(this);
-  NS_DispatchToMainThread(event.forget());
+  mMainThread->Dispatch(event.forget());
 
   images.ClearAndRetainStorage();
 }
 
 void VideoFrameContainer::ClearFrames()
 {
   ClearFutureFrames();
 }
@@ -237,21 +242,26 @@ void VideoFrameContainer::SetCurrentFram
     // set of images.
     // This means that the old principal handle has been flushed out and we can
     // notify our video element about this change.
     RefPtr<VideoFrameContainer> self = this;
     PrincipalHandle principalHandle = mPendingPrincipalHandle;
     mLastPrincipalHandle = mPendingPrincipalHandle;
     mPendingPrincipalHandle = PRINCIPAL_HANDLE_NONE;
     mFrameIDForPendingPrincipalHandle = 0;
-    NS_DispatchToMainThread(NS_NewRunnableFunction([self, principalHandle]() {
-      if (self->mElement) {
-        self->mElement->PrincipalHandleChangedForVideoFrameContainer(self, principalHandle);
-      }
-    }));
+    mMainThread->Dispatch(
+      NS_NewRunnableFunction(
+        "PrincipalHandleChangedForVideoFrameContainer",
+        [self, principalHandle]() {
+          if (self->mElement) {
+            self->mElement->PrincipalHandleChangedForVideoFrameContainer(self, principalHandle);
+          }
+        }
+      )
+    );
   }
 
   if (aImages.IsEmpty()) {
     mImageContainer->ClearAllImages();
   } else {
     mImageContainer->SetCurrentImages(aImages);
   }
   gfx::IntSize newFrameSize = mImageContainer->GetCurrentSize();
@@ -352,8 +362,10 @@ void VideoFrameContainer::InvalidateWith
                              asyncInvalidate ? nsIFrame::UPDATE_IS_ASYNC : 0);
     }
   }
 
   nsSVGEffects::InvalidateDirectRenderingObservers(mElement);
 }
 
 } // namespace mozilla
+
+#undef NS_DispatchToMainThread
--- a/dom/media/VideoFrameContainer.h
+++ b/dom/media/VideoFrameContainer.h
@@ -135,13 +135,15 @@ protected:
   bool mImageSizeChanged;
   // The last PrincipalHandle we notified mElement about.
   PrincipalHandle mLastPrincipalHandle;
   // The PrincipalHandle the client has notified us is changing with FrameID
   // mFrameIDForPendingPrincipalHandle.
   PrincipalHandle mPendingPrincipalHandle;
   // The FrameID for which mPendingPrincipal is first valid.
   ImageContainer::FrameID mFrameIDForPendingPrincipalHandle;
+
+  const RefPtr<AbstractThread> mMainThread;
 };
 
 } // namespace mozilla
 
 #endif /* VIDEOFRAMECONTAINER_H_ */
--- a/dom/media/mediasource/AutoTaskQueue.h
+++ b/dom/media/mediasource/AutoTaskQueue.h
@@ -13,21 +13,19 @@
 
 namespace mozilla {
 
 // A convenience TaskQueue not requiring explicit shutdown.
 class AutoTaskQueue : public AbstractThread
 {
 public:
   explicit AutoTaskQueue(already_AddRefed<SharedThreadPool> aPool,
-                         AbstractThread* aAbstractMainThread,
                          bool aSupportsTailDispatch = false)
   : AbstractThread(aSupportsTailDispatch)
   , mTaskQueue(new TaskQueue(Move(aPool), aSupportsTailDispatch))
-  , mAbstractMainThread(aAbstractMainThread)
   {}
 
   TaskDispatcher& TailDispatcher() override
   {
     return mTaskQueue->TailDispatcher();
   }
 
   void Dispatch(already_AddRefed<nsIRunnable> aRunnable,
@@ -47,17 +45,16 @@ public:
   bool IsCurrentThreadIn() override { return mTaskQueue->IsCurrentThreadIn(); }
 
 private:
   ~AutoTaskQueue()
   {
     RefPtr<TaskQueue> taskqueue = mTaskQueue;
     nsCOMPtr<nsIRunnable> task =
       NS_NewRunnableFunction([taskqueue]() { taskqueue->BeginShutdown(); });
-    mAbstractMainThread->Dispatch(task.forget());
+    SystemGroup::Dispatch("~AutoTaskQueue", TaskCategory::Other, task.forget());
   }
   RefPtr<TaskQueue> mTaskQueue;
-  const RefPtr<AbstractThread> mAbstractMainThread;
 };
 
 } // namespace mozilla
 
 #endif
--- a/dom/media/mediasource/MediaSourceDecoder.cpp
+++ b/dom/media/mediasource/MediaSourceDecoder.cpp
@@ -3,16 +3,17 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 #include "MediaSourceDecoder.h"
 
 #include "mozilla/Logging.h"
 #include "mozilla/dom/HTMLMediaElement.h"
 #include "MediaDecoderStateMachine.h"
+#include "MediaShutdownManager.h"
 #include "MediaSource.h"
 #include "MediaSourceResource.h"
 #include "MediaSourceUtils.h"
 #include "VideoUtils.h"
 #include "MediaSourceDemuxer.h"
 #include "SourceBufferList.h"
 #include <algorithm>
 
@@ -49,23 +50,29 @@ MediaSourceDecoder::CreateStateMachine()
   return new MediaDecoderStateMachine(this, mReader);
 }
 
 nsresult
 MediaSourceDecoder::Load(nsIStreamListener**)
 {
   MOZ_ASSERT(NS_IsMainThread());
   MOZ_ASSERT(!GetStateMachine());
+
+  nsresult rv = MediaShutdownManager::Instance().Register(this);
+  if (NS_WARN_IF(NS_FAILED(rv))) {
+    return rv;
+  }
+
   SetStateMachine(CreateStateMachine());
   if (!GetStateMachine()) {
     NS_WARNING("Failed to create state machine!");
     return NS_ERROR_FAILURE;
   }
 
-  nsresult rv = GetStateMachine()->Init(this);
+  rv = GetStateMachine()->Init(this);
   NS_ENSURE_SUCCESS(rv, rv);
 
   SetStateMachineParameters();
   return NS_OK;
 }
 
 media::TimeIntervals
 MediaSourceDecoder::GetSeekable()
--- a/dom/media/mediasource/MediaSourceDemuxer.cpp
+++ b/dom/media/mediasource/MediaSourceDemuxer.cpp
@@ -17,17 +17,16 @@
 namespace mozilla {
 
 typedef TrackInfo::TrackType TrackType;
 using media::TimeUnit;
 using media::TimeIntervals;
 
 MediaSourceDemuxer::MediaSourceDemuxer(AbstractThread* aAbstractMainThread)
   : mTaskQueue(new AutoTaskQueue(GetMediaThreadPool(MediaThreadType::PLAYBACK),
-                                 aAbstractMainThread,
                                  /* aSupportsTailDispatch = */ false))
   , mMonitor("MediaSourceDemuxer")
 {
   MOZ_ASSERT(NS_IsMainThread());
 }
 
 // Due to inaccuracies in determining buffer end
 // frames (Bug 1065207). This value is based on videos seen in the wild.
--- a/dom/media/moz.build
+++ b/dom/media/moz.build
@@ -122,16 +122,17 @@ EXPORTS += [
     'MediaMIMETypes.h',
     'MediaPrefs.h',
     'MediaQueue.h',
     'MediaRecorder.h',
     'MediaResource.h',
     'MediaResourceCallback.h',
     'MediaResult.h',
     'MediaSegment.h',
+    'MediaShutdownManager.h',
     'MediaStatistics.h',
     'MediaStreamGraph.h',
     'MediaStreamListener.h',
     'MediaStreamVideoSink.h',
     'MediaTimer.h',
     'MediaTrack.h',
     'MediaTrackList.h',
     'MP3Decoder.h',
--- a/dom/vr/VREventObserver.cpp
+++ b/dom/vr/VREventObserver.cpp
@@ -40,18 +40,18 @@ void
 VREventObserver::DisconnectFromOwner()
 {
   // In the event that nsGlobalWindow is deallocated, VREventObserver may
   // still be AddRef'ed elsewhere.  Ensure that we don't UAF by
   // dereferencing mWindow.
   mWindow = nullptr;
 
   // Unregister from VRManagerChild
-  VRManagerChild* vmc = VRManagerChild::Get();
-  if (vmc) {
+  if (VRManagerChild::IsCreated()) {
+    VRManagerChild* vmc = VRManagerChild::Get();
     vmc->RemoveListener(this);
   }
 }
 
 void
 VREventObserver::NotifyVRDisplayMounted(uint32_t aDisplayID)
 {
   if (mWindow && mWindow->AsInner()->IsCurrentInnerWindow()) {
--- a/dom/webidl/Range.webidl
+++ b/dom/webidl/Range.webidl
@@ -84,15 +84,15 @@ partial interface Range {
 // http://dvcs.w3.org/hg/csswg/raw-file/tip/cssom-view/Overview.html#extensions-to-the-range-interface
 partial interface Range {
   DOMRectList? getClientRects();
   DOMRect getBoundingClientRect();
 };
 
 dictionary ClientRectsAndTexts {
   required DOMRectList rectList;
-  required DOMStringList textList;
+  required sequence<DOMString> textList;
 };
 
 partial interface Range {
   [ChromeOnly, Throws]
   ClientRectsAndTexts getClientRectsAndTexts();
 };
--- a/editor/libeditor/CSSEditUtils.cpp
+++ b/editor/libeditor/CSSEditUtils.cpp
@@ -577,21 +577,21 @@ CSSEditUtils::GetComputedStyle(Element* 
     NS_NewComputedDOMStyle(aElement, EmptyString(), presShell);
 
   return style.forget();
 }
 
 // remove the CSS style "aProperty : aPropertyValue" and possibly remove the whole node
 // if it is a span and if its only attribute is _moz_dirty
 nsresult
-CSSEditUtils::RemoveCSSInlineStyle(nsIDOMNode* aNode,
+CSSEditUtils::RemoveCSSInlineStyle(nsINode& aNode,
                                    nsIAtom* aProperty,
                                    const nsAString& aPropertyValue)
 {
-  nsCOMPtr<Element> element = do_QueryInterface(aNode);
+  RefPtr<Element> element = aNode.AsElement();
   NS_ENSURE_STATE(element);
 
   // remove the property from the style attribute
   nsresult rv = RemoveCSSProperty(*element, *aProperty, aPropertyValue);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (!element->IsHTMLElement(nsGkAtoms::span) ||
       HTMLEditor::HasAttributes(element)) {
--- a/editor/libeditor/CSSEditUtils.h
+++ b/editor/libeditor/CSSEditUtils.h
@@ -148,17 +148,17 @@ public:
    * and removes the node if it is an useless span.
    *
    * @param aNode           [IN] The specific node we want to remove a style
    *                             from.
    * @param aProperty       [IN] The CSS property atom to remove.
    * @param aPropertyValue  [IN] The value of the property we have to remove
    *                             if the property accepts more than one value.
    */
-  nsresult RemoveCSSInlineStyle(nsIDOMNode* aNode, nsIAtom* aProperty,
+  nsresult RemoveCSSInlineStyle(nsINode& aNode, nsIAtom* aProperty,
                                 const nsAString& aPropertyValue);
 
   /**
    * Answers true is the property can be removed by setting a "none" CSS value
    * on a node.
    *
    * @param aProperty     [IN] An atom containing a CSS property.
    * @param aAttribute    [IN] Pointer to an attribute name or null if this
--- a/editor/libeditor/HTMLAbsPositionEditor.cpp
+++ b/editor/libeditor/HTMLAbsPositionEditor.cpp
@@ -529,17 +529,17 @@ HTMLEditor::AbsolutelyPositionElement(ns
     }
 
     nsCOMPtr<dom::Element> element = do_QueryInterface(aElement);
     if (element && element->IsHTMLElement(nsGkAtoms::div) &&
         !HasStyleOrIdOrClass(element)) {
       RefPtr<HTMLEditRules> htmlRules =
         static_cast<HTMLEditRules*>(mRules.get());
       NS_ENSURE_TRUE(htmlRules, NS_ERROR_FAILURE);
-      nsresult rv = htmlRules->MakeSureElemStartsOrEndsOnCR(aElement);
+      nsresult rv = htmlRules->MakeSureElemStartsOrEndsOnCR(*element);
       NS_ENSURE_SUCCESS(rv, rv);
       rv = RemoveContainer(element);
       NS_ENSURE_SUCCESS(rv, rv);
     }
   }
   return NS_OK;
 }
 
--- a/editor/libeditor/HTMLEditRules.cpp
+++ b/editor/libeditor/HTMLEditRules.cpp
@@ -884,17 +884,17 @@ HTMLEditRules::GetAlignment(bool* aMixed
   for (; nodeToExamine; nodeToExamine = nodeToExamine->GetParentNode()) {
     if (!isFirstNodeToExamine &&
         nodeToExamine->IsHTMLElement(nsGkAtoms::table)) {
       // The node to examine is a table and this is not the first node we
       // examine; let's break here to materialize the 'inline-block' behaviour
       // of html tables regarding to text alignment
       return NS_OK;
     }
-    if (HTMLEditUtils::SupportsAlignAttr(GetAsDOMNode(nodeToExamine))) {
+    if (HTMLEditUtils::SupportsAlignAttr(*nodeToExamine)) {
       // Check for alignment
       nsAutoString typeAttrVal;
       nodeToExamine->AsElement()->GetAttr(kNameSpaceID_None, nsGkAtoms::align,
                                           typeAttrVal);
       ToLowerCase(typeAttrVal);
       if (!typeAttrVal.IsEmpty()) {
         if (typeAttrVal.EqualsLiteral("center")) {
           *aAlign = nsIHTMLEditor::eCenter;
@@ -4627,17 +4627,17 @@ HTMLEditRules::WillAlign(Selection& aSel
 
   // If we don't have any nodes, or we have only a single br, then we are
   // creating an empty alignment div.  We have to do some different things for
   // these.
   bool emptyDiv = nodeArray.IsEmpty();
   if (nodeArray.Length() == 1) {
     OwningNonNull<nsINode> node = nodeArray[0];
 
-    if (HTMLEditUtils::SupportsAlignAttr(GetAsDOMNode(node))) {
+    if (HTMLEditUtils::SupportsAlignAttr(*node)) {
       // The node is a table element, an hr, a paragraph, a div or a section
       // header; in HTML 4, it can directly carry the ALIGN attribute and we
       // don't need to make a div! If we are in CSS mode, all the work is done
       // in AlignBlock
       rv = AlignBlock(*node->AsElement(), aAlignType, ContentsOnly::yes);
       NS_ENSURE_SUCCESS(rv, rv);
       return NS_OK;
     }
@@ -4726,17 +4726,17 @@ HTMLEditRules::WillAlign(Selection& aSel
     if (!htmlEditor->IsEditable(curNode)) {
       continue;
     }
 
     // The node is a table element, an hr, a paragraph, a div or a section
     // header; in HTML 4, it can directly carry the ALIGN attribute and we
     // don't need to nest it, just set the alignment.  In CSS, assign the
     // corresponding CSS styles in AlignBlock
-    if (HTMLEditUtils::SupportsAlignAttr(GetAsDOMNode(curNode))) {
+    if (HTMLEditUtils::SupportsAlignAttr(*curNode)) {
       rv = AlignBlock(*curNode->AsElement(), aAlignType, ContentsOnly::no);
       NS_ENSURE_SUCCESS(rv, rv);
       // Clear out curDiv so that we don't put nodes after this one into it
       curDiv = nullptr;
       continue;
     }
 
     nsCOMPtr<nsINode> curParent = curNode->GetParentNode();
@@ -4753,17 +4753,17 @@ HTMLEditRules::WillAlign(Selection& aSel
           isEmptyTextNode))) {
       continue;
     }
 
     // If it's a list item, or a list inside a list, forget any "current" div,
     // and instead put divs inside the appropriate block (td, li, etc.)
     if (HTMLEditUtils::IsListItem(curNode) ||
         HTMLEditUtils::IsList(curNode)) {
-      rv = RemoveAlignment(GetAsDOMNode(curNode), aAlignType, true);
+      rv = RemoveAlignment(*curNode, aAlignType, true);
       NS_ENSURE_SUCCESS(rv, rv);
       if (useCSS) {
         htmlEditor->mCSSEditUtils->SetCSSEquivalentToHTMLStyle(
             curNode->AsElement(), nullptr, nsGkAtoms::align,
             &aAlignType, false);
         curDiv = nullptr;
         continue;
       }
@@ -8323,173 +8323,150 @@ HTMLEditRules::DidDeleteSelection(nsISel
   return NS_OK;
 }
 
 // Let's remove all alignment hints in the children of aNode; it can
 // be an ALIGN attribute (in case we just remove it) or a CENTER
 // element (here we have to remove the container and keep its
 // children). We break on tables and don't look at their children.
 nsresult
-HTMLEditRules::RemoveAlignment(nsIDOMNode* aNode,
+HTMLEditRules::RemoveAlignment(nsINode& aNode,
                                const nsAString& aAlignType,
                                bool aChildrenOnly)
 {
-  NS_ENSURE_TRUE(aNode, NS_ERROR_NULL_POINTER);
-
-  if (EditorBase::IsTextNode(aNode) || HTMLEditUtils::IsTable(aNode)) {
+  if (EditorBase::IsTextNode(&aNode) || HTMLEditUtils::IsTable(&aNode)) {
     return NS_OK;
   }
 
-  nsCOMPtr<nsIDOMNode> child = aNode,tmp;
+  nsCOMPtr<nsINode> child, tmp;
   if (aChildrenOnly) {
-    aNode->GetFirstChild(getter_AddRefs(child));
+    child = aNode.GetFirstChild();
+  } else {
+    child = &aNode;
   }
   NS_ENSURE_STATE(mHTMLEditor);
   bool useCSS = mHTMLEditor->IsCSSEnabled();
 
   while (child) {
     if (aChildrenOnly) {
       // get the next sibling right now because we could have to remove child
-      child->GetNextSibling(getter_AddRefs(tmp));
+      tmp = child->GetNextSibling();
     } else {
       tmp = nullptr;
     }
-    bool isBlock;
-    NS_ENSURE_STATE(mHTMLEditor);
-    nsresult rv = mHTMLEditor->NodeIsBlockStatic(child, &isBlock);
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    if (EditorBase::NodeIsType(child, nsGkAtoms::center)) {
+
+    if (child->IsHTMLElement(nsGkAtoms::center)) {
       // the current node is a CENTER element
       // first remove children's alignment
-      rv = RemoveAlignment(child, aAlignType, true);
+      nsresult rv = RemoveAlignment(*child, aAlignType, true);
       NS_ENSURE_SUCCESS(rv, rv);
 
       // we may have to insert BRs in first and last position of element's children
       // if the nodes before/after are not blocks and not BRs
-      rv = MakeSureElemStartsOrEndsOnCR(child);
+      rv = MakeSureElemStartsOrEndsOnCR(*child);
       NS_ENSURE_SUCCESS(rv, rv);
 
       // now remove the CENTER container
       NS_ENSURE_STATE(mHTMLEditor);
-      nsCOMPtr<Element> childAsElement = do_QueryInterface(child);
-      NS_ENSURE_STATE(childAsElement);
-      rv = mHTMLEditor->RemoveContainer(childAsElement);
+      rv = mHTMLEditor->RemoveContainer(child->AsElement());
       NS_ENSURE_SUCCESS(rv, rv);
-    } else if (isBlock || HTMLEditUtils::IsHR(child)) {
+    } else if (IsBlockNode(*child) || child->IsHTMLElement(nsGkAtoms::hr)) {
       // the current node is a block element
-      nsCOMPtr<Element> curElem = do_QueryInterface(child);
-      if (HTMLEditUtils::SupportsAlignAttr(child)) {
+      if (HTMLEditUtils::SupportsAlignAttr(*child)) {
         // remove the ALIGN attribute if this element can have it
         NS_ENSURE_STATE(mHTMLEditor);
-        rv = mHTMLEditor->RemoveAttribute(curElem, nsGkAtoms::align);
+        nsresult rv = mHTMLEditor->RemoveAttribute(child->AsElement(),
+                                                   nsGkAtoms::align);
         NS_ENSURE_SUCCESS(rv, rv);
       }
       if (useCSS) {
-        if (HTMLEditUtils::IsTable(child) || HTMLEditUtils::IsHR(child)) {
+        if (child->IsAnyOfHTMLElements(nsGkAtoms::table, nsGkAtoms::hr)) {
           NS_ENSURE_STATE(mHTMLEditor);
-          rv = mHTMLEditor->SetAttributeOrEquivalent(curElem,
-                                                     nsGkAtoms::align,
-                                                     aAlignType, false);
+          nsresult rv =
+            mHTMLEditor->SetAttributeOrEquivalent(child->AsElement(),
+                                                  nsGkAtoms::align,
+                                                  aAlignType, false);
           if (NS_WARN_IF(NS_FAILED(rv))) {
             return rv;
           }
         } else {
           nsAutoString dummyCssValue;
           NS_ENSURE_STATE(mHTMLEditor);
-          rv = mHTMLEditor->mCSSEditUtils->RemoveCSSInlineStyle(
-                                             child,
-                                             nsGkAtoms::textAlign,
-                                             dummyCssValue);
+          nsresult rv = mHTMLEditor->mCSSEditUtils->RemoveCSSInlineStyle(
+                                                      *child,
+                                                      nsGkAtoms::textAlign,
+                                                      dummyCssValue);
           if (NS_WARN_IF(NS_FAILED(rv))) {
             return rv;
           }
         }
       }
-      if (!HTMLEditUtils::IsTable(child)) {
+      if (!child->IsHTMLElement(nsGkAtoms::table)) {
         // unless this is a table, look at children
-        rv = RemoveAlignment(child, aAlignType, true);
+        nsresult rv = RemoveAlignment(*child, aAlignType, true);
         NS_ENSURE_SUCCESS(rv, rv);
       }
     }
     child = tmp;
   }
   return NS_OK;
 }
 
 // Let's insert a BR as first (resp. last) child of aNode if its
 // first (resp. last) child is not a block nor a BR, and if the
 // previous (resp. next) sibling is not a block nor a BR
 nsresult
-HTMLEditRules::MakeSureElemStartsOrEndsOnCR(nsIDOMNode* aNode,
+HTMLEditRules::MakeSureElemStartsOrEndsOnCR(nsINode& aNode,
                                             bool aStarts)
 {
-  nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
-  NS_ENSURE_TRUE(node, NS_ERROR_NULL_POINTER);
-
-  nsCOMPtr<nsIDOMNode> child;
+  nsCOMPtr<nsINode> child;
   if (aStarts) {
     NS_ENSURE_STATE(mHTMLEditor);
-    child = GetAsDOMNode(mHTMLEditor->GetFirstEditableChild(*node));
+    child = mHTMLEditor->GetFirstEditableChild(aNode);
   } else {
     NS_ENSURE_STATE(mHTMLEditor);
-    child = GetAsDOMNode(mHTMLEditor->GetLastEditableChild(*node));
+    child = mHTMLEditor->GetLastEditableChild(aNode);
   }
   NS_ENSURE_TRUE(child, NS_OK);
-  bool isChildBlock;
-  NS_ENSURE_STATE(mHTMLEditor);
-  nsresult rv = mHTMLEditor->NodeIsBlockStatic(child, &isChildBlock);
-  NS_ENSURE_SUCCESS(rv, rv);
   bool foundCR = false;
-  if (isChildBlock || TextEditUtils::IsBreak(child)) {
+  if (IsBlockNode(*child) || child->IsHTMLElement(nsGkAtoms::br)) {
     foundCR = true;
   } else {
-    nsCOMPtr<nsIDOMNode> sibling;
+    nsCOMPtr<nsINode> sibling;
     if (aStarts) {
       NS_ENSURE_STATE(mHTMLEditor);
-      rv = mHTMLEditor->GetPriorHTMLSibling(aNode, address_of(sibling));
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        return rv;
-      }
+      sibling = mHTMLEditor->GetPriorHTMLSibling(&aNode);
     } else {
       NS_ENSURE_STATE(mHTMLEditor);
-      rv = mHTMLEditor->GetNextHTMLSibling(aNode, address_of(sibling));
-      if (NS_WARN_IF(NS_FAILED(rv))) {
-        return rv;
-      }
+      sibling = mHTMLEditor->GetNextHTMLSibling(&aNode);
     }
     if (sibling) {
-      bool isBlock;
-      NS_ENSURE_STATE(mHTMLEditor);
-      rv = mHTMLEditor->NodeIsBlockStatic(sibling, &isBlock);
-      NS_ENSURE_SUCCESS(rv, rv);
-      if (isBlock || TextEditUtils::IsBreak(sibling)) {
+      if (IsBlockNode(*sibling) || sibling->IsHTMLElement(nsGkAtoms::br)) {
         foundCR = true;
       }
     } else {
       foundCR = true;
     }
   }
   if (!foundCR) {
     int32_t offset = 0;
     if (!aStarts) {
-      nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
-      NS_ENSURE_STATE(node);
-      offset = node->GetChildCount();
-    }
-    nsCOMPtr<nsIDOMNode> brNode;
+      offset = aNode.GetChildCount();
+    }
     NS_ENSURE_STATE(mHTMLEditor);
-    rv = mHTMLEditor->CreateBR(aNode, offset, address_of(brNode));
-    NS_ENSURE_SUCCESS(rv, rv);
+    RefPtr<Element> brNode = mHTMLEditor->CreateBR(&aNode, offset);
+    if (NS_WARN_IF(!brNode)) {
+      return NS_ERROR_FAILURE;
+    }
   }
   return NS_OK;
 }
 
 nsresult
-HTMLEditRules::MakeSureElemStartsOrEndsOnCR(nsIDOMNode* aNode)
+HTMLEditRules::MakeSureElemStartsOrEndsOnCR(nsINode& aNode)
 {
   nsresult rv = MakeSureElemStartsOrEndsOnCR(aNode, false);
   NS_ENSURE_SUCCESS(rv, rv);
   return MakeSureElemStartsOrEndsOnCR(aNode, true);
 }
 
 nsresult
 HTMLEditRules::AlignBlock(Element& aElement,
@@ -8499,29 +8476,29 @@ HTMLEditRules::AlignBlock(Element& aElem
   if (!IsBlockNode(aElement) && !aElement.IsHTMLElement(nsGkAtoms::hr)) {
     // We deal only with blocks; early way out
     return NS_OK;
   }
 
   NS_ENSURE_STATE(mHTMLEditor);
   RefPtr<HTMLEditor> htmlEditor(mHTMLEditor);
 
-  nsresult rv = RemoveAlignment(aElement.AsDOMNode(), aAlignType,
+  nsresult rv = RemoveAlignment(aElement, aAlignType,
                                 aContentsOnly == ContentsOnly::yes);
   NS_ENSURE_SUCCESS(rv, rv);
   if (htmlEditor->IsCSSEnabled()) {
     // Let's use CSS alignment; we use margin-left and margin-right for tables
     // and text-align for other block-level elements
     rv = htmlEditor->SetAttributeOrEquivalent(
                        &aElement, nsGkAtoms::align, aAlignType, false);
     NS_ENSURE_SUCCESS(rv, rv);
   } else {
     // HTML case; this code is supposed to be called ONLY if the element
     // supports the align attribute but we'll never know...
-    if (HTMLEditUtils::SupportsAlignAttr(aElement.AsDOMNode())) {
+    if (HTMLEditUtils::SupportsAlignAttr(aElement)) {
       rv = htmlEditor->SetAttribute(&aElement, nsGkAtoms::align, aAlignType);
       NS_ENSURE_SUCCESS(rv, rv);
     }
   }
   return NS_OK;
 }
 
 nsresult
--- a/editor/libeditor/HTMLEditRules.h
+++ b/editor/libeditor/HTMLEditRules.h
@@ -96,17 +96,17 @@ public:
                          nsresult aResult) override;
   NS_IMETHOD DocumentModified() override;
 
   nsresult GetListState(bool* aMixed, bool* aOL, bool* aUL, bool* aDL);
   nsresult GetListItemState(bool* aMixed, bool* aLI, bool* aDT, bool* aDD);
   nsresult GetIndentState(bool* aCanIndent, bool* aCanOutdent);
   nsresult GetAlignment(bool* aMixed, nsIHTMLEditor::EAlignment* aAlign);
   nsresult GetParagraphState(bool* aMixed, nsAString& outFormat);
-  nsresult MakeSureElemStartsOrEndsOnCR(nsIDOMNode* aNode);
+  nsresult MakeSureElemStartsOrEndsOnCR(nsINode& aNode);
 
   // nsIEditActionListener methods
 
   NS_IMETHOD WillCreateNode(const nsAString& aTag, nsIDOMNode* aParent,
                             int32_t aPosition) override;
   NS_IMETHOD DidCreateNode(const nsAString& aTag, nsIDOMNode* aNode,
                            nsIDOMNode* aParent, int32_t aPosition,
                            nsresult aResult) override;
@@ -398,19 +398,19 @@ protected:
   bool InDifferentTableElements(nsINode* aNode1, nsINode* aNode2);
   nsresult RemoveEmptyNodes();
   nsresult SelectionEndpointInNode(nsINode* aNode, bool* aResult);
   nsresult UpdateDocChangeRange(nsRange* aRange);
   nsresult ConfirmSelectionInBody();
   nsresult InsertMozBRIfNeeded(nsINode& aNode);
   bool IsEmptyInline(nsINode& aNode);
   bool ListIsEmptyLine(nsTArray<OwningNonNull<nsINode>>& arrayOfNodes);
-  nsresult RemoveAlignment(nsIDOMNode* aNode, const nsAString& aAlignType,
+  nsresult RemoveAlignment(nsINode& aNode, const nsAString& aAlignType,
                            bool aChildrenOnly);
-  nsresult MakeSureElemStartsOrEndsOnCR(nsIDOMNode* aNode, bool aStarts);
+  nsresult MakeSureElemStartsOrEndsOnCR(nsINode& aNode, bool aStarts);
   enum class ContentsOnly { no, yes };
   nsresult AlignBlock(Element& aElement,
                       const nsAString& aAlignType, ContentsOnly aContentsOnly);
   enum class Change { minus, plus };
   nsresult ChangeIndentation(Element& aElement, Change aChange);
   void DocumentModifiedWorker();
 
   /**
--- a/editor/libeditor/HTMLEditUtils.cpp
+++ b/editor/libeditor/HTMLEditUtils.cpp
@@ -126,25 +126,16 @@ HTMLEditUtils::IsHeader(nsIDOMNode* aNod
  */
 bool
 HTMLEditUtils::IsParagraph(nsIDOMNode* aNode)
 {
   return EditorBase::NodeIsType(aNode, nsGkAtoms::p);
 }
 
 /**
- * IsHR() returns true if aNode is an horizontal rule.
- */
-bool
-HTMLEditUtils::IsHR(nsIDOMNode* aNode)
-{
-  return EditorBase::NodeIsType(aNode, nsGkAtoms::hr);
-}
-
-/**
  * IsListItem() returns true if aNode is an html list item.
  */
 bool
 HTMLEditUtils::IsListItem(nsIDOMNode* aNode)
 {
   MOZ_ASSERT(aNode);
   nsCOMPtr<nsINode> node = do_QueryInterface(aNode);
   return node && IsListItem(node);
@@ -457,36 +448,34 @@ HTMLEditUtils::IsFormWidget(nsINode* aNo
                                     nsGkAtoms::output,
                                     nsGkAtoms::keygen,
                                     nsGkAtoms::progress,
                                     nsGkAtoms::meter,
                                     nsGkAtoms::input);
 }
 
 bool
-HTMLEditUtils::SupportsAlignAttr(nsIDOMNode* aNode)
+HTMLEditUtils::SupportsAlignAttr(nsINode& aNode)
 {
-  MOZ_ASSERT(aNode);
-  nsCOMPtr<nsIAtom> nodeAtom = EditorBase::GetTag(aNode);
-  return (nodeAtom == nsGkAtoms::hr)
-      || (nodeAtom == nsGkAtoms::table)
-      || (nodeAtom == nsGkAtoms::tbody)
-      || (nodeAtom == nsGkAtoms::tfoot)
-      || (nodeAtom == nsGkAtoms::thead)
-      || (nodeAtom == nsGkAtoms::tr)
-      || (nodeAtom == nsGkAtoms::td)
-      || (nodeAtom == nsGkAtoms::th)
-      || (nodeAtom == nsGkAtoms::div)
-      || (nodeAtom == nsGkAtoms::p)
-      || (nodeAtom == nsGkAtoms::h1)
-      || (nodeAtom == nsGkAtoms::h2)
-      || (nodeAtom == nsGkAtoms::h3)
-      || (nodeAtom == nsGkAtoms::h4)
-      || (nodeAtom == nsGkAtoms::h5)
-      || (nodeAtom == nsGkAtoms::h6);
+  return aNode.IsAnyOfHTMLElements(nsGkAtoms::hr,
+                                   nsGkAtoms::table,
+                                   nsGkAtoms::tbody,
+                                   nsGkAtoms::tfoot,
+                                   nsGkAtoms::thead,
+                                   nsGkAtoms::tr,
+                                   nsGkAtoms::td,
+                                   nsGkAtoms::th,
+                                   nsGkAtoms::div,
+                                   nsGkAtoms::p,
+                                   nsGkAtoms::h1,
+                                   nsGkAtoms::h2,
+                                   nsGkAtoms::h3,
+                                   nsGkAtoms::h4,
+                                   nsGkAtoms::h5,
+                                   nsGkAtoms::h6);
 }
 
 // We use bitmasks to test containment of elements. Elements are marked to be
 // in certain groups by setting the mGroup member of the nsElementInfo struct
 // to the corresponding GROUP_ values (OR'ed together). Similarly, elements are
 // marked to allow containment of certain groups by setting the
 // mCanContainGroups member of the nsElementInfo struct to the corresponding
 // GROUP_ values (OR'ed together).
--- a/editor/libeditor/HTMLEditUtils.h
+++ b/editor/libeditor/HTMLEditUtils.h
@@ -20,17 +20,16 @@ public:
   static bool IsInlineStyle(nsINode* aNode);
   static bool IsInlineStyle(nsIDOMNode *aNode);
   static bool IsFormatNode(nsINode* aNode);
   static bool IsFormatNode(nsIDOMNode* aNode);
   static bool IsNodeThatCanOutdent(nsIDOMNode* aNode);
   static bool IsHeader(nsINode& aNode);
   static bool IsHeader(nsIDOMNode* aNode);
   static bool IsParagraph(nsIDOMNode* aNode);
-  static bool IsHR(nsIDOMNode* aNode);
   static bool IsListItem(nsINode* aNode);
   static bool IsListItem(nsIDOMNode* aNode);
   static bool IsTable(nsIDOMNode* aNode);
   static bool IsTable(nsINode* aNode);
   static bool IsTableRow(nsIDOMNode* aNode);
   static bool IsTableElement(nsINode* aNode);
   static bool IsTableElement(nsIDOMNode* aNode);
   static bool IsTableElementButNotTable(nsINode* aNode);
@@ -53,17 +52,17 @@ public:
   static bool IsNamedAnchor(nsIDOMNode* aNode);
   static bool IsDiv(nsIDOMNode* aNode);
   static bool IsMozDiv(nsINode* aNode);
   static bool IsMozDiv(nsIDOMNode* aNode);
   static bool IsMailCite(nsINode* aNode);
   static bool IsMailCite(nsIDOMNode* aNode);
   static bool IsFormWidget(nsINode* aNode);
   static bool IsFormWidget(nsIDOMNode* aNode);
-  static bool SupportsAlignAttr(nsIDOMNode* aNode);
+  static bool SupportsAlignAttr(nsINode& aNode);
   static bool CanContain(int32_t aParent, int32_t aChild);
   static bool IsContainer(int32_t aTag);
 };
 
 } // namespace mozilla
 
 #endif // #ifndef HTMLEditUtils_h
 
--- a/gfx/thebes/gfxPrefs.h
+++ b/gfx/thebes/gfxPrefs.h
@@ -459,17 +459,17 @@ private:
   DECL_GFX_PREF(Live, "layers.acceleration.draw-fps.write-to-file", WriteFPSToFile, bool, false);
   DECL_GFX_PREF(Once, "layers.acceleration.force-enabled",     LayersAccelerationForceEnabledDoNotUseDirectly, bool, false);
   DECL_GFX_PREF(Live, "layers.advanced.border-layers",         LayersAllowBorderLayers, bool, false);
   DECL_GFX_PREF(Live, "layers.advanced.text-layers",           LayersAllowTextLayers, bool, false);
   DECL_GFX_PREF(Live, "layers.advanced.bullet-layers",         LayersAllowBulletLayers, bool, false);
   DECL_GFX_PREF(Live, "layers.advanced.caret-layers",          LayersAllowCaretLayers, bool, false);
   DECL_GFX_PREF(Live, "layers.advanced.boxshadow-outer-layers", LayersAllowOuterBoxShadow, bool, false);
   DECL_GFX_PREF(Live, "layers.advanced.outline-layers",        LayersAllowOutlineLayers, bool, false);
-  DECL_GFX_PREF(Once, "layers.allow-d3d9-fallback",            LayersAllowD3D9Fallback, bool, false);
+  DECL_GFX_PREF(Skip, "layers.allow-d3d9-fallback",            LayersAllowD3D9Fallback, bool, false);
   DECL_GFX_PREF(Once, "layers.amd-switchable-gfx.enabled",     LayersAMDSwitchableGfxEnabled, bool, false);
   DECL_GFX_PREF(Once, "layers.async-pan-zoom.enabled",         AsyncPanZoomEnabledDoNotUseDirectly, bool, true);
   DECL_GFX_PREF(Once, "layers.async-pan-zoom.separate-event-thread", AsyncPanZoomSeparateEventThread, bool, false);
   DECL_GFX_PREF(Live, "layers.bench.enabled",                  LayersBenchEnabled, bool, false);
   DECL_GFX_PREF(Once, "layers.bufferrotation.enabled",         BufferRotationEnabled, bool, true);
   DECL_GFX_PREF(Live, "layers.child-process-shutdown",         ChildProcessShutdown, bool, true);
 #ifdef MOZ_GFX_OPTIMIZE_MOBILE
   // If MOZ_GFX_OPTIMIZE_MOBILE is defined, we force component alpha off
@@ -510,17 +510,17 @@ private:
   DECL_GFX_PREF(Live, "layers.gpu-process.max_restarts",       GPUProcessMaxRestarts, int32_t, 0);
   DECL_GFX_PREF(Live, "layers.low-precision-buffer",           UseLowPrecisionBuffer, bool, false);
   DECL_GFX_PREF(Live, "layers.low-precision-opacity",          LowPrecisionOpacity, float, 1.0f);
   DECL_GFX_PREF(Live, "layers.low-precision-resolution",       LowPrecisionResolution, float, 0.25f);
   DECL_GFX_PREF(Live, "layers.max-active",                     MaxActiveLayers, int32_t, -1);
   DECL_GFX_PREF(Once, "layers.offmainthreadcomposition.force-disabled", LayersOffMainThreadCompositionForceDisabled, bool, false);
   DECL_GFX_PREF(Live, "layers.offmainthreadcomposition.frame-rate", LayersCompositionFrameRate, int32_t,-1);
   DECL_GFX_PREF(Live, "layers.orientation.sync.timeout",       OrientationSyncMillis, uint32_t, (uint32_t)0);
-  DECL_GFX_PREF(Once, "layers.prefer-d3d9",                    LayersPreferD3D9, bool, false);
+  DECL_GFX_PREF(Skip, "layers.prefer-d3d9",                    LayersPreferD3D9, bool, false);
   DECL_GFX_PREF(Once, "layers.prefer-opengl",                  LayersPreferOpenGL, bool, false);
   DECL_GFX_PREF(Live, "layers.progressive-paint",              ProgressivePaint, bool, false);
   DECL_GFX_PREF(Live, "layers.shared-buffer-provider.enabled", PersistentBufferProviderSharedEnabled, bool, false);
   DECL_GFX_PREF(Live, "layers.single-tile.enabled",            LayersSingleTileEnabled, bool, true);
   DECL_GFX_PREF(Once, "layers.stereo-video.enabled",           StereoVideoEnabled, bool, false);
 
   // We allow for configurable and rectangular tile size to avoid wasting memory on devices whose
   // screen size does not align nicely to the default tile size. Although layers can be any size,
--- a/js/src/doc/Debugger/Debugger.Source.md
+++ b/js/src/doc/Debugger/Debugger.Source.md
@@ -151,16 +151,18 @@ from its prototype:
 :   **If the instance refers to JavaScript source**, a string indicating how
     this source code was introduced into the system.  This accessor returns
     one of the following values:
 
     * `"eval"`, for code passed to `eval`.
 
     * `"Function"`, for code passed to the `Function` constructor.
 
+    * `"Function.prototype"`, for `Function.prototype` internally generated code.
+
     * `"Worker"`, for code loaded by calling the Web worker constructor—the
       worker's main script.
 
     * `"importScripts"`, for code by calling `importScripts` in a web worker.
 
     * `"eventHandler"`, for code assigned to DOM elements' event handler IDL
       attributes as a string.
 
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/bug1338914.js
@@ -0,0 +1,11 @@
+// In a debuggee, the Function.prototype script source has the introductionType
+// property set to "Function.prototype".
+
+var g = newGlobal();
+var dbg = new Debugger(g);
+var scripts = dbg.findScripts();
+assertEq(scripts.length > 0, true);
+var fpScripts = scripts.filter(s => s.source.introductionType == "Function.prototype");
+assertEq(fpScripts.length, 1);
+var source = fpScripts[0].source;
+assertEq(source.text, "() {\n}");
new file mode 100644
--- /dev/null
+++ b/js/src/jit-test/tests/debug/wasm-get-return.js
@@ -0,0 +1,62 @@
+// |jit-test| test-also-wasm-baseline
+// Tests that wasm frame opPop event can access function resumption value.
+
+load(libdir + "wasm.js");
+load(libdir + 'eqArrayHelper.js');
+
+function monitorFrameOnPopReturns(wast, expected) {
+    var values = [];
+    wasmRunWithDebugger(
+        wast,
+        undefined,
+        function ({dbg}) {
+            dbg.onEnterFrame = function (frame) {
+                if (frame.type != 'wasmcall') return;
+                frame.onPop = function (value) {
+                    values.push(value.return);
+                };
+            };
+        },
+        function ({error}) {
+            assertEq(error, undefined);
+        }
+    );
+    assertEqArray(values, expected);
+}
+
+monitorFrameOnPopReturns(
+  `(module (func (export "test")))`,
+  [undefined]);
+monitorFrameOnPopReturns(
+  `(module (func (export "test") (result i32) (i32.const 42)))`,
+  [42]);
+monitorFrameOnPopReturns(
+  `(module (func (export "test") (result f32) (f32.const 0.5)))`,
+  [0.5]);
+monitorFrameOnPopReturns(
+  `(module (func (export "test") (result f64) (f64.const -42.75)))`,
+  [-42.75]);
+monitorFrameOnPopReturns(
+  `(module (func (result i64) (i64.const 2)) (func (export "test") (call 0) (drop)))`,
+  [2, undefined]);
+
+// Checking if throwing frame has right resumption value.
+var throwCount = 0;
+wasmRunWithDebugger(
+    '(module (func (unreachable)) (func (export "test") (result i32) (call 0) (i32.const 1)))',
+    undefined,
+    function ({dbg, g}) {
+        dbg.onEnterFrame = function (frame) {
+            if (frame.type != 'wasmcall') return;
+            frame.onPop = function (value) {
+                if ('throw' in value)
+                    throwCount++;
+            };
+        };
+    },
+    function ({error}) {
+        assertEq(error != undefined, true);
+        assertEq(throwCount, 2);
+    }
+);
+
--- a/js/src/jsfun.cpp
+++ b/js/src/jsfun.cpp
@@ -849,18 +849,21 @@ CreateFunctionPrototype(JSContext* cx, J
     ScriptSource* ss = cx->new_<ScriptSource>();
     if (!ss)
         return nullptr;
     ScriptSourceHolder ssHolder(ss);
     if (!ss->setSource(cx, mozilla::Move(source), sourceLen))
         return nullptr;
 
     CompileOptions options(cx);
-    options.setNoScriptRval(true)
+    options.setIntroductionType("Function.prototype")
+           .setNoScriptRval(true)
            .setVersion(JSVERSION_DEFAULT);
+    if (!ss->initFromOptions(cx, options))
+        return nullptr;
     RootedScriptSource sourceObject(cx, ScriptSourceObject::create(cx, ss));
     if (!sourceObject || !ScriptSourceObject::initFromOptions(cx, sourceObject, options))
         return nullptr;
 
     RootedScript script(cx, JSScript::Create(cx,
                                              options,
                                              sourceObject,
                                              0,
--- a/js/src/vm/Stack-inl.h
+++ b/js/src/vm/Stack-inl.h
@@ -418,17 +418,17 @@ FrameIter::unaliasedForEachActual(JSCont
 }
 
 inline HandleValue
 AbstractFramePtr::returnValue() const
 {
     if (isInterpreterFrame())
         return asInterpreterFrame()->returnValue();
     if (isWasmDebugFrame())
-        return UndefinedHandleValue;
+        return asWasmDebugFrame()->returnValue();
     return asBaselineFrame()->returnValue();
 }
 
 inline void
 AbstractFramePtr::setReturnValue(const Value& rval) const
 {
     if (isInterpreterFrame()) {
         asInterpreterFrame()->setReturnValue(rval);
--- a/js/src/wasm/WasmCode.cpp
+++ b/js/src/wasm/WasmCode.cpp
@@ -466,17 +466,18 @@ Metadata::serializedSize() const
            SerializedPodVectorSize(customSections) +
            filename.serializedSize();
 }
 
 uint8_t*
 Metadata::serialize(uint8_t* cursor) const
 {
     MOZ_ASSERT(!debugEnabled && debugTrapFarJumpOffsets.empty() &&
-               debugFuncArgTypes.empty() && debugFuncToCodeRange.empty());
+               debugFuncArgTypes.empty() && debugFuncReturnTypes.empty() &&
+               debugFuncToCodeRange.empty());
     cursor = WriteBytes(cursor, &pod(), sizeof(pod()));
     cursor = SerializeVector(cursor, funcImports);
     cursor = SerializeVector(cursor, funcExports);
     cursor = SerializeVector(cursor, sigIds);
     cursor = SerializePodVector(cursor, globals);
     cursor = SerializePodVector(cursor, tables);
     cursor = SerializePodVector(cursor, memoryAccesses);
     cursor = SerializePodVector(cursor, memoryPatches);
@@ -507,16 +508,17 @@ Metadata::deserialize(const uint8_t* cur
     (cursor = DeserializePodVector(cursor, &callThunks)) &&
     (cursor = DeserializePodVector(cursor, &funcNames)) &&
     (cursor = DeserializePodVector(cursor, &customSections)) &&
     (cursor = filename.deserialize(cursor));
     debugEnabled = false;
     debugTrapFarJumpOffsets.clear();
     debugFuncToCodeRange.clear();
     debugFuncArgTypes.clear();
+    debugFuncReturnTypes.clear();
     return cursor;
 }
 
 size_t
 Metadata::sizeOfExcludingThis(MallocSizeOf mallocSizeOf) const
 {
     return SizeOfVectorExcludingThis(funcImports, mallocSizeOf) +
            SizeOfVectorExcludingThis(funcExports, mallocSizeOf) +
@@ -1188,16 +1190,23 @@ Code::debugGetLocalTypes(uint32_t funcIn
     // In wasm, the Code points to the function start via funcLineOrBytecode.
     MOZ_ASSERT(!metadata_->isAsmJS() && maybeBytecode_);
     size_t offsetInModule = range.funcLineOrBytecode();
     Decoder d(maybeBytecode_->begin() + offsetInModule,  maybeBytecode_->end(),
               offsetInModule, /* error = */ nullptr);
     return DecodeLocalEntries(d, metadata_->kind, locals);
 }
 
+ExprType
+Code::debugGetResultType(uint32_t funcIndex)
+{
+    MOZ_ASSERT(metadata_->debugEnabled);
+    return metadata_->debugFuncReturnTypes[funcIndex];
+}
+
 void
 Code::addSizeOfMisc(MallocSizeOf mallocSizeOf,
                     Metadata::SeenSet* seenMetadata,
                     ShareableBytes::SeenSet* seenBytes,
                     size_t* code,
                     size_t* data) const
 {
     *code += segment_->length();
--- a/js/src/wasm/WasmCode.h
+++ b/js/src/wasm/WasmCode.h
@@ -423,16 +423,17 @@ struct CustomSection
     CustomSection() = default;
     CustomSection(NameInBytecode name, uint32_t offset, uint32_t length)
       : name(name), offset(offset), length(length)
     {}
 };
 
 typedef Vector<CustomSection, 0, SystemAllocPolicy> CustomSectionVector;
 typedef Vector<ValTypeVector, 0, SystemAllocPolicy> FuncArgTypesVector;
+typedef Vector<ExprType, 0, SystemAllocPolicy> FuncReturnTypesVector;
 
 // Metadata holds all the data that is needed to describe compiled wasm code
 // at runtime (as opposed to data that is only used to statically link or
 // instantiate a module).
 //
 // Metadata is built incrementally by ModuleGenerator and then shared immutably
 // between modules.
 
@@ -474,16 +475,17 @@ struct Metadata : ShareableBase<Metadata
     CustomSectionVector   customSections;
     CacheableChars        filename;
 
     // Debug-enabled code is not serialized.
     bool                  debugEnabled;
     Uint32Vector          debugTrapFarJumpOffsets;
     Uint32Vector          debugFuncToCodeRange;
     FuncArgTypesVector    debugFuncArgTypes;
+    FuncReturnTypesVector debugFuncReturnTypes;
 
     bool usesMemory() const { return UsesMemory(memoryUsage); }
     bool hasSharedMemory() const { return memoryUsage == MemoryUsage::Shared; }
 
     const FuncExport& lookupFuncExport(uint32_t funcIndex) const;
 
     // AsmJSMetadata derives Metadata iff isAsmJS(). Mostly this distinction is
     // encapsulated within AsmJS.cpp, but the additional virtual functions allow
@@ -666,16 +668,17 @@ class Code
 
     bool stepModeEnabled(uint32_t funcIndex) const;
     bool incrementStepModeCount(JSContext* cx, uint32_t funcIndex);
     bool decrementStepModeCount(JSContext* cx, uint32_t funcIndex);
 
     // Stack inspection helpers.
 
     bool debugGetLocalTypes(uint32_t funcIndex, ValTypeVector* locals, size_t* argsLength);
+    ExprType debugGetResultType(uint32_t funcIndex);
 
     // about:memory reporting:
 
     void addSizeOfMisc(MallocSizeOf mallocSizeOf,
                        Metadata::SeenSet* seenMetadata,
                        ShareableBytes::SeenSet* seenBytes,
                        size_t* code,
                        size_t* data) const;
--- a/js/src/wasm/WasmDebugFrame.cpp
+++ b/js/src/wasm/WasmDebugFrame.cpp
@@ -60,16 +60,50 @@ DebugFrame::leaveFrame(JSContext* cx)
 {
    if (!observing_)
        return;
 
    instance()->code().adjustEnterAndLeaveFrameTrapsState(cx, /* enabled = */ false);
    observing_ = false;
 }
 
+void
+DebugFrame::clearReturnJSValue()
+{
+    hasCachedReturnJSValue_ = true;
+    cachedReturnJSValue_.setUndefined();
+}
+
+void
+DebugFrame::updateReturnJSValue()
+{
+    hasCachedReturnJSValue_ = true;
+    ExprType returnType = instance()->code().debugGetResultType(funcIndex());
+    switch (returnType) {
+      case ExprType::Void:
+          cachedReturnJSValue_.setUndefined();
+          break;
+      case ExprType::I32:
+          cachedReturnJSValue_.setInt32(resultI32_);
+          break;
+      case ExprType::I64:
+          // Just display as a Number; it's ok if we lose some precision
+          cachedReturnJSValue_.setDouble((double)resultI64_);
+          break;
+      case ExprType::F32:
+          cachedReturnJSValue_.setDouble(JS::CanonicalizeNaN(resultF32_));
+          break;
+      case ExprType::F64:
+          cachedReturnJSValue_.setDouble(JS::CanonicalizeNaN(resultF64_));
+          break;
+      default:
+          MOZ_CRASH("result type");
+    }
+}
+
 bool
 DebugFrame::getLocal(uint32_t localIndex, MutableHandleValue vp)
 {
     ValTypeVector locals;
     size_t argsLength;
     if (!instance()->code().debugGetLocalTypes(funcIndex(), &locals, &argsLength))
         return false;
 
@@ -84,19 +118,19 @@ DebugFrame::getLocal(uint32_t localIndex
       case jit::MIRType::Int32:
           vp.set(Int32Value(*static_cast<int32_t*>(dataPtr)));
           break;
       case jit::MIRType::Int64:
           // Just display as a Number; it's ok if we lose some precision
           vp.set(NumberValue((double)*static_cast<int64_t*>(dataPtr)));
           break;
       case jit::MIRType::Float32:
-          vp.set(NumberValue(*static_cast<float*>(dataPtr)));
+          vp.set(NumberValue(JS::CanonicalizeNaN(*static_cast<float*>(dataPtr))));
           break;
       case jit::MIRType::Double:
-          vp.set(NumberValue(*static_cast<double*>(dataPtr)));
+          vp.set(NumberValue(JS::CanonicalizeNaN(*static_cast<double*>(dataPtr))));
           break;
       default:
           MOZ_CRASH("local type");
     }
     return true;
 }
 
--- a/js/src/wasm/WasmDebugFrame.h
+++ b/js/src/wasm/WasmDebugFrame.h
@@ -35,28 +35,31 @@ class DebugFrame
     union
     {
         int32_t resultI32_;
         int64_t resultI64_;
         float   resultF32_;
         double  resultF64_;
     };
 
+    js::Value   cachedReturnJSValue_;
+
     // The fields below are initialized by the baseline compiler.
     uint32_t    funcIndex_;
     uint32_t    reserved0_;
 
     union
     {
         struct
         {
             bool    observing_ : 1;
             bool    isDebuggee_ : 1;
             bool    prevUpToDate_ : 1;
             bool    hasCachedSavedFrame_ : 1;
+            bool    hasCachedReturnJSValue_ : 1;
         };
         void*   reserved1_;
     };
 
     TlsData*    tlsData_;
     Frame       frame_;
 
     explicit DebugFrame() {}
@@ -86,16 +89,23 @@ class DebugFrame
     inline void setPrevUpToDate() { prevUpToDate_ = true; }
     inline void unsetPrevUpToDate() { prevUpToDate_ = false; }
 
     inline bool hasCachedSavedFrame() const { return hasCachedSavedFrame_; }
     inline void setHasCachedSavedFrame() { hasCachedSavedFrame_ = true; }
 
     inline void* resultsPtr() { return &resultI32_; }
 
+    inline HandleValue returnValue() const {
+        MOZ_ASSERT(hasCachedReturnJSValue_);
+        return HandleValue::fromMarkedLocation(&cachedReturnJSValue_);
+    }
+    void updateReturnJSValue();
+    void clearReturnJSValue();
+
     bool getLocal(uint32_t localIndex, MutableHandleValue vp);
 
     static constexpr size_t offsetOfResults() { return offsetof(DebugFrame, resultI32_); }
     static constexpr size_t offsetOfFlagsWord() { return offsetof(DebugFrame, reserved1_); }
     static constexpr size_t offsetOfFuncIndex() { return offsetof(DebugFrame, funcIndex_); }
     static constexpr size_t offsetOfTlsData() { return offsetof(DebugFrame, tlsData_); }
     static constexpr size_t offsetOfFrame() { return offsetof(DebugFrame, frame_); }
 };
--- a/js/src/wasm/WasmGenerator.cpp
+++ b/js/src/wasm/WasmGenerator.cpp
@@ -199,19 +199,22 @@ ModuleGenerator::initWasm(const CompileA
         metadata_->startFuncIndex.emplace(*env_->startFuncIndex);
         if (!exportedFuncs_.put(*env_->startFuncIndex))
             return false;
     }
 
     if (metadata_->debugEnabled) {
         if (!debugFuncArgTypes_.resize(env_->funcSigs.length()))
             return false;
+        if (!debugFuncReturnTypes_.resize(env_->funcSigs.length()))
+            return false;
         for (size_t i = 0; i < debugFuncArgTypes_.length(); i++) {
             if (!debugFuncArgTypes_[i].appendAll(env_->funcSigs[i]->args()))
                 return false;
+            debugFuncReturnTypes_[i] = env_->funcSigs[i]->ret();
         }
     }
 
     return true;
 }
 
 bool
 ModuleGenerator::init(UniqueModuleEnvironment env, const CompileArgs& args,
@@ -1155,16 +1158,17 @@ ModuleGenerator::finish(const ShareableB
     metadata_->maxMemoryLength = env_->maxMemoryLength;
     metadata_->tables = Move(env_->tables);
     metadata_->globals = Move(env_->globals);
     metadata_->funcNames = Move(env_->funcNames);
     metadata_->customSections = Move(env_->customSections);
 
     // Additional debug information to copy.
     metadata_->debugFuncArgTypes = Move(debugFuncArgTypes_);
+    metadata_->debugFuncReturnTypes = Move(debugFuncReturnTypes_);
     if (metadata_->debugEnabled)
         metadata_->debugFuncToCodeRange = Move(funcToCodeRange_);
 
     // These Vectors can get large and the excess capacity can be significant,
     // so realloc them down to size.
     metadata_->memoryAccesses.podResizeToFit();
     metadata_->memoryPatches.podResizeToFit();
     metadata_->boundsChecks.podResizeToFit();
--- a/js/src/wasm/WasmGenerator.h
+++ b/js/src/wasm/WasmGenerator.h
@@ -232,16 +232,17 @@ class MOZ_STACK_CLASS ModuleGenerator
     jit::TempAllocator              masmAlloc_;
     jit::MacroAssembler             masm_;
     Uint32Vector                    funcToCodeRange_;
     Uint32Set                       exportedFuncs_;
     uint32_t                        lastPatchedCallsite_;
     uint32_t                        startOfUnpatchedCallsites_;
     Uint32Vector                    debugTrapFarJumps_;
     FuncArgTypesVector              debugFuncArgTypes_;
+    FuncReturnTypesVector           debugFuncReturnTypes_;
 
     // Parallel compilation
     bool                            parallel_;
     uint32_t                        outstanding_;
     CompileTaskVector               tasks_;
     CompileTaskPtrVector            freeTasks_;
     UniqueFuncBytesVector           freeFuncBytes_;
     CompileTask*                    currentTask_;
--- a/js/src/wasm/WasmTypes.cpp
+++ b/js/src/wasm/WasmTypes.cpp
@@ -124,16 +124,17 @@ WasmHandleDebugTrap()
             // TODO properly handle JSTRAP_RETURN and resume wasm execution.
             JS_ReportErrorASCII(cx, "Unexpected resumption value from onEnterFrame");
             return false;
         }
         return status == JSTRAP_CONTINUE;
     }
     if (site->kind() == CallSite::LeaveFrame) {
         DebugFrame* frame = iter.debugFrame();
+        frame->updateReturnJSValue();
         bool ok = Debugger::onLeaveFrame(cx, frame, nullptr, true);
         frame->leaveFrame(cx);
         return ok;
     }
 
     DebugFrame* frame = iter.debugFrame();
     Code& code = iter.instance()->code();
     MOZ_ASSERT(code.hasBreakpointTrapAtOffset(site->lineOrBytecode()));
@@ -169,16 +170,17 @@ WasmHandleThrow()
     MOZ_ASSERT(activation);
     JSContext* cx = activation->cx();
 
     for (FrameIterator iter(activation, FrameIterator::Unwind::True); !iter.done(); ++iter) {
         if (!iter.debugEnabled())
             continue;
 
         DebugFrame* frame = iter.debugFrame();
+        frame->clearReturnJSValue();
 
         // Assume JSTRAP_ERROR status if no exception is pending --
         // no onExceptionUnwind handlers must be fired.
         if (cx->isExceptionPending()) {
             JSTrapStatus status = Debugger::onExceptionUnwind(cx, frame);
             if (status == JSTRAP_RETURN) {
                 // Unexpected trap return -- raising error since throw recovery
                 // is not yet implemented in the wasm baseline.
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -3967,21 +3967,21 @@ struct BoxToRect : public nsLayoutUtils:
       r = nsLayoutUtils::TransformFrameRectToAncestor(outer, r, mRelativeTo);
     } else {
       r += outer->GetOffsetTo(mRelativeTo);
     }
     mCallback->AddRect(r);
   }
 };
 
-struct BoxToRectAndText : public BoxToRect {
-  mozilla::dom::DOMStringList* mTextList;
+struct MOZ_RAII BoxToRectAndText : public BoxToRect {
+  Sequence<nsString>* mTextList;
 
   BoxToRectAndText(nsIFrame* aRelativeTo, nsLayoutUtils::RectCallback* aCallback,
-                   mozilla::dom::DOMStringList* aTextList, uint32_t aFlags)
+                   Sequence<nsString>* aTextList, uint32_t aFlags)
     : BoxToRect(aRelativeTo, aCallback, aFlags), mTextList(aTextList) {}
 
   static void AccumulateText(nsIFrame* aFrame, nsAString& aResult) {
     MOZ_ASSERT(aFrame);
 
     // Get all the text in aFrame and child frames, while respecting
     // the content offsets in each of the nsTextFrames.
     if (aFrame->GetType() == nsGkAtoms::textFrame) {
@@ -4004,35 +4004,36 @@ struct BoxToRectAndText : public BoxToRe
          child = child->GetNextSibling()) {
       AccumulateText(child, aResult);
     }
   }
 
   virtual void AddBox(nsIFrame* aFrame) override {
     BoxToRect::AddBox(aFrame);
     if (mTextList) {
-      nsAutoString textForFrame;
-      AccumulateText(aFrame, textForFrame);
-      mTextList->Add(textForFrame);
+      nsString* textForFrame = mTextList->AppendElement(fallible);
+      if (textForFrame) {
+        AccumulateText(aFrame, *textForFrame);
+      }
     }
   }
 };
 
 void
 nsLayoutUtils::GetAllInFlowRects(nsIFrame* aFrame, nsIFrame* aRelativeTo,
                                  RectCallback* aCallback, uint32_t aFlags)
 {
   BoxToRect converter(aRelativeTo, aCallback, aFlags);
   GetAllInFlowBoxes(aFrame, &converter);
 }
 
 void
 nsLayoutUtils::GetAllInFlowRectsAndTexts(nsIFrame* aFrame, nsIFrame* aRelativeTo,
                                          RectCallback* aCallback,
-                                         mozilla::dom::DOMStringList* aTextList,
+                                         Sequence<nsString>* aTextList,
                                          uint32_t aFlags)
 {
   BoxToRectAndText converter(aRelativeTo, aCallback, aTextList, aFlags);
   GetAllInFlowBoxes(aFrame, &converter);
 }
 
 nsLayoutUtils::RectAccumulator::RectAccumulator() : mSeenFirstRect(false) {}
 
--- a/layout/base/nsLayoutUtils.h
+++ b/layout/base/nsLayoutUtils.h
@@ -1208,17 +1208,17 @@ public:
    * or RECTS_USE_MARGIN_BOX, the corresponding type of box is used.
    * Otherwise (by default), the border box is used.
    */
   static void GetAllInFlowRects(nsIFrame* aFrame, nsIFrame* aRelativeTo,
                                 RectCallback* aCallback, uint32_t aFlags = 0);
 
   static void GetAllInFlowRectsAndTexts(nsIFrame* aFrame, nsIFrame* aRelativeTo,
                                         RectCallback* aCallback,
-                                        mozilla::dom::DOMStringList* aTextList,
+                                        mozilla::dom::Sequence<nsString>* aTextList,
                                         uint32_t aFlags = 0);
 
   /**
    * Computes the union of all rects returned by GetAllInFlowRects. If
    * the union is empty, returns the first rect.
    * If aFlags includes RECTS_ACCOUNT_FOR_TRANSFORMS, then when converting
    * the boxes into aRelativeTo coordinates, transforms (including CSS
    * and SVG transforms) are taken into account.
--- a/layout/style/CSSStyleSheet.cpp
+++ b/layout/style/CSSStyleSheet.cpp
@@ -130,24 +130,16 @@ namespace mozilla {
 CSSStyleSheetInner::CSSStyleSheetInner(CORSMode aCORSMode,
                                        ReferrerPolicy aReferrerPolicy,
                                        const SRIMetadata& aIntegrity)
   : StyleSheetInfo(aCORSMode, aReferrerPolicy, aIntegrity)
 {
   MOZ_COUNT_CTOR(CSSStyleSheetInner);
 }
 
-static bool SetStyleSheetReference(css::Rule* aRule, void* aSheet)
-{
-  if (aRule) {
-    aRule->SetStyleSheet(static_cast<CSSStyleSheet*>(aSheet));
-  }
-  return true;
-}
-
 struct ChildSheetListBuilder {
   RefPtr<StyleSheet>* sheetSlot;
   StyleSheet* parent;
 
   void SetParentLinks(StyleSheet* aSheet) {
     aSheet->mParent = parent;
     aSheet->SetAssociatedDocument(parent->mDocument,
                                   parent->mDocumentAssociationMode);
@@ -160,51 +152,49 @@ struct ChildSheetListBuilder {
       child->mParent = aPrimarySheet;
       child->SetAssociatedDocument(aPrimarySheet->mDocument,
                                    aPrimarySheet->mDocumentAssociationMode);
     }
   }
 };
 
 bool
-CSSStyleSheet::RebuildChildList(css::Rule* aRule, void* aBuilder)
+CSSStyleSheet::RebuildChildList(css::Rule* aRule,
+                                ChildSheetListBuilder* aBuilder)
 {
   int32_t type = aRule->GetType();
   if (type < css::Rule::IMPORT_RULE) {
     // Keep going till we get to the import rules.
     return true;
   }
 
   if (type != css::Rule::IMPORT_RULE) {
     // We're past all the import rules; stop the enumeration.
     return false;
   }
 
-  ChildSheetListBuilder* builder =
-    static_cast<ChildSheetListBuilder*>(aBuilder);
-
   // XXXbz We really need to decomtaminate all this stuff.  Is there a reason
   // that I can't just QI to ImportRule and get a CSSStyleSheet
   // directly from it?
   nsCOMPtr<nsIDOMCSSImportRule> importRule(do_QueryInterface(aRule));
   NS_ASSERTION(importRule, "GetType lied");
 
   nsCOMPtr<nsIDOMCSSStyleSheet> childSheet;
   importRule->GetStyleSheet(getter_AddRefs(childSheet));
 
   // Have to do this QI to be safe, since XPConnect can fake
   // nsIDOMCSSStyleSheets
   RefPtr<CSSStyleSheet> sheet = do_QueryObject(childSheet);
   if (!sheet) {
     return true;
   }
 
-  (*builder->sheetSlot) = sheet;
-  builder->SetParentLinks(*builder->sheetSlot);
-  builder->sheetSlot = &(*builder->sheetSlot)->mNext;
+  (*aBuilder->sheetSlot) = sheet;
+  aBuilder->SetParentLinks(*aBuilder->sheetSlot);
+  aBuilder->sheetSlot = &(*aBuilder->sheetSlot)->mNext;
   return true;
 }
 
 size_t
 CSSStyleSheet::SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const
 {
   size_t n = StyleSheet::SizeOfIncludingThis(aMallocSizeOf);
   const CSSStyleSheet* s = this;
@@ -233,42 +223,54 @@ CSSStyleSheet::SizeOfIncludingThis(Mallo
   return n;
 }
 
 CSSStyleSheetInner::CSSStyleSheetInner(CSSStyleSheetInner& aCopy,
                                        CSSStyleSheet* aPrimarySheet)
   : StyleSheetInfo(aCopy, aPrimarySheet)
 {
   MOZ_COUNT_CTOR(CSSStyleSheetInner);
-  aCopy.mOrderedRules.EnumerateForwards(css::GroupRule::CloneRuleInto, &mOrderedRules);
-  mOrderedRules.EnumerateForwards(SetStyleSheetReference, aPrimarySheet);
+  for (css::Rule* rule : aCopy.mOrderedRules) {
+    RefPtr<css::Rule> clone = rule->Clone();
+    mOrderedRules.AppendObject(clone);
+    clone->SetStyleSheet(aPrimarySheet);
+  }
 
   ChildSheetListBuilder builder = { &mFirstChild, aPrimarySheet };
-  mOrderedRules.EnumerateForwards(CSSStyleSheet::RebuildChildList, &builder);
+  for (css::Rule* rule : mOrderedRules) {
+    if (!CSSStyleSheet::RebuildChildList(rule, &builder)) {
+      break;
+    }
+  }
 
   RebuildNameSpaces();
 }
 
 CSSStyleSheetInner::~CSSStyleSheetInner()
 {
   MOZ_COUNT_DTOR(CSSStyleSheetInner);
-  mOrderedRules.EnumerateForwards(SetStyleSheetReference, nullptr);
+  for (css::Rule* rule : mOrderedRules) {
+    rule->SetStyleSheet(nullptr);
+  }
 }
 
 CSSStyleSheetInner*
 CSSStyleSheetInner::CloneFor(CSSStyleSheet* aPrimarySheet)
 {
   return new CSSStyleSheetInner(*this, aPrimarySheet);
 }
 
 void
 CSSStyleSheetInner::RemoveSheet(StyleSheet* aSheet)
 {
-  if ((aSheet == mSheets.ElementAt(0)) && (mSheets.Length() > 1)) {
-    mOrderedRules.EnumerateForwards(SetStyleSheetReference, mSheets[1]);
+  if (aSheet == mSheets.ElementAt(0) && mSheets.Length() > 1) {
+    StyleSheet* sheet = mSheets[1];
+    for (css::Rule* rule : mOrderedRules) {
+      rule->SetStyleSheet(sheet);
+    }
 
     ChildSheetListBuilder::ReparentChildList(mSheets[1], mFirstChild);
   }
 
   // Don't do anything after this call, because superclass implementation
   // may delete this.
   StyleSheetInfo::RemoveSheet(aSheet);
 }
@@ -281,36 +283,32 @@ AddNamespaceRuleToMap(css::Rule* aRule, 
   RefPtr<css::NameSpaceRule> nameSpaceRule = do_QueryObject(aRule);
 
   nsAutoString  urlSpec;
   nameSpaceRule->GetURLSpec(urlSpec);
 
   aMap->AddPrefix(nameSpaceRule->GetPrefix(), urlSpec);
 }
 
-static bool
-CreateNameSpace(css::Rule* aRule, void* aNameSpacePtr)
-{
-  int32_t type = aRule->GetType();
-  if (css::Rule::NAMESPACE_RULE == type) {
-    AddNamespaceRuleToMap(aRule,
-                          static_cast<nsXMLNameSpaceMap*>(aNameSpacePtr));
-    return true;
-  }
-  // stop if not namespace, import or charset because namespace can't follow
-  // anything else
-  return (css::Rule::CHARSET_RULE == type || css::Rule::IMPORT_RULE == type);
-}
-
 void 
 CSSStyleSheetInner::RebuildNameSpaces()
 {
   // Just nuke our existing namespace map, if any
   if (NS_SUCCEEDED(CreateNamespaceMap())) {
-    mOrderedRules.EnumerateForwards(CreateNameSpace, mNameSpaceMap);
+    for (css::Rule* rule : mOrderedRules) {
+      switch (rule->GetType()) {
+        case css::Rule::NAMESPACE_RULE:
+          AddNamespaceRuleToMap(rule, mNameSpaceMap);
+          continue;
+        case css::Rule::CHARSET_RULE:
+        case css::Rule::IMPORT_RULE:
+          continue;
+      }
+      break;
+    }
   }
 }
 
 nsresult
 CSSStyleSheetInner::CreateNamespaceMap()
 {
   mNameSpaceMap = nsXMLNameSpaceMap::Create(false);
   NS_ENSURE_TRUE(mNameSpaceMap, NS_ERROR_OUT_OF_MEMORY);
@@ -432,17 +430,19 @@ void
 CSSStyleSheet::UnlinkInner()
 {
   // We can only have a cycle through our inner if we have a unique inner,
   // because otherwise there are no JS wrappers for anything in the inner.
   if (mInner->mSheets.Length() != 1) {
     return;
   }
 
-  Inner()->mOrderedRules.EnumerateForwards(SetStyleSheetReference, nullptr);
+  for (css::Rule* rule : Inner()->mOrderedRules) {
+    rule->SetStyleSheet(nullptr);
+  }
   Inner()->mOrderedRules.Clear();
 
   StyleSheet::UnlinkInner();
 }
 
 void
 CSSStyleSheet::TraverseInner(nsCycleCollectionTraversalCallback &cb)
 {
--- a/layout/style/CSSStyleSheet.h
+++ b/layout/style/CSSStyleSheet.h
@@ -33,16 +33,17 @@ class nsCSSRuleProcessor;
 class nsIURI;
 class nsMediaQueryResultCacheKey;
 class nsStyleSet;
 class nsPresContext;
 class nsXMLNameSpaceMap;
 
 namespace mozilla {
 class CSSStyleSheet;
+struct ChildSheetListBuilder;
 
 namespace css {
 class GroupRule;
 } // namespace css
 namespace dom {
 class CSSRuleList;
 } // namespace dom
 
@@ -159,17 +160,18 @@ public:
                             nsMediaQueryResultCacheKey& aKey) const;
 
   nsresult ReparseSheet(const nsAString& aInput);
 
   void SetInRuleProcessorCache() { mInRuleProcessorCache = true; }
 
   // Function used as a callback to rebuild our inner's child sheet
   // list after we clone a unique inner for ourselves.
-  static bool RebuildChildList(css::Rule* aRule, void* aBuilder);
+  static bool RebuildChildList(css::Rule* aRule,
+                               ChildSheetListBuilder* aBuilder);
 
   size_t SizeOfIncludingThis(MallocSizeOf aMallocSizeOf) const override;
 
   dom::Element* GetScopeElement() const { return mScopeElement; }
   void SetScopeElement(dom::Element* aScopeElement)
   {
     mScopeElement = aScopeElement;
   }
--- a/layout/style/GroupRule.h
+++ b/layout/style/GroupRule.h
@@ -53,17 +53,17 @@ public:
   virtual void SetStyleSheet(StyleSheet* aSheet) override;
 
 public:
   void AppendStyleRule(Rule* aRule);
 
   int32_t StyleRuleCount() const { return mRules.Count(); }
   Rule* GetStyleRuleAt(int32_t aIndex) const;
 
-  typedef IncrementalClearCOMRuleArray::nsCOMArrayEnumFunc RuleEnumFunc;
+  typedef bool (*RuleEnumFunc)(Rule* aElement, void* aData);
   bool EnumerateRulesForwards(RuleEnumFunc aFunc, void * aData) const;
 
   /*
    * The next three methods should never be called unless you have first
    * called WillDirty() on the parent stylesheet.  After they are
    * called, DidDirty() needs to be called on the sheet.
    */
   nsresult DeleteStyleRuleAt(uint32_t aIndex);
@@ -71,24 +71,16 @@ public:
 
   virtual bool UseForPresentation(nsPresContext* aPresContext,
                                     nsMediaQueryResultCacheKey& aKey) = 0;
 
   // non-virtual -- it is only called by subclasses
   size_t SizeOfExcludingThis(mozilla::MallocSizeOf aMallocSizeOf) const;
   virtual size_t SizeOfIncludingThis(mozilla::MallocSizeOf aMallocSizeOf) const override = 0;
 
-  static bool
-  CloneRuleInto(Rule* aRule, void* aArray)
-  {
-    RefPtr<Rule> clone = aRule->Clone();
-    static_cast<IncrementalClearCOMRuleArray*>(aArray)->AppendObject(clone);
-    return true;
-  }
-
   // WebIDL API
   dom::CSSRuleList* CssRules();
   uint32_t InsertRule(const nsAString& aRule, uint32_t aIndex,
                       ErrorResult& aRv);
   void DeleteRule(uint32_t aIndex, ErrorResult& aRv);
 
 protected:
   // to help implement nsIDOMCSSRule
--- a/layout/style/nsCSSRuleProcessor.cpp
+++ b/layout/style/nsCSSRuleProcessor.cpp
@@ -3668,19 +3668,21 @@ nsCSSRuleProcessor::CascadeSheet(CSSStyl
 
     StyleSheet* child = aSheet->GetFirstChild();
     while (child) {
       CascadeSheet(child->AsGecko(), aData);
 
       child = child->mNext;
     }
 
-    if (!aSheet->Inner()->mOrderedRules.EnumerateForwards(CascadeRuleEnumFunc,
-                                                         aData))
-      return false;
+    for (css::Rule* rule : aSheet->Inner()->mOrderedRules) {
+      if (!CascadeRuleEnumFunc(rule, aData)) {
+        return false;
+      }
+    }
   }
   return true;
 }
 
 static int CompareWeightData(const void* aArg1, const void* aArg2,
                              void* closure)
 {
   const PerWeightData* arg1 = static_cast<const PerWeightData*>(aArg1);
--- a/layout/style/nsCSSRules.cpp
+++ b/layout/style/nsCSSRules.cpp
@@ -426,35 +426,32 @@ ImportRule::WrapObject(JSContext* aCx,
   return CSSImportRuleBinding::Wrap(aCx, this, aGivenProto);
 }
 
 GroupRule::GroupRule(uint32_t aLineNumber, uint32_t aColumnNumber)
   : Rule(aLineNumber, aColumnNumber)
 {
 }
 
-static bool
-SetParentRuleReference(Rule* aRule, void* aParentRule)
-{
-  GroupRule* parentRule = static_cast<GroupRule*>(aParentRule);
-  aRule->SetParentRule(parentRule);
-  return true;
-}
-
 GroupRule::GroupRule(const GroupRule& aCopy)
   : Rule(aCopy)
 {
-  const_cast<GroupRule&>(aCopy).mRules.EnumerateForwards(GroupRule::CloneRuleInto, &mRules);
-  mRules.EnumerateForwards(SetParentRuleReference, this);
+  for (const Rule* rule : aCopy.mRules) {
+    RefPtr<Rule> clone = rule->Clone();
+    mRules.AppendObject(clone);
+    clone->SetParentRule(this);
+  }
 }
 
 GroupRule::~GroupRule()
 {
   MOZ_ASSERT(!mSheet, "SetStyleSheet should have been called");
-  mRules.EnumerateForwards(SetParentRuleReference, nullptr);
+  for (Rule* rule : mRules) {
+    rule->SetParentRule(nullptr);
+  }
   if (mRuleCollection) {
     mRuleCollection->DropReference();
   }
 }
 
 NS_IMPL_ADDREF_INHERITED(GroupRule, Rule)
 NS_IMPL_RELEASE_INHERITED(GroupRule, Rule)
 
@@ -463,35 +460,32 @@ NS_INTERFACE_MAP_END_INHERITING(Rule)
 
 bool
 GroupRule::IsCCLeaf() const
 {
   // Let's not worry for now about sorting out whether we're a leaf or not.
   return false;
 }
 
-static bool
-SetStyleSheetReference(Rule* aRule, void* aSheet)
-{
-  aRule->SetStyleSheet(reinterpret_cast<StyleSheet*>(aSheet));
-  return true;
-}
-
 NS_IMPL_CYCLE_COLLECTION_CLASS(GroupRule)
 
 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(GroupRule, Rule)
-  tmp->mRules.EnumerateForwards(SetParentRuleReference, nullptr);
+  for (Rule* rule : tmp->mRules) {
+    rule->SetParentRule(nullptr);
+  }
   // If tmp does not have a stylesheet, neither do its descendants.  In that
   // case, don't try to null out their stylesheet, to avoid O(N^2) behavior in
   // depth of group rule nesting.  But if tmp _does_ have a stylesheet (which
   // can happen if it gets unlinked earlier than its owning stylesheet), then we
   // need to null out the stylesheet pointer on descendants now, before we clear
   // tmp->mRules.
   if (tmp->GetStyleSheet()) {
-    tmp->mRules.EnumerateForwards(SetStyleSheetReference, nullptr);
+    for (Rule* rule : tmp->mRules) {
+      rule->SetStyleSheet(nullptr);
+    }
   }
   tmp->mRules.Clear();
   if (tmp->mRuleCollection) {
     tmp->mRuleCollection->DropReference();
     tmp->mRuleCollection = nullptr;
   }
 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 
@@ -509,17 +503,19 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 /* virtual */ void
 GroupRule::SetStyleSheet(StyleSheet* aSheet)
 {
   // Don't set the sheet on the kids if it's already the same as the sheet we
   // already have.  This is needed to avoid O(N^2) behavior in group nesting
   // depth when seting the sheet to null during unlink, if we happen to unlin in
   // order from most nested rule up to least nested rule.
   if (aSheet != GetStyleSheet()) {
-    mRules.EnumerateForwards(SetStyleSheetReference, aSheet);
+    for (Rule* rule : mRules) {
+      rule->SetStyleSheet(aSheet);
+    }
     Rule::SetStyleSheet(aSheet);
   }
 }
 
 #ifdef DEBUG
 /* virtual */ void
 GroupRule::List(FILE* out, int32_t aIndent) const
 {
@@ -545,18 +541,22 @@ Rule*
 GroupRule::GetStyleRuleAt(int32_t aIndex) const
 {
   return mRules.SafeObjectAt(aIndex);
 }
 
 bool
 GroupRule::EnumerateRulesForwards(RuleEnumFunc aFunc, void * aData) const
 {
-  return
-    const_cast<GroupRule*>(this)->mRules.EnumerateForwards(aFunc, aData);
+  for (const Rule* rule : mRules) {
+    if (!aFunc(const_cast<Rule*>(rule), aData)) {
+      return false;
+    }
+  }
+  return true;
 }
 
 /*
  * The next two methods (DeleteStyleRuleAt and InsertStyleRuleAt)
  * should never be called unless you have first called WillDirty() on
  * the parents stylesheet.  After they are called, DidDirty() needs to
  * be called on the sheet
  */
--- a/layout/style/nsStyleStruct.h
+++ b/layout/style/nsStyleStruct.h
@@ -4073,9 +4073,26 @@ public:
 
 STATIC_ASSERT_TYPE_LAYOUTS_MATCH(nsTArray<nsStyleImageLayers::Layer>,
                                  nsTArray_Simple<nsStyleImageLayers::Layer>);
 STATIC_ASSERT_TYPE_LAYOUTS_MATCH(nsTArray<mozilla::StyleTransition>,
                                  nsTArray_Simple<mozilla::StyleTransition>);
 STATIC_ASSERT_TYPE_LAYOUTS_MATCH(nsTArray<mozilla::StyleAnimation>,
                                  nsTArray_Simple<mozilla::StyleAnimation>);
 
+/**
+ * <div rustbindgen replaces="nsCOMArray"></div>
+ *
+ * mozilla::ArrayIterator doesn't work well with bindgen.
+ */
+template<typename T>
+class nsCOMArray_Simple {
+  nsTArray<nsISupports*> mBuffer;
+};
+
+STATIC_ASSERT_TYPE_LAYOUTS_MATCH(nsCOMArray<nsIContent>,
+                                 nsCOMArray_Simple<nsIContent>);
+STATIC_ASSERT_TYPE_LAYOUTS_MATCH(nsCOMArray<nsINode>,
+                                 nsCOMArray_Simple<nsINode>);
+STATIC_ASSERT_TYPE_LAYOUTS_MATCH(nsCOMArray<imgIContainer>,
+                                 nsCOMArray_Simple<imgIContainer>);
+
 #endif /* nsStyleStruct_h___ */
--- a/mobile/android/installer/package-manifest.in
+++ b/mobile/android/installer/package-manifest.in
@@ -393,16 +393,21 @@
 @BINPATH@/components/PresentationDataChannelSessionTransport.js
 @BINPATH@/components/PresentationDataChannelSessionTransport.manifest
 @BINPATH@/components/AndroidCastDeviceProvider.manifest
 @BINPATH@/components/AndroidCastDeviceProvider.js
 
 @BINPATH@/components/TVSimulatorService.js
 @BINPATH@/components/TVSimulatorService.manifest
 
+#ifdef ENABLE_INTL_API
+@BINPATH@/components/mozIntl.manifest
+@BINPATH@/components/mozIntl.js
+#endif
+
 ; Modules
 @BINPATH@/modules/*
 
 ; Safe Browsing
 @BINPATH@/components/nsURLClassifier.manifest
 @BINPATH@/components/nsUrlClassifierHashCompleter.js
 @BINPATH@/components/nsUrlClassifierListManager.js
 @BINPATH@/components/nsUrlClassifierLib.js
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -4709,19 +4709,16 @@ pref("gfx.xrender.enabled",false);
 // Whether to disable the automatic detection and use of direct2d.
 pref("gfx.direct2d.disabled", false);
 
 // Whether to attempt to enable Direct2D regardless of automatic detection or
 // blacklisting
 pref("gfx.direct2d.force-enabled", false);
 
 pref("layers.prefer-opengl", false);
-pref("layers.prefer-d3d9", false);
-// Disable for now due to bug 1304360
-pref("layers.allow-d3d9-fallback", false);
 #endif
 
 // Copy-on-write canvas
 pref("layers.shared-buffer-provider.enabled", true);
 
 #ifdef XP_WIN
 pref("layers.shared-buffer-provider.enabled", false);
 #endif
@@ -5570,21 +5567,17 @@ pref("dom.maxHardwareConcurrency", 16);
 pref("osfile.reset_worker_delay", 30000);
 #endif
 
 #if !defined(MOZ_WIDGET_GONK) && !defined(MOZ_WIDGET_ANDROID)
 pref("dom.webkitBlink.dirPicker.enabled", true);
 pref("dom.webkitBlink.filesystem.enabled", true);
 #endif
 
-#ifdef NIGHTLY_BUILD
 pref("media.block-autoplay-until-in-foreground", true);
-#else
-pref("media.block-autoplay-until-in-foreground", false);
-#endif
 
 #ifdef MOZ_STYLO
 // Is the Servo-backed style system enabled?
 pref("layout.css.servo.enabled", true);
 #endif
 
 // HSTS Priming
 // If a request is mixed-content, send an HSTS priming request to attempt to
--- a/moz.configure
+++ b/moz.configure
@@ -152,22 +152,28 @@ option('--build-backends', nargs='+', de
        choices=build_backends_choices, help='Build backends to generate')
 
 @depends('--build-backends')
 def build_backends(backends):
     return backends
 
 set_config('BUILD_BACKENDS', build_backends)
 
+option('--disable-gtest-in-build',
+       help='Force disable building the gtest libxul during the build.',
+       when='--enable-compile-environment')
+
 # Determine whether to build the gtest xul. This happens in automation
 # on Desktop platforms with the exception of Windows PGO, where linking
 # xul-gtest.dll takes too long.
-@depends('MOZ_PGO', build_project, target, 'MOZ_AUTOMATION',
+@depends('MOZ_PGO', build_project, target, 'MOZ_AUTOMATION', '--disable-gtest-in-build',
          when='--enable-compile-environment')
-def build_gtest(pgo, build_project, target, automation):
+def build_gtest(pgo, build_project, target, automation, enabled):
+    if not enabled:
+        return None
     if (automation and build_project == 'browser' and
         not (pgo and target.os == 'WINNT')):
         return True
 
 set_config('LINK_GTEST_DURING_COMPILE', build_gtest)
 
 # Awk detection
 # ==============================================================
--- a/old-configure.in
+++ b/old-configure.in
@@ -5307,17 +5307,21 @@ fi
 dnl win32 options
 AC_SUBST(WIN32_REDIST_DIR)
 AC_SUBST(WIN_UCRT_REDIST_DIR)
 
 dnl ========================================================
 dnl ICU Support
 dnl ========================================================
 
-_INTL_API=yes
+if test "$MOZ_WIDGET_TOOLKIT" != "android" -o -z "$RELEASE_OR_BETA"; then
+    _INTL_API=yes
+else
+    _INTL_API=no
+fi
 
 if test "$MOZ_WIDGET_TOOLKIT" = "cocoa"; then
     USE_ICU=1
 fi
 
 MOZ_CONFIG_ICU()
 
 dnl Echo the CFLAGS to remove extra whitespace.
--- a/security/manager/ssl/nsNSSIOLayer.cpp
+++ b/security/manager/ssl/nsNSSIOLayer.cpp
@@ -2454,16 +2454,18 @@ nsSSLIOLayerSetOptions(PRFileDesc* fd, b
     if (SECSuccess != SSL_SetDowngradeCheckVersion(fd, maxEnabledVersion)) {
       return NS_ERROR_FAILURE;
     }
   }
 
   if (range.max > SSL_LIBRARY_VERSION_TLS_1_2) {
     SSL_CipherPrefSet(fd, TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA, false);
     SSL_CipherPrefSet(fd, TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA, false);
+    SSL_CipherPrefSet(fd, TLS_DHE_RSA_WITH_AES_128_CBC_SHA, false);
+    SSL_CipherPrefSet(fd, TLS_DHE_RSA_WITH_AES_256_CBC_SHA, false);
   }
 
   // Include a modest set of named groups.
   const SSLNamedGroup namedGroups[] = {
     ssl_grp_ec_curve25519, ssl_grp_ec_secp256r1, ssl_grp_ec_secp384r1,
     ssl_grp_ec_secp521r1, ssl_grp_ffdhe_2048, ssl_grp_ffdhe_3072
   };
   if (SECSuccess != SSL_NamedGroupConfig(fd, namedGroups,
--- a/servo/Cargo.lock
+++ b/servo/Cargo.lock
@@ -278,31 +278,31 @@ dependencies = [
  "cssparser 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "gleam 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)",
  "ipc-channel 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "num-traits 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)",
  "offscreen_gl_context 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "servo_config 0.0.1",
- "webrender_traits 0.22.0 (git+https://github.com/servo/webrender)",
+ "webrender_traits 0.23.1 (git+https://github.com/servo/webrender)",
 ]
 
 [[package]]
 name = "canvas_traits"
 version = "0.0.1"
 dependencies = [
  "cssparser 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "heapsize 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "heapsize_derive 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "ipc-channel 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde 0.9.7 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_derive 0.9.7 (registry+https://github.com/rust-lang/crates.io-index)",
- "webrender_traits 0.22.0 (git+https://github.com/servo/webrender)",
+ "webrender_traits 0.23.1 (git+https://github.com/servo/webrender)",
 ]
 
 [[package]]
 name = "caseless"
 version = "0.1.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "regex 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -413,18 +413,18 @@ dependencies = [
  "net_traits 0.0.1",
  "profile_traits 0.0.1",
  "script_traits 0.0.1",
  "servo_config 0.0.1",
  "servo_geometry 0.0.1",
  "servo_url 0.0.1",
  "style_traits 0.0.1",
  "time 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)",
- "webrender 0.21.0 (git+https://github.com/servo/webrender)",
- "webrender_traits 0.22.0 (git+https://github.com/servo/webrender)",
+ "webrender 0.22.1 (git+https://github.com/servo/webrender)",
+ "webrender_traits 0.23.1 (git+https://github.com/servo/webrender)",
 ]
 
 [[package]]
 name = "constellation"
 version = "0.0.1"
 dependencies = [
  "backtrace 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "bluetooth_traits 0.0.1",
@@ -447,17 +447,17 @@ dependencies = [
  "script_traits 0.0.1",
  "serde 0.9.7 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_derive 0.9.7 (registry+https://github.com/rust-lang/crates.io-index)",
  "servo_config 0.0.1",
  "servo_rand 0.0.1",
  "servo_remutex 0.0.1",
  "servo_url 0.0.1",
  "style_traits 0.0.1",
- "webrender_traits 0.22.0 (git+https://github.com/servo/webrender)",
+ "webrender_traits 0.23.1 (git+https://github.com/servo/webrender)",
  "webvr_traits 0.0.1",
 ]
 
 [[package]]
 name = "content-blocker"
 version = "0.2.3"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
@@ -702,17 +702,17 @@ dependencies = [
  "msg 0.0.1",
  "net_traits 0.0.1",
  "objc 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "script_traits 0.0.1",
  "servo_config 0.0.1",
  "servo_geometry 0.0.1",
  "servo_url 0.0.1",
  "style_traits 0.0.1",
- "webrender_traits 0.22.0 (git+https://github.com/servo/webrender)",
+ "webrender_traits 0.23.1 (git+https://github.com/servo/webrender)",
  "x11 2.12.1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "encoding"
 version = "0.2.33"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
@@ -989,17 +989,17 @@ dependencies = [
  "servo_url 0.0.1",
  "simd 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "smallvec 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "style 0.0.1",
  "style_traits 0.0.1",
  "time 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)",
  "truetype 0.26.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "unicode-script 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "webrender_traits 0.22.0 (git+https://github.com/servo/webrender)",
+ "webrender_traits 0.23.1 (git+https://github.com/servo/webrender)",
  "xi-unicode 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "gfx_tests"
 version = "0.0.1"
 dependencies = [
  "gfx 0.0.1",
@@ -1068,17 +1068,17 @@ dependencies = [
  "script_traits 0.0.1",
  "servo-egl 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "servo-glutin 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "servo_config 0.0.1",
  "servo_geometry 0.0.1",
  "servo_url 0.0.1",
  "style_traits 0.0.1",
  "user32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "webrender_traits 0.22.0 (git+https://github.com/servo/webrender)",
+ "webrender_traits 0.23.1 (git+https://github.com/servo/webrender)",
  "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "x11 2.12.1 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "glx"
 version = "0.1.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1370,17 +1370,17 @@ dependencies = [
  "servo_config 0.0.1",
  "servo_geometry 0.0.1",
  "servo_url 0.0.1",
  "smallvec 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "style 0.0.1",
  "style_traits 0.0.1",
  "unicode-bidi 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)",
  "unicode-script 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
- "webrender_traits 0.22.0 (git+https://github.com/servo/webrender)",
+ "webrender_traits 0.23.1 (git+https://github.com/servo/webrender)",
 ]
 
 [[package]]
 name = "layout_tests"
 version = "0.0.1"
 dependencies = [
  "layout 0.0.1",
 ]
@@ -1410,31 +1410,31 @@ dependencies = [
  "script_traits 0.0.1",
  "selectors 0.18.0",
  "serde_derive 0.9.7 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_json 0.9.7 (registry+https://github.com/rust-lang/crates.io-index)",
  "servo_config 0.0.1",
  "servo_geometry 0.0.1",
  "servo_url 0.0.1",
  "style 0.0.1",
- "webrender_traits 0.22.0 (git+https://github.com/servo/webrender)",
+ "webrender_traits 0.23.1 (git+https://github.com/servo/webrender)",
 ]
 
 [[package]]
 name = "layout_traits"
 version = "0.0.1"
 dependencies = [
  "gfx 0.0.1",
  "ipc-channel 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "msg 0.0.1",
  "net_traits 0.0.1",
  "profile_traits 0.0.1",
  "script_traits 0.0.1",
  "servo_url 0.0.1",
- "webrender_traits 0.22.0 (git+https://github.com/servo/webrender)",
+ "webrender_traits 0.23.1 (git+https://github.com/servo/webrender)",
 ]
 
 [[package]]
 name = "lazy_static"
 version = "0.2.2"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
 [[package]]
@@ -1509,18 +1509,18 @@ dependencies = [
  "script_layout_interface 0.0.1",
  "script_traits 0.0.1",
  "servo_config 0.0.1",
  "servo_geometry 0.0.1",
  "servo_url 0.0.1",
  "style 0.0.1",
  "style_traits 0.0.1",
  "webdriver_server 0.0.1",
- "webrender 0.21.0 (git+https://github.com/servo/webrender)",
- "webrender_traits 0.22.0 (git+https://github.com/servo/webrender)",
+ "webrender 0.22.1 (git+https://github.com/servo/webrender)",
+ "webrender_traits 0.23.1 (git+https://github.com/servo/webrender)",
  "webvr 0.0.1",
  "webvr_traits 0.0.1",
 ]
 
 [[package]]
 name = "libz-sys"
 version = "1.0.13"
 source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1657,17 +1657,17 @@ name = "msg"
 version = "0.0.1"
 dependencies = [
  "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "cssparser 0.12.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "heapsize 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "heapsize_derive 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde 0.9.7 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_derive 0.9.7 (registry+https://github.com/rust-lang/crates.io-index)",
- "webrender_traits 0.22.0 (git+https://github.com/servo/webrender)",
+ "webrender_traits 0.23.1 (git+https://github.com/servo/webrender)",
 ]
 
 [[package]]
 name = "net"
 version = "0.0.1"
 dependencies = [
  "brotli 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)",
  "content-blocker 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1696,17 +1696,17 @@ dependencies = [
  "servo_config 0.0.1",
  "servo_url 0.0.1",
  "threadpool 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "time 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)",
  "tinyfiledialogs 2.5.9 (registry+https://github.com/rust-lang/crates.io-index)",
  "unicase 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "url 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "uuid 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "webrender_traits 0.22.0 (git+https://github.com/servo/webrender)",
+ "webrender_traits 0.23.1 (git+https://github.com/servo/webrender)",
 ]
 
 [[package]]
 name = "net2"
 version = "0.2.26"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 dependencies = [
  "cfg-if 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -1755,17 +1755,17 @@ dependencies = [
  "num-traits 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde 0.9.7 (registry+https://github.com/rust-lang/crates.io-index)",
  "serde_derive 0.9.7 (registry+https://github.com/rust-lang/crates.io-index)",
  "servo-websocket 0.18.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "servo_config 0.0.1",
  "servo_url 0.0.1",
  "url 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "uuid 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "webrender_traits 0.22.0 (git+https://github.com/servo/webrender)",
+ "webrender_traits 0.23.1 (git+https://github.com/servo/webrender)",
 ]
 
 [[package]]
 name = "net_traits_tests"
 version = "0.0.1"
 dependencies = [
  "net_traits 0.0.1",
 ]
@@ -2293,17 +2293,17 @@ dependencies = [
  "servo_url 0.0.1",
  "smallvec 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)",
  "style 0.0.1",
  "style_traits 0.0.1",
  "time 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)",
  "tinyfiledialogs 2.5.9 (registry+https://github.com/rust-lang/crates.io-index)",
  "url 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "uuid 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
- "webrender_traits 0.22.0 (git+https://github.com/servo/webrender)",
+ "webrender_traits 0.23.1 (git+https://github.com/servo/webrender)",
  "webvr 0.0.1",
  "webvr_traits 0.0.1",
  "xml5ever 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "script_layout_interface"
 version = "0.0.1"
@@ -3170,18 +3170,18 @@ dependencies = [
  "servo_url 0.0.1",
  "url 1.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "uuid 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "webdriver 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)",
 ]
 
 [[package]]
 name = "webrender"
-version = "0.21.0"
-source = "git+https://github.com/servo/webrender#040805f31650527311c0a0d58ccf9620b0f3eca9"
+version = "0.22.1"
+source = "git+https://github.com/servo/webrender#68f5b72fb3f871f6930c772c7e2705412512dde2"
 dependencies = [
  "app_units 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "bincode 1.0.0-alpha2 (registry+https://github.com/rust-lang/crates.io-index)",
  "bit-set 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "bitflags 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "core-graphics 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "core-text 4.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -3193,23 +3193,23 @@ dependencies = [
  "gleam 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)",
  "lazy_static 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "num-traits 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)",
  "offscreen_gl_context 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "thread_profiler 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
  "threadpool 1.3.2 (registry+https://github.com/rust-lang/crates.io-index)",
  "time 0.1.36 (registry+https://github.com/rust-lang/crates.io-index)",
- "webrender_traits 0.22.0 (git+https://github.com/servo/webrender)",
+ "webrender_traits 0.23.1 (git+https://github.com/servo/webrender)",
 ]
 
 [[package]]
 name = "webrender_traits"
-version = "0.22.0"
-source = "git+https://github.com/servo/webrender#040805f31650527311c0a0d58ccf9620b0f3eca9"
+version = "0.23.1"
+source = "git+https://github.com/servo/webrender#68f5b72fb3f871f6930c772c7e2705412512dde2"
 dependencies = [
  "app_units 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "byteorder 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "core-graphics 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "dwrote 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "euclid 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "gleam 0.2.32 (registry+https://github.com/rust-lang/crates.io-index)",
  "heapsize 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)",
@@ -3223,17 +3223,17 @@ dependencies = [
 name = "webvr"
 version = "0.0.1"
 dependencies = [
  "ipc-channel 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)",
  "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)",
  "msg 0.0.1",
  "script_traits 0.0.1",
  "servo_config 0.0.1",
- "webrender_traits 0.22.0 (git+https://github.com/servo/webrender)",
+ "webrender_traits 0.23.1 (git+https://github.com/servo/webrender)",
  "webvr_traits 0.0.1",
 ]
 
 [[package]]
 name = "winapi"
 version = "0.2.8"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 
@@ -3563,18 +3563,18 @@ dependencies = [
 "checksum user32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "4ef4711d107b21b410a3a974b1204d9accc8b10dad75d8324b5d755de1617d47"
 "checksum utf-8 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a9aee9ba280438b56d1ebc5329f2094f0ff457f811eeeff0b278d75aa99db400"
 "checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122"
 "checksum uuid 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7cfec50b0842181ba6e713151b72f4ec84a6a7e2c9c8a8a3ffc37bb1cd16b231"
 "checksum vec_map 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cac5efe5cb0fa14ec2f84f83c701c562ee63f6dcc680861b21d65c682adfb05f"
 "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d"
 "checksum walkdir 1.0.7 (registry+https://github.com/rust-lang/crates.io-index)" = "bb08f9e670fab86099470b97cd2b252d6527f0b3cc1401acdb595ffc9dd288ff"
 "checksum webdriver 0.20.0 (registry+https://github.com/rust-lang/crates.io-index)" = "cdc28802daddee94267a657ffeac2593a33881fb7a3a44fedd320b1319efcaf6"
-"checksum webrender 0.21.0 (git+https://github.com/servo/webrender)" = "<none>"
-"checksum webrender_traits 0.22.0 (git+https://github.com/servo/webrender)" = "<none>"
+"checksum webrender 0.22.1 (git+https://github.com/servo/webrender)" = "<none>"
+"checksum webrender_traits 0.23.1 (git+https://github.com/servo/webrender)" = "<none>"
 "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a"
 "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc"
 "checksum ws 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "04614a58714f3fd4a8b1da4bcae9f031c532d35988c3d39627619248113f8be8"
 "checksum ws2_32-sys 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "d59cefebd0c892fa2dd6de581e937301d8552cb44489cdff035c6187cb63fa5e"
 "checksum x11 2.12.1 (registry+https://github.com/rust-lang/crates.io-index)" = "124eb405bf0262a54e1a982d4ffe4cd1c24261bdb306e49996e2ce7d492284a8"
 "checksum x11-dl 2.12.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bf1f9986368c9bbdd8191a783a7ceb42e0c9c6d3348616c873f829b3288a139c"
 "checksum xdg 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "77b831a5ba77110f438f0ac5583aafeb087f70432998ba6b7dcb1d32185db453"
 "checksum xi-unicode 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "12ea8eda4b1eb72f02d148402e23832d56a33f55d8c1b2d5bcdde91d79d47cb1"
--- a/servo/components/gfx/display_list/mod.rs
+++ b/servo/components/gfx/display_list/mod.rs
@@ -177,27 +177,35 @@ impl DisplayList {
         // Convert the parent translated point into stacking context local transform space if the
         // stacking context isn't fixed.  If it's fixed, we need to use the client point anyway.
         debug_assert!(stacking_context.context_type == StackingContextType::Real);
         let is_fixed = stacking_context.scroll_policy == ScrollPolicy::Fixed;
         *translated_point = if is_fixed {
             *client_point
         } else {
             let point = *translated_point - stacking_context.bounds.origin;
-            let inv_transform = match stacking_context.transform.inverse() {
-                Some(transform) => transform,
+
+            match stacking_context.transform {
+                Some(transform) => {
+                    let inv_transform = match transform.inverse() {
+                        Some(transform) => transform,
+                        None => {
+                            // If a transform function causes the current transformation matrix of an object
+                            // to be non-invertible, the object and its content do not get displayed.
+                            return;
+                        }
+                    };
+                    let frac_point = inv_transform.transform_point(&Point2D::new(point.x.to_f32_px(),
+                                                                                 point.y.to_f32_px()));
+                    Point2D::new(Au::from_f32_px(frac_point.x), Au::from_f32_px(frac_point.y))
+                }
                 None => {
-                    // If a transform function causes the current transformation matrix of an object
-                    // to be non-invertible, the object and its content do not get displayed.
-                    return;
+                    point
                 }
-            };
-            let frac_point = inv_transform.transform_point(&Point2D::new(point.x.to_f32_px(),
-                                                                         point.y.to_f32_px()));
-            Point2D::new(Au::from_f32_px(frac_point.x), Au::from_f32_px(frac_point.y))
+            }
         };
     }
 
     #[inline]
     fn scroll_root<'a>(scroll_root: &ScrollRoot,
                        translated_point: &mut Point2D<Au>,
                        scroll_offsets: &ScrollOffsetMap) {
         // Adjust the translated point to account for the scroll offset if necessary.
@@ -355,20 +363,20 @@ pub struct StackingContext {
 
     /// CSS filters to be applied to this stacking context (including opacity).
     pub filters: filter::T,
 
     /// The blend mode with which this stacking context blends with its backdrop.
     pub blend_mode: mix_blend_mode::T,
 
     /// A transform to be applied to this stacking context.
-    pub transform: Matrix4D<f32>,
+    pub transform: Option<Matrix4D<f32>>,
 
     /// The perspective matrix to be applied to children.
-    pub perspective: Matrix4D<f32>,
+    pub perspective: Option<Matrix4D<f32>>,
 
     /// Whether this stacking context creates a new 3d rendering context.
     pub establishes_3d_context: bool,
 
     /// The scroll policy of this layer.
     pub scroll_policy: ScrollPolicy,
 
     /// The id of the parent scrolling area that contains this StackingContext.
@@ -380,18 +388,18 @@ impl StackingContext {
     #[inline]
     pub fn new(id: StackingContextId,
                context_type: StackingContextType,
                bounds: &Rect<Au>,
                overflow: &Rect<Au>,
                z_index: i32,
                filters: filter::T,
                blend_mode: mix_blend_mode::T,
-               transform: Matrix4D<f32>,
-               perspective: Matrix4D<f32>,
+               transform: Option<Matrix4D<f32>>,
+               perspective: Option<Matrix4D<f32>>,
                establishes_3d_context: bool,
                scroll_policy: ScrollPolicy,
                parent_scroll_id: ScrollRootId)
                -> StackingContext {
         StackingContext {
             id: id,
             context_type: context_type,
             bounds: *bounds,
@@ -411,18 +419,18 @@ impl StackingContext {
     pub fn root() -> StackingContext {
         StackingContext::new(StackingContextId::new(0),
                              StackingContextType::Real,
                              &Rect::zero(),
                              &Rect::zero(),
                              0,
                              filter::T::new(Vec::new()),
                              mix_blend_mode::T::normal,
-                             Matrix4D::identity(),
-                             Matrix4D::identity(),
+                             None,
+                             None,
                              true,
                              ScrollPolicy::Scrollable,
                              ScrollRootId::root())
     }
 
     pub fn to_display_list_items(self) -> (DisplayItem, DisplayItem) {
         let mut base_item = BaseDisplayItem::empty();
         base_item.stacking_context_id = self.id;
--- a/servo/components/layout/block.rs
+++ b/servo/components/layout/block.rs
@@ -26,17 +26,17 @@
 //!   http://dev.w3.org/csswg/css-sizing/
 
 #![deny(unsafe_code)]
 
 use app_units::{Au, MAX_AU};
 use context::LayoutContext;
 use display_list_builder::{BorderPaintingMode, DisplayListBuildState, FragmentDisplayListBuilding};
 use display_list_builder::BlockFlowDisplayListBuilding;
-use euclid::{Point2D, Rect, Size2D};
+use euclid::{Matrix4D, Point2D, Rect, Size2D};
 use floats::{ClearType, FloatKind, Floats, PlacementInfo};
 use flow::{self, BaseFlow, EarlyAbsolutePositionInfo, Flow, FlowClass, ForceNonfloatedFlag};
 use flow::{BLOCK_POSITION_IS_STATIC, CLEARS_LEFT, CLEARS_RIGHT};
 use flow::{CONTAINS_TEXT_OR_REPLACED_FRAGMENTS, INLINE_POSITION_IS_STATIC};
 use flow::{FragmentationContext, MARGINS_CANNOT_COLLAPSE, PreorderFlowTraversal};
 use flow::{ImmutableFlowUtils, LateAbsolutePositionInfo, MutableFlowUtils, OpaqueFlow};
 use flow::IS_ABSOLUTELY_POSITIONED;
 use flow_list::FlowList;
@@ -1806,16 +1806,17 @@ impl BlockFlow {
         self.base.clip = self.base.clip.translate(&-stacking_relative_border_box.origin);
 
         // Account for `transform`, if applicable.
         if self.fragment.style.get_box().transform.0.is_none() {
             return
         }
         let transform = match self.fragment
                                   .transform_matrix(&stacking_relative_border_box)
+                                  .unwrap_or(Matrix4D::identity())
                                   .inverse() {
             Some(transform) => transform,
             None => {
                 // Singular matrix. Ignore it.
                 return
             }
         };
 
--- a/servo/components/layout/flow.rs
+++ b/servo/components/layout/flow.rs
@@ -24,17 +24,17 @@
 //!   fragments/flows that are subject to inline layout and line breaking and structs to represent
 //!   line breaks and mapping to CSS boxes, for the purpose of handling `getClientRects()` and
 //!   similar methods.
 
 use app_units::Au;
 use block::{BlockFlow, FormattingContextType};
 use context::LayoutContext;
 use display_list_builder::DisplayListBuildState;
-use euclid::{Point2D, Size2D};
+use euclid::{Matrix4D, Point2D, Size2D};
 use flex::FlexFlow;
 use floats::{Floats, SpeculatedFloatPlacement};
 use flow_list::{FlowList, MutFlowListIterator};
 use flow_ref::{FlowRef, WeakFlowRef};
 use fragment::{Fragment, FragmentBorderBoxIterator, Overflow};
 use gfx::display_list::ClippingRegion;
 use gfx_traits::{ScrollRootId, StackingContextId};
 use gfx_traits::print_tree::PrintTree;
@@ -267,17 +267,21 @@ pub trait Flow: fmt::Debug + Sync + Send
         if !self.as_block().fragment.establishes_stacking_context() ||
            self.as_block().fragment.style.get_box().transform.0.is_none() {
             overflow.translate(&position.origin);
             return overflow;
         }
 
         // TODO: Take into account 3d transforms, even though it's a fairly
         // uncommon case.
-        let transform_2d = self.as_block().fragment.transform_matrix(&position).to_2d();
+        let transform_2d = self.as_block()
+                               .fragment
+                               .transform_matrix(&position)
+                               .unwrap_or(Matrix4D::identity())
+                               .to_2d();
         let transformed_overflow = Overflow {
             paint: f32_rect_to_au_rect(transform_2d.transform_rect(
                                        &au_rect_to_f32_rect(overflow.paint))),
             scroll: f32_rect_to_au_rect(transform_2d.transform_rect(
                                        &au_rect_to_f32_rect(overflow.scroll))),
         };
 
         // TODO: We are taking the union of the overflow and transformed overflow here, which
--- a/servo/components/layout/fragment.rs
+++ b/servo/components/layout/fragment.rs
@@ -2868,23 +2868,23 @@ impl Fragment {
             SpecificFragmentInfo::ScannedText(_) |
             SpecificFragmentInfo::TruncatedFragment(_) |
             SpecificFragmentInfo::Svg(_) |
             SpecificFragmentInfo::UnscannedText(_) => true
         }
     }
 
     /// Returns the 4D matrix representing this fragment's transform.
-    pub fn transform_matrix(&self, stacking_relative_border_box: &Rect<Au>) -> Matrix4D<f32> {
-        let mut transform = Matrix4D::identity();
+    pub fn transform_matrix(&self, stacking_relative_border_box: &Rect<Au>) -> Option<Matrix4D<f32>> {
         let operations = match self.style.get_box().transform.0 {
-            None => return transform,
+            None => return None,
             Some(ref operations) => operations,
         };
 
+        let mut transform = Matrix4D::identity();
         let transform_origin = &self.style.get_box().transform_origin;
         let transform_origin_x = model::specified(transform_origin.horizontal,
                                                   stacking_relative_border_box.size
                                                                               .width).to_f32_px();
         let transform_origin_y = model::specified(transform_origin.vertical,
                                                   stacking_relative_border_box.size
                                                                               .height).to_f32_px();
         let transform_origin_z = transform_origin.depth.to_f32_px();
@@ -2923,21 +2923,21 @@ impl Fragment {
                     Matrix4D::create_skew(Radians::new(theta_x.radians()),
                                           Radians::new(theta_y.radians()))
                 }
             };
 
             transform = transform.pre_mul(&matrix);
         }
 
-        pre_transform.pre_mul(&transform).pre_mul(&post_transform)
+        Some(pre_transform.pre_mul(&transform).pre_mul(&post_transform))
     }
 
     /// Returns the 4D matrix representing this fragment's perspective.
-    pub fn perspective_matrix(&self, stacking_relative_border_box: &Rect<Au>) -> Matrix4D<f32> {
+    pub fn perspective_matrix(&self, stacking_relative_border_box: &Rect<Au>) -> Option<Matrix4D<f32>> {
         match self.style().get_box().perspective {
             Either::First(length) => {
                 let perspective_origin = self.style().get_box().perspective_origin;
                 let perspective_origin =
                     Point2D::new(model::specified(perspective_origin.horizontal,
                                                   stacking_relative_border_box.size.width).to_f32_px(),
                                  model::specified(perspective_origin.vertical,
                                                   stacking_relative_border_box.size.height).to_f32_px());
@@ -2946,20 +2946,20 @@ impl Fragment {
                                                                  perspective_origin.y,
                                                                  0.0);
                 let post_transform = Matrix4D::create_translation(-perspective_origin.x,
                                                                   -perspective_origin.y,
                                                                   0.0);
 
                 let perspective_matrix = create_perspective_matrix(length);
 
-                pre_transform.pre_mul(&perspective_matrix).pre_mul(&post_transform)
+                Some(pre_transform.pre_mul(&perspective_matrix).pre_mul(&post_transform))
             }
             Either::Second(values::None_) => {
-                Matrix4D::identity()
+                None
             }
         }
     }
 }
 
 impl fmt::Debug for Fragment {
     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
         let border_padding_string = if !self.border_padding.is_zero() {
--- a/servo/components/layout/lib.rs
+++ b/servo/components/layout/lib.rs
@@ -68,17 +68,17 @@ pub mod flow_ref;
 mod fragment;
 mod generated_content;
 pub mod incremental;
 mod inline;
 mod linked_list;
 mod list_item;
 mod model;
 mod multicol;
-mod opaque_node;
+pub mod opaque_node;
 pub mod parallel;
 mod persistent_list;
 pub mod query;
 pub mod sequential;
 mod table;
 mod table_caption;
 mod table_cell;
 mod table_colgroup;
--- a/servo/components/layout/query.rs
+++ b/servo/components/layout/query.rs
@@ -89,16 +89,19 @@ pub struct LayoutThreadData {
     /// Scroll offsets of stacking contexts. This will only be populated if WebRender is in use.
     pub stacking_context_scroll_offsets: ScrollOffsetMap,
 
     /// Index in a text fragment. We need this do determine the insertion point.
     pub text_index_response: TextIndexResponse,
 
     /// A list of images requests that need to be initiated.
     pub pending_images: Vec<PendingImage>,
+
+    /// A queued response for the list of nodes at a given point.
+    pub nodes_from_point_response: Vec<UntrustedNodeAddress>,
 }
 
 pub struct LayoutRPCImpl(pub Arc<Mutex<LayoutThreadData>>);
 
 // https://drafts.csswg.org/cssom-view/#overflow-directions
 fn overflow_direction(writing_mode: &WritingMode) -> OverflowDirection {
     match (writing_mode.block_flow_direction(), writing_mode.inline_base_direction()) {
         (BlockFlowDirection::TopToBottom, InlineBaseDirection::LeftToRight) |
@@ -139,43 +142,20 @@ impl LayoutRPC for LayoutRPCImpl {
             };
             rw_data.constellation_chan.send(ConstellationMsg::SetCursor(cursor)).unwrap();
         }
         HitTestResponse {
             node_address: result.map(|dim| dim.node.to_untrusted_node_address()),
         }
     }
 
-    fn nodes_from_point(&self,
-                        page_point: Point2D<f32>,
-                        client_point: Point2D<f32>) -> Vec<UntrustedNodeAddress> {
-        let page_point = Point2D::new(Au::from_f32_px(page_point.x),
-                                      Au::from_f32_px(page_point.y));
-        let client_point = Point2D::new(Au::from_f32_px(client_point.x),
-                                        Au::from_f32_px(client_point.y));
-
-        let nodes_from_point_list = {
-            let &LayoutRPCImpl(ref rw_data) = self;
-            let rw_data = rw_data.lock().unwrap();
-            let result = match rw_data.display_list {
-                None => panic!("Tried to hit test without a DisplayList"),
-                Some(ref display_list) => {
-                    display_list.hit_test(&page_point,
-                                          &client_point,
-                                          &rw_data.stacking_context_scroll_offsets)
-                }
-            };
-
-            result
-        };
-
-        nodes_from_point_list.iter()
-           .rev()
-           .map(|metadata| metadata.node.to_untrusted_node_address())
-           .collect()
+    fn nodes_from_point_response(&self) -> Vec<UntrustedNodeAddress> {
+        let &LayoutRPCImpl(ref rw_data) = self;
+        let rw_data = rw_data.lock().unwrap();
+        rw_data.nodes_from_point_response.clone()
     }
 
     fn node_geometry(&self) -> NodeGeometryResponse {
         let &LayoutRPCImpl(ref rw_data) = self;
         let rw_data = rw_data.lock().unwrap();
         NodeGeometryResponse {
             client_rect: rw_data.client_rect_response
         }
@@ -188,18 +168,18 @@ impl LayoutRPC for LayoutRPCImpl {
     fn node_scroll_area(&self) -> NodeGeometryResponse {
         NodeGeometryResponse {
             client_rect: self.0.lock().unwrap().scroll_area_response
         }
     }
 
     fn node_scroll_root_id(&self) -> NodeScrollRootIdResponse {
         NodeScrollRootIdResponse(self.0.lock()
-                                        .unwrap().scroll_root_id_response
-                                        .expect("scroll_root_id is not correctly fetched"))
+                                       .unwrap().scroll_root_id_response
+                                       .expect("scroll_root_id is not correctly fetched"))
     }
 
     /// Retrieves the resolved value for a CSS style property.
     fn resolved_style(&self) -> ResolvedStyleResponse {
         let &LayoutRPCImpl(ref rw_data) = self;
         let rw_data = rw_data.lock().unwrap();
         ResolvedStyleResponse(rw_data.resolved_style_response.clone())
     }
--- a/servo/components/layout/webrender_helpers.rs
+++ b/servo/components/layout/webrender_helpers.rs
@@ -376,22 +376,29 @@ impl WebRenderDisplayItemConverter for D
             DisplayItem::PushStackingContext(ref item) => {
                 let stacking_context = &item.stacking_context;
                 debug_assert!(stacking_context.context_type == StackingContextType::Real);
 
                 let clip = builder.new_clip_region(&stacking_context.overflow.to_rectf(),
                                                    vec![],
                                                    None);
 
+                let transform = stacking_context.transform.map(|transform| {
+                    LayoutTransform::from_untyped(&transform).into()
+                });
+                let perspective = stacking_context.perspective.map(|perspective| {
+                    LayoutTransform::from_untyped(&perspective)
+                });
+
                 builder.push_stacking_context(stacking_context.scroll_policy,
                                               stacking_context.bounds.to_rectf(),
                                               clip,
                                               stacking_context.z_index,
-                                              LayoutTransform::from_untyped(&stacking_context.transform).into(),
-                                              LayoutTransform::from_untyped(&stacking_context.perspective),
+                                              transform,
+                                              perspective,
                                               stacking_context.blend_mode.to_blend_mode(),
                                               stacking_context.filters.to_filter_ops());
             }
             DisplayItem::PopStackingContext(_) => builder.pop_stacking_context(),
             DisplayItem::PushScrollRoot(ref item) => {
                 let clip = builder.new_clip_region(&item.scroll_root.clip.to_rectf(),
                                                    vec![],
                                                    None);
--- a/servo/components/layout_thread/lib.rs
+++ b/servo/components/layout_thread/lib.rs
@@ -58,16 +58,17 @@ use layout::animation;
 use layout::construct::ConstructionResult;
 use layout::context::LayoutContext;
 use layout::context::heap_size_of_persistent_local_context;
 use layout::display_list_builder::ToGfxColor;
 use layout::flow::{self, Flow, ImmutableFlowUtils, MutableFlowUtils, MutableOwnedFlowUtils};
 use layout::flow_ref::FlowRef;
 use layout::incremental::{LayoutDamageComputation, REFLOW_ENTIRE_DOCUMENT};
 use layout::layout_debug;
+use layout::opaque_node::OpaqueNodeMethods;
 use layout::parallel;
 use layout::query::{LayoutRPCImpl, LayoutThreadData, process_content_box_request, process_content_boxes_request};
 use layout::query::{process_margin_style_query, process_node_overflow_request, process_resolved_style_request};
 use layout::query::{process_node_geometry_request, process_node_scroll_area_request};
 use layout::query::{process_node_scroll_root_id_request, process_offset_parent_query};
 use layout::sequential;
 use layout::traversal::{ComputeAbsolutePositions, RecalcStyleAndConstructFlows};
 use layout::webrender_helpers::WebRenderDisplayListConverter;
@@ -454,16 +455,17 @@ impl LayoutThread {
                     scroll_area_response: Rect::zero(),
                     overflow_response: NodeOverflowResponse(None),
                     resolved_style_response: String::new(),
                     offset_parent_response: OffsetParentResponse::empty(),
                     margin_style_response: MarginStyleResponse::empty(),
                     stacking_context_scroll_offsets: HashMap::new(),
                     text_index_response: TextIndexResponse(None),
                     pending_images: vec![],
+                    nodes_from_point_response: vec![],
                 })),
             error_reporter: CSSErrorReporter {
                 pipelineid: id,
                 script_chan: Arc::new(Mutex::new(script_chan)),
             },
             webrender_image_cache:
                 Arc::new(RwLock::new(HashMap::with_hasher(Default::default()))),
             timer:
@@ -489,29 +491,25 @@ impl LayoutThread {
         while self.handle_request(&mut rw_data) {
             // Loop indefinitely.
         }
     }
 
     // Create a layout context for use in building display lists, hit testing, &c.
     fn build_layout_context(&self,
                             rw_data: &LayoutThreadData,
-                            screen_size_changed: bool,
-                            goal: ReflowGoal,
                             request_images: bool)
                             -> LayoutContext {
         let thread_local_style_context_creation_data =
             ThreadLocalStyleContextCreationInfo::new(self.new_animations_sender.clone());
 
         LayoutContext {
             style_context: SharedStyleContext {
                 viewport_size: self.viewport_size.clone(),
-                screen_size_changed: screen_size_changed,
                 stylist: rw_data.stylist.clone(),
-                goal: goal,
                 running_animations: self.running_animations.clone(),
                 expired_animations: self.expired_animations.clone(),
                 error_reporter: self.error_reporter.clone(),
                 local_context_creation_data: Mutex::new(thread_local_style_context_creation_data),
                 timer: self.timer.clone(),
                 quirks_mode: self.quirks_mode.unwrap(),
                 // FIXME(bz): This isn't really right, but it's no more wrong
                 // than what we used to do.  See
@@ -972,16 +970,19 @@ impl LayoutThread {
                         rw_data.content_box_response = None;
                     },
                     ReflowQueryType::ContentBoxesQuery(_) => {
                         rw_data.content_boxes_response = Vec::new();
                     },
                     ReflowQueryType::HitTestQuery(..) => {
                         rw_data.hit_test_response = (None, false);
                     },
+                    ReflowQueryType::NodesFromPoint(..) => {
+                        rw_data.nodes_from_point_response = Vec::new();
+                    },
                     ReflowQueryType::NodeGeometryQuery(_) => {
                         rw_data.client_rect_response = Rect::zero();
                     },
                     ReflowQueryType::NodeScrollGeometryQuery(_) => {
                         rw_data.scroll_area_response = Rect::zero();
                     },
                     ReflowQueryType::NodeOverflowQuery(_) => {
                         rw_data.overflow_response = NodeOverflowResponse(None);
@@ -1025,19 +1026,16 @@ impl LayoutThread {
             rw_data.stylist.viewport_constraints().map_or(current_screen_size, |constraints| {
                 debug!("Viewport constraints: {:?}", constraints);
 
                 // other rules are evaluated against the actual viewport
                 Size2D::new(Au::from_f32_px(constraints.size.width),
                             Au::from_f32_px(constraints.size.height))
             });
 
-        // Handle conditions where the entire flow tree is invalid.
-        let mut needs_dirtying = false;
-
         let viewport_size_changed = self.viewport_size != old_viewport_size;
         if viewport_size_changed {
             if let Some(constraints) = rw_data.stylist.viewport_constraints() {
                 // let the constellation know about the viewport constraints
                 rw_data.constellation_chan
                        .send(ConstellationMsg::ViewportConstrained(self.id, constraints.clone()))
                        .unwrap();
             }
@@ -1061,19 +1059,19 @@ impl LayoutThread {
                     } else {
                         next = iter.next();
                     }
                 }
             }
         }
 
         // If the entire flow tree is invalid, then it will be reflowed anyhow.
-        needs_dirtying |= Arc::get_mut(&mut rw_data.stylist).unwrap().update(&data.document_stylesheets,
-                                                                             Some(&*UA_STYLESHEETS),
-                                                                             data.stylesheets_changed);
+        let needs_dirtying = Arc::get_mut(&mut rw_data.stylist).unwrap().update(&data.document_stylesheets,
+                                                                                 Some(&*UA_STYLESHEETS),
+                                                                                 data.stylesheets_changed);
         let needs_reflow = viewport_size_changed && !needs_dirtying;
         if needs_dirtying {
             if let Some(mut d) = element.mutate_data() {
                 if d.has_styles() {
                     d.ensure_restyle().hint.insert(&StoredRestyleHint::subtree());
                 }
             }
         }
@@ -1109,20 +1107,17 @@ impl LayoutThread {
                 if let Some(s) = restyle.snapshot {
                     restyle_data.snapshot.ensure(move || s);
                 }
                 debug!("Noting restyle for {:?}: {:?}", el, restyle_data);
             }
         }
 
         // Create a layout context for use throughout the following passes.
-        let mut layout_context = self.build_layout_context(&*rw_data,
-                                                           viewport_size_changed,
-                                                           data.reflow_info.goal,
-                                                           true);
+        let mut layout_context = self.build_layout_context(&*rw_data, true);
 
         // NB: Type inference falls apart here for some reason, so we need to be very verbose. :-(
         let traversal_driver = if self.parallel_flag && self.parallel_traversal.is_some() {
             TraversalDriver::Parallel
         } else {
             TraversalDriver::Sequential
         };
 
@@ -1274,16 +1269,39 @@ impl LayoutThread {
             ReflowQueryType::OffsetParentQuery(node) => {
                 let node = unsafe { ServoLayoutNode::new(&node) };
                 rw_data.offset_parent_response = process_offset_parent_query(node, root_flow);
             },
             ReflowQueryType::MarginStyleQuery(node) => {
                 let node = unsafe { ServoLayoutNode::new(&node) };
                 rw_data.margin_style_response = process_margin_style_query(node);
             },
+            ReflowQueryType::NodesFromPoint(page_point, client_point) => {
+                let page_point = Point2D::new(Au::from_f32_px(page_point.x),
+                                              Au::from_f32_px(page_point.y));
+                let client_point = Point2D::new(Au::from_f32_px(client_point.x),
+                                                Au::from_f32_px(client_point.y));
+                let nodes_from_point_list = {
+                    let result = match rw_data.display_list {
+                        None => panic!("Tried to hit test without a DisplayList"),
+                        Some(ref display_list) => {
+                            display_list.hit_test(&page_point,
+                                                  &client_point,
+                                                  &rw_data.stacking_context_scroll_offsets)
+                        }
+                    };
+
+                    result
+                };
+
+                rw_data.nodes_from_point_response = nodes_from_point_list.iter()
+                   .rev()
+                   .map(|metadata| metadata.node.to_untrusted_node_address())
+                   .collect()
+            },
             ReflowQueryType::NoQuery => {}
         }
     }
 
     fn set_stacking_context_scroll_states<'a, 'b>(
             &mut self,
             new_scroll_states: Vec<StackingContextScrollState>,
             possibly_locked_rw_data: &mut RwData<'a, 'b>) {
@@ -1317,20 +1335,17 @@ impl LayoutThread {
             println!("**** pipeline={}\tForDisplay\tSpecial\tAnimationTick", self.id);
         }
 
         let reflow_info = Reflow {
             goal: ReflowGoal::ForDisplay,
             page_clip_rect: max_rect(),
         };
 
-        let mut layout_context = self.build_layout_context(&*rw_data,
-                                                           false,
-                                                           reflow_info.goal,
-                                                           false);
+        let mut layout_context = self.build_layout_context(&*rw_data, false);
 
         if let Some(mut root_flow) = self.root_flow.clone() {
             // Perform an abbreviated style recalc that operates without access to the DOM.
             let animations = self.running_animations.read();
             profile(time::ProfilerCategory::LayoutStyleRecalc,
                     self.profiler_metadata(),
                     self.time_profiler_chan.clone(),
                     || {
@@ -1353,20 +1368,17 @@ impl LayoutThread {
         let mut rw_data = possibly_locked_rw_data.lock();
         font_context::invalidate_font_caches();
 
         let reflow_info = Reflow {
             goal: ReflowGoal::ForDisplay,
             page_clip_rect: max_rect(),
         };
 
-        let mut layout_context = self.build_layout_context(&*rw_data,
-                                                           false,
-                                                           reflow_info.goal,
-                                                           false);
+        let mut layout_context = self.build_layout_context(&*rw_data, false);
 
         // No need to do a style recalc here.
         if self.root_flow.is_none() {
             return
         }
         self.perform_post_style_recalc_layout_passes(&reflow_info,
                                                      None,
                                                      None,
@@ -1483,17 +1495,19 @@ impl LayoutThread {
             }
 
             self.generation += 1;
         }
     }
 
     fn reflow_all_nodes(flow: &mut Flow) {
         debug!("reflowing all nodes!");
-        flow::mut_base(flow).restyle_damage.insert(REPAINT | STORE_OVERFLOW | REFLOW);
+        flow::mut_base(flow)
+            .restyle_damage
+            .insert(REPAINT | STORE_OVERFLOW | REFLOW | REPOSITION);
 
         for child in flow::child_iter_mut(flow) {
             LayoutThread::reflow_all_nodes(child);
         }
     }
 
     /// Returns profiling information which is passed to the time profiler.
     fn profiler_metadata(&self) -> Option<TimerMetadata> {
@@ -1580,17 +1594,18 @@ fn get_ua_stylesheets() -> Result<UserAg
         quirks_mode_stylesheet: quirks_mode_stylesheet,
     })
 }
 
 /// Returns true if the given reflow query type needs a full, up-to-date display list to be present
 /// or false if it only needs stacking-relative positions.
 fn reflow_query_type_needs_display_list(query_type: &ReflowQueryType) -> bool {
     match *query_type {
-        ReflowQueryType::HitTestQuery(..) | ReflowQueryType::TextIndexQuery(..) => true,
+        ReflowQueryType::HitTestQuery(..) | ReflowQueryType::TextIndexQuery(..) |
+        ReflowQueryType::NodesFromPoint(..) => true,
         ReflowQueryType::ContentBoxQuery(_) | ReflowQueryType::ContentBoxesQuery(_) |
         ReflowQueryType::NodeGeometryQuery(_) | ReflowQueryType::NodeScrollGeometryQuery(_) |
         ReflowQueryType::NodeOverflowQuery(_) | ReflowQueryType::NodeScrollRootIdQuery(_) |
         ReflowQueryType::ResolvedStyleQuery(..) | ReflowQueryType::OffsetParentQuery(_) |
         ReflowQueryType::MarginStyleQuery(_) | ReflowQueryType::NoQuery => false,
     }
 }
 
--- a/servo/components/script/dom/document.rs
+++ b/servo/components/script/dom/document.rs
@@ -1872,17 +1872,23 @@ impl Document {
         !self.has_browsing_context || !url_has_network_scheme(&self.url())
     }
 
     pub fn nodes_from_point(&self, client_point: &Point2D<f32>) -> Vec<UntrustedNodeAddress> {
         let page_point =
             Point2D::new(client_point.x + self.window.PageXOffset() as f32,
                          client_point.y + self.window.PageYOffset() as f32);
 
-        self.window.layout().nodes_from_point(page_point, *client_point)
+        if !self.window.reflow(ReflowGoal::ForScriptQuery,
+                               ReflowQueryType::NodesFromPoint(page_point, *client_point),
+                               ReflowReason::Query) {
+            return vec!();
+        };
+
+        self.window.layout().nodes_from_point_response()
     }
 }
 
 #[derive(PartialEq, HeapSizeOf)]
 pub enum DocumentSource {
     FromParser,
     NotFromParser,
 }
--- a/servo/components/script/dom/window.rs
+++ b/servo/components/script/dom/window.rs
@@ -1806,16 +1806,17 @@ fn debug_reflow_events(id: PipelineId, g
         ReflowGoal::ForScriptQuery => "\tForScriptQuery",
     });
 
     debug_msg.push_str(match *query_type {
         ReflowQueryType::NoQuery => "\tNoQuery",
         ReflowQueryType::ContentBoxQuery(_n) => "\tContentBoxQuery",
         ReflowQueryType::ContentBoxesQuery(_n) => "\tContentBoxesQuery",
         ReflowQueryType::HitTestQuery(..) => "\tHitTestQuery",
+        ReflowQueryType::NodesFromPoint(..) => "\tNodesFromPoint",
         ReflowQueryType::NodeGeometryQuery(_n) => "\tNodeGeometryQuery",
         ReflowQueryType::NodeOverflowQuery(_n) => "\tNodeOverFlowQuery",
         ReflowQueryType::NodeScrollGeometryQuery(_n) => "\tNodeScrollGeometryQuery",
         ReflowQueryType::NodeScrollRootIdQuery(_n) => "\tNodeScrollRootIdQuery",
         ReflowQueryType::ResolvedStyleQuery(_, _, _) => "\tResolvedStyleQuery",
         ReflowQueryType::OffsetParentQuery(_n) => "\tOffsetParentQuery",
         ReflowQueryType::MarginStyleQuery(_n) => "\tMarginStyleQuery",
         ReflowQueryType::TextIndexQuery(..) => "\tTextIndexQuery",
--- a/servo/components/script_layout_interface/message.rs
+++ b/servo/components/script_layout_interface/message.rs
@@ -96,16 +96,17 @@ pub enum ReflowQueryType {
     HitTestQuery(Point2D<f32>, Point2D<f32>, bool),
     NodeScrollRootIdQuery(TrustedNodeAddress),
     NodeGeometryQuery(TrustedNodeAddress),
     NodeScrollGeometryQuery(TrustedNodeAddress),
     ResolvedStyleQuery(TrustedNodeAddress, Option<PseudoElement>, PropertyId),
     OffsetParentQuery(TrustedNodeAddress),
     MarginStyleQuery(TrustedNodeAddress),
     TextIndexQuery(TrustedNodeAddress, i32, i32),
+    NodesFromPoint(Point2D<f32>, Point2D<f32>),
 }
 
 /// Information needed for a reflow.
 pub struct Reflow {
     /// The goal of reflow: either to render to the screen or to flush layout info for script.
     pub goal: ReflowGoal,
     ///  A clipping rectangle for the page, an enlarged rectangle containing the viewport.
     pub page_clip_rect: Rect<Au>,
--- a/servo/components/script_layout_interface/rpc.rs
+++ b/servo/components/script_layout_interface/rpc.rs
@@ -35,17 +35,18 @@ pub trait LayoutRPC {
     fn hit_test(&self) -> HitTestResponse;
     /// Query layout for the resolved value of a given CSS property
     fn resolved_style(&self) -> ResolvedStyleResponse;
     fn offset_parent(&self) -> OffsetParentResponse;
     /// Query layout for the resolve values of the margin properties for an element.
     fn margin_style(&self) -> MarginStyleResponse;
     /// Requests the list of not-yet-loaded images that were encountered in the last reflow.
     fn pending_images(&self) -> Vec<PendingImage>;
-    fn nodes_from_point(&self, page_point: Point2D<f32>, client_point: Point2D<f32>) -> Vec<UntrustedNodeAddress>;
+    /// Requests the list of nodes from the given point.
+    fn nodes_from_point_response(&self) -> Vec<UntrustedNodeAddress>;
 
     fn text_index(&self) -> TextIndexResponse;
 }
 
 pub struct ContentBoxResponse(pub Option<Rect<Au>>);
 
 pub struct ContentBoxesResponse(pub Vec<Rect<Au>>);
 
--- a/servo/components/style/context.rs
+++ b/servo/components/style/context.rs
@@ -59,25 +59,19 @@ pub enum QuirksMode {
 /// A shared style context.
 ///
 /// There's exactly one of these during a given restyle traversal, and it's
 /// shared among the worker threads.
 pub struct SharedStyleContext {
     /// The current viewport size.
     pub viewport_size: Size2D<Au>,
 
-    /// Screen sized changed?
-    pub screen_size_changed: bool,
-
     /// The CSS selector stylist.
     pub stylist: Arc<Stylist>,
 
-    /// Why is this reflow occurring
-    pub goal: ReflowGoal,
-
     /// The animations that are currently running.
     pub running_animations: Arc<RwLock<HashMap<OpaqueNode, Vec<Animation>>>>,
 
     /// The list of animations that have expired since the last style recalculation.
     pub expired_animations: Arc<RwLock<HashMap<OpaqueNode, Vec<Animation>>>>,
 
     ///The CSS error reporter for all CSS loaded in this layout thread
     pub error_reporter: Box<ParseErrorReporter + Sync>,
--- a/servo/ports/geckolib/glue.rs
+++ b/servo/ports/geckolib/glue.rs
@@ -15,17 +15,17 @@ use selectors::Element;
 use servo_url::ServoUrl;
 use std::borrow::Cow;
 use std::cmp;
 use std::env;
 use std::fmt::Write;
 use std::ptr;
 use std::sync::{Arc, Mutex};
 use style::arc_ptr_eq;
-use style::context::{QuirksMode, ReflowGoal, SharedStyleContext, StyleContext};
+use style::context::{QuirksMode, SharedStyleContext, StyleContext};
 use style::context::{ThreadLocalStyleContext, ThreadLocalStyleContextCreationInfo};
 use style::data::{ElementData, ElementStyles, RestyleData};
 use style::dom::{ShowSubtreeData, TElement, TNode};
 use style::error_reporting::StdoutErrorReporter;
 use style::gecko::data::{PerDocumentStyleData, PerDocumentStyleDataImpl};
 use style::gecko::restyle_damage::GeckoRestyleDamage;
 use style::gecko::selector_parser::{SelectorImpl, PseudoElement};
 use style::gecko::traversal::RecalcStyleOnly;
@@ -159,18 +159,16 @@ pub extern "C" fn Servo_Shutdown() -> ()
 
 fn create_shared_context(per_doc_data: &PerDocumentStyleDataImpl) -> SharedStyleContext {
     let local_context_data =
         ThreadLocalStyleContextCreationInfo::new(per_doc_data.new_animations_sender.clone());
 
     SharedStyleContext {
         // FIXME (bug 1303229): Use the actual viewport size here
         viewport_size: Size2D::new(Au(0), Au(0)),
-        screen_size_changed: false,
-        goal: ReflowGoal::ForScriptQuery,
         stylist: per_doc_data.stylist.clone(),
         running_animations: per_doc_data.running_animations.clone(),
         expired_animations: per_doc_data.expired_animations.clone(),
         error_reporter: Box::new(StdoutErrorReporter),
         local_context_creation_data: Mutex::new(local_context_data),
         timer: Timer::new(),
         // FIXME Find the real QuirksMode information for this document
         quirks_mode: QuirksMode::NoQuirks,
rename from release/docker/beet-mover/Dockerfile
rename to taskcluster/docker/beet-mover/Dockerfile
rename from release/docker/beet-mover/requirements.txt
rename to taskcluster/docker/beet-mover/requirements.txt
rename from release/docker/firefox-snap/Dockerfile
rename to taskcluster/docker/firefox-snap/Dockerfile
rename from release/docker/firefox-snap/Makefile
rename to taskcluster/docker/firefox-snap/Makefile
rename from release/docker/firefox-snap/distribution.ini
rename to taskcluster/docker/firefox-snap/distribution.ini
rename from release/docker/firefox-snap/runme.sh
rename to taskcluster/docker/firefox-snap/runme.sh
rename from release/docker/firefox-snap/snapcraft.yaml.in
rename to taskcluster/docker/firefox-snap/snapcraft.yaml.in
rename from release/docker/funsize-balrog-submitter/Dockerfile
rename to taskcluster/docker/funsize-balrog-submitter/Dockerfile
rename from release/docker/funsize-balrog-submitter/Makefile
rename to taskcluster/docker/funsize-balrog-submitter/Makefile
rename from release/docker/funsize-balrog-submitter/dep.pubkey
rename to taskcluster/docker/funsize-balrog-submitter/dep.pubkey
rename from release/docker/funsize-balrog-submitter/nightly.pubkey
rename to taskcluster/docker/funsize-balrog-submitter/nightly.pubkey
rename from release/docker/funsize-balrog-submitter/release.pubkey
rename to taskcluster/docker/funsize-balrog-submitter/release.pubkey
rename from release/docker/funsize-balrog-submitter/requirements.txt
rename to taskcluster/docker/funsize-balrog-submitter/requirements.txt
rename from release/docker/funsize-balrog-submitter/runme.sh
rename to taskcluster/docker/funsize-balrog-submitter/runme.sh
rename from release/docker/funsize-balrog-submitter/scripts/funsize-balrog-submitter.py
rename to taskcluster/docker/funsize-balrog-submitter/scripts/funsize-balrog-submitter.py
--- a/release/docker/funsize-balrog-submitter/scripts/funsize-balrog-submitter.py
+++ b/taskcluster/docker/funsize-balrog-submitter/scripts/funsize-balrog-submitter.py
@@ -1,15 +1,14 @@
 #!/usr/bin/env python
 import site
 import os
 import logging
 import argparse
 import json
-import sys
 import hashlib
 import requests
 import tempfile
 from boto.s3.connection import S3Connection
 from mardor.marfile import MarFile
 
 site.addsitedir("/home/worker/tools/lib/python")
 
rename from release/docker/funsize-update-generator/Dockerfile
rename to taskcluster/docker/funsize-update-generator/Dockerfile
rename from release/docker/funsize-update-generator/Makefile
rename to taskcluster/docker/funsize-update-generator/Makefile
rename from release/docker/funsize-update-generator/dep.pubkey
rename to taskcluster/docker/funsize-update-generator/dep.pubkey
rename from release/docker/funsize-update-generator/nightly.pubkey
rename to taskcluster/docker/funsize-update-generator/nightly.pubkey
rename from release/docker/funsize-update-generator/release.pubkey
rename to taskcluster/docker/funsize-update-generator/release.pubkey
rename from release/docker/funsize-update-generator/requirements.txt
rename to taskcluster/docker/funsize-update-generator/requirements.txt
rename from release/docker/funsize-update-generator/runme.sh
rename to taskcluster/docker/funsize-update-generator/runme.sh
rename from release/docker/funsize-update-generator/scripts/funsize.py
rename to taskcluster/docker/funsize-update-generator/scripts/funsize.py
--- a/release/docker/funsize-update-generator/scripts/funsize.py
+++ b/taskcluster/docker/funsize-update-generator/scripts/funsize.py
@@ -183,18 +183,18 @@ def main():
     task = json.load(args.task_definition)
     # TODO: verify task["extra"]["funsize"]["partials"] with jsonschema
 
     if args.no_freshclam:
         log.info("Skipping freshclam")
     else:
         log.info("Refreshing clamav db...")
         try:
-            redo.retry(lambda:
-                    sh.freshclam("--stdout", "--verbose", _timeout=300, _err_to_out=True))
+            redo.retry(lambda: sh.freshclam("--stdout", "--verbose",
+                                            _timeout=300, _err_to_out=True))
             log.info("Done.")
         except sh.ErrorReturnCode:
             log.warning("Freshclam failed, skipping DB update")
     manifest = []
     for e in task["extra"]["funsize"]["partials"]:
         for mar in (e["from_mar"], e["to_mar"]):
             verify_allowed_url(mar)
 
rename from release/docker/funsize-update-generator/scripts/mbsdiff_hook.sh
rename to taskcluster/docker/funsize-update-generator/scripts/mbsdiff_hook.sh
--- a/testing/mochitest/tests/SimpleTest/ExtensionTestUtils.js
+++ b/testing/mochitest/tests/SimpleTest/ExtensionTestUtils.js
@@ -1,10 +1,12 @@
 var ExtensionTestUtils = {};
 
+const {ExtensionTestCommon} = SpecialPowers.Cu.import("resource://testing-common/ExtensionTestCommon.jsm", {});
+
 ExtensionTestUtils.loadExtension = function(ext)
 {
   // Cleanup functions need to be registered differently depending on
   // whether we're in browser chrome or plain mochitests.
   var registerCleanup;
   if (typeof registerCleanupFunction != "undefined") {
     registerCleanup = registerCleanupFunction;
   } else {
@@ -86,23 +88,23 @@ ExtensionTestUtils.loadExtension = funct
   // Mimic serialization of functions as done in `Extension.generateXPI` and
   // `Extension.generateZipFile` because functions are dropped when `ext` object
   // is sent to the main process via the message manager.
   ext = Object.assign({}, ext);
   if (ext.files) {
     ext.files = Object.assign({}, ext.files);
     for (let filename of Object.keys(ext.files)) {
       let file = ext.files[filename];
-      if (typeof file == "function") {
-        ext.files[filename] = `(${file})();`
+      if (typeof file === "function" || Array.isArray(file)) {
+        ext.files[filename] = ExtensionTestCommon.serializeScript(file);
       }
     }
   }
-  if (typeof ext.background == "function") {
-    ext.background = `(${ext.background})();`
+  if ("background" in ext) {
+    ext.background = ExtensionTestCommon.serializeScript(ext.background);
   }
 
   var extension = SpecialPowers.loadExtension(ext, handler);
 
   registerCleanup(() => {
     if (extension.state == "pending" || extension.state == "running") {
       SimpleTest.ok(false, "Extension left running at test shutdown")
       return extension.unload();
rename from testing/mozharness/configs/beetmover/en_us.yml.tmpl
rename to testing/mozharness/configs/beetmover/en_us_build.yml.tmpl
--- a/testing/mozharness/configs/beetmover/en_us.yml.tmpl
+++ b/testing/mozharness/configs/beetmover/en_us_build.yml.tmpl
@@ -1,27 +1,52 @@
 ---
 metadata:
     name: "Beet Mover Manifest"
     description: "Maps artifact locations to s3 key names for the en-US locale"
     owner: "release@mozilla.com"
 
 mapping:
 {% for locale in locales %}
-  # common deliverables
   {{ locale }}:
-    complete_mar:
-      artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.complete.mar
-      s3_key: {{ s3_prefix }}update/{{ platform }}/{{ locale }}/firefox-{{ version }}.complete.mar
-    checksum:
-      artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.checksums
-      s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.checksums
-    checksum_sig:
-      artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.checksums.asc
-      s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.checksums.asc
+
+  {% if platform == "win32" %}
+    buildinfo:
+      artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.json
+      s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.json
+    mozinfo:
+      artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.mozinfo.json
+      s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.mozinfo.json
+    socorroinfo:
+      artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.txt
+      s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.txt
+    jsshell:
+      artifact: {{ artifact_base_url }}/jsshell-{{ platform }}.zip
+      s3_key: {{ s3_prefix }}jsshell-{{ platform }}.zip
+    mozharness_package:
+      artifact: {{ artifact_base_url }}/mozharness.zip
+      s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/mozharness.zip
+    xpi:
+      artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.langpack.xpi
+      s3_key: {{ s3_prefix }}{{ platform }}/xpi/{{ locale }}.xpi
+    symbols:
+      artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.crashreporter-symbols.zip
+      s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.crashreporter-symbols.zip
+    buildid_info:
+      artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}_info.txt
+      s3_key: {{ s3_prefix }}win32_info.txt
+    mar_tools_mar:
+      artifact: {{ artifact_base_url }}/mar.exe
+      s3_key: {{ s3_prefix }}mar-tools/win32/mar.exe
+    mar_tools_mbdiff:
+      artifact: {{ artifact_base_url }}/mbsdiff.exe
+      s3_key: {{ s3_prefix }}mar-tools/win32/mbsdiff.exe
+  {% endif %}
+
+  {% if platform == "win64" %}
     buildinfo:
       artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.json
       s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.json
     mozinfo:
       artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.mozinfo.json
       s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.mozinfo.json
     socorroinfo:
       artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.txt
@@ -30,104 +55,115 @@ mapping:
       artifact: {{ artifact_base_url }}/jsshell-{{ platform }}.zip
       s3_key: {{ s3_prefix }}jsshell-{{ platform }}.zip
     mozharness_package:
       artifact: {{ artifact_base_url }}/mozharness.zip
       s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/mozharness.zip
     xpi:
       artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.langpack.xpi
       s3_key: {{ s3_prefix }}{{ platform }}/xpi/{{ locale }}.xpi
-
-  {% if platform == "win32" %}
-    full_installer:
-      artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.installer.exe
-      s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/Firefox Setup {{ version }}.exe
-    {% if "esr" not in version %}
-    stub_installer:
-      artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.installer-stub.exe
-      s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/Firefox Setup Stub {{ version }}.exe
-    {% endif %}
-    package:
-      artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.zip
-      s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.zip
-    symbols:
-      artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.crashreporter-symbols.zip
-      s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.crashreporter-symbols.zip
-    buildid_info:
-      artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}_info.txt
-      s3_key: {{ s3_prefix }}win32_info.txt
-    mar_tools_mar:
-      artifact: {{ artifact_base_url }}/mar.exe
-      s3_key: {{ s3_prefix }}mar-tools/win32/mar.exe
-    mar_tools_mbdiff:
-      artifact: {{ artifact_base_url }}/mbsdiff.exe
-      s3_key: {{ s3_prefix }}mar-tools/win32/mbsdiff.exe
-  {% endif %}
-
-  {% if platform == "win64" %}
-    full_installer:
-      artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.installer.exe
-      s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/Firefox Setup {{ version }}.exe
-    package:
-      artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.zip
-      s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.zip
     symbols:
       artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.crashreporter-symbols.zip
       s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.crashreporter-symbols.zip
     buildid_info:
       artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}_info.txt
       s3_key: {{ s3_prefix }}win64_info.txt
     mar_tools_mar:
       artifact: {{ artifact_base_url }}/mar.exe
       s3_key: {{ s3_prefix }}mar-tools/win64/mar.exe
     mar_tools_mbdiff:
       artifact: {{ artifact_base_url }}/mbsdiff.exe
       s3_key: {{ s3_prefix }}mar-tools/win64/mbsdiff.exe
   {% endif %}
 
   {% if platform == "linux-i686" %}
-    package:
-      artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.tar.bz2
-      s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.tar.bz2
+    buildinfo:
+      artifact: {{ artifact_base_url }}/target.json
+      s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.json
+    mozinfo:
+      artifact: {{ artifact_base_url }}/target.mozinfo.json
+      s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.mozinfo.json
+    socorroinfo:
+      artifact: {{ artifact_base_url }}/target.txt
+      s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.txt
+    jsshell:
+      artifact: {{ artifact_base_url }}/target.jsshell.zip
+      s3_key: {{ s3_prefix }}jsshell-{{ platform }}.zip
+    mozharness_package:
+      artifact: {{ artifact_base_url }}/mozharness.zip
+      s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/mozharness.zip
+    xpi:
+      artifact: {{ artifact_base_url }}/target.langpack.xpi
+      s3_key: {{ s3_prefix }}{{ platform }}/xpi/{{ locale }}.xpi
     symbols:
-      artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.crashreporter-symbols.zip
+      artifact: {{ artifact_base_url }}/target.crashreporter-symbols.zip
       s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.crashreporter-symbols.zip
     buildid_info:
-      artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}_info.txt
+      artifact: {{ artifact_base_url }}/target_info.txt
       s3_key: {{ s3_prefix }}linux_info.txt
     mar_tools_mar:
-      artifact: {{ artifact_base_url }}/mar
+      artifact: {{ artifact_base_url }}/host/bin/mar
       s3_key: {{ s3_prefix }}mar-tools/linux/mar
     mar_tools_mbdiff:
-      artifact: {{ artifact_base_url }}/mbsdiff
+      artifact: {{ artifact_base_url }}/host/bin/mbsdiff
       s3_key: {{ s3_prefix }}mar-tools/linux/mbsdiff
   {% endif %}
 
   {% if platform == "linux-x86_64" %}
-    package:
-      artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.tar.bz2
-      s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.tar.bz2
+    buildinfo:
+      artifact: {{ artifact_base_url }}/target.json
+      s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.json
+    mozinfo:
+      artifact: {{ artifact_base_url }}/target.mozinfo.json
+      s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.mozinfo.json
+    socorroinfo:
+      artifact: {{ artifact_base_url }}/target.txt
+      s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.txt
+    jsshell:
+      artifact: {{ artifact_base_url }}/target.jsshell.zip
+      s3_key: {{ s3_prefix }}jsshell-{{ platform }}.zip
+    mozharness_package:
+      artifact: {{ artifact_base_url }}/mozharness.zip
+      s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/mozharness.zip
+    xpi:
+      artifact: {{ artifact_base_url }}/target.langpack.xpi
+      s3_key: {{ s3_prefix }}{{ platform }}/xpi/{{ locale }}.xpi
     symbols:
-      artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.crashreporter-symbols.zip
+      artifact: {{ artifact_base_url }}/target.crashreporter-symbols.zip
       s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.crashreporter-symbols.zip
     buildid_info:
-      artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}_info.txt
+      artifact: {{ artifact_base_url }}/target_info.txt
       s3_key: {{ s3_prefix }}linux64_info.txt
     mar_tools_mar:
-      artifact: {{ artifact_base_url }}/mar
+      artifact: {{ artifact_base_url }}/host/bin/mar
       s3_key: {{ s3_prefix }}mar-tools/linux64/mar
     mar_tools_mbdiff:
-      artifact: {{ artifact_base_url }}/mbsdiff
+      artifact: {{ artifact_base_url }}/host/bin/mbsdiff
       s3_key: {{ s3_prefix }}mar-tools/linux64/mbsdiff
   {% endif %}
 
   {% if platform == "mac" %}
-    package:
-      artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.dmg
-      s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/Firefox {{ version }}.dmg
+    buildinfo:
+      artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.json
+      s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.json
+    mozinfo:
+      artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.mozinfo.json
+      s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.mozinfo.json
+    socorroinfo:
+      artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.txt
+      s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.txt
+    jsshell:
+      artifact: {{ artifact_base_url }}/jsshell-{{ platform }}.zip
+      s3_key: {{ s3_prefix }}jsshell-{{ platform }}.zip
+    mozharness_package:
+      artifact: {{ artifact_base_url }}/mozharness.zip
+      s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/mozharness.zip
+    xpi:
+      artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.langpack.xpi
+      s3_key: {{ s3_prefix }}{{ platform }}/xpi/{{ locale }}.xpi
     symbols:
       artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.crashreporter-symbols.zip
       s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/Firefox {{ version }}.crashreporter-symbols.zip
     buildid_info:
       artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}_info.txt
       s3_key: {{ s3_prefix }}macosx64_info.txt
     mar_tools_mar:
       artifact: {{ artifact_base_url }}/mar
copy from testing/mozharness/configs/beetmover/en_us.yml.tmpl
copy to testing/mozharness/configs/beetmover/en_us_signing.yml.tmpl
--- a/testing/mozharness/configs/beetmover/en_us.yml.tmpl
+++ b/testing/mozharness/configs/beetmover/en_us_signing.yml.tmpl
@@ -1,140 +1,66 @@
 ---
 metadata:
     name: "Beet Mover Manifest"
     description: "Maps artifact locations to s3 key names for the en-US locale"
     owner: "release@mozilla.com"
 
 mapping:
 {% for locale in locales %}
-  # common deliverables
   {{ locale }}:
+  {% if platform == "win32" %}
     complete_mar:
       artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.complete.mar
       s3_key: {{ s3_prefix }}update/{{ platform }}/{{ locale }}/firefox-{{ version }}.complete.mar
-    checksum:
-      artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.checksums
-      s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.checksums
-    checksum_sig:
-      artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.checksums.asc
-      s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.checksums.asc
-    buildinfo:
-      artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.json
-      s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.json
-    mozinfo:
-      artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.mozinfo.json
-      s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.mozinfo.json
-    socorroinfo:
-      artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.txt
-      s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.txt
-    jsshell:
-      artifact: {{ artifact_base_url }}/jsshell-{{ platform }}.zip
-      s3_key: {{ s3_prefix }}jsshell-{{ platform }}.zip
-    mozharness_package:
-      artifact: {{ artifact_base_url }}/mozharness.zip
-      s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/mozharness.zip
-    xpi:
-      artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.langpack.xpi
-      s3_key: {{ s3_prefix }}{{ platform }}/xpi/{{ locale }}.xpi
-
-  {% if platform == "win32" %}
     full_installer:
       artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.installer.exe
       s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/Firefox Setup {{ version }}.exe
     {% if "esr" not in version %}
     stub_installer:
       artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.installer-stub.exe
       s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/Firefox Setup Stub {{ version }}.exe
     {% endif %}
     package:
       artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.zip
       s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.zip
-    symbols:
-      artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.crashreporter-symbols.zip
-      s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.crashreporter-symbols.zip
-    buildid_info:
-      artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}_info.txt
-      s3_key: {{ s3_prefix }}win32_info.txt
-    mar_tools_mar:
-      artifact: {{ artifact_base_url }}/mar.exe
-      s3_key: {{ s3_prefix }}mar-tools/win32/mar.exe
-    mar_tools_mbdiff:
-      artifact: {{ artifact_base_url }}/mbsdiff.exe
-      s3_key: {{ s3_prefix }}mar-tools/win32/mbsdiff.exe
   {% endif %}
 
   {% if platform == "win64" %}
+    complete_mar:
+      artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.complete.mar
+      s3_key: {{ s3_prefix }}update/{{ platform }}/{{ locale }}/firefox-{{ version }}.complete.mar
     full_installer:
       artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.installer.exe
       s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/Firefox Setup {{ version }}.exe
     package:
       artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.zip
       s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.zip
-    symbols:
-      artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.crashreporter-symbols.zip
-      s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.crashreporter-symbols.zip
-    buildid_info:
-      artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}_info.txt
-      s3_key: {{ s3_prefix }}win64_info.txt
-    mar_tools_mar:
-      artifact: {{ artifact_base_url }}/mar.exe
-      s3_key: {{ s3_prefix }}mar-tools/win64/mar.exe
-    mar_tools_mbdiff:
-      artifact: {{ artifact_base_url }}/mbsdiff.exe
-      s3_key: {{ s3_prefix }}mar-tools/win64/mbsdiff.exe
   {% endif %}
 
   {% if platform == "linux-i686" %}
     package:
-      artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.tar.bz2
+      artifact: {{ artifact_base_url }}/target.tar.bz2
       s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.tar.bz2
-    symbols:
-      artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.crashreporter-symbols.zip
-      s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.crashreporter-symbols.zip
-    buildid_info:
-      artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}_info.txt
-      s3_key: {{ s3_prefix }}linux_info.txt
-    mar_tools_mar:
-      artifact: {{ artifact_base_url }}/mar
-      s3_key: {{ s3_prefix }}mar-tools/linux/mar
-    mar_tools_mbdiff:
-      artifact: {{ artifact_base_url }}/mbsdiff
-      s3_key: {{ s3_prefix }}mar-tools/linux/mbsdiff
+    complete_mar:
+      artifact: {{ artifact_base_url }}/update/target.complete.mar
+      s3_key: {{ s3_prefix }}update/{{ platform }}/{{ locale }}/firefox-{{ version }}.complete.mar
   {% endif %}
 
   {% if platform == "linux-x86_64" %}
     package:
-      artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.tar.bz2
+      artifact: {{ artifact_base_url }}/target.tar.bz2
       s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.tar.bz2
-    symbols:
-      artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.crashreporter-symbols.zip
-      s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/firefox-{{ version }}.crashreporter-symbols.zip
-    buildid_info:
-      artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}_info.txt
-      s3_key: {{ s3_prefix }}linux64_info.txt
-    mar_tools_mar:
-      artifact: {{ artifact_base_url }}/mar
-      s3_key: {{ s3_prefix }}mar-tools/linux64/mar
-    mar_tools_mbdiff:
-      artifact: {{ artifact_base_url }}/mbsdiff
-      s3_key: {{ s3_prefix }}mar-tools/linux64/mbsdiff
+    complete_mar:
+      artifact: {{ artifact_base_url }}/update/target.complete.mar
+      s3_key: {{ s3_prefix }}update/{{ platform }}/{{ locale }}/firefox-{{ version }}.complete.mar
   {% endif %}
 
   {% if platform == "mac" %}
+    complete_mar:
+      artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.complete.mar
+      s3_key: {{ s3_prefix }}update/{{ platform }}/{{ locale }}/firefox-{{ version }}.complete.mar
     package:
       artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.dmg
       s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/Firefox {{ version }}.dmg
-    symbols:
-      artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}.crashreporter-symbols.zip
-      s3_key: {{ s3_prefix }}{{ platform }}/{{ locale }}/Firefox {{ version }}.crashreporter-symbols.zip
-    buildid_info:
-      artifact: {{ artifact_base_url }}/firefox-{{ app_version }}.{{ locale }}.{{ platform }}_info.txt
-      s3_key: {{ s3_prefix }}macosx64_info.txt
-    mar_tools_mar:
-      artifact: {{ artifact_base_url }}/mar
-      s3_key: {{ s3_prefix }}mar-tools/macosx64/mar
-    mar_tools_mbdiff:
-      artifact: {{ artifact_base_url }}/mbsdiff
-      s3_key: {{ s3_prefix }}mar-tools/macosx64/mbsdiff
   {% endif %}
 
 {% endfor %}
--- a/testing/mozharness/configs/builds/releng_base_windows_32_builds.py
+++ b/testing/mozharness/configs/builds/releng_base_windows_32_builds.py
@@ -69,17 +69,17 @@ config = {
         'MOZ_CRASHREPORTER_NO_REPORT': '1',
         'MOZ_OBJDIR': 'obj-firefox',
         'PATH': 'C:/mozilla-build/nsis-3.0b1;C:/mozilla-build/python27;'
                 'C:/mozilla-build/buildbotve/scripts;'
                 '%s' % (os.environ.get('path')),
         'PDBSTR_PATH': '/c/Program Files (x86)/Windows Kits/8.0/Debuggers/x64/srcsrv/pdbstr.exe',
         'PROPERTIES_FILE': os.path.join(os.getcwd(), 'buildprops.json'),
         'TINDERBOX_OUTPUT': '1',
-        'TOOLTOOL_CACHE': '/c/builds/tooltool_cache',
+        'TOOLTOOL_CACHE': 'c:/builds/tooltool_cache',
         'TOOLTOOL_HOME': '/c/builds',
     },
     'upload_env': {
         # stage_server is dictated from build_pool_specifics.py
         'UPLOAD_HOST': '%(stage_server)s',
         'UPLOAD_USER': '%(stage_username)s',
         'UPLOAD_SSH_KEY': '/c/Users/cltbld/.ssh/%(stage_ssh_key)s',
         'UPLOAD_TO_TEMP': '1',
--- a/testing/mozharness/configs/builds/releng_base_windows_64_builds.py
+++ b/testing/mozharness/configs/builds/releng_base_windows_64_builds.py
@@ -67,17 +67,17 @@ config = {
         'MOZ_CRASHREPORTER_NO_REPORT': '1',
         'MOZ_OBJDIR': 'obj-firefox',
         'PATH': 'C:/mozilla-build/nsis-3.0b1;C:/mozilla-build/python27;'
                 'C:/mozilla-build/buildbotve/scripts;'
                 '%s' % (os.environ.get('path')),
         'PDBSTR_PATH': '/c/Program Files (x86)/Windows Kits/8.0/Debuggers/x64/srcsrv/pdbstr.exe',
         'PROPERTIES_FILE': os.path.join(os.getcwd(), 'buildprops.json'),
         'TINDERBOX_OUTPUT': '1',
-        'TOOLTOOL_CACHE': '/c/builds/tooltool_cache',
+        'TOOLTOOL_CACHE': 'c:/builds/tooltool_cache',
         'TOOLTOOL_HOME': '/c/builds',
     },
     'upload_env': {
         # stage_server is dictated from build_pool_specifics.py
         'UPLOAD_HOST': '%(stage_server)s',
         'UPLOAD_USER': '%(stage_username)s',
         'UPLOAD_SSH_KEY': '/c/Users/cltbld/.ssh/%(stage_ssh_key)s',
         'UPLOAD_TO_TEMP': '1',
--- a/testing/mozharness/configs/builds/releng_sub_windows_configs/32_add-on-devel.py
+++ b/testing/mozharness/configs/builds/releng_sub_windows_configs/32_add-on-devel.py
@@ -25,14 +25,14 @@ config = {
         'MOZ_CRASHREPORTER_NO_REPORT': '1',
         'MOZ_OBJDIR': 'obj-firefox',
         'PATH': 'C:/mozilla-build/nsis-3.0b1;C:/mozilla-build/python27;'
                 'C:/mozilla-build/buildbotve/scripts;'
                 '%s' % (os.environ.get('path')),
         'PROPERTIES_FILE': os.path.join(os.getcwd(), 'buildprops.json'),
         'TINDERBOX_OUTPUT': '1',
         'XPCOM_DEBUG_BREAK': 'stack-and-abort',
-        'TOOLTOOL_CACHE': '/c/builds/tooltool_cache',
+        'TOOLTOOL_CACHE': 'c:/builds/tooltool_cache',
         'TOOLTOOL_HOME': '/c/builds',
     },
     'src_mozconfig': 'browser/config/mozconfigs/win32/add-on-devel',
     #######################
 }
--- a/testing/mozharness/configs/builds/releng_sub_windows_configs/32_debug.py
+++ b/testing/mozharness/configs/builds/releng_sub_windows_configs/32_debug.py
@@ -27,14 +27,14 @@ config = {
         'MOZ_CRASHREPORTER_NO_REPORT': '1',
         'MOZ_OBJDIR': 'obj-firefox',
         'PATH': 'C:/mozilla-build/nsis-3.0b1;C:/mozilla-build/python27;'
                 'C:/mozilla-build/buildbotve/scripts;'
                 '%s' % (os.environ.get('path')),
         'PROPERTIES_FILE': os.path.join(os.getcwd(), 'buildprops.json'),
         'TINDERBOX_OUTPUT': '1',
         'XPCOM_DEBUG_BREAK': 'stack-and-abort',
-        'TOOLTOOL_CACHE': '/c/builds/tooltool_cache',
+        'TOOLTOOL_CACHE': 'c:/builds/tooltool_cache',
         'TOOLTOOL_HOME': '/c/builds',
     },
     'src_mozconfig': 'browser/config/mozconfigs/win32/debug',
     #######################
 }
--- a/testing/mozharness/configs/builds/releng_sub_windows_configs/32_stat_and_debug.py
+++ b/testing/mozharness/configs/builds/releng_sub_windows_configs/32_stat_and_debug.py
@@ -31,15 +31,15 @@ clang.manifest",
         'MOZ_CRASHREPORTER_NO_REPORT': '1',
         'MOZ_OBJDIR': 'obj-firefox',
         'PATH': 'C:/mozilla-build/nsis-3.0a2;C:/mozilla-build/nsis-2.46u;C:/mozilla-build/python27;'
                 'C:/mozilla-build/buildbotve/scripts;'
                 '%s' % (os.environ.get('path')),
         'PROPERTIES_FILE': os.path.join(os.getcwd(), 'buildprops.json'),
         'TINDERBOX_OUTPUT': '1',
         'XPCOM_DEBUG_BREAK': 'stack-and-abort',
-        'TOOLTOOL_CACHE': '/c/builds/tooltool_cache',
+        'TOOLTOOL_CACHE': 'c:/builds/tooltool_cache',
         'TOOLTOOL_HOME': '/c/builds',
     },
     'src_mozconfig': 'browser/config/mozconfigs/win32/debug-static-analysis',
     'purge_minsize': 9,
     #######################
 }
--- a/testing/mozharness/configs/builds/releng_sub_windows_configs/64_add-on-devel.py
+++ b/testing/mozharness/configs/builds/releng_sub_windows_configs/64_add-on-devel.py
@@ -24,14 +24,14 @@ config = {
         'MOZ_CRASHREPORTER_NO_REPORT': '1',
         'MOZ_OBJDIR': 'obj-firefox',
         'PATH': 'C:/mozilla-build/nsis-3.0b1;C:/mozilla-build/python27;'
                 'C:/mozilla-build/buildbotve/scripts;'
                 '%s' % (os.environ.get('path')),
         'PROPERTIES_FILE': os.path.join(os.getcwd(), 'buildprops.json'),
         'TINDERBOX_OUTPUT': '1',
         'XPCOM_DEBUG_BREAK': 'stack-and-abort',
-        'TOOLTOOL_CACHE': '/c/builds/tooltool_cache',
+        'TOOLTOOL_CACHE': 'c:/builds/tooltool_cache',
         'TOOLTOOL_HOME': '/c/builds',
     },
     'src_mozconfig': 'browser/config/mozconfigs/win64/add-on-devel',
     #######################
 }
--- a/testing/mozharness/configs/builds/releng_sub_windows_configs/64_debug.py
+++ b/testing/mozharness/configs/builds/releng_sub_windows_configs/64_debug.py
@@ -26,14 +26,14 @@ config = {
         'MOZ_CRASHREPORTER_NO_REPORT': '1',
         'MOZ_OBJDIR': 'obj-firefox',
         'PATH': 'C:/mozilla-build/nsis-3.0b1;C:/mozilla-build/python27;'
                 'C:/mozilla-build/buildbotve/scripts;'
                 '%s' % (os.environ.get('path')),
         'PROPERTIES_FILE': os.path.join(os.getcwd(), 'buildprops.json'),
         'TINDERBOX_OUTPUT': '1',
         'XPCOM_DEBUG_BREAK': 'stack-and-abort',
-        'TOOLTOOL_CACHE': '/c/builds/tooltool_cache',
+        'TOOLTOOL_CACHE': 'c:/builds/tooltool_cache',
         'TOOLTOOL_HOME': '/c/builds',
     },
     'src_mozconfig': 'browser/config/mozconfigs/win64/debug',
     #######################
 }
--- a/testing/mozharness/configs/builds/taskcluster_firefox_win32_clang.py
+++ b/testing/mozharness/configs/builds/taskcluster_firefox_win32_clang.py
@@ -69,17 +69,17 @@ config = {
         ),
         'HG_SHARE_BASE_DIR': os.path.join('y:', os.sep, 'hg-shared'),
         'MOZBUILD_STATE_PATH': os.path.join(os.getcwd(), '.mozbuild'),
         'MOZ_AUTOMATION': '1',
         'MOZ_CRASHREPORTER_NO_REPORT': '1',
         'MOZ_OBJDIR': 'obj-firefox',
         'PDBSTR_PATH': '/c/Program Files (x86)/Windows Kits/10/Debuggers/x86/srcsrv/pdbstr.exe',
         'TINDERBOX_OUTPUT': '1',
-        'TOOLTOOL_CACHE': '/c/builds/tooltool_cache',
+        'TOOLTOOL_CACHE': 'c:/builds/tooltool_cache',
         'TOOLTOOL_HOME': '/c/builds',
         'MSYSTEM': 'MINGW32',
     },
     'upload_env': {
         'UPLOAD_HOST': 'localhost',
         'UPLOAD_PATH': os.path.join(os.getcwd(), 'public', 'build'),
     },
     "check_test_env": {
--- a/testing/mozharness/configs/builds/taskcluster_firefox_win32_clang_debug.py
+++ b/testing/mozharness/configs/builds/taskcluster_firefox_win32_clang_debug.py
@@ -70,17 +70,17 @@ config = {
         ),
         'HG_SHARE_BASE_DIR': os.path.join('y:', os.sep, 'hg-shared'),
         'MOZBUILD_STATE_PATH': os.path.join(os.getcwd(), '.mozbuild'),
         'MOZ_AUTOMATION': '1',
         'MOZ_CRASHREPORTER_NO_REPORT': '1',
         'MOZ_OBJDIR': 'obj-firefox',
         'PDBSTR_PATH': '/c/Program Files (x86)/Windows Kits/10/Debuggers/x86/srcsrv/pdbstr.exe',
         'TINDERBOX_OUTPUT': '1',
-        'TOOLTOOL_CACHE': '/c/builds/tooltool_cache',
+        'TOOLTOOL_CACHE': 'c:/builds/tooltool_cache',
         'TOOLTOOL_HOME': '/c/builds',
         'XPCOM_DEBUG_BREAK': 'stack-and-abort',
         'MSYSTEM': 'MINGW32',
     },
     'upload_env': {
         'UPLOAD_HOST': 'localhost',
         'UPLOAD_PATH': os.path.join(os.getcwd(), 'public', 'build'),
     },
--- a/testing/mozharness/configs/builds/taskcluster_firefox_win32_qr_debug.py
+++ b/testing/mozharness/configs/builds/taskcluster_firefox_win32_qr_debug.py
@@ -71,17 +71,17 @@ config = {
         ),
         'HG_SHARE_BASE_DIR': os.path.join('y:', os.sep, 'hg-shared'),
         'MOZBUILD_STATE_PATH': os.path.join(os.getcwd(), '.mozbuild'),
         'MOZ_AUTOMATION': '1',
         'MOZ_CRASHREPORTER_NO_REPORT': '1',
         'MOZ_OBJDIR': 'obj-firefox',
         'PDBSTR_PATH': '/c/Program Files (x86)/Windows Kits/10/Debuggers/x86/srcsrv/pdbstr.exe',
         'TINDERBOX_OUTPUT': '1',
-        'TOOLTOOL_CACHE': '/c/builds/tooltool_cache',
+        'TOOLTOOL_CACHE': 'c:/builds/tooltool_cache',
         'TOOLTOOL_HOME': '/c/builds',
         'XPCOM_DEBUG_BREAK': 'stack-and-abort',
         'MSYSTEM': 'MINGW32',
     },
     'upload_env': {
         'UPLOAD_HOST': 'localhost',
         'UPLOAD_PATH': os.path.join(os.getcwd(), 'public', 'build'),
     },
--- a/testing/mozharness/configs/builds/taskcluster_firefox_win32_qr_opt.py
+++ b/testing/mozharness/configs/builds/taskcluster_firefox_win32_qr_opt.py
@@ -70,17 +70,17 @@ config = {
         ),
         'HG_SHARE_BASE_DIR': os.path.join('y:', os.sep, 'hg-shared'),
         'MOZBUILD_STATE_PATH': os.path.join(os.getcwd(), '.mozbuild'),
         'MOZ_AUTOMATION': '1',
         'MOZ_CRASHREPORTER_NO_REPORT': '1',
         'MOZ_OBJDIR': 'obj-firefox',
         'PDBSTR_PATH': '/c/Program Files (x86)/Windows Kits/10/Debuggers/x86/srcsrv/pdbstr.exe',
         'TINDERBOX_OUTPUT': '1',
-        'TOOLTOOL_CACHE': '/c/builds/tooltool_cache',
+        'TOOLTOOL_CACHE': 'c:/builds/tooltool_cache',
         'TOOLTOOL_HOME': '/c/builds',
         'MSYSTEM': 'MINGW32',
     },
     'upload_env': {
         'UPLOAD_HOST': 'localhost',
         'UPLOAD_PATH': os.path.join(os.getcwd(), 'public', 'build'),
     },
     "check_test_env": {
--- a/testing/mozharness/configs/builds/taskcluster_firefox_win64_clang.py
+++ b/testing/mozharness/configs/builds/taskcluster_firefox_win64_clang.py
@@ -65,17 +65,17 @@ config = {
     'publish_nightly_en_US_routes': True,
     'env': {
         'HG_SHARE_BASE_DIR': os.path.join('y:', os.sep, 'hg-shared'),
         'MOZ_AUTOMATION': '1',
         'MOZ_CRASHREPORTER_NO_REPORT': '1',
         'MOZ_OBJDIR': 'obj-firefox',
         'PDBSTR_PATH': '/c/Program Files (x86)/Windows Kits/10/Debuggers/x64/srcsrv/pdbstr.exe',
         'TINDERBOX_OUTPUT': '1',
-        'TOOLTOOL_CACHE': '/c/builds/tooltool_cache',
+        'TOOLTOOL_CACHE': 'c:/builds/tooltool_cache',
         'TOOLTOOL_HOME': '/c/builds',
         'MSYSTEM': 'MINGW32',
     },
     'upload_env': {
         'UPLOAD_HOST': 'localhost',
         'UPLOAD_PATH': os.path.join(os.getcwd(), 'public', 'build'),
     },
     "check_test_env": {
--- a/testing/mozharness/configs/builds/taskcluster_firefox_win64_clang_debug.py
+++ b/testing/mozharness/configs/builds/taskcluster_firefox_win64_clang_debug.py
@@ -66,17 +66,17 @@ config = {
     'publish_nightly_en_US_routes': True,
     'env': {
         'HG_SHARE_BASE_DIR': os.path.join('y:', os.sep, 'hg-shared'),
         'MOZ_AUTOMATION': '1',
         'MOZ_CRASHREPORTER_NO_REPORT': '1',
         'MOZ_OBJDIR': 'obj-firefox',
         'PDBSTR_PATH': '/c/Program Files (x86)/Windows Kits/10/Debuggers/x64/srcsrv/pdbstr.exe',
         'TINDERBOX_OUTPUT': '1',
-        'TOOLTOOL_CACHE': '/c/builds/tooltool_cache',
+        'TOOLTOOL_CACHE': 'c:/builds/tooltool_cache',
         'TOOLTOOL_HOME': '/c/builds',
         'XPCOM_DEBUG_BREAK': 'stack-and-abort',
         'MSYSTEM': 'MINGW32',
     },
     'upload_env': {
         'UPLOAD_HOST': 'localhost',
         'UPLOAD_PATH': os.path.join(os.getcwd(), 'public', 'build'),
     },
--- a/testing/mozharness/configs/builds/taskcluster_firefox_win64_qr_debug.py
+++ b/testing/mozharness/configs/builds/taskcluster_firefox_win64_qr_debug.py
@@ -62,17 +62,17 @@ config = {
     'publish_nightly_en_US_routes': True,
     'env': {
         'HG_SHARE_BASE_DIR': os.path.join('y:', os.sep, 'hg-shared'),
         'MOZ_AUTOMATION': '1',
         'MOZ_CRASHREPORTER_NO_REPORT': '1',
         'MOZ_OBJDIR': 'obj-firefox',
         'PDBSTR_PATH': '/c/Program Files (x86)/Windows Kits/10/Debuggers/x64/srcsrv/pdbstr.exe',
         'TINDERBOX_OUTPUT': '1',
-        'TOOLTOOL_CACHE': '/c/builds/tooltool_cache',
+        'TOOLTOOL_CACHE': 'c:/builds/tooltool_cache',
         'TOOLTOOL_HOME': '/c/builds',
         'XPCOM_DEBUG_BREAK': 'stack-and-abort',
         'MSYSTEM': 'MINGW32',
     },
     'upload_env': {
         'UPLOAD_HOST': 'localhost',
         'UPLOAD_PATH': os.path.join(os.getcwd(), 'public', 'build'),
     },
--- a/testing/mozharness/configs/builds/taskcluster_firefox_win64_qr_opt.py
+++ b/testing/mozharness/configs/builds/taskcluster_firefox_win64_qr_opt.py
@@ -61,17 +61,17 @@ config = {
     'publish_nightly_en_US_routes': True,
     'env': {
         'HG_SHARE_BASE_DIR': os.path.join('y:', os.sep, 'hg-shared'),
         'MOZ_AUTOMATION': '1',
         'MOZ_CRASHREPORTER_NO_REPORT': '1',
         'MOZ_OBJDIR': 'obj-firefox',
         'PDBSTR_PATH': '/c/Program Files (x86)/Windows Kits/10/Debuggers/x64/srcsrv/pdbstr.exe',
         'TINDERBOX_OUTPUT': '1',
-        'TOOLTOOL_CACHE': '/c/builds/tooltool_cache',
+        'TOOLTOOL_CACHE': 'c:/builds/tooltool_cache',
         'TOOLTOOL_HOME': '/c/builds',
         'MSYSTEM': 'MINGW32',
     },
     'upload_env': {
         'UPLOAD_HOST': 'localhost',
         'UPLOAD_PATH': os.path.join(os.getcwd(), 'public', 'build'),
     },
     "check_test_env": {
--- a/testing/mozharness/configs/builds/taskcluster_firefox_windows_32_addondevel.py
+++ b/testing/mozharness/configs/builds/taskcluster_firefox_windows_32_addondevel.py
@@ -70,17 +70,17 @@ config = {
         ),
         'HG_SHARE_BASE_DIR': os.path.join('y:', os.sep, 'hg-shared'),
         'MOZBUILD_STATE_PATH': os.path.join(os.getcwd(), '.mozbuild'),
         'MOZ_AUTOMATION': '1',
         'MOZ_CRASHREPORTER_NO_REPORT': '1',
         'MOZ_OBJDIR': 'obj-firefox',
         'PDBSTR_PATH': '/c/Program Files (x86)/Windows Kits/10/Debuggers/x86/srcsrv/pdbstr.exe',
         'TINDERBOX_OUTPUT': '1',
-        'TOOLTOOL_CACHE': '/c/builds/tooltool_cache',
+        'TOOLTOOL_CACHE': 'c:/builds/tooltool_cache',
         'TOOLTOOL_HOME': '/c/builds',
         'MSYSTEM': 'MINGW32',
     },
     'upload_env': {
         'UPLOAD_HOST': 'localhost',
         'UPLOAD_PATH': os.path.join(os.getcwd(), 'public', 'build'),
     },
     "check_test_env": {
--- a/testing/mozharness/configs/builds/taskcluster_firefox_windows_32_debug.py
+++ b/testing/mozharness/configs/builds/taskcluster_firefox_windows_32_debug.py
@@ -71,17 +71,17 @@ config = {
         ),
         'HG_SHARE_BASE_DIR': os.path.join('y:', os.sep, 'hg-shared'),
         'MOZBUILD_STATE_PATH': os.path.join(os.getcwd(), '.mozbuild'),
         'MOZ_AUTOMATION': '1',
         'MOZ_CRASHREPORTER_NO_REPORT': '1',
         'MOZ_OBJDIR': 'obj-firefox',
         'PDBSTR_PATH': '/c/Program Files (x86)/Windows Kits/10/Debuggers/x86/srcsrv/pdbstr.exe',
         'TINDERBOX_OUTPUT': '1',
-        'TOOLTOOL_CACHE': '/c/builds/tooltool_cache',
+        'TOOLTOOL_CACHE': 'c:/builds/tooltool_cache',
         'TOOLTOOL_HOME': '/c/builds',
         'XPCOM_DEBUG_BREAK': 'stack-and-abort',
         'MSYSTEM': 'MINGW32',
     },
     'upload_env': {
         'UPLOAD_HOST': 'localhost',
         'UPLOAD_PATH': os.path.join(os.getcwd(), 'public', 'build'),
     },
--- a/testing/mozharness/configs/builds/taskcluster_firefox_windows_32_opt.py
+++ b/testing/mozharness/configs/builds/taskcluster_firefox_windows_32_opt.py
@@ -70,17 +70,17 @@ config = {
         ),
         'HG_SHARE_BASE_DIR': os.path.join('y:', os.sep, 'hg-shared'),
         'MOZBUILD_STATE_PATH': os.path.join(os.getcwd(), '.mozbuild'),
         'MOZ_AUTOMATION': '1',
         'MOZ_CRASHREPORTER_NO_REPORT': '1',
         'MOZ_OBJDIR': 'obj-firefox',
         'PDBSTR_PATH': '/c/Program Files (x86)/Windows Kits/10/Debuggers/x86/srcsrv/pdbstr.exe',
         'TINDERBOX_OUTPUT': '1',
-        'TOOLTOOL_CACHE': '/c/builds/tooltool_cache',
+        'TOOLTOOL_CACHE': 'c:/builds/tooltool_cache',
         'TOOLTOOL_HOME': '/c/builds',
         'MSYSTEM': 'MINGW32',
     },
     'upload_env': {
         'UPLOAD_HOST': 'localhost',
         'UPLOAD_PATH': os.path.join(os.getcwd(), 'public', 'build'),
     },
     "check_test_env": {
--- a/testing/mozharness/configs/builds/taskcluster_firefox_windows_64_addondevel.py
+++ b/testing/mozharness/configs/builds/taskcluster_firefox_windows_64_addondevel.py
@@ -67,17 +67,17 @@ config = {
     'publish_nightly_en_US_routes': False,
     'env': {
         'HG_SHARE_BASE_DIR': os.path.join('y:', os.sep, 'hg-shared'),
         'MOZ_AUTOMATION': '1',
         'MOZ_CRASHREPORTER_NO_REPORT': '1',
         'MOZ_OBJDIR': 'obj-firefox',
         'PDBSTR_PATH': '/c/Program Files (x86)/Windows Kits/10/Debuggers/x64/srcsrv/pdbstr.exe',
         'TINDERBOX_OUTPUT': '1',
-        'TOOLTOOL_CACHE': '/c/builds/tooltool_cache',
+        'TOOLTOOL_CACHE': 'c:/builds/tooltool_cache',
         'TOOLTOOL_HOME': '/c/builds',
         'MSYSTEM': 'MINGW32',
     },
     'upload_env': {
         'UPLOAD_HOST': 'localhost',
         'UPLOAD_PATH': os.path.join(os.getcwd(), 'public', 'build'),
     },
     "check_test_env": {
--- a/testing/mozharness/configs/builds/taskcluster_firefox_windows_64_debug.py
+++ b/testing/mozharness/configs/builds/taskcluster_firefox_windows_64_debug.py
@@ -67,17 +67,17 @@ config = {
     'publish_nightly_en_US_routes': True,
     'env': {
         'HG_SHARE_BASE_DIR': os.path.join('y:', os.sep, 'hg-shared'),
         'MOZ_AUTOMATION': '1',
         'MOZ_CRASHREPORTER_NO_REPORT': '1',
         'MOZ_OBJDIR': 'obj-firefox',
         'PDBSTR_PATH': '/c/Program Files (x86)/Windows Kits/10/Debuggers/x64/srcsrv/pdbstr.exe',
         'TINDERBOX_OUTPUT': '1',
-        'TOOLTOOL_CACHE': '/c/builds/tooltool_cache',
+        'TOOLTOOL_CACHE': 'c:/builds/tooltool_cache',
         'TOOLTOOL_HOME': '/c/builds',
         'XPCOM_DEBUG_BREAK': 'stack-and-abort',
         'MSYSTEM': 'MINGW32',
     },
     'upload_env': {
         'UPLOAD_HOST': 'localhost',
         'UPLOAD_PATH': os.path.join(os.getcwd(), 'public', 'build'),
     },
--- a/testing/mozharness/configs/builds/taskcluster_firefox_windows_64_opt.py
+++ b/testing/mozharness/configs/builds/taskcluster_firefox_windows_64_opt.py
@@ -66,17 +66,17 @@ config = {
     'publish_nightly_en_US_routes': True,
     'env': {
         'HG_SHARE_BASE_DIR': os.path.join('y:', os.sep, 'hg-shared'),
         'MOZ_AUTOMATION': '1',
         'MOZ_CRASHREPORTER_NO_REPORT': '1',
         'MOZ_OBJDIR': 'obj-firefox',
         'PDBSTR_PATH': '/c/Program Files (x86)/Windows Kits/10/Debuggers/x64/srcsrv/pdbstr.exe',
         'TINDERBOX_OUTPUT': '1',
-        'TOOLTOOL_CACHE': '/c/builds/tooltool_cache',
+        'TOOLTOOL_CACHE': 'c:/builds/tooltool_cache',
         'TOOLTOOL_HOME': '/c/builds',
         'MSYSTEM': 'MINGW32',
     },
     'upload_env': {
         'UPLOAD_HOST': 'localhost',
         'UPLOAD_PATH': os.path.join(os.getcwd(), 'public', 'build'),
     },
     "check_test_env": {
--- a/testing/mozharness/configs/releases/bouncer_firefox_beta.py
+++ b/testing/mozharness/configs/releases/bouncer_firefox_beta.py
@@ -65,16 +65,20 @@ config = {
             "alias": "firefox-beta-stub",
             "ssl-only": True,
             "add-locales": True,
             "paths": {
                 "win32": {
                     "path": "/firefox/releases/%(version)s/win32/:lang/Firefox%%20Setup%%20Stub%%20%(version)s.exe",
                     "bouncer-platform": "win",
                 },
+                "win64": {
+                    "path": "/firefox/releases/%(version)s/win32/:lang/Firefox%%20Setup%%20Stub%%20%(version)s.exe",
+                    "bouncer-platform": "win64",
+                },
             },
         },
         "complete-mar": {
             "product-name": "Firefox-%(version)s-Complete",
             "check_uptake": True,
             "ssl-only": False,
             "add-locales": True,
             "paths": {
--- a/testing/mozharness/configs/releases/bouncer_firefox_release.py
+++ b/testing/mozharness/configs/releases/bouncer_firefox_release.py
@@ -65,16 +65,20 @@ config = {
             "alias": "firefox-stub",
             "ssl-only": True,
             "add-locales": True,
             "paths": {
                 "win32": {
                     "path": "/firefox/releases/%(version)s/win32/:lang/Firefox%%20Setup%%20Stub%%20%(version)s.exe",
                     "bouncer-platform": "win",
                 },
+                "win64": {
+                    "path": "/firefox/releases/%(version)s/win32/:lang/Firefox%%20Setup%%20Stub%%20%(version)s.exe",
+                    "bouncer-platform": "win64",
+                },
             },
         },
         "complete-mar": {
             "product-name": "Firefox-%(version)s-Complete",
             "check_uptake": True,
             "ssl-only": False,
             "add-locales": True,
             "paths": {
--- a/testing/mozharness/configs/releases/dev_bouncer_firefox_beta.py
+++ b/testing/mozharness/configs/releases/dev_bouncer_firefox_beta.py
@@ -64,16 +64,20 @@ config = {
             "alias": "firefox-beta-stub",
             "ssl-only": True,
             "add-locales": False,
             "paths": {
                 "win32": {
                     "path": "/firefox/releases/%(version)s/win32/:lang/Firefox%%20Setup%%20Stub%%20%(version)s.exe",
                     "bouncer-platform": "win",
                 },
+                "win64": {
+                    "path": "/firefox/releases/%(version)s/win32/:lang/Firefox%%20Setup%%20Stub%%20%(version)s.exe",
+                    "bouncer-platform": "win64",
+                },
             },
         },
         "complete-mar": {
             "product-name": "Firefox-%(version)s-Complete",
             "check_uptake": True,
             "ssl-only": False,
             "add-locales": False,
             "paths": {
--- a/testing/mozharness/configs/single_locale/win32.py
+++ b/testing/mozharness/configs/single_locale/win32.py
@@ -11,17 +11,17 @@ config = {
         "EN_US_BINARY_URL": "%(en_us_binary_url)s",
         "LOCALE_MERGEDIR": "%(abs_merge_dir)s",
         "MOZ_UPDATE_CHANNEL": "%(update_channel)s",
         "DIST": "%(abs_objdir)s",
         "L10NBASEDIR": "../../l10n",
         "MOZ_MAKE_COMPLETE_MAR": "1",
         "PATH": 'C:\\mozilla-build\\nsis-3.0b1;'
                 '%s' % (os.environ.get('path')),
-        'TOOLTOOL_CACHE': '/c/builds/tooltool_cache',
+        'TOOLTOOL_CACHE': 'c:/builds/tooltool_cache',
         'TOOLTOOL_HOME': '/c/builds',
     },
     "ssh_key_dir": "~/.ssh",
     "log_name": "single_locale",
     "objdir": "obj-l10n",
     "js_src_dir": "js/src",
     "vcs_share_base": "c:/builds/hg-shared",
 
--- a/testing/mozharness/configs/single_locale/win64.py
+++ b/testing/mozharness/configs/single_locale/win64.py
@@ -11,17 +11,17 @@ config = {
         "EN_US_BINARY_URL": "%(en_us_binary_url)s",
         "MOZ_UPDATE_CHANNEL": "%(update_channel)s",
         "DIST": "%(abs_objdir)s",
         "LOCALE_MERGEDIR": "%(abs_merge_dir)s",
         "L10NBASEDIR": "../../l10n",
         "MOZ_MAKE_COMPLETE_MAR": "1",
         "PATH": 'C:\\mozilla-build\\nsis-3.0b1;'
                 '%s' % (os.environ.get('path')),
-        'TOOLTOOL_CACHE': '/c/builds/tooltool_cache',
+        'TOOLTOOL_CACHE': 'c:/builds/tooltool_cache',
         'TOOLTOOL_HOME': '/c/builds',
     },
     "ssh_key_dir": "~/.ssh",
     "log_name": "single_locale",
     "objdir": "obj-l10n",
     "js_src_dir": "js/src",
     "vcs_share_base": "c:/builds/hg-shared",
 
new file mode 100644
--- /dev/null
+++ b/testing/mozharness/mozharness/mozilla/merkle.py
@@ -0,0 +1,176 @@
+#!/usr/bin/env python
+
+import struct
+
+def _round2(n):
+    k = 1
+    while k < n:
+        k <<= 1
+    return k >> 1
+
+def _leaf_hash(hash_fn, leaf):
+    return hash_fn(b'\x00' + leaf).digest()
+
+def _pair_hash(hash_fn, left, right):
+    return hash_fn(b'\x01' + left + right).digest()
+
+class InclusionProof:
+    """
+    Represents a Merkle inclusion proof for purposes of serialization,
+    deserialization, and verification of the proof.  The format for inclusion
+    proofs in RFC 6962-bis is as follows:
+
+        opaque LogID<2..127>;
+        opaque NodeHash<32..2^8-1>;
+
+        struct {
+            LogID log_id;
+            uint64 tree_size;
+            uint64 leaf_index;
+            NodeHash inclusion_path<1..2^16-1>;
+        } InclusionProofDataV2;
+
+    In other words:
+      - 1 + N octets of log_id (currently zero)
+      - 8 octets of tree_size = self.n
+      - 8 octets of leaf_index = m
+      - 2 octets of path length, followed by
+      * 1 + N octets of NodeHash
+    """
+
+    # Pre-generated 'log ID'.  Not used by Firefox; it is only needed because
+    # there's a slot in the RFC 6962-bis format that requires a value at least
+    # two bytes long (plus a length byte).
+    LOG_ID = b'\x02\x00\x00'
+
+    def __init__(self, tree_size, leaf_index, path_elements):
+        self.tree_size = tree_size
+        self.leaf_index = leaf_index
+        self.path_elements = path_elements
+
+    @staticmethod
+    def from_rfc6962_bis(serialized):
+        start = 0
+        read = 1
+        if len(serialized) < start + read:
+            raise Exception('Inclusion proof too short for log ID header')
+        log_id_len, = struct.unpack('B', serialized[start:start+read])
+        start += read
+        start += log_id_len # Ignore the log ID itself
+
+        read = 8 + 8 + 2
+        if len(serialized) < start + read:
+            raise Exception('Inclusion proof too short for middle section')
+        tree_size, leaf_index, path_len = struct.unpack('!QQH', serialized[start:start+read])
+        start += read
+
+        path_elements = []
+        end = 1 + log_id_len + 8 + 8 + 2 + path_len
+        while start < end:
+            read = 1
+            if len(serialized) < start + read:
+                raise Exception('Inclusion proof too short for middle section')
+            elem_len, = struct.unpack('!B', serialized[start:start+read])
+            start += read
+
+            read = elem_len
+            if len(serialized) < start + read:
+                raise Exception('Inclusion proof too short for middle section')
+            if end < start + read:
+                raise Exception('Inclusion proof element exceeds declared length')
+            path_elements.append(serialized[start:start+read])
+            start += read
+
+        return InclusionProof(tree_size, leaf_index, path_elements)
+
+    def to_rfc6962_bis(self):
+        inclusion_path = b''
+        for step in self.path_elements:
+            step_len = struct.pack('B', len(step))
+            inclusion_path += step_len + step
+
+        middle = struct.pack('!QQH', self.tree_size, self.leaf_index, len(inclusion_path))
+        return self.LOG_ID + middle + inclusion_path
+
+    def _expected_head(self, hash_fn, leaf, leaf_index, tree_size):
+        node = _leaf_hash(hash_fn, leaf)
+
+        # Compute indicators of which direction the pair hashes should be done.
+        # Derived from the PATH logic in draft-ietf-trans-rfc6962-bis
+        lr = []
+        while tree_size > 1:
+            k = _round2(tree_size)
+            left = leaf_index < k
+            lr = [left] + lr
+
+            if left:
+                tree_size = k
+            else:
+                tree_size = tree_size - k
+                leaf_index = leaf_index - k
+
+        assert(len(lr) == len(self.path_elements))
+        for i, elem in enumerate(self.path_elements):
+            if lr[i]:
+                node = _pair_hash(hash_fn, node, elem)
+            else:
+                node = _pair_hash(hash_fn, elem, node)
+
+        return node
+
+
+    def verify(self, hash_fn, leaf, leaf_index, tree_size, tree_head):
+        return self._expected_head(hash_fn, leaf, leaf_index, tree_size) == tree_head
+
+class MerkleTree:
+    """
+    Implements a Merkle tree on a set of data items following the
+    structure defined in RFC 6962-bis.  This allows us to create a
+    single hash value that summarizes the data (the 'head'), and an
+    'inclusion proof' for each element that connects it to the head.
+
+    https://tools.ietf.org/html/draft-ietf-trans-rfc6962-bis-24
+    """
+
+    def __init__(self, hash_fn, data):
+        self.n = len(data)
+        self.hash_fn = hash_fn
+
+        # We cache intermediate node values, as a dictionary of dictionaries,
+        # where the node representing data elements data[m:n] is represented by
+        # nodes[m][n]. This corresponds to the 'D[m:n]' notation in RFC
+        # 6962-bis.  In particular, the leaves are stored in nodes[i][i+1] and
+        # the head is nodes[0][n].
+        self.nodes = {}
+        for i in range(self.n):
+            self.nodes[i, i+1] = _leaf_hash(self.hash_fn, data[i])
+
+    def _node(self, start, end):
+        if (start, end) in self.nodes:
+            return self.nodes[start, end]
+
+        k = _round2(end - start)
+        left = self._node(start, start + k)
+        right = self._node(start + k, end)
+        node = _pair_hash(self.hash_fn, left, right)
+
+        self.nodes[start, end] = node
+        return node
+
+    def head(self):
+        return self._node(0, self.n)
+
+    def _relative_proof(self, target, start, end):
+        n = end - start
+        k = _round2(n)
+
+        if n == 1:
+            return []
+        elif target - start < k:
+            return self._relative_proof(target, start, start + k) + [self._node(start + k, end)]
+        elif target - start >= k:
+            return self._relative_proof(target, start + k, end) + [self._node(start, start + k)]
+
+    def inclusion_proof(self, leaf_index):
+        path_elements = self._relative_proof(leaf_index, 0, self.n)
+        return InclusionProof(self.n, leaf_index, path_elements)
--- a/testing/mozharness/scripts/release/generate-checksums.py
+++ b/testing/mozharness/scripts/release/generate-checksums.py
@@ -1,23 +1,25 @@
 from multiprocessing.pool import ThreadPool
 import os
 from os import path
 import re
 import sys
 import posixpath
+import hashlib
 
 sys.path.insert(1, os.path.dirname(os.path.dirname(sys.path[0])))
 
 from mozharness.base.python import VirtualenvMixin, virtualenv_config_options
 from mozharness.base.script import BaseScript
 from mozharness.base.vcs.vcsbase import VCSMixin
 from mozharness.mozilla.checksums import parse_checksums_file
 from mozharness.mozilla.signing import SigningMixin
 from mozharness.mozilla.buildbot import BuildbotMixin
+from mozharness.mozilla.merkle import MerkleTree
 
 class ChecksumsGenerator(BaseScript, VirtualenvMixin, SigningMixin, VCSMixin, BuildbotMixin):
     config_options = [
         [["--stage-product"], {
             "dest": "stage_product",
             "help": "Name of product used in file server's directory structure, eg: firefox, mobile",
         }],
         [["--version"], {
@@ -74,24 +76,26 @@ class ChecksumsGenerator(BaseScript, Vir
                 ],
                 "virtualenv_path": "venv",
                 'buildbot_json_path': 'buildprops.json',
             },
             all_actions=[
                 "create-virtualenv",
                 "collect-individual-checksums",
                 "create-big-checksums",
+                "create-summary",
                 "sign",
                 "upload",
                 "copy-info-files",
             ],
             default_actions=[
                 "create-virtualenv",
                 "collect-individual-checksums",
                 "create-big-checksums",
+                "create-summary",
                 "sign",
                 "upload",
             ],
         )
 
         self.checksums = {}
         self.bucket = None
         self.bucket_name = self._get_bucket_name()
@@ -147,16 +151,25 @@ class ChecksumsGenerator(BaseScript, Vir
     def _get_file_prefix(self):
         return "pub/{}/candidates/{}-candidates/build{}/".format(
             self.config["stage_product"], self.config["version"], self.config["build_number"]
         )
 
     def _get_sums_filename(self, format_):
         return "{}SUMS".format(format_.upper())
 
+    def _get_summary_filename(self, format_):
+        return "{}SUMMARY".format(format_.upper())
+
+    def _get_hash_function(self, format_):
+        if format_ in ("sha256", "sha384", "sha512"):
+            return getattr(hashlib, format_)
+        else:
+            self.fatal("Unsupported format {}".format(format_))
+
     def _get_bucket(self):
         if not self.bucket:
             self.activate_virtualenv()
             from boto.s3.connection import S3Connection
 
             self.info("Connecting to S3")
             conn = S3Connection()
             self.debug("Successfully connected to S3")
@@ -211,16 +224,40 @@ class ChecksumsGenerator(BaseScript, Vir
                         if not set(self.config["formats"]) <= set(info["hashes"]):
                             self.fatal("Missing necessary format for file {}".format(f))
                         self.debug("Adding checksums for file: {}".format(f))
                         self.checksums[f] = info
                         break
                 else:
                     self.debug("Ignoring checksums for file: {}".format(f))
 
+    def create_summary(self):
+        """
+        This step computes a Merkle tree over the checksums for each format
+        and writes a file containing the head of the tree and inclusion proofs
+        for each file.
+        """
+        for fmt in self.config["formats"]:
+            hash_fn = self._get_hash_function(fmt)
+            files = [fn for fn in sorted(self.checksums)]
+            data = [self.checksums[fn]["hashes"][fmt] for fn in files]
+
+            tree = MerkleTree(hash_fn, data)
+            head = tree.head().encode("hex")
+            proofs = [tree.inclusion_proof(i).to_rfc6962_bis().encode("hex") for i in range(len(files))]
+
+            summary = self._get_summary_filename(fmt)
+            self.info("Creating summary file: {}".format(summary))
+
+            content = "{} TREE_HEAD\n".format(head)
+            for i in range(len(files)):
+                content += "{} {}\n".format(proofs[i], files[i])
+
+            self.write_to_file(summary, content)
+
     def create_big_checksums(self):
         for fmt in self.config["formats"]:
             sums = self._get_sums_filename(fmt)
             self.info("Creating big checksums file: {}".format(sums))
             with open(sums, "w+") as output_file:
                 for fn in sorted(self.checksums):
                     output_file.write("{}  {}\n".format(self.checksums[fn]["hashes"][fmt], fn))
 
new file mode 100644
--- /dev/null
+++ b/testing/mozharness/test/test_mozilla_merkle.py
@@ -0,0 +1,109 @@
+import unittest
+import hashlib
+import random
+from mozharness.mozilla.merkle import MerkleTree, InclusionProof
+
+# Pre-computed tree on 7 inputs
+#
+#         ______F_____
+#        /            \
+#     __D__           _E_
+#    /     \         /   \
+#   A       B       C     |
+#  / \     / \     / \    |
+# 0   1   2   3   4   5   6
+hash_fn = hashlib.sha256
+
+data = [
+    'fbc459361fc111024c6d1fd83d23a9ff'.decode('hex'),
+    'ae3a44925afec860451cd8658b3cadde'.decode('hex'),
+    '418903fe6ef29fc8cab93d778a7b018b'.decode('hex'),
+    '3d1c53c00b2e137af8c4c23a06388c6b'.decode('hex'),
+    'e656ebd8e2758bc72599e5896be357be'.decode('hex'),
+    '81aae91cf90be172eedd1c75c349bf9e'.decode('hex'),
+    '00c262edf8b0bc345aca769e8733e25e'.decode('hex'),
+]
+
+leaves = [
+    '5cb551f87797381a24a5359a986e2cef25b1f2113b387197fe48e8babc9ad5c7'.decode('hex'),
+    '9899dc0be00306bda2a8e69cec32525ca6244f132479bcf840d8c1bc8bdfbff2'.decode('hex'),
+    'fdd27d0393e32637b474efb9b3efad29568c3ec9b091fdda40fd57ec9196f06d'.decode('hex'),
+    'c87292a6c8528c2a0679b6c1eefb47e4dbac7840d23645d5b7cb47cf1a8d365f'.decode('hex'),
+    '2ff3bdac9bec3580b82da8a357746f15919414d9cbe517e2dd96910c9814c30c'.decode('hex'),
+    '883e318240eccc0e2effafebdb0fd4fd26d0996da1b01439566cb9babef8725f'.decode('hex'),
+    'bb13dfb7b202a95f241ea1715c8549dc048d9936ec747028002f7c795de72fcf'.decode('hex'),
+]
+
+nodeA = '06447a7baa079cb0b4b6119d0f575bec508915403fdc30923eba982b63759805'.decode('hex')
+nodeB = '3db98027c655ead4fe897bef3a4b361839a337941a9e624b475580c9d4e882ee'.decode('hex')
+nodeC = '17524f8b0169b2745c67846925d55449ae80a8022ef8189dcf4cbb0ec7fcc470'.decode('hex')
+nodeD = '380d0dc6fd7d4f37859a12dbfc7171b3cce29ab0688c6cffd2b15f3e0b21af49'.decode('hex')
+nodeE = '3a9c2886a5179a6e1948876034f99d52a8f393f47a09887adee6d1b4a5c5fbd6'.decode('hex')
+nodeF = 'd1a0d3947db4ae8305f2ac32985957e02659b2ea3c10da52a48d2526e9af3bbc'.decode('hex')
+
+proofs = [
+    [leaves[1], nodeB, nodeE],
+    [leaves[0], nodeB, nodeE],
+    [leaves[3], nodeA, nodeE],
+    [leaves[2], nodeA, nodeE],
+    [leaves[5], leaves[6], nodeD],
+    [leaves[4], leaves[6], nodeD],
+    [nodeC, nodeD],
+]
+
+known_proof5 = ('020000' + \
+                '0000000000000007' + '0000000000000005' + \
+                '0063' + \
+                '20' + leaves[4].encode('hex') + \
+                '20' + leaves[6].encode('hex') + \
+                '20' + nodeD.encode('hex')).decode('hex')
+
+
+class TestMerkleTree(unittest.TestCase):
+    def testPreComputed(self):
+        tree = MerkleTree(hash_fn, data)
+        head = tree.head()
+        self.assertEquals(head, nodeF)
+
+        for i in range(len(data)):
+            proof = tree.inclusion_proof(i)
+
+            self.assertTrue(proof.verify(hash_fn, data[i], i, len(data), head))
+            self.assertEquals(proof.leaf_index, i)
+            self.assertEquals(proof.tree_size, tree.n)
+            self.assertEquals(proof.path_elements, proofs[i])
+
+
+    def testInclusionProofEncodeDecode(self):
+        tree = MerkleTree(hash_fn, data)
+
+        # Inclusion proof encode/decode round trip test
+        proof5 = tree.inclusion_proof(5)
+        serialized5 = proof5.to_rfc6962_bis()
+        deserialized5 = InclusionProof.from_rfc6962_bis(serialized5)
+        reserialized5 = deserialized5.to_rfc6962_bis()
+        self.assertEquals(serialized5, reserialized5)
+
+        # Inclusion proof encode known answer test
+        serialized5 = proof5.to_rfc6962_bis()
+        self.assertEquals(serialized5, known_proof5)
+
+        # Inclusion proof decode known answer test
+        known_deserialized5 = InclusionProof.from_rfc6962_bis(known_proof5)
+        self.assertEquals(proof5.leaf_index, known_deserialized5.leaf_index)
+        self.assertEquals(proof5.tree_size, known_deserialized5.tree_size)
+        self.assertEquals(proof5.path_elements, known_deserialized5.path_elements)
+
+    def testLargeTree(self):
+        TEST_SIZE = 5000
+        ELEM_SIZE_BYTES = 16
+        data = [bytearray(random.getrandbits(8) for _ in xrange(ELEM_SIZE_BYTES)) for _ in xrange(TEST_SIZE)]
+        tree = MerkleTree(hash_fn, data)
+        head = tree.head()
+
+        for i in range(len(data)):
+            proof = tree.inclusion_proof(i)
+
+            self.assertTrue(proof.verify(hash_fn, data[i], i, len(data), head))
+            self.assertEquals(proof.leaf_index, i)
+            self.assertEquals(proof.tree_size, tree.n)
--- a/toolkit/components/extensions/ExtensionPreferencesManager.jsm
+++ b/toolkit/components/extensions/ExtensionPreferencesManager.jsm
@@ -52,32 +52,42 @@ function initialValueCallback() {
   let initialValue = {};
   for (let pref of this.prefNames) {
     initialValue[pref] = Preferences.get(pref);
   }
   return initialValue;
 }
 
 /**
- * Takes an object of preferenceName:value pairs and either sets or resets the
- * preference to the value.
+ * Takes an item returned by the ExtensionSettingsStore and conditionally sets
+ * preferences based on the item's contents.
  *
- * @param {Object} prefsObject
- *        An object with one property per preference, which holds the value to
- *        store in that preference. If the value is undefined then the
- *        preference is reset.
- */
-function setPrefs(prefsObject) {
-  for (let pref in prefsObject) {
-    if (prefsObject[pref] === undefined) {
-      Preferences.reset(pref);
-    } else {
-      Preferences.set(pref, prefsObject[pref]);
+ * @param {string} name
+ *        The name of the setting being processed.
+ * @param {Object|null} item
+ *        Either null, or an object with a value property which indicates the
+ *        value stored for the setting in the settings store.
+
+ * @returns {Promise}
+ *          Resolves to true if the preferences were changed and to false if
+ *          the preferences were not changed.
+*/
+async function processItem(name, item) {
+  if (item) {
+    let prefs = item.initialValue || await settingsMap.get(name).setCallback(item.value);
+    for (let pref in prefs) {
+      if (prefs[pref] === undefined) {
+        Preferences.reset(pref);
+      } else {
+        Preferences.set(pref, prefs[pref]);
+      }
     }
+    return true;
   }
+  return false;
 }
 
 this.ExtensionPreferencesManager = {
   /**
    * Adds a setting to the settingsMap. This is how an API tells the
    * preferences manager what its setting object is. The preferences
    * manager needs to know this when settings need to be removed
    * automatically.
@@ -117,52 +127,119 @@ this.ExtensionPreferencesManager = {
    * @returns {Promise}
    *          Resolves to true if the preferences were changed and to false if
    *          the preferences were not changed.
    */
   async setSetting(extension, name, value) {
     let setting = settingsMap.get(name);
     let item = await ExtensionSettingsStore.addSetting(
       extension, STORE_TYPE, name, value, initialValueCallback.bind(setting));
-    if (item) {
-      let prefs = await setting.setCallback(item.value);
-      setPrefs(prefs);
-      return true;
-    }
-    return false;
+    return await processItem(name, item);
   },
 
   /**
-   * Indicates that this extension no longer wants to set the given preference.
+   * Indicates that this extension wants to temporarily cede control over the
+   * given setting.
+   *
+   * @param {Extension} extension
+   *        The extension for which a preference setting is being removed.
+   * @param {string} name
+   *        The unique id of the setting.
+   *
+   * @returns {Promise}
+   *          Resolves to true if the preferences were changed and to false if
+   *          the preferences were not changed.
+   */
+  async disableSetting(extension, name) {
+    let item = await ExtensionSettingsStore.disable(
+      extension, STORE_TYPE, name);
+    return await processItem(name, item);
+  },
+
+  /**
+   * Enable a setting that has been disabled.
+   *
+   * @param {Extension} extension
+   *        The extension for which a setting is being enabled.
+   * @param {string} name
+   *        The unique id of the setting.
+   *
+   * @returns {Promise}
+   *          Resolves to true if the preferences were changed and to false if
+   *          the preferences were not changed.
+   */
+  async enableSetting(extension, name) {
+    let item = await ExtensionSettingsStore.enable(extension, STORE_TYPE, name);
+    return await processItem(name, item);
+  },
+
+  /**
+   * Indicates that this extension no longer wants to set the given setting.
    *
    * @param {Extension} extension
    *        The extension for which a preference setting is being removed.
    * @param {string} name
    *        The unique id of the setting.
+   *
+   * @returns {Promise}
+   *          Resolves to true if the preferences were changed and to false if
+   *          the preferences were not changed.
    */
-  async unsetSetting(extension, name) {
+  async removeSetting(extension, name) {
     let item = await ExtensionSettingsStore.removeSetting(
       extension, STORE_TYPE, name);
-    if (item) {
-      let prefs = item.initialValue || await settingsMap.get(name).setCallback(item.value);
-      setPrefs(prefs);
+    return await processItem(name, item);
+  },
+
+  /**
+   * Disables all previously set settings for an extension. This can be called when
+   * an extension is being disabled, for example.
+   *
+   * @param {Extension} extension
+   *        The extension for which all settings are being unset.
+   */
+  async disableAll(extension) {
+    let settings = await ExtensionSettingsStore.getAllForExtension(extension, STORE_TYPE);
+    let disablePromises = [];
+    for (let name of settings) {
+      disablePromises.push(this.disableSetting(extension, name));
     }
+    await Promise.all(disablePromises);
   },
 
   /**
-   * Unsets all previously set settings for an extension. This can be called when
-   * an extension is being uninstalled or disabled, for example.
+   * Enables all disabled settings for an extension. This can be called when
+   * an extension has finsihed updating or is being re-enabled, for example.
    *
-   * @param {Extension} extension The extension for which all settings are being unset.
+   * @param {Extension} extension
+   *        The extension for which all settings are being enabled.
    */
-  async unsetAll(extension) {
+  async enableAll(extension) {
     let settings = await ExtensionSettingsStore.getAllForExtension(extension, STORE_TYPE);
+    let enablePromises = [];
     for (let name of settings) {
-      await this.unsetSetting(extension, name);
+      enablePromises.push(this.enableSetting(extension, name));
     }
+    await Promise.all(enablePromises);
+  },
+
+  /**
+   * Removes all previously set settings for an extension. This can be called when
+   * an extension is being uninstalled, for example.
+   *
+   * @param {Extension} extension
+   *        The extension for which all settings are being unset.
+   */
+  async removeAll(extension) {
+    let settings = await ExtensionSettingsStore.getAllForExtension(extension, STORE_TYPE);
+    let removePromises = [];
+    for (let name of settings) {
+      removePromises.push(this.removeSetting(extension, name));
+    }
+    await Promise.all(removePromises);
   },
 
   /**
    * Return the levelOfControl for a setting / extension combo.
    * This queries the levelOfControl from the ExtensionSettingsStore and also
    * takes into account whether any of the setting's preferences are locked.
    *
    * @param {Extension} extension
--- a/toolkit/components/extensions/ExtensionSettingsStore.jsm
+++ b/toolkit/components/extensions/ExtensionSettingsStore.jsm
@@ -19,17 +19,18 @@
  * {
  *   type: { // The type of settings being stored in this object, i.e., prefs.
  *     key: { // The unique key for the setting.
  *       initialValue, // The initial value of the setting.
  *       precedenceList: [
  *         {
  *           id, // The id of the extension requesting the setting.
  *           installDate, // The install date of the extension.
- *           value // The value of the setting requested by the extension.
+ *           value, // The value of the setting requested by the extension.
+ *           enabled // Whether the setting is currently enabled.
  *         }
  *       ],
  *     },
  *     key: {
  *       // ...
  *     }
  *   }
  * }
@@ -52,48 +53,133 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource://gre/modules/JSONFile.jsm");
 
 const JSON_FILE_NAME = "extension-settings.json";
 const STORE_PATH = OS.Path.join(Services.dirsvc.get("ProfD", Ci.nsIFile).path, JSON_FILE_NAME);
 
 let _store;
 
 // Get the internal settings store, which is persisted in a JSON file.
-async function getStore(type) {
+function getStore(type) {
   if (!_store) {
-    _store = new JSONFile({
+    let initStore = new JSONFile({
       path: STORE_PATH,
     });
-    await _store.load();
+    initStore.ensureDataReady();
+    _store = initStore;
   }
-  _store.ensureDataReady();
 
   // Ensure a property exists for the given type.
   if (!_store.data[type]) {
     _store.data[type] = {};
   }
 
   return _store;
 }
 
 // Return an object with properties for key and value|initialValue, or null
 // if no setting has been stored for that key.
 async function getTopItem(type, key) {
-  let store = await getStore(type);
+  let store = getStore(type);
 
   let keyInfo = store.data[type][key];
   if (!keyInfo) {
     return null;
   }
 
-  if (!keyInfo.precedenceList.length) {
-    return {key, initialValue: keyInfo.initialValue};
+  // Find the highest precedence, enabled setting.
+  for (let item of keyInfo.precedenceList) {
+    if (item.enabled) {
+      return {key, value: item.value};
+    }
   }
 
-  return {key, value: keyInfo.precedenceList[0].value};
+  // Nothing found in the precedenceList, return the initialValue.
+  return {key, initialValue: keyInfo.initialValue};
+}
+
+// Comparator used when sorting the precedence list.
+function precedenceComparator(a, b) {
+  if (a.enabled && !b.enabled) {
+    return -1;
+  }
+  if (b.enabled && !a.enabled) {
+    return 1;
+  }
+  return b.installDate - a.installDate;
+}
+
+/**
+ * Helper method that alters a setting, either by changing its enabled status
+ * or by removing it.
+ *
+ * @param {Extension} extension
+ *        The extension for which a setting is being removed/disabled.
+ * @param {string} type
+ *        The type of setting to be altered.
+ * @param {string} key
+ *        A string that uniquely identifies the setting.
+ * @param {string} action
+ *        The action to perform on the setting.
+ *        Will be one of remove|enable|disable.
+ *
+ * @returns {object | null}
+ *          Either an object with properties for key and value, which
+ *          corresponds to the current top precedent setting, or null if
+ *          the current top precedent setting has not changed.
+ */
+async function alterSetting(extension, type, key, action) {
+  let returnItem;
+  let store = getStore(type);
+
+  let keyInfo = store.data[type][key];
+  if (!keyInfo) {
+    throw new Error(
+      `Cannot alter the setting for ${type}:${key} as it does not exist.`);
+  }
+
+  let id = extension.id;
+  let foundIndex = keyInfo.precedenceList.findIndex(item => item.id == id);
+
+  if (foundIndex === -1) {
+    throw new Error(
+      `Cannot alter the setting for ${type}:${key} as it does not exist.`);
+  }
+
+  switch (action) {
+    case "remove":
+      keyInfo.precedenceList.splice(foundIndex, 1);
+      break;
+
+    case "enable":
+      keyInfo.precedenceList[foundIndex].enabled = true;
+      keyInfo.precedenceList.sort(precedenceComparator);
+      foundIndex = keyInfo.precedenceList.findIndex(item => item.id == id);
+      break;
+
+    case "disable":
+      keyInfo.precedenceList[foundIndex].enabled = false;
+      keyInfo.precedenceList.sort(precedenceComparator);
+      break;
+
+    default:
+      throw new Error(`${action} is not a valid action for alterSetting.`);
+  }
+
+  if (foundIndex === 0) {
+    returnItem = await getTopItem(type, key);
+  }
+
+  if (action === "remove" && keyInfo.precedenceList.length === 0) {
+    delete store.data[type][key];
+  }
+
+  store.saveSoon();
+
+  return returnItem;
 }
 
 this.ExtensionSettingsStore = {
   /**
    * Adds a setting to the store, possibly returning the current top precedent
    * setting.
    *
    * @param {Extension} extension
@@ -119,107 +205,120 @@ this.ExtensionSettingsStore = {
    *                          at the top of the precedence list.
    */
   async addSetting(extension, type, key, value, initialValueCallback, callbackArgument = key) {
     if (typeof initialValueCallback != "function") {
       throw new Error("initialValueCallback must be a function.");
     }
 
     let id = extension.id;
-    let store = await getStore(type);
+    let store = getStore(type);
 
     if (!store.data[type][key]) {
       // The setting for this key does not exist. Set the initial value.
       let initialValue = await initialValueCallback(callbackArgument);
       store.data[type][key] = {
         initialValue,
         precedenceList: [],
       };
     }
     let keyInfo = store.data[type][key];
     // Check for this item in the precedenceList.
     let foundIndex = keyInfo.precedenceList.findIndex(item => item.id == id);
-    if (foundIndex == -1) {
+    if (foundIndex === -1) {
       // No item for this extension, so add a new one.
       let addon = await AddonManager.getAddonByID(id);
-      keyInfo.precedenceList.push({id, installDate: addon.installDate, value});
+      keyInfo.precedenceList.push({id, installDate: addon.installDate, value, enabled: true});
     } else {
       // Item already exists or this extension, so update it.
       keyInfo.precedenceList[foundIndex].value = value;
     }
 
     // Sort the list.
-    keyInfo.precedenceList.sort((a, b) => {
-      return b.installDate - a.installDate;
-    });
+    keyInfo.precedenceList.sort(precedenceComparator);
 
     store.saveSoon();
 
     // Check whether this is currently the top item.
     if (keyInfo.precedenceList[0].id == id) {
       return {key, value};
     }
     return null;
   },
 
   /**
-   * Removes a setting from the store, returning the current top precedent
-   * setting.
+   * Removes a setting from the store, possibly returning the current top
+   * precedent setting.
    *
-   * @param {Extension} extension The extension for which a setting is being removed.
-   * @param {string} type The type of setting to be removed.
-   * @param {string} key A string that uniquely identifies the setting.
+   * @param {Extension} extension
+   *        The extension for which a setting is being removed.
+   * @param {string} type
+   *        The type of setting to be removed.
+   * @param {string} key
+   *        A string that uniquely identifies the setting.
    *
-   * @returns {object | null} Either an object with properties for key and
-   *                          value, which corresponds to the current top
-   *                          precedent setting, or null if the current top
-   *                          precedent setting has not changed.
+   * @returns {object | null}
+   *          Either an object with properties for key and value, which
+   *          corresponds to the current top precedent setting, or null if
+   *          the current top precedent setting has not changed.
    */
   async removeSetting(extension, type, key) {
-    let returnItem;
-    let store = await getStore(type);
-
-    let keyInfo = store.data[type][key];
-    if (!keyInfo) {
-      throw new Error(
-        `Cannot remove setting for ${type}:${key} as it does not exist.`);
-    }
-
-    let id = extension.id;
-    let foundIndex = keyInfo.precedenceList.findIndex(item => item.id == id);
+    return await alterSetting(extension, type, key, "remove");
+  },
 
-    if (foundIndex == -1) {
-      throw new Error(
-        `Cannot remove setting for ${type}:${key} as it does not exist.`);
-    }
-
-    keyInfo.precedenceList.splice(foundIndex, 1);
+  /**
+   * Enables a setting in the store, possibly returning the current top
+   * precedent setting.
+   *
+   * @param {Extension} extension
+   *        The extension for which a setting is being enabled.
+   * @param {string} type
+   *        The type of setting to be enabled.
+   * @param {string} key
+   *        A string that uniquely identifies the setting.
+   *
+   * @returns {object | null}
+   *          Either an object with properties for key and value, which
+   *          corresponds to the current top precedent setting, or null if
+   *          the current top precedent setting has not changed.
+   */
+  async enable(extension, type, key) {
+    return await alterSetting(extension, type, key, "enable");
+  },
 
-    if (foundIndex == 0) {
-      returnItem = await getTopItem(type, key);
-    }
-
-    if (keyInfo.precedenceList.length == 0) {
-      delete store.data[type][key];
-    }
-    store.saveSoon();
-
-    return returnItem;
+  /**
+   * Disables a setting in the store, possibly returning the current top
+   * precedent setting.
+   *
+   * @param {Extension} extension
+   *        The extension for which a setting is being disabled.
+   * @param {string} type
+   *        The type of setting to be disabled.
+   * @param {string} key
+   *        A string that uniquely identifies the setting.
+   *
+   * @returns {object | null}
+   *          Either an object with properties for key and value, which
+   *          corresponds to the current top precedent setting, or null if
+   *          the current top precedent setting has not changed.
+   */
+  async disable(extension, type, key) {
+    return await alterSetting(extension, type, key, "disable");
   },
 
   /**
    * Retrieves all settings from the store for a given extension.
    *
    * @param {Extension} extension The extension for which a settings are being retrieved.
    * @param {string} type The type of setting to be returned.
    *
    * @returns {array} A list of settings which have been stored for the extension.
    */
   async getAllForExtension(extension, type) {
-    let store = await getStore(type);
+    let store = getStore(type);
 
     let keysObj = store.data[type];
     let items = [];
     for (let key in keysObj) {
       if (keysObj[key].precedenceList.find(item => item.id == extension.id)) {
         items.push(key);
       }
     }
@@ -246,34 +345,42 @@ this.ExtensionSettingsStore = {
    *
    * It informs a caller of the state of a setting with respect to the current
    * extension, and can be one of the following values:
    *
    * controlled_by_other_extensions: controlled by extensions with higher precedence
    * controllable_by_this_extension: can be controlled by this extension
    * controlled_by_this_extension: controlled by this extension
    *
-   * @param {Extension} extension The extension for which levelOfControl is being
-   *                              requested.
-   * @param {string} type The type of setting to be returned. For example `pref`.
-   * @param {string} key A string that uniquely identifies the setting, for
-   *                     example, a preference name.
+   * @param {Extension} extension
+   *        The extension for which levelOfControl is being requested.
+   * @param {string} type
+   *        The type of setting to be returned. For example `pref`.
+   * @param {string} key
+   *        A string that uniquely identifies the setting, for example, a
+   *        preference name.
    *
-   * @returns {string} The level of control of the extension over the key.
+   * @returns {string}
+   *          The level of control of the extension over the key.
    */
   async getLevelOfControl(extension, type, key) {
-    let store = await getStore(type);
+    let store = getStore(type);
 
     let keyInfo = store.data[type][key];
     if (!keyInfo || !keyInfo.precedenceList.length) {
       return "controllable_by_this_extension";
     }
 
     let id = extension.id;
-    let topItem = keyInfo.precedenceList[0];
+    let enabledItems = keyInfo.precedenceList.filter(item => item.enabled);
+    if (!enabledItems.length) {
+      return "controllable_by_this_extension";
+    }
+
+    let topItem = enabledItems[0];
     if (topItem.id == id) {
       return "controlled_by_this_extension";
     }
 
     let addon = await AddonManager.getAddonByID(id);
     return topItem.installDate > addon.installDate ?
       "controlled_by_other_extensions" :
       "controllable_by_this_extension";
--- a/toolkit/components/extensions/ExtensionTestCommon.jsm
+++ b/toolkit/components/extensions/ExtensionTestCommon.jsm
@@ -135,17 +135,17 @@ class MockExtension {
     return this._extensionPromise.then(extension => {
       return extension.broadcast("Extension:FlushJarCache", {path: this.file.path});
     }).then(() => {
       return OS.File.remove(this.file.path);
     });
   }
 }
 
-class ExtensionTestCommon {
+this.ExtensionTestCommon = class ExtensionTestCommon {
   /**
    * This code is designed to make it easy to test a WebExtension
    * without creating a bunch of files. Everything is contained in a
    * single JSON blob.
    *
    * Properties:
    *   "background": "<JS code>"
    *     A script to be loaded as the background script.
@@ -278,17 +278,17 @@ class ExtensionTestCommon {
           zipW.addEntryDirectory(path, time, false);
         }
       }
     }
 
     for (let filename in files) {
       let script = files[filename];
       if (typeof(script) == "function") {
-        script = "(" + script.toString() + ")()";
+        script = this.serializeScript(script);
       } else if (instanceOf(script, "Object") || instanceOf(script, "Array")) {
         script = JSON.stringify(script);
       }
 
       if (!instanceOf(script, "ArrayBuffer")) {
         script = new TextEncoder("utf-8").encode(script).buffer;
       }
 
@@ -300,16 +300,40 @@ class ExtensionTestCommon {
     }
 
     zipW.close();
 
     return file;
   }
 
   /**
+   * Properly serialize a script into eval-able code string.
+   *
+   * @param {string|function|Array} script
+   * @returns {string}
+   */
+  static serializeScript(script) {
+    if (Array.isArray(script)) {
+      return script.map(this.serializeScript).join(";");
+    }
+    if (typeof script !== "function") {
+      return script;
+    }
+    // Serialization of object methods doesn't include `function` anymore.
+    const method = /^(async )?(\w+)\(/;
+
+    let code = script.toString();
+    let match = code.match(method);
+    if (match && match[2] !== "function") {
+      code = code.replace(method, "$1function $2(");
+    }
+    return `(${code})();`;
+  }
+
+  /**
    * Generates a new extension using |Extension.generateXPI|, and initializes a
    * new |Extension| instance which will execute it.
    *
    * @param {object} data
    * @returns {Extension}
    */
   static generate(data) {
     let file = this.generateXPI(data);
@@ -338,9 +362,9 @@ class ExtensionTestCommon {
     }
 
     return new Extension({
       id,
       resourceURI: jarURI,
       cleanupFile: file,
     });
   }
-}
+};
--- a/toolkit/components/extensions/ext-privacy.js
+++ b/toolkit/components/extensions/ext-privacy.js
@@ -8,54 +8,64 @@ XPCOMUtils.defineLazyModuleGetter(this, 
                                   "resource://gre/modules/Preferences.jsm");
 
 Cu.import("resource://gre/modules/ExtensionPreferencesManager.jsm");
 Cu.import("resource://gre/modules/ExtensionUtils.jsm");
 const {
   ExtensionError,
 } = ExtensionUtils;
 
+/* eslint-disable mozilla/balanced-listeners */
+extensions.on("startup", async (type, extension) => {
+  if (["ADDON_ENABLE", "ADDON_UPGRADE", "ADDON_DOWNGRADE"].includes(extension.startupReason)) {
+    await ExtensionPreferencesManager.enableAll(extension);
+  }
+});
+
+extensions.on("shutdown", async (type, extension) => {
+  switch (extension.shutdownReason) {
+    case "ADDON_DISABLE":
+    case "ADDON_DOWNGRADE":
+    case "ADDON_UPGRADE":
+      await ExtensionPreferencesManager.disableAll(extension);
+      break;
+
+    case "ADDON_UNINSTALL":
+      await ExtensionPreferencesManager.removeAll(extension);
+      break;
+  }
+});
+/* eslint-enable mozilla/balanced-listeners */
+
 function checkScope(scope) {
   if (scope && scope !== "regular") {
     throw new ExtensionError(
       `Firefox does not support the ${scope} settings scope.`);
   }
 }
 
-function getAPI(extension, context, name, callback) {
-  let anythingSet = false;
+function getAPI(extension, name, callback) {
   return {
     async get(details) {
       return {
         levelOfControl: details.incognito ?
           "not_controllable" :
           await ExtensionPreferencesManager.getLevelOfControl(
             extension, name),
         value: await callback(),
       };
     },
     async set(details) {
       checkScope(details.scope);
-      if (!anythingSet) {
-        anythingSet = true;
-        context.callOnClose({
-          close: async () => {
-            if (["ADDON_DISABLE", "ADDON_UNINSTALL"].includes(extension.shutdownReason)) {
-              await ExtensionPreferencesManager.unsetAll(extension);
-              anythingSet = false;
-            }
-          },
-        });
-      }
       return await ExtensionPreferencesManager.setSetting(
         extension, name, details.value);
     },
     async clear(details) {
       checkScope(details.scope);
-      return await ExtensionPreferencesManager.unsetSetting(
+      return await ExtensionPreferencesManager.removeSetting(
         extension, name);
     },
   };
 }
 
 // Add settings objects for supported APIs to the preferences manager.
 ExtensionPreferencesManager.addSetting("network.networkPredictionEnabled", {
   prefNames: [
@@ -120,25 +130,25 @@ ExtensionPreferencesManager.addSetting("
   },
 });
 
 extensions.registerSchemaAPI("privacy.network", "addon_parent", context => {
   let {extension} = context;
   return {
     privacy: {
       network: {
-        networkPredictionEnabled: getAPI(extension, context,
+        networkPredictionEnabled: getAPI(extension,
           "network.networkPredictionEnabled",
           () => {
             return Preferences.get("network.predictor.enabled") &&
               Preferences.get("network.prefetch-next") &&
               Preferences.get("network.http.speculative-parallel-limit") > 0 &&
               !Preferences.get("network.dns.disablePrefetch");
           }),
-        webRTCIPHandlingPolicy: getAPI(extension, context,
+        webRTCIPHandlingPolicy: getAPI(extension,
           "network.webRTCIPHandlingPolicy",
           () => {
             if (Preferences.get("media.peerconnection.ice.proxy_only")) {
               return "disable_non_proxied_udp";
             }
 
             let default_address_only =
               Preferences.get("media.peerconnection.ice.default_address_only");
@@ -148,17 +158,17 @@ extensions.registerSchemaAPI("privacy.ne
               }
               return "default_public_and_private_interfaces";
             }
 
             return "default";
           }),
       },
       websites: {
-        hyperlinkAuditingEnabled: getAPI(extension, context,
+        hyperlinkAuditingEnabled: getAPI(extension,
           "websites.hyperlinkAuditingEnabled",
           () => {
             return Preferences.get("browser.send_pings");
           }),
       },
     },
   };
 });
--- a/toolkit/components/extensions/test/mochitest/test_ext_clipboard.html
+++ b/toolkit/components/extensions/test/mochitest/test_ext_clipboard.html
@@ -35,17 +35,17 @@ add_task(function* test_background_clipb
   function backgroundScript() {
     browser.test.assertEq(false, doCopy("whatever"),
       "copy should be denied without permission");
     browser.test.assertEq(false, doPaste(),
       "paste should be denied without permission");
     browser.test.sendMessage("ready");
   }
   let extensionData = {
-    background: `(${shared})();(${backgroundScript})();`,
+    background: [shared, backgroundScript],
   };
   let extension = ExtensionTestUtils.loadExtension(extensionData);
   yield extension.startup();
 
   yield extension.awaitMessage("ready");
 
   yield extension.unload();
 });
@@ -189,17 +189,17 @@ add_task(function* test_background_clipb
   function background() {
     browser.test.sendMessage("paste", doPaste());
   }
 
   const extension = ExtensionTestUtils.loadExtension({
     manifest: {
       permissions: ["clipboardRead"],
     },
-    background: `(${shared})();(${background})();`,
+    background: [shared, background],
   });
 
   const STRANGE = "Stranger Things";
   SpecialPowers.clipboardCopyString(STRANGE);
 
   yield extension.startup();
 
   const paste = yield extension.awaitMessage("paste");
--- a/toolkit/components/extensions/test/xpcshell/test_ext_extensionPreferencesManager.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_extensionPreferencesManager.js
@@ -66,16 +66,22 @@ ExtensionPreferencesManager.addSetting("
 // Set initial values for prefs.
 for (let setting in SETTINGS) {
   setting = SETTINGS[setting];
   for (let i = 0; i < setting.prefNames.length; i++) {
     Preferences.set(setting.prefNames[i], setting.initalValues[i]);
   }
 }
 
+function checkPrefs(settingObj, value, msg) {
+  for (let pref of settingObj.prefNames) {
+    equal(Preferences.get(pref), settingObj.valueFn(pref, value), msg);
+  }
+}
+
 add_task(async function test_preference_manager() {
   // Create an array of test framework extension wrappers to install.
   let testExtensions = [
     ExtensionTestUtils.loadExtension({
       useAddonManager: "temporary",
       manifest: {},
     }),
     ExtensionTestUtils.loadExtension({
@@ -92,100 +98,134 @@ add_task(async function test_preference_
 
   // Create an array actual Extension objects which correspond to the
   // test framework extension wrappers.
   let extensions = testExtensions.map(extension => extension.extension._extension);
 
   for (let setting in SETTINGS) {
     let settingObj = SETTINGS[setting];
     let newValue1 = "newValue1";
-    let levelOfControl = await ExtensionPreferencesManager.getLevelOfControl(extensions[1], setting);
+    let levelOfControl = await ExtensionPreferencesManager.getLevelOfControl(
+      extensions[1], setting);
     equal(levelOfControl, "controllable_by_this_extension",
       "getLevelOfControl returns correct levelOfControl with no settings set.");
-    let settingSet = await ExtensionPreferencesManager.setSetting(extensions[1], setting, newValue1);
-    ok(settingSet, "setSetting returns true when the pref(s) have been set.");
-    for (let pref of settingObj.prefNames) {
-      equal(Preferences.get(pref), settingObj.valueFn(pref, newValue1),
-        "setSetting sets the prefs for the first extension.");
-    }
+
+    let prefsChanged = await ExtensionPreferencesManager.setSetting(
+      extensions[1], setting, newValue1);
+    ok(prefsChanged, "setSetting returns true when the pref(s) have been set.");
+    checkPrefs(settingObj, newValue1,
+      "setSetting sets the prefs for the first extension.");
     levelOfControl = await ExtensionPreferencesManager.getLevelOfControl(extensions[1], setting);
     equal(
       levelOfControl,
       "controlled_by_this_extension",
       "getLevelOfControl returns correct levelOfControl when a pref has been set.");
 
     let newValue2 = "newValue2";
-    settingSet = await ExtensionPreferencesManager.setSetting(extensions[0], setting, newValue2);
-    ok(!settingSet, "setSetting returns false when the pref(s) have not been set.");
-    for (let pref of settingObj.prefNames) {
-      equal(Preferences.get(pref), settingObj.valueFn(pref, newValue1),
-        "setSetting does not set the pref(s) for an earlier extension.");
-    }
+    prefsChanged = await ExtensionPreferencesManager.setSetting(extensions[0], setting, newValue2);
+    ok(!prefsChanged, "setSetting returns false when the pref(s) have not been set.");
+    checkPrefs(settingObj, newValue1,
+      "setSetting does not set the pref(s) for an earlier extension.");
+
+    prefsChanged = await ExtensionPreferencesManager.disableSetting(extensions[0], setting);
+    ok(!prefsChanged, "disableSetting returns false when the pref(s) have not been set.");
+    checkPrefs(settingObj, newValue1,
+      "disableSetting does not change the pref(s) for the non-top extension.");
 
-    await ExtensionPreferencesManager.unsetSetting(extensions[0], setting);
-    for (let pref of settingObj.prefNames) {
-      equal(Preferences.get(pref), settingObj.valueFn(pref, newValue1),
-        "unsetSetting does not change the pref(s) for the non-top extension.");
-    }
+    prefsChanged = await ExtensionPreferencesManager.enableSetting(extensions[0], setting);
+    ok(!prefsChanged, "enableSetting returns false when the pref(s) have not been set.");
+    checkPrefs(settingObj, newValue1,
+      "enableSetting does not change the pref(s) for the non-top extension.");
+
+    prefsChanged = await ExtensionPreferencesManager.removeSetting(extensions[0], setting);
+    ok(!prefsChanged, "removeSetting returns false when the pref(s) have not been set.");
+    checkPrefs(settingObj, newValue1,
+      "removeSetting does not change the pref(s) for the non-top extension.");
 
-    await ExtensionPreferencesManager.setSetting(extensions[0], setting, newValue2);
-    for (let pref of settingObj.prefNames) {
-      equal(Preferences.get(pref), settingObj.valueFn(pref, newValue1),
-        "setSetting does not set the pref(s) for an earlier extension.");
-    }
+    prefsChanged = await ExtensionPreferencesManager.setSetting(extensions[0], setting, newValue2);
+    ok(!prefsChanged, "setSetting returns false when the pref(s) have not been set.");
+    checkPrefs(settingObj, newValue1,
+      "setSetting does not set the pref(s) for an earlier extension.");
+
+    prefsChanged = await ExtensionPreferencesManager.disableSetting(extensions[1], setting);
+    ok(prefsChanged, "disableSetting returns true when the pref(s) have been set.");
+    checkPrefs(settingObj, newValue2,
+      "disableSetting sets the pref(s) to the next value when disabling the top extension.");
 
-    await ExtensionPreferencesManager.unsetSetting(extensions[1], setting);
-    for (let pref of settingObj.prefNames) {
-      equal(Preferences.get(pref), settingObj.valueFn(pref, newValue2),
-        "unsetSetting sets the pref(s) to the next value when removing the top extension.");
-    }
+    prefsChanged = await ExtensionPreferencesManager.enableSetting(extensions[1], setting);
+    ok(prefsChanged, "enableSetting returns true when the pref(s) have been set.");
+    checkPrefs(settingObj, newValue1,
+      "enableSetting sets the pref(s) to the previous value(s).");
 
-    await ExtensionPreferencesManager.unsetSetting(extensions[0], setting);
+    prefsChanged = await ExtensionPreferencesManager.removeSetting(extensions[1], setting);
+    ok(prefsChanged, "removeSetting returns true when the pref(s) have been set.");
+    checkPrefs(settingObj, newValue2,
+      "removeSetting sets the pref(s) to the next value when removing the top extension.");
+
+    prefsChanged = await ExtensionPreferencesManager.removeSetting(extensions[0], setting);
+    ok(prefsChanged, "removeSetting returns true when the pref(s) have been set.");
     for (let i = 0; i < settingObj.prefNames.length; i++) {
       equal(Preferences.get(settingObj.prefNames[i]), settingObj.initalValues[i],
-        "unsetSetting sets the pref(s) to the initial value(s) when removing the last extension.");
+        "removeSetting sets the pref(s) to the initial value(s) when removing the last extension.");
     }
   }
 
   // Tests for unsetAll.
   let newValue3 = "newValue3";
   for (let setting in SETTINGS) {
     let settingObj = SETTINGS[setting];
     await ExtensionPreferencesManager.setSetting(extensions[0], setting, newValue3);
-    for (let pref of settingObj.prefNames) {
-      equal(Preferences.get(pref), settingObj.valueFn(pref, newValue3), `setSetting set the pref for ${pref}.`);
-    }
+    checkPrefs(settingObj, newValue3, "setSetting set the pref.");
   }
 
   let setSettings = await ExtensionSettingsStore.getAllForExtension(extensions[0], STORE_TYPE);
   deepEqual(setSettings, Object.keys(SETTINGS), "Expected settings were set for extension.");
-  await ExtensionPreferencesManager.unsetAll(extensions[0]);
+  await ExtensionPreferencesManager.disableAll(extensions[0]);
 
   for (let setting in SETTINGS) {
     let settingObj = SETTINGS[setting];
     for (let i = 0; i < settingObj.prefNames.length; i++) {
       equal(Preferences.get(settingObj.prefNames[i]), settingObj.initalValues[i],
-        "unsetAll unset the pref.");
+        "disableAll unset the pref.");
     }
   }
 
   setSettings = await ExtensionSettingsStore.getAllForExtension(extensions[0], STORE_TYPE);
-  deepEqual(setSettings, [], "unsetAll removed all settings.");
+  deepEqual(setSettings, Object.keys(SETTINGS), "disableAll retains the settings.");
+
+  await ExtensionPreferencesManager.enableAll(extensions[0]);
+  for (let setting in SETTINGS) {
+    let settingObj = SETTINGS[setting];
+    checkPrefs(settingObj, newValue3, "enableAll re-set the pref.");
+  }
+
+  await ExtensionPreferencesManager.removeAll(extensions[0]);
+
+  for (let setting in SETTINGS) {
+    let settingObj = SETTINGS[setting];
+    for (let i = 0; i < settingObj.prefNames.length; i++) {
+      equal(Preferences.get(settingObj.prefNames[i]), settingObj.initalValues[i],
+        "removeAll unset the pref.");
+    }
+  }
+
+  setSettings = await ExtensionSettingsStore.getAllForExtension(extensions[0], STORE_TYPE);
+  deepEqual(setSettings, [], "removeAll removed all settings.");
 
   // Test with an uninitialized pref.
   let setting = "singlePref";
   let settingObj = SETTINGS[setting];
   let pref = settingObj.prefNames[0];
   let newValue = "newValue";
   Preferences.reset(pref);
   await ExtensionPreferencesManager.setSetting(extensions[1], setting, newValue);
   equal(Preferences.get(pref), settingObj.valueFn(pref, newValue),
     "Uninitialized pref is set.");
-  await ExtensionPreferencesManager.unsetSetting(extensions[1], setting);
-  ok(!Preferences.has(pref), "unsetSetting removed the pref.");
+  await ExtensionPreferencesManager.removeSetting(extensions[1], setting);
+  ok(!Preferences.has(pref), "removeSetting removed the pref.");
 
   // Test levelOfControl with a locked pref.
   setting = "multiple_prefs";
   let prefToLock = SETTINGS[setting].prefNames[0];
   Preferences.lock(prefToLock, 1);
   ok(Preferences.locked(prefToLock), `Preference ${prefToLock} is locked.`);
   let levelOfControl = await ExtensionPreferencesManager.getLevelOfControl(extensions[1], setting);
   equal(
--- a/toolkit/components/extensions/test/xpcshell/test_ext_extensionSettingsStore.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_extensionSettingsStore.js
@@ -152,30 +152,80 @@ add_task(async function test_settings_st
     let items = await ExtensionSettingsStore.getAllForExtension(extension, TEST_TYPE);
     deepEqual(items, KEY_LIST, "getAllForExtension returns expected keys.");
   }
 
   // Attempting to remove a setting that has not been set should throw an exception.
   await Assert.rejects(
     ExtensionSettingsStore.removeSetting(
       extensions[0], "myType", "unset_key"),
-    /Cannot remove setting for myType:unset_key as it does not exist/,
+    /Cannot alter the setting for myType:unset_key as it does not exist/,
     "removeSetting rejects with an unset key.");
 
+  // Attempting to disable a setting that has not been set should throw an exception.
+  await Assert.rejects(
+    ExtensionSettingsStore.disable(extensions[0], "myType", "unset_key"),
+    /Cannot alter the setting for myType:unset_key as it does not exist/,
+    "disable rejects with an unset key.");
+
+  // Attempting to enable a setting that has not been set should throw an exception.
+  await Assert.rejects(
+    ExtensionSettingsStore.enable(extensions[0], "myType", "unset_key"),
+    /Cannot alter the setting for myType:unset_key as it does not exist/,
+    "enable rejects with an unset key.");
+
   let expectedKeys = KEY_LIST;
-  // Remove the non-top item for a key.
+  // Disable the non-top item for a key.
   for (let key of KEY_LIST) {
     let extensionIndex = 0;
     let item = await ExtensionSettingsStore.addSetting(
       extensions[extensionIndex], TEST_TYPE, key, "new value", initialValue);
     equal(callbackCount,
       expectedCallbackCount,
       "initialValueCallback called the expected number of times.");
     equal(item, null, "Updating non-top item for a key returns null");
-    item = await ExtensionSettingsStore.removeSetting(extensions[extensionIndex], TEST_TYPE, key);
+    item = await ExtensionSettingsStore.disable(extensions[extensionIndex], TEST_TYPE, key);
+    equal(item, null, "Disabling non-top item for a key returns null.");
+    let allForExtension = await ExtensionSettingsStore.getAllForExtension(extensions[extensionIndex], TEST_TYPE);
+    deepEqual(allForExtension, expectedKeys, "getAllForExtension returns expected keys after a disable.");
+    item = await ExtensionSettingsStore.getSetting(TEST_TYPE, key);
+    deepEqual(
+      item,
+      ITEMS[key][2],
+      "getSetting returns correct item after a disable.");
+    let levelOfControl = await ExtensionSettingsStore.getLevelOfControl(extensions[extensionIndex], TEST_TYPE, key);
+    equal(
+      levelOfControl,
+      "controlled_by_other_extensions",
+      "getLevelOfControl returns correct levelOfControl after disabling of non-top item.");
+  }
+
+  // Re-enable the non-top item for a key.
+  for (let key of KEY_LIST) {
+    let extensionIndex = 0;
+    let item = await ExtensionSettingsStore.enable(extensions[extensionIndex], TEST_TYPE, key);
+    equal(item, null, "Enabling non-top item for a key returns null.");
+    let allForExtension = await ExtensionSettingsStore.getAllForExtension(extensions[extensionIndex], TEST_TYPE);
+    deepEqual(allForExtension, expectedKeys, "getAllForExtension returns expected keys after an enable.");
+    item = await ExtensionSettingsStore.getSetting(TEST_TYPE, key);
+    deepEqual(
+      item,
+      ITEMS[key][2],
+      "getSetting returns correct item after an enable.");
+    let levelOfControl = await ExtensionSettingsStore.getLevelOfControl(extensions[extensionIndex], TEST_TYPE, key);
+    equal(
+      levelOfControl,
+      "controlled_by_other_extensions",
+      "getLevelOfControl returns correct levelOfControl after enabling of non-top item.");
+  }
+
+  // Remove the non-top item for a key.
+  for (let key of KEY_LIST) {
+    let extensionIndex = 0;
+    let item = await ExtensionSettingsStore.removeSetting(extensions[extensionIndex], TEST_TYPE, key);
     equal(item, null, "Removing non-top item for a key returns null.");
     expectedKeys = expectedKeys.filter(expectedKey => expectedKey != key);
     let allForExtension = await ExtensionSettingsStore.getAllForExtension(extensions[extensionIndex], TEST_TYPE);
     deepEqual(allForExtension, expectedKeys, "getAllForExtension returns expected keys after a removal.");
     item = await ExtensionSettingsStore.getSetting(TEST_TYPE, key);
     deepEqual(
       item,
       ITEMS[key][2],
@@ -183,28 +233,62 @@ add_task(async function test_settings_st
     let levelOfControl = await ExtensionSettingsStore.getLevelOfControl(extensions[extensionIndex], TEST_TYPE, key);
     equal(
       levelOfControl,
       "controlled_by_other_extensions",
       "getLevelOfControl returns correct levelOfControl after removal of non-top item.");
   }
 
   for (let key of KEY_LIST) {
+    // Disable the top item for a key.
+    let item = await ExtensionSettingsStore.disable(extensions[2], TEST_TYPE, key);
+    deepEqual(
+      item,
+      ITEMS[key][1],
+      "Disabling top item for a key returns the new top item.");
+    item = await ExtensionSettingsStore.getSetting(TEST_TYPE, key);
+    deepEqual(
+      item,
+      ITEMS[key][1],
+      "getSetting returns correct item after a disable.");
+    let levelOfControl = await ExtensionSettingsStore.getLevelOfControl(extensions[2], TEST_TYPE, key);
+    equal(
+      levelOfControl,
+      "controllable_by_this_extension",
+      "getLevelOfControl returns correct levelOfControl after disabling of top item.");
+
+    // Re-enable the top item for a key.
+    item = await ExtensionSettingsStore.enable(extensions[2], TEST_TYPE, key);
+    deepEqual(
+      item,
+      ITEMS[key][2],
+      "Re-enabling top item for a key returns the old top item.");
+    item = await ExtensionSettingsStore.getSetting(TEST_TYPE, key);
+    deepEqual(
+      item,
+      ITEMS[key][2],
+      "getSetting returns correct item after an enable.");
+    levelOfControl = await ExtensionSettingsStore.getLevelOfControl(extensions[2], TEST_TYPE, key);
+    equal(
+      levelOfControl,
+      "controlled_by_this_extension",
+      "getLevelOfControl returns correct levelOfControl after re-enabling top item.");
+
     // Remove the top item for a key.
-    let item = await ExtensionSettingsStore.removeSetting(extensions[2], TEST_TYPE, key);
+    item = await ExtensionSettingsStore.removeSetting(extensions[2], TEST_TYPE, key);
     deepEqual(
       item,
       ITEMS[key][1],
       "Removing top item for a key returns the new top item.");
     item = await ExtensionSettingsStore.getSetting(TEST_TYPE, key);
     deepEqual(
       item,
       ITEMS[key][1],
       "getSetting returns correct item after a removal.");
-    let levelOfControl = await ExtensionSettingsStore.getLevelOfControl(extensions[2], TEST_TYPE, key);
+    levelOfControl = await ExtensionSettingsStore.getLevelOfControl(extensions[2], TEST_TYPE, key);
     equal(
       levelOfControl,
       "controllable_by_this_extension",
       "getLevelOfControl returns correct levelOfControl after removal of top item.");
 
     // Add a setting for the current top item.
     let itemToAdd = {key, value: `new-${key}`};
     item = await ExtensionSettingsStore.addSetting(
@@ -222,21 +306,55 @@ add_task(async function test_settings_st
       itemToAdd,
       "getSetting returns correct item after updating.");
     levelOfControl = await ExtensionSettingsStore.getLevelOfControl(extensions[1], TEST_TYPE, key);
     equal(
       levelOfControl,
       "controlled_by_this_extension",
       "getLevelOfControl returns correct levelOfControl after updating.");
 
-    // Remove the last remaining item for a key.
+    // Disable the last remaining item for a key.
     let expectedItem = {key, initialValue: initialValue(key)};
     // We're using the callback to set the expected value, so we need to increment the
     // expectedCallbackCount.
     expectedCallbackCount++;
+    item = await ExtensionSettingsStore.disable(extensions[1], TEST_TYPE, key);
+    deepEqual(
+      item,
+      expectedItem,
+      "Disabling last item for a key returns the initial value.");
+    item = await ExtensionSettingsStore.getSetting(TEST_TYPE, key);
+    deepEqual(
+      item,
+      expectedItem,
+      "getSetting returns the initial value after all are disabled.");
+    levelOfControl = await ExtensionSettingsStore.getLevelOfControl(extensions[1], TEST_TYPE, key);
+    equal(
+      levelOfControl,
+      "controllable_by_this_extension",
+      "getLevelOfControl returns correct levelOfControl after all are disabled.");
+
+    // Re-enable the last remaining item for a key.
+    item = await ExtensionSettingsStore.enable(extensions[1], TEST_TYPE, key);
+    deepEqual(
+      item,
+      itemToAdd,
+      "Re-enabling last item for a key returns the old value.");
+    item = await ExtensionSettingsStore.getSetting(TEST_TYPE, key);
+    deepEqual(
+      item,
+      itemToAdd,
+      "getSetting returns expected value after re-enabling.");
+    levelOfControl = await ExtensionSettingsStore.getLevelOfControl(extensions[1], TEST_TYPE, key);
+    equal(
+      levelOfControl,
+      "controlled_by_this_extension",
+      "getLevelOfControl returns correct levelOfControl after re-enabling.");
+
+    // Remove the last remaining item for a key.
     item = await ExtensionSettingsStore.removeSetting(extensions[1], TEST_TYPE, key);
     deepEqual(
       item,
       expectedItem,
       "Removing last item for a key returns the initial value.");
     item = await ExtensionSettingsStore.getSetting(TEST_TYPE, key);
     deepEqual(
       item,
@@ -244,17 +362,17 @@ add_task(async function test_settings_st
       "getSetting returns null after all are removed.");
     levelOfControl = await ExtensionSettingsStore.getLevelOfControl(extensions[1], TEST_TYPE, key);
     equal(
       levelOfControl,
       "controllable_by_this_extension",
       "getLevelOfControl returns correct levelOfControl after all are removed.");
 
     // Attempting to remove a setting that has had all extensions removed should throw an exception.
-    let expectedMessage = new RegExp(`Cannot remove setting for ${TEST_TYPE}:${key} as it does not exist`);
+    let expectedMessage = new RegExp(`Cannot alter the setting for ${TEST_TYPE}:${key} as it does not exist`);
     await Assert.rejects(
       ExtensionSettingsStore.removeSetting(
         extensions[1], TEST_TYPE, key),
       expectedMessage,
       "removeSetting rejects with an key that has all records removed.");
   }
 
   // Test adding a setting with a value in callbackArgument.
--- a/toolkit/components/extensions/test/xpcshell/test_ext_privacy.js
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_privacy.js
@@ -15,17 +15,17 @@ const {
   promiseStartupManager,
 } = AddonTestUtils;
 
 AddonTestUtils.init(this);
 
 createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "42");
 
 add_task(async function test_privacy() {
-  // Create a object to hold the values to which we will initialize the prefs.
+  // Create an object to hold the values to which we will initialize the prefs.
   const SETTINGS = {
     "network.networkPredictionEnabled": {
       "network.predictor.enabled": true,
       "network.prefetch-next": true,
       "network.http.speculative-parallel-limit": 10,
       "network.dns.disablePrefetch": false,
     },
     "websites.hyperlinkAuditingEnabled": {
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_privacy_disable.js
@@ -0,0 +1,185 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+XPCOMUtils.defineLazyGetter(this, "Management", () => {
+  const {Management} = Cu.import("resource://gre/modules/Extension.jsm", {});
+  return Management;
+});
+
+XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
+                                  "resource://gre/modules/AddonManager.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "ExtensionPreferencesManager",
+                                  "resource://gre/modules/ExtensionPreferencesManager.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
+                                  "resource://gre/modules/Preferences.jsm");
+
+const {
+  createAppInfo,
+  promiseShutdownManager,
+  promiseStartupManager,
+} = AddonTestUtils;
+
+AddonTestUtils.init(this);
+
+createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "42");
+
+function awaitEvent(eventName) {
+  return new Promise(resolve => {
+    let listener = (_eventName, ...args) => {
+      if (_eventName === eventName) {
+        Management.off(eventName, listener);
+        resolve(...args);
+      }
+    };
+
+    Management.on(eventName, listener);
+  });
+}
+
+function awaitPrefChange(prefName) {
+  return new Promise(resolve => {
+    let listener = (args) => {
+      Preferences.ignore(prefName, listener);
+      resolve();
+    };
+
+    Preferences.observe(prefName, listener);
+  });
+}
+
+add_task(async function test_disable() {
+  const OLD_ID = "old_id@tests.mozilla.org";
+  const NEW_ID = "new_id@tests.mozilla.org";
+
+  const PREF_TO_WATCH = "network.http.speculative-parallel-limit";
+
+  // Create an object to hold the values to which we will initialize the prefs.
+  const PREFS = {
+    "network.predictor.enabled": true,
+    "network.prefetch-next": true,
+    "network.http.speculative-parallel-limit": 10,
+    "network.dns.disablePrefetch": false,
+  };
+
+  // Set prefs to our initial values.
+  for (let pref in PREFS) {
+    Preferences.set(pref, PREFS[pref]);
+  }
+
+  do_register_cleanup(() => {
+    // Reset the prefs.
+    for (let pref in PREFS) {
+      Preferences.reset(pref);
+    }
+  });
+
+  function checkPrefs(expected) {
+    for (let pref in PREFS) {
+      let msg = `${pref} set correctly.`;
+      let expectedValue = expected ? PREFS[pref] : !PREFS[pref];
+      if (pref === "network.http.speculative-parallel-limit") {
+        expectedValue = expected ? ExtensionPreferencesManager.getDefaultValue(pref) : 0;
+      }
+      equal(Preferences.get(pref), expectedValue, msg);
+    }
+  }
+
+  async function background() {
+    browser.test.onMessage.addListener(async (msg, data) => {
+      await browser.privacy.network.networkPredictionEnabled.set(data);
+      let settingData = await browser.privacy.network.networkPredictionEnabled.get({});
+      browser.test.sendMessage("privacyData", settingData);
+    });
+  }
+
+  // Create an array of extensions to install.
+  let testExtensions = [
+    ExtensionTestUtils.loadExtension({
+      background,
+      manifest: {
+        applications: {
+          gecko: {
+            id: OLD_ID,
+          },
+        },
+        permissions: ["privacy"],
+      },
+      useAddonManager: "temporary",
+    }),
+
+    ExtensionTestUtils.loadExtension({
+      background,
+      manifest: {
+        applications: {
+          gecko: {
+            id: NEW_ID,
+          },
+        },
+        permissions: ["privacy"],
+      },
+      useAddonManager: "temporary",
+    }),
+  ];
+
+  await promiseStartupManager();
+
+  for (let extension of testExtensions) {
+    await extension.startup();
+  }
+
+  // Set the value to true for the older extension.
+  testExtensions[0].sendMessage("set", {value: true});
+  let data = await testExtensions[0].awaitMessage("privacyData");
+  ok(data.value, "Value set to true for the older extension.");
+
+  // Set the value to false for the newest extension.
+  testExtensions[1].sendMessage("set", {value: false});
+  data = await testExtensions[1].awaitMessage("privacyData");
+  ok(!data.value, "Value set to false for the newest extension.");
+
+  // Verify the prefs have been set to match the "false" setting.
+  checkPrefs(false);
+
+  // Disable the newest extension.
+  let disabledPromise = awaitPrefChange(PREF_TO_WATCH);
+  let newAddon = await AddonManager.getAddonByID(NEW_ID);
+  newAddon.userDisabled = true;
+  await disabledPromise;
+
+  // Verify the prefs have been set to match the "true" setting.
+  checkPrefs(true);
+
+  // Disable the older extension.
+  disabledPromise = awaitPrefChange(PREF_TO_WATCH);
+  let oldAddon = await AddonManager.getAddonByID(OLD_ID);
+  oldAddon.userDisabled = true;
+  await disabledPromise;
+
+  // Verify the prefs have reverted back to their initial values.
+  for (let pref in PREFS) {
+    equal(Preferences.get(pref), PREFS[pref], `${pref} reset correctly.`);
+  }
+
+  // Re-enable the newest extension.
+  let enabledPromise = awaitEvent("ready");
+  newAddon.userDisabled = false;
+  await enabledPromise;
+
+  // Verify the prefs have been set to match the "false" setting.
+  checkPrefs(false);
+
+  // Re-enable the older extension.
+  enabledPromise = awaitEvent("ready");
+  oldAddon.userDisabled = false;
+  await enabledPromise;
+
+  // Verify the prefs have remained set to match the "false" setting.
+  checkPrefs(false);
+
+  for (let extension of testExtensions) {
+    await extension.unload();
+  }
+
+  await promiseShutdownManager();
+});
new file mode 100644
--- /dev/null
+++ b/toolkit/components/extensions/test/xpcshell/test_ext_privacy_update.js
@@ -0,0 +1,180 @@
+/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* vim: set sts=2 sw=2 et tw=80: */
+"use strict";
+
+XPCOMUtils.defineLazyGetter(this, "Management", () => {
+  const {Management} = Cu.import("resource://gre/modules/Extension.jsm", {});
+  return Management;
+});
+
+XPCOMUtils.defineLazyModuleGetter(this, "AddonManager",
+                                  "resource://gre/modules/AddonManager.jsm");
+XPCOMUtils.defineLazyModuleGetter(this, "Preferences",
+                                  "resource://gre/modules/Preferences.jsm");
+
+const {
+  createAppInfo,
+  createTempWebExtensionFile,
+  promiseAddonEvent,
+  promiseCompleteAllInstalls,
+  promiseFindAddonUpdates,
+  promiseShutdownManager,
+  promiseStartupManager,
+} = AddonTestUtils;
+
+AddonTestUtils.init(this);
+
+// Allow for unsigned addons.
+AddonTestUtils.overrideCertDB();
+
+createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "42");
+
+function awaitEvent(eventName) {
+  return new Promise(resolve => {
+    let listener = (_eventName, ...args) => {
+      if (_eventName === eventName) {
+        Management.off(eventName, listener);
+        resolve(...args);
+      }
+    };
+
+    Management.on(eventName, listener);
+  });
+}
+
+add_task(async function test_privacy_update() {
+  // Create a object to hold the values to which we will initialize the prefs.
+  const PREFS = {
+    "network.predictor.enabled": true,
+    "network.prefetch-next": true,
+    "network.http.speculative-parallel-limit": 10,
+    "network.dns.disablePrefetch": false,
+  };
+
+  const EXTENSION_ID = "test_privacy_addon_update@tests.mozilla.org";
+  const PREF_EM_CHECK_UPDATE_SECURITY = "extensions.checkUpdateSecurity";
+
+  // Set prefs to our initial values.
+  for (let pref in PREFS) {
+    Preferences.set(pref, PREFS[pref]);
+  }
+
+  do_register_cleanup(() => {
+    // Reset the prefs.
+    for (let pref in PREFS) {
+      Preferences.reset(pref);
+    }
+  });
+
+  async function background() {
+    browser.test.onMessage.addListener(async (msg, data) => {
+      let settingData;
+      switch (msg) {
+        case "get":
+          settingData = await browser.privacy.network.networkPredictionEnabled.get({});
+          browser.test.sendMessage("privacyData", settingData);
+          break;
+
+        case "set":
+          await browser.privacy.network.networkPredictionEnabled.set(data);
+          settingData = await browser.privacy.network.networkPredictionEnabled.get({});
+          browser.test.sendMessage("privacyData", settingData);
+          break;
+      }
+    });
+  }
+
+  const testServer = createHttpServer();
+  const port = testServer.identity.primaryPort;
+
+  // The test extension uses an insecure update url.
+  Services.prefs.setBoolPref(PREF_EM_CHECK_UPDATE_SECURITY, false);
+
+  testServer.registerPathHandler("/test_update.json", (request, response) => {
+    response.write(`{
+      "addons": {
+        "${EXTENSION_ID}": {
+          "updates": [
+            {
+              "version": "2.0",
+              "update_link": "http://localhost:${port}/addons/test_privacy-2.0.xpi"
+            }
+          ]
+        }
+      }
+    }`);
+  });
+
+  let webExtensionFile = createTempWebExtensionFile({
+    manifest: {
+      version: "2.0",
+      applications: {
+        gecko: {
+          id: EXTENSION_ID,
+        },
+      },
+      permissions: ["privacy"],
+    },
+    background,
+  });
+
+  testServer.registerFile("/addons/test_privacy-2.0.xpi", webExtensionFile);
+
+  await promiseStartupManager();
+
+  let extension = ExtensionTestUtils.loadExtension({
+    useAddonManager: "permanent",
+    manifest: {
+      "version": "1.0",
+      "applications": {
+        "gecko": {
+          "id": EXTENSION_ID,
+          "update_url": `http://localhost:${port}/test_update.json`,
+        },
+      },
+      permissions: ["privacy"],
+    },
+    background,
+  });
+
+  await extension.startup();
+
+  // Change the value to false.
+  extension.sendMessage("set", {value: false});
+  let data = await extension.awaitMessage("privacyData");
+  ok(!data.value, "get returns expected value after setting.");
+
+  let addon = await AddonManager.getAddonByID(EXTENSION_ID);
+  equal(addon.version, "1.0", "The installed addon has the expected version.");
+
+  let update = await promiseFindAddonUpdates(addon);
+  let install = update.updateAvailable;
+
+  let promiseInstalled = promiseAddonEvent("onInstalled");
+  await promiseCompleteAllInstalls([install]);
+
+  let startupPromise = awaitEvent("ready");
+
+  let [updated_addon] = await promiseInstalled;
+  equal(updated_addon.version, "2.0", "The updated addon has the expected version.");
+
+  extension.extension = await startupPromise;
+  extension.attachListeners();
+
+  extension.sendMessage("get");
+  data = await extension.awaitMessage("privacyData");
+  ok(!data.value, "get returns expected value after updating.");
+
+  // Verify the prefs are still set to match the "false" setting.
+  for (let pref in PREFS) {
+    let msg = `${pref} set correctly.`;
+    let expectedValue = pref === "network.http.speculative-parallel-limit" ? 0 : !PREFS[pref];
+    equal(Preferences.get(pref), expectedValue, msg);
+  }
+
+  await extension.unload();
+
+  await updated_addon.uninstall();
+
+  await promiseShutdownManager();
+});
--- a/toolkit/components/extensions/test/xpcshell/xpcshell.ini
+++ b/toolkit/components/extensions/test/xpcshell/xpcshell.ini
@@ -44,16 +44,18 @@ skip-if = release_or_beta
 [test_ext_management.js]
 [test_ext_management_uninstall_self.js]
 [test_ext_manifest_content_security_policy.js]
 [test_ext_manifest_incognito.js]
 [test_ext_manifest_minimum_chrome_version.js]
 [test_ext_onmessage_removelistener.js]
 skip-if = true # This test no longer tests what it is meant to test.
 [test_ext_privacy.js]
+[test_ext_privacy_disable.js]
+[test_ext_privacy_update.js]
 [test_ext_runtime_connect_no_receiver.js]
 [test_ext_runtime_getBrowserInfo.js]
 [test_ext_runtime_getPlatformInfo.js]
 [test_ext_runtime_onInstalled_and_onStartup.js]
 [test_ext_runtime_sendMessage.js]
 [test_ext_runtime_sendMessage_errors.js]
 [test_ext_runtime_sendMessage_no_receiver.js]
 [test_ext_runtime_sendMessage_self.js]
rename from toolkit/components/mozintl/MozIntl.cpp
rename to toolkit/components/mozintl/MozIntlHelper.cpp
--- a/toolkit/components/mozintl/MozIntl.cpp
+++ b/toolkit/components/mozintl/MozIntlHelper.cpp
@@ -1,27 +1,27 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode:nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#include "MozIntl.h"
+#include "MozIntlHelper.h"
 #include "jswrapper.h"
 #include "mozilla/ModuleUtils.h"
 
-#define MOZ_MOZINTL_CID \
-  { 0x83f8f991, 0x6b81, 0x4dd8, { 0xa0, 0x93, 0x72, 0x0b, 0xfb, 0x67, 0x4d, 0x38 } }
+#define MOZ_MOZINTLHELPER_CID \
+  { 0xb43c96be, 0x2b3a, 0x4dc4, { 0x90, 0xe9, 0xb0, 0x6d, 0x34, 0x21, 0x9b, 0x68 } }
 
 using namespace mozilla;
 
-NS_IMPL_ISUPPORTS(MozIntl, mozIMozIntl)
+NS_IMPL_ISUPPORTS(MozIntlHelper, mozIMozIntlHelper)
 
-MozIntl::MozIntl() = default;
+MozIntlHelper::MozIntlHelper() = default;
 
-MozIntl::~MozIntl() = default;
+MozIntlHelper::~MozIntlHelper() = default;
 
 static nsresult
 AddFunctions(JSContext* cx, JS::Handle<JS::Value> val, const JSFunctionSpec* funcs)
 {
   if (!val.isObject()) {
     return NS_ERROR_INVALID_ARG;
   }
 
@@ -35,39 +35,39 @@ AddFunctions(JSContext* cx, JS::Handle<J
   if (!JS_DefineFunctions(cx, realIntlObj, funcs)) {
     return NS_ERROR_FAILURE;
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
-MozIntl::AddGetCalendarInfo(JS::Handle<JS::Value> val, JSContext* cx)
+MozIntlHelper::AddGetCalendarInfo(JS::Handle<JS::Value> val, JSContext* cx)
 {
   static const JSFunctionSpec funcs[] = {
     JS_SELF_HOSTED_FN("getCalendarInfo", "Intl_getCalendarInfo", 1, 0),
     JS_FS_END
   };
 
   return AddFunctions(cx, val, funcs);
 }
 
 NS_IMETHODIMP
-MozIntl::AddGetDisplayNames(JS::Handle<JS::Value> val, JSContext* cx)
+MozIntlHelper::AddGetDisplayNames(JS::Handle<JS::Value> val, JSContext* cx)
 {
   static const JSFunctionSpec funcs[] = {
     JS_SELF_HOSTED_FN("getDisplayNames", "Intl_getDisplayNames", 2, 0),
     JS_FS_END
   };
 
   return AddFunctions(cx, val, funcs);
 }
 
 NS_IMETHODIMP
-MozIntl::AddPluralRulesConstructor(JS::Handle<JS::Value> val, JSContext* cx)
+MozIntlHelper::AddPluralRulesConstructor(JS::Handle<JS::Value> val, JSContext* cx)
 {
   if (!val.isObject()) {
     return NS_ERROR_INVALID_ARG;
   }
 
   JS::Rooted<JSObject*> realIntlObj(cx, js::CheckedUnwrap(&val.toObject()));
   if (!realIntlObj) {
     return NS_ERROR_INVALID_ARG;
@@ -78,42 +78,42 @@ MozIntl::AddPluralRulesConstructor(JS::H
   if (!js::AddPluralRulesConstructor(cx, realIntlObj)) {
     return NS_ERROR_FAILURE;
   }
 
   return NS_OK;
 }
 
 NS_IMETHODIMP
-MozIntl::AddGetLocaleInfo(JS::Handle<JS::Value> val, JSContext* cx)
+MozIntlHelper::AddGetLocaleInfo(JS::Handle<JS::Value> val, JSContext* cx)
 {
   static const JSFunctionSpec funcs[] = {
     JS_SELF_HOSTED_FN("getLocaleInfo", "Intl_getLocaleInfo", 1, 0),
     JS_FS_END
   };
 
   return AddFunctions(cx, val, funcs);
 }
 
-NS_GENERIC_FACTORY_CONSTRUCTOR(MozIntl)
-NS_DEFINE_NAMED_CID(MOZ_MOZINTL_CID);
+NS_GENERIC_FACTORY_CONSTRUCTOR(MozIntlHelper)
+NS_DEFINE_NAMED_CID(MOZ_MOZINTLHELPER_CID);
 
-static const Module::CIDEntry kMozIntlCIDs[] = {
-  { &kMOZ_MOZINTL_CID, false, nullptr, MozIntlConstructor },
+static const Module::CIDEntry kMozIntlHelperCIDs[] = {
+  { &kMOZ_MOZINTLHELPER_CID, false, nullptr, MozIntlHelperConstructor },
   { nullptr }
 };
 
-static const mozilla::Module::ContractIDEntry kMozIntlContracts[] = {
-  { "@mozilla.org/mozintl;1", &kMOZ_MOZINTL_CID },
+static const mozilla::Module::ContractIDEntry kMozIntlHelperContracts[] = {
+  { "@mozilla.org/mozintlhelper;1", &kMOZ_MOZINTLHELPER_CID },
   { nullptr }
 };
 
-static const mozilla::Module kMozIntlModule = {
+static const mozilla::Module kMozIntlHelperModule = {
   mozilla::Module::kVersion,
-  kMozIntlCIDs,
-  kMozIntlContracts,
+  kMozIntlHelperCIDs,
+  kMozIntlHelperContracts,
   nullptr,
   nullptr,
   nullptr,
   nullptr
 };
 
-NSMODULE_DEFN(mozMozIntlModule) = &kMozIntlModule;
+NSMODULE_DEFN(mozMozIntlHelperModule) = &kMozIntlHelperModule;
rename from toolkit/components/mozintl/MozIntl.h
rename to toolkit/components/mozintl/MozIntlHelper.h
--- a/toolkit/components/mozintl/MozIntl.h
+++ b/toolkit/components/mozintl/MozIntlHelper.h
@@ -1,22 +1,22 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode:nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#include "mozIMozIntl.h"
+#include "mozIMozIntlHelper.h"
 
 namespace mozilla {
 
-class MozIntl final : public mozIMozIntl
+class MozIntlHelper final : public mozIMozIntlHelper
 {
 public:
   NS_DECL_ISUPPORTS
-  NS_DECL_MOZIMOZINTL
+  NS_DECL_MOZIMOZINTLHELPER
 
-  MozIntl();
+  MozIntlHelper();
 
 private:
-  ~MozIntl();
+  ~MozIntlHelper();
 };
 
 } // namespace mozilla
--- a/toolkit/components/mozintl/moz.build
+++ b/toolkit/components/mozintl/moz.build
@@ -3,17 +3,23 @@
 # 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/.
 
 XPCSHELL_TESTS_MANIFESTS += ['test/xpcshell.ini']
 
 XPIDL_SOURCES += [
     'mozIMozIntl.idl',
+    'mozIMozIntlHelper.idl',
 ]
 
 XPIDL_MODULE = 'mozintl'
 
 SOURCES += [
-    'MozIntl.cpp',
+    'MozIntlHelper.cpp',
+]
+
+EXTRA_COMPONENTS += [
+    'mozIntl.js',
+    'mozIntl.manifest',
 ]
 
 FINAL_LIBRARY = 'xul'
--- a/toolkit/components/mozintl/mozIMozIntl.idl
+++ b/toolkit/components/mozintl/mozIMozIntl.idl
@@ -1,21 +1,45 @@
 /* -*- 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/. */
 
 #include "nsISupports.idl"
 
-[scriptable, uuid(9f9bc42e-54f4-11e6-9aed-4b1429ac0ba0)]
+/**
+ * This is a set of APIs that are of general usefulness for user interface
+ * internationalization.
+ *
+ * They're all in various stages of the standardization process through
+ * ECMA402, so they are exposed to privileged content only but are written
+ * in the way to allow for easy migration to standard Intl object once
+ * the appropriate stage of the ECMA402 is achieved.
+ *
+ * The exact structure of the code is a little bit complex because of that:
+ *
+ * 1) The core is in SpiderMonkey together with other Intl APIs
+ *
+ * This allows us to write the code once, stick to the spec language
+ * of the proposal, reuse our ICU bindings in Spidermonkey and use
+ * the code to inform us on refining the spec proposal for the given API itself.
+ *
+ * 2) The MozIntlHelper API exposes the SpiderMonkey APIs
+ *
+ * This helper API allows attaching the new APIs on any regular object.
+ *
+ * 3) The MozIntl API provides the access to those APIs
+ *
+ * This API exposes the actual functionality and wraps around the MozIntlHelper
+ * lazily retrieving and setting the accessors.
+ * On top of that, the API also binds additional functionality like using
+ * current application locale by default, and fetching OS regional preferences
+ * for date time format.
+ */
+[scriptable, uuid(7f63279a-1a29-4ae6-9e7a-dc9684a23530)]
 interface mozIMozIntl : nsISupports
 {
-  [implicit_jscontext] void addGetCalendarInfo(in jsval intlObject);
-  [implicit_jscontext] void addGetDisplayNames(in jsval intlObject);
-  [implicit_jscontext] void addGetLocaleInfo(in jsval intlObject);
+  jsval getCalendarInfo([optional] in jsval locales);
+  jsval getDisplayNames([optional] in jsval locales, [optional] in jsval options);
+  jsval getLocaleInfo([optional] in jsval locales);
 
-  /**
-   * Adds a PluralRules constructor to the given object.  This function may only
-   * be called once within a realm/global object: calling it multiple times will
-   * throw.
-   */
-  [implicit_jscontext] void addPluralRulesConstructor(in jsval intlObject);
+  jsval createPluralRules([optional] in jsval locales, [optional] in jsval options);
 };
copy from toolkit/components/mozintl/mozIMozIntl.idl
copy to toolkit/components/mozintl/mozIMozIntlHelper.idl
--- a/toolkit/components/mozintl/mozIMozIntl.idl
+++ b/toolkit/components/mozintl/mozIMozIntlHelper.idl
@@ -1,17 +1,25 @@
 /* -*- 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/. */
 
 #include "nsISupports.idl"
 
-[scriptable, uuid(9f9bc42e-54f4-11e6-9aed-4b1429ac0ba0)]
-interface mozIMozIntl : nsISupports
+/**
+ * This is an internal helper for mozIMozIntl API. There should be virtually
+ * no reason for you to call this API except from mozIMozIntl implementation.
+ *
+ * This API helps accessing the SpiderMonkey Intl APIs, but it is mozIMozIntl
+ * that exposes the thin wrapper around them that binds the functionality
+ * to Gecko.
+ */
+[scriptable, uuid(189eaa7d-b29a-43a9-b1fb-7658990df940)]
+interface mozIMozIntlHelper : nsISupports
 {
   [implicit_jscontext] void addGetCalendarInfo(in jsval intlObject);
   [implicit_jscontext] void addGetDisplayNames(in jsval intlObject);
   [implicit_jscontext] void addGetLocaleInfo(in jsval intlObject);
 
   /**
    * Adds a PluralRules constructor to the given object.  This function may only
    * be called once within a realm/global object: calling it multiple times will
new file mode 100644
--- /dev/null
+++ b/toolkit/components/mozintl/mozIntl.js
@@ -0,0 +1,69 @@
+/* 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/. */
+
+Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
+
+const Cc = Components.classes;
+const Ci = Components.interfaces;
+
+const mozIntlHelper =
+  Cc["@mozilla.org/mozintlhelper;1"].getService(Ci.mozIMozIntlHelper);
+const localeSvc =
+  Cc["@mozilla.org/intl/localeservice;1"].getService(Ci.mozILocaleService);
+
+/**
+ * This helper function retrives currently used app locales, allowing
+ * all mozIntl APIs to use the current app locales unless called with
+ * explicitly listed locales.
+ */
+function getLocales(locales) {
+  if (!locales) {
+    return localeSvc.getAppLocales();
+  }
+  return locales;
+}
+
+class MozIntl {
+  constructor() {
+    this._cache = {};
+  }
+
+  getCalendarInfo(locales, ...args) {
+    if (!this._cache.hasOwnProperty("getCalendarInfo")) {
+      mozIntlHelper.addGetCalendarInfo(this._cache);
+    }
+
+    return this._cache.getCalendarInfo(getLocales(locales), ...args);
+  }
+
+  getDisplayNames(locales, ...args) {
+    if (!this._cache.hasOwnProperty("getDisplayNames")) {
+      mozIntlHelper.addGetDisplayNames(this._cache);
+    }
+
+    return this._cache.getDisplayNames(getLocales(locales), ...args);
+  }
+
+  getLocaleInfo(locales, ...args) {
+    if (!this._cache.hasOwnProperty("getLocaleInfo")) {
+      mozIntlHelper.addGetLocaleInfo(this._cache);
+    }
+
+    return this._cache.getLocaleInfo(getLocales(locales), ...args);
+  }
+
+  createPluralRules(locales, ...args) {
+    if (!this._cache.hasOwnProperty("PluralRules")) {
+      mozIntlHelper.addPluralRulesConstructor(this._cache);
+    }
+
+    return new this._cache.PluralRules(getLocales(locales), ...args);
+  }
+}
+
+MozIntl.prototype.classID = Components.ID("{35ec195a-e8d0-4300-83af-c8a2cc84b4a3}");
+MozIntl.prototype.QueryInterface = XPCOMUtils.generateQI([Ci.mozIMozIntl, Ci.nsISupports]);
+
+var components = [MozIntl];
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory(components);
new file mode 100644
--- /dev/null
+++ b/toolkit/components/mozintl/mozIntl.manifest
@@ -0,0 +1,2 @@
+component {35ec195a-e8d0-4300-83af-c8a2cc84b4a3} mozIntl.js
+contract @mozilla.org/mozintl;1 {35ec195a-e8d0-4300-83af-c8a2cc84b4a3}
--- a/toolkit/components/mozintl/test/test_mozintl.js
+++ b/toolkit/components/mozintl/test/test_mozintl.js
@@ -1,50 +1,27 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 function run_test() {
   const mozIntl = Components.classes["@mozilla.org/mozintl;1"]
                             .getService(Components.interfaces.mozIMozIntl);
 
-  test_this_global(mozIntl);
-  test_cross_global(mozIntl);
   test_methods_presence(mozIntl);
+  test_methods_calling(mozIntl);
 
   ok(true);
 }
 
-function test_this_global(mozIntl) {
-  let x = {};
-
-  mozIntl.addGetCalendarInfo(x);
-  equal(x.getCalendarInfo instanceof Function, true);
-  equal(x.getCalendarInfo() instanceof Object, true);
+function test_methods_presence(mozIntl) {
+  equal(mozIntl.getCalendarInfo instanceof Function, true);
+  equal(mozIntl.getDisplayNames instanceof Function, true);
+  equal(mozIntl.getLocaleInfo instanceof Function, true);
+  equal(mozIntl.createPluralRules instanceof Function, true);
 }
 
-function test_cross_global(mozIntl) {
-  var global = new Components.utils.Sandbox("https://example.com/");
-  var x = global.Object();
-
-  mozIntl.addGetCalendarInfo(x);
-  var waivedX = Components.utils.waiveXrays(x);
-  equal(waivedX.getCalendarInfo instanceof Function, false);
-  equal(waivedX.getCalendarInfo instanceof global.Function, true);
-  equal(waivedX.getCalendarInfo() instanceof Object, false);
-  equal(waivedX.getCalendarInfo() instanceof global.Object, true);
+function test_methods_calling(mozIntl) {
+  mozIntl.getCalendarInfo("pl");
+  mozIntl.getDisplayNames("ar");
+  mozIntl.getLocaleInfo("de");
+  mozIntl.createPluralRules("fr");
+  ok(true);
 }
-
-function test_methods_presence(mozIntl) {
-  equal(mozIntl.addGetCalendarInfo instanceof Function, true);
-  equal(mozIntl.addGetDisplayNames instanceof Function, true);
-  equal(mozIntl.addGetLocaleInfo instanceof Function, true);
-
-  let x = {};
-
-  mozIntl.addGetCalendarInfo(x);
-  equal(x.getCalendarInfo instanceof Function, true);
-
-  mozIntl.addGetDisplayNames(x);
-  equal(x.getDisplayNames instanceof Function, true);
-
-  mozIntl.addGetLocaleInfo(x);
-  equal(x.getLocaleInfo instanceof Function, true);
-}
new file mode 100644
--- /dev/null
+++ b/toolkit/components/mozintl/test/test_mozintlhelper.js
@@ -0,0 +1,50 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+function run_test() {
+  const miHelper = Components.classes["@mozilla.org/mozintlhelper;1"]
+                             .getService(Components.interfaces.mozIMozIntlHelper);
+
+  test_this_global(miHelper);
+  test_cross_global(miHelper);
+  test_methods_presence(miHelper);
+
+  ok(true);
+}
+
+function test_this_global(miHelper) {
+  let x = {};
+
+  miHelper.addGetCalendarInfo(x);
+  equal(x.getCalendarInfo instanceof Function, true);
+  equal(x.getCalendarInfo() instanceof Object, true);
+}
+
+function test_cross_global(miHelper) {
+  var global = new Components.utils.Sandbox("https://example.com/");
+  var x = global.Object();
+
+  miHelper.addGetCalendarInfo(x);
+  var waivedX = Components.utils.waiveXrays(x);
+  equal(waivedX.getCalendarInfo instanceof Function, false);
+  equal(waivedX.getCalendarInfo instanceof global.Function, true);
+  equal(waivedX.getCalendarInfo() instanceof Object, false);
+  equal(waivedX.getCalendarInfo() instanceof global.Object, true);
+}
+
+function test_methods_presence(miHelper) {
+  equal(miHelper.addGetCalendarInfo instanceof Function, true);
+  equal(miHelper.addGetDisplayNames instanceof Function, true);
+  equal(miHelper.addGetLocaleInfo instanceof Function, true);
+
+  let x = {};
+
+  miHelper.addGetCalendarInfo(x);
+  equal(x.getCalendarInfo instanceof Function, true);
+
+  miHelper.addGetDisplayNames(x);
+  equal(x.getDisplayNames instanceof Function, true);
+
+  miHelper.addGetLocaleInfo(x);
+  equal(x.getLocaleInfo instanceof Function, true);
+}
--- a/toolkit/components/mozintl/test/xpcshell.ini
+++ b/toolkit/components/mozintl/test/xpcshell.ini
@@ -1,4 +1,5 @@
 [DEFAULT]
 head =
 
 [test_mozintl.js]
+[test_mozintlhelper.js]
--- a/toolkit/content/widgets/datetimepopup.xml
+++ b/toolkit/content/widgets/datetimepopup.xml
@@ -18,21 +18,18 @@
       <field name="dateTimePopupFrame">
         this.querySelector("#dateTimePopupFrame");
       </field>
       <field name="TIME_PICKER_WIDTH" readonly="true">"12em"</field>
       <field name="TIME_PICKER_HEIGHT" readonly="true">"21em"</field>
       <field name="DATE_PICKER_WIDTH" readonly="true">"23.1em"</field>
       <field name="DATE_PICKER_HEIGHT" readonly="true">"20.7em"</field>
       <constructor><![CDATA[
-        this.l10n = {};
-        const mozIntl = Components.classes["@mozilla.org/mozintl;1"]
-                          .getService(Components.interfaces.mozIMozIntl);
-        mozIntl.addGetCalendarInfo(this.l10n);
-        mozIntl.addGetDisplayNames(this.l10n);
+        this.mozIntl = Components.classes["@mozilla.org/mozintl;1"]
+                                 .getService(Components.interfaces.mozIMozIntl);
       ]]></constructor>
       <method name="loadPicker">
         <parameter name="type"/>
         <parameter name="detail"/>
         <body><![CDATA[
           this.hidden = false;
           this.type = type;
           this.pickerState = {};
@@ -221,17 +218,17 @@
               break;
             }
           }
         ]]></body>
       </method>
       <method name="getCalendarInfo">
         <parameter name="locale"/>
         <body><![CDATA[
-          const calendarInfo = this.l10n.getCalendarInfo(locale);
+          const calendarInfo = this.mozIntl.getCalendarInfo(locale);
 
           // Day of week from calendarInfo starts from 1 as Sunday to 7 as Saturday,
           // so they need to be mapped to JavaScript convention with 0 as Sunday
           // and 6 as Saturday
           let firstDayOfWeek = calendarInfo.firstDayOfWeek - 1,
               weekendStart = calendarInfo.weekendStart - 1,
               weekendEnd = calendarInfo.weekendEnd - 1;
 
@@ -254,17 +251,17 @@
           }
         ]]></body>
       </method>
       <method name="getDisplayNames">
         <parameter name="locale"/>
         <parameter name="keys"/>
         <parameter name="style"/>
         <body><![CDATA[
-          const displayNames = this.l10n.getDisplayNames(locale, {keys, style});
+          const displayNames = this.mozIntl.getDisplayNames(locale, {keys, style});
           return keys.map(key => displayNames.values[key]);
         ]]></body>
       </method>
       <method name="handleEvent">
         <parameter name="aEvent"/>
         <body><![CDATA[
           switch (aEvent.type) {
             case "load": {
--- a/toolkit/library/gtest/Makefile.in
+++ b/toolkit/library/gtest/Makefile.in
@@ -3,16 +3,22 @@
 # You can obtain one at http://mozilla.org/MPL/2.0/.
 
 # Enforce that the clean/distclean rules removes everything that needs
 # to be removed from this directory.
 ifneq (,$(filter clean distclean,$(MAKECMDGOALS)))
 LINK_GTEST_DURING_COMPILE = 1
 endif
 
+# Don't link the gtest xul during MOZ_PROFILE_GENERATE, it doesn't get
+# used during profiling anyway.
+ifdef MOZ_PROFILE_GENERATE
+LINK_GTEST_DURING_COMPILE =
+endif
+
 ifndef LINK_GTEST_DURING_COMPILE
 # Force to not include backend.mk unless LINK_GTEST_DURING_COMPILE is set.
 # Not including backend.mk makes traversing this directory do nothing.
 STANDALONE_MAKEFILE = 1
 
 else
 
 include $(topsrcdir)/toolkit/library/libxul.mk
--- a/toolkit/mozapps/extensions/content/extensions.js
+++ b/toolkit/mozapps/extensions/content/extensions.js
@@ -1097,16 +1097,18 @@ var gViewController = {
           if (pendingChecks > 0)
             return;
 
           this.inProgress = false;
           gViewController.updateCommand("cmd_findAllUpdates");
           document.getElementById("updates-progress").hidden = true;
           gUpdatesView.maybeRefresh();
 
+          Services.obs.notifyObservers(null, "EM-update-check-finished", null);
+
           if (numManualUpdates > 0 && numUpdated == 0) {
             document.getElementById("updates-manualUpdatesFound-btn").hidden = false;
             return;
           }
 
           if (numUpdated == 0) {
             document.getElementById("updates-noneFound").hidden = false;
             return;
--- a/toolkit/mozapps/extensions/test/xpcshell/data/test_gfxBlacklist.xml
+++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_gfxBlacklist.xml
@@ -288,16 +288,31 @@
     <gfxBlacklistEntry>
       <os>All</os>
       <vendor>0xdcdc</vendor>
       <devices>
         <device>0x2783</device>
         <device>0x1234</device>
         <device>0x2782</device>
       </devices>
+      <feature> WEBGL2 </feature>
+      <featureStatus> BLOCKED_DRIVER_VERSION </featureStatus>
+      <driverVersion> 8.52.322.1000 </driverVersion>
+      <driverVersionMax> 8.52.322.1112 </driverVersionMax>
+      <driverVersionComparator> BETWEEN_INCLUSIVE_START </driverVersionComparator>
+    </gfxBlacklistEntry>
+
+    <gfxBlacklistEntry>
+      <os>All</os>
+      <vendor>0xdcdc</vendor>
+      <devices>
+        <device>0x2783</device>
+        <device>0x1234</device>
+        <device>0x2782</device>
+      </devices>
       <feature> CANVAS2D_ACCELERATION </feature>
       <featureStatus> BLOCKED_DRIVER_VERSION </featureStatus>
       <driverVersion> 8.52.322.1000 </driverVersion>
       <driverVersionMax> 9.52.322.1000 </driverVersionMax>
       <driverVersionComparator> BETWEEN_EXCLUSIVE </driverVersionComparator>
     </gfxBlacklistEntry>
 
   </gfxItems>
--- a/toolkit/mozapps/extensions/test/xpcshell/data/test_gfxBlacklist_AllOS.xml
+++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_gfxBlacklist_AllOS.xml
@@ -91,16 +91,28 @@
       </devices>
       <feature> WEBGL_ANGLE </feature>
       <featureStatus> BLOCKED_DRIVER_VERSION </featureStatus>
     </gfxBlacklistEntry>
 
     <gfxBlacklistEntry>
       <os>All</os>
       <vendor>0xabcd</vendor>
+      <devices>
+        <device>0x2783</device>
+        <device>0x1234</device>
+        <device>0x2782</device>
+      </devices>
+      <feature> WEBGL2 </feature>
+      <featureStatus> BLOCKED_DRIVER_VERSION </featureStatus>
+    </gfxBlacklistEntry>
+
+    <gfxBlacklistEntry>
+      <os>All</os>
+      <vendor>0xabcd</vendor>
       <versionRange minVersion="12.0" maxVersion="16.0"/>
       <devices>
         <device>0x2783</device>
         <device>0x1234</device>
         <device>0x2782</device>
       </devices>
       <feature> WEBGL_MSAA </feature>
       <featureStatus> BLOCKED_DRIVER_VERSION </featureStatus>
@@ -285,16 +297,28 @@
       </devices>
       <feature> WEBGL_ANGLE </feature>
       <featureStatus> BLOCKED_DRIVER_VERSION </featureStatus>
     </gfxBlacklistEntry>
 
     <gfxBlacklistEntry>
       <os>Darwin 13</os>
       <vendor>0xabcd</vendor>
+      <devices>
+        <device>0x2783</device>
+        <device>0x1234</device>
+        <device>0x2782</device>
+      </devices>
+      <feature> WEBGL2 </feature>
+      <featureStatus> BLOCKED_DRIVER_VERSION </featureStatus>
+    </gfxBlacklistEntry>
+
+    <gfxBlacklistEntry>
+      <os>Darwin 13</os>
+      <vendor>0xabcd</vendor>
       <versionRange minVersion="12.0" maxVersion="16.0"/>
       <devices>
         <device>0x2783</device>
         <device>0x1234</device>
         <device>0x2782</device>
       </devices>
       <feature> WEBGL_MSAA </feature>
       <featureStatus> BLOCKED_DRIVER_VERSION </featureStatus>
@@ -479,16 +503,28 @@
       </devices>
       <feature> WEBGL_ANGLE </feature>
       <featureStatus> BLOCKED_DRIVER_VERSION </featureStatus>
     </gfxBlacklistEntry>
 
     <gfxBlacklistEntry>
       <os>Linux</os>
       <vendor>0xabcd</vendor>
+      <devices>
+        <device>0x2783</device>
+        <device>0x1234</device>
+        <device>0x2782</device>
+      </devices>
+      <feature> WEBGL2 </feature>
+      <featureStatus> BLOCKED_DRIVER_VERSION </featureStatus>
+    </gfxBlacklistEntry>
+
+    <gfxBlacklistEntry>
+      <os>Linux</os>
+      <vendor>0xabcd</vendor>
       <versionRange minVersion="12.0" maxVersion="16.0"/>
       <devices>
         <device>0x2783</device>
         <device>0x1234</device>
         <device>0x2782</device>
       </devices>
       <feature> WEBGL_MSAA </feature>
       <featureStatus> BLOCKED_DRIVER_VERSION </featureStatus>
@@ -673,16 +709,28 @@
       </devices>
       <feature> WEBGL_ANGLE </feature>
       <featureStatus> BLOCKED_DRIVER_VERSION </featureStatus>
     </gfxBlacklistEntry>
 
     <gfxBlacklistEntry>
       <os>Android</os>
       <vendor>0xabcd</vendor>
+      <devices>
+        <device>0x2783</device>
+        <device>0x1234</device>
+        <device>0x2782</device>
+      </devices>
+      <feature> WEBGL2 </feature>
+      <featureStatus> BLOCKED_DRIVER_VERSION </featureStatus>
+    </gfxBlacklistEntry>
+
+    <gfxBlacklistEntry>
+      <os>Android</os>
+      <vendor>0xabcd</vendor>
       <versionRange minVersion="12.0" maxVersion="16.0"/>
       <devices>
         <device>0x2783</device>
         <device>0x1234</device>
         <device>0x2782</device>
       </devices>
       <feature> WEBGL_MSAA </feature>
       <featureStatus> BLOCKED_DRIVER_VERSION </featureStatus>
--- a/toolkit/mozapps/extensions/test/xpcshell/test_gfxBlacklist_Version.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_gfxBlacklist_Version.js
@@ -99,16 +99,20 @@ function run_test() {
     status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_WEBGL_OPENGL, failureId);
     do_check_eq(status, Ci.nsIGfxInfo.FEATURE_BLOCKED_DRIVER_VERSION);
     do_check_eq(failureId.value, "FEATURE_FAILURE_DL_BLACKLIST_g11");
 
     status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_WEBGL_ANGLE, failureId);
     do_check_eq(status, Ci.nsIGfxInfo.FEATURE_BLOCKED_DRIVER_VERSION);
     do_check_eq(failureId.value, "FEATURE_FAILURE_DL_BLACKLIST_NO_ID");
 
+    status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_WEBGL2, failureId);
+    do_check_eq(status, Ci.nsIGfxInfo.FEATURE_BLOCKED_DRIVER_VERSION);
+    do_check_eq(failureId.value, "FEATURE_FAILURE_DL_BLACKLIST_NO_ID");
+
     status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_WEBGL_MSAA, failureId);
     do_check_eq(status, Ci.nsIGfxInfo.FEATURE_BLOCKED_DRIVER_VERSION);
 
     status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_STAGEFRIGHT, failureId);
     do_check_eq(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK);
 
     status = gfxInfo.getFeatureStatus(Ci.nsIGfxInfo.FEATURE_WEBRTC_HW_ACCELERATION, failureId);
     do_check_eq(status, Ci.nsIGfxInfo.FEATURE_STATUS_OK);
--- a/widget/GfxInfoBase.cpp
+++ b/widget/GfxInfoBase.cpp
@@ -161,16 +161,19 @@ GetPrefNameForFeature(int32_t aFeature)
       name = BLACKLIST_PREF_BRANCH "webrtc.hw.acceleration.encode";
       break;
     case nsIGfxInfo::FEATURE_WEBRTC_HW_ACCELERATION_DECODE:
       name = BLACKLIST_PREF_BRANCH "webrtc.hw.acceleration.decode";
       break;
     case nsIGfxInfo::FEATURE_CANVAS2D_ACCELERATION:
       name = BLACKLIST_PREF_BRANCH "canvas2d.acceleration";
       break;
+    case nsIGfxInfo::FEATURE_WEBGL2:
+      name = BLACKLIST_PREF_BRANCH "webgl2";
+      break;
     case nsIGfxInfo::FEATURE_VP8_HW_DECODE:
     case nsIGfxInfo::FEATURE_VP9_HW_DECODE:
     case nsIGfxInfo::FEATURE_DX_INTEROP2:
     case nsIGfxInfo::FEATURE_GPU_PROCESS:
       // We don't provide prefs for these features.
       break;
     default:
       MOZ_ASSERT_UNREACHABLE("Unexpected nsIGfxInfo feature?!");
@@ -339,16 +342,18 @@ BlacklistFeatureToGfxFeature(const nsASt
   else if (aFeature.EqualsLiteral("WEBRTC_HW_ACCELERATION_ENCODE"))
     return nsIGfxInfo::FEATURE_WEBRTC_HW_ACCELERATION_ENCODE;
   else if (aFeature.EqualsLiteral("WEBRTC_HW_ACCELERATION_DECODE"))
     return nsIGfxInfo::FEATURE_WEBRTC_HW_ACCELERATION_DECODE;
   else if (aFeature.EqualsLiteral("WEBRTC_HW_ACCELERATION"))
     return nsIGfxInfo::FEATURE_WEBRTC_HW_ACCELERATION;
   else if (aFeature.EqualsLiteral("CANVAS2D_ACCELERATION"))
       return nsIGfxInfo::FEATURE_CANVAS2D_ACCELERATION;
+  else if (aFeature.EqualsLiteral("WEBGL2"))
+    return nsIGfxInfo::FEATURE_WEBGL2;
 
   // If we don't recognize the feature, it may be new, and something
   // this version doesn't understand.  So, nothing to do.  This is
   // different from feature not being specified at all, in which case
   // this method should not get called and we should continue with the
   // "all features" blocklisting.
   return -1;
 }
@@ -968,16 +973,17 @@ GfxInfoBase::EvaluateDownloadedBlacklist
     nsIGfxInfo::FEATURE_WEBGL_OPENGL,
     nsIGfxInfo::FEATURE_WEBGL_ANGLE,
     nsIGfxInfo::FEATURE_WEBRTC_HW_ACCELERATION_ENCODE,
     nsIGfxInfo::FEATURE_WEBRTC_HW_ACCELERATION_DECODE,
     nsIGfxInfo::FEATURE_WEBGL_MSAA,
     nsIGfxInfo::FEATURE_STAGEFRIGHT,
     nsIGfxInfo::FEATURE_WEBRTC_HW_ACCELERATION,
     nsIGfxInfo::FEATURE_CANVAS2D_ACCELERATION,
+    nsIGfxInfo::FEATURE_WEBGL2,
     0
   };
 
   // For every feature we know about, we evaluate whether this blacklist has a
   // non-STATUS_OK status. If it does, we set the pref we evaluate in
   // GetFeatureStatus above, so we don't need to hold on to this blacklist
   // anywhere permanent.
   int i = 0;
--- a/widget/GfxInfoX11.cpp
+++ b/widget/GfxInfoX11.cpp
@@ -308,16 +308,17 @@ GfxInfo::GetFeatureStatusImpl(int32_t aF
       *aStatus = nsIGfxInfo::FEATURE_BLOCKED_DEVICE;
       aFailureId = "FEATURE_FAILURE_SOFTWARE_GL";
       return NS_OK;
     }
 
     // Only check features relevant to Linux.
     if (aFeature == nsIGfxInfo::FEATURE_OPENGL_LAYERS ||
         aFeature == nsIGfxInfo::FEATURE_WEBGL_OPENGL ||
+        aFeature == nsIGfxInfo::FEATURE_WEBGL2 ||
         aFeature == nsIGfxInfo::FEATURE_WEBGL_MSAA) {
 
       // whitelist the linux test slaves' current configuration.
       // this is necessary as they're still using the slightly outdated 190.42 driver.
       // this isn't a huge risk, as at least this is the exact setting in which we do continuous testing,
       // and this only affects GeForce 9400 cards on linux on this precise driver version, which is very few users.
       // We do the same thing on Windows XP, see in widget/windows/GfxInfo.cpp
       if (mIsNVIDIA &&
--- a/widget/nsIGfxInfo.idl
+++ b/widget/nsIGfxInfo.idl
@@ -116,18 +116,20 @@ interface nsIGfxInfo : nsISupports
   /* Whether hardware VP8 decoding is supported, starting in 48. */
   const long FEATURE_VP8_HW_DECODE = 17;
   /* Whether hardware VP9 decoding is supported, starting in 48. */
   const long FEATURE_VP9_HW_DECODE = 18;
   /* Whether NV_dx_interop2 is supported, starting in 50. */
   const long FEATURE_DX_INTEROP2 = 19;
   /* Whether the GPU process is supported, starting in 52. */
   const long FEATURE_GPU_PROCESS = 20;
+  /* Whether the WebGL2 is supported, starting in 54 */
+  const long FEATURE_WEBGL2 = 21;
   /* the maximum feature value. */
-  const long FEATURE_MAX_VALUE = FEATURE_GPU_PROCESS;
+  const long FEATURE_MAX_VALUE = FEATURE_WEBGL2;
 
   /*
    * A set of return values from GetFeatureStatus
    */
 
   /* The driver is safe to the best of our knowledge */
   const long FEATURE_STATUS_OK = 1;
   /* We don't know the status of the feature yet. The analysis probably hasn't finished yet. */
new file mode 100644
--- /dev/null
+++ b/xpcom/ds/ArrayIterator.h
@@ -0,0 +1,140 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// Common iterator implementation for array classes e.g. nsTArray.
+
+#ifndef mozilla_ArrayIterator_h
+#define mozilla_ArrayIterator_h
+
+#include <iterator>
+
+#include "mozilla/TypeTraits.h"
+
+namespace mozilla {
+
+// We have implemented a custom iterator class for array rather than using
+// raw pointers into the backing storage to improve the safety of C++11-style
+// range based iteration in the presence of array mutation, or script execution
+// (bug 1299489).
+//
+// Mutating an array which is being iterated is still wrong, and will either
+// cause elements to be missed or firefox to crash, but will not trigger memory
+// safety problems due to the release-mode bounds checking found in ElementAt.
+//
+// Dereferencing this iterator returns type Element. When Element is a reference
+// type, this iterator implements the full standard random access iterator spec,
+// and can be treated in many ways as though it is a pointer. Otherwise, it is
+// just enough to be used in range-based for loop.
+template<class Element, class ArrayType>
+class ArrayIterator
+{
+public:
+  typedef ArrayType                               array_type;
+  typedef ArrayIterator<Element, ArrayType>       iterator_type;
+  typedef typename array_type::index_type         index_type;
+  typedef typename RemoveReference<Element>::Type value_type;
+  typedef ptrdiff_t                               difference_type;
+  typedef value_type*                             pointer;
+  typedef value_type&                             reference;
+  typedef std::random_access_iterator_tag         iterator_category;
+
+private:
+  const array_type* mArray;
+  index_type mIndex;
+
+public:
+  ArrayIterator() : mArray(nullptr), mIndex(0) {}
+  ArrayIterator(const iterator_type& aOther)
+    : mArray(aOther.mArray), mIndex(aOther.mIndex) {}
+  ArrayIterator(const array_type& aArray, index_type aIndex)
+    : mArray(&aArray), mIndex(aIndex) {}
+
+  iterator_type& operator=(const iterator_type& aOther) {
+    mArray = aOther.mArray;
+    mIndex = aOther.mIndex;
+    return *this;
+  }
+
+  bool operator==(const iterator_type& aRhs) const {
+    return mIndex == aRhs.mIndex;
+  }
+  bool operator!=(const iterator_type& aRhs) const {
+    return !(*this == aRhs);
+  }
+  bool operator<(const iterator_type& aRhs) const {
+    return mIndex < aRhs.mIndex;
+  }
+  bool operator>(const iterator_type& aRhs) const {
+    return mIndex > aRhs.mIndex;
+  }
+  bool operator<=(const iterator_type& aRhs) const {
+    return mIndex <= aRhs.mIndex;
+  }
+  bool operator>=(const iterator_type& aRhs) const {
+    return mIndex >= aRhs.mIndex;
+  }
+
+  // These operators depend on the release mode bounds checks in
+  // ArrayIterator::ElementAt for safety.
+  value_type* operator->() const {
+    return const_cast<value_type*>(&mArray->ElementAt(mIndex));
+  }
+  Element operator*() const {
+    return const_cast<Element>(mArray->ElementAt(mIndex));
+  }
+
+  iterator_type& operator++() {
+    ++mIndex;
+    return *this;
+  }
+  iterator_type operator++(int) {
+    iterator_type it = *this;
+    ++*this;
+    return it;
+  }
+  iterator_type& operator--() {
+    --mIndex;
+    return *this;
+  }
+  iterator_type operator--(int) {
+    iterator_type it = *this;
+    --*this;
+    return it;
+  }
+
+  iterator_type& operator+=(difference_type aDiff) {
+    mIndex += aDiff;
+    return *this;
+  }
+  iterator_type& operator-=(difference_type aDiff) {
+    mIndex -= aDiff;
+    return *this;
+  }
+
+  iterator_type operator+(difference_type aDiff) const {
+    iterator_type it = *this;
+    it += aDiff;
+    return it;
+  }
+  iterator_type operator-(difference_type aDiff) const {
+    iterator_type it = *this;
+    it -= aDiff;
+    return it;
+  }
+
+  difference_type operator-(const iterator_type& aOther) const {
+    return static_cast<difference_type>(mIndex) -
+      static_cast<difference_type>(aOther.mIndex);
+  }
+
+  Element operator[](difference_type aIndex) const {
+    return *this->operator+(aIndex);
+  }
+};
+
+} // namespace mozilla
+
+#endif // mozilla_ArrayIterator_h
--- a/xpcom/ds/moz.build
+++ b/xpcom/ds/moz.build
@@ -78,16 +78,17 @@ EXPORTS += [
     'nsTObserverArray.h',
     'nsTPriorityQueue.h',
     'nsVariant.h',
     'nsWhitespaceTokenizer.h',
     'PLDHashTable.h',
 ]
 
 EXPORTS.mozilla += [
+    'ArrayIterator.h',
     'IncrementalTokenizer.h',
     'Observer.h',
     'StickyTimeDuration.h',
     'Tokenizer.h',
 ]
 
 UNIFIED_SOURCES += [
     'IncrementalTokenizer.cpp',
--- a/xpcom/ds/nsCOMArray.h
+++ b/xpcom/ds/nsCOMArray.h
@@ -3,17 +3,19 @@
 /* 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 nsCOMArray_h__
 #define nsCOMArray_h__
 
 #include "mozilla/Attributes.h"
+#include "mozilla/ArrayIterator.h"
 #include "mozilla/MemoryReporting.h"
+#include "mozilla/ReverseIterator.h"
 
 #include "nsCycleCollectionNoteChild.h"
 #include "nsTArray.h"
 #include "nsISupports.h"
 
 // See below for the definition of nsCOMArray<T>
 
 // a class that's nsISupports-specific, so that we can contain the
@@ -226,16 +228,22 @@ ImplCycleCollectionTraverse(nsCycleColle
 //
 // This array will accept null as an argument for any object, and will store
 // null in the array. But that also means that methods like ObjectAt() may
 // return null when referring to an existing, but null entry in the array.
 template<class T>
 class nsCOMArray : public nsCOMArray_base
 {
 public:
+  typedef int32_t                                       index_type;
+  typedef mozilla::ArrayIterator<T*, nsCOMArray>        iterator;
+  typedef mozilla::ArrayIterator<const T*, nsCOMArray>  const_iterator;
+  typedef mozilla::ReverseIterator<iterator>            reverse_iterator;
+  typedef mozilla::ReverseIterator<const_iterator>      const_reverse_iterator;
+
   nsCOMArray() {}
   explicit nsCOMArray(int32_t aCount) : nsCOMArray_base(aCount) {}
   explicit nsCOMArray(const nsCOMArray<T>& aOther) : nsCOMArray_base(aOther) {}
   nsCOMArray(nsCOMArray<T>&& aOther) { SwapElements(aOther); }
   ~nsCOMArray() {}
 
   // We have a move assignment operator, but no copy assignment operator.
   nsCOMArray<T>& operator=(nsCOMArray<T> && aOther)
@@ -331,34 +339,16 @@ public:
     nsCOMArray_base::ReplaceObjectAt(aObject, aIndex);
   }
   // nsTArray-compatible version
   void ReplaceElementAt(uint32_t aIndex, T* aElement)
   {
     nsCOMArray_base::ReplaceElementAt(aIndex, aElement);
   }
 
-  // Enumerator callback function. Return false to stop
-  // Here's a more readable form:
-  // bool enumerate(T* aElement, void* aData)
-  typedef bool (*nsCOMArrayEnumFunc)(T* aElement, void* aData);
-
-  // enumerate through the array with a callback.
-  bool EnumerateForwards(nsCOMArrayEnumFunc aFunc, void* aData)
-  {
-    return nsCOMArray_base::EnumerateForwards(nsBaseArrayEnumFunc(aFunc),
-                                              aData);
-  }
-
-  bool EnumerateBackwards(nsCOMArrayEnumFunc aFunc, void* aData)
-  {
-    return nsCOMArray_base::EnumerateBackwards(nsBaseArrayEnumFunc(aFunc),
-                                               aData);
-  }
-
   typedef int (*nsCOMArrayComparatorFunc)(T* aElement1, T* aElement2,
                                           void* aData);
 
   void Sort(nsCOMArrayComparatorFunc aFunc, void* aData)
   {
     nsCOMArray_base::Sort(nsBaseArrayComparatorFunc(aFunc), aData);
   }
 
@@ -438,16 +428,32 @@ public:
    * nsCOMArray<nsISomeInterface> array;
    * *length = array.Forget(retval);
    */
   uint32_t Forget(T*** aElements)
   {
     return nsCOMArray_base::Forget(reinterpret_cast<nsISupports***>(aElements));
   }
 
+  // Methods for range-based for loops.
+  iterator begin() { return iterator(*this, 0); }
+  const_iterator begin() const { return const_iterator(*this, 0); }
+  const_iterator cbegin() const { return begin(); }
+  iterator end() { return iterator(*this, Length()); }
+  const_iterator end() const { return const_iterator(*this, Length()); }
+  const_iterator cend() const { return end(); }
+
+  // Methods for reverse iterating.
+  reverse_iterator rbegin() { return reverse_iterator(end()); }
+  const_reverse_iterator rbegin() const { return const_reverse_iterator(end()); }
+  const_reverse_iterator crbegin() const { return rbegin(); }
+  reverse_iterator rend() { return reverse_iterator(begin()); }
+  const_reverse_iterator rend() const { return const_reverse_iterator(begin()); }
+  const_reverse_iterator crend() const { return rend(); }
+
 private:
 
   // don't implement these!
   nsCOMArray<T>& operator=(const nsCOMArray<T>& aOther) = delete;
 };
 
 template<typename T>
 inline void
--- a/xpcom/ds/nsTArray.h
+++ b/xpcom/ds/nsTArray.h
@@ -4,16 +4,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef nsTArray_h__
 #define nsTArray_h__
 
 #include "nsTArrayForwardDeclare.h"
 #include "mozilla/Alignment.h"
+#include "mozilla/ArrayIterator.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/Attributes.h"
 #include &qu