Merge mozilla-central to mozilla-inbound
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Fri, 03 Mar 2017 13:06:37 +0100
changeset 374840 17aafd546d57dfac9a403e1cfc78a89a82cec01a
parent 374839 e824e868c3799a2322c57724e4316b514507c3de (current diff)
parent 374798 a793136c90bc5d32f2c82aa7dea4bc300c4f1836 (diff)
child 374841 cfd2fd77ff046cfbe2d1693858f093df9af41d74
push id10863
push userjlorenzo@mozilla.com
push dateMon, 06 Mar 2017 23:02:23 +0000
treeherdermozilla-aurora@0931190cd725 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone54.0a1
Merge mozilla-central to mozilla-inbound
devtools/client/framework/source-map-util.js
devtools/client/framework/source-map-worker.js
devtools/client/framework/source-map.js
js/src/wasm/WasmTypes.cpp
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
toolkit/mozapps/extensions/content/extensions.js
--- 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) +
@@ -1172,16 +1174,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
@@ -639,16 +641,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
@@ -4706,19 +4706,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
@@ -5567,21 +5564,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/toolkit/mozapps/installer/packager.mk
+++ b/toolkit/mozapps/installer/packager.mk
@@ -94,17 +94,18 @@ make-package-internal: prepare-package m
 
 make-package: FORCE
 	$(MAKE) make-package-internal
 	$(TOUCH) $@
 
 GARBAGE += make-package
 
 make-sourcestamp-file::
-	@awk '$$2 == "MOZ_BUILDID" {print $$3}' $(DEPTH)/buildid.h > $(MOZ_SOURCESTAMP_FILE)
+	$(NSINSTALL) -D $(DIST)/$(PKG_PATH)
+	@echo '$(BUILDID)' > $(MOZ_SOURCESTAMP_FILE)
 ifdef MOZ_INCLUDE_SOURCE_INFO
 	@awk '$$2 == "MOZ_SOURCE_URL" {print $$3}' $(DEPTH)/source-repo.h >> $(MOZ_SOURCESTAMP_FILE)
 endif
 
 .PHONY: make-buildinfo-file
 make-buildinfo-file:
 	$(PYTHON) $(MOZILLA_DIR)/toolkit/mozapps/installer/informulate.py \
 		$(MOZ_BUILDINFO_FILE) \
@@ -154,17 +155,17 @@ upload: checksum
 		$(CHECKSUM_FILES)
 
 # source-package creates a source tarball from the files in MOZ_PKG_SRCDIR,
 # which is either set to a clean checkout or defaults to $topsrcdir
 source-package:
 	@echo 'Generate the sourcestamp file'
 	# Make sure to have repository information available and then generate the
 	# sourcestamp file.
-	$(MAKE) -C $(DEPTH) 'source-repo.h' 'buildid.h'
+	$(MAKE) -C $(DEPTH) 'source-repo.h'
 	$(MAKE) make-sourcestamp-file
 	@echo 'Packaging source tarball...'
 	# We want to include the sourcestamp file in the source tarball, so copy it
 	# in the root source directory. This is useful to enable telemetry submissions
 	# from builds made from the source package with the correct revision information.
 	# Don't bother removing it as this is only used by automation.
 	@cp $(MOZ_SOURCESTAMP_FILE) '$(MOZ_PKG_SRCDIR)/sourcestamp.txt'
 	$(MKDIR) -p $(DIST)/$(PKG_SRCPACK_PATH)
--- 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