Merge mozilla-inbound to mozilla-central a=merge
authorRazvan Maries <rmaries@mozilla.com>
Fri, 05 Jul 2019 00:40:17 +0300
changeset 540995 32d7797bd8bd91e7b62ef2a5e19b8888881766f1
parent 540963 9340f815a2d1fac178fae3e40f0134b9aab5e252 (current diff)
parent 540994 d5470ddece8d5a0d85cc55141e3ff473387a9c37 (diff)
child 540996 760966ed9ccf1457f49e998a2a53ae20dacb4bb8
child 541013 ac50a7bd09b0a9262eed73f9dee2ffbd0e1a33b2
push id11533
push userarchaeopteryx@coole-files.de
push dateMon, 08 Jul 2019 18:18:03 +0000
treeherdermozilla-beta@f4452e031aed [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmerge
milestone69.0a1
first release with
nightly linux32
32d7797bd8bd / 69.0a1 / 20190704214457 / files
nightly linux64
32d7797bd8bd / 69.0a1 / 20190704214457 / files
nightly mac
32d7797bd8bd / 69.0a1 / 20190704214457 / files
nightly win32
32d7797bd8bd / 69.0a1 / 20190704214457 / files
nightly win64
32d7797bd8bd / 69.0a1 / 20190704214457 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge mozilla-inbound to mozilla-central a=merge
dom/base/nsDOMWindowList.cpp
dom/base/nsDOMWindowList.h
testing/mochitest/nested_setup.js
testing/specialpowers/content/MozillaLogger.js
testing/specialpowers/content/SpecialPowersObserver.jsm
testing/specialpowers/content/SpecialPowersObserverAPI.js
testing/specialpowers/content/specialpowers.js
testing/specialpowers/content/specialpowersAPI.js
testing/specialpowers/content/specialpowersFrameScript.js
--- a/browser/base/content/test/general/head.js
+++ b/browser/base/content/test/general/head.js
@@ -156,25 +156,21 @@ function setTestPluginEnabledState(newEn
   var oldEnabledState = plugin.enabledState;
   plugin.enabledState = newEnabledState;
   SimpleTest.registerCleanupFunction(function() {
     getTestPlugin(pluginName).enabledState = oldEnabledState;
   });
 }
 
 function pushPrefs(...aPrefs) {
-  return new Promise(resolve => {
-    SpecialPowers.pushPrefEnv({"set": aPrefs}, resolve);
-  });
+  return SpecialPowers.pushPrefEnv({"set": aPrefs});
 }
 
 function popPrefs() {
-  return new Promise(resolve => {
-    SpecialPowers.popPrefEnv(resolve);
-  });
+  return SpecialPowers.popPrefEnv();
 }
 
 function updateBlocklist(aCallback) {
   var blocklistNotifier = Cc["@mozilla.org/extensions/blocklist;1"]
                           .getService(Ci.nsITimerCallback);
   var observer = function() {
     Services.obs.removeObserver(observer, "blocklist-updated");
     SimpleTest.executeSoon(aCallback);
--- a/browser/base/content/test/performance/browser_startup_content.js
+++ b/browser/base/content/test/performance/browser_startup_content.js
@@ -15,18 +15,19 @@
 "use strict";
 
 /* Set this to true only for debugging purpose; it makes the output noisy. */
 const kDumpAllStacks = false;
 
 const whitelist = {
   modules: new Set([
     "chrome://mochikit/content/ShutdownLeaksCollector.jsm",
-    "resource://specialpowers/specialpowers.js",
-    "resource://specialpowers/specialpowersAPI.js",
+    "resource://specialpowers/SpecialPowersChild.jsm",
+    "resource://specialpowers/SpecialPowersAPI.jsm",
+    "resource://specialpowers/WrapPrivileged.jsm",
 
     "resource://gre/modules/ContentProcessSingleton.jsm",
 
     // General utilities
     "resource://gre/modules/AppConstants.jsm",
     "resource://gre/modules/AsyncShutdown.jsm",
     "resource://gre/modules/DeferredTask.jsm",
     "resource://gre/modules/PromiseUtils.jsm",
@@ -59,18 +60,16 @@ const whitelist = {
 
     // Extensions
     "resource://gre/modules/ExtensionProcessScript.jsm",
     "resource://gre/modules/ExtensionUtils.jsm",
     "resource://gre/modules/MessageChannel.jsm",
   ]),
   frameScripts: new Set([
     // Test related
-    "resource://specialpowers/MozillaLogger.js",
-    "resource://specialpowers/specialpowersFrameScript.js",
     "chrome://mochikit/content/shutdown-leaks-collector.js",
     "chrome://mochikit/content/tests/SimpleTest/AsyncUtilsContent.js",
     "chrome://mochikit/content/tests/BrowserTestUtils/content-utils.js",
 
     // Browser front-end
     "chrome://global/content/browser-content.js",
 
     // Forms
--- a/browser/components/originattributes/test/mochitest/test_permissions_api.html
+++ b/browser/components/originattributes/test/mochitest/test_permissions_api.html
@@ -124,19 +124,19 @@
           () => ok(false, `query should not have rejected for '${name}'`));
       },
       testStatusOnChange() {
         return new Promise((resolve) => {
           SpecialPowers.popPermissions(() => {
             const permission = "geolocation";
             const promiseGranted = this.promiseStateChanged(permission, "granted");
             this.setPermissions(ALLOW_ACTION);
-            promiseGranted.then(() => {
+            promiseGranted.then(async () => {
               const promisePrompt = this.promiseStateChanged(permission, "prompt");
-              SpecialPowers.popPermissions();
+              await SpecialPowers.popPermissions();
               return promisePrompt;
             }).then(resolve);
           });
         });
       },
       testInvalidQuery() {
         return aWindow.navigator.permissions
           .query({ name: "invalid" })
--- a/browser/extensions/formautofill/test/mochitest/formautofill_common.js
+++ b/browser/extensions/formautofill/test/mochitest/formautofill_common.js
@@ -169,78 +169,71 @@ function checkMenuEntries(expectedValues
   let expectedLength = isFormAutofillResult ? expectedValues.length + 1 : expectedValues.length;
 
   is(actualValues.length, expectedLength, " Checking length of expected menu");
   for (let i = 0; i < expectedValues.length; i++) {
     is(actualValues[i], expectedValues[i], " Checking menu entry #" + i);
   }
 }
 
-function invokeAsyncChromeTask(message, response, payload = {}) {
+function invokeAsyncChromeTask(message, payload = {}) {
   info(`expecting the chrome task finished: ${message}`);
-  return new Promise(resolve => {
-    formFillChromeScript.sendAsyncMessage(message, payload);
-    formFillChromeScript.addMessageListener(response, function onReceived(data) {
-      formFillChromeScript.removeMessageListener(response, onReceived);
-
-      resolve(data);
-    });
-  });
+  return formFillChromeScript.sendQuery(message, payload);
 }
 
 async function addAddress(address) {
-  await invokeAsyncChromeTask("FormAutofillTest:AddAddress", "FormAutofillTest:AddressAdded", {address});
+  await invokeAsyncChromeTask("FormAutofillTest:AddAddress", {address});
   await sleep();
 }
 
 async function removeAddress(guid) {
-  return invokeAsyncChromeTask("FormAutofillTest:RemoveAddress", "FormAutofillTest:AddressRemoved", {guid});
+  return invokeAsyncChromeTask("FormAutofillTest:RemoveAddress", {guid});
 }
 
 async function updateAddress(guid, address) {
-  return invokeAsyncChromeTask("FormAutofillTest:UpdateAddress", "FormAutofillTest:AddressUpdated", {address, guid});
+  return invokeAsyncChromeTask("FormAutofillTest:UpdateAddress", {address, guid});
 }
 
 async function checkAddresses(expectedAddresses) {
-  return invokeAsyncChromeTask("FormAutofillTest:CheckAddresses", "FormAutofillTest:areAddressesMatching", {expectedAddresses});
+  return invokeAsyncChromeTask("FormAutofillTest:CheckAddresses", {expectedAddresses});
 }
 
 async function cleanUpAddresses() {
-  return invokeAsyncChromeTask("FormAutofillTest:CleanUpAddresses", "FormAutofillTest:AddressesCleanedUp");
+  return invokeAsyncChromeTask("FormAutofillTest:CleanUpAddresses");
 }
 
 async function addCreditCard(creditcard) {
-  await invokeAsyncChromeTask("FormAutofillTest:AddCreditCard", "FormAutofillTest:CreditCardAdded", {creditcard});
+  await invokeAsyncChromeTask("FormAutofillTest:AddCreditCard", {creditcard});
   await sleep();
 }
 
 async function removeCreditCard(guid) {
-  return invokeAsyncChromeTask("FormAutofillTest:RemoveCreditCard", "FormAutofillTest:CreditCardRemoved", {guid});
+  return invokeAsyncChromeTask("FormAutofillTest:RemoveCreditCard", {guid});
 }
 
 async function checkCreditCards(expectedCreditCards) {
-  return invokeAsyncChromeTask("FormAutofillTest:CheckCreditCards", "FormAutofillTest:areCreditCardsMatching", {expectedCreditCards});
+  return invokeAsyncChromeTask("FormAutofillTest:CheckCreditCards", {expectedCreditCards});
 }
 
 async function cleanUpCreditCards() {
-  return invokeAsyncChromeTask("FormAutofillTest:CleanUpCreditCards", "FormAutofillTest:CreditCardsCleanedUp");
+  return invokeAsyncChromeTask("FormAutofillTest:CleanUpCreditCards");
 }
 
 async function cleanUpStorage() {
   await cleanUpAddresses();
   await cleanUpCreditCards();
 }
 
 async function canTestOSKeyStoreLogin() {
-  let {canTest} = await invokeAsyncChromeTask("FormAutofillTest:CanTestOSKeyStoreLogin", "FormAutofillTest:CanTestOSKeyStoreLoginResult");
+  let {canTest} = await invokeAsyncChromeTask("FormAutofillTest:CanTestOSKeyStoreLogin");
   return canTest;
 }
 
 async function waitForOSKeyStoreLogin(login = false) {
-  await invokeAsyncChromeTask("FormAutofillTest:OSKeyStoreLogin", "FormAutofillTest:OSKeyStoreLoggedIn", {login});
+  await invokeAsyncChromeTask("FormAutofillTest:OSKeyStoreLogin", {login});
 }
 
 function patchRecordCCNumber(record) {
   const number = record["cc-number"];
   const ccNumberFmt = {
     affix: "****",
     label: number.substr(-4),
   };
@@ -297,25 +290,23 @@ function formAutoFillCommonSetup() {
   formFillChromeScript.addMessageListener("onpopupshown", ({results}) => {
     gLastAutoCompleteResults = results;
     if (gPopupShownListener) {
       gPopupShownListener({results});
     }
   });
 
   add_task(async function setup() {
-    formFillChromeScript.sendAsyncMessage("setup");
     info(`expecting the storage setup`);
-    await formFillChromeScript.promiseOneMessage("setup-finished");
+    await formFillChromeScript.sendQuery("setup");
   });
 
   SimpleTest.registerCleanupFunction(async () => {
-    formFillChromeScript.sendAsyncMessage("cleanup");
     info(`expecting the storage cleanup`);
-    await formFillChromeScript.promiseOneMessage("cleanup-finished");
+    await formFillChromeScript.sendQuery("cleanup");
 
     formFillChromeScript.destroy();
     expectingPopup = null;
   });
 
   document.addEventListener("DOMContentLoaded", function() {
     defaultTextColor = window.getComputedStyle(document.querySelector("input"))
       .getPropertyValue("color");
--- a/browser/extensions/formautofill/test/mochitest/formautofill_parent_utils.js
+++ b/browser/extensions/formautofill/test/mochitest/formautofill_parent_utils.js
@@ -8,16 +8,18 @@ const {Services} = ChromeUtils.import("r
 const {FormAutofill} = ChromeUtils.import("resource://formautofill/FormAutofill.jsm");
 const {FormAutofillUtils} = ChromeUtils.import("resource://formautofill/FormAutofillUtils.jsm");
 const {OSKeyStoreTestUtils} = ChromeUtils.import("resource://testing-common/OSKeyStoreTestUtils.jsm");
 
 let {formAutofillStorage} = ChromeUtils.import("resource://formautofill/FormAutofillStorage.jsm");
 
 const {ADDRESSES_COLLECTION_NAME, CREDITCARDS_COLLECTION_NAME} = FormAutofillUtils;
 
+let destroyed = false;
+
 var ParentUtils = {
   async _getRecords(collectionName) {
     return new Promise(resolve => {
       Services.cpmm.addMessageListener("FormAutofill:Records", function getResult({data}) {
         Services.cpmm.removeMessageListener("FormAutofill:Records", getResult);
         resolve(data);
       });
       Services.cpmm.sendAsyncMessage("FormAutofill:GetRecords", {searchString: "", collectionName});
@@ -29,30 +31,34 @@ var ParentUtils = {
 
     return new Promise(resolve => {
       Services.obs.addObserver(function observer(subject, obsTopic, data) {
         if (type && data != type || !!--count) {
           return;
         }
 
         // every notification type should have the collection name.
-        let allowedNames = [ADDRESSES_COLLECTION_NAME, CREDITCARDS_COLLECTION_NAME];
-        assert.ok(allowedNames.includes(subject.wrappedJSObject.collectionName),
-                  "should include the collection name");
-        // every notification except removeAll should have a guid.
-        if (data != "removeAll") {
-          assert.ok(subject.wrappedJSObject.guid, "should have a guid");
+        // We're not allowed to trigger assertions during mochitest
+        // cleanup functions.
+        if (!destroyed) {
+          let allowedNames = [ADDRESSES_COLLECTION_NAME, CREDITCARDS_COLLECTION_NAME];
+          assert.ok(allowedNames.includes(subject.wrappedJSObject.collectionName),
+                    "should include the collection name");
+          // every notification except removeAll should have a guid.
+          if (data != "removeAll") {
+            assert.ok(subject.wrappedJSObject.guid, "should have a guid");
+          }
         }
         Services.obs.removeObserver(observer, obsTopic);
         resolve();
       }, topic);
     });
   },
 
-  async _operateRecord(collectionName, type, msgData, contentMsg) {
+  async _operateRecord(collectionName, type, msgData) {
     let times, topic;
 
     if (collectionName == ADDRESSES_COLLECTION_NAME) {
       switch (type) {
         case "add": {
           Services.cpmm.sendAsyncMessage("FormAutofill:SaveAddress", msgData);
           break;
         }
@@ -78,46 +84,43 @@ var ParentUtils = {
           times = msgData.guids.length;
           Services.cpmm.sendAsyncMessage("FormAutofill:RemoveCreditCards", msgData);
           break;
         }
       }
     }
 
     await this._storageChangeObserved({type, times, topic});
-    sendAsyncMessage(contentMsg);
   },
 
-  async operateAddress(type, msgData, contentMsg) {
+  async operateAddress(type, msgData) {
     await this._operateRecord(ADDRESSES_COLLECTION_NAME, ...arguments);
   },
 
-  async operateCreditCard(type, msgData, contentMsg) {
+  async operateCreditCard(type, msgData) {
     await this._operateRecord(CREDITCARDS_COLLECTION_NAME, ...arguments);
   },
 
   async cleanUpAddresses() {
     const guids = (await this._getRecords(ADDRESSES_COLLECTION_NAME)).map(record => record.guid);
 
     if (guids.length == 0) {
-      sendAsyncMessage("FormAutofillTest:AddressesCleanedUp");
       return;
     }
 
     await this.operateAddress("remove", {guids}, "FormAutofillTest:AddressesCleanedUp");
   },
 
   async cleanUpCreditCards() {
     if (!FormAutofill.isAutofillCreditCardsAvailable) {
       return;
     }
     const guids = (await this._getRecords(CREDITCARDS_COLLECTION_NAME)).map(record => record.guid);
 
     if (guids.length == 0) {
-      sendAsyncMessage("FormAutofillTest:CreditCardsCleanedUp");
       return;
     }
 
     await this.operateCreditCard("remove", {guids}, "FormAutofillTest:CreditCardsCleanedUp");
   },
 
   setup() {
     OSKeyStoreTestUtils.setup();
@@ -162,87 +165,81 @@ var ParentUtils = {
         return false;
       }
     }
 
     return true;
   },
 
   async checkAddresses({expectedAddresses}) {
-    const areMatched = await this._checkRecords(ADDRESSES_COLLECTION_NAME, expectedAddresses);
-
-    sendAsyncMessage("FormAutofillTest:areAddressesMatching", areMatched);
+    return this._checkRecords(ADDRESSES_COLLECTION_NAME, expectedAddresses);
   },
 
   async checkCreditCards({expectedCreditCards}) {
-    const areMatched = await this._checkRecords(CREDITCARDS_COLLECTION_NAME, expectedCreditCards);
-
-    sendAsyncMessage("FormAutofillTest:areCreditCardsMatching", areMatched);
+    return this._checkRecords(CREDITCARDS_COLLECTION_NAME, expectedCreditCards);
   },
 
   observe(subject, topic, data) {
-    assert.ok(topic === "formautofill-storage-changed");
+    if (!destroyed) {
+      assert.ok(topic === "formautofill-storage-changed");
+    }
     sendAsyncMessage("formautofill-storage-changed", {subject: null, topic, data});
   },
 };
 
 Services.obs.addObserver(ParentUtils, "formautofill-storage-changed");
 
 Services.mm.addMessageListener("FormAutofill:FieldsIdentified", () => {
-  sendAsyncMessage("FormAutofillTest:FieldsIdentified");
+  return null;
 });
 
 addMessageListener("FormAutofillTest:AddAddress", (msg) => {
-  ParentUtils.operateAddress("add", msg, "FormAutofillTest:AddressAdded");
+  return ParentUtils.operateAddress("add", msg);
 });
 
 addMessageListener("FormAutofillTest:RemoveAddress", (msg) => {
-  ParentUtils.operateAddress("remove", msg, "FormAutofillTest:AddressRemoved");
+  return ParentUtils.operateAddress("remove", msg);
 });
 
 addMessageListener("FormAutofillTest:UpdateAddress", (msg) => {
-  ParentUtils.operateAddress("update", msg, "FormAutofillTest:AddressUpdated");
+  return ParentUtils.operateAddress("update", msg);
 });
 
 addMessageListener("FormAutofillTest:CheckAddresses", (msg) => {
-  ParentUtils.checkAddresses(msg);
+  return ParentUtils.checkAddresses(msg);
 });
 
 addMessageListener("FormAutofillTest:CleanUpAddresses", (msg) => {
-  ParentUtils.cleanUpAddresses();
+  return ParentUtils.cleanUpAddresses();
 });
 
 addMessageListener("FormAutofillTest:AddCreditCard", (msg) => {
-  ParentUtils.operateCreditCard("add", msg, "FormAutofillTest:CreditCardAdded");
+  return ParentUtils.operateCreditCard("add", msg);
 });
 
 addMessageListener("FormAutofillTest:RemoveCreditCard", (msg) => {
-  ParentUtils.operateCreditCard("remove", msg, "FormAutofillTest:CreditCardRemoved");
+  return ParentUtils.operateCreditCard("remove", msg);
 });
 
 addMessageListener("FormAutofillTest:CheckCreditCards", (msg) => {
-  ParentUtils.checkCreditCards(msg);
+  return ParentUtils.checkCreditCards(msg);
 });
 
 addMessageListener("FormAutofillTest:CleanUpCreditCards", (msg) => {
-  ParentUtils.cleanUpCreditCards();
+  return ParentUtils.cleanUpCreditCards();
 });
 
 addMessageListener("FormAutofillTest:CanTestOSKeyStoreLogin", (msg) => {
-  sendAsyncMessage("FormAutofillTest:CanTestOSKeyStoreLoginResult",
-    {canTest: OSKeyStoreTestUtils.canTestOSKeyStoreLogin()});
+  return {canTest: OSKeyStoreTestUtils.canTestOSKeyStoreLogin()};
 });
 
 addMessageListener("FormAutofillTest:OSKeyStoreLogin", async (msg) => {
   await OSKeyStoreTestUtils.waitForOSKeyStoreLogin(msg.login);
-  sendAsyncMessage("FormAutofillTest:OSKeyStoreLoggedIn");
 });
 
 addMessageListener("setup", async () => {
   ParentUtils.setup();
-  sendAsyncMessage("setup-finished", {});
 });
 
 addMessageListener("cleanup", async () => {
+  destroyed = true;
   await ParentUtils.cleanup();
-
-  sendAsyncMessage("cleanup-finished", {});
 });
--- a/browser/extensions/formautofill/test/mochitest/test_address_level_1_submission.html
+++ b/browser/extensions/formautofill/test/mochitest/test_address_level_1_submission.html
@@ -39,18 +39,18 @@ add_task(async function test_DE_is_valid
       return !data.defaultLocale.sub_keys;
     });
   });
 
   SimpleTest.registerCleanupFunction(() => {
     chromeScript.destroy();
   });
 
-  let result = chromeScript.sendSyncMessage("CheckSubKeys");
-  ok(result[0][0], "Check that there are no sub_keys for the test country");
+  let result = await chromeScript.sendQuery("CheckSubKeys");
+  ok(result, "Check that there are no sub_keys for the test country");
 });
 
 add_task(async function test_form_will_submit_without_sub_keys() {
   await SpecialPowers.pushPrefEnv({
     set: [
       // This needs to match the country in the previous test and must have no sub_keys.
       ["browser.search.region", "DE"],
       // We already verified the first time use case in browser test
--- a/chrome/nsChromeRegistry.cpp
+++ b/chrome/nsChromeRegistry.cpp
@@ -11,17 +11,16 @@
 #include "nsCOMPtr.h"
 #include "nsError.h"
 #include "nsEscape.h"
 #include "nsNetUtil.h"
 #include "nsString.h"
 #include "nsQueryObject.h"
 
 #include "mozilla/dom/URL.h"
-#include "nsDOMWindowList.h"
 #include "nsIConsoleService.h"
 #include "mozilla/dom/Document.h"
 #include "nsIDOMWindow.h"
 #include "nsIObserverService.h"
 #include "nsIScriptError.h"
 #include "nsIWindowMediator.h"
 #include "nsIPrefService.h"
 #include "mozilla/Preferences.h"
--- a/devtools/client/inspector/extensions/test/browser_inspector_extension_sidebar.js
+++ b/devtools/client/inspector/extensions/test/browser_inspector_extension_sidebar.js
@@ -268,17 +268,17 @@ add_task(async function teardownExtensio
   extension = null;
 });
 
 add_task(async function testActiveTabOnNonExistingSidebar() {
   // Set a fake non existing sidebar id in the activeSidebar pref,
   // to simulate the scenario where an extension has installed a sidebar
   // which has been saved in the preference but it doesn't exist anymore.
   await SpecialPowers.pushPrefEnv({
-    set: [["devtools.inspector.activeSidebar"], "unexisting-sidebar-id"],
+    set: [["devtools.inspector.activeSidebar", "unexisting-sidebar-id"]],
   });
 
   const res = await openInspectorForURL("about:blank");
   inspector = res.inspector;
   toolbox = res.toolbox;
 
   const onceSidebarCreated = toolbox.once(`extension-sidebar-created-${SIDEBAR_ID}`);
   toolbox.registerInspectorExtensionSidebar(SIDEBAR_ID, {title: SIDEBAR_TITLE});
--- a/devtools/client/inspector/markup/views/element-editor.js
+++ b/devtools/client/inspector/markup/views/element-editor.js
@@ -29,17 +29,17 @@ const HTML_VOID_ELEMENTS = [
   "hr", "img", "input", "keygen", "link", "meta", "param", "source",
   "track", "wbr",
 ];
 
 // Contains only valid computed display property types of the node to display in the
 // element markup and their respective title tooltip text.
 const DISPLAY_TYPES = {
   "flex": INSPECTOR_L10N.getStr("markupView.display.flex.tooltiptext"),
-  "inline-flex": INSPECTOR_L10N.getStr("markupView.display.flex.tooltiptext"),
+  "inline-flex": INSPECTOR_L10N.getStr("markupView.display.inlineFlex.tooltiptext"),
   "grid": INSPECTOR_L10N.getStr("markupView.display.grid.tooltiptext"),
   "inline-grid": INSPECTOR_L10N.getStr("markupView.display.inlineGrid.tooltiptext"),
   "subgrid": INSPECTOR_L10N.getStr("markupView.display.subgrid.tooltiptiptext"),
   "flow-root": INSPECTOR_L10N.getStr("markupView.display.flowRoot.tooltiptext"),
   "contents": INSPECTOR_L10N.getStr("markupView.display.contents.tooltiptext2"),
 };
 
 /**
--- a/devtools/client/shared/test/shared-head.js
+++ b/devtools/client/shared/test/shared-head.js
@@ -451,16 +451,18 @@ var closeTabAndToolbox = async function(
   if (TargetFactory.isKnownTab(tab)) {
     const target = await TargetFactory.forTab(tab);
     if (target) {
       await gDevTools.closeToolbox(target);
     }
   }
 
   await removeTab(tab);
+
+  await new Promise(resolve => setTimeout(resolve, 0));
 };
 
 /**
  * Close a toolbox and the current tab.
  * @param {Toolbox} toolbox The toolbox to close.
  * @return {Promise} Resolves when the toolbox and tab have been destroyed and
  * closed.
  */
@@ -591,20 +593,18 @@ function waitForClipboardPromise(setup, 
  *
  * @param {String} preferenceName
  *        The name of the preference to updated
  * @param {} value
  *        The preference value, type can vary
  * @return {Promise} resolves when the preferences have been updated
  */
 function pushPref(preferenceName, value) {
-  return new Promise(resolve => {
-    const options = {"set": [[preferenceName, value]]};
-    SpecialPowers.pushPrefEnv(options, resolve);
-  });
+  const options = {"set": [[preferenceName, value]]};
+  return SpecialPowers.pushPrefEnv(options);
 }
 
 /**
  * Lookup the provided dotted path ("prop1.subprop2.myProp") in the provided object.
  *
  * @param {Object} obj
  *        Object to expand.
  * @param {String} path
--- a/devtools/shared/webconsole/test/test_console_serviceworker_cached.html
+++ b/devtools/shared/webconsole/test/test_console_serviceworker_cached.html
@@ -87,17 +87,17 @@ let startTest = async function () {
   await swr.unregister();
 
   SimpleTest.finish();
 };
 addEventListener("load", startTest);
 
 // This test needs to add tabs that are controlled by a service worker
 // so use some special powers to dig around and find gBrowser
-let {gBrowser} = SpecialPowers._getTopChromeWindow(SpecialPowers.window.get());
+let {gBrowser} = SpecialPowers._getTopChromeWindow(SpecialPowers.window);
 
 SimpleTest.registerCleanupFunction(() => {
   while (gBrowser.tabs.length > 1) {
     gBrowser.removeCurrentTab();
   }
 });
 
 function addTab(url) {
--- a/docshell/base/BrowsingContext.cpp
+++ b/docshell/base/BrowsingContext.cpp
@@ -11,16 +11,17 @@
 #include "mozilla/dom/CanonicalBrowsingContext.h"
 #include "mozilla/dom/BrowsingContextGroup.h"
 #include "mozilla/dom/BrowsingContextBinding.h"
 #include "mozilla/dom/ContentChild.h"
 #include "mozilla/dom/ContentParent.h"
 #include "mozilla/dom/Element.h"
 #include "mozilla/dom/Location.h"
 #include "mozilla/dom/LocationBinding.h"
+#include "mozilla/dom/StructuredCloneTags.h"
 #include "mozilla/dom/WindowBinding.h"
 #include "mozilla/dom/WindowGlobalChild.h"
 #include "mozilla/dom/WindowGlobalParent.h"
 #include "mozilla/dom/WindowProxyHolder.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/HashTable.h"
 #include "mozilla/Logging.h"
@@ -85,16 +86,22 @@ void BrowsingContext::Init() {
 /* static */
 LogModule* BrowsingContext::GetLog() { return gBrowsingContextLog; }
 
 /* static */
 already_AddRefed<BrowsingContext> BrowsingContext::Get(uint64_t aId) {
   return do_AddRef(sBrowsingContexts->Get(aId));
 }
 
+/* static */
+already_AddRefed<BrowsingContext> BrowsingContext::GetFromWindow(
+    WindowProxyHolder& aProxy) {
+  return do_AddRef(aProxy.get());
+}
+
 CanonicalBrowsingContext* BrowsingContext::Canonical() {
   return CanonicalBrowsingContext::Cast(this);
 }
 
 /* static */
 already_AddRefed<BrowsingContext> BrowsingContext::Create(
     BrowsingContext* aParent, BrowsingContext* aOpener, const nsAString& aName,
     Type aType) {
@@ -546,16 +553,55 @@ nsISupports* BrowsingContext::GetParentO
   return xpc::NativeGlobal(xpc::PrivilegedJunkScope());
 }
 
 JSObject* BrowsingContext::WrapObject(JSContext* aCx,
                                       JS::Handle<JSObject*> aGivenProto) {
   return BrowsingContext_Binding::Wrap(aCx, this, aGivenProto);
 }
 
+bool BrowsingContext::WriteStructuredClone(JSContext* aCx,
+                                           JSStructuredCloneWriter* aWriter,
+                                           StructuredCloneHolder* aHolder) {
+  return (JS_WriteUint32Pair(aWriter, SCTAG_DOM_BROWSING_CONTEXT, 0) &&
+          JS_WriteUint32Pair(aWriter, uint32_t(Id()), uint32_t(Id() >> 32)));
+}
+
+/* static */
+JSObject* BrowsingContext::ReadStructuredClone(JSContext* aCx,
+                                               JSStructuredCloneReader* aReader,
+                                               StructuredCloneHolder* aHolder) {
+  uint32_t idLow = 0;
+  uint32_t idHigh = 0;
+  if (!JS_ReadUint32Pair(aReader, &idLow, &idHigh)) {
+    return nullptr;
+  }
+  uint64_t id = uint64_t(idHigh) << 32 | idLow;
+
+  // Note: Do this check after reading our ID data. Returning null will abort
+  // the decode operation anyway, but we should at least be as safe as possible.
+  if (NS_WARN_IF(!NS_IsMainThread())) {
+    MOZ_DIAGNOSTIC_ASSERT(false,
+                          "We shouldn't be trying to decode a BrowsingContext "
+                          "on a background thread.");
+    return nullptr;
+  }
+
+  JS::RootedValue val(aCx, JS::NullValue());
+  // We'll get rooting hazard errors from the RefPtr destructor if it isn't
+  // destroyed before we try to return a raw JSObject*, so create it in its own
+  // scope.
+  if (RefPtr<BrowsingContext> context = Get(id)) {
+    if (!GetOrCreateDOMReflector(aCx, context, &val) || !val.isObject()) {
+      return nullptr;
+    }
+  }
+  return val.toObjectOrNull();
+}
+
 void BrowsingContext::NotifyUserGestureActivation() {
   // We would set the user gesture activation flag on the top level browsing
   // context, which would automatically be sync to other top level browsing
   // contexts which are in the different process.
   RefPtr<BrowsingContext> topLevelBC = Top();
   USER_ACTIVATION_LOG("Get top level browsing context 0x%08" PRIx64,
                       topLevelBC->Id());
   topLevelBC->SetIsActivatedByUserGesture(true);
--- a/docshell/base/BrowsingContext.h
+++ b/docshell/base/BrowsingContext.h
@@ -46,16 +46,17 @@ class BrowsingContent;
 class BrowsingContextGroup;
 class CanonicalBrowsingContext;
 class ContentParent;
 class Element;
 template <typename>
 struct Nullable;
 template <typename T>
 class Sequence;
+class StructuredCloneHolder;
 struct WindowPostMessageOptions;
 class WindowProxyHolder;
 
 class BrowsingContextBase {
  protected:
   BrowsingContextBase() {
     // default-construct each field.
 #define MOZ_BC_FIELD(name, type) m##name = type();
@@ -99,16 +100,23 @@ class BrowsingContext : public nsWrapper
   static void CleanupContexts(uint64_t aProcessId);
 
   // Look up a BrowsingContext in the current process by ID.
   static already_AddRefed<BrowsingContext> Get(uint64_t aId);
   static already_AddRefed<BrowsingContext> Get(GlobalObject&, uint64_t aId) {
     return Get(aId);
   }
 
+  static already_AddRefed<BrowsingContext> GetFromWindow(
+      WindowProxyHolder& aProxy);
+  static already_AddRefed<BrowsingContext> GetFromWindow(
+      GlobalObject&, WindowProxyHolder& aProxy) {
+    return GetFromWindow(aProxy);
+  }
+
   // Create a brand-new BrowsingContext object.
   static already_AddRefed<BrowsingContext> Create(BrowsingContext* aParent,
                                                   BrowsingContext* aOpener,
                                                   const nsAString& aName,
                                                   Type aType);
 
   // Cast this object to a canonical browsing context, and return it.
   CanonicalBrowsingContext* Canonical();
@@ -261,16 +269,22 @@ class BrowsingContext : public nsWrapper
                       const Sequence<JSObject*>& aTransfer,
                       nsIPrincipal& aSubjectPrincipal, ErrorResult& aError);
   void PostMessageMoz(JSContext* aCx, JS::Handle<JS::Value> aMessage,
                       const WindowPostMessageOptions& aOptions,
                       nsIPrincipal& aSubjectPrincipal, ErrorResult& aError);
 
   JSObject* WrapObject(JSContext* aCx);
 
+  static JSObject* ReadStructuredClone(JSContext* aCx,
+                                       JSStructuredCloneReader* aReader,
+                                       StructuredCloneHolder* aHolder);
+  bool WriteStructuredClone(JSContext* aCx, JSStructuredCloneWriter* aWriter,
+                            StructuredCloneHolder* aHolder);
+
   void StartDelayedAutoplayMediaComponents();
 
   /**
    * Each synced racy field in a BrowsingContext needs to have a epoch value
    * which is used to resolve race conflicts by ensuring that only the last
    * message received in the parent process wins.
    */
   struct FieldEpochs {
--- a/docshell/test/chrome/bug293235_window.xul
+++ b/docshell/test/chrome/bug293235_window.xul
@@ -3,25 +3,23 @@
 
 <window id="293235Test"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
         width="600"
         height="600"
         onload="setTimeout(nextTest, 0);"
         title="bug 293235 test">
 
-  <script src="chrome://mochikit/content/tests/SimpleTest/specialpowersAPI.js"/>
-  <script src="chrome://mochikit/content/tests/SimpleTest/SpecialPowersObserverAPI.js"/>
   <script src="chrome://mochikit/content/tests/SimpleTest/ChromePowers.js"/>
   <script type="application/javascript" src= "chrome://mochikit/content/chrome-harness.js" />
   <script type="application/javascript" src="docshell_helpers.js" />
   <script src="chrome://mochikit/content/tests/SimpleTest/WindowSnapshot.js"></script>
 
   <script type="application/javascript"><![CDATA[
-    const {NetUtil} = ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
+    var {NetUtil} = ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
 
     // Define the generator-iterator for the tests.
     var tests = testIterator();
 
     ////
     // Execute the next test in the generator function.
     //
     function nextTest() {
--- a/docshell/test/chrome/bug396649_window.xul
+++ b/docshell/test/chrome/bug396649_window.xul
@@ -3,18 +3,16 @@
 
 <window id="396649Test"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
         width="600"
         height="600"
         onload="setTimeout(nextTest, 0);"
         title="bug 396649 test">
 
-  <script src="chrome://mochikit/content/tests/SimpleTest/specialpowersAPI.js"/>
-  <script src="chrome://mochikit/content/tests/SimpleTest/SpecialPowersObserverAPI.js"/>
   <script src="chrome://mochikit/content/tests/SimpleTest/ChromePowers.js"/>
   <script type="application/javascript" src= "chrome://mochikit/content/chrome-harness.js" />
   <script type="application/javascript" src="docshell_helpers.js" />
   <script type="application/javascript"><![CDATA[
   
     // Define the generator-iterator for the tests.
     var tests = testIterator();
     
--- a/docshell/test/chrome/bug89419_window.xul
+++ b/docshell/test/chrome/bug89419_window.xul
@@ -4,18 +4,17 @@
 <window id="89419Test"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
         width="600"
         height="600"
         onload="setTimeout(nextTest, 0);"
         title="bug 89419 test">
 
   <script type="application/javascript" src= "chrome://mochikit/content/chrome-harness.js" />
-  <script src="chrome://mochikit/content/tests/SimpleTest/SpecialPowersObserverAPI.js"/>
-  <script src="chrome://mochikit/content/tests/SimpleTest/specialpowers.js"/>
+  <script src="chrome://mochikit/content/tests/SimpleTest/ChromePowers.js"/>
   <script type="application/javascript" src="docshell_helpers.js" />
   <script src="chrome://mochikit/content/tests/SimpleTest/WindowSnapshot.js"></script>
 
   <script type="application/javascript"><![CDATA[
     // Define the generator-iterator for the tests.
     var tests = testIterator();
 
     ////
--- a/docshell/test/mochitest/test_triggeringprincipal_location_seturi.html
+++ b/docshell/test/mochitest/test_triggeringprincipal_location_seturi.html
@@ -39,28 +39,30 @@ function checkLoadFrame1() {
   SpecialPowers.wrap(myFrame1.contentWindow).location.hash = "#bar";
   is(SpecialPowers.wrap(myFrame1.contentWindow).location.href, SAME_ORIGIN_URI + "#bar",
     "initial same origin dummy#bar loaded into iframe1");
 
   myFrame1.addEventListener("load", checkNavFrame1);
   myFrame1.src = CROSS_ORIGIN_URI;
 }
 
-function checkNavFrame1() {
+async function checkNavFrame1() {
   myFrame1.removeEventListener("load", checkNavFrame1);
-  is(SpecialPowers.wrap(myFrame1.contentWindow).location.href, CROSS_ORIGIN_URI,
+  is(await SpecialPowers.spawn(myFrame1, [], () => this.content.location.href),
+     CROSS_ORIGIN_URI,
     "cross origin dummy loaded into frame1");
 
   myFrame1.addEventListener("load", checkBackNavFrame1);
   myFrame1.src = SAME_ORIGIN_URI + "#bar";
 }
 
-function checkBackNavFrame1() {
+async function checkBackNavFrame1() {
   myFrame1.removeEventListener("load", checkBackNavFrame1);
-  is(SpecialPowers.wrap(myFrame1.contentWindow).location.href, SAME_ORIGIN_URI + "#bar",
+  is(await SpecialPowers.spawn(myFrame1, [], () => this.content.location.href),
+     SAME_ORIGIN_URI + "#bar",
     "navagiating back to same origin dummy for frame1");
   checkFinish();
 }
 
 // ---- test 2 ----
 
 let myFrame2 = document.createElement("iframe");
 myFrame2.src = "about:blank";
--- a/docshell/test/navigation/NavigationUtils.js
+++ b/docshell/test/navigation/NavigationUtils.js
@@ -50,20 +50,21 @@ function navigateByHyperlink(name) {
   document.body.appendChild(link);
   sendMouseEvent({type: "click"}, link.id);
 }
 
 // /////////////////////////////////////////////////////////////////////////
 // Functions that call into Mochitest framework
 // /////////////////////////////////////////////////////////////////////////
 
-function isNavigated(wnd, message) {
+async function isNavigated(wnd, message) {
   var result = null;
   try {
-    result = SpecialPowers.wrap(wnd).document.body.innerHTML.trim();
+    result = await SpecialPowers.spawn(
+      wnd, [], () => this.content.document.body.innerHTML.trim());
   } catch (ex) {
     result = ex;
   }
   is(result, body, message);
 }
 
 function isBlank(wnd, message) {
   var result = null;
--- a/docshell/test/navigation/test_bug270414.html
+++ b/docshell/test/navigation/test_bug270414.html
@@ -56,21 +56,21 @@ function testChild3() {
     window3.document.open();
     window3.document.write(headerHTML);
     window3.document.write("<script>navigateByHyperlink('child3');</scr" + "ipt>");
     window3.document.write(footerHTML);
     window3.document.close();
   }
 }
 
-xpcWaitForFinishedFrames(function() {
-  isNavigated(frames[0], "Should be able to navigate on-domain opener's children by setting location.");
-  isNavigated(frames[1], "Should be able to navigate on-domain opener's children by calling window.open.");
-  isNavigated(frames[2], "Should be able to navigate on-domain opener's children by submitting form.");
-  isNavigated(frames[3], "Should be able to navigate on-domain opener's children by targeted hyperlink.");
+xpcWaitForFinishedFrames(async function() {
+  await isNavigated(frames[0], "Should be able to navigate on-domain opener's children by setting location.");
+  await isNavigated(frames[1], "Should be able to navigate on-domain opener's children by calling window.open.");
+  await isNavigated(frames[2], "Should be able to navigate on-domain opener's children by submitting form.");
+  await isNavigated(frames[3], "Should be able to navigate on-domain opener's children by targeted hyperlink.");
 
   window0.close();
   window1.close();
   window2.close();
   window3.close();
 
   xpcCleanupWindows();
   SimpleTest.finish();
--- a/docshell/test/navigation/test_child.html
+++ b/docshell/test/navigation/test_child.html
@@ -14,21 +14,21 @@ if (!navigator.platform.startsWith("Win"
 }
 
 window.onload = function() {
   navigateByLocation(frames[0]);
   navigateByOpen("child1");
   navigateByForm("child2");
   navigateByHyperlink("child3");
 
-  xpcWaitForFinishedFrames(function() {
-    isNavigated(frames[0], "Should be able to navigate off-domain child by setting location.");
-    isNavigated(frames[1], "Should be able to navigate off-domain child by calling window.open.");
-    isNavigated(frames[2], "Should be able to navigate off-domain child by submitting form.");
-    isNavigated(frames[3], "Should be able to navigate off-domain child by targeted hyperlink.");
+  xpcWaitForFinishedFrames(async function() {
+    await isNavigated(frames[0], "Should be able to navigate off-domain child by setting location.");
+    await isNavigated(frames[1], "Should be able to navigate off-domain child by calling window.open.");
+    await isNavigated(frames[2], "Should be able to navigate off-domain child by submitting form.");
+    await isNavigated(frames[3], "Should be able to navigate off-domain child by targeted hyperlink.");
 
     xpcCleanupWindows();
     SimpleTest.finish();
   }, 4);
 };
 </script>
 </head>
 <body>
--- a/docshell/test/navigation/test_grandchild.html
+++ b/docshell/test/navigation/test_grandchild.html
@@ -14,21 +14,21 @@ if (!navigator.platform.startsWith("Win"
 }
 
 window.onload = function() {
   navigateByLocation(frames[0].frames[0]);
   navigateByOpen("child1_child0");
   navigateByForm("child2_child0");
   navigateByHyperlink("child3_child0");
 
-  xpcWaitForFinishedFrames(function() {
-    isNavigated(frames[0].frames[0], "Should be able to navigate off-domain grandchild by setting location.");
-    isNavigated(frames[1].frames[0], "Should be able to navigate off-domain grandchild by calling window.open.");
-    isNavigated(frames[2].frames[0], "Should be able to navigate off-domain grandchild by submitting form.");
-    isNavigated(frames[3].frames[0], "Should be able to navigate off-domain grandchild by targeted hyperlink.");
+  xpcWaitForFinishedFrames(async function() {
+    await isNavigated(frames[0].frames[0], "Should be able to navigate off-domain grandchild by setting location.");
+    await isNavigated(frames[1].frames[0], "Should be able to navigate off-domain grandchild by calling window.open.");
+    await isNavigated(frames[2].frames[0], "Should be able to navigate off-domain grandchild by submitting form.");
+    await isNavigated(frames[3].frames[0], "Should be able to navigate off-domain grandchild by targeted hyperlink.");
 
     xpcCleanupWindows();
     SimpleTest.finish();
   }, 4);
 };
 </script>
 </head>
 <body>
--- a/docshell/test/navigation/test_opener.html
+++ b/docshell/test/navigation/test_opener.html
@@ -14,21 +14,21 @@ if (navigator.platform.startsWith("Linux
 }
 
 window.onload = function() {
   navigateByLocation(window0);
   navigateByOpen("window1");
   navigateByForm("window2");
   navigateByHyperlink("window3");
 
-  xpcWaitForFinishedFrames(function() {
-    isNavigated(window0, "Should be able to navigate popup by setting location.");
-    isNavigated(window1, "Should be able to navigate popup by calling window.open.");
-    isNavigated(window2, "Should be able to navigate popup by submitting form.");
-    isNavigated(window3, "Should be able to navigate popup by targeted hyperlink.");
+  xpcWaitForFinishedFrames(async function() {
+    await isNavigated(window0, "Should be able to navigate popup by setting location.");
+    await isNavigated(window1, "Should be able to navigate popup by calling window.open.");
+    await isNavigated(window2, "Should be able to navigate popup by submitting form.");
+    await isNavigated(window3, "Should be able to navigate popup by targeted hyperlink.");
 
     window0.close();
     window1.close();
     window2.close();
     window3.close();
 
     xpcCleanupWindows();
     SimpleTest.finish();
--- a/docshell/test/navigation/test_popup-navigates-children.html
+++ b/docshell/test/navigation/test_popup-navigates-children.html
@@ -30,21 +30,21 @@ function testChild2() {
     window2 = window.open("navigate.html#child2,form", "window2", "width=10,height=10");
 }
 
 function testChild3() {
   if (!window.window3)
     window3 = window.open("navigate.html#child3,hyperlink", "window3", "width=10,height=10");
 }
 
-xpcWaitForFinishedFrames(function() {
-  isNavigated(frames[0], "Should be able to navigate on-domain opener's children by setting location.");
-  isNavigated(frames[1], "Should be able to navigate on-domain opener's children by calling window.open.");
-  isNavigated(frames[2], "Should be able to navigate on-domain opener's children by submitting form.");
-  isNavigated(frames[3], "Should be able to navigate on-domain opener's children by targeted hyperlink.");
+xpcWaitForFinishedFrames(async function() {
+  await isNavigated(frames[0], "Should be able to navigate on-domain opener's children by setting location.");
+  await isNavigated(frames[1], "Should be able to navigate on-domain opener's children by calling window.open.");
+  await isNavigated(frames[2], "Should be able to navigate on-domain opener's children by submitting form.");
+  await isNavigated(frames[3], "Should be able to navigate on-domain opener's children by targeted hyperlink.");
 
   window0.close();
   window1.close();
   window2.close();
   window3.close();
 
   xpcCleanupWindows();
   SimpleTest.finish();
--- a/docshell/test/navigation/test_sibling-matching-parent.html
+++ b/docshell/test/navigation/test_sibling-matching-parent.html
@@ -11,21 +11,21 @@
 <script>
 window.onload = function() {
   document.getElementById("active").innerHTML =
       '<iframe src="navigate.html#parent.frames[0],location"></iframe>' +
       '<iframe src="navigate.html#child1,open"></iframe>' +
       '<iframe src="navigate.html#child2,form"></iframe>' +
       '<iframe src="navigate.html#child3,hyperlink"></iframe>';
 
-  xpcWaitForFinishedFrames(function() {
-    isNavigated(frames[0], "Should be able to navigate sibling with on-domain parent by setting location.");
-    isNavigated(frames[1], "Should be able to navigate sibling with on-domain parent by calling window.open.");
-    isNavigated(frames[2], "Should be able to navigate sibling with on-domain parent by submitting form.");
-    isNavigated(frames[3], "Should be able to navigate sibling with on-domain parent by targeted hyperlink.");
+  xpcWaitForFinishedFrames(async function() {
+    await isNavigated(frames[0], "Should be able to navigate sibling with on-domain parent by setting location.");
+    await isNavigated(frames[1], "Should be able to navigate sibling with on-domain parent by calling window.open.");
+    await isNavigated(frames[2], "Should be able to navigate sibling with on-domain parent by submitting form.");
+    await isNavigated(frames[3], "Should be able to navigate sibling with on-domain parent by targeted hyperlink.");
 
     xpcCleanupWindows();
     SimpleTest.finish();
   }, 4);
 };
 </script>
 </head>
 <body>
new file mode 100644
--- /dev/null
+++ b/docshell/test/unit/test_browsing_context_structured_clone.js
@@ -0,0 +1,62 @@
+"use strict";
+
+add_task(async function test_BrowsingContext_structured_clone() {
+  let browser = Services.appShell.createWindowlessBrowser(false);
+
+  let frame = browser.document.createElement("iframe");
+  browser.document.body.appendChild(frame);
+
+  let {browsingContext} = frame;
+
+  let sch = new StructuredCloneHolder({browsingContext});
+
+  let deserialize = () => sch.deserialize({}, true);
+
+  // Check that decoding a live browsing context produces the correct
+  // object.
+  equal(deserialize().browsingContext, browsingContext,
+        "Got correct browsing context from StructuredClone deserialize");
+
+  // Check that decoding a second time still succeeds.
+  equal(deserialize().browsingContext, browsingContext,
+        "Got correct browsing context from second StructuredClone deserialize");
+
+  // Destroy the browsing context and make sure that the decode fails
+  // with a DataCloneError.
+  //
+  // Making sure the BrowsingContext is actually destroyed by the time
+  // we do the second decode is a bit tricky. We obviously have clear
+  // our local references to it, and give the GC a chance to reap them.
+  // And we also, of course, have to destroy the frame that it belongs
+  // to, or its frame loader and window global would hold it alive.
+  //
+  // Beyond that, we don't *have* to reload or destroy the parent
+  // document, but we do anyway just to be safe.
+  //
+  // Then comes the tricky bit: The WindowGlobal actors (which keep
+  // references to their BrowsingContexts) require an IPC round trip to
+  // be completely destroyed, even though they're in-process, so we make
+  // a quick trip through the event loop, and then run the CC in order
+  // to allow their (now snow-white) references to be collected.
+
+  frame.remove();
+  frame = null;
+  browsingContext = null;
+
+  browser.document.location.reload();
+  browser.close();
+
+  Cu.forceGC();
+
+  // Give the IPC messages that destroy the actors a chance to be
+  // processed.
+  await new Promise(executeSoon);
+
+  Cu.forceCC();
+
+  // OK. We can be fairly confident that the BrowsingContext object
+  // stored in our structured clone data has been destroyed. Make sure
+  // that attempting to decode it again leads to the appropriate error.
+  Assert.throws(deserialize, e => e.name === "DataCloneError",
+                "Should get a DataCloneError when trying to decode a dead BrowsingContext");
+});
--- a/docshell/test/unit/xpcshell.ini
+++ b/docshell/test/unit/xpcshell.ini
@@ -1,12 +1,13 @@
 [DEFAULT]
 head = head_docshell.js
 
 [test_bug442584.js]
+[test_browsing_context_structured_clone.js]
 [test_nsDefaultURIFixup.js]
 [test_nsDefaultURIFixup_search.js]
 skip-if = os == 'android'
 [test_nsDefaultURIFixup_info.js]
 skip-if = os == 'android'
 [test_pb_notification.js]
 # Bug 751575: unrelated JS changes cause timeouts on random platforms
 skip-if = true
--- a/dom/base/Selection.cpp
+++ b/dom/base/Selection.cpp
@@ -579,17 +579,17 @@ nsresult Selection::AddTableCellRange(ns
 
   // Set frame selection mode only if not already set to a table mode
   // so we don't lose the select row and column flags (not detected by
   // getTableCellLocation)
   if (mFrameSelection->mSelectingTableCellMode == TableSelection::None)
     mFrameSelection->mSelectingTableCellMode = tableMode;
 
   *aDidAddRange = true;
-  return AddItem(aRange, aOutIndex);
+  return AddRangesForSelectableNodes(aRange, aOutIndex);
 }
 
 // TODO: Figure out TableSelection::Column and TableSelection::AllCells
 nsresult Selection::GetTableSelectionType(nsRange* aRange,
                                           TableSelection* aTableSelectionType) {
   if (!aRange || !aTableSelectionType) return NS_ERROR_NULL_POINTER;
 
   *aTableSelectionType = TableSelection::None;
@@ -922,18 +922,19 @@ void Selection::UserSelectRangesToAdd(ns
     // Don't mess with the selection ranges for editing, editor doesn't really
     // deal well with multi-range selections.
     aRangesToAdd.AppendElement(aItem);
   } else {
     aItem->ExcludeNonSelectableNodes(&aRangesToAdd);
   }
 }
 
-nsresult Selection::AddItem(nsRange* aItem, int32_t* aOutIndex,
-                            bool aNoStartSelect) {
+nsresult Selection::AddRangesForSelectableNodes(nsRange* aItem,
+                                                int32_t* aOutIndex,
+                                                bool aNoStartSelect) {
   if (!aItem) return NS_ERROR_NULL_POINTER;
   if (!aItem->IsPositioned()) return NS_ERROR_UNEXPECTED;
 
   NS_ASSERTION(aOutIndex, "aOutIndex can't be null");
 
   if (mUserInitiated) {
     AutoTArray<RefPtr<nsRange>, 4> rangesToAdd;
     *aOutIndex = int32_t(mRanges.Length()) - 1;
@@ -1005,79 +1006,83 @@ nsresult Selection::AddItem(nsRange* aIt
     }
 
     // Generate the ranges to add
     UserSelectRangesToAdd(aItem, rangesToAdd);
     size_t newAnchorFocusIndex =
         GetDirection() == eDirPrevious ? 0 : rangesToAdd.Length() - 1;
     for (size_t i = 0; i < rangesToAdd.Length(); ++i) {
       int32_t index;
-      nsresult rv = AddItemInternal(rangesToAdd[i], &index);
+      nsresult rv = MaybeAddRangeAndTruncateOverlaps(rangesToAdd[i], &index);
       NS_ENSURE_SUCCESS(rv, rv);
       if (i == newAnchorFocusIndex) {
         *aOutIndex = index;
         rangesToAdd[i]->SetIsGenerated(false);
       } else {
         rangesToAdd[i]->SetIsGenerated(true);
       }
     }
     return NS_OK;
   }
-  return AddItemInternal(aItem, aOutIndex);
+  return MaybeAddRangeAndTruncateOverlaps(aItem, aOutIndex);
 }
 
-nsresult Selection::AddItemInternal(nsRange* aItem, int32_t* aOutIndex) {
-  MOZ_ASSERT(aItem);
-  MOZ_ASSERT(aItem->IsPositioned());
+nsresult Selection::MaybeAddRangeAndTruncateOverlaps(nsRange* aRange,
+                                                     int32_t* aOutIndex) {
+  MOZ_ASSERT(aRange);
+  MOZ_ASSERT(aRange->IsPositioned());
   MOZ_ASSERT(aOutIndex);
 
   *aOutIndex = -1;
 
   // a common case is that we have no ranges yet
   if (mRanges.Length() == 0) {
-    if (!mRanges.AppendElement(RangeData(aItem))) return NS_ERROR_OUT_OF_MEMORY;
-    aItem->SetSelection(this);
+    if (!mRanges.AppendElement(RangeData(aRange))) {
+      return NS_ERROR_OUT_OF_MEMORY;
+    }
+    aRange->SetSelection(this);
 
     *aOutIndex = 0;
     return NS_OK;
   }
 
   int32_t startIndex, endIndex;
   nsresult rv =
-      GetIndicesForInterval(aItem->GetStartContainer(), aItem->StartOffset(),
-                            aItem->GetEndContainer(), aItem->EndOffset(), false,
-                            &startIndex, &endIndex);
+      GetIndicesForInterval(aRange->GetStartContainer(), aRange->StartOffset(),
+                            aRange->GetEndContainer(), aRange->EndOffset(),
+                            false, &startIndex, &endIndex);
   NS_ENSURE_SUCCESS(rv, rv);
 
   if (endIndex == -1) {
     // All ranges start after the given range. We can insert our range at
     // position 0, knowing there are no overlaps (handled below)
     startIndex = 0;
     endIndex = 0;
   } else if (startIndex == -1) {
     // All ranges end before the given range. We can insert our range at
     // the end of the array, knowing there are no overlaps (handled below)
     startIndex = mRanges.Length();
     endIndex = startIndex;
   }
 
   // If the range is already contained in mRanges, silently succeed
   bool sameRange = EqualsRangeAtPoint(
-      aItem->GetStartContainer(), aItem->StartOffset(),
-      aItem->GetEndContainer(), aItem->EndOffset(), startIndex);
+      aRange->GetStartContainer(), aRange->StartOffset(),
+      aRange->GetEndContainer(), aRange->EndOffset(), startIndex);
   if (sameRange) {
     *aOutIndex = startIndex;
     return NS_OK;
   }
 
   if (startIndex == endIndex) {
     // The new range doesn't overlap any existing ranges
-    if (!mRanges.InsertElementAt(startIndex, RangeData(aItem)))
+    if (!mRanges.InsertElementAt(startIndex, RangeData(aRange))) {
       return NS_ERROR_OUT_OF_MEMORY;
-    aItem->SetSelection(this);
+    }
+    aRange->SetSelection(this);
     *aOutIndex = startIndex;
     return NS_OK;
   }
 
   // We now know that at least 1 existing range overlaps with the range that
   // we are trying to add. In fact, the only ranges of interest are those at
   // the two end points, startIndex and endIndex - 1 (which may point to the
   // same range) as these may partially overlap the new range. Any ranges
@@ -1095,69 +1100,68 @@ nsresult Selection::AddItemInternal(nsRa
   // Remove all the overlapping ranges
   for (int32_t i = startIndex; i < endIndex; ++i) {
     mRanges[i].mRange->SetSelection(nullptr);
   }
   mRanges.RemoveElementsAt(startIndex, endIndex - startIndex);
 
   nsTArray<RangeData> temp;
   for (int32_t i = overlaps.Length() - 1; i >= 0; i--) {
-    nsresult rv = SubtractRange(&overlaps[i], aItem, &temp);
+    nsresult rv = SubtractRange(&overlaps[i], aRange, &temp);
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   // Insert the new element into our "leftovers" array
   int32_t insertionPoint;
-  rv = FindInsertionPoint(&temp, aItem->GetStartContainer(),
-                          aItem->StartOffset(), CompareToRangeStart,
+  rv = FindInsertionPoint(&temp, aRange->GetStartContainer(),
+                          aRange->StartOffset(), CompareToRangeStart,
                           &insertionPoint);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  if (!temp.InsertElementAt(insertionPoint, RangeData(aItem)))
+  if (!temp.InsertElementAt(insertionPoint, RangeData(aRange))) {
     return NS_ERROR_OUT_OF_MEMORY;
+  }
 
   // Merge the leftovers back in to mRanges
   if (!mRanges.InsertElementsAt(startIndex, temp))
     return NS_ERROR_OUT_OF_MEMORY;
 
   for (uint32_t i = 0; i < temp.Length(); ++i) {
     temp[i].mRange->SetSelection(this);
   }
 
   *aOutIndex = startIndex + insertionPoint;
   return NS_OK;
 }
 
-nsresult Selection::RemoveItem(nsRange* aItem) {
-  if (!aItem) return NS_ERROR_NULL_POINTER;
-
+nsresult Selection::RemoveRangeInternal(nsRange& aRange) {
   // Find the range's index & remove it. We could use FindInsertionPoint to
   // get O(log n) time, but that requires many expensive DOM comparisons.
   // For even several thousand items, this is probably faster because the
   // comparisons are so fast.
   int32_t idx = -1;
   uint32_t i;
   for (i = 0; i < mRanges.Length(); i++) {
-    if (mRanges[i].mRange == aItem) {
+    if (mRanges[i].mRange == &aRange) {
       idx = (int32_t)i;
       break;
     }
   }
   if (idx < 0) return NS_ERROR_DOM_NOT_FOUND_ERR;
 
   mRanges.RemoveElementAt(idx);
-  aItem->SetSelection(nullptr);
+  aRange.SetSelection(nullptr);
   return NS_OK;
 }
 
 nsresult Selection::RemoveCollapsedRanges() {
   uint32_t i = 0;
   while (i < mRanges.Length()) {
     if (mRanges[i].mRange->Collapsed()) {
-      nsresult rv = RemoveItem(mRanges[i].mRange);
+      nsresult rv = RemoveRangeInternal(*mRanges[i].mRange);
       NS_ENSURE_SUCCESS(rv, rv);
     } else {
       ++i;
     }
   }
   return NS_OK;
 }
 
@@ -2032,17 +2036,17 @@ void Selection::AddRangeAndSelectFramesA
   int32_t rangeIndex;
   nsresult result = AddTableCellRange(range, &didAddRange, &rangeIndex);
   if (NS_FAILED(result)) {
     aRv.Throw(result);
     return;
   }
 
   if (!didAddRange) {
-    result = AddItem(range, &rangeIndex);
+    result = AddRangesForSelectableNodes(range, &rangeIndex);
     if (NS_FAILED(result)) {
       aRv.Throw(result);
       return;
     }
   }
 
   if (rangeIndex < 0) {
     return;
@@ -2078,17 +2082,17 @@ void Selection::AddRangeAndSelectFramesA
 //    another range on the frame (bug 346185).
 //
 //    We therefore find any ranges that intersect the same nodes as the range
 //    being removed, and cause them to set the selected bits back on their
 //    selected frames after we've cleared the bit from ours.
 
 void Selection::RemoveRangeAndUnselectFramesAndNotifyListeners(
     nsRange& aRange, ErrorResult& aRv) {
-  nsresult rv = RemoveItem(&aRange);
+  nsresult rv = RemoveRangeInternal(aRange);
   if (NS_FAILED(rv)) {
     aRv.Throw(rv);
     return;
   }
 
   nsINode* beginNode = aRange.GetStartContainer();
   nsINode* endNode = aRange.GetEndContainer();
 
@@ -2262,17 +2266,17 @@ void Selection::Collapse(const RawRangeB
   nsCOMPtr<Document> doc = do_QueryInterface(aPoint.Container());
   printf("Sel. Collapse to %p %s %d\n", container.get(),
          content ? nsAtomCString(content->NodeInfo()->NameAtom()).get()
                  : (doc ? "DOCUMENT" : "???"),
          aPoint.Offset());
 #endif
 
   int32_t rangeIndex = -1;
-  result = AddItem(range, &rangeIndex);
+  result = AddRangesForSelectableNodes(range, &rangeIndex);
   if (NS_FAILED(result)) {
     aRv.Throw(result);
     return;
   }
   SetAnchorFocusRange(0);
   SelectFrames(presContext, range, true);
 
   // Be aware, this instance may be destroyed after this call.
@@ -2384,21 +2388,21 @@ nsRange* Selection::GetRangeAt(int32_t a
 /*
 utility function
 */
 nsresult Selection::SetAnchorFocusToRange(nsRange* aRange) {
   NS_ENSURE_STATE(mAnchorFocusRange);
 
   bool collapsed = IsCollapsed();
 
-  nsresult res = RemoveItem(mAnchorFocusRange);
+  nsresult res = RemoveRangeInternal(*mAnchorFocusRange);
   if (NS_FAILED(res)) return res;
 
   int32_t aOutIndex = -1;
-  res = AddItem(aRange, &aOutIndex, !collapsed);
+  res = AddRangesForSelectableNodes(aRange, &aOutIndex, !collapsed);
   if (NS_FAILED(res)) return res;
   SetAnchorFocusRange(aOutIndex);
 
   return NS_OK;
 }
 
 void Selection::ReplaceAnchorFocusRange(nsRange* aRange) {
   NS_ENSURE_TRUE_VOID(mAnchorFocusRange);
--- a/dom/base/Selection.h
+++ b/dom/base/Selection.h
@@ -156,27 +156,31 @@ class Selection final : public nsSupport
   // Otherwise, if SCROLL_DO_FLUSH is also in aFlags, then this method will
   // flush layout and you MUST hold a strong ref on 'this' for the duration
   // of this call.  This might destroy arbitrary layout objects.
   MOZ_CAN_RUN_SCRIPT_BOUNDARY
   nsresult ScrollIntoView(SelectionRegion aRegion,
                           ScrollAxis aVertical = ScrollAxis(),
                           ScrollAxis aHorizontal = ScrollAxis(),
                           int32_t aFlags = 0);
-  nsresult SubtractRange(RangeData* aRange, nsRange* aSubtract,
-                         nsTArray<RangeData>* aOutput);
+  static nsresult SubtractRange(RangeData* aRange, nsRange* aSubtract,
+                                nsTArray<RangeData>* aOutput);
+
+ private:
   /**
-   * AddItem adds aRange to this Selection.  If mUserInitiated is true,
+   * Adds aRange to this Selection.  If mUserInitiated is true,
    * then aRange is first scanned for -moz-user-select:none nodes and split up
    * into multiple ranges to exclude those before adding the resulting ranges
    * to this Selection.
    */
-  nsresult AddItem(nsRange* aRange, int32_t* aOutIndex,
-                   bool aNoStartSelect = false);
-  nsresult RemoveItem(nsRange* aRange);
+  nsresult AddRangesForSelectableNodes(nsRange* aRange, int32_t* aOutIndex,
+                                       bool aNoStartSelect = false);
+  nsresult RemoveRangeInternal(nsRange& aRange);
+
+ public:
   nsresult RemoveCollapsedRanges();
   nsresult Clear(nsPresContext* aPresContext);
   nsresult Collapse(nsINode* aContainer, int32_t aOffset) {
     if (!aContainer) {
       return NS_ERROR_INVALID_ARG;
     }
     return Collapse(RawRangeBoundary(aContainer, aOffset));
   }
@@ -732,23 +736,24 @@ class Selection final : public nsSupport
                           nsINode* aEndNode, int32_t aEndOffset,
                           int32_t aRangeIndex);
   nsresult GetIndicesForInterval(nsINode* aBeginNode, int32_t aBeginOffset,
                                  nsINode* aEndNode, int32_t aEndOffset,
                                  bool aAllowAdjacent, int32_t* aStartIndex,
                                  int32_t* aEndIndex);
   RangeData* FindRangeData(nsRange* aRange);
 
-  void UserSelectRangesToAdd(nsRange* aItem,
-                             nsTArray<RefPtr<nsRange>>& rangesToAdd);
+  static void UserSelectRangesToAdd(nsRange* aItem,
+                                    nsTArray<RefPtr<nsRange>>& rangesToAdd);
 
   /**
-   * Helper method for AddItem.
+   * Preserves the sorting of mRanges.
    */
-  nsresult AddItemInternal(nsRange* aRange, int32_t* aOutIndex);
+  nsresult MaybeAddRangeAndTruncateOverlaps(nsRange* aRange,
+                                            int32_t* aOutIndex);
 
   Document* GetDocument() const;
   nsPIDOMWindowOuter* GetWindow() const;
   HTMLEditor* GetHTMLEditor() const;
 
   /**
    * GetCommonEditingHostForAllRanges() returns common editing host of all
    * ranges if there is. If at least one of the ranges is in non-editable
--- a/dom/base/StructuredCloneHolder.cpp
+++ b/dom/base/StructuredCloneHolder.cpp
@@ -5,16 +5,18 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "StructuredCloneHolder.h"
 
 #include "ImageContainer.h"
 #include "mozilla/AutoRestore.h"
 #include "mozilla/dom/BindingUtils.h"
 #include "mozilla/dom/BlobBinding.h"
+#include "mozilla/dom/BrowsingContext.h"
+#include "mozilla/dom/BrowsingContextBinding.h"
 #include "mozilla/dom/StructuredCloneBlob.h"
 #include "mozilla/dom/Directory.h"
 #include "mozilla/dom/DirectoryBinding.h"
 #include "mozilla/dom/File.h"
 #include "mozilla/dom/FileList.h"
 #include "mozilla/dom/FileListBinding.h"
 #include "mozilla/dom/FormData.h"
 #include "mozilla/dom/ImageBitmap.h"
@@ -905,16 +907,20 @@ JSObject* StructuredCloneHolder::CustomR
            StructuredCloneScope::SameProcessDifferentThread)) {
     return ReadWasmModule(aCx, aIndex, this);
   }
 
   if (aTag == SCTAG_DOM_INPUTSTREAM) {
     return ReadInputStream(aCx, aIndex, this);
   }
 
+  if (aTag == SCTAG_DOM_BROWSING_CONTEXT) {
+    return BrowsingContext::ReadStructuredClone(aCx, aReader, this);
+  }
+
   return ReadFullySerializableObjects(aCx, aReader, aTag);
 }
 
 bool StructuredCloneHolder::CustomWriteHandler(JSContext* aCx,
                                                JSStructuredCloneWriter* aWriter,
                                                JS::Handle<JSObject*> aObj) {
   if (!mSupportsCloning) {
     return false;
@@ -968,16 +974,25 @@ bool StructuredCloneHolder::CustomWriteH
   // See if this is a StructuredCloneBlob object.
   {
     StructuredCloneBlob* holder = nullptr;
     if (NS_SUCCEEDED(UNWRAP_OBJECT(StructuredCloneHolder, &obj, holder))) {
       return holder->WriteStructuredClone(aCx, aWriter, this);
     }
   }
 
+  // See if this is a BrowsingContext object.
+  if (mStructuredCloneScope == StructuredCloneScope::SameProcessSameThread ||
+      mStructuredCloneScope == StructuredCloneScope::DifferentProcess) {
+    BrowsingContext* holder = nullptr;
+    if (NS_SUCCEEDED(UNWRAP_OBJECT(BrowsingContext, &obj, holder))) {
+      return holder->WriteStructuredClone(aCx, aWriter, this);
+    }
+  }
+
   // See if this is a WasmModule.
   if ((mStructuredCloneScope == StructuredCloneScope::SameProcessSameThread ||
        mStructuredCloneScope ==
            StructuredCloneScope::SameProcessDifferentThread) &&
       JS::IsWasmModuleObject(obj)) {
     RefPtr<JS::WasmModule> module = JS::GetWasmModule(obj);
     MOZ_ASSERT(module);
 
--- a/dom/base/StructuredCloneTags.h
+++ b/dom/base/StructuredCloneTags.h
@@ -132,16 +132,18 @@ enum StructuredCloneTags {
   SCTAG_DOM_CANVAS,
 
   SCTAG_DOM_DIRECTORY,
 
   SCTAG_DOM_INPUTSTREAM,
 
   SCTAG_DOM_STRUCTURED_CLONE_HOLDER,
 
+  SCTAG_DOM_BROWSING_CONTEXT,
+
   // IMPORTANT: If you plan to add an new IDB tag, it _must_ be add before the
   // "less stable" tags!
 };
 
 }  // namespace dom
 }  // namespace mozilla
 
 #endif  // StructuredCloneTags_h__
--- a/dom/base/WindowNamedPropertiesHandler.cpp
+++ b/dom/base/WindowNamedPropertiesHandler.cpp
@@ -4,37 +4,35 @@
  * 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 "WindowNamedPropertiesHandler.h"
 #include "mozilla/dom/EventTargetBinding.h"
 #include "mozilla/dom/WindowBinding.h"
 #include "mozilla/dom/WindowProxyHolder.h"
 #include "nsContentUtils.h"
-#include "nsDOMWindowList.h"
 #include "nsGlobalWindow.h"
 #include "nsHTMLDocument.h"
 #include "nsJSUtils.h"
 #include "xpcprivate.h"
 
 namespace mozilla {
 namespace dom {
 
-static bool ShouldExposeChildWindow(nsString& aNameBeingResolved,
+static bool ShouldExposeChildWindow(const nsString& aNameBeingResolved,
                                     BrowsingContext* aChild) {
-  nsPIDOMWindowOuter* child = aChild->GetDOMWindow();
-  Element* e = child->GetFrameElementInternal();
+  Element* e = aChild->GetEmbedderElement();
   if (e && e->IsInShadowTree()) {
     return false;
   }
 
   // If we're same-origin with the child, go ahead and expose it.
+  nsPIDOMWindowOuter* child = aChild->GetDOMWindow();
   nsCOMPtr<nsIScriptObjectPrincipal> sop = do_QueryInterface(child);
-  NS_ENSURE_TRUE(sop, false);
-  if (nsContentUtils::SubjectPrincipal()->Equals(sop->GetPrincipal())) {
+  if (sop && nsContentUtils::SubjectPrincipal()->Equals(sop->GetPrincipal())) {
     return true;
   }
 
   // If we're not same-origin, expose it _only_ if the name of the browsing
   // context matches the 'name' attribute of the frame element in the parent.
   // The motivations behind this heuristic are worth explaining here.
   //
   // Historically, all UAs supported global named access to any child browsing
@@ -164,33 +162,22 @@ bool WindowNamedPropertiesHandler::ownPr
   }
 
   // Grab the DOM window.
   nsGlobalWindowInner* win = xpc::WindowGlobalOrNull(aProxy);
   nsTArray<nsString> names;
   // The names live on the outer window, which might be null
   nsGlobalWindowOuter* outer = win->GetOuterWindowInternal();
   if (outer) {
-    nsDOMWindowList* childWindows = outer->GetFrames();
-    if (childWindows) {
-      uint32_t length = childWindows->GetLength();
-      for (uint32_t i = 0; i < length; ++i) {
-        nsCOMPtr<nsIDocShellTreeItem> item =
-            childWindows->GetDocShellTreeItemAt(i);
-        // This is a bit silly, since we could presumably just do
-        // item->GetWindow().  But it's not obvious whether this does the same
-        // thing as GetChildWindow() with the item's name (due to the complexity
-        // of FindChildWithName).  Since GetChildWindow is what we use in
-        // getOwnPropDescriptor, let's try to be consistent.
-        nsString name;
-        item->GetName(name);
-        if (!names.Contains(name)) {
+    if (BrowsingContext* bc = outer->GetBrowsingContext()) {
+      for (const auto& child : bc->GetChildren()) {
+        const nsString& name = child->Name();
+        if (!name.IsEmpty() && !names.Contains(name)) {
           // Make sure we really would expose it from getOwnPropDescriptor.
-          RefPtr<BrowsingContext> child = win->GetChildWindow(name);
-          if (child && ShouldExposeChildWindow(name, child)) {
+          if (ShouldExposeChildWindow(name, child)) {
             names.AppendElement(name);
           }
         }
       }
     }
   }
   if (!AppendNamedPropertyIds(aCx, aProxy, names, false, aProps)) {
     return false;
--- a/dom/base/moz.build
+++ b/dom/base/moz.build
@@ -331,17 +331,16 @@ UNIFIED_SOURCES += [
     'nsDataDocumentContentPolicy.cpp',
     'nsDocumentEncoder.cpp',
     'nsDOMAttributeMap.cpp',
     'nsDOMCaretPosition.cpp',
     'nsDOMMutationObserver.cpp',
     'nsDOMNavigationTiming.cpp',
     'nsDOMSerializer.cpp',
     'nsDOMTokenList.cpp',
-    'nsDOMWindowList.cpp',
     'nsFocusManager.cpp',
     'nsFrameLoader.cpp',
     'nsFrameLoaderOwner.cpp',
     'nsGlobalWindowCommands.cpp',
     'nsHistory.cpp',
     'nsHTMLContentSerializer.cpp',
     'nsIGlobalObject.cpp',
     'nsINode.cpp',
deleted file mode 100644
--- a/dom/base/nsDOMWindowList.cpp
+++ /dev/null
@@ -1,82 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: set ts=8 sts=2 et sw=2 tw=80: */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#include "nsDOMWindowList.h"
-
-#include "FlushType.h"
-#include "nsCOMPtr.h"
-#include "mozilla/dom/Document.h"
-#include "nsIDOMWindow.h"
-#include "nsIDocShell.h"
-#include "nsIInterfaceRequestorUtils.h"
-#include "nsIScriptGlobalObject.h"
-#include "nsIWebNavigation.h"
-
-using namespace mozilla;
-
-nsDOMWindowList::nsDOMWindowList(nsIDocShell* aDocShell) {
-  SetDocShell(aDocShell);
-}
-
-nsDOMWindowList::~nsDOMWindowList() {}
-
-void nsDOMWindowList::SetDocShell(nsIDocShell* aDocShell) {
-  mDocShellNode = aDocShell;  // Weak Reference
-}
-
-void nsDOMWindowList::EnsureFresh() {
-  nsCOMPtr<nsIWebNavigation> shellAsNav = do_QueryInterface(mDocShellNode);
-
-  if (shellAsNav) {
-    nsCOMPtr<dom::Document> doc;
-    shellAsNav->GetDocument(getter_AddRefs(doc));
-
-    if (doc) {
-      doc->FlushPendingNotifications(FlushType::ContentAndNotify);
-    }
-  }
-}
-
-uint32_t nsDOMWindowList::GetLength() {
-  EnsureFresh();
-
-  NS_ENSURE_TRUE(mDocShellNode, 0);
-
-  int32_t length;
-  nsresult rv = mDocShellNode->GetChildCount(&length);
-  NS_ENSURE_SUCCESS(rv, 0);
-
-  return uint32_t(length);
-}
-
-already_AddRefed<nsPIDOMWindowOuter> nsDOMWindowList::IndexedGetter(
-    uint32_t aIndex) {
-  nsCOMPtr<nsIDocShellTreeItem> item = GetDocShellTreeItemAt(aIndex);
-  if (!item) {
-    return nullptr;
-  }
-
-  nsCOMPtr<nsPIDOMWindowOuter> window = item->GetWindow();
-  MOZ_ASSERT(window);
-
-  return window.forget();
-}
-
-already_AddRefed<nsPIDOMWindowOuter> nsDOMWindowList::NamedItem(
-    const nsAString& aName) {
-  EnsureFresh();
-
-  if (!mDocShellNode) {
-    return nullptr;
-  }
-
-  nsCOMPtr<nsIDocShellTreeItem> item;
-  mDocShellNode->FindChildWithName(aName, false, false, nullptr, nullptr,
-                                   getter_AddRefs(item));
-
-  nsCOMPtr<nsPIDOMWindowOuter> childWindow(do_GetInterface(item));
-  return childWindow.forget();
-}
deleted file mode 100644
--- a/dom/base/nsDOMWindowList.h
+++ /dev/null
@@ -1,46 +0,0 @@
-/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* vim: set ts=8 sts=2 et sw=2 tw=80: */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-#ifndef nsDOMWindowList_h___
-#define nsDOMWindowList_h___
-
-#include "nsCOMPtr.h"
-#include <stdint.h>
-#include "nsIDocShell.h"
-
-class nsIDocShell;
-class nsIDOMWindow;
-
-class nsDOMWindowList final {
- public:
-  explicit nsDOMWindowList(nsIDocShell* aDocShell);
-
-  NS_INLINE_DECL_REFCOUNTING(nsDOMWindowList)
-
-  uint32_t GetLength();
-  already_AddRefed<nsPIDOMWindowOuter> IndexedGetter(uint32_t aIndex);
-  already_AddRefed<nsPIDOMWindowOuter> NamedItem(const nsAString& aName);
-
-  // local methods
-  void SetDocShell(nsIDocShell* aDocShell);
-  already_AddRefed<nsIDocShellTreeItem> GetDocShellTreeItemAt(uint32_t aIndex) {
-    EnsureFresh();
-    nsCOMPtr<nsIDocShellTreeItem> item;
-    if (mDocShellNode) {
-      mDocShellNode->GetChildAt(aIndex, getter_AddRefs(item));
-    }
-    return item.forget();
-  }
-
- protected:
-  ~nsDOMWindowList();
-
-  // Note: this function may flush and cause mDocShellNode to become null.
-  void EnsureFresh();
-
-  nsIDocShell* mDocShellNode;  // Weak Reference
-};
-
-#endif  // nsDOMWindowList_h___
--- a/dom/base/nsGlobalWindowInner.cpp
+++ b/dom/base/nsGlobalWindowInner.cpp
@@ -45,17 +45,16 @@
 #if defined(MOZ_WIDGET_ANDROID)
 #  include "mozilla/dom/WindowOrientationObserver.h"
 #endif
 #include "nsDOMOfflineResourceList.h"
 #include "nsError.h"
 #include "nsISizeOfEventTarget.h"
 #include "nsDOMJSUtils.h"
 #include "nsArrayUtils.h"
-#include "nsDOMWindowList.h"
 #include "mozilla/dom/WakeLock.h"
 #include "mozilla/dom/power/PowerManagerService.h"
 #include "nsIContentSecurityPolicy.h"
 #include "nsIDocShellTreeOwner.h"
 #include "nsIDocumentLoader.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsIPermission.h"
 #include "nsIPermissionManager.h"
@@ -2703,21 +2702,17 @@ bool nsGlobalWindowInner::GetClosed(Erro
   // If we're called from JS (which is the only way we should be getting called
   // here) and we reach this point, that means our JS global is the current
   // target of the WindowProxy, which means that we are the "current inner"
   // of our outer. So if FORWARD_TO_OUTER fails to forward, that means the
   // outer is already torn down, which corresponds to the closed state.
   FORWARD_TO_OUTER(GetClosedOuter, (), true);
 }
 
-nsDOMWindowList* nsGlobalWindowInner::GetFrames() {
-  FORWARD_TO_OUTER(GetFrames, (), nullptr);
-}
-
-already_AddRefed<nsPIDOMWindowOuter> nsGlobalWindowInner::IndexedGetter(
+Nullable<WindowProxyHolder> nsGlobalWindowInner::IndexedGetter(
     uint32_t aIndex) {
   FORWARD_TO_OUTER(IndexedGetterOuter, (aIndex), nullptr);
 }
 
 namespace {
 
 struct InterfaceShimEntry {
   const char* geckoName;
--- a/dom/base/nsGlobalWindowInner.h
+++ b/dom/base/nsGlobalWindowInner.h
@@ -72,17 +72,16 @@ class nsIScrollableFrame;
 class nsIControllers;
 class nsIScriptContext;
 class nsIScriptTimeoutHandler;
 class nsIBrowserChild;
 class nsITimeoutHandler;
 class nsIWebBrowserChrome;
 class mozIDOMWindowProxy;
 
-class nsDOMWindowList;
 class nsScreen;
 class nsHistory;
 class nsGlobalWindowObserver;
 class nsGlobalWindowOuter;
 class nsDOMWindowUtils;
 class nsIIdleService;
 struct nsRect;
 
@@ -376,17 +375,18 @@ class nsGlobalWindowInner final : public
   virtual void EventListenerAdded(nsAtom* aType) override;
   using EventTarget::EventListenerRemoved;
   virtual void EventListenerRemoved(nsAtom* aType) override;
 
   // nsIInterfaceRequestor
   NS_DECL_NSIINTERFACEREQUESTOR
 
   // WebIDL interface.
-  already_AddRefed<nsPIDOMWindowOuter> IndexedGetter(uint32_t aIndex);
+  mozilla::dom::Nullable<mozilla::dom::WindowProxyHolder> IndexedGetter(
+      uint32_t aIndex);
 
   static bool IsPrivilegedChromeWindow(JSContext* /* unused */, JSObject* aObj);
 
   static bool OfflineCacheAllowedForContext(JSContext* /* unused */,
                                             JSObject* aObj);
 
   static bool IsRequestIdleCallbackEnabled(JSContext* aCx,
                                            JSObject* /* unused */);
@@ -607,17 +607,16 @@ class nsGlobalWindowInner final : public
   void Close(mozilla::dom::CallerType aCallerType,
              mozilla::ErrorResult& aError);
   nsresult Close() override;
   bool GetClosed(mozilla::ErrorResult& aError);
   void Stop(mozilla::ErrorResult& aError);
   void Focus(mozilla::ErrorResult& aError);
   nsresult Focus() override;
   void Blur(mozilla::ErrorResult& aError);
-  nsDOMWindowList* GetFrames() final;
   mozilla::dom::BrowsingContext* GetFrames(mozilla::ErrorResult& aError);
   uint32_t Length();
   mozilla::dom::Nullable<mozilla::dom::WindowProxyHolder> GetTop(
       mozilla::ErrorResult& aError);
 
  protected:
   explicit nsGlobalWindowInner(nsGlobalWindowOuter* aOuterWindow);
   // Initializes the mWasOffline member variable
--- a/dom/base/nsGlobalWindowOuter.cpp
+++ b/dom/base/nsGlobalWindowOuter.cpp
@@ -43,17 +43,16 @@
 #if defined(MOZ_WIDGET_ANDROID)
 #  include "mozilla/dom/WindowOrientationObserver.h"
 #endif
 #include "nsBaseCommandController.h"
 #include "nsError.h"
 #include "nsISizeOfEventTarget.h"
 #include "nsDOMJSUtils.h"
 #include "nsArrayUtils.h"
-#include "nsDOMWindowList.h"
 #include "mozilla/dom/WakeLock.h"
 #include "mozilla/dom/power/PowerManagerService.h"
 #include "nsIDocShellTreeOwner.h"
 #include "nsIInterfaceRequestorUtils.h"
 #include "nsIPermissionManager.h"
 #include "nsIScriptContext.h"
 #include "nsISlowScriptDebug.h"
 #include "nsWindowMemoryReporter.h"
@@ -505,18 +504,19 @@ class nsOuterWindowProxy : public MaybeC
   // False return value means we threw an exception.  True return value
   // but false "found" means we didn't have a subframe at that index.
   bool GetSubframeWindow(JSContext* cx, JS::Handle<JSObject*> proxy,
                          JS::Handle<jsid> id, JS::MutableHandle<JS::Value> vp,
                          bool& found) const;
 
   // Returns a non-null window only if id is an index and we have a
   // window at that index.
-  already_AddRefed<nsPIDOMWindowOuter> GetSubframeWindow(
-      JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id) const;
+  Nullable<WindowProxyHolder> GetSubframeWindow(JSContext* cx,
+                                                JS::Handle<JSObject*> proxy,
+                                                JS::Handle<jsid> id) const;
 
   bool AppendIndexedPropertyNames(JSObject* proxy,
                                   JS::MutableHandleVector<jsid> props) const;
 
   using MaybeCrossOriginObjectMixins::EnsureHolder;
   bool EnsureHolder(JSContext* cx, JS::Handle<JSObject*> proxy,
                     JS::MutableHandle<JSObject*> holder) const override;
 };
@@ -779,17 +779,17 @@ bool nsOuterWindowProxy::ownPropertyKeys
 
 bool nsOuterWindowProxy::delete_(JSContext* cx, JS::Handle<JSObject*> proxy,
                                  JS::Handle<jsid> id,
                                  JS::ObjectOpResult& result) const {
   if (!IsPlatformObjectSameOrigin(cx, proxy)) {
     return ReportCrossOriginDenial(cx, id, NS_LITERAL_CSTRING("delete"));
   }
 
-  if (nsCOMPtr<nsPIDOMWindowOuter> frame = GetSubframeWindow(cx, proxy, id)) {
+  if (!GetSubframeWindow(cx, proxy, id).IsNull()) {
     // Fail (which means throw if strict, else return false).
     return result.failCantDeleteWindowElement();
   }
 
   if (IsArrayIndex(GetArrayIndexFromId(id))) {
     // Indexed, but not supported.  Spec says return true.
     return result.succeed();
   }
@@ -813,17 +813,17 @@ bool nsOuterWindowProxy::has(JSContext* 
   // work than we have to do for has() on the Window.
 
   if (!IsPlatformObjectSameOrigin(cx, proxy)) {
     // In the cross-origin case we only have own properties.  Just call hasOwn
     // directly.
     return hasOwn(cx, proxy, id, bp);
   }
 
-  if (nsCOMPtr<nsPIDOMWindowOuter> frame = GetSubframeWindow(cx, proxy, id)) {
+  if (!GetSubframeWindow(cx, proxy, id).IsNull()) {
     *bp = true;
     return true;
   }
 
   // Just to be safe in terms of compartment asserts, enter the Realm of
   // "proxy".  We're same-origin with it, so this should be safe.
   JSAutoRealm ar(cx, proxy);
   JS_MarkCrossZoneId(cx, id);
@@ -846,17 +846,17 @@ bool nsOuterWindowProxy::hasOwn(JSContex
     // not do the right security and cross-origin checks and will pass through
     // the call to the Window.
     //
     // The BaseProxyHandler code is OK with this happening without entering the
     // compartment of "proxy".
     return js::BaseProxyHandler::hasOwn(cx, proxy, id, bp);
   }
 
-  if (nsCOMPtr<nsPIDOMWindowOuter> frame = GetSubframeWindow(cx, proxy, id)) {
+  if (!GetSubframeWindow(cx, proxy, id).IsNull()) {
     *bp = true;
     return true;
   }
 
   // Just to be safe in terms of compartment asserts, enter the Realm of
   // "proxy".  We're same-origin with it, so this should be safe.
   JSAutoRealm ar(cx, proxy);
   JS_MarkCrossZoneId(cx, id);
@@ -977,38 +977,27 @@ bool nsOuterWindowProxy::getOwnEnumerabl
   return js::AppendUnique(cx, props, innerProps);
 }
 
 bool nsOuterWindowProxy::GetSubframeWindow(JSContext* cx,
                                            JS::Handle<JSObject*> proxy,
                                            JS::Handle<jsid> id,
                                            JS::MutableHandle<JS::Value> vp,
                                            bool& found) const {
-  nsCOMPtr<nsPIDOMWindowOuter> frame = GetSubframeWindow(cx, proxy, id);
-  if (!frame) {
+  Nullable<WindowProxyHolder> frame = GetSubframeWindow(cx, proxy, id);
+  if (frame.IsNull()) {
     found = false;
     return true;
   }
 
   found = true;
-  // Just return the window's global
-  nsGlobalWindowOuter* global = nsGlobalWindowOuter::Cast(frame);
-  frame->EnsureInnerWindow();
-  JSObject* obj = global->GetGlobalJSObject();
-  // This null check fixes a hard-to-reproduce crash that occurs when we
-  // get here when we're mid-call to nsDocShell::Destroy. See bug 640904
-  // comment 105.
-  if (MOZ_UNLIKELY(!obj)) {
-    return xpc::Throw(cx, NS_ERROR_FAILURE);
-  }
-  vp.setObject(*obj);
-  return JS_WrapValue(cx, vp);
-}
-
-already_AddRefed<nsPIDOMWindowOuter> nsOuterWindowProxy::GetSubframeWindow(
+  return WrapObject(cx, frame.Value(), vp);
+}
+
+Nullable<WindowProxyHolder> nsOuterWindowProxy::GetSubframeWindow(
     JSContext* cx, JS::Handle<JSObject*> proxy, JS::Handle<jsid> id) const {
   uint32_t index = GetArrayIndexFromId(id);
   if (!IsArrayIndex(index)) {
     return nullptr;
   }
 
   nsGlobalWindowOuter* win = GetOuterWindow(proxy);
   return win->IndexedGetterOuter(index);
@@ -1295,17 +1284,16 @@ void nsGlobalWindowOuter::DropOuterWindo
 
 void nsGlobalWindowOuter::CleanUp() {
   // Guarantee idempotence.
   if (mCleanedUp) return;
   mCleanedUp = true;
 
   StartDying();
 
-  mFrames = nullptr;
   mWindowUtils = nullptr;
 
   ClearControllers();
 
   mOpener = nullptr;  // Forces Release
   if (mContext) {
     mContext = nullptr;  // Forces Release
   }
@@ -2375,20 +2363,16 @@ void nsGlobalWindowOuter::SetDocShell(ns
   nsCOMPtr<nsPIDOMWindowOuter> parentWindow = GetScriptableParentOrNull();
   MOZ_RELEASE_ASSERT(!parentWindow || !mTabGroup ||
                      mTabGroup ==
                          nsGlobalWindowOuter::Cast(parentWindow)->mTabGroup);
 
   mTopLevelOuterContentWindow =
       !mIsChrome && GetScriptableTopInternal() == this;
 
-  if (mFrames) {
-    mFrames->SetDocShell(aDocShell);
-  }
-
   // Get our enclosing chrome shell and retrieve its global window impl, so
   // that we can do some forwarding to the chrome document.
   RefPtr<EventTarget> chromeEventHandler;
   mDocShell->GetChromeEventHandler(getter_AddRefs(chromeEventHandler));
   mChromeEventHandler = chromeEventHandler;
   if (!mChromeEventHandler) {
     // We have no chrome event handler. If we have a parent,
     // get our chrome event handler from the parent. If
@@ -2470,20 +2454,16 @@ void nsGlobalWindowOuter::DetachFromDocS
                             ? nullptr
                             : GetWrapperPreserveColor());
     mContext = nullptr;
   }
 
   mDocShell = nullptr;
   mBrowsingContext->ClearDocShell();
 
-  if (mFrames) {
-    mFrames->SetDocShell(nullptr);
-  }
-
   MaybeForgiveSpamCount();
   CleanUp();
 }
 
 void nsGlobalWindowOuter::SetOpenerWindow(nsPIDOMWindowOuter* aOpener,
                                           bool aOriginalOpener) {
   nsWeakPtr opener = do_GetWeakReference(aOpener);
   if (opener == mOpener) {
@@ -3191,30 +3171,26 @@ nsresult nsGlobalWindowOuter::GetPrompte
 
 bool nsGlobalWindowOuter::GetClosedOuter() {
   // If someone called close(), or if we don't have a docshell, we're closed.
   return mIsClosed || !mDocShell;
 }
 
 bool nsGlobalWindowOuter::Closed() { return GetClosedOuter(); }
 
-nsDOMWindowList* nsGlobalWindowOuter::GetFrames() {
-  if (!mFrames && mDocShell) {
-    mFrames = new nsDOMWindowList(mDocShell);
-  }
-
-  return mFrames;
-}
-
-already_AddRefed<nsPIDOMWindowOuter> nsGlobalWindowOuter::IndexedGetterOuter(
+Nullable<WindowProxyHolder> nsGlobalWindowOuter::IndexedGetterOuter(
     uint32_t aIndex) {
-  nsDOMWindowList* windows = GetFrames();
-  NS_ENSURE_TRUE(windows, nullptr);
-
-  return windows->IndexedGetter(aIndex);
+  BrowsingContext* bc = GetBrowsingContext();
+  NS_ENSURE_TRUE(bc, nullptr);
+
+  const BrowsingContext::Children& children = bc->GetChildren();
+  if (aIndex < children.Length()) {
+    return WindowProxyHolder(children[aIndex]);
+  }
+  return nullptr;
 }
 
 nsIControllers* nsGlobalWindowOuter::GetControllersOuter(ErrorResult& aError) {
   if (!mControllers) {
     mControllers = new nsXULControllers();
     if (!mControllers) {
       aError.Throw(NS_ERROR_FAILURE);
       return nullptr;
@@ -3957,19 +3933,18 @@ CSSPoint nsGlobalWindowOuter::GetScrollX
   return CSSPoint::FromAppUnits(scrollPos);
 }
 
 double nsGlobalWindowOuter::GetScrollXOuter() { return GetScrollXY(false).x; }
 
 double nsGlobalWindowOuter::GetScrollYOuter() { return GetScrollXY(false).y; }
 
 uint32_t nsGlobalWindowOuter::Length() {
-  nsDOMWindowList* windows = GetFrames();
-
-  return windows ? windows->GetLength() : 0;
+  BrowsingContext* bc = GetBrowsingContext();
+  return bc ? bc->GetChildren().Length() : 0;
 }
 
 Nullable<WindowProxyHolder> nsGlobalWindowOuter::GetTopOuter() {
   BrowsingContext* bc = GetBrowsingContext();
   return bc ? bc->GetTop(IgnoreErrors()) : nullptr;
 }
 
 already_AddRefed<BrowsingContext> nsGlobalWindowOuter::GetChildWindow(
--- a/dom/base/nsGlobalWindowOuter.h
+++ b/dom/base/nsGlobalWindowOuter.h
@@ -69,17 +69,16 @@ class nsIControllers;
 class nsIScriptContext;
 class nsIScriptTimeoutHandler;
 class nsIBrowserChild;
 class nsITimeoutHandler;
 class nsIWebBrowserChrome;
 class mozIDOMWindowProxy;
 
 class nsDocShellLoadState;
-class nsDOMWindowList;
 class nsScreen;
 class nsHistory;
 class nsGlobalWindowObserver;
 class nsGlobalWindowInner;
 class nsDOMWindowUtils;
 struct nsRect;
 
 class nsWindowSizes;
@@ -349,17 +348,18 @@ class nsGlobalWindowOuter final : public
   bool Fullscreen() const;
 
   // nsIInterfaceRequestor
   NS_DECL_NSIINTERFACEREQUESTOR
 
   // nsIObserver
   NS_DECL_NSIOBSERVER
 
-  already_AddRefed<nsPIDOMWindowOuter> IndexedGetterOuter(uint32_t aIndex);
+  mozilla::dom::Nullable<mozilla::dom::WindowProxyHolder> IndexedGetterOuter(
+      uint32_t aIndex);
 
   already_AddRefed<nsPIDOMWindowOuter> GetTop() override;
   // Similar to GetTop() except that it stops at content frames that an
   // extension has permission to access.  This is used by the third-party util
   // service in order to determine the top window for a channel which is used
   // in third-partiness checks.
   already_AddRefed<nsPIDOMWindowOuter>
   GetTopExcludingExtensionAccessibleContentFrames(nsIURI* aURIBeingLoaded);
@@ -530,17 +530,16 @@ class nsGlobalWindowOuter final : public
   nsresult Close() override;
   bool GetClosedOuter();
   bool Closed() override;
   void StopOuter(mozilla::ErrorResult& aError);
   void FocusOuter();
   nsresult Focus() override;
   void BlurOuter();
   mozilla::dom::BrowsingContext* GetFramesOuter();
-  nsDOMWindowList* GetFrames() final;
   uint32_t Length();
   mozilla::dom::Nullable<mozilla::dom::WindowProxyHolder> GetTopOuter();
 
   nsresult GetPrompter(nsIPrompt** aPrompt) override;
 
   RefPtr<mozilla::ThrottledEventQueue> mPostMessageEventQueue;
  protected:
   nsPIDOMWindowOuter* GetOpenerWindowOuter();
@@ -913,18 +912,18 @@ class nsGlobalWindowOuter final : public
   virtual void SetFocusedElement(mozilla::dom::Element* aElement,
                                  uint32_t aFocusMethod = 0,
                                  bool aNeedsFocus = false) override;
 
   virtual uint32_t GetFocusMethod() override;
 
   virtual bool ShouldShowFocusRing() override;
 
-  virtual void SetKeyboardIndicators(UIStateChangeType aShowFocusRings)
-      override;
+  virtual void SetKeyboardIndicators(
+      UIStateChangeType aShowFocusRings) override;
 
  public:
   virtual already_AddRefed<nsPIWindowRoot> GetTopWindowRoot() override;
 
  protected:
   void NotifyWindowIDDestroyed(const char* aTopic);
 
   void ClearStatus();
@@ -1094,17 +1093,16 @@ class nsGlobalWindowOuter final : public
 
   nsCOMPtr<nsIScriptContext> mContext;
   nsWeakPtr mOpener;
   nsCOMPtr<nsIControllers> mControllers;
 
   // For |window.arguments|, via |openDialog|.
   nsCOMPtr<nsIArray> mArguments;
 
-  RefPtr<nsDOMWindowList> mFrames;
   RefPtr<nsDOMWindowUtils> mWindowUtils;
   nsString mStatus;
 
   RefPtr<mozilla::dom::Storage> mLocalStorage;
 
   nsCOMPtr<nsIPrincipal> mDocumentPrincipal;
   nsCOMPtr<nsIPrincipal> mDocumentStoragePrincipal;
 
--- a/dom/base/nsPIDOMWindow.h
+++ b/dom/base/nsPIDOMWindow.h
@@ -20,17 +20,16 @@
 #include "nsRefPtrHashtable.h"
 
 // Only fired for inner windows.
 #define DOM_WINDOW_DESTROYED_TOPIC "dom-window-destroyed"
 #define DOM_WINDOW_FROZEN_TOPIC "dom-window-frozen"
 #define DOM_WINDOW_THAWED_TOPIC "dom-window-thawed"
 
 class nsDOMOfflineResourceList;
-class nsDOMWindowList;
 class nsGlobalWindowInner;
 class nsGlobalWindowOuter;
 class nsIArray;
 class nsIChannel;
 class nsIContent;
 class nsIContentSecurityPolicy;
 class nsICSSDeclaration;
 class nsIDocShell;
@@ -541,18 +540,16 @@ class nsPIDOMWindowInner : public mozIDO
 
   uint32_t GetMarkedCCGeneration() { return mMarkedCCGeneration; }
 
   mozilla::dom::Navigator* Navigator();
   virtual mozilla::dom::Location* Location() = 0;
 
   virtual nsresult GetControllers(nsIControllers** aControllers) = 0;
 
-  virtual nsDOMWindowList* GetFrames() = 0;
-
   virtual nsresult GetInnerWidth(int32_t* aWidth) = 0;
   virtual nsresult GetInnerHeight(int32_t* aHeight) = 0;
 
   virtual already_AddRefed<nsICSSDeclaration> GetComputedStyle(
       mozilla::dom::Element& aElt, const nsAString& aPseudoElt,
       mozilla::ErrorResult& aError) = 0;
 
   virtual mozilla::dom::Element* GetFrameElement() = 0;
@@ -1040,18 +1037,16 @@ class nsPIDOMWindowOuter : public mozIDO
   virtual mozilla::dom::Navigator* GetNavigator() = 0;
   virtual mozilla::dom::Location* GetLocation() = 0;
 
   virtual nsresult GetPrompter(nsIPrompt** aPrompt) = 0;
   virtual nsresult GetControllers(nsIControllers** aControllers) = 0;
   virtual already_AddRefed<mozilla::dom::Selection> GetSelection() = 0;
   virtual already_AddRefed<nsPIDOMWindowOuter> GetOpener() = 0;
 
-  virtual nsDOMWindowList* GetFrames() = 0;
-
   // aLoadState will be passed on through to the windowwatcher.
   // aForceNoOpener will act just like a "noopener" feature in aOptions except
   //                will not affect any other window features.
   virtual nsresult Open(const nsAString& aUrl, const nsAString& aName,
                         const nsAString& aOptions,
                         nsDocShellLoadState* aLoadState, bool aForceNoOpener,
                         nsPIDOMWindowOuter** _retval) = 0;
   virtual nsresult OpenDialog(const nsAString& aUrl, const nsAString& aName,
--- a/dom/base/nsRange.h
+++ b/dom/base/nsRange.h
@@ -107,17 +107,17 @@ class nsRange final : public mozilla::do
    * @see SetIsGenerated
    */
   bool IsGenerated() const { return mIsGenerated; }
 
   /**
    * Mark this range as being generated or not.
    * Currently it is used for marking ranges that are created when splitting up
    * a range to exclude a -moz-user-select:none region.
-   * @see Selection::AddItem
+   * @see Selection::AddRangesForSelectableNodes
    * @see ExcludeNonSelectableNodes
    */
   void SetIsGenerated(bool aIsGenerated) { mIsGenerated = aIsGenerated; }
 
   void Reset();
 
   /**
    * ResetTemporarily() is called when Selection starts to cache the instance
--- a/dom/base/nsWindowMemoryReporter.cpp
+++ b/dom/base/nsWindowMemoryReporter.cpp
@@ -2,23 +2,24 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsWindowMemoryReporter.h"
 #include "nsWindowSizes.h"
 #include "nsGlobalWindow.h"
+#include "mozilla/dom/BrowsingContext.h"
 #include "mozilla/dom/Document.h"
-#include "nsDOMWindowList.h"
 #include "mozilla/ClearOnShutdown.h"
 #include "mozilla/Preferences.h"
 #include "mozilla/Services.h"
 #include "mozilla/StaticPtr.h"
 #include "mozilla/Telemetry.h"
+#include "mozilla/ResultExtensions.h"
 #include "nsNetCID.h"
 #include "nsPrintfCString.h"
 #include "XPCJSMemoryReporter.h"
 #include "js/MemoryMetrics.h"
 #include "nsQueryObject.h"
 #include "nsServiceManagerUtils.h"
 #ifdef MOZ_XUL
 #  include "nsXULPrototypeCache.h"
@@ -56,29 +57,26 @@ static nsresult AddNonJSSizeOfWindowAndI
   // Measure the inner window, if there is one.
   nsGlobalWindowInner* inner = aWindow->GetCurrentInnerWindowInternal();
   if (inner) {
     inner->AddSizeOfIncludingThis(windowSizes);
   }
 
   windowSizes.addToTabSizes(aSizes);
 
-  nsDOMWindowList* frames = aWindow->GetFrames();
-
-  uint32_t length = frames->GetLength();
+  BrowsingContext* bc = aWindow->GetBrowsingContext();
+  if (!bc) {
+    return NS_OK;
+  }
 
   // Measure this window's descendents.
-  for (uint32_t i = 0; i < length; i++) {
-    nsCOMPtr<nsPIDOMWindowOuter> child = frames->IndexedGetter(i);
-    NS_ENSURE_STATE(child);
-
-    nsGlobalWindowOuter* childWin = nsGlobalWindowOuter::Cast(child);
-
-    nsresult rv = AddNonJSSizeOfWindowAndItsDescendents(childWin, aSizes);
-    NS_ENSURE_SUCCESS(rv, rv);
+  for (const auto& frame : bc->GetChildren()) {
+    if (auto* childWin = nsGlobalWindowOuter::Cast(frame->GetDOMWindow())) {
+      MOZ_TRY(AddNonJSSizeOfWindowAndItsDescendents(childWin, aSizes));
+    }
   }
   return NS_OK;
 }
 
 static nsresult NonJSSizeOfTab(nsPIDOMWindowOuter* aWindow, size_t* aDomSize,
                                size_t* aStyleSize, size_t* aOtherSize) {
   nsGlobalWindowOuter* window = nsGlobalWindowOuter::Cast(aWindow);
 
--- a/dom/base/test/chrome/cpows_parent.xul
+++ b/dom/base/test/chrome/cpows_parent.xul
@@ -5,16 +5,18 @@
 <window title="MessageManager CPOW tests"
   xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
   onload="start()">
 
   <!-- test results are displayed in the html:body -->
   <label value="CPOWs"/>
 
   <script type="application/javascript"><![CDATA[
+    const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
     var test_state = "remote";
     var test_node = null;
     var reentered = false;
     var savedMM = null;
 
     function info(message) {
       return opener.wrappedJSObject.info(message);
     }
@@ -395,33 +397,33 @@
       ok(failed, "CPOW should fail due to cancelation");
       msg.target.messageManager.sendAsyncMessage("cpows:cancel_test2_done");
     }
 
     function recvUnsafe(msg) {
       let failed = false;
 
       const PREF_UNSAFE_FORBIDDEN = "dom.ipc.cpows.forbid-unsafe-from-browser";
-      opener.wrappedJSObject.SpecialPowers.setBoolPref(PREF_UNSAFE_FORBIDDEN, true);
+      Services.prefs.setBoolPref(PREF_UNSAFE_FORBIDDEN, true);
       try {
         msg.objects.f();
       } catch (e) {
         if (!/unsafe CPOW usage forbidden/.test(String(e))) {
           throw e;
         }
         failed = true;
       }
       opener.wrappedJSObject.SpecialPowers.clearUserPref(PREF_UNSAFE_FORBIDDEN);
       ok(failed, "CPOW should fail when unsafe");
       msg.target.messageManager.sendAsyncMessage("cpows:unsafe_done");
     }
 
     function recvSafe(msg) {
       const PREF_UNSAFE_FORBIDDEN = "dom.ipc.cpows.forbid-unsafe-from-browser";
-      opener.wrappedJSObject.SpecialPowers.setBoolPref(PREF_UNSAFE_FORBIDDEN, true);
+      Services.prefs.setBoolPref(PREF_UNSAFE_FORBIDDEN, true);
       try {
         msg.objects.f();
       } catch (e) {
         if (!/unsafe CPOW usage forbidden/.test(String(e))) {
           throw e;
         }
         ok(false, "cpow failed");
       }
--- a/dom/base/test/chrome/test_bug914381.html
+++ b/dom/base/test/chrome/test_bug914381.html
@@ -29,17 +29,17 @@ function createFileWithData(fileData) {
   outStream.close();
 
   return testFile;
 }
 
 /** Test for Bug 914381. File's created in JS using an nsIFile should allow mozGetFullPathInternal calls to succeed **/
 var file = createFileWithData("Test bug 914381");
 
-SpecialPowers.pushPrefEnv({ set: [ "dom.file.createInChild" ]})
+SpecialPowers.pushPrefEnv({ set: [["dom.file.createInChild", true]]})
 .then(() => {
   return File.createFromNsIFile(file);
 })
 .then(f => {
   is(f.mozFullPathInternal, undefined, "mozFullPathInternal is undefined from js");
   is(f.mozFullPath, file.path, "mozFullPath returns path if created with nsIFile");
 })
 .then(() => {
--- a/dom/base/test/chrome/test_cpows.xul
+++ b/dom/base/test/chrome/test_cpows.xul
@@ -8,22 +8,24 @@
 
   <!-- test results are displayed in the html:body -->
   <body xmlns="http://www.w3.org/1999/xhtml">
   </body>
 
   <!-- test code goes here -->
   <script type="application/javascript"><![CDATA[
 
+  const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
   SimpleTest.waitForExplicitFinish();
 
   const PREF_UNSAFE_FORBIDDEN = "dom.ipc.cpows.forbid-unsafe-from-browser";
-  SpecialPowers.setBoolPref(PREF_UNSAFE_FORBIDDEN, false);
+  Services.prefs.setBoolPref(PREF_UNSAFE_FORBIDDEN, false);
   SimpleTest.registerCleanupFunction(() => {
-    SpecialPowers.clearUserPref(PREF_UNSAFE_FORBIDDEN);
+    Services.prefs.clearUserPref(PREF_UNSAFE_FORBIDDEN);
   });
 
   function done() {
     SimpleTest.finish();
   }
 
   addLoadEvent(function() {
     window.open("cpows_parent.xul", "", "chrome");
--- a/dom/base/test/chrome/window_nsITextInputProcessor.xul
+++ b/dom/base/test/chrome/window_nsITextInputProcessor.xul
@@ -18,17 +18,18 @@
 </div>
 <pre id="test">
 </pre>
 </body>
 
 <script class="testbody" type="application/javascript">
 <![CDATA[
 
-var SpecialPowers = window.opener.wrappedJSObject.SpecialPowers;
+const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
 var SimpleTest = window.opener.wrappedJSObject.SimpleTest;
 
 SimpleTest.waitForFocus(runTests, window);
 
 function ok(aCondition, aMessage)
 {
   SimpleTest.ok(aCondition, aMessage);
 }
@@ -1475,17 +1476,17 @@ function runReleaseTests()
   TIP.setCaretInPendingComposition(3);
   TIP.flushPendingComposition();
   is(input.value, "foo",
      description + "the input should have composition string");
 
   // Release the TIP
   TIP = null;
   // Needs to run GC forcibly for testing this.
-  SpecialPowers.gc();
+  Cu.forceGC();
 
   is(input.value, "",
      description + "the input should be empty because the composition should be canceled");
 
   TIP = createTIP();
   ok(TIP.beginInputTransactionForTests(window),
      description + "TIP.beginInputTransactionForTests() should succeed #2");
 }
@@ -1765,17 +1766,17 @@ function runCompositionWithKeyEventTests
   input.focus();
 
   var printableKeyEvent = new KeyboardEvent("", { key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A });
   var enterKeyEvent = new KeyboardEvent("", { key: "Enter", code: "Enter", keyCode: KeyboardEvent.DOM_VK_RETURN });
   var escKeyEvent = new KeyboardEvent("", { key: "Escape", code: "Escape", keyCode: KeyboardEvent.DOM_VK_ESCAPE });
   var convertKeyEvent = new KeyboardEvent("", { key: "Convert", code: "Convert", keyCode: KeyboardEvent.DOM_VK_CONVERT });
   var backspaceKeyEvent = new KeyboardEvent("", { key: "Backspace", code: "Backspace", keyCode: KeyboardEvent.DOM_VK_BACK_SPACE });
 
-  SpecialPowers.setBoolPref("dom.keyboardevent.dispatch_during_composition", false);
+  Services.prefs.setBoolPref("dom.keyboardevent.dispatch_during_composition", false);
 
   // nsITextInputProcessor.startComposition()
   reset();
   TIP.startComposition(printableKeyEvent);
   is(events.length, 2,
      description + "startComposition(printableKeyEvent) should cause keydown and compositionstart");
   is(events[0].type, "keydown",
      description + "startComposition(printableKeyEvent) should cause keydown");
@@ -1999,17 +2000,17 @@ function runCompositionWithKeyEventTests
      description + "committing text directly should cause compositionend after compositionupdate");
   is(events[3].data, "boo!",
      description + "compositionend caused by committing text directly should have the committing text in its data");
   is(events[4].type, "keyup",
      description + "committing text directly should cause keyup after compositionend");
   is(input.value, "FOobarbuzzboo!",
      description + "committing text directly should append the committing text to the focused editor");
 
-  SpecialPowers.setBoolPref("dom.keyboardevent.dispatch_during_composition", true);
+  Services.prefs.setBoolPref("dom.keyboardevent.dispatch_during_composition", true);
 
   // Even if "dom.keyboardevent.dispatch_during_composition" is true, keypress event shouldn't be fired during composition
   reset();
   TIP.startComposition(printableKeyEvent);
   is(events.length, 3,
      description + "TIP.startComposition(printableKeyEvent) should cause keydown, compositionstart and keyup (keypress event shouldn't be fired during composition)");
   is(events[0].type, "keydown",
      description + "TIP.startComposition(printableKeyEvent) should cause keydown (keypress event shouldn't be fired during composition)");
@@ -2103,17 +2104,17 @@ function runCompositionWithKeyEventTests
   TIP.cancelComposition(escKeydownEvent);
   is(events.length, 2,
      description + "TIP.cancelComposition(escKeydownEvent) should cause keydown and compositionend (keyup event shouldn't be fired)");
   is(events[0].type, "keydown",
      description + "TIP.cancelComposition(escKeydownEvent) should cause keydown (keyup event shouldn't be fired)");
   is(events[1].type, "compositionend",
      description + "TIP.cancelComposition(escKeydownEvent) should cause compositionend (keyup event shouldn't be fired)");
 
-  SpecialPowers.clearUserPref("dom.keyboardevent.dispatch_during_composition");
+  Services.prefs.clearUserPref("dom.keyboardevent.dispatch_during_composition");
 
   window.removeEventListener("compositionstart", handler, false);
   window.removeEventListener("compositionupdate", handler, false);
   window.removeEventListener("compositionend", handler, false);
   window.removeEventListener("keydown", handler, false);
   window.removeEventListener("keypress", handler, false);
   window.removeEventListener("keyup", handler, false);
 }
@@ -2150,17 +2151,17 @@ function runConsumingKeydownBeforeCompos
 
   input.value = "";
   input.focus();
 
   var printableKeyEvent = new KeyboardEvent("", { key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A });
   var enterKeyEvent = new KeyboardEvent("", { key: "Enter", code: "Enter", keyCode: KeyboardEvent.DOM_VK_RETURN });
   var escKeyEvent = new KeyboardEvent("", { key: "Escape", code: "Escape", keyCode: KeyboardEvent.DOM_VK_ESCAPE });
 
-  SpecialPowers.setBoolPref("dom.keyboardevent.dispatch_during_composition", false);
+  Services.prefs.setBoolPref("dom.keyboardevent.dispatch_during_composition", false);
 
   // If keydown before compositionstart is consumed, composition shouldn't be started.
   reset();
   ok(!TIP.startComposition(printableKeyEvent),
      description + "TIP.startComposition(printableKeyEvent) should return false because it's keydown is consumed");
   is(events.length, 2,
      description + "TIP.startComposition(printableKeyEvent) should cause only keydown and keyup events");
   is(events[0].type, "keydown",
@@ -2200,17 +2201,17 @@ function runConsumingKeydownBeforeCompos
      description + "TIP.commitCompositionWith(\"foo\", printableKeyEvent) should cause keydown event first");
   is(events[1].type, "keyup",
      description + "TIP.commitCompositionWith(\"foo\", printableKeyEvent) should cause keyup event after keydown");
   ok(!TIP.hasComposition,
      description + "TIP.commitCompositionWith(\"foo\", printableKeyEvent) shouldn't cause composition");
   is(input.value, "",
      description + "TIP.commitCompositionWith(\"foo\", printableKeyEvent) shouldn't cause inserting text");
 
-  SpecialPowers.setBoolPref("dom.keyboardevent.dispatch_during_composition", true);
+  Services.prefs.setBoolPref("dom.keyboardevent.dispatch_during_composition", true);
 
   // If composition is already started, TIP.flushPendingComposition(printableKeyEvent) shouldn't be canceled.
   TIP.startComposition();
   ok(TIP.hasComposition,
      description + "Before TIP.flushPendingComposition(printableKeyEvent), composition should've been created");
   reset();
   TIP.setPendingCompositionString("foo");
   TIP.appendClauseToPendingComposition(3, TIP.ATTR_RAW_CLAUSE);
@@ -2268,17 +2269,17 @@ function runConsumingKeydownBeforeCompos
      description + "TIP.cancelComposition(escKeyEvent) should cause compositionend event after compositionupdate");
   is(events[3].type, "keyup",
      description + "TIP.cancelComposition(escKeyEvent) should cause keyup event after compositionend");
   ok(!TIP.hasComposition,
      description + "TIP.cancelComposition(escKeyEvent) should cause canceling composition even if preceding keydown is consumed because there was a composition already");
   is(input.value, "",
      description + "TIP.cancelComposition(escKeyEvent) should cancel composition even if preceding keydown is consumed because there was a composition already");
 
-  SpecialPowers.clearUserPref("dom.keyboardevent.dispatch_during_composition");
+  Services.prefs.clearUserPref("dom.keyboardevent.dispatch_during_composition");
 
   window.removeEventListener("compositionstart", handler, false);
   window.removeEventListener("compositionupdate", handler, false);
   window.removeEventListener("compositionend", handler, false);
   window.removeEventListener("keydown", handler, false);
   window.removeEventListener("keypress", handler, false);
   window.removeEventListener("keyup", handler, false);
 }
@@ -2589,47 +2590,47 @@ function runKeyTests()
                 { type: "keypress", key: "a", code: "KeyA", keyCode: 0,                      charCode: "a".charCodeAt(0), defaultPrevented: true });
   checkKeyAttrs("TIP.keydown(keyA) and TIP.keyup(keyA) with preventing default of keyup", events[2],
                 { type: "keyup",    key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A, charCode: 0,                 defaultPrevented: true });
   is(input.value, "a",
      description + "input.value should be \"a\" by TIP.keyup(keyA) even if the keyup event is consumed");
 
   // key events during composition
   try {
-    SpecialPowers.setBoolPref("dom.keyboardevent.dispatch_during_composition", false);
+    Services.prefs.setBoolPref("dom.keyboardevent.dispatch_during_composition", false);
 
     ok(TIP.startComposition(), "TIP.startComposition() should start composition");
 
     input.value = "";
     reset();
     TIP.keydown(keyA);
     is(events.length, 0,
        description + "TIP.keydown(keyA) shouldn't cause key events during composition if it's disabled by the pref");
     reset();
     TIP.keyup(keyA);
     is(events.length, 0,
        description + "TIP.keyup(keyA) shouldn't cause key events during composition if it's disabled by the pref");
 
-    SpecialPowers.setBoolPref("dom.keyboardevent.dispatch_during_composition", true);
+    Services.prefs.setBoolPref("dom.keyboardevent.dispatch_during_composition", true);
     reset();
     TIP.keydown(keyA);
     is(events.length, 1,
        description + "TIP.keydown(keyA) should cause keydown event even composition if it's enabled by the pref");
     checkKeyAttrs("TIP.keydown(keyA) during composition", events[0],
                   { type: "keydown",  key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A, charCode: 0, isComposing: true });
     reset();
     TIP.keyup(keyA);
     is(events.length, 1,
        description + "TIP.keyup(keyA) should cause keyup event even composition if it's enabled by the pref");
     checkKeyAttrs("TIP.keyup(keyA) during composition", events[0],
                   { type: "keyup",    key: "a", code: "KeyA", keyCode: KeyboardEvent.DOM_VK_A, charCode: 0, isComposing: true });
 
   } finally {
     TIP.cancelComposition();
-    SpecialPowers.clearUserPref("dom.keyboardevent.dispatch_during_composition");
+    Services.prefs.clearUserPref("dom.keyboardevent.dispatch_during_composition");
   }
 
   // Test .location computation
   const kCodeToLocation = [
     { code: "BracketLeft",              location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
     { code: "BracketRight",             location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
     { code: "Comma",                    location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
     { code: "Digit0",                   location: KeyboardEvent.DOM_KEY_LOCATION_STANDARD },
--- a/dom/base/test/plugin.js
+++ b/dom/base/test/plugin.js
@@ -9,24 +9,24 @@ function getTestPlugin(pluginName) {
       return tag;
     }
   }
 
   ok(false, "Could not find plugin tag with plugin name '" + name + "'");
   return null;
 }
 // Copied from /dom/plugins/test/mochitest/utils.js
-function setTestPluginEnabledState(newEnabledState, pluginName) {
-  var oldEnabledState = SpecialPowers.setTestPluginEnabledState(newEnabledState, pluginName);
+async function setTestPluginEnabledState(newEnabledState, pluginName) {
+  var oldEnabledState = await SpecialPowers.setTestPluginEnabledState(newEnabledState, pluginName);
   if (!oldEnabledState) {
     return;
   }
   var plugin = getTestPlugin(pluginName);
   // Run a nested event loop to wait for the preference change to
   // propagate to the child. Yuck!
   SpecialPowers.Services.tm.spinEventLoopUntil(() => {
     return plugin.enabledState == newEnabledState;
   });
   SimpleTest.registerCleanupFunction(function() {
-    SpecialPowers.setTestPluginEnabledState(oldEnabledState, pluginName);
+    return SpecialPowers.setTestPluginEnabledState(oldEnabledState, pluginName);
   });
 }
 setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED);
--- a/dom/bindings/test/test_exception_options_from_jsimplemented.html
+++ b/dom/bindings/test/test_exception_options_from_jsimplemented.html
@@ -11,20 +11,20 @@ https://bugzilla.mozilla.org/show_bug.cg
   <script type="application/javascript">
   /* global TestInterfaceJS */
   /** Test for Bug 1107592 **/
 
   SimpleTest.waitForExplicitFinish();
 
   function doTest() {
     var file = location.href;
+
     var asyncFrame;
     /* Async parent frames from pushPrefEnv don't show up in e10s.  */
-    var isE10S = !SpecialPowers.isMainProcess();
-    if (!isE10S && SpecialPowers.getBoolPref("javascript.options.asyncstack")) {
+    if (SpecialPowers.getBoolPref("javascript.options.asyncstack")) {
       asyncFrame = `Async*@${file}:153:17
 `;
     } else {
       asyncFrame = "";
     }
 
     var t = new TestInterfaceJS();
     try {
--- a/dom/bindings/test/test_promise_rejections_from_jsimplemented.html
+++ b/dom/bindings/test/test_promise_rejections_from_jsimplemented.html
@@ -32,22 +32,22 @@ https://bugzilla.mozilla.org/show_bug.cg
   }
 
   function ensurePromiseFail(testNumber, value) {
     ok(false, "Test " + testNumber + " should not have a fulfilled promise");
   }
 
   function doTest() {
     var t = new TestInterfaceJS();
-    /* Async parent frames from pushPrefEnv don't show up in e10s.  */
-    var isE10S = !SpecialPowers.isMainProcess();
+
+
     var asyncStack = SpecialPowers.getBoolPref("javascript.options.asyncstack");
     var ourFile = location.href;
     var unwrapError = "Promise rejection value is a non-unwrappable cross-compartment wrapper.";
-    var parentFrame = (asyncStack && !isE10S) ? `Async*@${ourFile}:130:17
+    var parentFrame = asyncStack ? `Async*@${ourFile}:130:17
 ` : "";
 
     Promise.all([
       t.testPromiseWithThrowingChromePromiseInit().then(
           ensurePromiseFail.bind(null, 1),
           checkExn.bind(null, 49, "InternalError", unwrapError,
                         undefined, ourFile, 1,
                         `doTest@${ourFile}:49:9
--- a/dom/canvas/test/test_hitregion_event.html
+++ b/dom/canvas/test/test_hitregion_event.html
@@ -11,17 +11,17 @@
 <canvas id="input">
 </canvas>
 </p>
 <div id="content" style="display: none">
 
 </div>
 <pre id="test">
 <script type="application/javascript">
-SpecialPowers.pushPrefEnv({"set": [["canvas.hitregions.enabled", true]]}, function() {
+SpecialPowers.pushPrefEnv({"set": [["canvas.hitregions.enabled", true]]}).then(function() {
 
   var input = document.getElementById("input");
   var regionId = "";
   input.addEventListener('mousedown', function(evt){
     regionId = evt.region;
   })
 
   function runTests()
--- a/dom/chrome-webidl/BrowsingContext.webidl
+++ b/dom/chrome-webidl/BrowsingContext.webidl
@@ -4,16 +4,18 @@
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 interface nsIDocShell;
 
 [Exposed=Window, ChromeOnly]
 interface BrowsingContext {
   static BrowsingContext? get(unsigned long long aId);
 
+  static BrowsingContext? getFromWindow(WindowProxy window);
+
   BrowsingContext? findChildWithName(DOMString name);
   BrowsingContext? findWithName(DOMString name);
 
   readonly attribute DOMString name;
 
   readonly attribute BrowsingContext? parent;
 
   readonly attribute BrowsingContext top;
--- a/dom/events/test/test_legacy_non-primary_click.html
+++ b/dom/events/test/test_legacy_non-primary_click.html
@@ -18,35 +18,36 @@ SimpleTest.waitForExplicitFinish();
 
 const HACK_PREF = "dom.mouseevent.click.hack.use_legacy_non-primary_dispatch";
 const testEl = document.getElementById("test");
 const linkEl = document.getElementById("link-test");
 let seenClick = false;
 
 SpecialPowers.pushPrefEnv(
   { set: [[HACK_PREF, document.domain]] },
-  SimpleTest.waitForFocus(() => {
-    // Test seeing the non-primary 'click'
-    document.addEventListener("click", (e) => {
-      ok(true, "Saw 'click' event");
-      seenClick = true;
-    }, { once: true });
-    document.addEventListener("auxclick", (e) => {
-      ok(true, "Saw 'auxclick' event");
-      ok(seenClick, "Saw 'click' event before 'auxclick' event");
-    }, { once: true });
-    synthesizeMouseAtCenter(testEl, { button: 1 });
+  () => {
+    SimpleTest.waitForFocus(() => {
+      // Test seeing the non-primary 'click'
+      document.addEventListener("click", (e) => {
+        ok(true, "Saw 'click' event");
+        seenClick = true;
+      }, { once: true });
+      document.addEventListener("auxclick", (e) => {
+        ok(true, "Saw 'auxclick' event");
+        ok(seenClick, "Saw 'click' event before 'auxclick' event");
+      }, { once: true });
+      synthesizeMouseAtCenter(testEl, { button: 1 });
 
-    // Test preventDefaulting on non-primary 'click'
-    document.addEventListener("click", (e) => {
-      is(e.target, linkEl, "Saw 'click' on link");
-      e.preventDefault();
-      SimpleTest.finish();
-    }, { once: true, capture: true });
-    document.addEventListener("auxclick", (e) => {
-      ok(false, "Shouldn't have got 'auxclick' after preventDefaulting 'click'");
-    }, { once: true });
-    synthesizeMouseAtCenter(linkEl, { button: 1 });
-  })
-);
+      // Test preventDefaulting on non-primary 'click'
+      document.addEventListener("click", (e) => {
+        is(e.target, linkEl, "Saw 'click' on link");
+        e.preventDefault();
+        SimpleTest.finish();
+      }, { once: true, capture: true });
+      document.addEventListener("auxclick", (e) => {
+        ok(false, "Shouldn't have got 'auxclick' after preventDefaulting 'click'");
+      }, { once: true });
+      synthesizeMouseAtCenter(linkEl, { button: 1 });
+    });
+  });
 </script>
 </body>
 </html>
--- a/dom/html/test/file_fullscreen-denied.html
+++ b/dom/html/test/file_fullscreen-denied.html
@@ -147,16 +147,17 @@ async function testFullscreenMouseBtn(ev
       requestFullscreenMouseBtn(evt, mouseButton);
       await fsDenied;
       ok(!document.fullscreenElement, `Should not grant request on '${evt}' triggered by mouse button ${mouseButton}`);
     }
   }
   // Restore the pref environment we changed before
   // entering testNonTrustContext.
   await SpecialPowers.popPrefEnv();
+  await SpecialPowers.popPrefEnv();
   finish();
 }
 
 function finish() {
   opener.nextTest();
 }
 
 </script>
--- a/dom/html/test/test_bug1146116.html
+++ b/dom/html/test/test_bug1146116.html
@@ -34,18 +34,18 @@ function getGlobal(thing) {
   return SpecialPowers.unwrap(SpecialPowers.Cu.getGlobalForObject(thing));
 }
 
 function onFileOpened(message) {
   const file = message.domFile;
   const elem = document.getElementById("file");
   is(getGlobal(elem), window,
      "getGlobal() works as expected");
-  isnot(getGlobal(file), window,
-        "File from MessageManager is wrapped");
+  is(getGlobal(file), window,
+     "File from MessageManager is not wrapped");
   SpecialPowers.wrap(elem).mozSetFileArray([file]);
   is(getGlobal(elem.files[0]), window,
      "File read back from input element is not wrapped");
   helper.addMessageListener("file.removed", onFileRemoved);
   helper.sendAsyncMessage("file.remove", null);
 }
 
 function onFileRemoved() {
--- a/dom/html/test/test_bug1414077.html
+++ b/dom/html/test/test_bug1414077.html
@@ -7,44 +7,46 @@ https://bugzilla.mozilla.org/show_bug.cg
 <meta charset="utf-8">
 <title>Test for Bug 1414077</title>
 <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
 <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
 <script type="application/javascript">
 
 /** Test for Bug 1414077 **/
 
-SimpleTest.waitForExplicitFinish();
+var {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+add_task(async function() {
+  await SpecialPowers.pushPrefEnv({"set": [["browser.enable_automatic_image_resizing", true]]});
 
-SpecialPowers.pushPrefEnv({"set":[["browser.enable_automatic_image_resizing", true]]}, function() {
-  var testWin = document.querySelector("iframe");
-  testWin.height = 0;
-  testWin.width = 0;
-  testWin.src = "image.png";
-  testWin.onload = function() {
-    var testDoc = testWin.contentDocument;
+  return new Promise(resolve => {
+    var testWin = document.querySelector("iframe");
+    testWin.src = "image.png";
+    testWin.onload = function() {
+      var testDoc = testWin.contentDocument;
 
-    // testDoc should be a image document.
-    ok(testDoc.imageIsOverflowing, "image is overflowing");
-    ok(testDoc.imageIsResized, "image is resized to fit visible area by default");
+      // testDoc should be a image document.
+      ok(testDoc.imageIsOverflowing, "image is overflowing");
+      ok(testDoc.imageIsResized, "image is resized to fit visible area by default");
+
+      // Restore image to original size.
+      testDoc.restoreImage();
+      ok(testDoc.imageIsOverflowing, "image is overflowing");
+      ok(!testDoc.imageIsResized, "image is restored to original size");
 
-    // Restore image to original size.
-    testDoc.restoreImage();
-    ok(testDoc.imageIsOverflowing, "image is overflowing");
-    ok(!testDoc.imageIsResized, "image is restored to original size");
+      // Resize the image to fit visible area
+      testDoc.shrinkToFit();
+      ok(testDoc.imageIsOverflowing, "image is overflowing");
+      ok(testDoc.imageIsResized, "image is resized to fit visible area");
 
-    // Resize the image to fit visible area
-    testDoc.shrinkToFit();
-    ok(testDoc.imageIsOverflowing, "image is overflowing");
-    ok(testDoc.imageIsResized, "image is resized to fit visible area");
-
-    SimpleTest.finish();
-  };
+      resolve();
+    };
+  })
 });
 
 </script>
 </head>
 
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1414077">Mozilla Bug 1414077</a>
-<iframe></iframe>
+<iframe width="0" height="0"></iframe>
 </body>
 </html>
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -883,22 +883,31 @@ nsresult ContentChild::ProvideWindowComm
     sNoopenerNewProcessInited = true;
   }
 
   // Check if we should load in a different process. We always want to load in a
   // different process if we have noopener set, but we also might if we can't
   // load in the current process.
   bool loadInDifferentProcess = aForceNoOpener && sNoopenerNewProcess;
   if (aTabOpener && !loadInDifferentProcess && aURI) {
-    nsCOMPtr<nsIWebBrowserChrome3> browserChrome3;
-    rv = aTabOpener->GetWebBrowserChrome(getter_AddRefs(browserChrome3));
-    if (NS_SUCCEEDED(rv) && browserChrome3) {
-      bool shouldLoad;
-      rv = browserChrome3->ShouldLoadURIInThisProcess(aURI, &shouldLoad);
-      loadInDifferentProcess = NS_SUCCEEDED(rv) && !shouldLoad;
+    nsCOMPtr<nsILoadContext> context;
+    if (aParent) {
+      context = do_GetInterface(aTabOpener->WebNavigation());
+    }
+    // Only special-case cross-process loads if Fission is disabled. With
+    // Fission enabled, the initial in-process load will automatically be
+    // retargeted to the correct process.
+    if (!(context && context->UseRemoteSubframes())) {
+      nsCOMPtr<nsIWebBrowserChrome3> browserChrome3;
+      rv = aTabOpener->GetWebBrowserChrome(getter_AddRefs(browserChrome3));
+      if (NS_SUCCEEDED(rv) && browserChrome3) {
+        bool shouldLoad;
+        rv = browserChrome3->ShouldLoadURIInThisProcess(aURI, &shouldLoad);
+        loadInDifferentProcess = NS_SUCCEEDED(rv) && !shouldLoad;
+      }
     }
   }
 
   // If we're in a content process and we have noopener set, there's no reason
   // to load in our process, so let's load it elsewhere!
   if (loadInDifferentProcess) {
     float fullZoom;
     nsCOMPtr<nsIPrincipal> triggeringPrincipal;
--- a/dom/ipc/JSWindowActor.cpp
+++ b/dom/ipc/JSWindowActor.cpp
@@ -3,16 +3,18 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "mozilla/dom/JSWindowActor.h"
 #include "mozilla/dom/JSWindowActorBinding.h"
 #include "mozilla/dom/MessageManagerBinding.h"
 #include "mozilla/dom/PWindowGlobal.h"
+#include "mozilla/dom/Promise.h"
+#include "js/Promise.h"
 
 namespace mozilla {
 namespace dom {
 
 NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION(JSWindowActor)
   NS_WRAPPERCACHE_INTERFACE_MAP_ENTRY
   NS_INTERFACE_MAP_ENTRY(nsISupports)
 NS_INTERFACE_MAP_END
@@ -30,31 +32,26 @@ NS_IMPL_CYCLE_COLLECTION_UNLINK_END
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(JSWindowActor)
   NS_IMPL_CYCLE_COLLECTION_TRAVERSE(mPendingQueries)
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_TRACE_WRAPPERCACHE(JSWindowActor)
 
 JSWindowActor::JSWindowActor() : mNextQueryId(0) {}
 
-nsIGlobalObject* JSWindowActor::GetParentObject() const {
-  return xpc::NativeGlobal(xpc::PrivilegedJunkScope());
-}
-
 void JSWindowActor::StartDestroy() {
   DestroyCallback(DestroyCallbackFunction::WillDestroy);
 }
 
 void JSWindowActor::AfterDestroy() {
   DestroyCallback(DestroyCallbackFunction::DidDestroy);
 }
 
 void JSWindowActor::DestroyCallback(DestroyCallbackFunction callback) {
-  AutoEntryScript aes(xpc::PrivilegedJunkScope(),
-                      "JSWindowActor destroy callback");
+  AutoEntryScript aes(GetParentObject(), "JSWindowActor destroy callback");
   JSContext* cx = aes.cx();
   MozActorDestroyCallbacks callbacksHolder;
   NS_ENSURE_TRUE_VOID(GetWrapper());
   JS::Rooted<JS::Value> val(cx, JS::ObjectValue(*GetWrapper()));
   if (NS_WARN_IF(!callbacksHolder.Init(cx, val))) {
     return;
   }
 
@@ -86,36 +83,36 @@ void JSWindowActor::SetName(const nsAStr
 }
 
 void JSWindowActor::SendAsyncMessage(JSContext* aCx,
                                      const nsAString& aMessageName,
                                      JS::Handle<JS::Value> aObj,
                                      JS::Handle<JS::Value> aTransfers,
                                      ErrorResult& aRv) {
   ipc::StructuredCloneData data;
-  if (!aObj.isUndefined() && !nsFrameMessageManager::GetParamsForMessage(
-                                 aCx, aObj, aTransfers, data)) {
+  if (!nsFrameMessageManager::GetParamsForMessage(aCx, aObj, aTransfers,
+                                                  data)) {
     aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR);
     return;
   }
 
   JSWindowActorMessageMeta meta;
   meta.actorName() = mName;
   meta.messageName() = aMessageName;
   meta.kind() = JSWindowActorMessageKind::Message;
 
   SendRawMessage(meta, std::move(data), aRv);
 }
 
 already_AddRefed<Promise> JSWindowActor::SendQuery(
     JSContext* aCx, const nsAString& aMessageName, JS::Handle<JS::Value> aObj,
     JS::Handle<JS::Value> aTransfers, ErrorResult& aRv) {
   ipc::StructuredCloneData data;
-  if (!aObj.isUndefined() && !nsFrameMessageManager::GetParamsForMessage(
-                                 aCx, aObj, aTransfers, data)) {
+  if (!nsFrameMessageManager::GetParamsForMessage(aCx, aObj, aTransfers,
+                                                  data)) {
     aRv.Throw(NS_ERROR_DOM_DATA_CLONE_ERR);
     return nullptr;
   }
 
   nsIGlobalObject* global = xpc::CurrentNativeGlobal(aCx);
   if (NS_WARN_IF(!global)) {
     aRv.Throw(NS_ERROR_UNEXPECTED);
     return nullptr;
@@ -135,25 +132,29 @@ already_AddRefed<Promise> JSWindowActor:
   mPendingQueries.Put(meta.queryId(), promise);
 
   SendRawMessage(meta, std::move(data), aRv);
   return promise.forget();
 }
 
 void JSWindowActor::ReceiveRawMessage(const JSWindowActorMessageMeta& aMetadata,
                                       ipc::StructuredCloneData&& aData) {
-  AutoEntryScript aes(xpc::PrivilegedJunkScope(),
-                      "JSWindowActor message handler");
+  AutoEntryScript aes(GetParentObject(), "JSWindowActor message handler");
   JSContext* cx = aes.cx();
 
   // Read the message into a JS object from IPC.
   ErrorResult error;
   JS::Rooted<JS::Value> data(cx);
   aData.Read(cx, &data, error);
   if (NS_WARN_IF(error.Failed())) {
+    if (XRE_IsParentProcess()) {
+      MOZ_ASSERT(false, "Should not receive non-decodable data");
+    } else {
+      MOZ_DIAGNOSTIC_ASSERT(false, "Should not receive non-decodable data");
+    }
     MOZ_ALWAYS_TRUE(error.MaybeSetPendingException(cx));
     return;
   }
 
   switch (aMetadata.kind()) {
     case JSWindowActorMessageKind::QueryResolve:
     case JSWindowActorMessageKind::QueryReject:
       ReceiveQueryReply(cx, aMetadata, data, error);
@@ -230,18 +231,25 @@ void JSWindowActor::ReceiveQueryReply(JS
 
   RefPtr<Promise> promise;
   if (NS_WARN_IF(!mPendingQueries.Remove(aMetadata.queryId(),
                                          getter_AddRefs(promise)))) {
     aRv.Throw(NS_ERROR_DOM_INVALID_STATE_ERR);
     return;
   }
 
+  JSAutoRealm ar(aCx, promise->PromiseObj());
+  JS::RootedValue data(aCx, aData);
+  if (NS_WARN_IF(!JS_WrapValue(aCx, &data))) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return;
+  }
+
   if (aMetadata.kind() == JSWindowActorMessageKind::QueryResolve) {
-    promise->MaybeResolve(aCx, aData);
+    promise->MaybeResolve(aCx, data);
   } else {
     promise->MaybeReject(NS_ERROR_DOM_OPERATION_ERR);
   }
 }
 
 // Native handler for our generated promise which is used to handle Queries and
 // send the reply when their promises have been resolved.
 JSWindowActor::QueryHandler::QueryHandler(
@@ -257,18 +265,19 @@ void JSWindowActor::QueryHandler::Reject
   }
 
   // Make sure that this rejection is reported, despite being "handled". This
   // is done by creating a new promise in the rejected state, and throwing it
   // away. This will be reported as an unhandled rejected promise.
   Unused << JS::CallOriginalPromiseReject(aCx, aValue);
 
   // The exception probably isn't cloneable, so just send down undefined.
-  SendReply(aCx, JSWindowActorMessageKind::QueryReject,
-            ipc::StructuredCloneData());
+  ipc::StructuredCloneData data;
+  data.Write(aCx, JS::UndefinedHandleValue, IgnoredErrorResult());
+  SendReply(aCx, JSWindowActorMessageKind::QueryReject, std::move(data));
 }
 
 void JSWindowActor::QueryHandler::ResolvedCallback(
     JSContext* aCx, JS::Handle<JS::Value> aValue) {
   if (!mActor) {
     return;
   }
 
@@ -284,18 +293,19 @@ void JSWindowActor::QueryHandler::Resolv
     msg.Append(mActor->Name());
     msg.Append(':');
     msg.Append(mMessageName);
     msg.Append(NS_LITERAL_STRING(": message reply cannot be cloned."));
     nsContentUtils::LogSimpleConsoleError(msg, "chrome", false, true);
 
     JS_ClearPendingException(aCx);
 
-    SendReply(aCx, JSWindowActorMessageKind::QueryReject,
-              ipc::StructuredCloneData());
+    ipc::StructuredCloneData data;
+    data.Write(aCx, JS::UndefinedHandleValue, IgnoredErrorResult());
+    SendReply(aCx, JSWindowActorMessageKind::QueryReject, std::move(data));
     return;
   }
 
   SendReply(aCx, JSWindowActorMessageKind::QueryResolve, std::move(data));
 }
 
 void JSWindowActor::QueryHandler::SendReply(JSContext* aCx,
                                             JSWindowActorMessageKind aKind,
--- a/dom/ipc/JSWindowActor.h
+++ b/dom/ipc/JSWindowActor.h
@@ -51,17 +51,17 @@ class JSWindowActor : public nsISupports
                                       const nsAString& aMessageName,
                                       JS::Handle<JS::Value> aObj,
                                       JS::Handle<JS::Value> aTransfers,
                                       ErrorResult& aRv);
 
   void ReceiveRawMessage(const JSWindowActorMessageMeta& aMetadata,
                          ipc::StructuredCloneData&& aData);
 
-  nsIGlobalObject* GetParentObject() const;
+  virtual nsIGlobalObject* GetParentObject() const = 0;
 
   void RejectPendingQueries();
 
  protected:
   // Send the message described by the structured clone data |aData|, and the
   // message metadata |aMetadata|. The underlying transport should call the
   // |ReceiveMessage| method on the other side asynchronously.
   virtual void SendRawMessage(const JSWindowActorMessageMeta& aMetadata,
--- a/dom/ipc/JSWindowActorChild.cpp
+++ b/dom/ipc/JSWindowActorChild.cpp
@@ -12,16 +12,20 @@
 #include "mozilla/dom/WindowProxyHolder.h"
 #include "mozilla/dom/MessageManagerBinding.h"
 #include "mozilla/dom/BrowsingContext.h"
 #include "nsGlobalWindowInner.h"
 
 namespace mozilla {
 namespace dom {
 
+JSWindowActorChild::JSWindowActorChild(nsIGlobalObject* aGlobal)
+    : mGlobal(aGlobal ? aGlobal
+                      : xpc::NativeGlobal(xpc::PrivilegedJunkScope())) {}
+
 JSWindowActorChild::~JSWindowActorChild() { MOZ_ASSERT(!mManager); }
 
 JSObject* JSWindowActorChild::WrapObject(JSContext* aCx,
                                          JS::Handle<JSObject*> aGivenProto) {
   return JSWindowActorChild_Binding::Wrap(aCx, this, aGivenProto);
 }
 
 WindowGlobalChild* JSWindowActorChild::GetManager() const { return mManager; }
--- a/dom/ipc/JSWindowActorChild.h
+++ b/dom/ipc/JSWindowActorChild.h
@@ -32,22 +32,27 @@ namespace mozilla {
 namespace dom {
 
 class JSWindowActorChild final : public JSWindowActor {
  public:
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(JSWindowActorChild,
                                                          JSWindowActor)
 
+  explicit JSWindowActorChild(nsIGlobalObject* aGlobal = nullptr);
+
+  nsIGlobalObject* GetParentObject() const override { return mGlobal; }
+
   JSObject* WrapObject(JSContext* aCx,
                        JS::Handle<JSObject*> aGivenProto) override;
 
   static already_AddRefed<JSWindowActorChild> Constructor(GlobalObject& aGlobal,
                                                           ErrorResult& aRv) {
-    return MakeAndAddRef<JSWindowActorChild>();
+    nsCOMPtr<nsIGlobalObject> global(do_QueryInterface(aGlobal.GetAsSupports()));
+    return MakeAndAddRef<JSWindowActorChild>(global);
   }
 
   WindowGlobalChild* GetManager() const;
   void Init(const nsAString& aName, WindowGlobalChild* aManager);
   void StartDestroy();
   void AfterDestroy();
   Document* GetDocument(ErrorResult& aRv);
   BrowsingContext* GetBrowsingContext(ErrorResult& aRv);
@@ -59,14 +64,16 @@ class JSWindowActorChild final : public 
                       ipc::StructuredCloneData&& aData,
                       ErrorResult& aRv) override;
 
  private:
   ~JSWindowActorChild();
 
   bool mCanSend = true;
   RefPtr<WindowGlobalChild> mManager;
+
+  nsCOMPtr<nsIGlobalObject> mGlobal;
 };
 
 }  // namespace dom
 }  // namespace mozilla
 
 #endif  // mozilla_dom_JSWindowActorChild_h
--- a/dom/ipc/JSWindowActorParent.cpp
+++ b/dom/ipc/JSWindowActorParent.cpp
@@ -9,16 +9,20 @@
 #include "mozilla/dom/WindowGlobalParent.h"
 #include "mozilla/dom/MessageManagerBinding.h"
 
 namespace mozilla {
 namespace dom {
 
 JSWindowActorParent::~JSWindowActorParent() { MOZ_ASSERT(!mManager); }
 
+nsIGlobalObject* JSWindowActorParent::GetParentObject() const {
+  return xpc::NativeGlobal(xpc::PrivilegedJunkScope());
+}
+
 JSObject* JSWindowActorParent::WrapObject(JSContext* aCx,
                                           JS::Handle<JSObject*> aGivenProto) {
   return JSWindowActorParent_Binding::Wrap(aCx, this, aGivenProto);
 }
 
 WindowGlobalParent* JSWindowActorParent::GetManager() const { return mManager; }
 
 void JSWindowActorParent::Init(const nsAString& aName,
--- a/dom/ipc/JSWindowActorParent.h
+++ b/dom/ipc/JSWindowActorParent.h
@@ -35,16 +35,18 @@ class JSWindowActorParent final : public
   JSObject* WrapObject(JSContext* aCx,
                        JS::Handle<JSObject*> aGivenProto) override;
 
   static already_AddRefed<JSWindowActorParent> Constructor(
       GlobalObject& aGlobal, ErrorResult& aRv) {
     return MakeAndAddRef<JSWindowActorParent>();
   }
 
+  nsIGlobalObject* GetParentObject() const override;
+
   WindowGlobalParent* GetManager() const;
   void Init(const nsAString& aName, WindowGlobalParent* aManager);
   void StartDestroy();
   void AfterDestroy();
   CanonicalBrowsingContext* GetBrowsingContext(ErrorResult& aRv);
 
  protected:
   void SendRawMessage(const JSWindowActorMessageMeta& aMeta,
--- a/dom/media/test/test_eme_request_notifications.html
+++ b/dom/media/test/test_eme_request_notifications.html
@@ -55,17 +55,17 @@ var tests = [
   {
     keySystem: "com.widevine.alpha",
     expectedStatus: 'cdm-disabled',
     prefs: [["media.eme.enabled", true], ["media.gmp-widevinecdm.enabled", false]]
   },
   {
     keySystem: "com.widevine.alpha",
     expectedStatus: 'cdm-not-installed',
-    prefs: [["media.eme.enabled", true], , ["media.gmp-widevinecdm.enabled", true]]
+    prefs: [["media.eme.enabled", true], ["media.gmp-widevinecdm.enabled", true]]
   },
   {
     keySystem: CLEARKEY_KEYSYSTEM,
     expectedStatus: 'cdm-created',
     prefs: [["media.eme.enabled", true]]
   }
 ];
 
--- a/dom/media/test/test_info_leak.html
+++ b/dom/media/test/test_info_leak.html
@@ -157,16 +157,17 @@ function checkState(v) {
 
 
 function startTest(test, token) {
   manager.started(token);
   log("Testing: " + test.type + " @ " + test.name);
   createMedia(test.type, test.name, token);  
 }
 
+SimpleTest.waitForExplicitFinish();
 createTestArray().then(testArray => {
   manager.runTests(testArray, startTest);
 });
 
 </script>
 </pre>
 
 </body>
--- a/dom/notification/test/mochitest/NotificationTest.js
+++ b/dom/notification/test/mochitest/NotificationTest.js
@@ -3,22 +3,22 @@ var NotificationTest = (function() {
 
   function info(msg, name) {
     SimpleTest.info("::Notification Tests::" + (name || ""), msg);
   }
 
   function setup_testing_env() {
     SimpleTest.waitForExplicitFinish();
     // turn on testing pref (used by notification.cpp, and mock the alerts
-    SpecialPowers.setBoolPref("notification.prompt.testing", true);
+    return SpecialPowers.setBoolPref("notification.prompt.testing", true);
   }
 
-  function teardown_testing_env() {
-    SpecialPowers.clearUserPref("notification.prompt.testing");
-    SpecialPowers.clearUserPref("notification.prompt.testing.allow");
+  async function teardown_testing_env() {
+    await SpecialPowers.clearUserPref("notification.prompt.testing");
+    await SpecialPowers.clearUserPref("notification.prompt.testing.allow");
 
     SimpleTest.finish();
   }
 
   function executeTests(tests, callback) {
     // context is `this` object in test functions
     // it can be used to track data between tests
     var context = {};
@@ -73,32 +73,33 @@ var NotificationTest = (function() {
       map,
       set,
     };
   })();
 
   // NotificationTest API
   return {
     run(tests, callback) {
-      setup_testing_env();
+      let ready = setup_testing_env();
 
-      addLoadEvent(function() {
+      addLoadEvent(async function() {
+        await ready;
         executeTests(tests, function() {
           teardown_testing_env();
           callback && callback();
         });
       });
     },
 
     allowNotifications() {
-      SpecialPowers.setBoolPref("notification.prompt.testing.allow", true);
+      return SpecialPowers.setBoolPref("notification.prompt.testing.allow", true);
     },
 
     denyNotifications() {
-      SpecialPowers.setBoolPref("notification.prompt.testing.allow", false);
+      return SpecialPowers.setBoolPref("notification.prompt.testing.allow", false);
     },
 
     clickNotification(notification) {
       // TODO: how??
     },
 
     fireCloseEvent(title) {
       window.dispatchEvent(new CustomEvent("mock-notification-close-event", {
--- a/dom/notification/test/mochitest/test_notification_basics.html
+++ b/dom/notification/test/mochitest/test_notification_basics.html
@@ -27,39 +27,39 @@
       ok(Notification.get, "Notification.get exists");
     },
 
     function() {
       info("Test requestPermission without callback");
       Notification.requestPermission();
     },
 
-    function(done) {
+    async function(done) {
       info("Test requestPermission deny");
       function assertPermissionDenied(perm) {
         is(perm, "denied", "Permission should be denied.");
         is(Notification.permission, "denied", "Permission should be denied.");
       }
-      NotificationTest.denyNotifications();
+      await NotificationTest.denyNotifications();
       Notification.requestPermission()
         .then(assertPermissionDenied)
         .then(_ => Notification.requestPermission(assertPermissionDenied))
         .catch(err => {
           ok(!err, "requestPermission should not reject promise");
         })
         .then(done);
     },
 
-    function(done) {
+    async function(done) {
       info("Test requestPermission grant");
       function assertPermissionGranted(perm) {
         is(perm, "granted", "Permission should be granted.");
         is(Notification.permission, "granted", "Permission should be granted");
       }
-      NotificationTest.allowNotifications();
+      await NotificationTest.allowNotifications();
       Notification.requestPermission()
         .then(assertPermissionGranted)
         .then(_ => Notification.requestPermission(assertPermissionGranted))
         .catch(err => {
           ok(!err, "requestPermission should not reject promise");
         })
         .then(done);
     },
--- a/dom/notification/test/mochitest/test_notification_insecure_context.html
+++ b/dom/notification/test/mochitest/test_notification_insecure_context.html
@@ -32,17 +32,17 @@ https://bugzilla.mozilla.org/show_bug.cg
     let response = await Notification.requestPermission();
     is(response, "denied", "Denied permission in insecure context");
 
     await SpecialPowers.pushPrefEnv({"set": [["dom.webnotifications.allowinsecure", true]]});
 
     response = await Notification.requestPermission();
     is(response, "granted", "Granted permission in insecure context with pref set");
 
-    script.sendSyncMessage("destroy");
+    script.sendAsyncMessage("destroy");
     script.destroy();
 
     SimpleTest.finish();
   })();
   </script>
   </pre>
 </body>
 </html>
--- a/dom/permission/tests/test_permissions_api.html
+++ b/dom/permission/tests/test_permissions_api.html
@@ -125,19 +125,19 @@
           () => ok(false, `query should not have rejected for '${name}'`));
       },
       testStatusOnChange() {
         return new Promise((resolve) => {
           SpecialPowers.popPermissions(() => {
             const permission = 'geolocation';
             const promiseGranted = this.promiseStateChanged(permission, 'granted');
             this.setPermissions(ALLOW_ACTION);
-            promiseGranted.then(() => {
+            promiseGranted.then(async () => {
               const promisePrompt = this.promiseStateChanged(permission, 'prompt');
-              SpecialPowers.popPermissions();
+              await SpecialPowers.popPermissions();
               return promisePrompt;
             }).then(resolve);
           });
         });
       },
       testInvalidQuery() {
         return aWindow.navigator.permissions
           .query({ name: 'invalid' })
--- a/dom/plugins/test/mochitest/head.js
+++ b/dom/plugins/test/mochitest/head.js
@@ -103,17 +103,21 @@ function waitScrollFinish(aTarget) {
 
 /**
  * Set a plugin activation state. See nsIPluginTag for
  * supported states. Affected plugin default to the first
  * test plugin.
  */
 function setTestPluginEnabledState(aState, aPluginName) {
   let name = aPluginName || "Test Plug-in";
-  SpecialPowers.setTestPluginEnabledState(aState, name);
+  let resolved = false;
+  SpecialPowers.setTestPluginEnabledState(aState, name).then(() => {
+    resolved = true;
+  });
+  SpecialPowers.Services.tm.spinEventLoopUntil(() => resolved);
 }
 
 /**
  * Returns the chrome side nsIPluginTag for this plugin, helper for
  * setTestPluginEnabledState.
  */
 function getTestPlugin(aName) {
   let pluginName = aName || "Test Plug-in";
--- a/dom/plugins/test/mochitest/plugin-utils.js
+++ b/dom/plugins/test/mochitest/plugin-utils.js
@@ -28,18 +28,18 @@ function getTestPlugin(pluginName) {
 function setTestPluginEnabledState(newEnabledState, pluginName) {
   var oldEnabledState = SpecialPowers.setTestPluginEnabledState(newEnabledState, pluginName);
   var plugin = getTestPlugin(pluginName);
   // Run a nested event loop to wait for the preference change to
   // propagate to the child. Yuck!
   SpecialPowers.Services.tm.spinEventLoopUntil(() => {
     return plugin.enabledState == newEnabledState;
   });
-  SimpleTest.registerCleanupFunction(function() {
-    SpecialPowers.setTestPluginEnabledState(oldEnabledState, pluginName);
+  SimpleTest.registerCleanupFunction(async function() {
+    return SpecialPowers.setTestPluginEnabledState(await oldEnabledState, pluginName);
   });
 }
 
 function crashAndGetCrashServiceRecord(crashMethodName, callback) {
   var crashMan =
     SpecialPowers.Cu.import("resource://gre/modules/Services.jsm").
     Services.crashmanager;
 
--- a/dom/plugins/test/mochitest/test_bug1165981.html
+++ b/dom/plugins/test/mochitest/test_bug1165981.html
@@ -5,21 +5,16 @@
     <script src="/tests/SimpleTest/SimpleTest.js"></script>
     <script type="text/javascript" src="plugin-utils.js"></script>
     <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   </head>
     <script class="testbody" type="application/javascript">
       "use strict";
 
       SimpleTest.waitForExplicitFinish();
-      function addPerms() {
-        ok(SpecialPowers.setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED, "Shockwave Flash"), "Should find allowed test flash plugin");
-        ok(!SpecialPowers.setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED, "Third Test Plug-in"), "Should not find disallowed plugin");
-        SpecialPowers.pushPermissions([{type: "plugin:flash", allow: true, context: document}], run);
-      }
 
       function findPlugin(pluginName) {
           for (var i = 0; i < navigator.plugins.length; i++) {
             var plugin = navigator.plugins[i];
             if (plugin.name === pluginName) {
               return plugin;
             }
           }
@@ -40,17 +35,23 @@
           let obj = document.createElement("object");
           obj.type = type;
           obj.id = id;
           obj.width = 200;
           obj.height = 200;
           document.body.appendChild(obj);
       }
 
-      function run() {
+      async function run() {
+        ok(await SpecialPowers.setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED, "Shockwave Flash"), "Should find allowed test flash plugin");
+        ok(!await SpecialPowers.setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED, "Third Test Plug-in"), "Should not find disallowed plugin");
+        await new Promise(resolve => {
+          SpecialPowers.pushPermissions([{type: "plugin:flash", allow: true, context: document}], resolve);
+        });
+
         createNode("plugin-flash", "application/x-shockwave-flash-test");
         createNode("disallowedPlugin", "application/x-third-test");
         var pluginElement = document.getElementById("plugin-flash");
         is(pluginElement.identifierToStringTest("foo"), "foo", "Should be able to call a function provided by the plugin");
 
         pluginElement = document.getElementById("disallowedPlugin");
         is(typeof pluginElement.identifierToStringTest, "undefined", "Should NOT be able to call a function on a disallowed plugin");
 
@@ -65,11 +66,11 @@
 
         ok(findMimeType("application/x-shockwave-flash-test"), "Should have found a MIME type named 'application/x-shockwave-flash-test'");
         ok(!findMimeType("application/x-third-test"), "Should NOT have found a disallowed MIME type named 'application/x-third-test'");
 
         SimpleTest.finish();
       }
     </script>
 
-  <body onload="addPerms()">
+  <body onload="run()">
   </body>
 </html>
--- a/dom/plugins/test/mochitest/test_hangui.xul
+++ b/dom/plugins/test/mochitest/test_hangui.xul
@@ -14,16 +14,18 @@
   <script type="application/javascript"
           src="http://mochi.test:8888/chrome/dom/plugins/test/mochitest/hangui_common.js" />
 
 <body xmlns="http://www.w3.org/1999/xhtml">
   <iframe id="iframe1" src="hangui_subpage.html" width="400" height="400"></iframe>
 </body>
 <script class="testbody" type="application/javascript">
 <![CDATA[
+var {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
 SimpleTest.waitForExplicitFinish();
 SimpleTest.expectChildProcessCrash();
 SpecialPowers.pushPrefEnv({"set": [["security.allow_eval_with_system_principal",
                                     true]]});
 setTestPluginEnabledState(SpecialPowers.Ci.nsIPluginTag.STATE_ENABLED);
 
 const hangUITimeoutPref = "dom.ipc.plugins.hangUITimeoutSecs";
 const hangUIMinDisplayPref = "dom.ipc.plugins.hangUIMinDisplaySecs";
@@ -84,19 +86,19 @@ function hanguiCancel(testName, cb) {
   hanguiOperation(testName, timeoutSec, true, true, HANGUIOP_CANCEL, 0, false, cb);
 }
 
 function finishTest() {
   if (obsCount > 0) {
     os.removeObserver(testObserver, "plugin-crashed");
     --obsCount;
   }
-  SpecialPowers.clearUserPref(hangUITimeoutPref);
-  SpecialPowers.clearUserPref(hangUIMinDisplayPref);
-  SpecialPowers.clearUserPref(timeoutPref);
+  Services.prefs.clearUserPref(hangUITimeoutPref);
+  Services.prefs.clearUserPref(hangUIMinDisplayPref);
+  Services.prefs.clearUserPref(timeoutPref);
   SimpleTest.finish();
 }
 
 function runTests() {
   resetVars();
 
   hanguiOperation("Prime ChromeWorker", 0, false, false, HANGUIOP_NOTHING, 0,
                   false, "test1");
@@ -146,32 +148,32 @@ function test9b() {
   hanguiExpect("test9b: Plugin Hang UI is not showing (checkbox)", false);
   p.stall(STALL_DURATION);
   hanguiExpect("test9b: Plugin Hang UI is still not showing (checkbox)", false, false, "finishTest");
   p.stall(STALL_DURATION);
 }
 
 function test9a() {
   resetVars();
-  SpecialPowers.setIntPref(hangUITimeoutPref, 1);
-  SpecialPowers.setIntPref(hangUIMinDisplayPref, 1);
-  SpecialPowers.setIntPref(timeoutPref, 45);
+  Services.prefs.setIntPref(hangUITimeoutPref, 1);
+  Services.prefs.setIntPref(hangUIMinDisplayPref, 1);
+  Services.prefs.setIntPref(timeoutPref, 45);
   hanguiContinue("test9a: Continue button works with checkbox", true, "test9b");
   p.stall(STALL_DURATION);
 }
 
 function test9() {
   window.frameLoaded = test9a;
   iframe.contentWindow.location.reload();
 }
 
 function test8a() {
   resetVars();
-  SpecialPowers.setIntPref(hangUITimeoutPref, 1);
-  SpecialPowers.setIntPref(hangUIMinDisplayPref, 4);
+  Services.prefs.setIntPref(hangUITimeoutPref, 1);
+  Services.prefs.setIntPref(hangUIMinDisplayPref, 4);
   hanguiExpect("test8a: Plugin Hang UI is not showing (disabled due to hangUIMinDisplaySecs)", false, false, "test9");
   var exceptionThrown = false;
   try {
     p.hang();
   } catch(e) {
     exceptionThrown = true;
   }
   ok(exceptionThrown, "test8a: Exception thrown from hang() when plugin was terminated");
@@ -179,36 +181,36 @@ function test8a() {
 
 function test8() {
   window.frameLoaded = test8a;
   iframe.contentWindow.location.reload();
 }
 
 function test7a() {
   resetVars();
-  SpecialPowers.setIntPref(hangUITimeoutPref, 0);
+  Services.prefs.setIntPref(hangUITimeoutPref, 0);
   hanguiExpect("test7a: Plugin Hang UI is not showing (disabled)", false, false, "test8");
   var exceptionThrown = false;
   try {
     p.hang();
   } catch(e) {
     exceptionThrown = true;
   }
   ok(exceptionThrown, "test7a: Exception thrown from hang() when plugin was terminated");
 }
 
 function test7() {
   window.frameLoaded = test7a;
   iframe.contentWindow.location.reload();
 }
 
 function test6() {
-  SpecialPowers.setIntPref(hangUITimeoutPref, 1);
-  SpecialPowers.setIntPref(hangUIMinDisplayPref, 1);
-  SpecialPowers.setIntPref(timeoutPref, 3);
+  Services.prefs.setIntPref(hangUITimeoutPref, 1);
+  Services.prefs.setIntPref(hangUIMinDisplayPref, 1);
+  Services.prefs.setIntPref(timeoutPref, 3);
   hanguiExpect("test6: Plugin Hang UI is showing", true, true, "test7");
   var exceptionThrown = false;
   try {
     p.hang();
   } catch(e) {
     exceptionThrown = true;
   }
   ok(exceptionThrown, "test6: Exception thrown from hang() when plugin was terminated (child timeout)");
@@ -245,18 +247,18 @@ function test3() {
 function test2() {
   // This test is identical to test1 because there were some bugs where the
   // Hang UI would show on the first hang but not on subsequent hangs
   hanguiExpect("test2: Plugin Hang UI is showing", true, true, "test3");
   p.stall(STALL_DURATION);
 }
 
 function test1() {
-  SpecialPowers.setIntPref(hangUITimeoutPref, 1);
-  SpecialPowers.setIntPref(hangUIMinDisplayPref, 1);
-  SpecialPowers.setIntPref(timeoutPref, 45);
+  Services.prefs.setIntPref(hangUITimeoutPref, 1);
+  Services.prefs.setIntPref(hangUIMinDisplayPref, 1);
+  Services.prefs.setIntPref(timeoutPref, 45);
   hanguiExpect("test1: Plugin Hang UI is showing", true, true, "test2");
   p.stall(STALL_DURATION);
 }
 
 ]]>
 </script>
 </window>
--- a/dom/presentation/tests/mochitest/test_presentation_availability_iframe.html
+++ b/dom/presentation/tests/mochitest/test_presentation_availability_iframe.html
@@ -35,24 +35,24 @@ function testSetup() {
     });
     gScript.sendAsyncMessage("setup");
   });
 }
 
 function testInitialUnavailable() {
   request = new PresentationRequest("https://example.com");
 
-  return request.getAvailability().then(function(aAvailability) {
+  return request.getAvailability().then(async function(aAvailability) {
     is(aAvailability.value, false, "Should have no available device after setup");
     aAvailability.onchange = function() {
       aAvailability.onchange = null;
       ok(aAvailability.value, "Device should be available.");
     };
     availability = aAvailability;
-    gScript.sendAsyncMessage("trigger-device-add", testDevice);
+    await gScript.sendQuery("trigger-device-add", testDevice);
   }).catch(function(aError) {
     ok(false, "Error occurred when getting availability: " + aError);
     teardown();
   });
 }
 
 function testInitialAvailable() {
   let anotherRequest = new PresentationRequest("https://example.net");
--- a/dom/push/test/test_utils.js
+++ b/dom/push/test/test_utils.js
@@ -57,28 +57,28 @@ let currentMockSocket = null;
  * Sets up a mock connection for the WebSocket backend. This only replaces
  * the transport layer; `PushService.jsm` still handles DOM API requests,
  * observes permission changes, writes to IndexedDB, and notifies service
  * workers of incoming push messages.
  */
 function setupMockPushSocket(mockWebSocket) {
   currentMockSocket = mockWebSocket;
   currentMockSocket._isActive = true;
-  chromeScript.sendSyncMessage("socket-setup");
+  chromeScript.sendAsyncMessage("socket-setup");
   chromeScript.addMessageListener("socket-client-msg", function(msg) {
     mockWebSocket.handleMessage(msg);
   });
 }
 
 function teardownMockPushSocket() {
   if (currentMockSocket) {
     return new Promise(resolve => {
       currentMockSocket._isActive = false;
       chromeScript.addMessageListener("socket-server-teardown", resolve);
-      chromeScript.sendSyncMessage("socket-teardown");
+      chromeScript.sendAsyncMessage("socket-teardown");
     });
   }
   return Promise.resolve();
 }
 
 /**
  * Minimal implementation of web sockets for use in testing. Forwards
  * messages to a mock web socket in the parent process that is used
--- a/dom/security/test/mixedcontentblocker/test_frameNavigation.html
+++ b/dom/security/test/mixedcontentblocker/test_frameNavigation.html
@@ -28,17 +28,17 @@ https://bugzilla.mozilla.org/show_bug.cg
     blankTarget: false,
   };
 
   function log(msg) {
     document.getElementById("log").textContent += "\n" + msg;
   }
 
   var secureTestsStarted = false;
-  function checkTestsCompleted() {
+  async function checkTestsCompleted() {
     for (var prop in testsToRunInsecure) {
       // some test hasn't run yet so we're not done
       if (!testsToRunInsecure[prop])
         return;
     }
     // If we are here, all the insecure tests have run.
     // If we haven't changed the iframe to run the secure tests, change it now.
     if (!secureTestsStarted) {
@@ -55,17 +55,17 @@ https://bugzilla.mozilla.org/show_bug.cg
        for (var prop in testsToRunSecure) {
          testsToRunSecure[prop] = false;
        }
        for (var prop in testsToRunInsecure) {
          testsToRunInsecure[prop] = false;
        }
       //call to change the preferences
       counter++;
-      SpecialPowers.setBoolPref("security.mixed_content.block_active_content", false);
+      await SpecialPowers.setBoolPref("security.mixed_content.block_active_content", false);
       blockActive = SpecialPowers.getBoolPref("security.mixed_content.block_active_content");
       log("blockActive set to "+blockActive+".");
       secureTestsStarted = false;
       document.getElementById('framediv').innerHTML = '<iframe src="http://example.com/tests/dom/security/test/mixedcontentblocker/file_frameNavigation.html" id="testing_frame"></iframe>';
     }
     else {
       //set the prefs back to what they were set to originally
       SpecialPowers.setBoolPref("security.mixed_content.block_active_content", origBlockActive);
--- a/dom/serviceworkers/test/test_scopes.html
+++ b/dom/serviceworkers/test/test_scopes.html
@@ -59,78 +59,75 @@
 
     unregisterArray.push(navigator.serviceWorker.getRegistration("scope/").then(function (reg) {
       return reg.unregister();
     }));
 
     return Promise.all(unregisterArray);
   }
 
-  function testScopes() {
-    return new Promise(function(resolve, reject) {
-      function chromeScriptSource() {
-        let swm = Cc["@mozilla.org/serviceworkers/manager;1"]
-                    .getService(Ci.nsIServiceWorkerManager);
-        let secMan = Cc["@mozilla.org/scriptsecuritymanager;1"]
-                       .getService(Ci.nsIScriptSecurityManager);
-        addMessageListener("getScope", (msg) => {
-          let principal = secMan.createCodebasePrincipalFromOrigin(msg.principal);
-          try {
-            return { scope: swm.getScopeForUrl(principal, msg.path) };
-          } catch (e) {
-            return { exception: e.message };
-          }
-        });
-      }
+  async function testScopes() {
+    function chromeScriptSource() {
+      let swm = Cc["@mozilla.org/serviceworkers/manager;1"]
+                  .getService(Ci.nsIServiceWorkerManager);
+      let secMan = Cc["@mozilla.org/scriptsecuritymanager;1"]
+                     .getService(Ci.nsIScriptSecurityManager);
+      addMessageListener("getScope", (msg) => {
+        let principal = secMan.createCodebasePrincipalFromOrigin(msg.principal);
+        try {
+          return { scope: swm.getScopeForUrl(principal, msg.path) };
+        } catch (e) {
+          return { exception: e.message };
+        }
+      });
+    }
 
-      let getScope;
-      let parent_intercept_enabled =
-        SpecialPowers.getBoolPref("dom.serviceWorkers.parent_intercept");
+    let getScope;
+    let parent_intercept_enabled =
+      SpecialPowers.getBoolPref("dom.serviceWorkers.parent_intercept");
 
-      if (parent_intercept_enabled) {
-        let chromeScript = SpecialPowers.loadChromeScript(chromeScriptSource);
-        let docPrincipal = SpecialPowers.wrap(document).nodePrincipal.URI.spec;
+    if (parent_intercept_enabled) {
+      let chromeScript = SpecialPowers.loadChromeScript(chromeScriptSource);
+      let docPrincipal = SpecialPowers.wrap(document).nodePrincipal.URI.spec;
 
-        getScope = (path) => {
-          let rv = chromeScript.sendSyncMessage("getScope", { principal: docPrincipal, path })[0][0];
-          if (rv.exception)
-            throw rv.exception;
-          return rv.scope;
-        };
-      } else {
-        getScope = navigator.serviceWorker.getScopeForUrl.bind(navigator.serviceWorker);
-      }
+      getScope = async (path) => {
+        let rv = await chromeScript.sendQuery("getScope", { principal: docPrincipal, path });
+        if (rv.exception)
+          throw rv.exception;
+        return rv.scope;
+      };
+    } else {
+      getScope = navigator.serviceWorker.getScopeForUrl.bind(navigator.serviceWorker);
+    }
 
-      var base = new URL(".", document.baseURI);
+    var base = new URL(".", document.baseURI);
 
-      function p(s) {
-        return base + s;
-      }
+    function p(s) {
+      return base + s;
+    }
 
-      function fail(fn) {
-        try {
-          getScope(p("index.html"));
-          ok(false, "No registration");
-        } catch(e) {
-          ok(true, "No registration");
-        }
+    async function fail(fn) {
+      try {
+        await getScope(p("index.html"));
+        ok(false, "No registration");
+      } catch(e) {
+        ok(true, "No registration");
       }
+    }
 
-      is(getScope(p("sub.html")), p("sub"), "Scope should match");
-      is(getScope(p("sub/dir.html")), p("sub/dir.html"), "Scope should match");
-      is(getScope(p("sub/dir")), p("sub/dir"), "Scope should match");
-      is(getScope(p("sub/dir/foo")), p("sub/dir/"), "Scope should match");
-      is(getScope(p("sub/dir/afoo")), p("sub/dir/a"), "Scope should match");
-      is(getScope(p("star*wars")), p("star*"), "Scope should match");
-      is(getScope(p("scope/some_file.html")), p("scope/"), "Scope should match");
-      fail("index.html");
-      fail("sua.html");
-      fail("star/a.html");
-      resolve();
-    });
+    is(await getScope(p("sub.html")), p("sub"), "Scope should match");
+    is(await getScope(p("sub/dir.html")), p("sub/dir.html"), "Scope should match");
+    is(await getScope(p("sub/dir")), p("sub/dir"), "Scope should match");
+    is(await getScope(p("sub/dir/foo")), p("sub/dir/"), "Scope should match");
+    is(await getScope(p("sub/dir/afoo")), p("sub/dir/a"), "Scope should match");
+    is(await getScope(p("star*wars")), p("star*"), "Scope should match");
+    is(await getScope(p("scope/some_file.html")), p("scope/"), "Scope should match");
+    await fail("index.html");
+    await fail("sua.html");
+    await fail("star/a.html");
   }
 
   function runTest() {
     registerWorkers()
       .then(testScopes)
       .then(unregisterWorkers)
       .then(function() {
         SimpleTest.finish();
--- a/dom/tests/mochitest/bugs/test_navigator_buildID.html
+++ b/dom/tests/mochitest/bugs/test_navigator_buildID.html
@@ -42,23 +42,23 @@ is(+contentBuildID, LEGACY_BUILD_ID,
 let chromeScript = SpecialPowers.loadChromeScript(() => {
   const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
   addMessageListener("test:getBuildID", nav => {
     const browser = Services.wm.getMostRecentBrowserWindow();
     return browser.navigator.buildID;
   });
 });
 
-let chromeBuildID = chromeScript.sendSyncMessage("test:getBuildID")[0][0];
-chromeScript.destroy();
+async function onMozillaIFrameLoaded() {
+  let chromeBuildID = await chromeScript.sendQuery("test:getBuildID");
+  chromeScript.destroy();
 
-ok(+chromeBuildID > LEGACY_BUILD_ID,
-   `navigator.buildID should be exposed in chrome - got "${chromeBuildID}"`);
+  ok(+chromeBuildID > LEGACY_BUILD_ID,
+     `navigator.buildID should be exposed in chrome - got "${chromeBuildID}"`);
 
-function onMozillaIFrameLoaded() {
   //
   // Access navigator.buildID from mozilla.org.
   //
   let mozillaBuildID = getMozillaBuildID();
 
   ok(+mozillaBuildID > LEGACY_BUILD_ID,
      `navigator.buildID should be exposed on mozilla.org ` +
      `- got "${mozillaBuildID}"`);
--- a/dom/tests/mochitest/bugs/test_no_find_showDialog.html
+++ b/dom/tests/mochitest/bugs/test_no_find_showDialog.html
@@ -3,34 +3,34 @@
 <head>
   <meta charset="utf-8">
   <title>Test for Bug 1348409</title>
   <script src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <iframe src="about:blank"></iframe>
   <script type="text/javascript">
 
-  function checkForFindDialog() {
+  async function checkForFindDialog() {
     let chromeScript = SpecialPowers.loadChromeScript(_ => {
       addMessageListener("test:check", () => {
         const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
 
         let sawFind = false;
         let findDialog = Services.wm.getMostRecentWindow("findInPage");
         if (findDialog) {
           findDialog.close();
           sawFind = true;
         }
 
         return sawFind;
       });
 
     });
 
-    let sawFind = chromeScript.sendSyncMessage("test:check")[0][0];
+    let sawFind = await chromeScript.sendQuery("test:check");
     chromeScript.destroy();
     return sawFind;
   }
 
   function ensureFinished(chromeScript) {
     return new Promise(resolve => {
       chromeScript.addMessageListener("test:disarm:done", (sawWindow) => {
         resolve(sawWindow);
@@ -62,17 +62,17 @@
       ok(doWraparoundFind("fhqwhgads", true),
          "Should return true and not show a dialog if the string exists in the page.");
       ok(!doWraparoundFind(null, true),
          "Should return false and not show a dialog if we pass a null string.");
       ok(!doWraparoundFind("", true),
          "Should return false and not show a dialog if we pass an empty string.");
 
       // Double check to ensure that the parent didn't open a find dialog
-      let sawWindow = checkForFindDialog();
+      let sawWindow = await checkForFindDialog();
       ok(!sawWindow, "Should never have seen the dialog.");
     });
   }
   </script>
 </head>
 <body onload="startTest()">
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1348409">Mozilla Bug 1348409</a>
 
--- a/dom/tests/mochitest/chrome/file_DOM_element_instanceof.xul
+++ b/dom/tests/mochitest/chrome/file_DOM_element_instanceof.xul
@@ -6,17 +6,19 @@
   <iframe type="content"></iframe>
 
   <script type="application/javascript">
   <![CDATA[
   /** Test for Bug 799299 **/
   var SimpleTest = opener.wrappedJSObject.SimpleTest;
   var ok = opener.wrappedJSObject.ok;
 
-  var doc = frames[0].document;
+  // Note: We can't use frames[0] here because the type="content" attribute
+  // isolates it into a separate browsing context hierarchy.
+  var doc = document.querySelector("iframe").contentDocument;
   ok(doc.createElement("body") instanceof HTMLBodyElement,
      "Should be instance of HTMLBodyElement");
   ok(doc.createElement("div") instanceof HTMLDivElement,
      "Should be instance of HTMLDivElement");
   ok(doc.createElement("frameset") instanceof HTMLFrameSetElement,
      "Should be instance of HTMLFrameSetElement");
   ok(doc.createElement("h1") instanceof HTMLHeadingElement,
      "Should be instance of HTMLHeadingElement");
--- a/dom/tests/mochitest/dom-level0/test_background_loading_iframes.html
+++ b/dom/tests/mochitest/dom-level0/test_background_loading_iframes.html
@@ -29,17 +29,17 @@ window.addEventListener('message', funct
     SimpleTest.finish();
   }
 })
 
 window.onload = function() {
   myLoadTime = performance.now();
 }
 
-SpecialPowers.pushPrefEnv({"set":[["dom.background_loading_iframe", true]]}, function () {
+SpecialPowers.pushPrefEnv({"set":[["dom.background_loading_iframe", true]]}).then(function () {
   var iframe1 = document.createElement("iframe");
   var iframe2 = document.createElement("iframe");
   var iframe3 = document.createElement("iframe");
 
   iframe1.src = "http://example.org:80/tests/dom/tests/mochitest/dom-level0/file_test_background_loading_iframes.html";
   iframe2.src = "http://example.org:80/tests/dom/tests/mochitest/dom-level0/file_test_background_loading_iframes.html";
   iframe3.src = "http://example.org:80/tests/dom/tests/mochitest/dom-level0/file_test_background_loading_iframes.html";
 
--- a/dom/tests/mochitest/gamepad/mock_gamepad.js
+++ b/dom/tests/mochitest/gamepad/mock_gamepad.js
@@ -1,17 +1,17 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 var GamepadService;
 
-function setGamepadPreferenceAndCreateIframe(iframeSrc) {
-  SpecialPowers.pushPrefEnv({"set" : [["dom.gamepad.test.enabled", true]]}, () => {
-    let iframe = document.createElement("iframe");
-    iframe.src = iframeSrc;
-    document.body.appendChild(iframe);
-  });
+async function setGamepadPreferenceAndCreateIframe(iframeSrc) {
+  await SpecialPowers.pushPrefEnv({"set" : [["dom.gamepad.test.enabled", true]]});
+
+  let iframe = document.createElement("iframe");
+  iframe.src = iframeSrc;
+  document.body.appendChild(iframe);
 }
 
 function runGamepadTest (callback) {
   GamepadService = navigator.requestGamepadServiceTest();
   callback();
 }
--- a/dom/tests/mochitest/gamepad/test_gamepad_extensions_iframe.html
+++ b/dom/tests/mochitest/gamepad/test_gamepad_extensions_iframe.html
@@ -38,21 +38,21 @@ window.addEventListener("gamepadbuttondo
   SpecialPowers.executeSoon(tests[testNum++]);
 });
 
 function pressButton() {
   GamepadService.newButtonEvent(gamepad_index, 0, true, true);
   GamepadService.newButtonEvent(gamepad_index, 0, false, false);
 }
 
-function startTest() {
-  SpecialPowers.pushPrefEnv({ "set": [
-                              ["dom.gamepad.extensions.enabled", true],
-                              ["dom.gamepad.extensions.lightindicator", true],
-                              ["dom.gamepad.extensions.multitouch", true]] });
+async function startTest() {
+  await SpecialPowers.pushPrefEnv({ "set": [
+                                    ["dom.gamepad.extensions.enabled", true],
+                                    ["dom.gamepad.extensions.lightindicator", true],
+                                    ["dom.gamepad.extensions.multitouch", true]] });
   // Add a gamepad
   GamepadService.addGamepad("test gamepad", // id
                      GamepadService.standardMapping,
                      GamepadService.leftHand,
                      4,
                      2,
                      1,
                      1,
--- a/dom/tests/mochitest/gamepad/test_gamepad_multitouch_crossorigin_iframe.html
+++ b/dom/tests/mochitest/gamepad/test_gamepad_multitouch_crossorigin_iframe.html
@@ -47,23 +47,24 @@ window.addEventListener("gamepadbuttondo
 });
 
 function pressButton() {
   GamepadService.newButtonEvent(gamepad_index, 0, true, true);
   GamepadService.newButtonEvent(gamepad_index, 0, false, false);
 }
 
 let frames_loaded = 0;
-function startTest() {
+async function startTest() {
   frames_loaded++;
-  SpecialPowers.pushPrefEnv({ "set": [
+  let promise = SpecialPowers.pushPrefEnv({ "set": [
                               ["dom.gamepad.extensions.enabled", true],
                               ["dom.gamepad.extensions.lightindicator", true],
                               ["dom.gamepad.extensions.multitouch", true]] });
   if (frames_loaded == 2) {
+  await promise;
   // Add a gamepad
   GamepadService.addGamepad("test gamepad", // id
                       GamepadService.standardMapping,
                       GamepadService.leftHand,
                       4,
                       2,
                       1,
                       1,
--- a/editor/libeditor/tests/test_bug569988.html
+++ b/editor/libeditor/tests/test_bug569988.html
@@ -88,17 +88,17 @@ function runTest() {
   });
   script.addMessageListener("ok", ([val, msg]) => ok(val, msg));
   script.addMessageListener("info", msg => info(msg));
 
   info("opening prompt...");
   prompt("summary", "text");
   info("prompt is closed");
 
-  script.sendSyncMessage("destroy");
+  script.sendAsyncMessage("destroy");
 
   SimpleTest.finish();
 }
 
 </script>
 </pre>
 </body>
 </html>
--- a/editor/libeditor/tests/test_selection_move_commands.html
+++ b/editor/libeditor/tests/test_selection_move_commands.html
@@ -12,17 +12,17 @@ SimpleTest.requestFlakyTimeout("Legacy t
 
 var winUtils = SpecialPowers.getDOMWindowUtils(window);
 
 async function setup() {
   await SpecialPowers.pushPrefEnv({set: [["general.smoothScroll", false]]});
   winUtils.advanceTimeAndRefresh(100);
 }
 
-function* runTests() {
+async function* runTests() {
   var e = document.getElementById("edit");
   var doc = e.contentDocument;
   var win = e.contentWindow;
   var root = doc.documentElement;
   var body = doc.body;
 
   body.style.fontSize = "16px";
   body.style.lineHeight = "16px";
@@ -176,32 +176,32 @@ function* runTests() {
 
     SpecialPowers.doCommand(window, "cmd_moveTop");
     is(testPageSelectCommand("cmd_selectPageDown", 0), lineNum, "cmd_selectPageDown");
     SpecialPowers.doCommand(window, "cmd_moveBottom");
     SpecialPowers.doCommand(window, "cmd_beginLine");
     is(testPageSelectCommand("cmd_selectPageUp", 0), 22 - lineNum, "cmd_selectPageUp");
   };
 
-  yield SpecialPowers.pushPrefEnv({set: [["layout.word_select.eat_space_to_next_word", false]]});
+  await SpecialPowers.pushPrefEnv({set: [["layout.word_select.eat_space_to_next_word", false]]});
   runSelectionTests(body, 1);
-  yield SpecialPowers.pushPrefEnv({set: [["layout.word_select.eat_space_to_next_word", true]]});
+  await SpecialPowers.pushPrefEnv({set: [["layout.word_select.eat_space_to_next_word", true]]});
   runSelectionTests(node(2), 0);
 }
 
 function cleanup() {
   winUtils.restoreNormalRefresh();
   SimpleTest.finish();
 }
 
 async function testRunner() {
   let curTest = runTests();
   while (true) {
     winUtils.advanceTimeAndRefresh(100);
-    if (curTest.next().done) {
+    if ((await curTest.next()).done) {
       break;
     }
     winUtils.advanceTimeAndRefresh(100);
     await new Promise(resolve => setTimeout(resolve, 20));
   }
 }
 
 setup()
--- a/editor/spellchecker/tests/test_bug1200533.html
+++ b/editor/spellchecker/tests/test_bug1200533.html
@@ -45,17 +45,17 @@ var tests = [
     [ "en-generic",      "de-DE", "*" ],
     // Result: Preference value.
     [ "ko-not-avail",    "de-DE", "de-DE" ],
   ];
 
 var loadCount = 0;
 var script;
 
-var loadListener = function(evt) {
+var loadListener = async function(evt) {
   if (loadCount == 0) {
     /* eslint-env mozilla/frame-script */
     script = SpecialPowers.loadChromeScript(function() {
       // eslint-disable-next-line mozilla/use-services
       var dir = Cc["@mozilla.org/file/directory_service;1"]
                   .getService(Ci.nsIProperties)
                   .get("CurWorkD", Ci.nsIFile);
       dir.append("tests");
@@ -81,17 +81,17 @@ var loadListener = function(evt) {
                          () => [en_GB.exists(), en_AU.exists(),
                                 de_DE.exists()]);
       addMessageListener("destroy", () => {
         hunspell.removeDirectory(en_GB);
         hunspell.removeDirectory(en_AU);
         hunspell.removeDirectory(de_DE);
       });
     });
-    var existenceChecks = script.sendSyncMessage("check-existence")[0][0];
+    var existenceChecks = await script.sendQuery("check-existence");
     is(existenceChecks[0], true, "true expected (en-GB directory should exist)");
     is(existenceChecks[1], true, "true expected (en-AU directory should exist)");
     is(existenceChecks[2], true, "true expected (de-DE directory should exist)");
   }
 
   SpecialPowers.pushPrefEnv({set: [["spellchecker.dictionary", tests[loadCount][1]]]},
                             function() { continueTest(evt); });
 };
@@ -119,17 +119,17 @@ function continueTest(evt) {
     }
 
     loadCount++;
     if (loadCount < tests.length) {
       // Load the iframe again.
       content.src = "http://mochi.test:8888/tests/editor/spellchecker/tests/bug1200533_subframe.html?firstload=false";
     } else {
       // Remove the fake  dictionaries again, since it's otherwise picked up by later tests.
-      script.sendSyncMessage("destroy");
+      script.sendAsyncMessage("destroy");
 
       SimpleTest.finish();
     }
   });
 }
 
 content.addEventListener("load", loadListener);
 
--- a/editor/spellchecker/tests/test_bug1204147.html
+++ b/editor/spellchecker/tests/test_bug1204147.html
@@ -24,17 +24,17 @@ var content = document.getElementById("c
 // load, it will set the dictionary to "en-GB". The bug was that a content preference
 // was also created. At second load, we check the dictionary for another element,
 // one that should use "en-US". With the bug corrected, we get "en-US", before
 // we got "en-GB" from the content preference.
 
 var firstLoad = true;
 var script;
 
-var loadListener = function(evt) {
+var loadListener = async function(evt) {
   if (firstLoad) {
     /* eslint-env mozilla/frame-script */
     script = SpecialPowers.loadChromeScript(function() {
       // eslint-disable-next-line mozilla/use-services
       var dir = Cc["@mozilla.org/file/directory_service;1"]
                   .getService(Ci.nsIProperties)
                   .get("CurWorkD", Ci.nsIFile);
       dir.append("tests");
@@ -48,17 +48,17 @@ var loadListener = function(evt) {
       // Install en-GB dictionary.
       let en_GB = dir.clone();
       en_GB.append("en-GB");
       hunspell.addDirectory(en_GB);
 
       addMessageListener("en_GB-exists", () => en_GB.exists());
       addMessageListener("destroy", () => hunspell.removeDirectory(en_GB));
     });
-    is(script.sendSyncMessage("en_GB-exists")[0][0], true,
+    is(await script.sendQuery("en_GB-exists"), true,
        "true expected (en-GB directory should exist)");
   }
 
   var doc = evt.target.contentDocument;
   var elem;
   if (firstLoad) {
     elem = doc.getElementById("en-GB");
   } else {
@@ -92,17 +92,17 @@ var loadListener = function(evt) {
       content.src = "http://mochi.test:8888/tests/editor/spellchecker/tests/bug1204147_subframe2.html?firstload=false";
     } else {
       // Second time around, the element should default to en-US.
       // Without the fix, the first run sets the content preference to en-GB for the whole site.
       is(currentDictonary, "en-US", "unexpected lang " + currentDictonary + " instead of en-US");
       content.removeEventListener("load", loadListener);
 
       // Remove the fake en-GB dictionary again, since it's otherwise picked up by later tests.
-      script.sendSyncMessage("destroy");
+      script.sendAsyncMessage("destroy");
 
       // Reset the preference, so the last value we set doesn't collide with the next test.
       SimpleTest.finish();
     }
   });
 };
 
 content.addEventListener("load", loadListener);
--- a/editor/spellchecker/tests/test_bug1205983.html
+++ b/editor/spellchecker/tests/test_bug1205983.html
@@ -29,17 +29,17 @@ var selcon_de;
 var script;
 
 var onSpellCheck =
   SpecialPowers.Cu.import(
     "resource://testing-common/AsyncSpellCheckTestHelper.jsm").onSpellCheck;
 
 /** Test for Bug 1205983 **/
 SimpleTest.waitForExplicitFinish();
-SimpleTest.waitForFocus(function() {
+SimpleTest.waitForFocus(async function() {
   /* eslint-env mozilla/frame-script */
   script = SpecialPowers.loadChromeScript(function() {
     // eslint-disable-next-line mozilla/use-services
     var dir = Cc["@mozilla.org/file/directory_service;1"]
                 .getService(Ci.nsIProperties)
                 .get("CurWorkD", Ci.nsIFile);
     dir.append("tests");
     dir.append("editor");
@@ -52,17 +52,17 @@ SimpleTest.waitForFocus(function() {
     // Install de-DE dictionary.
     var de_DE = dir.clone();
     de_DE.append("de-DE");
     hunspell.addDirectory(de_DE);
 
     addMessageListener("de_DE-exists", () => de_DE.exists());
     addMessageListener("destroy", () => hunspell.removeDirectory(de_DE));
   });
-  is(script.sendSyncMessage("de_DE-exists")[0][0], true,
+  is(await script.sendQuery("de_DE-exists"), true,
      "true expected (de_DE directory should exist)");
 
   document.getElementById("de-DE").focus();
 });
 
 function deFocus() {
   elem_de = document.getElementById("de-DE");
 
@@ -101,17 +101,17 @@ function enFocus() {
 
     // So far all was boring. The important thing is whether the spell check result
     // in the de-DE editor is still the same. After losing focus, no spell check
     // updates should take place there.
     var sel = selcon_de.getSelection(selcon_de.SELECTION_SPELLCHECK);
     is(sel.toString(), "German", "one misspelled word expected: German");
 
     // Remove the fake de_DE dictionary again.
-    script.sendSyncMessage("destroy");
+    script.sendAsyncMessage("destroy");
 
     // Focus again, so the spelling gets updated, but before we need to kill the focus handler.
     elem_de.onfocus = null;
     elem_de.blur();
     elem_de.focus();
 
     // After removal, the de_DE editor should refresh the spelling with en-US.
     onSpellCheck(elem_de, function() {
--- a/editor/spellchecker/tests/test_bug1209414.html
+++ b/editor/spellchecker/tests/test_bug1209414.html
@@ -36,23 +36,23 @@ var script;
  * user behaviour.
  */
 
 var onSpellCheck =
   SpecialPowers.Cu.import(
     "resource://testing-common/AsyncSpellCheckTestHelper.jsm").onSpellCheck;
 
 SimpleTest.waitForExplicitFinish();
-SimpleTest.waitForFocus(function() {
-  /* global browserElement */
+SimpleTest.waitForFocus(async function() {
+  /* global actorParent */
   /* eslint-env mozilla/frame-script */
   script = SpecialPowers.loadChromeScript(function() {
-    var chromeWin = browserElement.ownerGlobal.docShell
-                    .rootTreeItem.domWindow
-                    .QueryInterface(Ci.nsIDOMChromeWindow);
+    var chromeWin = actorParent.rootFrameLoader
+                    .ownerElement.ownerGlobal.docShell
+                    .rootTreeItem.domWindow;
     var contextMenu = chromeWin.document.getElementById("contentAreaContextMenu");
     contextMenu.addEventListener("popupshown",
                                  () => sendAsyncMessage("popupshown"));
 
     // eslint-disable-next-line mozilla/use-services
     var dir = Cc["@mozilla.org/file/directory_service;1"]
                 .getService(Ci.nsIProperties)
                 .get("CurWorkD", Ci.nsIFile);
@@ -80,19 +80,19 @@ SimpleTest.waitForFocus(function() {
       contextMenu.hidePopup();
 
       return state;
     });
     addMessageListener("destroy", () => hunspell.removeDirectory(de_DE));
     addMessageListener("contextMenu-not-null", () => contextMenu != null);
     addMessageListener("de_DE-exists", () => de_DE.exists());
   });
-  is(script.sendSyncMessage("contextMenu-not-null")[0][0], true,
+  is(await script.sendQuery("contextMenu-not-null"), true,
      "Got context menu XUL");
-  is(script.sendSyncMessage("de_DE-exists")[0][0], true,
+  is(await script.sendQuery("de_DE-exists"), true,
      "true expected (de_DE directory should exist)");
   script.addMessageListener("popupshown", handlePopup);
 
   elem_de = document.getElementById("de-DE");
   editor_de = SpecialPowers.wrap(elem_de).editor;
   editor_de.setSpellcheckUserOverride(true);
 
   onSpellCheck(elem_de, function() {
@@ -112,35 +112,35 @@ SimpleTest.waitForFocus(function() {
 
     // Make sure all spell checking action is done before right-click to select the en-US dictionary.
     onSpellCheck(elem_de, function() {
       synthesizeMouse(elem_de, 2, 2, { type: "contextmenu", button: 2 }, window);
     });
   });
 });
 
-function handlePopup() {
-  var state = script.sendSyncMessage("hidepopup")[0][0];
+async function handlePopup() {
+  var state = await script.sendQuery("hidepopup");
   is(state, "open", "checking if popup is open");
 
   onSpellCheck(elem_de, function() {
     var inlineSpellChecker = editor_de.getInlineSpellChecker(true);
     var spellchecker = inlineSpellChecker.spellChecker;
     let currentDictonary;
     try {
       currentDictonary = spellchecker.GetCurrentDictionary();
     } catch (e) {}
 
     // Check that the English dictionary is loaded and that the spell check has worked.
     is(currentDictonary, "en-US", "expected en-US");
     // eslint-disable-next-line no-useless-concat
     is(getMisspelledWords(editor_de), "heute" + "ist" + "ein" + "guter", "some misspelled words expected: heute ist ein guter");
 
     // Remove the fake de_DE dictionary again.
-    script.sendSyncMessage("destroy");
+    script.sendAsyncMessage("destroy");
 
     // This will clear the content preferences and reset "spellchecker.dictionary".
     spellchecker.SetCurrentDictionary("");
     SimpleTest.finish();
   });
 }
 
 </script>
--- a/editor/spellchecker/tests/test_bug678842.html
+++ b/editor/spellchecker/tests/test_bug678842.html
@@ -23,17 +23,17 @@ var content = document.getElementById("c
 // load a subframe containing an editor with a defined unknown lang. At first
 // load, it will set dictionary to en-US. At second load, it will return current
 // dictionary. So, we can check, dictionary is correctly remembered between
 // loads.
 
 var firstLoad = true;
 var script;
 
-var loadListener = function(evt) {
+var loadListener = async function(evt) {
   if (firstLoad) {
     /* eslint-env mozilla/frame-script */
     script = SpecialPowers.loadChromeScript(function() {
       // eslint-disable-next-line mozilla/use-services
       var dir = Cc["@mozilla.org/file/directory_service;1"]
                   .getService(Ci.nsIProperties)
                   .get("CurWorkD", Ci.nsIFile);
       dir.append("tests");
@@ -47,17 +47,17 @@ var loadListener = function(evt) {
       // Install en-GB dictionary.
       let en_GB = dir.clone();
       en_GB.append("en-GB");
       hunspell.addDirectory(en_GB);
 
       addMessageListener("en_GB-exists", () => en_GB.exists());
       addMessageListener("destroy", () => hunspell.removeDirectory(en_GB));
     });
-    is(script.sendSyncMessage("en_GB-exists")[0][0], true,
+    is(await script.sendQuery("en_GB-exists"), true,
        "true expected (en-GB directory should exist)");
   }
 
   var doc = evt.target.contentDocument;
   var elem = doc.getElementById("textarea");
   var editor = SpecialPowers.wrap(elem).editor;
   editor.setSpellcheckUserOverride(true);
   var inlineSpellChecker = editor.getInlineSpellChecker(true);
@@ -84,17 +84,17 @@ var loadListener = function(evt) {
       spellchecker.SetCurrentDictionary("en-GB");
 
       content.src = "http://mochi.test:8888/tests/editor/spellchecker/tests/bug678842_subframe.html?firstload=false";
     } else {
       is(currentDictonary, "en-GB", "unexpected lang " + currentDictonary + " instead of en-GB");
       content.removeEventListener("load", loadListener);
 
       // Remove the fake en-GB dictionary again, since it's otherwise picked up by later tests.
-      script.sendSyncMessage("destroy");
+      script.sendAsyncMessage("destroy");
 
       // This will clear the content preferences and reset "spellchecker.dictionary".
       spellchecker.SetCurrentDictionary("");
       SimpleTest.finish();
     }
   });
 };
 
--- a/editor/spellchecker/tests/test_bug697981.html
+++ b/editor/spellchecker/tests/test_bug697981.html
@@ -29,17 +29,17 @@ var script;
 
 var onSpellCheck =
   SpecialPowers.Cu.import(
     "resource://testing-common/AsyncSpellCheckTestHelper.jsm")
                .onSpellCheck;
 
 /** Test for Bug 697981 **/
 SimpleTest.waitForExplicitFinish();
-SimpleTest.waitForFocus(function() {
+SimpleTest.waitForFocus(async function() {
   /* eslint-env mozilla/frame-script */
   script = SpecialPowers.loadChromeScript(function() {
     // eslint-disable-next-line mozilla/use-services
     var dir = Cc["@mozilla.org/file/directory_service;1"]
                 .getService(Ci.nsIProperties)
                 .get("CurWorkD", Ci.nsIFile);
     dir.append("tests");
     dir.append("editor");
@@ -52,17 +52,17 @@ SimpleTest.waitForFocus(function() {
     // Install de-DE dictionary.
     var de_DE = dir.clone();
     de_DE.append("de-DE");
     hunspell.addDirectory(de_DE);
 
     addMessageListener("de_DE-exists", () => de_DE.exists());
     addMessageListener("destroy", () => hunspell.removeDirectory(de_DE));
   });
-  is(script.sendSyncMessage("de_DE-exists")[0][0], true,
+  is(await script.sendQuery("de_DE-exists"), true,
      "true expected (de_DE directory should exist)");
 
   document.getElementById("de-DE").focus();
 });
 
 function deFocus() {
   elem_de = document.getElementById("de-DE");
   editor_de = SpecialPowers.wrap(elem_de).editor;
@@ -102,17 +102,17 @@ function enFocus() {
     is(getMisspelledWords(editor_en), "Nogoodword", "one misspelled word expected: Nogoodword");
 
     // So far all was boring. The important thing is whether the spell check result
     // in the de-DE editor is still the same. After losing focus, no spell check
     // updates should take place there.
     is(getMisspelledWords(editor_de), "German", "one misspelled word expected: German");
 
     // Remove the fake de_DE dictionary again.
-    script.sendSyncMessage("destroy");
+    script.sendAsyncMessage("destroy");
 
     // Focus again, so the spelling gets updated, but before we need to kill the focus handler.
     elem_de.onfocus = null;
     elem_de.blur();
     elem_de.focus();
 
     // After removal, the de_DE editor should refresh the spelling with en-US.
     onSpellCheck(elem_de, function() {
--- a/editor/spellchecker/tests/test_bug717433.html
+++ b/editor/spellchecker/tests/test_bug717433.html
@@ -24,17 +24,17 @@ var content = document.getElementById("c
 // load, it will set the dictionary to en-GB or en-US. We set the other one.
 // At second load, it will return the current dictionary. We can check that the
 // dictionary is correctly remembered between loads.
 
 var firstLoad = true;
 var expected = "";
 var script;
 
-var loadListener = function(evt) {
+var loadListener = async function(evt) {
   if (firstLoad) {
     /* eslint-env mozilla/frame-script */
     script = SpecialPowers.loadChromeScript(function() {
       // eslint-disable-next-line mozilla/use-services
       var dir = Cc["@mozilla.org/file/directory_service;1"]
                   .getService(Ci.nsIProperties)
                   .get("CurWorkD", Ci.nsIFile);
       dir.append("tests");
@@ -48,17 +48,17 @@ var loadListener = function(evt) {
       // Install en-GB dictionary.
       var en_GB = dir.clone();
       en_GB.append("en-GB");
       hunspell.addDirectory(en_GB);
 
       addMessageListener("en_GB-exists", () => en_GB.exists());
       addMessageListener("destroy", () => hunspell.removeDirectory(en_GB));
     });
-    is(script.sendSyncMessage("en_GB-exists")[0][0], true,
+    is(await script.sendQuery("en_GB-exists"), true,
        "true expected (en-GB directory should exist)");
   }
 
   var doc = evt.target.contentDocument;
   var elem = doc.getElementById("textarea");
   var editor = SpecialPowers.wrap(elem).editor;
   editor.setSpellcheckUserOverride(true);
   var inlineSpellChecker = editor.getInlineSpellChecker(true);
@@ -85,17 +85,17 @@ var loadListener = function(evt) {
         is(true, false, "Neither en-US nor en-GB are current");
       }
       content.src = "http://mochi.test:8888/tests/editor/spellchecker/tests/bug717433_subframe.html?firstload=false";
     } else {
       is(currentDictonary, expected, expected + " expected");
       content.removeEventListener("load", loadListener);
 
       // Remove the fake en-GB dictionary again, since it's otherwise picked up by later tests.
-      script.sendSyncMessage("destroy");
+      script.sendAsyncMessage("destroy");
 
       // This will clear the content preferences and reset "spellchecker.dictionary".
       spellchecker.SetCurrentDictionary("");
       SimpleTest.finish();
     }
   });
 };
 
--- a/gfx/docs/DocumentSplitting.rst
+++ b/gfx/docs/DocumentSplitting.rst
@@ -118,29 +118,29 @@ are also in the UI process. Consider thi
       - display item P
         - display item A
         - display item B (flagged as being in the content renderroot)
         - display item C
 
 If item P was a filter, for example, that would normally apply to all of items
 A, B, and C. This would mean either sharing the filter between the "chrome" renderroot
 and the "content" renderroot, or duplicating it such that it existed in both
-renderroots. The sharing is not possibly as it violates the independence of WR
+renderroots. The sharing is not possible as it violates the independence of WR
 documents. The duplication is technically possible, but could result in visual
 glitches as the two documents would be processed and composited separately.
 
 In order to avoid this problem, the design of document splitting explicitly assumes
 that such a scenario will not happen. In particular, the only information that
 gets carried across the render root boundary is the positioning offset. Any
 filters, transforms that are not 2D axis-aligned, opacity, or mix blend mode
 properties do NOT get carried across the render root boundary. Similarly, a
 scrollframe may not contain content from multiple render roots, because that
 would lead to a similar problem in APZ where it would have to update the scroll
 position of scrollframes in multiple documents and they might get composited
-at separate times resulting in visual glitches.
+at separate times, resulting in visual glitches.
 
 Security Concerns
 -----------------
 
 On the content side, all of the document splitting work happens in the UI process.
 In other words, content processes don't generally know what document they are part
 of, and don't ever split their display lists into multiple documents. Only the UI
 process ever sends multiple display lists to the compositor side.
@@ -220,17 +220,17 @@ corresponding queue. The catch is that o
 element or scrollframe on the page, and so when the message is sent from the
 UI process, we need to do a DOM or frame tree walk to determine which render root
 that element is associated with. There are some `GetRenderRootForXXX
 <https://searchfox.org/mozilla-central/rev/06bd14ced96f25ff1dbd5352cb985fc0fa12a64e/gfx/thebes/gfxUtils.h#317-322>`_
 helpers in gfxUtils that assist with this task.
 
 The other catch is that an APZ message may be associated with multiple documents.
 A concrete example is if a user on a touch device does a multitouch action with
-one fingers landing on different documents, which would trigger a call to
+different fingers landing on different documents, which would trigger a call to
 `RecvSetTargetAPZC
 <https://searchfox.org/mozilla-central/rev/06bd14ced96f25ff1dbd5352cb985fc0fa12a64e/gfx/layers/ipc/APZCTreeManagerParent.cpp#76>`_
 with multiple targets, each potentially belonging to a different render root.
 In this case, we need to ensure that the message only gets processed after
 the corresponding scene swaps for all the related documents. This is currently
 implemented by having each message in the queue associated with a set of documents
 rather than a single document, and only processing the message once all the
 documents have done their scene swap. In the example above, this is indicated by
@@ -238,29 +238,29 @@ building the set of render roots `here
 <https://searchfox.org/mozilla-central/rev/06bd14ced96f25ff1dbd5352cb985fc0fa12a64e/gfx/layers/ipc/APZCTreeManagerParent.cpp#83>`_
 and passing that to the updater queue when queueing the message. This interaction
 is a source of some complexity and may have latent bugs.
 
 Deferred updates
 ~~~~~~~~~~~~~~~~
 
 Bug 1547351 provided a new and tricky problem where a content process is rendering
-stuff that needs go into the "default" document because it's actually an
+stuff that needs to go into the "default" document because it's actually an
 out-of-process addon content that renders in the chrome area. Prior to this bug,
 the WebRenderBridgeParent instances that corresponded to content processes
 (hereafter referred to as "sub-WRBPs", in contrast to the "root WRBP" that
 corresponds to the UI process) simply assumed they were in the "Content" document,
 but this bug proved that this simplistic assumption does not always hold.
 
 The solution chosen to this problem was to have the root WebRenderLayerManager
 (that lives in the trusted UI process) to annotate each out-of-process subpipeline
 with the render root it belongs in, and send that information over to the
 root WRBP as part of the display list transaction. The sub-WRBPs know their own
 pipeline ids, and therefore can find their render root by querying the root WRBP.
-The catch is that sub-WRBPs may receive display list transactions before the
+The catch is that sub-WRBPs may receive display list transactions *before* the
 root WRBP receives the display list update that contains the render root mapping
 information. This happens in cases like during tab switch preload, where the
 user mouses over a background tab, and we pre-render it (i.e. compute and send
 the display list for that tab to the compositor) so that the tab switch is faster.
 In this scenario, that display list/subpipeline is not actually rendered, is not
 tied in to the display list of the UI process, and therefore doesn't get associated
 with a render root.
 
@@ -305,28 +305,28 @@ With document splitting, a transaction s
 and sent to multiple WR documents, each of which are operating independently of
 each other. If we propagate the transaction id to each of those WR documents,
 then the first document to complete its work would trigger the "transaction complete"
 message back to the content, which would unthrottle the next transaction. In this
 scenario, other documents may still be backlogged, so the unthrottling is
 undesirable.
 
 Instead, what we want is for all documents processing a particular transaction
-id to finish their word and render before we send the completion message back
+id to finish their work and render before we send the completion message back
 to content. In fact, there's a bunch of work that falls into the same category
 as this completion message - stuff that should happen after all the WR documents
 are done processing their pieces of the split transaction.
 
 The way this is managed is via a conditional in `HandleFrame
 <https://searchfox.org/mozilla-central/rev/06bd14ced96f25ff1dbd5352cb985fc0fa12a64e/gfx/webrender_bindings/RenderThread.cpp#988>`_.
 This code is invoked once for each document as it advances to the rendering step,
 and the code in `RenderThread::IncRenderingFrameCount
 <https://searchfox.org/mozilla-central/rev/06bd14ced96f25ff1dbd5352cb985fc0fa12a64e/gfx/webrender_bindings/RenderThread.cpp#552-553>`_
 acts as a barrier to ensure that the call chain only gets propagated once all
 the documents have done their processing work.
 
 I'm listing this piece as a potential source of complexity for document splitting
-because it seems like a fairly important piece but the relevant code that is
-"buried" away in place where one might not easily stumble upon it. It's also not
+because it seems like a fairly important piece but the relevant code is
+"buried" away in a place where one might not easily stumble upon it. It's also not
 clear to me that the implications of this problem and solution have been fully
 explored. In particular, I assume that there are latent bugs here because other
 pieces of code were assuming a certain behaviour from the pre-document-splitting
 code that the post-document-splitting code may not satisfy exactly.
--- a/gfx/layers/apz/test/mochitest/apz_test_utils.js
+++ b/gfx/layers/apz/test/mochitest/apz_test_utils.js
@@ -417,17 +417,17 @@ async function waitUntilApzStable() {
       addMessageListener("apz-flush", apzFlush);
       addMessageListener("cleanup", cleanup);
     }
 
     // This is the first time waitUntilApzStable is being called, do initialization
     if (typeof waitUntilApzStable.chromeHelper == "undefined") {
       waitUntilApzStable.chromeHelper = SpecialPowers.loadChromeScript(parentProcessFlush);
       ApzCleanup.register(() => {
-        waitUntilApzStable.chromeHelper.sendSyncMessage("cleanup", null);
+        waitUntilApzStable.chromeHelper.sendAsyncMessage("cleanup", null);
         waitUntilApzStable.chromeHelper.destroy();
         delete waitUntilApzStable.chromeHelper;
       });
     }
 
     // Actually trigger the parent-process flush and wait for it to finish
     waitUntilApzStable.chromeHelper.sendAsyncMessage("apz-flush", null);
     await waitUntilApzStable.chromeHelper.promiseOneMessage("apz-flush-done");
@@ -575,17 +575,17 @@ function getSnapshot(rect) {
   }
 
   if (typeof getSnapshot.chromeHelper == "undefined") {
     // This is the first time getSnapshot is being called; do initialization
     getSnapshot.chromeHelper = SpecialPowers.loadChromeScript(parentProcessSnapshot);
     ApzCleanup.register(function() { getSnapshot.chromeHelper.destroy(); });
   }
 
-  return getSnapshot.chromeHelper.sendSyncMessage("snapshot", JSON.stringify(rect)).toString();
+  return getSnapshot.chromeHelper.sendQuery("snapshot", JSON.stringify(rect));
 }
 
 // Takes the document's query string and parses it, assuming the query string
 // is composed of key-value pairs where the value is in JSON format. The object
 // returned contains the various values indexed by their respective keys. In
 // case of duplicate keys, the last value be used.
 // Examples:
 //   ?key="value"&key2=false&key3=500
--- a/gfx/layers/apz/test/mochitest/helper_touch_action_regions.html
+++ b/gfx/layers/apz/test/mochitest/helper_touch_action_regions.html
@@ -28,64 +28,75 @@ function listener(callback) {
 // This returns true on success, and false on failure.
 // The 'report' invocation can be invoked multiple times, and returns an object
 // (in JSON string format) containing the counters.
 // The 'end' invocation tears down the listeners, and should be invoked once
 // at the end to clean up. Returns true on success, false on failure.
 /* eslint-env mozilla/frame-script */
 function chromeTouchEventCounter(operation) {
   function chromeProcessCounter() {
-    addMessageListener("start", function() {
-      const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
-      var topWin = Services.wm.getMostRecentWindow("navigator:browser");
-      if (typeof topWin.eventCounts != "undefined") {
-        dump("Found pre-existing eventCounts object on the top window!\n");
-        return false;
-      }
-      topWin.eventCounts = { "touchstart": 0, "touchmove": 0, "touchend": 0 };
-      topWin.counter = function(e) {
-        topWin.eventCounts[e.type]++;
-      };
+    const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+    const PREFIX = "apz:ctec:";
 
-      topWin.addEventListener("touchstart", topWin.counter, { passive: true });
-      topWin.addEventListener("touchmove", topWin.counter, { passive: true });
-      topWin.addEventListener("touchend", topWin.counter, { passive: true });
+    const LISTENERS = {
+      "start": function() {
+        var topWin = Services.wm.getMostRecentWindow("navigator:browser");
+        if (typeof topWin.eventCounts != "undefined") {
+          dump("Found pre-existing eventCounts object on the top window!\n");
+          return false;
+        }
+        topWin.eventCounts = { "touchstart": 0, "touchmove": 0, "touchend": 0 };
+        topWin.counter = function(e) {
+          topWin.eventCounts[e.type]++;
+        };
 
-      return true;
-    });
+        topWin.addEventListener("touchstart", topWin.counter, { passive: true });
+        topWin.addEventListener("touchmove", topWin.counter, { passive: true });
+        topWin.addEventListener("touchend", topWin.counter, { passive: true });
+
+        return true;
+      },
 
-    addMessageListener("report", function() {
-      const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
-      var topWin = Services.wm.getMostRecentWindow("navigator:browser");
-      return JSON.stringify(topWin.eventCounts);
-    });
+      "report": function() {
+        var topWin = Services.wm.getMostRecentWindow("navigator:browser");
+        return JSON.stringify(topWin.eventCounts);
+      },
+
+      "end": function() {
+        for (let [msg, func] of Object.entries(LISTENERS)) {
+          Services.ppmm.removeMessageListener(PREFIX + msg, func);
+        }
 
-    addMessageListener("end", function() {
-      const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
-      var topWin = Services.wm.getMostRecentWindow("navigator:browser");
-      if (typeof topWin.eventCounts == "undefined") {
-        dump("The eventCounts object was not found on the top window!\n");
-        return false;
-      }
-      topWin.removeEventListener("touchstart", topWin.counter);
-      topWin.removeEventListener("touchmove", topWin.counter);
-      topWin.removeEventListener("touchend", topWin.counter);
-      delete topWin.counter;
-      delete topWin.eventCounts;
-      return true;
-    });
+        var topWin = Services.wm.getMostRecentWindow("navigator:browser");
+        if (typeof topWin.eventCounts == "undefined") {
+          dump("The eventCounts object was not found on the top window!\n");
+          return false;
+        }
+        topWin.removeEventListener("touchstart", topWin.counter);
+        topWin.removeEventListener("touchmove", topWin.counter);
+        topWin.removeEventListener("touchend", topWin.counter);
+        delete topWin.counter;
+        delete topWin.eventCounts;
+        return true;
+      },
+    };
+
+    for (let [msg, func] of Object.entries(LISTENERS)) {
+      Services.ppmm.addMessageListener(PREFIX + msg, func);
+    }
   }
 
   if (typeof chromeTouchEventCounter.chromeHelper == "undefined") {
     // This is the first time chromeTouchEventCounter is being called; do initialization
     chromeTouchEventCounter.chromeHelper = SpecialPowers.loadChromeScript(chromeProcessCounter);
     ApzCleanup.register(function() { chromeTouchEventCounter.chromeHelper.destroy(); });
   }
 
-  return chromeTouchEventCounter.chromeHelper.sendSyncMessage(operation, "");
+  return SpecialPowers.Services.cpmm.sendSyncMessage(`apz:ctec:${operation}`, "")[0];
 }
 
 // Simple wrapper that waits until the chrome process has seen |count| instances
 // of the |eventType| event. Returns true on success, and false if 10 seconds
 // go by without the condition being satisfied.
 function waitFor(eventType, count) {
   var start = Date.now();
   while (JSON.parse(chromeTouchEventCounter("report"))[eventType] != count) {
--- a/gfx/skia/skia/third_party/skcms/src/Transform_inl.h
+++ b/gfx/skia/skia/third_party/skcms/src/Transform_inl.h
@@ -554,17 +554,18 @@ SI void sample_clut_16(const skcms_A2B* 
     *r = cast<F>((rgb >>  0) & 0xffff) * (1/65535.0f);
     *g = cast<F>((rgb >> 16) & 0xffff) * (1/65535.0f);
     *b = cast<F>((rgb >> 32) & 0xffff) * (1/65535.0f);
 #endif
 }
 
 // GCC 7.2.0 hits an internal compiler error with -finline-functions (or -O3)
 // when targeting MIPS 64,  I think attempting to inline clut() into exec_ops().
-#if 1 && defined(__GNUC__) && !defined(__clang__) && defined(__mips64)
+// s390x and i*86 also hit this with GCC 7.4 and -O2
+#if 1 && defined(__GNUC__) && !defined(__clang__) && (defined(__mips64) || defined(__s390x__) || defined(__i586__) || defined(__i486__) || defined(__i386__))
     #define MAYBE_NOINLINE __attribute__((noinline))
 #else
     #define MAYBE_NOINLINE
 #endif
 
 MAYBE_NOINLINE
 static void clut(const skcms_A2B* a2b, F* r, F* g, F* b, F a) {
     const int dim = (int)a2b->input_channels;
--- a/gfx/tests/mochitest/mochitest.ini
+++ b/gfx/tests/mochitest/mochitest.ini
@@ -1,8 +1,9 @@
 [DEFAULT]
 
 [test_acceleration.html]
 skip-if = (os == 'win') # Bug 1430530
 subsuite = gpu
 [test_bug509244.html]
 [test_bug513439.html]
 [test_font_whitelist.html]
+skip-if = debug || asan # Race between pref service and gfx platform IPC causes frequent failures on debug/ASan
--- a/gfx/tests/mochitest/test_font_whitelist.html
+++ b/gfx/tests/mochitest/test_font_whitelist.html
@@ -15,16 +15,18 @@ https://bugzilla.mozilla.org/show_bug.cg
 <span id="serif" style="font-family: serif; font-size: 64px;">M</span>
 <div id="content" style="display: none">
 
 </div>
 <script class="testbody" type="application/javascript">
 
 /** Test for Bug 1121643 **/
 
+SimpleTest.requestFlakyTimeout("This test is flaky.");
+
 const InspectorUtils = SpecialPowers.InspectorUtils;
 
 // Given an element id, returns the first font face name encountered.
 let fontUsed = id => {
   let element = document.getElementById(id),
       range = document.createRange();
   range.selectNode(element);
   return InspectorUtils.getUsedFontFaces(range)[0].CSSFamilyName;
@@ -48,16 +50,17 @@ let testFontWhitelist = async function(u
   if (useSans) {
     whitelist.push(fonts.sans);
   }
   if (useSerif) {
     whitelist.push(fonts.serif);
   }
   await SpecialPowers.pushPrefEnv({"set": [["font.system.whitelist",
                                             whitelist.join(", ")]]});
+  await new Promise(resolve => setTimeout(resolve, 2000));
   // If whitelist is empty, then whitelisting is considered disabled
   // and all fonts are allowed.
   info("font whitelist: " + JSON.stringify(whitelist));
   let whitelistEmpty = whitelist.length === 0;
   is(useMono || whitelistEmpty, fontUsed("mono") === fonts.mono,
      "Correct mono whitelisting state; got " + fontUsed("mono") + ", requested " + fonts.mono);
   is(useSans || whitelistEmpty, fontUsed("sans") === fonts.sans,
      "Correct sans whitelisting state; got " + fontUsed("sans") + ", requested " + fonts.sans);
--- a/js/xpconnect/tests/chrome/test_bug1124898.html
+++ b/js/xpconnect/tests/chrome/test_bug1124898.html
@@ -8,37 +8,40 @@ https://bugzilla.mozilla.org/show_bug.cg
   <title>Test for Bug 1124898</title>
   <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://global/skin"/>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
   <script type="application/javascript">
 
   /** Test for Bug 1124898 **/
   SimpleTest.waitForExplicitFinish();
-  SpecialPowers.pushPrefEnv({"set": [["security.allow_eval_with_system_principal",
-																			true]]});
-  SimpleTest.expectAssertions(0, 1); // Dumb unrelated widget assertion - see bug 1126023.
-  var w = window.open("about:blank", "w", "chrome");
-  is(w.eval('typeof getAttention'), 'function', 'getAttention exists on regular chrome window');
-  is(w.eval('typeof messageManager'), 'object', 'messageManager exists on regular chrome window');
-  var contentURL = "http://example.org/tests/js/xpconnect/tests/mochitest/file_empty.html";
-  w.location = contentURL;
-  tryWindow();
+  (async () => {
+    await SpecialPowers.pushPrefEnv({"set": [["security.allow_eval_with_system_principal", true]]});
+
+    SimpleTest.expectAssertions(0, 1); // Dumb unrelated widget assertion - see bug 1126023.
+
+    var w = window.open("about:blank", "w", "chrome");
+    is(w.eval('typeof getAttention'), 'function', 'getAttention exists on regular chrome window');
+    is(w.eval('typeof messageManager'), 'object', 'messageManager exists on regular chrome window');
+    var contentURL = "http://example.org/tests/js/xpconnect/tests/mochitest/file_empty.html";
+    w.location = contentURL;
+    tryWindow();
 
-  function tryWindow() {
-    if (w.document.title != 'empty test page') {
-      info("Document not loaded yet - retrying");
-      SimpleTest.executeSoon(tryWindow);
-      return;
+    function tryWindow() {
+      if (w.document.title != 'empty test page') {
+        info("Document not loaded yet - retrying");
+        SimpleTest.executeSoon(tryWindow);
+        return;
+      }
+      is(w.eval('typeof getAttention'), 'undefined', 'getAttention doesnt exist on content-in-chrome window');
+      is(w.eval('typeof messageManager'), 'undefined', 'messageManager doesnt exist on content-in-chrome window');
+      w.close();
+      SimpleTest.finish();
     }
-    is(w.eval('typeof getAttention'), 'undefined', 'getAttention doesnt exist on content-in-chrome window');
-    is(w.eval('typeof messageManager'), 'undefined', 'messageManager doesnt exist on content-in-chrome window');
-    w.close();
-    SimpleTest.finish();
-  }
+  })();
 
   </script>
 </head>
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1124898">Mozilla Bug 1124898</a>
 <p id="display"></p>
 <div id="content" style="display: none">
 
--- a/js/xpconnect/tests/chrome/test_bug596580.xul
+++ b/js/xpconnect/tests/chrome/test_bug596580.xul
@@ -9,41 +9,43 @@
   <!-- test results are displayed in the html:body -->
   <body xmlns="http://www.w3.org/1999/xhtml">
   <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=596580"
      target="_blank">Mozilla Bug 596580</a>
   </body>
 
   <!-- test code goes here -->
   <script type="application/javascript"><![CDATA[
-    SpecialPowers.pushPrefEnv({"set": [["security.allow_eval_with_system_principal",
-                                        true]]});
+    SimpleTest.waitForExplicitFinish();
+
     function init() {
       var f = new Function("let test = 'let is ok'; return test;");
       is(f(), 'let is ok', 'let should be ok');
       SimpleTest.finish();
     }
 
-    Test = {
-      include: function(p) {
-	var sawError = false;
-	try {
-	  Cc["@mozilla.org/moz/jssubscript-loader;1"].
-	    getService(Ci["mozIJSSubScriptLoader"]).
-	    loadSubScript(p);
-	} catch (e) {
-	  sawError = true;
-	}
-	ok(sawError, 'should receive an error loading a not-found file');
-      }
-    };
+    (async () => {
+      await SpecialPowers.pushPrefEnv({"set": [["security.allow_eval_with_system_principal",
+                                               true]]});
+      Test = {
+        include: function(p) {
+          var sawError = false;
+          try {
+            Cc["@mozilla.org/moz/jssubscript-loader;1"].
+              getService(Ci["mozIJSSubScriptLoader"]).
+              loadSubScript(p);
+          } catch (e) {
+            sawError = true;
+          }
+          ok(sawError, 'should receive an error loading a not-found file');
+        }
+      };
 
-    // If the include method is defined as a global function, it works.
-    // try to load a non existing file to produce the error
-    Test.include("notfound.js");
+      // If the include method is defined as a global function, it works.
+      // try to load a non existing file to produce the error
+      Test.include("notfound.js");
 
-    // If init is called directly, it works.
-    setTimeout('init();', 0);
-
-    SimpleTest.waitForExplicitFinish();
+      // If init is called directly, it works.
+      setTimeout('init();', 0);
+    })();
 
   ]]></script>
 </window>
--- a/js/xpconnect/tests/chrome/test_bug732665.xul
+++ b/js/xpconnect/tests/chrome/test_bug732665.xul
@@ -13,18 +13,19 @@ https://bugzilla.mozilla.org/show_bug.cg
   <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=732665"
      target="_blank">Mozilla Bug 732665</a>
   </body>
 
   <!-- test code goes here -->
   <script type="application/javascript">
   <![CDATA[
 
-  SpecialPowers.pushPrefEnv({"set": [["security.allow_eval_with_system_principal",
-                                       true]]});
+add_task(async () => {
+  await SpecialPowers.pushPrefEnv({"set": [["security.allow_eval_with_system_principal",
+                                            true]]});
   //
   // Important! If this test starts failing after a tricky platform-y change,
   // the stack quota numbers in XPCJSContext probably need twiddling. We want
   // to maintain the invariants in this test (at least to some approximation)
   // for security reasons.
   //
 
   // Executes f() d steps from the probed native stack limit, and returns
@@ -71,12 +72,12 @@ https://bugzilla.mozilla.org/show_bug.cg
   //
   // If this assertion fails, the current work-around so far is to measure
   // again the worst frame size, by using the JS Shell to run
   // test_bug732665_meta.js . This script will output numbers to update
   // XPCJSContext.cpp comment, as well as the kTrustedScriptBuffer constant.
   contentSb.nnslChrome = chromeSb.nearNativeStackLimit;
   var nestedLimit = Cu.evalInSandbox("nearNativeStackLimit(1, function() { nestedLimit = nnslChrome(0);}); nestedLimit;", contentSb);
   ok(nestedLimit >= 11, "Chrome should be invokable from content script with an exhausted stack: " + nestedLimit);
-
+});
   ]]>
   </script>
 </window>
--- a/js/xpconnect/tests/chrome/test_cows.xul
+++ b/js/xpconnect/tests/chrome/test_cows.xul
@@ -12,22 +12,23 @@ https://bugzilla.mozilla.org/show_bug.cg
   <!-- test results are displayed in the html:body -->
   <body xmlns="http://www.w3.org/1999/xhtml">
   <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=522764 "
      target="_blank">Mozilla Bug 522764 </a>
   </body>
 
   <!-- test code goes here -->
   <script type="application/javascript"><![CDATA[
+add_task(async () => {
 var sandbox = new Cu.Sandbox("about:blank");
 
 var test_utils = window.windowUtils;
 
-SpecialPowers.pushPrefEnv({"set": [["security.allow_eval_with_system_principal",
-                                    true]]});
+await SpecialPowers.pushPrefEnv({"set": [["security.allow_eval_with_system_principal",
+                                          true]]});
 
 function getCOW(x) {
   if (typeof x != 'object' && typeof x != 'function')
     return x;
   x = Cu.waiveXrays(x);
   var rval = {};
   if (typeof x == "function")
     rval = eval(uneval(x));
@@ -219,10 +220,11 @@ var unwrapped = Cu.evalInSandbox(
 
 try {
     is(unwrapped.bar, 6,
        "COWs should be unwrapped when entering chrome space");
 } catch (e) {
     todo(false, "COWs should be unwrapped when entering chrome space, " +
                 "not raise " + e);
 }
+});
   ]]></script>
 </window>
--- a/js/xpconnect/tests/chrome/test_windowProxyDeadWrapper.html
+++ b/js/xpconnect/tests/chrome/test_windowProxyDeadWrapper.html
@@ -9,20 +9,20 @@ https://bugzilla.mozilla.org/show_bug.cg
   <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="chrome://global/skin"/>
   <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
   <script type="application/javascript">
 
 /** Test for Bug 1223372 **/
 const {TestUtils} = ChromeUtils.import("resource://testing-common/TestUtils.jsm");
 
-function go() {
-    SpecialPowers.pushPrefEnv({"set": [["security.allow_eval_with_system_principal",
-                                        true]]});
+async function go() {
     SimpleTest.waitForExplicitFinish();
+    await SpecialPowers.pushPrefEnv({"set": [["security.allow_eval_with_system_principal",
+                                             true]]});
 
     var frame = $('subframe');
     frame.onload = null;
 
     var w = frame.contentWindow;
 
     w.eval("checkDead = function() { return Components.utils.isDeadWrapper(this); };");
     var checkDead = w.checkDead;
--- a/js/xpconnect/tests/chrome/test_xrayToJS.xul
+++ b/js/xpconnect/tests/chrome/test_xrayToJS.xul
@@ -13,19 +13,16 @@ https://bugzilla.mozilla.org/show_bug.cg
   <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=933681"
      target="_blank">Mozilla Bug 933681</a>
   </body>
 
   <!-- test code goes here -->
   <script type="application/javascript">
   <![CDATA[
 
-  SpecialPowers.pushPrefEnv({"set": [["security.allow_eval_with_system_principal",
-                                       true]]});
-
   /** Test for ES constructors on Xrayed globals. **/
   SimpleTest.waitForExplicitFinish();
   let global = Cu.getGlobalForObject.bind(Cu);
 
   function checkThrows(f, rgxp, msg) {
     try {
       f();
       ok(false, "Should have thrown: " + msg);
@@ -474,83 +471,80 @@ https://bugzilla.mozilla.org/show_bug.cg
     testCtorCallables(ctorCallables, xrayCtor, localCtor);
     is(Object.getOwnPropertyNames(xrayCtor).sort().toSource(),
        ctorProps.toSource(), "getOwnPropertyNames works on Xrayed ctors");
     is(Object.getOwnPropertySymbols(xrayCtor).map(uneval).sort().toSource(),
        ctorSymbols.toSource(), "getOwnPropertySymbols works on Xrayed ctors");
   }
 
   // We will need arraysEqual and testArrayIterators both in this global scope
-  // and in sandboxes, so define them as strings up front.
-  var arraysEqualSource = `function arraysEqual(arr1, arr2, reason) {
-    is(arr1.length, arr2.length, \`\${reason}; lengths should be equal\`)
+  // and in sandboxes.
+  function arraysEqual(arr1, arr2, reason) {
+    is(arr1.length, arr2.length, `${reason}; lengths should be equal`)
     for (var i = 0; i < arr1.length; ++i) {
       if (Array.isArray(arr2[i])) {
-        arraysEqual(arr1[i], arr2[i], \`\${reason}; item at index \${i}\`);
+        arraysEqual(arr1[i], arr2[i], `${reason}; item at index ${i}`);
       } else {
-        is(arr1[i], arr2[i], \`\${reason}; item at index \${i} should be equal\`);
+        is(arr1[i], arr2[i], `${reason}; item at index ${i} should be equal`);
       }
     }
-  }`;
-  eval(arraysEqualSource);
+  }
 
-  var testArrayIteratorsSource = `
-    function testArrayIterators(arrayLike, equivalentArray, reason) {
-    arraysEqual([...arrayLike], equivalentArray, \`\${reason}; spread operator\`);
+  function testArrayIterators(arrayLike, equivalentArray, reason) {
+    arraysEqual([...arrayLike], equivalentArray, `${reason}; spread operator`);
     arraysEqual([...arrayLike.entries()], [...equivalentArray.entries()],
-                \`\${reason}; entries\`);
+                `${reason}; entries`);
     arraysEqual([...arrayLike.keys()], [...equivalentArray.keys()],
-                \`\${reason}; keys\`);
+                `${reason}; keys`);
     if (arrayLike.values) {
       arraysEqual([...arrayLike.values()], equivalentArray,
-                  \`\${reason}; values\`);
+                  `${reason}; values`);
     }
 
     var forEachCopy = [];
     arrayLike.forEach(function(arg) { forEachCopy.push(arg); });
-    arraysEqual(forEachCopy, equivalentArray, \`\${reason}; forEach copy\`);
+    arraysEqual(forEachCopy, equivalentArray, `${reason}; forEach copy`);
 
     var everyCopy = [];
     arrayLike.every(function(arg) { everyCopy.push(arg); return true; });
-    arraysEqual(everyCopy, equivalentArray, \`\${reason}; every() copy\`);
+    arraysEqual(everyCopy, equivalentArray, `${reason}; every() copy`);
 
     var filterCopy = [];
     var filterResult = arrayLike.filter(function(arg) {
       filterCopy.push(arg);
       return true;
     });
-    arraysEqual(filterCopy, equivalentArray, \`\${reason}; filter copy\`);
-    arraysEqual([...filterResult], equivalentArray, \`\${reason}; filter result\`);
+    arraysEqual(filterCopy, equivalentArray, `${reason}; filter copy`);
+    arraysEqual([...filterResult], equivalentArray, `${reason}; filter result`);
 
     var findCopy = [];
     arrayLike.find(function(arg) { findCopy.push(arg); return false; });
-    arraysEqual(findCopy, equivalentArray, \`\${reason}; find() copy\`);
+    arraysEqual(findCopy, equivalentArray, `${reason}; find() copy`);
 
     var findIndexCopy = [];
     arrayLike.findIndex(function(arg) { findIndexCopy.push(arg); return false; });
-    arraysEqual(findIndexCopy, equivalentArray, \`\${reason}; findIndex() copy\`);
+    arraysEqual(findIndexCopy, equivalentArray, `${reason}; findIndex() copy`);
 
     var mapCopy = [];
     var mapResult = arrayLike.map(function(arg) { mapCopy.push(arg); return arg});
-    arraysEqual(mapCopy, equivalentArray, \`\${reason}; map() copy\`);
-    arraysEqual([...mapResult], equivalentArray, \`\${reason}; map() result\`);
+    arraysEqual(mapCopy, equivalentArray, `${reason}; map() copy`);
+    arraysEqual([...mapResult], equivalentArray, `${reason}; map() result`);
 
     var reduceCopy = [];
     arrayLike.reduce(function(_, arg) { reduceCopy.push(arg); }, 0);
-    arraysEqual(reduceCopy, equivalentArray, \`\${reason}; reduce() copy\`);
+    arraysEqual(reduceCopy, equivalentArray, `${reason}; reduce() copy`);
 
     var reduceRightCopy = [];
     arrayLike.reduceRight(function(_, arg) { reduceRightCopy.unshift(arg); }, 0);
-    arraysEqual(reduceRightCopy, equivalentArray, \`\${reason}; reduceRight() copy\`);
+    arraysEqual(reduceRightCopy, equivalentArray, `${reason}; reduceRight() copy`);
 
     var someCopy = [];
     arrayLike.some(function(arg) { someCopy.push(arg); return false; });
-    arraysEqual(someCopy, equivalentArray, \`\${reason}; some() copy\`);
-  }`;
-  eval(testArrayIteratorsSource);
+    arraysEqual(someCopy, equivalentArray, `${reason}; some() copy`);
+  }
 
   function testDate() {
     // toGMTString is handled oddly in the engine. We don't bother to support
     // it over Xrays.
     let propsToSkip = ['toGMTString'];
 
     testXray('Date', new iwin.Date(), new iwin.Date(), propsToSkip);
 
@@ -795,31 +789,31 @@ https://bugzilla.mozilla.org/show_bug.cg
          "Only indexed properties visible over Xrays");
       Object.defineProperty(t.wrappedJSObject, 'length', {value: 42});
       is(t.wrappedJSObject.length, 42, "Set tricky expando")
       is(t.length, 10, "Length accessor works over Xrays")
       is(t.byteLength, t.length * window[c].prototype.BYTES_PER_ELEMENT, "byteLength accessor works over Xrays")
 
       // Can create TypedArray from content ArrayBuffer
       var buffer = new iwin.ArrayBuffer(8);
-      eval(`new ${c}(buffer);`);
+      new window[c](buffer);
 
       var xray = new iwin[c](0);
       var xrayTypedArrayProto = Object.getPrototypeOf(Object.getPrototypeOf(xray));
       testProtoCallables(inheritedCallables, new iwin[c](0), xrayTypedArrayProto, typedArrayProto);
 
       // When testing iterators, make sure to do so from inside our web
       // extension sandbox, since from chrome we can't poke their indices.  Note
       // that we have to actually recreate our functions that touch typed array
       // indices inside the sandbox, not just export them, because otherwise
       // they'll just run with our principal anyway.
       //
       // But we do want to export is(), since we want ours called.
-      wesb.eval(arraysEqualSource);
-      wesb.eval(testArrayIteratorsSource);
+      wesb.eval(String(arraysEqual));
+      wesb.eval(String(testArrayIterators));
       Cu.exportFunction(is, wesb,
                         { defineAs: "is" });
       wesb.eval('testArrayIterators(t, [0, 0, 3, 0, 0, 0, 0, 0, 0, 0])');
     }
   }
 
   function testErrorObjects() {
     // We only invoke testXray with Error, because that function isn't set up
--- a/js/xpconnect/wrappers/AccessCheck.cpp
+++ b/js/xpconnect/wrappers/AccessCheck.cpp
@@ -2,17 +2,16 @@
 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "AccessCheck.h"
 
 #include "nsJSPrincipals.h"
-#include "nsDOMWindowList.h"
 #include "nsGlobalWindow.h"
 
 #include "XPCWrapper.h"
 #include "XrayWrapper.h"
 #include "FilteringWrapper.h"
 
 #include "jsfriendapi.h"
 #include "mozilla/BasePrincipal.h"
--- a/js/xpconnect/wrappers/XrayWrapper.cpp
+++ b/js/xpconnect/wrappers/XrayWrapper.cpp
@@ -1643,26 +1643,22 @@ bool DOMXrayTraits::resolveOwnProperty(J
   }
 
   // Check for indexed access on a window.
   uint32_t index = GetArrayIndexFromId(id);
   if (IsArrayIndex(index)) {
     nsGlobalWindowInner* win = AsWindow(cx, wrapper);
     // Note: As() unwraps outer windows to get to the inner window.
     if (win) {
-      nsCOMPtr<nsPIDOMWindowOuter> subframe = win->IndexedGetter(index);
-      if (subframe) {
-        subframe->EnsureInnerWindow();
-        nsGlobalWindowOuter* global = nsGlobalWindowOuter::Cast(subframe);
-        JSObject* obj = global->GetGlobalJSObject();
-        if (MOZ_UNLIKELY(!obj)) {
+      Nullable<WindowProxyHolder> subframe = win->IndexedGetter(index);
+      if (!subframe.IsNull()) {
+        if (MOZ_UNLIKELY(!WrapObject(cx, subframe.Value(), desc.value()))) {
           // It's gone?
           return xpc::Throw(cx, NS_ERROR_FAILURE);
         }
-        desc.value().setObject(*obj);
         FillPropertyDescriptor(desc, wrapper, true);
         return JS_WrapPropertyDescriptor(cx, desc);
       }
     }
   }
 
   if (!JS_GetOwnPropertyDescriptorById(cx, holder, id, desc)) {
     return false;
--- a/layout/base/tests/chrome/printpreview_bug396024_helper.xul
+++ b/layout/base/tests/chrome/printpreview_bug396024_helper.xul
@@ -6,23 +6,27 @@
 https://bugzilla.mozilla.org/show_bug.cgi?id=396024
 -->
 <window title="Mozilla Bug 396024" onload="run()"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 <iframe id="i" src="about:blank" type="content"></iframe>
 <iframe src="about:blank" type="content"></iframe>
 <script type="application/javascript">
 <![CDATA[
+// Note: We can't use window.frames directly here because the type="content"
+// attributes isolate the frames into their own BrowsingContext hierarchies.
+let frameElts = document.getElementsByTagName("iframe");
+
 var is = window.opener.wrappedJSObject.is;
 var ok = window.opener.wrappedJSObject.ok;
 var todo = window.opener.wrappedJSObject.todo;
 var SimpleTest = window.opener.wrappedJSObject.SimpleTest;
 var gWbp;
 function printpreview() {
-  gWbp = window.frames[1].docShell.initOrReusePrintPreviewViewer();
+  gWbp = frameElts[1].contentWindow.docShell.initOrReusePrintPreviewViewer();
   var listener = {
     onLocationChange: function(webProgress, request, location, flags) { },
     onProgressChange: function(webProgress, request, curSelfProgress, 
                                maxSelfProgress, curTotalProgress,
                                maxTotalProgress) { },
     onSecurityChange: function(webProgress, request, state) { },
     onStateChange: function(webProgress, request, stateFlags, status) { },
     onStatusChange: function(webProgress, request, status, message) { },
@@ -34,22 +38,22 @@ function printpreview() {
       throw Cr.NS_NOINTERFACE;
     }
   }
   var prefs = Cc["@mozilla.org/preferences-service;1"]
                 .getService(Ci.nsIPrefBranch);
   prefs.setBoolPref('print.show_print_progress', false);
   //XXX I would have thought this would work, instead I'm forced to use prefs service
   gWbp.globalPrintSettings.showPrintProgress = false; 
-  gWbp.printPreview(gWbp.globalPrintSettings, window.frames[0], listener);
+  gWbp.printPreview(gWbp.globalPrintSettings, frameElts[0].contentWindow, listener);
   prefs.clearUserPref('print.show_print_progress');
 }
 
 function exitprintpreview() {
-  window.frames[1].docShell.exitPrintPreview();
+  frameElts[1].contentWindow.docShell.exitPrintPreview();
 }
 
 function finish() {
   SimpleTest.finish();
   window.close();
 }
 
 function run()
@@ -81,21 +85,21 @@ function run()
 }
 
 function run2() {
   var loadhandler = function() {
     document.getElementById("i").removeEventListener("load", arguments.callee, true);
     setTimeout(run3, 0);
   };
   document.getElementById("i").addEventListener("load", loadhandler, true);
-  window.frames[0].location.reload();
+  frameElts[0].contentWindow.location.reload();
 }
 
 function run3() {
-  gWbp = window.frames[1].docShell.initOrReusePrintPreviewViewer();
+  gWbp = frameElts[1].contentWindow.docShell.initOrReusePrintPreviewViewer();
   ok(gWbp.doingPrintPreview, "Should be doing print preview");
   exitprintpreview();
   setTimeout(run4, 0);
 }
 
 function run4() {
   var i = document.getElementById("i");
   i.remove();
@@ -104,17 +108,17 @@ function run4() {
     setTimeout(run5, 0);
   };
   i.addEventListener("load", loadhandler, true);
   document.documentElement.getBoundingClientRect();
   document.documentElement.prepend(i);
 }
 
 function run5() {
-  gWbp = window.frames[1].docShell.initOrReusePrintPreviewViewer();
+  gWbp = frameElts[1].contentWindow.docShell.initOrReusePrintPreviewViewer();
   ok(!gWbp.doingPrintPreview, "Should not be doing print preview anymore2");
 
   //XXX this shouldn't be necessary, see bug 405555
   printpreview();
   exitprintpreview();
   finish(); //should not have crashed after all of this
 }
 ]]></script>
--- a/layout/base/tests/chrome/printpreview_bug482976_helper.xul
+++ b/layout/base/tests/chrome/printpreview_bug482976_helper.xul
@@ -6,23 +6,27 @@
 https://bugzilla.mozilla.org/show_bug.cgi?id=482976
 -->
 <window title="Mozilla Bug 482976" onload="run1()"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 <iframe src="about:blank" type="content"></iframe>
 <iframe src="about:blank" type="content"></iframe>
 <script type="application/javascript">
 <![CDATA[
+// Note: We can't use window.frames directly here because the type="content"
+// attributes isolate the frames into their own BrowsingContext hierarchies.
+let frameElts = document.getElementsByTagName("iframe");
+
 var is = window.opener.wrappedJSObject.is;
 var ok = window.opener.wrappedJSObject.ok;
 var todo = window.opener.wrappedJSObject.todo;
 var SimpleTest = window.opener.wrappedJSObject.SimpleTest;
 var gWbp;
 function printpreview() {
-  gWbp = window.frames[1].docShell.initOrReusePrintPreviewViewer();
+  gWbp = frameElts[1].contentWindow.docShell.initOrReusePrintPreviewViewer();
   var listener = {
     onLocationChange: function(webProgress, request, location, flags) { },
     onProgressChange: function(webProgress, request, curSelfProgress, 
                                maxSelfProgress, curTotalProgress,
                                maxTotalProgress) { },
     onSecurityChange: function(webProgress, request, state) { },
     onStateChange: function(webProgress, request, stateFlags, status) { },
     onStatusChange: function(webProgress, request, status, message) { },
@@ -34,22 +38,22 @@ function printpreview() {
       throw Cr.NS_NOINTERFACE;
     }
   }
   var prefs = Cc["@mozilla.org/preferences-service;1"]
                 .getService(Ci.nsIPrefBranch);
   prefs.setBoolPref('print.show_print_progress', false);
   //XXX I would have thought this would work, instead I'm forced to use prefs service
   gWbp.globalPrintSettings.showPrintProgress = false; 
-  gWbp.printPreview(gWbp.globalPrintSettings, window.frames[0], listener);
+  gWbp.printPreview(gWbp.globalPrintSettings, frameElts[0].contentWindow, listener);
   prefs.clearUserPref('print.show_print_progress');
 }
 
 function exitprintpreview() {
-  window.frames[1].docShell.exitPrintPreview();
+  frameElts[1].contentWindow.docShell.exitPrintPreview();
 }
 
 function finish() {
   SimpleTest.finish();
   window.close();
 }
 
 function run1()
--- a/layout/base/tests/chrome/printpreview_helper.xul
+++ b/layout/base/tests/chrome/printpreview_helper.xul
@@ -3,33 +3,37 @@
 <?xml-stylesheet href="chrome://mochikit/content/tests/SimpleTest/test.css"
                  type="text/css"?>
 <window onload="runTests()"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
 <iframe height="200" width="600" type="content"></iframe>
 <iframe height="200" width="600" type="content"></iframe>
 <script type="application/javascript">
 <![CDATA[
+// Note: We can't use window.frames directly here because the type="content"
+// attributes isolate the frames into their own BrowsingContext hierarchies.
+let frameElts = document.getElementsByTagName("iframe");
+
 var is = window.opener.wrappedJSObject.is;
 var isnot = window.opener.wrappedJSObject.isnot;
 var ok = window.opener.wrappedJSObject.ok;
 var todo = window.opener.wrappedJSObject.todo;
 var SimpleTest = window.opener.wrappedJSObject.SimpleTest;
 var gWbp;
 var ctx1;
 var ctx2;
 var counter = 0;
 
 var file = Cc["@mozilla.org/file/directory_service;1"]
              .getService(Ci.nsIProperties)
              .get("TmpD", Ci.nsIFile);
 filePath = file.path;
 
 function printpreview(hasMozPrintCallback) {
-  gWbp = window.frames[1].docShell.initOrReusePrintPreviewViewer();
+  gWbp = frameElts[1].docShell.initOrReusePrintPreviewViewer();
   var listener = {
     onLocationChange: function(webProgress, request, location, flags) { },
     onProgressChange: function(webProgress, request, curSelfProgress, 
                                maxSelfProgress, curTotalProgress,
                                maxTotalProgress) { },
     onSecurityChange: function(webProgress, request, state) { },
     onStateChange: function(webProgress, request, stateFlags, status) { },
     onStatusChange: function(webProgress, request, status, message) { },
@@ -45,32 +49,32 @@ function printpreview(hasMozPrintCallbac
                 .getService(Ci.nsIPrefBranch);
   prefs.setBoolPref('print.show_print_progress', false);
   //XXX I would have thought this would work, instead I'm forced to use prefs service
   gWbp.globalPrintSettings.showPrintProgress = false;
   var before = 0;
   var after = 0;
   function beforeprint() { ++before; }
   function afterprint() { ++after; }
-  window.frames[0].addEventListener("beforeprint", beforeprint, true);
-  window.frames[0].addEventListener("afterprint", afterprint, true);
-  gWbp.printPreview(gWbp.globalPrintSettings, window.frames[0], listener);
+  frameElts[0].contentWindow.addEventListener("beforeprint", beforeprint, true);
+  frameElts[0].contentWindow.addEventListener("afterprint", afterprint, true);
+  gWbp.printPreview(gWbp.globalPrintSettings, frameElts[0].contentWindow, listener);
   is(before, 1, "Should have called beforeprint listener!");
   if (!hasMozPrintCallback) {
     // If there's a mozPrintCallback the after print event won't fire until
     // later.
     is(after, 1, "Should have called afterprint listener!");
   }
-  window.frames[0].removeEventListener("beforeprint", beforeprint, true);
-  window.frames[0].removeEventListener("afterprint", afterprint, true);
+  frameElts[0].contentWindow.removeEventListener("beforeprint", beforeprint, true);
+  frameElts[0].contentWindow.removeEventListener("afterprint", afterprint, true);
   prefs.clearUserPref('print.show_print_progress');
 }
 
 function exitprintpreview() {
-  window.frames[1].docShell.exitPrintPreview();
+  frameElts[1].contentWindow.docShell.exitPrintPreview();
 }
 
 function finish() {
   SimpleTest.finish();
   window.close();
 }
 
 function runTests()
@@ -112,55 +116,55 @@ function addHTMLContent(parent) {
   }
   s += "</table>";
   n.innerHTML = s;
 }
 
 function startTest1() {
   ctx1 = document.getElementsByTagName("canvas")[0].getContext("2d");
   ctx2 = document.getElementsByTagName("canvas")[1].getContext("2d");
-  window.frames[0].document.body.innerHTML = "<div> </div><div>" + counter + " timers</div><div> </div>";
+  frameElts[0].contentDocument.body.innerHTML = "<div> </div><div>" + counter + " timers</div><div> </div>";
 
   // Note this timeout is needed so that we can check that timers run
   // after print preview, but not during it.
-  window.frames[0].wrappedJSObject.counter = counter;
-  window.frames[0].counterTimeout = "document.body.firstChild.nextSibling.innerHTML = ++counter + ' timers';" +
+  frameElts[0].contentWindow.wrappedJSObject.counter = counter;
+  frameElts[0].contentWindow.counterTimeout = "document.body.firstChild.nextSibling.innerHTML = ++counter + ' timers';" +
                                     "window.setTimeout(counterTimeout, 0);";
-  window.frames[0].setTimeout(window.frames[0].counterTimeout, 0);
-  window.frames[0].document.body.firstChild.innerHTML = "Print preview";
+  frameElts[0].contentWindow.setTimeout(frameElts[0].contentWindow.counterTimeout, 0);
+  frameElts[0].contentDocument.body.firstChild.innerHTML = "Print preview";
 
   printpreview();
-  ctx1.drawWindow(window.frames[1], 0, 0, 400, 400, "rgb(256,256,256)");
-  window.frames[0].document.body.firstChild.innerHTML = "Galley presentation";
+  ctx1.drawWindow(frameElts[1].contentWindow, 0, 0, 400, 400, "rgb(256,256,256)");
+  frameElts[0].contentDocument.body.firstChild.innerHTML = "Galley presentation";
 
   // Add some elements.
-  addHTMLContent(window.frames[0].document.body.lastChild);
+  addHTMLContent(frameElts[0].contentDocument.body.lastChild);
   // Delete them.
-  window.frames[0].document.body.lastChild.innerHTML = "";
+  frameElts[0].contentDocument.body.lastChild.innerHTML = "";
   // And readd.
-  addHTMLContent(window.frames[0].document.body.lastChild);
+  addHTMLContent(frameElts[0].contentDocument.body.lastChild);
 
   setTimeout(finalizeTest1, 1000);
 }
 
 function finalizeTest1() {
-  ctx2.drawWindow(window.frames[1], 0, 0, 400, 400, "rgb(256,256,256)");
+  ctx2.drawWindow(frameElts[1].contentWindow, 0, 0, 400, 400, "rgb(256,256,256)");
   exitprintpreview();
   ok(compareCanvases(), "Canvas should be the same!");
-  counter = window.frames[0].counter;
+  counter = frameElts[0].contentWindow.counter;
   // This timeout is needed so that we can check that timers do run after
   // print preview.
   setTimeout(runTest2, 1000);
 }
 
 function runTest2() {
-  isnot(window.frames[0].document.body.firstChild.nextSibling.textContent, "0 timers", "Timers should have run!");
-  isnot(window.frames[0].counter, 0, "Timers should have run!");
-  counter = window.frames[0].counter;
-  window.frames[0].counterTimeout = "";
+  isnot(frameElts[0].contentDocument.body.firstChild.nextSibling.textContent, "0 timers", "Timers should have run!");
+  isnot(frameElts[0].contentWindow.counter, 0, "Timers should have run!");
+  counter = frameElts[0].contentWindow.counter;
+  frameElts[0].contentWindow.counterTimeout = "";
   setTimeout(runTest3, 0);
 }
 
 var elementIndex = 0;
 var compareEmptyElement = true;
 var emptyFormElements =
   ["<input type='text'>",
    "<input type='password'>",
@@ -203,154 +207,154 @@ function runTest3() {
     compareFormElementPrint(emptyFormElements[currentIndex], formElements[currentIndex], false);
     return;
   }
 
   setTimeout(runTest4, 0)
 }
 
 function compareFormElementPrint(el1, el2, equals) {
-  window.frames[0].document.body.innerHTML = el1;
-  window.frames[0].document.body.firstChild.value =
-    window.frames[0].document.body.firstChild.getAttribute('value');
+  frameElts[0].contentDocument.body.innerHTML = el1;
+  frameElts[0].contentDocument.body.firstChild.value =
+    frameElts[0].contentDocument.body.firstChild.getAttribute('value');
   printpreview();
-  ctx1.drawWindow(window.frames[1], 0, 0, 400, 400, "rgb(256,256,256)");
+  ctx1.drawWindow(frameElts[1].contentWindow, 0, 0, 400, 400, "rgb(256,256,256)");
   exitprintpreview();
-  window.frames[0].document.body.innerHTML = el2;
-  window.frames[0].document.body.firstChild.value =
-    window.frames[0].document.body.firstChild.getAttribute('value');
+  frameElts[0].contentDocument.body.innerHTML = el2;
+  frameElts[0].contentDocument.body.firstChild.value =
+    frameElts[0].contentDocument.body.firstChild.getAttribute('value');
   printpreview();
-  ctx2.drawWindow(window.frames[1], 0, 0, 400, 400, "rgb(256,256,256)");
+  ctx2.drawWindow(frameElts[1].contentWindow, 0, 0, 400, 400, "rgb(256,256,256)");
   exitprintpreview();
   is(compareCanvases(), equals,
      "Comparing print preview didn't succeed [" + el1 + " : " + el2 + "]");
   setTimeout(runTest3, 100);
 }
 
 // This is a crash test for bug 539060.
 function runTest4() {
-  window.frames[0].document.body.innerHTML =
+  frameElts[0].contentDocument.body.innerHTML =
     "<iframe style='display: none;' src='data:text/html,<iframe>'></iframe>";
   setTimeout(runTest4end, 500);
 }
 
 function runTest4end() {
   printpreview();
   exitprintpreview();
 
   runTest5();
 }
 
 // This is a crash test for bug 595337
 function runTest5() {
-  window.frames[0].document.body.innerHTML =
+  frameElts[0].contentDocument.body.innerHTML =
     '<iframe style="position: fixed; visibility: hidden; bottom: 10em;"></iframe>' +
     '<input contenteditable="true" style="display: table; page-break-before: left; width: 10000px;">';
   printpreview();
   exitprintpreview();
 
   setTimeout(runTest6, 0);
 }
 
 // Crash test for bug 878037
 function runTest6() {
-  window.frames[0].document.body.innerHTML =
+  frameElts[0].contentDocument.body.innerHTML =
     '<style> li { list-style-image: url("animated.gif"); } </style>' +
     '<li>Firefox will crash if you try and print this page</li>';
 
   setTimeout(runTest6end, 500);
 }
 
 function runTest6end() {
   printpreview();
   exitprintpreview();
 
   requestAnimationFrame(function() { setTimeout(runTest7); } );
 }
 
 function runTest7() {
   var contentText = "<a href='#'>mozilla</a><input>test<select><option>option1</option></select>";
   // Create normal content
-  window.frames[0].document.body.innerHTML =
+  frameElts[0].contentDocument.body.innerHTML =
     "<div>" + contentText + "</div>";
-  window.frames[0].document.body.firstChild.value =
-    window.frames[0].document.body.firstChild.getAttribute('value');
+  frameElts[0].contentDocument.body.firstChild.value =
+    frameElts[0].contentDocument.body.firstChild.getAttribute('value');
   printpreview();
-  ctx1.drawWindow(window.frames[1], 0, 0, 400, 400, "rgb(255,255,255)");
+  ctx1.drawWindow(frameElts[1].contentWindow, 0, 0, 400, 400, "rgb(255,255,255)");
   exitprintpreview();
 
-  window.frames[0].document.body.innerHTML = "<div></div>";
-  var sr = window.frames[0].document.body.firstChild.attachShadow({mode: "open"});
+  frameElts[0].contentDocument.body.innerHTML = "<div></div>";
+  var sr = frameElts[0].contentDocument.body.firstChild.attachShadow({mode: "open"});
   sr.innerHTML = contentText;
   printpreview();
-  ctx2.drawWindow(window.frames[1], 0, 0, 400, 400, "rgb(255,255,255)");
+  ctx2.drawWindow(frameElts[1].contentWindow, 0, 0, 400, 400, "rgb(255,255,255)");
   exitprintpreview();
   ok(compareCanvases(), "Printing light DOM and shadow DOM should create same output");
 
   requestAnimationFrame(function() { setTimeout(runTest8); } );
 }
 
 async function runTest8() {
   // Test that fonts loaded with CSS and JS are printed the same.
   const iframeElement = document.getElementsByTagName("iframe")[0];
 
   // First, snapshot the page with font defined in CSS.
   await new Promise((resolve) => {
     iframeElement.addEventListener("load", resolve, { capture: true, once: true });
     iframeElement.setAttribute("src", "printpreview_font_api_ref.html");
   });
   printpreview();
-  ctx1.drawWindow(window.frames[1], 0, 0, 400, 400, "rgb(255,255,255)");
+  ctx1.drawWindow(frameElts[1].contentWindow, 0, 0, 400, 400, "rgb(255,255,255)");
   exitprintpreview();
 
   // Second, snapshot the page with font loaded in JS.
   await new Promise((resolve) => {
     iframeElement.addEventListener("message", resolve, { capture: true, once: true });
     iframeElement.setAttribute("src", "printpreview_font_api.html");
   });
   printpreview();
-  ctx2.drawWindow(window.frames[1], 0, 0, 400, 400, "rgb(255,255,255)");
+  ctx2.drawWindow(frameElts[1].contentWindow, 0, 0, 400, 400, "rgb(255,255,255)");
   exitprintpreview();
   ok(compareCanvases(), "Printing pages with fonts loaded from CSS and JS should be the same.");
 
   requestAnimationFrame(function() { setTimeout(runTest9); } );
 }
 
 // Test for bug 1487649
 async function runTest9() {
-  window.frames[0].document.body.innerHTML = `
+  frameElts[0].contentDocument.body.innerHTML = `
     <svg width="100" height="100">
       <rect width='100' height='100' fill='lime'/>
     </svg>
   `;
 
   printpreview();
-  ctx1.drawWindow(window.frames[1], 0, 0, 400, 400, "rgb(255,255,255)");
+  ctx1.drawWindow(frameElts[1].contentWindow, 0, 0, 400, 400, "rgb(255,255,255)");
   exitprintpreview();
 
-  window.frames[0].document.body.innerHTML = `
+  frameElts[0].contentDocument.body.innerHTML = `
     <svg width="100" height="100">
       <defs>
         <g id="useme">
           <rect width='100' height='100' fill='lime'/>
         </g>
       </defs>
       <use />
     </svg>
   `;
 
   // Set the attribute explicitly because this is a chrome document, and the
   // href attribute would get sanitized.
-  window.frames[0].document.querySelector("use").setAttribute("href", "#useme");
+  frameElts[0].contentDocument.querySelector("use").setAttribute("href", "#useme");
 
   // Ensure the <use> shadow tree is created so we test what we want to test.
-  window.frames[0].document.body.offsetTop;
+  frameElts[0].contentDocument.body.offsetTop;
 
   printpreview();
-  ctx2.drawWindow(window.frames[1], 0, 0, 400, 400, "rgb(255,255,255)");
+  ctx2.drawWindow(frameElts[1].contentWindow, 0, 0, 400, 400, "rgb(255,255,255)");
   exitprintpreview();
   ok(compareCanvases(), "Printing <use> subtrees should create same output");
 
   requestAnimationFrame(function() { setTimeout(runTest10); } );
 }
 
 // Test for bug 1524640
 async function runTest10() {
@@ -363,31 +367,31 @@ async function runTest10() {
     iframeElement.addEventListener("load", resolve, { capture: true, once: true });
     iframeElement.setAttribute("src", "printpreview_font_mozprintcallback_ref.html");
   });
   let mozPrintCallbackDone = new Promise((resolve) => {
     iframeElement.addEventListener("message", resolve, { capture: true, once: true });
   });
   printpreview(true);
   await mozPrintCallbackDone;
-  ctx1.drawWindow(window.frames[1], 0, 0, 400, 400, "rgb(255,255,255)");
+  ctx1.drawWindow(frameElts[1].contentWindow, 0, 0, 400, 400, "rgb(255,255,255)");
   exitprintpreview();
 
   // Second, snapshot the page with font loaded in JS.
   await new Promise((resolve) => {
     iframeElement.addEventListener("load", resolve, { capture: true, once: true });
     iframeElement.setAttribute("src", "printpreview_font_mozprintcallback.html");
   });
   mozPrintCallbackDone = new Promise((resolve) => {
     iframeElement.addEventListener("message", resolve, { capture: true, once: true });
   });
   printpreview(true);
   // Wait for the mozprintcallback to finish.
   await mozPrintCallbackDone;
-  ctx2.drawWindow(window.frames[1], 0, 0, 400, 400, "rgb(255,255,255)");
+  ctx2.drawWindow(frameElts[1].contentWindow, 0, 0, 400, 400, "rgb(255,255,255)");
 
   exitprintpreview();
   ok(compareCanvases(), "Printing pages with fonts loaded from a mozPrintCallback should be the same.");
 
   finish();
 }
 
 ]]></script>
--- a/layout/base/tests/test_bug394057.html
+++ b/layout/base/tests/test_bug394057.html
@@ -58,22 +58,22 @@ if (serifWidth == monospaceWidth) {
     if (serifWidth != monospaceWidth)
       break;
   }
 }
 
 isnot(serifWidth, monospaceWidth,
       "can't find serif and monospace fonts of different width");
 
-SpecialPowers.pushPrefEnv({'set': [['font.name.serif.x-western', serifFonts[serifIdx]]]}, step2);
+SpecialPowers.pushPrefEnv({'set': [['font.name.serif.x-western', serifFonts[serifIdx]]]}).then(step2);
 
 var serifWidthFromPref;
 function step2() {
     serifWidthFromPref = tableElement.offsetWidth;
-    SpecialPowers.pushPrefEnv({'set': [['font.name.serif.x-western', monospaceFonts[monospaceIdx]]]}, step3);
+    SpecialPowers.pushPrefEnv({'set': [['font.name.serif.x-western', monospaceFonts[monospaceIdx]]]}).then(step3);
 }
 var monospaceWidthFromPref;
 function step3() {
     monospaceWidthFromPref = tableElement.offsetWidth;
 
     is(serifWidthFromPref, serifWidth,
        "changing font pref should change width of table (serif)");
     is(monospaceWidthFromPref, monospaceWidth,
--- a/layout/tools/reftest/reftest-content.js
+++ b/layout/tools/reftest/reftest-content.js
@@ -519,17 +519,21 @@ function FlushRendering(aFlushMode) {
         }
 
         if (!afterPaintWasPending && utils.isMozAfterPaintPending) {
             LogInfo("FlushRendering generated paint for window " + win.location.href);
             anyPendingPaintsGeneratedInDescendants = true;
         }
 
         for (var i = 0; i < win.frames.length; ++i) {
-            flushWindow(win.frames[i]);
+            try {
+                flushWindow(win.frames[i]);
+            } catch (e) {
+                Cu.reportError(e);
+            }
         }
     }
 
     flushWindow(content);
 
     if (anyPendingPaintsGeneratedInDescendants &&
         !windowUtils().isMozAfterPaintPending) {
         LogWarning("Internal error: descendant frame generated a MozAfterPaint event, but the root document doesn't have one!");
--- a/netwerk/test/mochitests/test_rel_preconnect.html
+++ b/netwerk/test/mochitests/test_rel_preconnect.html
@@ -10,19 +10,19 @@
 <script type="text/javascript">
 SimpleTest.waitForExplicitFinish();
 
 const Cc = SpecialPowers.Cc, Ci = SpecialPowers.Ci, Cr = SpecialPowers.Cr;
 
 var remainder = 4;
 var observer;
 
-function doTest()
+async function doTest()
 {
-  SpecialPowers.setBoolPref("network.http.debug-observations", true);
+  await SpecialPowers.setBoolPref("network.http.debug-observations", true);
   
   observer = SpecialPowers.wrapCallback(function(subject, topic, data) {
     remainder--;
     ok(true, "observed remainder = " + remainder);
     if (!remainder) {
       SpecialPowers.removeObserver(observer, "speculative-connect-request");
       SpecialPowers.setBoolPref("network.http.debug-observations", false);
       SimpleTest.finish();
--- a/netwerk/test/mochitests/test_user_agent_updates.html
+++ b/netwerk/test/mochitests/test_user_agent_updates.html
@@ -197,17 +197,17 @@ SimpleTest.requestFlakyTimeout("Test set
 
 SpecialPowers.pushPrefEnv({
   set: [
     [PREF_APP_UPDATE_TIMERMINIMUMDELAY, 0]
   ]
 }, function () {
   // Sets the OVERRIDES var in the chrome script.
   // We do this to avoid code duplication.
-  chromeScript.sendSyncMessage("set-overrides", OVERRIDES);
+  chromeScript.sendAsyncMessage("set-overrides", OVERRIDES);
 
   // testProfileLoad, testDownload, and testProfileSave must run in this order
   // because testDownload depends on testProfileLoad and testProfileSave depends
   // on testDownload to save overrides to the profile
   chromeScript.sendAsyncMessage("testProfileLoad", location.hostname);
 });
 
 
--- a/security/manager/ssl/tests/mochitest/stricttransportsecurity/test_sts_privatebrowsing_perwindowpb.html
+++ b/security/manager/ssl/tests/mochitest/stricttransportsecurity/test_sts_privatebrowsing_perwindowpb.html
@@ -175,21 +175,21 @@
       let browser = win.gBrowser.selectedBrowser;
       ContentTask.spawn(browser, testframes, async function(contentTestFrames) {
         content.document.body.removeChild(
           content.document.getElementById("ifr_bootstrap"));
         for (let test in contentTestFrames) {
           content.document.body.removeChild(
             content.document.getElementById("ifr_" + test));
         }
-      }).then(() => {
+      }).then(async () => {
         currentround = "";
 
         if (!isPrivate) {
-          clean_up_sts_state(isPrivate);
+          await clean_up_sts_state(isPrivate);
         }
         // Close test window.
         win.close();
         // And advance to the next test.
         // Defer this so it doesn't muck with the stack too much.
         SimpleTest.executeSoon(nextTest);
       });
     }
@@ -214,21 +214,21 @@
   function test_sts_after_exiting_private_mode() {
     testOnWindow(false, function(win) {
       SimpleTest.info("In a new public window");
       dump_STSState(false);
       startRound(win, false, "nosts");
     });
   }
 
-  function clean_up_sts_state(isPrivate) {
+  async function clean_up_sts_state(isPrivate) {
     // erase all signs that this test ran.
     SimpleTest.info("Cleaning up STS data");
     let flags = isPrivate ? Ci.nsISocketProvider.NO_PERMANENT_STORAGE : 0;
-    SpecialPowers.cleanUpSTSData("http://example.com", flags);
+    await SpecialPowers.cleanUpSTSData("http://example.com", flags);
     dump_STSState(isPrivate);
   }
 
   function dump_STSState(isPrivate) {
     let sss = Cc["@mozilla.org/ssservice;1"]
                 .getService(Ci.nsISiteSecurityService);
     let flags = isPrivate ? Ci.nsISocketProvider.NO_PERMANENT_STORAGE : 0;
     SimpleTest.info("State of example.com: " +
--- a/security/nss/TAG-INFO
+++ b/security/nss/TAG-INFO
@@ -1,1 +1,1 @@
-NSS_3_45_BETA1
+NSS_3_45_BETA2
--- a/security/nss/coreconf/coreconf.dep
+++ b/security/nss/coreconf/coreconf.dep
@@ -5,8 +5,9 @@
 
 /*
  * A dummy header file that is a dependency for all the object files.
  * Used to force a full recompilation of NSS in Mozilla's Tinderbox
  * depend builds.  See comments in rules.mk.
  */
 
 #error "Do not include this header file."
+
--- a/security/nss/lib/freebl/ecl/curve25519_32.c
+++ b/security/nss/lib/freebl/ecl/curve25519_32.c
@@ -1,390 +1,1209 @@
-/* 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/. */
+// The MIT License (MIT)
+//
+// Copyright (c) 2015-2016 the fiat-crypto authors (see the AUTHORS file).
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to deal
+// in the Software without restriction, including without limitation the rights
+// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+// copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in all
+// copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+// SOFTWARE.
 
 /*
- * Derived from public domain code by Matthew Dempsky and D. J. Bernstein.
+ * Derived from machine-generated code via Fiat-Crypto:
+ * https://github.com/mit-plv/fiat-crypto and https://github.com/briansmith/ring
+ *
+ * The below captures notable changes:
+ *
+ *  1. Convert custom integer types to stdint.h types
  */
 
 #include "ecl-priv.h"
-#include "mpi.h"
+
+/* fe means field element. Here the field is \Z/(2^255-19). An element t,
+ * entries t[0]...t[9], represents the integer t[0]+2^26 t[1]+2^51 t[2]+2^77
+ * t[3]+2^102 t[4]+...+2^230 t[9].
+ * fe limbs are bounded by 1.125*2^26,1.125*2^25,1.125*2^26,1.125*2^25,etc.
+ * Multiplication and carrying produce fe from fe_loose.
+ */
+typedef struct fe {
+    uint32_t v[10];
+} fe;
+
+/* fe_loose limbs are bounded by 3.375*2^26,3.375*2^25,3.375*2^26,3.375*2^25,etc
+ * Addition and subtraction produce fe_loose from (fe, fe).
+ */
+typedef struct fe_loose {
+    uint32_t v[10];
+} fe_loose;
+
+#define assert_fe(f)                                                         \
+    do {                                                                     \
+        for (unsigned _assert_fe_i = 0; _assert_fe_i < 10; _assert_fe_i++) { \
+            PORT_Assert(f[_assert_fe_i] <=                                   \
+                        ((_assert_fe_i & 1) ? 0x2333333u : 0x4666666u));     \
+        }                                                                    \
+    } while (0)
+
+#define assert_fe_loose(f)                                                   \
+    do {                                                                     \
+        for (unsigned _assert_fe_i = 0; _assert_fe_i < 10; _assert_fe_i++) { \
+            PORT_Assert(f[_assert_fe_i] <=                                   \
+                        ((_assert_fe_i & 1) ? 0x6999999u : 0xd333332u));     \
+        }                                                                    \
+    } while (0)
+
+/*
+ * The function fiat_25519_subborrowx_u26 is a subtraction with borrow.
+ * Postconditions:
+ *   out1 = (-arg1 + arg2 + -arg3) mod 2^26
+ *   out2 = -⌊(-arg1 + arg2 + -arg3) / 2^26⌋
+ *
+ * Input Bounds:
+ *   arg1: [0x0 ~> 0x1]
+ *   arg2: [0x0 ~> 0x3ffffff]
+ *   arg3: [0x0 ~> 0x3ffffff]
+ * Output Bounds:
+ *   out1: [0x0 ~> 0x3ffffff]
+ *   out2: [0x0 ~> 0x1]
+ */
+static void
+fiat_25519_subborrowx_u26(uint32_t *out1, uint8_t *out2, uint8_t arg1, uint32_t arg2, uint32_t arg3)
+{
+    int32_t x1 = ((int32_t)(arg2 - arg1) - (int32_t)arg3);
+    int8_t x2 = (int8_t)(x1 >> 26);
+    uint32_t x3 = (x1 & UINT32_C(0x3ffffff));
+    *out1 = x3;
+    *out2 = (uint8_t)(0x0 - x2);
+}
+
+/*
+ * The function fiat_25519_subborrowx_u25 is a subtraction with borrow.
+ * Postconditions:
+ *   out1 = (-arg1 + arg2 + -arg3) mod 2^25
+ *   out2 = -⌊(-arg1 + arg2 + -arg3) / 2^25⌋
+ *
+ * Input Bounds:
+ *   arg1: [0x0 ~> 0x1]
+ *   arg2: [0x0 ~> 0x1ffffff]
+ *   arg3: [0x0 ~> 0x1ffffff]
+ * Output Bounds:
+ *   out1: [0x0 ~> 0x1ffffff]
+ *   out2: [0x0 ~> 0x1]
+ */
+static void
+fiat_25519_subborrowx_u25(uint32_t *out1, uint8_t *out2, uint8_t arg1, uint32_t arg2, uint32_t arg3)
+{
+    int32_t x1 = ((int32_t)(arg2 - arg1) - (int32_t)arg3);
+    int8_t x2 = (int8_t)(x1 >> 25);
+    uint32_t x3 = (x1 & UINT32_C(0x1ffffff));
+    *out1 = x3;
+    *out2 = (uint8_t)(0x0 - x2);
+}
+
+/*
+ * The function fiat_25519_addcarryx_u26 is an addition with carry.
+ * Postconditions:
+ *   out1 = (arg1 + arg2 + arg3) mod 2^26
+ *   out2 = ⌊(arg1 + arg2 + arg3) / 2^26⌋
+ *
+ * Input Bounds:
+ *   arg1: [0x0 ~> 0x1]
+ *   arg2: [0x0 ~> 0x3ffffff]
+ *   arg3: [0x0 ~> 0x3ffffff]
+ * Output Bounds:
+ *   out1: [0x0 ~> 0x3ffffff]
+ *   out2: [0x0 ~> 0x1]
+ */
+static void
+fiat_25519_addcarryx_u26(uint32_t *out1, uint8_t *out2, uint8_t arg1, uint32_t arg2, uint32_t arg3)
+{
+    uint32_t x1 = ((arg1 + arg2) + arg3);
+    uint32_t x2 = (x1 & UINT32_C(0x3ffffff));
+    uint8_t x3 = (uint8_t)(x1 >> 26);
+    *out1 = x2;
+    *out2 = x3;
+}
+
+/*
+ * The function fiat_25519_addcarryx_u25 is an addition with carry.
+ * Postconditions:
+ *   out1 = (arg1 + arg2 + arg3) mod 2^25
+ *   out2 = ⌊(arg1 + arg2 + arg3) / 2^25⌋
+ *
+ * Input Bounds:
+ *   arg1: [0x0 ~> 0x1]
+ *   arg2: [0x0 ~> 0x1ffffff]
+ *   arg3: [0x0 ~> 0x1ffffff]
+ * Output Bounds:
+ *   out1: [0x0 ~> 0x1ffffff]
+ *   out2: [0x0 ~> 0x1]
+ */
+static void
+fiat_25519_addcarryx_u25(uint32_t *out1, uint8_t *out2, uint8_t arg1, uint32_t arg2, uint32_t arg3)
+{
+    uint32_t x1 = ((arg1 + arg2) + arg3);
+    uint32_t x2 = (x1 & UINT32_C(0x1ffffff));
+    uint8_t x3 = (uint8_t)(x1 >> 25);
+    *out1 = x2;
+    *out2 = x3;
+}
+
+/*
+ * The function fiat_25519_cmovznz_u32 is a single-word conditional move.
+ * Postconditions:
+ *   out1 = (if arg1 = 0 then arg2 else arg3)
+ *
+ * Input Bounds:
+ *   arg1: [0x0 ~> 0x1]
+ *   arg2: [0x0 ~> 0xffffffff]
+ *   arg3: [0x0 ~> 0xffffffff]
+ * Output Bounds:
+ *   out1: [0x0 ~> 0xffffffff]
+ */
+static void
+fiat_25519_cmovznz_u32(uint32_t *out1, uint8_t arg1, uint32_t arg2, uint32_t arg3)
+{
+    uint8_t x1 = (!(!arg1));
+    uint32_t x2 = ((int8_t)(0x0 - x1) & UINT32_C(0xffffffff));
+    uint32_t x3 = ((x2 & arg3) | ((~x2) & arg2));
+    *out1 = x3;
+}
+
+/*
+ * The function fiat_25519_from_bytes deserializes a field element from bytes in little-endian order.
+ * Postconditions:
+ *   eval out1 mod m = bytes_eval arg1 mod m
+ *
+ * Input Bounds:
+ *   arg1: [[0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0x7f]]
+ * Output Bounds:
+ *   out1: [[0x0 ~> 0x4666666], [0x0 ~> 0x2333333], [0x0 ~> 0x4666666], [0x0 ~> 0x2333333], [0x0 ~> 0x4666666], [0x0 ~> 0x2333333], [0x0 ~> 0x4666666], [0x0 ~> 0x2333333], [0x0 ~> 0x4666666], [0x0 ~> 0x2333333]]
+ */
+static void
+fiat_25519_from_bytes(uint32_t out1[10], const uint8_t arg1[32])
+{
+    uint32_t x1 = ((uint32_t)(arg1[31]) << 18);
+    uint32_t x2 = ((uint32_t)(arg1[30]) << 10);
+    uint32_t x3 = ((uint32_t)(arg1[29]) << 2);
+    uint32_t x4 = ((uint32_t)(arg1[28]) << 20);
+    uint32_t x5 = ((uint32_t)(arg1[27]) << 12);
+    uint32_t x6 = ((uint32_t)(arg1[26]) << 4);
+    uint32_t x7 = ((uint32_t)(arg1[25]) << 21);
+    uint32_t x8 = ((uint32_t)(arg1[24]) << 13);
+    uint32_t x9 = ((uint32_t)(arg1[23]) << 5);
+    uint32_t x10 = ((uint32_t)(arg1[22]) << 23);
+    uint32_t x11 = ((uint32_t)(arg1[21]) << 15);
+    uint32_t x12 = ((uint32_t)(arg1[20]) << 7);
+    uint32_t x13 = ((uint32_t)(arg1[19]) << 24);
+    uint32_t x14 = ((uint32_t)(arg1[18]) << 16);
+    uint32_t x15 = ((uint32_t)(arg1[17]) << 8);
+    uint8_t x16 = (arg1[16]);
+    uint32_t x17 = ((uint32_t)(arg1[15]) << 18);
+    uint32_t x18 = ((uint32_t)(arg1[14]) << 10);
+    uint32_t x19 = ((uint32_t)(arg1[13]) << 2);
+    uint32_t x20 = ((uint32_t)(arg1[12]) << 19);
+    uint32_t x21 = ((uint32_t)(arg1[11]) << 11);
+    uint32_t x22 = ((uint32_t)(arg1[10]) << 3);
+    uint32_t x23 = ((uint32_t)(arg1[9]) << 21);
+    uint32_t x24 = ((uint32_t)(arg1[8]) << 13);
+    uint32_t x25 = ((uint32_t)(arg1[7]) << 5);
+    uint32_t x26 = ((uint32_t)(arg1[6]) << 22);
+    uint32_t x27 = ((uint32_t)(arg1[5]) << 14);
+    uint32_t x28 = ((uint32_t)(arg1[4]) << 6);
+    uint32_t x29 = ((uint32_t)(arg1[3]) << 24);
+    uint32_t x30 = ((uint32_t)(arg1[2]) << 16);
+    uint32_t x31 = ((uint32_t)(arg1[1]) << 8);
+    uint8_t x32 = (arg1[0]);
+    uint32_t x33 = (x32 + (x31 + (x30 + x29)));
+    uint8_t x34 = (uint8_t)(x33 >> 26);
+    uint32_t x35 = (x33 & UINT32_C(0x3ffffff));
+    uint32_t x36 = (x3 + (x2 + x1));
+    uint32_t x37 = (x6 + (x5 + x4));
+    uint32_t x38 = (x9 + (x8 + x7));
+    uint32_t x39 = (x12 + (x11 + x10));
+    uint32_t x40 = (x16 + (x15 + (x14 + x13)));
+    uint32_t x41 = (x19 + (x18 + x17));
+    uint32_t x42 = (x22 + (x21 + x20));
+    uint32_t x43 = (x25 + (x24 + x23));
+    uint32_t x44 = (x28 + (x27 + x26));
+    uint32_t x45 = (x34 + x44);
+    uint8_t x46 = (uint8_t)(x45 >> 25);
+    uint32_t x47 = (x45 & UINT32_C(0x1ffffff));
+    uint32_t x48 = (x46 + x43);
+    uint8_t x49 = (uint8_t)(x48 >> 26);
+    uint32_t x50 = (x48 & UINT32_C(0x3ffffff));
+    uint32_t x51 = (x49 + x42);
+    uint8_t x52 = (uint8_t)(x51 >> 25);
+    uint32_t x53 = (x51 & UINT32_C(0x1ffffff));
+    uint32_t x54 = (x52 + x41);
+    uint32_t x55 = (x54 & UINT32_C(0x3ffffff));
+    uint8_t x56 = (uint8_t)(x40 >> 25);
+    uint32_t x57 = (x40 & UINT32_C(0x1ffffff));
+    uint32_t x58 = (x56 + x39);
+    uint8_t x59 = (uint8_t)(x58 >> 26);
+    uint32_t x60 = (x58 & UINT32_C(0x3ffffff));
+    uint32_t x61 = (x59 + x38);
+    uint8_t x62 = (uint8_t)(x61 >> 25);
+    uint32_t x63 = (x61 & UINT32_C(0x1ffffff));
+    uint32_t x64 = (x62 + x37);
+    uint8_t x65 = (uint8_t)(x64 >> 26);
+    uint32_t x66 = (x64 & UINT32_C(0x3ffffff));
+    uint32_t x67 = (x65 + x36);
+    out1[0] = x35;
+    out1[1] = x47;
+    out1[2] = x50;
+    out1[3] = x53;
+    out1[4] = x55;
+    out1[5] = x57;
+    out1[6] = x60;
+    out1[7] = x63;
+    out1[8] = x66;
+    out1[9] = x67;
+}
+
+static void
+fe_frombytes_strict(fe *h, const uint8_t s[32])
+{
+    // |fiat_25519_from_bytes| requires the top-most bit be clear.
+    PORT_Assert((s[31] & 0x80) == 0);
+    fiat_25519_from_bytes(h->v, s);
+    assert_fe(h->v);
+}
+
+static inline void
+fe_frombytes(fe *h, const uint8_t *s)
+{
+    uint8_t s_copy[32];
+    memcpy(s_copy, s, 32);
+    s_copy[31] &= 0x7f;
+    fe_frombytes_strict(h, s_copy);
+}
 
-#include <stdint.h>
-#include <stdio.h>
+/*
+ * The function fiat_25519_to_bytes serializes a field element to bytes in little-endian order.
+ * Postconditions:
+ *   out1 = map (λ x, ⌊((eval arg1 mod m) mod 2^(8 * (x + 1))) / 2^(8 * x)⌋) [0..31]
+ *
+ * Input Bounds:
+ *   arg1: [[0x0 ~> 0x4666666], [0x0 ~> 0x2333333], [0x0 ~> 0x4666666], [0x0 ~> 0x2333333], [0x0 ~> 0x4666666], [0x0 ~> 0x2333333], [0x0 ~> 0x4666666], [0x0 ~> 0x2333333], [0x0 ~> 0x4666666], [0x0 ~> 0x2333333]]
+ * Output Bounds:
+ *   out1: [[0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0xff], [0x0 ~> 0x7f]]
+ */
+static void
+fiat_25519_to_bytes(uint8_t out1[32], const uint32_t arg1[10])
+{
+    uint32_t x1;
+    uint8_t x2;
+    fiat_25519_subborrowx_u26(&x1, &x2, 0x0, (arg1[0]), UINT32_C(0x3ffffed));
+    uint32_t x3;
+    uint8_t x4;
+    fiat_25519_subborrowx_u25(&x3, &x4, x2, (arg1[1]), UINT32_C(0x1ffffff));
+    uint32_t x5;
+    uint8_t x6;
+    fiat_25519_subborrowx_u26(&x5, &x6, x4, (arg1[2]), UINT32_C(0x3ffffff));
+    uint32_t x7;
+    uint8_t x8;
+    fiat_25519_subborrowx_u25(&x7, &x8, x6, (arg1[3]), UINT32_C(0x1ffffff));
+    uint32_t x9;
+    uint8_t x10;
+    fiat_25519_subborrowx_u26(&x9, &x10, x8, (arg1[4]), UINT32_C(0x3ffffff));
+    uint32_t x11;
+    uint8_t x12;
+    fiat_25519_subborrowx_u25(&x11, &x12, x10, (arg1[5]), UINT32_C(0x1ffffff));
+    uint32_t x13;
+    uint8_t x14;
+    fiat_25519_subborrowx_u26(&x13, &x14, x12, (arg1[6]), UINT32_C(0x3ffffff));
+    uint32_t x15;
+    uint8_t x16;
+    fiat_25519_subborrowx_u25(&x15, &x16, x14, (arg1[7]), UINT32_C(0x1ffffff));
+    uint32_t x17;
+    uint8_t x18;
+    fiat_25519_subborrowx_u26(&x17, &x18, x16, (arg1[8]), UINT32_C(0x3ffffff));
+    uint32_t x19;
+    uint8_t x20;
+    fiat_25519_subborrowx_u25(&x19, &x20, x18, (arg1[9]), UINT32_C(0x1ffffff));
+    uint32_t x21;
+    fiat_25519_cmovznz_u32(&x21, x20, 0x0, UINT32_C(0xffffffff));
+    uint32_t x22;
+    uint8_t x23;
+    fiat_25519_addcarryx_u26(&x22, &x23, 0x0, x1, (x21 & UINT32_C(0x3ffffed)));
+    uint32_t x24;
+    uint8_t x25;
+    fiat_25519_addcarryx_u25(&x24, &x25, x23, x3, (x21 & UINT32_C(0x1ffffff)));
+    uint32_t x26;
+    uint8_t x27;
+    fiat_25519_addcarryx_u26(&x26, &x27, x25, x5, (x21 & UINT32_C(0x3ffffff)));
+    uint32_t x28;
+    uint8_t x29;
+    fiat_25519_addcarryx_u25(&x28, &x29, x27, x7, (x21 & UINT32_C(0x1ffffff)));
+    uint32_t x30;
+    uint8_t x31;
+    fiat_25519_addcarryx_u26(&x30, &x31, x29, x9, (x21 & UINT32_C(0x3ffffff)));
+    uint32_t x32;
+    uint8_t x33;
+    fiat_25519_addcarryx_u25(&x32, &x33, x31, x11, (x21 & UINT32_C(0x1ffffff)));
+    uint32_t x34;
+    uint8_t x35;
+    fiat_25519_addcarryx_u26(&x34, &x35, x33, x13, (x21 & UINT32_C(0x3ffffff)));
+    uint32_t x36;
+    uint8_t x37;
+    fiat_25519_addcarryx_u25(&x36, &x37, x35, x15, (x21 & UINT32_C(0x1ffffff)));
+    uint32_t x38;
+    uint8_t x39;
+    fiat_25519_addcarryx_u26(&x38, &x39, x37, x17, (x21 & UINT32_C(0x3ffffff)));
+    uint32_t x40;
+    uint8_t x41;
+    fiat_25519_addcarryx_u25(&x40, &x41, x39, x19, (x21 & UINT32_C(0x1ffffff)));
+    uint32_t x42 = (x40 << 6);
+    uint32_t x43 = (x38 << 4);
+    uint32_t x44 = (x36 << 3);
+    uint32_t x45 = (x34 * (uint32_t)0x2);
+    uint32_t x46 = (x30 << 6);
+    uint32_t x47 = (x28 << 5);
+    uint32_t x48 = (x26 << 3);
+    uint32_t x49 = (x24 << 2);
+    uint32_t x50 = (x22 >> 8);
+    uint8_t x51 = (uint8_t)(x22 & UINT8_C(0xff));
+    uint32_t x52 = (x50 >> 8);
+    uint8_t x53 = (uint8_t)(x50 & UINT8_C(0xff));
+    uint8_t x54 = (uint8_t)(x52 >> 8);
+    uint8_t x55 = (uint8_t)(x52 & UINT8_C(0xff));
+    uint32_t x56 = (x54 + x49);
+    uint32_t x57 = (x56 >> 8);
+    uint8_t x58 = (uint8_t)(x56 & UINT8_C(0xff));
+    uint32_t x59 = (x57 >> 8);
+    uint8_t x60 = (uint8_t)(x57 & UINT8_C(0xff));
+    uint8_t x61 = (uint8_t)(x59 >> 8);
+    uint8_t x62 = (uint8_t)(x59 & UINT8_C(0xff));
+    uint32_t x63 = (x61 + x48);
+    uint32_t x64 = (x63 >> 8);
+    uint8_t x65 = (uint8_t)(x63 & UINT8_C(0xff));
+    uint32_t x66 = (x64 >> 8);
+    uint8_t x67 = (uint8_t)(x64 & UINT8_C(0xff));
+    uint8_t x68 = (uint8_t)(x66 >> 8);
+    uint8_t x69 = (uint8_t)(x66 & UINT8_C(0xff));
+    uint32_t x70 = (x68 + x47);
+    uint32_t x71 = (x70 >> 8);
+    uint8_t x72 = (uint8_t)(x70 & UINT8_C(0xff));
+    uint32_t x73 = (x71 >> 8);
+    uint8_t x74 = (uint8_t)(x71 & UINT8_C(0xff));
+    uint8_t x75 = (uint8_t)(x73 >> 8);
+    uint8_t x76 = (uint8_t)(x73 & UINT8_C(0xff));
+    uint32_t x77 = (x75 + x46);
+    uint32_t x78 = (x77 >> 8);
+    uint8_t x79 = (uint8_t)(x77 & UINT8_C(0xff));
+    uint32_t x80 = (x78 >> 8);
+    uint8_t x81 = (uint8_t)(x78 & UINT8_C(0xff));
+    uint8_t x82 = (uint8_t)(x80 >> 8);
+    uint8_t x83 = (uint8_t)(x80 & UINT8_C(0xff));
+    uint8_t x84 = (uint8_t)(x82 & UINT8_C(0xff));
+    uint32_t x85 = (x32 >> 8);
+    uint8_t x86 = (uint8_t)(x32 & UINT8_C(0xff));
+    uint32_t x87 = (x85 >> 8);
+    uint8_t x88 = (uint8_t)(x85 & UINT8_C(0xff));
+    uint8_t x89 = (uint8_t)(x87 >> 8);
+    uint8_t x90 = (uint8_t)(x87 & UINT8_C(0xff));
+    uint32_t x91 = (x89 + x45);
+    uint32_t x92 = (x91 >> 8);
+    uint8_t x93 = (uint8_t)(x91 & UINT8_C(0xff));
+    uint32_t x94 = (x92 >> 8);
+    uint8_t x95 = (uint8_t)(x92 & UINT8_C(0xff));
+    uint8_t x96 = (uint8_t)(x94 >> 8);
+    uint8_t x97 = (uint8_t)(x94 & UINT8_C(0xff));
+    uint32_t x98 = (x96 + x44);
+    uint32_t x99 = (x98 >> 8);
+    uint8_t x100 = (uint8_t)(x98 & UINT8_C(0xff));
+    uint32_t x101 = (x99 >> 8);
+    uint8_t x102 = (uint8_t)(x99 & UINT8_C(0xff));
+    uint8_t x103 = (uint8_t)(x101 >> 8);
+    uint8_t x104 = (uint8_t)(x101 & UINT8_C(0xff));
+    uint32_t x105 = (x103 + x43);
+    uint32_t x106 = (x105 >> 8);
+    uint8_t x107 = (uint8_t)(x105 & UINT8_C(0xff));
+    uint32_t x108 = (x106 >> 8);
+    uint8_t x109 = (uint8_t)(x106 & UINT8_C(0xff));
+    uint8_t x110 = (uint8_t)(x108 >> 8);
+    uint8_t x111 = (uint8_t)(x108 & UINT8_C(0xff));
+    uint32_t x112 = (x110 + x42);
+    uint32_t x113 = (x112 >> 8);
+    uint8_t x114 = (uint8_t)(x112 & UINT8_C(0xff));
+    uint32_t x115 = (x113 >> 8);
+    uint8_t x116 = (uint8_t)(x113 & UINT8_C(0xff));
+    uint8_t x117 = (uint8_t)(x115 >> 8);
+    uint8_t x118 = (uint8_t)(x115 & UINT8_C(0xff));
+    out1[0] = x51;
+    out1[1] = x53;
+    out1[2] = x55;
+    out1[3] = x58;
+    out1[4] = x60;
+    out1[5] = x62;
+    out1[6] = x65;
+    out1[7] = x67;
+    out1[8] = x69;
+    out1[9] = x72;
+    out1[10] = x74;
+    out1[11] = x76;
+    out1[12] = x79;
+    out1[13] = x81;
+    out1[14] = x83;
+    out1[15] = x84;
+    out1[16] = x86;
+    out1[17] = x88;
+    out1[18] = x90;
+    out1[19] = x93;
+    out1[20] = x95;
+    out1[21] = x97;
+    out1[22] = x100;
+    out1[23] = x102;
+    out1[24] = x104;
+    out1[25] = x107;
+    out1[26] = x109;
+    out1[27] = x111;
+    out1[28] = x114;
+    out1[29] = x116;
+    out1[30] = x118;
+    out1[31] = x117;
+}
 
-typedef uint32_t elem[32];
+static inline void
+fe_tobytes(uint8_t s[32], const fe *f)
+{
+    assert_fe(f->v);
+    fiat_25519_to_bytes(s, f->v);
+}
+
+/* h = f */
+static inline void
+fe_copy(fe *h, const fe *f)
+{
+    memmove(h, f, sizeof(fe));
+}
+
+static inline void
+fe_copy_lt(fe_loose *h, const fe *f)
+{
+    PORT_Assert(sizeof(fe) == sizeof(fe_loose));
+    memmove(h, f, sizeof(fe));
+}
+
+/*
+ * h = 0
+ */
+static inline void
+fe_0(fe *h)
+{
+    memset(h, 0, sizeof(fe));
+}
+
+/*
+ * h = 1
+ */
+static inline void
+fe_1(fe *h)
+{
+    memset(h, 0, sizeof(fe));
+    h->v[0] = 1;
+}
+/*
+ * The function fiat_25519_add adds two field elements.
+ * Postconditions:
+ *   eval out1 mod m = (eval arg1 + eval arg2) mod m
+ *
+ * Input Bounds:
+ *   arg1: [[0x0 ~> 0x4666666], [0x0 ~> 0x2333333], [0x0 ~> 0x4666666], [0x0 ~> 0x2333333], [0x0 ~> 0x4666666], [0x0 ~> 0x2333333], [0x0 ~> 0x4666666], [0x0 ~> 0x2333333], [0x0 ~> 0x4666666], [0x0 ~> 0x2333333]]
+ *   arg2: [[0x0 ~> 0x4666666], [0x0 ~> 0x2333333], [0x0 ~> 0x4666666], [0x0 ~> 0x2333333], [0x0 ~> 0x4666666], [0x0 ~> 0x2333333], [0x0 ~> 0x4666666], [0x0 ~> 0x2333333], [0x0 ~> 0x4666666], [0x0 ~> 0x2333333]]
+ * Output Bounds:
+ *   out1: [[0x0 ~> 0xd333332], [0x0 ~> 0x6999999], [0x0 ~> 0xd333332], [0x0 ~> 0x6999999], [0x0 ~> 0xd333332], [0x0 ~> 0x6999999], [0x0 ~> 0xd333332], [0x0 ~> 0x6999999], [0x0 ~> 0xd333332], [0x0 ~> 0x6999999]]
+ */
+static void
+fiat_25519_add(uint32_t out1[10], const uint32_t arg1[10], const uint32_t arg2[10])
+{
+    uint32_t x1 = ((arg1[0]) + (arg2[0]));
+    uint32_t x2 = ((arg1[1]) + (arg2[1]));
+    uint32_t x3 = ((arg1[2]) + (arg2[2]));
+    uint32_t x4 = ((arg1[3]) + (arg2[3]));
+    uint32_t x5 = ((arg1[4]) + (arg2[4]));
+    uint32_t x6 = ((arg1[5]) + (arg2[5]));
+    uint32_t x7 = ((arg1[6]) + (arg2[6]));
+    uint32_t x8 = ((arg1[7]) + (arg2[7]));
+    uint32_t x9 = ((arg1[8]) + (arg2[8]));
+    uint32_t x10 = ((arg1[9]) + (arg2[9]));
+    out1[0] = x1;
+    out1[1] = x2;
+    out1[2] = x3;
+    out1[3] = x4;
+    out1[4] = x5;
+    out1[5] = x6;
+    out1[6] = x7;
+    out1[7] = x8;
+    out1[8] = x9;
+    out1[9] = x10;
+}
 
 /*
  * Add two field elements.
- * out = a + b
+ * h = f + g
+ */
+static inline void
+fe_add(fe_loose *h, const fe *f, const fe *g)
+{
+    assert_fe(f->v);
+    assert_fe(g->v);
+    fiat_25519_add(h->v, f->v, g->v);
+    assert_fe_loose(h->v);
+}
+
+/*
+ * The function fiat_25519_sub subtracts two field elements.
+ * Postconditions:
+ *   eval out1 mod m = (eval arg1 - eval arg2) mod m
+ *
+ * Input Bounds:
+ *   arg1: [[0x0 ~> 0x4666666], [0x0 ~> 0x2333333], [0x0 ~> 0x4666666], [0x0 ~> 0x2333333], [0x0 ~> 0x4666666], [0x0 ~> 0x2333333], [0x0 ~> 0x4666666], [0x0 ~> 0x2333333], [0x0 ~> 0x4666666], [0x0 ~> 0x2333333]]
+ *   arg2: [[0x0 ~> 0x4666666], [0x0 ~> 0x2333333], [0x0 ~> 0x4666666], [0x0 ~> 0x2333333], [0x0 ~> 0x4666666], [0x0 ~> 0x2333333], [0x0 ~> 0x4666666], [0x0 ~> 0x2333333], [0x0 ~> 0x4666666], [0x0 ~> 0x2333333]]
+ * Output Bounds:
+ *   out1: [[0x0 ~> 0xd333332], [0x0 ~> 0x6999999], [0x0 ~> 0xd333332], [0x0 ~> 0x6999999], [0x0 ~> 0xd333332], [0x0 ~> 0x6999999], [0x0 ~> 0xd333332], [0x0 ~> 0x6999999], [0x0 ~> 0xd333332], [0x0 ~> 0x6999999]]
  */
 static void
-add(elem out, const elem a, const elem b)
+fiat_25519_sub(uint32_t out1[10], const uint32_t arg1[10], const uint32_t arg2[10])
 {
-    uint32_t j;
-    uint32_t u = 0;
-    for (j = 0; j < 31; ++j) {
-        u += a[j] + b[j];
-        out[j] = u & 0xFF;
-        u >>= 8;
-    }
-    u += a[31] + b[31];
-    out[31] = u;
+    uint32_t x1 = ((UINT32_C(0x7ffffda) + (arg1[0])) - (arg2[0]));
+    uint32_t x2 = ((UINT32_C(0x3fffffe) + (arg1[1])) - (arg2[1]));
+    uint32_t x3 = ((UINT32_C(0x7fffffe) + (arg1[2])) - (arg2[2]));
+    uint32_t x4 = ((UINT32_C(0x3fffffe) + (arg1[3])) - (arg2[3]));
+    uint32_t x5 = ((UINT32_C(0x7fffffe) + (arg1[4])) - (arg2[4]));
+    uint32_t x6 = ((UINT32_C(0x3fffffe) + (arg1[5])) - (arg2[5]));
+    uint32_t x7 = ((UINT32_C(0x7fffffe) + (arg1[6])) - (arg2[6]));
+    uint32_t x8 = ((UINT32_C(0x3fffffe) + (arg1[7])) - (arg2[7]));
+    uint32_t x9 = ((UINT32_C(0x7fffffe) + (arg1[8])) - (arg2[8]));
+    uint32_t x10 = ((UINT32_C(0x3fffffe) + (arg1[9])) - (arg2[9]));
+    out1[0] = x1;
+    out1[1] = x2;
+    out1[2] = x3;
+    out1[3] = x4;
+    out1[4] = x5;
+    out1[5] = x6;
+    out1[6] = x7;
+    out1[7] = x8;
+    out1[8] = x9;
+    out1[9] = x10;
 }
 
 /*
  * Subtract two field elements.
- * out = a - b
- */
-static void
-sub(elem out, const elem a, const elem b)
-{
-    uint32_t j;
-    uint32_t u;
-    u = 218;
-    for (j = 0; j < 31; ++j) {
-        u += a[j] + 0xFF00 - b[j];
-        out[j] = u & 0xFF;
-        u >>= 8;
-    }
-    u += a[31] - b[31];
-    out[31] = u;
-}
-
-/*
- * "Squeeze" an element after multiplication (and square).
- */
-static void
-squeeze(elem a)
-{
-    uint32_t j;
-    uint32_t u;
-    u = 0;
-    for (j = 0; j < 31; ++j) {
-        u += a[j];
-        a[j] = u & 0xFF;
-        u >>= 8;
-    }
-    u += a[31];
-    a[31] = u & 0x7F;
-    u = 19 * (u >> 7);
-    for (j = 0; j < 31; ++j) {
-        u += a[j];
-        a[j] = u & 0xFF;
-        u >>= 8;
-    }
-    a[31] += u;
-}
-
-static const elem minusp = { 19, 0, 0, 0, 0, 0, 0, 0,
-                             0, 0, 0, 0, 0, 0, 0, 0,
-                             0, 0, 0, 0, 0, 0, 0, 0,
-                             0, 0, 0, 0, 0, 0, 0, 128 };
-
-/*
- * Reduce point a by 2^255-19
- */
-static void
-reduce(elem a)
-{
-    elem aorig;
-    uint32_t j;
-    uint32_t negative;
-
-    for (j = 0; j < 32; ++j) {
-        aorig[j] = a[j];
-    }
-    add(a, a, minusp);
-    negative = 1 + ~((a[31] >> 7) & 1);
-    for (j = 0; j < 32; ++j) {
-        a[j] ^= negative & (aorig[j] ^ a[j]);
-    }
-}
-
-/*
- * Multiplication and squeeze
- * out = a * b
+ * h = f - g
  */
 static void
-mult(elem out, const elem a, const elem b)
-{
-    uint32_t i;
-    uint32_t j;
-    uint32_t u;
-
-    for (i = 0; i < 32; ++i) {
-        u = 0;
-        for (j = 0; j <= i; ++j) {
-            u += a[j] * b[i - j];
-        }
-        for (j = i + 1; j < 32; ++j) {
-            u += 38 * a[j] * b[i + 32 - j];
-        }
-        out[i] = u;
-    }
-    squeeze(out);
-}
-
-/*
- * Multiplication
- * out = 121665 * a
- */
-static void
-mult121665(elem out, const elem a)
+fe_sub(fe_loose *h, const fe *f, const fe *g)
 {
-    uint32_t j;
-    uint32_t u;
-
-    u = 0;
-    for (j = 0; j < 31; ++j) {
-        u += 121665 * a[j];
-        out[j] = u & 0xFF;
-        u >>= 8;
-    }
-    u += 121665 * a[31];
-    out[31] = u & 0x7F;
-    u = 19 * (u >> 7);
-    for (j = 0; j < 31; ++j) {
-        u += out[j];
-        out[j] = u & 0xFF;
-        u >>= 8;
-    }
-    u += out[j];
-    out[j] = u;
-}
-
-/*
- * Square a and squeeze the result.
- * out = a * a
- */
-static void
-square(elem out, const elem a)
-{
-    uint32_t i;
-    uint32_t j;
-    uint32_t u;
-
-    for (i = 0; i < 32; ++i) {
-        u = 0;
-        for (j = 0; j < i - j; ++j) {
-            u += a[j] * a[i - j];
-        }
-        for (j = i + 1; j < i + 32 - j; ++j) {
-            u += 38 * a[j] * a[i + 32 - j];
-        }
-        u *= 2;
-        if ((i & 1) == 0) {
-            u += a[i / 2] * a[i / 2];
-            u += 38 * a[i / 2 + 16] * a[i / 2 + 16];
-        }
-        out[i] = u;
-    }
-    squeeze(out);
-}
-
-/*
- * Constant time swap between r and s depending on b
- */
-static void
-cswap(uint32_t p[64], uint32_t q[64], uint32_t b)
-{
-    uint32_t j;
-    uint32_t swap = 1 + ~b;
-
-    for (j = 0; j < 64; ++j) {
-        const uint32_t t = swap & (p[j] ^ q[j]);
-        p[j] ^= t;
-        q[j] ^= t;
-    }
+    assert_fe(f->v);
+    assert_fe(g->v);
+    fiat_25519_sub(h->v, f->v, g->v);
+    assert_fe_loose(h->v);
 }
 
 /*
- * Montgomery ladder
+ * The function fiat_25519_carry_mul multiplies two field elements and reduces the result.
+ * Postconditions:
+ *   eval out1 mod m = (eval arg1 * eval arg2) mod m
+ *
+ * Input Bounds:
+ *   arg1: [[0x0 ~> 0xd333332], [0x0 ~> 0x6999999], [0x0 ~> 0xd333332], [0x0 ~> 0x6999999], [0x0 ~> 0xd333332], [0x0 ~> 0x6999999], [0x0 ~> 0xd333332], [0x0 ~> 0x6999999], [0x0 ~> 0xd333332], [0x0 ~> 0x6999999]]
+ *   arg2: [[0x0 ~> 0xd333332], [0x0 ~> 0x6999999], [0x0 ~> 0xd333332], [0x0 ~> 0x6999999], [0x0 ~> 0xd333332], [0x0 ~> 0x6999999], [0x0 ~> 0xd333332], [0x0 ~> 0x6999999], [0x0 ~> 0xd333332], [0x0 ~> 0x6999999]]
+ * Output Bounds:
+ *   out1: [[0x0 ~> 0x4666666], [0x0 ~> 0x2333333], [0x0 ~> 0x4666666], [0x0 ~> 0x2333333], [0x0 ~> 0x4666666], [0x0 ~> 0x2333333], [0x0 ~> 0x4666666], [0x0 ~> 0x2333333], [0x0 ~> 0x4666666], [0x0 ~> 0x2333333]]
  */
 static void
-monty(elem x_2_out, elem z_2_out,
-      const elem point, const elem scalar)
+fiat_25519_carry_mul(uint32_t out1[10], const uint32_t arg1[10], const uint32_t arg2[10])
 {
-    uint32_t x_3[64] = { 0 };
-    uint32_t x_2[64] = { 0 };
-    uint32_t a0[64];
-    uint32_t a1[64];
-    uint32_t b0[64];
-    uint32_t b1[64];
-    uint32_t c1[64];
-    uint32_t r[32];
-    uint32_t s[32];
-    uint32_t t[32];
-    uint32_t u[32];
-    uint32_t swap = 0;
-    uint32_t k_t = 0;
-    int j;
-
-    for (j = 0; j < 32; ++j) {
-        x_3[j] = point[j];
-    }
-    x_3[32] = 1;
-    x_2[0] = 1;
+    uint64_t x1 = ((uint64_t)(arg1[9]) * ((arg2[9]) * ((uint32_t)0x2 * UINT8_C(0x13))));
+    uint64_t x2 = ((uint64_t)(arg1[9]) * ((arg2[8]) * (uint32_t)UINT8_C(0x13)));
+    uint64_t x3 = ((uint64_t)(arg1[9]) * ((arg2[7]) * ((uint32_t)0x2 * UINT8_C(0x13))));
+    uint64_t x4 = ((uint64_t)(arg1[9]) * ((arg2[6]) * (uint32_t)UINT8_C(0x13)));
+    uint64_t x5 = ((uint64_t)(arg1[9]) * ((arg2[5]) * ((uint32_t)0x2 * UINT8_C(0x13))));
+    uint64_t x6 = ((uint64_t)(arg1[9]) * ((arg2[4]) * (uint32_t)UINT8_C(0x13)));
+    uint64_t x7 = ((uint64_t)(arg1[9]) * ((arg2[3]) * ((uint32_t)0x2 * UINT8_C(0x13))));
+    uint64_t x8 = ((uint64_t)(arg1[9]) * ((arg2[2]) * (uint32_t)UINT8_C(0x13)));
+    uint64_t x9 = ((uint64_t)(arg1[9]) * ((arg2[1]) * ((uint32_t)0x2 * UINT8_C(0x13))));
+    uint64_t x10 = ((uint64_t)(arg1[8]) * ((arg2[9]) * (uint32_t)UINT8_C(0x13)));
+    uint64_t x11 = ((uint64_t)(arg1[8]) * ((arg2[8]) * (uint32_t)UINT8_C(0x13)));
+    uint64_t x12 = ((uint64_t)(arg1[8]) * ((arg2[7]) * (uint32_t)UINT8_C(0x13)));
+    uint64_t x13 = ((uint64_t)(arg1[8]) * ((arg2[6]) * (uint32_t)UINT8_C(0x13)));
+    uint64_t x14 = ((uint64_t)(arg1[8]) * ((arg2[5]) * (uint32_t)UINT8_C(0x13)));
+    uint64_t x15 = ((uint64_t)(arg1[8]) * ((arg2[4]) * (uint32_t)UINT8_C(0x13)));
+    uint64_t x16 = ((uint64_t)(arg1[8]) * ((arg2[3]) * (uint32_t)UINT8_C(0x13)));
+    uint64_t x17 = ((uint64_t)(arg1[8]) * ((arg2[2]) * (uint32_t)UINT8_C(0x13)));
+    uint64_t x18 = ((uint64_t)(arg1[7]) * ((arg2[9]) * ((uint32_t)0x2 * UINT8_C(0x13))));
+    uint64_t x19 = ((uint64_t)(arg1[7]) * ((arg2[8]) * (uint32_t)UINT8_C(0x13)));
+    uint64_t x20 = ((uint64_t)(arg1[7]) * ((arg2[7]) * ((uint32_t)0x2 * UINT8_C(0x13))));
+    uint64_t x21 = ((uint64_t)(arg1[7]) * ((arg2[6]) * (uint32_t)UINT8_C(0x13)));
+    uint64_t x22 = ((uint64_t)(arg1[7]) * ((arg2[5]) * ((uint32_t)0x2 * UINT8_C(0x13))));
+    uint64_t x23 = ((uint64_t)(arg1[7]) * ((arg2[4]) * (uint32_t)UINT8_C(0x13)));
+    uint64_t x24 = ((uint64_t)(arg1[7]) * ((arg2[3]) * ((uint32_t)0x2 * UINT8_C(0x13))));
+    uint64_t x25 = ((uint64_t)(arg1[6]) * ((arg2[9]) * (uint32_t)UINT8_C(0x13)));
+    uint64_t x26 = ((uint64_t)(arg1[6]) * ((arg2[8]) * (uint32_t)UINT8_C(0x13)));
+    uint64_t x27 = ((uint64_t)(arg1[6]) * ((arg2[7]) * (uint32_t)UINT8_C(0x13)));
+    uint64_t x28 = ((uint64_t)(arg1[6]) * ((arg2[6]) * (uint32_t)UINT8_C(0x13)));
+    uint64_t x29 = ((uint64_t)(arg1[6]) * ((arg2[5]) * (uint32_t)UINT8_C(0x13)));
+    uint64_t x30 = ((uint64_t)(arg1[6]) * ((arg2[4]) * (uint32_t)UINT8_C(0x13)));
+    uint64_t x31 = ((uint64_t)(arg1[5]) * ((arg2[9]) * ((uint32_t)0x2 * UINT8_C(0x13))));
+    uint64_t x32 = ((uint64_t)(arg1[5]) * ((arg2[8]) * (uint32_t)UINT8_C(0x13)));
+    uint64_t x33 = ((uint64_t)(arg1[5]) * ((arg2[7]) * ((uint32_t)0x2 * UINT8_C(0x13))));
+    uint64_t x34 = ((uint64_t)(arg1[5]) * ((arg2[6]) * (uint32_t)UINT8_C(0x13)));
+    uint64_t x35 = ((uint64_t)(arg1[5]) * ((arg2[5]) * ((uint32_t)0x2 * UINT8_C(0x13))));
+    uint64_t x36 = ((uint64_t)(arg1[4]) * ((arg2[9]) * (uint32_t)UINT8_C(0x13)));
+    uint64_t x37 = ((uint64_t)(arg1[4]) * ((arg2[8]) * (uint32_t)UINT8_C(0x13)));
+    uint64_t x38 = ((uint64_t)(arg1[4]) * ((arg2[7]) * (uint32_t)UINT8_C(0x13)));
+    uint64_t x39 = ((uint64_t)(arg1[4]) * ((arg2[6]) * (uint32_t)UINT8_C(0x13)));
+    uint64_t x40 = ((uint64_t)(arg1[3]) * ((arg2[9]) * ((uint32_t)0x2 * UINT8_C(0x13))));
+    uint64_t x41 = ((uint64_t)(arg1[3]) * ((arg2[8]) * (uint32_t)UINT8_C(0x13)));
+    uint64_t x42 = ((uint64_t)(arg1[3]) * ((arg2[7]) * ((uint32_t)0x2 * UINT8_C(0x13))));
+    uint64_t x43 = ((uint64_t)(arg1[2]) * ((arg2[9]) * (uint32_t)UINT8_C(0x13)));
+    uint64_t x44 = ((uint64_t)(arg1[2]) * ((arg2[8]) * (uint32_t)UINT8_C(0x13)));
+    uint64_t x45 = ((uint64_t)(arg1[1]) * ((arg2[9]) * ((uint32_t)0x2 * UINT8_C(0x13))));
+    uint64_t x46 = ((uint64_t)(arg1[9]) * (arg2[0]));
+    uint64_t x47 = ((uint64_t)(arg1[8]) * (arg2[1]));
+    uint64_t x48 = ((uint64_t)(arg1[8]) * (arg2[0]));
+    uint64_t x49 = ((uint64_t)(arg1[7]) * (arg2[2]));
+    uint64_t x50 = ((uint64_t)(arg1[7]) * ((arg2[1]) * (uint32_t)0x2));
+    uint64_t x51 = ((uint64_t)(arg1[7]) * (arg2[0]));
+    uint64_t x52 = ((uint64_t)(arg1[6]) * (arg2[3]));
+    uint64_t x53 = ((uint64_t)(arg1[6]) * (arg2[2]));
+    uint64_t x54 = ((uint64_t)(arg1[6]) * (arg2[1]));
+    uint64_t x55 = ((uint64_t)(arg1[6]) * (arg2[0]));
+    uint64_t x56 = ((uint64_t)(arg1[5]) * (arg2[4]));
+    uint64_t x57 = ((uint64_t)(arg1[5]) * ((arg2[3]) * (uint32_t)0x2));
+    uint64_t x58 = ((uint64_t)(arg1[5]) * (arg2[2]));
+    uint64_t x59 = ((uint64_t)(arg1[5]) * ((arg2[1]) * (uint32_t)0x2));
+    uint64_t x60 = ((uint64_t)(arg1[5]) * (arg2[0]));
+    uint64_t x61 = ((uint64_t)(arg1[4]) * (arg2[5]));
+    uint64_t x62 = ((uint64_t)(arg1[4]) * (arg2[4]));
+    uint64_t x63 = ((uint64_t)(arg1[4]) * (arg2[3]));
+    uint64_t x64 = ((uint64_t)(arg1[4]) * (arg2[2]));
+    uint64_t x65 = ((uint64_t)(arg1[4]) * (arg2[1]));
+    uint64_t x66 = ((uint64_t)(arg1[4]) * (arg2[0]));
+    uint64_t x67 = ((uint64_t)(arg1[3]) * (arg2[6]));
+    uint64_t x68 = ((uint64_t)(arg1[3]) * ((arg2[5]) * (uint32_t)0x2));
+    uint64_t x69 = ((uint64_t)(arg1[3]) * (arg2[4]));
+    uint64_t x70 = ((uint64_t)(arg1[3]) * ((arg2[3]) * (uint32_t)0x2));
+    uint64_t x71 = ((uint64_t)(arg1[3]) * (arg2[2]));
+    uint64_t x72 = ((uint64_t)(arg1[3]) * ((arg2[1]) * (uint32_t)0x2));
+    uint64_t x73 = ((uint64_t)(arg1[3]) * (arg2[0]));
+    uint64_t x74 = ((uint64_t)(arg1[2]) * (arg2[7]));
+    uint64_t x75 = ((uint64_t)(arg1[2]) * (arg2[6]));
+    uint64_t x76 = ((uint64_t)(arg1[2]) * (arg2[5]));
+    uint64_t x77 = ((uint64_t)(arg1[2]) * (arg2[4]));
+    uint64_t x78 = ((uint64_t)(arg1[2]) * (arg2[3]));
+    uint64_t x79 = ((uint64_t)(arg1[2]) * (arg2[2]));
+    uint64_t x80 = ((uint64_t)(arg1[2]) * (arg2[1]));
+    uint64_t x81 = ((uint64_t)(arg1[2]) * (arg2[0]));
+    uint64_t x82 = ((uint64_t)(arg1[1]) * (arg2[8]));
+    uint64_t x83 = ((uint64_t)(arg1[1]) * ((arg2[7]) * (uint32_t)0x2));
+    uint64_t x84 = ((uint64_t)(arg1[1]) * (arg2[6]));
+    uint64_t x85 = ((uint64_t)(arg1[1]) * ((arg2[5]) * (uint32_t)0x2));
+    uint64_t x86 = ((uint64_t)(arg1[1]) * (arg2[4]));
+    uint64_t x87 = ((uint64_t)(arg1[1]) * ((arg2[3]) * (uint32_t)0x2));
+    uint64_t x88 = ((uint64_t)(arg1[1]) * (arg2[2]));
+    uint64_t x89 = ((uint64_t)(arg1[1]) * ((arg2[1]) * (uint32_t)0x2));
+    uint64_t x90 = ((uint64_t)(arg1[1]) * (arg2[0]));
+    uint64_t x91 = ((uint64_t)(arg1[0]) * (arg2[9]));
+    uint64_t x92 = ((uint64_t)(arg1[0]) * (arg2[8]));
+    uint64_t x93 = ((uint64_t)(arg1[0]) * (arg2[7]));
+    uint64_t x94 = ((uint64_t)(arg1[0]) * (arg2[6]));
+    uint64_t x95 = ((uint64_t)(arg1[0]) * (arg2[5]));
+    uint64_t x96 = ((uint64_t)(arg1[0]) * (arg2[4]));
+    uint64_t x97 = ((uint64_t)(arg1[0]) * (arg2[3]));
+    uint64_t x98 = ((uint64_t)(arg1[0]) * (arg2[2]));
+    uint64_t x99 = ((uint64_t)(arg1[0]) * (arg2[1]));
+    uint64_t x100 = ((uint64_t)(arg1[0]) * (arg2[0]));
+    uint64_t x101 = (x100 + (x45 + (x44 + (x42 + (x39 + (x35 + (x30 + (x24 + (x17 + x9)))))))));
+    uint64_t x102 = (x101 >> 26);
+    uint32_t x103 = (uint32_t)(x101 & UINT32_C(0x3ffffff));
+    uint64_t x104 = (x91 + (x82 + (x74 + (x67 + (x61 + (x56 + (x52 + (x49 + (x47 + x46)))))))));
+    uint64_t x105 = (x92 + (x83 + (x75 + (x68 + (x62 + (x57 + (x53 + (x50 + (x48 + x1)))))))));
+    uint64_t x106 = (x93 + (x84 + (x76 + (x69 + (x63 + (x58 + (x54 + (x51 + (x10 + x2)))))))));
+    uint64_t x107 = (x94 + (x85 + (x77 + (x70 + (x64 + (x59 + (x55 + (x18 + (x11 + x3)))))))));
+    uint64_t x108 = (x95 + (x86 + (x78 + (x71 + (x65 + (x60 + (x25 + (x19 + (x12 + x4)))))))));
+    uint64_t x109 = (x96 + (x87 + (x79 + (x72 + (x66 + (x31 + (x26 + (x20 + (x13 + x5)))))))));
+    uint64_t x110 = (x97 + (x88 + (x80 + (x73 + (x36 + (x32 + (x27 + (x21 + (x14 + x6)))))))));
+    uint64_t x111 = (x98 + (x89 + (x81 + (x40 + (x37 + (x33 + (x28 + (x22 + (x15 + x7)))))))));
+    uint64_t x112 = (x99 + (x90 + (x43 + (x41 + (x38 + (x34 + (x29 + (x23 + (x16 + x8)))))))));
+    uint64_t x113 = (x102 + x112);
+    uint64_t x114 = (x113 >> 25);
+    uint32_t x115 = (uint32_t)(x113 & UINT32_C(0x1ffffff));
+    uint64_t x116 = (x114 + x111);
+    uint64_t x117 = (x116 >> 26);
+    uint32_t x118 = (uint32_t)(x116 & UINT32_C(0x3ffffff));
+    uint64_t x119 = (x117 + x110);
+    uint64_t x120 = (x119 >> 25);
+    uint32_t x121 = (uint32_t)(x119 & UINT32_C(0x1ffffff));
+    uint64_t x122 = (x120 + x109);
+    uint64_t x123 = (x122 >> 26);
+    uint32_t x124 = (uint32_t)(x122 & UINT32_C(0x3ffffff));
+    uint64_t x125 = (x123 + x108);
+    uint64_t x126 = (x125 >> 25);
+    uint32_t x127 = (uint32_t)(x125 & UINT32_C(0x1ffffff));
+    uint64_t x128 = (x126 + x107);
+    uint64_t x129 = (x128 >> 26);
+    uint32_t x130 = (uint32_t)(x128 & UINT32_C(0x3ffffff));
+    uint64_t x131 = (x129 + x106);
+    uint64_t x132 = (x131 >> 25);
+    uint32_t x133 = (uint32_t)(x131 & UINT32_C(0x1ffffff));
+    uint64_t x134 = (x132 + x105);
+    uint64_t x135 = (x134 >> 26);
+    uint32_t x136 = (uint32_t)(x134 & UINT32_C(0x3ffffff));
+    uint64_t x137 = (x135 + x104);
+    uint64_t x138 = (x137 >> 25);
+    uint32_t x139 = (uint32_t)(x137 & UINT32_C(0x1ffffff));
+    uint64_t x140 = (x138 * (uint64_t)UINT8_C(0x13));
+    uint64_t x141 = (x103 + x140);
+    uint32_t x142 = (uint32_t)(x141 >> 26);
+    uint32_t x143 = (uint32_t)(x141 & UINT32_C(0x3ffffff));
+    uint32_t x144 = (x142 + x115);
+    uint32_t x145 = (x144 >> 25);
+    uint32_t x146 = (x144 & UINT32_C(0x1ffffff));
+    uint32_t x147 = (x145 + x118);
+    out1[0] = x143;
+    out1[1] = x146;
+    out1[2] = x147;
+    out1[3] = x121;
+    out1[4] = x124;
+    out1[5] = x127;
+    out1[6] = x130;
+    out1[7] = x133;
+    out1[8] = x136;
+    out1[9] = x139;
+}
 
-    for (j = 254; j >= 0; --j) {
-        k_t = (scalar[j >> 3] >> (j & 7)) & 1;
-        swap ^= k_t;
-        cswap(x_2, x_3, swap);
-        swap = k_t;
-        add(a0, x_2, x_2 + 32);
-        sub(a0 + 32, x_2, x_2 + 32);
-        add(a1, x_3, x_3 + 32);
-        sub(a1 + 32, x_3, x_3 + 32);
-        square(b0, a0);
-        square(b0 + 32, a0 + 32);
-        mult(b1, a1, a0 + 32);
-        mult(b1 + 32, a1 + 32, a0);
-        add(c1, b1, b1 + 32);
-        sub(c1 + 32, b1, b1 + 32);
-        square(r, c1 + 32);
-        sub(s, b0, b0 + 32);
-        mult121665(t, s);
-        add(u, t, b0);
-        mult(x_2, b0, b0 + 32);
-        mult(x_2 + 32, s, u);
-        square(x_3, c1);
-        mult(x_3 + 32, r, point);
-    }
+static void
+fe_mul(uint32_t out1[10], const uint32_t arg1[10], const uint32_t arg2[10])
+{
+    assert_fe_loose(arg1);
+    assert_fe_loose(arg2);
+    fiat_25519_carry_mul(out1, arg1, arg2);
+    assert_fe(out1);
+}
 
-    cswap(x_2, x_3, swap);
-    for (j = 0; j < 32; ++j) {
-        x_2_out[j] = x_2[j];
-    }
-    for (j = 0; j < 32; ++j) {
-        z_2_out[j] = x_2[j + 32];
-    }
+static void
+fe_mul_ttt(fe *h, const fe *f, const fe *g)
+{
+    fe_mul(h->v, f->v, g->v);
+}
+
+static void
+fe_mul_tlt(fe *h, const fe_loose *f, const fe *g)
+{
+    fe_mul(h->v, f->v, g->v);
+}
+
+static void
+fe_mul_tll(fe *h, const fe_loose *f, const fe_loose *g)
+{
+    fe_mul(h->v, f->v, g->v);
 }
 
 static void
-recip(elem out, const elem z)
+fe_sq(uint32_t out[10], const uint32_t in1[10])
 {
-    elem z2;
-    elem z9;
-    elem z11;
-    elem z2_5_0;
-    elem z2_10_0;
-    elem z2_20_0;
-    elem z2_50_0;
-    elem z2_100_0;
-    elem t0;
-    elem t1;
+    const uint32_t x17 = in1[9];
+    const uint32_t x18 = in1[8];
+    const uint32_t x16 = in1[7];
+    const uint32_t x14 = in1[6];
+    const uint32_t x12 = in1[5];
+    const uint32_t x10 = in1[4];
+    const uint32_t x8 = in1[3];
+    const uint32_t x6 = in1[2];
+    const uint32_t x4 = in1[1];
+    const uint32_t x2 = in1[0];
+    uint64_t x19 = ((uint64_t)x2 * x2);
+    uint64_t x20 = ((uint64_t)(0x2 * x2) * x4);
+    uint64_t x21 = (0x2 * (((uint64_t)x4 * x4) + ((uint64_t)x2 * x6)));
+    uint64_t x22 = (0x2 * (((uint64_t)x4 * x6) + ((uint64_t)x2 * x8)));
+    uint64_t x23 = ((((uint64_t)x6 * x6) + ((uint64_t)(0x4 * x4) * x8)) + ((uint64_t)(0x2 * x2) * x10));
+    uint64_t x24 = (0x2 * ((((uint64_t)x6 * x8) + ((uint64_t)x4 * x10)) + ((uint64_t)x2 * x12)));
+    uint64_t x25 = (0x2 * (((((uint64_t)x8 * x8) + ((uint64_t)x6 * x10)) + ((uint64_t)x2 * x14)) + ((uint64_t)(0x2 * x4) * x12)));
+    uint64_t x26 = (0x2 * (((((uint64_t)x8 * x10) + ((uint64_t)x6 * x12)) + ((uint64_t)x4 * x14)) + ((uint64_t)x2 * x16)));
+    uint64_t x27 = (((uint64_t)x10 * x10) + (0x2 * ((((uint64_t)x6 * x14) + ((uint64_t)x2 * x18)) + (0x2 * (((uint64_t)x4 * x16) + ((uint64_t)x8 * x12))))));
+    uint64_t x28 = (0x2 * ((((((uint64_t)x10 * x12) + ((uint64_t)x8 * x14)) + ((uint64_t)x6 * x16)) + ((uint64_t)x4 * x18)) + ((uint64_t)x2 * x17)));
+    uint64_t x29 = (0x2 * (((((uint64_t)x12 * x12) + ((uint64_t)x10 * x14)) + ((uint64_t)x6 * x18)) + (0x2 * (((uint64_t)x8 * x16) + ((uint64_t)x4 * x17)))));
+    uint64_t x30 = (0x2 * (((((uint64_t)x12 * x14) + ((uint64_t)x10 * x16)) + ((uint64_t)x8 * x18)) + ((uint64_t)x6 * x17)));
+    uint64_t x31 = (((uint64_t)x14 * x14) + (0x2 * (((uint64_t)x10 * x18) + (0x2 * (((uint64_t)x12 * x16) + ((uint64_t)x8 * x17))))));
+    uint64_t x32 = (0x2 * ((((uint64_t)x14 * x16) + ((uint64_t)x12 * x18)) + ((uint64_t)x10 * x17)));
+    uint64_t x33 = (0x2 * ((((uint64_t)x16 * x16) + ((uint64_t)x14 * x18)) + ((uint64_t)(0x2 * x12) * x17)));
+    uint64_t x34 = (0x2 * (((uint64_t)x16 * x18) + ((uint64_t)x14 * x17)));
+    uint64_t x35 = (((uint64_t)x18 * x18) + ((uint64_t)(0x4 * x16) * x17));
+    uint64_t x36 = ((uint64_t)(0x2 * x18) * x17);
+    uint64_t x37 = ((uint64_t)(0x2 * x17) * x17);
+    uint64_t x38 = (x27 + (x37 << 0x4));
+    uint64_t x39 = (x38 + (x37 << 0x1));
+    uint64_t x40 = (x39 + x37);
+    uint64_t x41 = (x26 + (x36 << 0x4));
+    uint64_t x42 = (x41 + (x36 << 0x1));
+    uint64_t x43 = (x42 + x36);
+    uint64_t x44 = (x25 + (x35 << 0x4));
+    uint64_t x45 = (x44 + (x35 << 0x1));
+    uint64_t x46 = (x45 + x35);
+    uint64_t x47 = (x24 + (x34 << 0x4));
+    uint64_t x48 = (x47 + (x34 << 0x1));
+    uint64_t x49 = (x48 + x34);
+    uint64_t x50 = (x23 + (x33 << 0x4));
+    uint64_t x51 = (x50 + (x33 << 0x1));
+    uint64_t x52 = (x51 + x33);
+    uint64_t x53 = (x22 + (x32 << 0x4));
+    uint64_t x54 = (x53 + (x32 << 0x1));
+    uint64_t x55 = (x54 + x32);
+    uint64_t x56 = (x21 + (x31 << 0x4));
+    uint64_t x57 = (x56 + (x31 << 0x1));
+    uint64_t x58 = (x57 + x31);
+    uint64_t x59 = (x20 + (x30 << 0x4));
+    uint64_t x60 = (x59 + (x30 << 0x1));
+    uint64_t x61 = (x60 + x30);
+    uint64_t x62 = (x19 + (x29 << 0x4));
+    uint64_t x63 = (x62 + (x29 << 0x1));
+    uint64_t x64 = (x63 + x29);
+    uint64_t x65 = (x64 >> 0x1a);
+    uint32_t x66 = ((uint32_t)x64 & 0x3ffffff);
+    uint64_t x67 = (x65 + x61);
+    uint64_t x68 = (x67 >> 0x19);
+    uint32_t x69 = ((uint32_t)x67 & 0x1ffffff);
+    uint64_t x70 = (x68 + x58);
+    uint64_t x71 = (x70 >> 0x1a);
+    uint32_t x72 = ((uint32_t)x70 & 0x3ffffff);
+    uint64_t x73 = (x71 + x55);
+    uint64_t x74 = (x73 >> 0x19);
+    uint32_t x75 = ((uint32_t)x73 & 0x1ffffff);
+    uint64_t x76 = (x74 + x52);
+    uint64_t x77 = (x76 >> 0x1a);
+    uint32_t x78 = ((uint32_t)x76 & 0x3ffffff);
+    uint64_t x79 = (x77 + x49);
+    uint64_t x80 = (x79 >> 0x19);
+    uint32_t x81 = ((uint32_t)x79 & 0x1ffffff);
+    uint64_t x82 = (x80 + x46);
+    uint64_t x83 = (x82 >> 0x1a);
+    uint32_t x84 = ((uint32_t)x82 & 0x3ffffff);
+    uint64_t x85 = (x83 + x43);
+    uint64_t x86 = (x85 >> 0x19);
+    uint32_t x87 = ((uint32_t)x85 & 0x1ffffff);
+    uint64_t x88 = (x86 + x40);
+    uint64_t x89 = (x88 >> 0x1a);
+    uint32_t x90 = ((uint32_t)x88 & 0x3ffffff);
+    uint64_t x91 = (x89 + x28);
+    uint64_t x92 = (x91 >> 0x19);
+    uint32_t x93 = ((uint32_t)x91 & 0x1ffffff);
+    uint64_t x94 = (x66 + (0x13 * x92));
+    uint32_t x95 = (uint32_t)(x94 >> 0x1a);
+    uint32_t x96 = ((uint32_t)x94 & 0x3ffffff);
+    uint32_t x97 = (x95 + x69);
+    uint32_t x98 = (x97 >> 0x19);
+    uint32_t x99 = (x97 & 0x1ffffff);
+    out[0] = x96;
+    out[1] = x99;
+    out[2] = (x98 + x72);
+    out[3] = x75;
+    out[4] = x78;
+    out[5] = x81;
+    out[6] = x84;
+    out[7] = x87;
+    out[8] = x90;
+    out[9] = x93;
+}
+
+static void
+fe_sq_tl(fe *h, const fe_loose *f)
+{
+    fe_sq(h->v, f->v);
+}
+
+static void
+fe_sq_tt(fe *h, const fe *f)
+{
+    fe_sq(h->v, f->v);
+}
+
+static inline void
+fe_loose_invert(fe *out, const fe_loose *z)
+{
+    fe t0, t1, t2, t3;
     int i;
 
-    /* 2 */ square(z2, z);
-    /* 4 */ square(t1, z2);
-    /* 8 */ square(t0, t1);
-    /* 9 */ mult(z9, t0, z);
-    /* 11 */ mult(z11, z9, z2);
-    /* 22 */ square(t0, z11);
-    /* 2^5 - 2^0 = 31 */ mult(z2_5_0, t0, z9);
-
-    /* 2^6 - 2^1 */ square(t0, z2_5_0);
-    /* 2^7 - 2^2 */ square(t1, t0);
-    /* 2^8 - 2^3 */ square(t0, t1);
-    /* 2^9 - 2^4 */ square(t1, t0);
-    /* 2^10 - 2^5 */ square(t0, t1);
-    /* 2^10 - 2^0 */ mult(z2_10_0, t0, z2_5_0);
-
-    /* 2^11 - 2^1 */ square(t0, z2_10_0);
-    /* 2^12 - 2^2 */ square(t1, t0);
-    /* 2^20 - 2^10 */
-    for (i = 2; i < 10; i += 2) {
-        square(t0, t1);
-        square(t1, t0);
+    fe_sq_tl(&t0, z);
+    fe_sq_tt(&t1, &t0);
+    for (i = 1; i < 2; ++i) {
+        fe_sq_tt(&t1, &t1);
+    }
+    fe_mul_tlt(&t1, z, &t1);
+    fe_mul_ttt(&t0, &t0, &t1);
+    fe_sq_tt(&t2, &t0);
+    fe_mul_ttt(&t1, &t1, &t2);
+    fe_sq_tt(&t2, &t1);
+    for (i = 1; i < 5; ++i) {
+        fe_sq_tt(&t2, &t2);
+    }
+    fe_mul_ttt(&t1, &t2, &t1);
+    fe_sq_tt(&t2, &t1);
+    for (i = 1; i < 10; ++i) {
+        fe_sq_tt(&t2, &t2);
+    }
+    fe_mul_ttt(&t2, &t2, &t1);
+    fe_sq_tt(&t3, &t2);
+    for (i = 1; i < 20; ++i) {
+        fe_sq_tt(&t3, &t3);
     }
-    /* 2^20 - 2^0 */ mult(z2_20_0, t1, z2_10_0);
-
-    /* 2^21 - 2^1 */ square(t0, z2_20_0);
-    /* 2^22 - 2^2 */ square(t1, t0);
-    /* 2^40 - 2^20 */
-    for (i = 2; i < 20; i += 2) {
-        square(t0, t1);
-        square(t1, t0);
+    fe_mul_ttt(&t2, &t3, &t2);
+    fe_sq_tt(&t2, &t2);
+    for (i = 1; i < 10; ++i) {
+        fe_sq_tt(&t2, &t2);
+    }
+    fe_mul_ttt(&t1, &t2, &t1);
+    fe_sq_tt(&t2, &t1);
+    for (i = 1; i < 50; ++i) {
+        fe_sq_tt(&t2, &t2);
     }
-    /* 2^40 - 2^0 */ mult(t0, t1, z2_20_0);
+    fe_mul_ttt(&t2, &t2, &t1);
+    fe_sq_tt(&t3, &t2);
+    for (i = 1; i < 100; ++i) {
+        fe_sq_tt(&t3, &t3);
+    }
+    fe_mul_ttt(&t2, &t3, &t2);
+    fe_sq_tt(&t2, &t2);
+    for (i = 1; i < 50; ++i) {
+        fe_sq_tt(&t2, &t2);
+    }
+    fe_mul_ttt(&t1, &t2, &t1);
+    fe_sq_tt(&t1, &t1);
+    for (i = 1; i < 5; ++i) {
+        fe_sq_tt(&t1, &t1);
+    }
+    fe_mul_ttt(out, &t1, &t0);
+}
 
-    /* 2^41 - 2^1 */ square(t1, t0);
-    /* 2^42 - 2^2 */ square(t0, t1);
-    /* 2^50 - 2^10 */
-    for (i = 2; i < 10; i += 2) {
-        square(t1, t0);
-        square(t0, t1);
-    }
-    /* 2^50 - 2^0 */ mult(z2_50_0, t0, z2_10_0);
-
-    /* 2^51 - 2^1 */ square(t0, z2_50_0);
-    /* 2^52 - 2^2 */ square(t1, t0);
-    /* 2^100 - 2^50 */
-    for (i = 2; i < 50; i += 2) {
-        square(t0, t1);
-        square(t1, t0);
-    }
-    /* 2^100 - 2^0 */ mult(z2_100_0, t1, z2_50_0);
+static inline void
+fe_invert(fe *out, const fe *z)
+{
+    fe_loose l;
+    fe_copy_lt(&l, z);
+    fe_loose_invert(out, &l);
+}
 
-    /* 2^101 - 2^1 */ square(t1, z2_100_0);
-    /* 2^102 - 2^2 */ square(t0, t1);
-    /* 2^200 - 2^100 */
-    for (i = 2; i < 100; i += 2) {
-        square(t1, t0);
-        square(t0, t1);
+/* Replace (f,g) with (g,f) if b == 1;
+ * replace (f,g) with (f,g) if b == 0.
+ *
+ * Preconditions: b in {0,1}
+ */
+static inline void
+fe_cswap(fe *f, fe *g, unsigned int b)
+{
+    PORT_Assert(b < 2);
+    unsigned int i;
+    b = 0 - b;
+    for (i = 0; i < 10; i++) {
+        uint32_t x = f->v[i] ^ g->v[i];
+        x &= b;
+        f->v[i] ^= x;
+        g->v[i] ^= x;
     }
-    /* 2^200 - 2^0 */ mult(t1, t0, z2_100_0);
-
-    /* 2^201 - 2^1 */ square(t0, t1);
-    /* 2^202 - 2^2 */ square(t1, t0);
-    /* 2^250 - 2^50 */
-    for (i = 2; i < 50; i += 2) {
-        square(t0, t1);
-        square(t1, t0);
-    }
-    /* 2^250 - 2^0 */ mult(t0, t1, z2_50_0);
-
-    /* 2^251 - 2^1 */ square(t1, t0);
-    /* 2^252 - 2^2 */ square(t0, t1);
-    /* 2^253 - 2^3 */ square(t1, t0);
-    /* 2^254 - 2^4 */ square(t0, t1);
-    /* 2^255 - 2^5 */ square(t1, t0);
-    /* 2^255 - 21 */ mult(out, t1, z11);
 }
 
-/*
- * Computes q = Curve25519(p, s)
- */
-SECStatus
-ec_Curve25519_mul(PRUint8 *q, const PRUint8 *s, const PRUint8 *p)
+/* NOTE: based on fiat-crypto fe_mul, edited for in2=121666, 0, 0.*/
+static inline void
+fe_mul_121666(uint32_t out[10], const uint32_t in1[10])
 {
-    elem point = { 0 };
-    elem x_2 = { 0 };
-    elem z_2 = { 0 };
-    elem X = { 0 };
-    elem scalar = { 0 };
-    uint32_t i;
+    const uint32_t x20 = in1[9];
+    const uint32_t x21 = in1[8];
+    const uint32_t x19 = in1[7];
+    const uint32_t x17 = in1[6];
+    const uint32_t x15 = in1[5];
+    const uint32_t x13 = in1[4];
+    const uint32_t x11 = in1[3];
+    const uint32_t x9 = in1[2];
+    const uint32_t x7 = in1[1];
+    const uint32_t x5 = in1[0];
+    const uint32_t x38 = 0;
+    const uint32_t x39 = 0;
+    const uint32_t x37 = 0;
+    const uint32_t x35 = 0;
+    const uint32_t x33 = 0;
+    const uint32_t x31 = 0;
+    const uint32_t x29 = 0;
+    const uint32_t x27 = 0;
+    const uint32_t x25 = 0;
+    const uint32_t x23 = 121666;
+    uint64_t x40 = ((uint64_t)x23 * x5);
+    uint64_t x41 = (((uint64_t)x23 * x7) + ((uint64_t)x25 * x5));
+    uint64_t x42 = ((((uint64_t)(0x2 * x25) * x7) + ((uint64_t)x23 * x9)) + ((uint64_t)x27 * x5));
+    uint64_t x43 = (((((uint64_t)x25 * x9) + ((uint64_t)x27 * x7)) + ((uint64_t)x23 * x11)) + ((uint64_t)x29 * x5));
+    uint64_t x44 = (((((uint64_t)x27 * x9) + (0x2 * (((uint64_t)x25 * x11) + ((uint64_t)x29 * x7)))) + ((uint64_t)x23 * x13)) + ((uint64_t)x31 * x5));
+    uint64_t x45 = (((((((uint64_t)x27 * x11) + ((uint64_t)x29 * x9)) + ((uint64_t)x25 * x13)) + ((uint64_t)x31 * x7)) + ((uint64_t)x23 * x15)) + ((uint64_t)x33 * x5));
+    uint64_t x46 = (((((0x2 * ((((uint64_t)x29 * x11) + ((uint64_t)x25 * x15)) + ((uint64_t)x33 * x7))) + ((uint64_t)x27 * x13)) + ((uint64_t)x31 * x9)) + ((uint64_t)x23 * x17)) + ((uint64_t)x35 * x5));
+    uint64_t x47 = (((((((((uint64_t)x29 * x13) + ((uint64_t)x31 * x11)) + ((uint64_t)x27 * x15)) + ((uint64_t)x33 * x9)) + ((uint64_t)x25 * x17)) + ((uint64_t)x35 * x7)) + ((uint64_t)x23 * x19)) + ((uint64_t)x37 * x5));
+    uint64_t x48 = (((((((uint64_t)x31 * x13) + (0x2 * (((((uint64_t)x29 * x15) + ((uint64_t)x33 * x11)) + ((uint64_t)x25 * x19)) + ((uint64_t)x37 * x7)))) + ((uint64_t)x27 * x17)) + ((uint64_t)x35 * x9)) + ((uint64_t)x23 * x21)) + ((uint64_t)x39 * x5));
+    uint64_t x49 = (((((((((((uint64_t)x31 * x15) + ((uint64_t)x33 * x13)) + ((uint64_t)x29 * x17)) + ((uint64_t)x35 * x11)) + ((uint64_t)x27 * x19)) + ((uint64_t)x37 * x9)) + ((uint64_t)x25 * x21)) + ((uint64_t)x39 * x7)) + ((uint64_t)x23 * x20)) + ((uint64_t)x38 * x5));
+    uint64_t x50 = (((((0x2 * ((((((uint64_t)x33 * x15) + ((uint64_t)x29 * x19)) + ((uint64_t)x37 * x11)) + ((uint64_t)x25 * x20)) + ((uint64_t)x38 * x7))) + ((uint64_t)x31 * x17)) + ((uint64_t)x35 * x13)) + ((uint64_t)x27 * x21)) + ((uint64_t)x39 * x9));
+    uint64_t x51 = (((((((((uint64_t)x33 * x17) + ((uint64_t)x35 * x15)) + ((uint64_t)x31 * x19)) + ((uint64_t)x37 * x13)) + ((uint64_t)x29 * x21)) + ((uint64_t)x39 * x11)) + ((uint64_t)x27 * x20)) + ((uint64_t)x38 * x9));
+    uint64_t x52 = (((((uint64_t)x35 * x17) + (0x2 * (((((uint64_t)x33 * x19) + ((uint64_t)x37 * x15)) + ((uint64_t)x29 * x20)) + ((uint64_t)x38 * x11)))) + ((uint64_t)x31 * x21)) + ((uint64_t)x39 * x13));
+    uint64_t x53 = (((((((uint64_t)x35 * x19) + ((uint64_t)x37 * x17)) + ((uint64_t)x33 * x21)) + ((uint64_t)x39 * x15)) + ((uint64_t)x31 * x20)) + ((uint64_t)x38 * x13));
+    uint64_t x54 = (((0x2 * ((((uint64_t)x37 * x19) + ((uint64_t)x33 * x20)) + ((uint64_t)x38 * x15))) + ((uint64_t)x35 * x21)) + ((uint64_t)x39 * x17));
+    uint64_t x55 = (((((uint64_t)x37 * x21) + ((uint64_t)x39 * x19)) + ((uint64_t)x35 * x20)) + ((uint64_t)x38 * x17));
+    uint64_t x56 = (((uint64_t)x39 * x21) + (0x2 * (((uint64_t)x37 * x20) + ((uint64_t)x38 * x19))));
+    uint64_t x57 = (((uint64_t)x39 * x20) + ((uint64_t)x38 * x21));
+    uint64_t x58 = ((uint64_t)(0x2 * x38) * x20);
+    uint64_t x59 = (x48 + (x58 << 0x4));
+    uint64_t x60 = (x59 + (x58 << 0x1));
+    uint64_t x61 = (x60 + x58);
+    uint64_t x62 = (x47 + (x57 << 0x4));
+    uint64_t x63 = (x62 + (x57 << 0x1));
+    uint64_t x64 = (x63 + x57);
+    uint64_t x65 = (x46 + (x56 << 0x4));
+    uint64_t x66 = (x65 + (x56 << 0x1));
+    uint64_t x67 = (x66 + x56);
+    uint64_t x68 = (x45 + (x55 << 0x4));
+    uint64_t x69 = (x68 + (x55 << 0x1));
+    uint64_t x70 = (x69 + x55);
+    uint64_t x71 = (x44 + (x54 << 0x4));
+    uint64_t x72 = (x71 + (x54 << 0x1));
+    uint64_t x73 = (x72 + x54);
+    uint64_t x74 = (x43 + (x53 << 0x4));
+    uint64_t x75 = (x74 + (x53 << 0x1));
+    uint64_t x76 = (x75 + x53);
+    uint64_t x77 = (x42 + (x52 << 0x4));
+    uint64_t x78 = (x77 + (x52 << 0x1));
+    uint64_t x79 = (x78 + x52);
+    uint64_t x80 = (x41 + (x51 << 0x4));
+    uint64_t x81 = (x80 + (x51 << 0x1));
+    uint64_t x82 = (x81 + x51);
+    uint64_t x83 = (x40 + (x50 << 0x4));
+    uint64_t x84 = (x83 + (x50 << 0x1));
+    uint64_t x85 = (x84 + x50);
+    uint64_t x86 = (x85 >> 0x1a);
+    uint32_t x87 = ((uint32_t)x85 & 0x3ffffff);
+    uint64_t x88 = (x86 + x82);
+    uint64_t x89 = (x88 >> 0x19);
+    uint32_t x90 = ((uint32_t)x88 & 0x1ffffff);
+    uint64_t x91 = (x89 + x79);
+    uint64_t x92 = (x91 >> 0x1a);
+    uint32_t x93 = ((uint32_t)x91 & 0x3ffffff);
+    uint64_t x94 = (x92 + x76);
+    uint64_t x95 = (x94 >> 0x19);
+    uint32_t x96 = ((uint32_t)x94 & 0x1ffffff);
+    uint64_t x97 = (x95 + x73);
+    uint64_t x98 = (x97 >> 0x1a);
+    uint32_t x99 = ((uint32_t)x97 & 0x3ffffff);
+    uint64_t x100 = (x98 + x70);
+    uint64_t x101 = (x100 >> 0x19);
+    uint32_t x102 = ((uint32_t)x100 & 0x1ffffff);
+    uint64_t x103 = (x101 + x67);
+    uint64_t x104 = (x103 >> 0x1a);
+    uint32_t x105 = ((uint32_t)x103 & 0x3ffffff);
+    uint64_t x106 = (x104 + x64);
+    uint64_t x107 = (x106 >> 0x19);
+    uint32_t x108 = ((uint32_t)x106 & 0x1ffffff);
+    uint64_t x109 = (x107 + x61);
+    uint64_t x110 = (x109 >> 0x1a);
+    uint32_t x111 = ((uint32_t)x109 & 0x3ffffff);
+    uint64_t x112 = (x110 + x49);
+    uint64_t x113 = (x112 >> 0x19);
+    uint32_t x114 = ((uint32_t)x112 & 0x1ffffff);
+    uint64_t x115 = (x87 + (0x13 * x113));
+    uint32_t x116 = (uint32_t)(x115 >> 0x1a);
+    uint32_t x117 = ((uint32_t)x115 & 0x3ffffff);
+    uint32_t x118 = (x116 + x90);
+    uint32_t x119 = (x118 >> 0x19);
+    uint32_t x120 = (x118 & 0x1ffffff);
+    out[0] = x117;
+    out[1] = x120;
+    out[2] = (x119 + x93);
+    out[3] = x96;
+    out[4] = x99;
+    out[5] = x102;
+    out[6] = x105;
+    out[7] = x108;
+    out[8] = x111;
+    out[9] = x114;
+}
 
-    /* read and mask scalar */
-    for (i = 0; i < 32; ++i) {
-        scalar[i] = s[i];
+static void
+fe_mul_121666_tl(fe *h, const fe_loose *f)
+{
+    assert_fe_loose(f->v);
+    fe_mul_121666(h->v, f->v);
+    assert_fe(h->v);
+}
+
+SECStatus
+ec_Curve25519_mul(PRUint8 *out, const PRUint8 *scalar, const PRUint8 *point)
+{
+    fe x1, x2, z2, x3, z3, tmp0, tmp1;
+    fe_loose x2l, z2l, x3l, tmp0l, tmp1l;
+    unsigned int swap = 0;
+    unsigned int b;
+    int pos;
+    uint8_t e[32];
+
+    memcpy(e, scalar, 32);
+    e[0] &= 0xF8;
+    e[31] &= 0x7F;
+    e[31] |= 0x40;
+
+    fe_frombytes(&x1, point);
+    fe_1(&x2);
+    fe_0(&z2);
+    fe_copy(&x3, &x1);
+    fe_1(&z3);
+
+    for (pos = 254; pos >= 0; --pos) {
+        b = e[pos / 8] >> (pos & 7);
+        b &= 1;
+        swap ^= b;
+        fe_cswap(&x2, &x3, swap);
+        fe_cswap(&z2, &z3, swap);
+        swap = b;
+        fe_sub(&tmp0l, &x3, &z3);
+        fe_sub(&tmp1l, &x2, &z2);
+        fe_add(&x2l, &x2, &z2);
+        fe_add(&z2l, &x3, &z3);
+        fe_mul_tll(&z3, &tmp0l, &x2l);
+        fe_mul_tll(&z2, &z2l, &tmp1l);
+        fe_sq_tl(&tmp0, &tmp1l);
+        fe_sq_tl(&tmp1, &x2l);
+        fe_add(&x3l, &z3, &z2);
+        fe_sub(&z2l, &z3, &z2);
+        fe_mul_ttt(&x2, &tmp1, &tmp0);
+        fe_sub(&tmp1l, &tmp1, &tmp0);
+        fe_sq_tl(&z2, &z2l);
+        fe_mul_121666_tl(&z3, &tmp1l);
+        fe_sq_tl(&x3, &x3l);
+        fe_add(&tmp0l, &tmp0, &z3);
+        fe_mul_ttt(&z3, &x1, &z2);
+        fe_mul_tll(&z2, &tmp1l, &tmp0l);
     }
-    scalar[0] &= 0xF8;
-    scalar[31] &= 0x7F;
-    scalar[31] |= 64;
+
+    fe_cswap(&x2, &x3, swap);
+    fe_cswap(&z2, &z3, swap);
+
+    fe_invert(&z2, &z2);
+    fe_mul_ttt(&x2, &x2, &z2);
+    fe_tobytes(out, &x2);
 
-    /* read and mask point */
-    for (i = 0; i < 32; ++i) {
-        point[i] = p[i];
-    }
-    point[31] &= 0x7F;
-
-    monty(x_2, z_2, point, scalar);
-    recip(z_2, z_2);
-    mult(X, x_2, z_2);
-    reduce(X);
-    for (i = 0; i < 32; ++i) {
-        q[i] = X[i];
-    }
+    memset(x1.v, 0, sizeof(x1));
+    memset(x2.v, 0, sizeof(x2));
+    memset(z2.v, 0, sizeof(z2));
+    memset(x3.v, 0, sizeof(x3));
+    memset(z3.v, 0, sizeof(z3));
+    memset(x2l.v, 0, sizeof(x2l));
+    memset(z2l.v, 0, sizeof(z2l));
+    memset(x3l.v, 0, sizeof(x3l));
+    memset(e, 0, sizeof(e));
     return 0;
 }
--- a/security/nss/mach
+++ b/security/nss/mach
@@ -82,51 +82,53 @@ class coverityAction(argparse.Action):
 
         if cov_config is None:
             print('Unable to load Coverity config.')
             return 1
 
         self.cov_analysis_url = cov_config.get('package_url')
         self.cov_package_name = cov_config.get('package_name')
         self.cov_url = cov_config.get('server_url')
+        self.cov_port = cov_config.get('server_port')
         self.cov_auth = cov_config.get('auth_key')
         self.cov_package_ver = cov_config.get('package_ver')
         self.cov_full_stack = cov_config.get('full_stack', False)
 
         return 0
 
     def download_coverity(self):
-        if self.cov_url is None or self.cov_analysis_url is None or self.cov_auth is None:
+        if self.cov_url is None or self.cov_port is None or self.cov_analysis_url is None or self.cov_auth is None:
             print('Missing Coverity config options!')
             return 1
 
         COVERITY_CONFIG = '''
         {
             "type": "Coverity configuration",
             "format_version": 1,
             "settings": {
             "server": {
                 "host": "%s",
+                "port": %s,
                 "ssl" : true,
                 "on_new_cert" : "trust",
                 "auth_key_file": "%s"
             },
             "stream": "NSS",
             "cov_run_desktop": {
                 "build_cmd": ["%s"],
                 "clean_cmd": ["%s", "-cc"],
             }
             }
         }
         '''
         # Generate the coverity.conf and auth files
         build_cmd = os.path.join(cwd, 'build.sh')
         cov_auth_path = os.path.join(self.cov_state_path, 'auth')
         cov_setup_path = os.path.join(self.cov_state_path, 'coverity.conf')
-        cov_conf = COVERITY_CONFIG % (self.cov_url, cov_auth_path, build_cmd, build_cmd)
+        cov_conf = COVERITY_CONFIG % (self.cov_url, self.cov_port, cov_auth_path, build_cmd, build_cmd)
 
         def download(artifact_url, target):
             import requests
             resp = requests.get(artifact_url, verify=False, stream=True)
             resp.raise_for_status()
 
             # Extract archive into destination
             with tarfile.open(fileobj=io.BytesIO(resp.content)) as tar:
--- a/testing/mochitest/browser-test.js
+++ b/testing/mochitest/browser-test.js
@@ -391,18 +391,16 @@ function Tester(aTests, structuredLogger
     sandboxPrototype: window,
   });
   Cu.permitCPOWsInScope(this.cpowSandbox);
 
   this.cpowEventUtils = new this.cpowSandbox.Object();
   this._scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/EventUtils.js", this.cpowEventUtils);
 
   var simpleTestScope = {};
-  this._scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/specialpowersAPI.js", simpleTestScope);
-  this._scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/SpecialPowersObserverAPI.js", simpleTestScope);
   this._scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/ChromePowers.js", simpleTestScope);
   this._scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/SimpleTest.js", simpleTestScope);
   this._scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/MemoryStats.js", simpleTestScope);
   this._scriptLoader.loadSubScript("chrome://mochikit/content/chrome-harness.js", simpleTestScope);
   this.SimpleTest = simpleTestScope.SimpleTest;
 
   var extensionUtilsScope = {
     registerCleanupFunction: (fn) => {
--- a/testing/mochitest/mochitest_options.py
+++ b/testing/mochitest/mochitest_options.py
@@ -416,21 +416,16 @@ class MochitestArguments(ArgumentContain
         [["--jscov-dir-prefix"],
          {"action": "store",
           "help": "Directory to store per-test line coverage data as json "
                   "(browser-chrome only). To emit lcov formatted data, set "
                   "JS_CODE_COVERAGE_OUTPUT_DIR in the environment.",
           "default": None,
           "suppress": True,
           }],
-        [["--nested_oop"],
-         {"action": "store_true",
-          "default": False,
-          "help": "Run tests with nested_oop preferences and test filtering enabled.",
-          }],
         [["--dmd"],
          {"action": "store_true",
           "default": False,
           "help": "Run tests with DMD active.",
           }],
         [["--dump-output-directory"],
          {"default": None,
           "dest": "dumpOutputDirectory",
@@ -622,19 +617,16 @@ class MochitestArguments(ArgumentContain
         # set to the SSL proxy setting. See:
         # see https://bugzilla.mozilla.org/show_bug.cgi?id=916517
         # args.webSocketPort = DEFAULT_PORTS['ws']
     }
 
     def validate(self, parser, options, context):
         """Validate generic options."""
 
-        # for test manifest parsing.
-        mozinfo.update({"nested_oop": options.nested_oop})
-
         # and android doesn't use 'app' the same way, so skip validation
         if parser.app != 'android':
             if options.app is None:
                 if build_obj:
                     options.app = build_obj.get_binary_path()
                 else:
                     parser.error(
                         "could not find the application path, --appname must be specified")
@@ -822,19 +814,16 @@ class MochitestArguments(ArgumentContain
                     'Missing gst-launch-{0.1,1.0}, required for '
                     '--use-test-media-devices')
 
             if not pactl:
                 parser.error(
                     'Missing binary pactl required for '
                     '--use-test-media-devices')
 
-        if options.nested_oop:
-            options.e10s = True
-
         # The a11y and chrome flavors can't run with e10s.
         if options.flavor in ('a11y', 'chrome') and options.e10s:
             parser.error("mochitest-{} does not support e10s, try again with "
                          "--disable-e10s.".format(options.flavor))
 
         options.leakThresholds = {
             "default": options.defaultLeakThreshold,
             "tab": options.defaultLeakThreshold,
--- a/testing/mochitest/moz.build
+++ b/testing/mochitest/moz.build
@@ -24,17 +24,16 @@ FINAL_TARGET_FILES += [
 FINAL_TARGET_FILES.content += [
     'browser-harness.xul',
     'browser-test.js',
     'chrome-harness.js',
     'chunkifyTests.js',
     'harness.xul',
     'manifestLibrary.js',
     'mochitest-e10s-utils.js',
-    'nested_setup.js',
     'redirect.html',
     'server.js',
     'shutdown-leaks-collector.js',
     'ShutdownLeaksCollector.jsm',
 ]
 
 FINAL_TARGET_FILES.content.dynamic += [
     'dynamic/getMyDirectory.sjs',
@@ -42,28 +41,25 @@ FINAL_TARGET_FILES.content.dynamic += [
 
 FINAL_TARGET_FILES.content.static += [
     'static/harness.css',
 ]
 
 FINAL_TARGET_FILES.content.tests.SimpleTest += [
     '../../docshell/test/chrome/docshell_helpers.js',
     '../modules/StructuredLog.jsm',
-    '../specialpowers/content/MozillaLogger.js',
-    '../specialpowers/content/specialpowers.js',
-    '../specialpowers/content/specialpowersAPI.js',
-    '../specialpowers/content/SpecialPowersObserverAPI.js',
     'tests/SimpleTest/AsyncUtilsContent.js',
     'tests/SimpleTest/ChromePowers.js',
     'tests/SimpleTest/EventUtils.js',
     'tests/SimpleTest/ExtensionTestUtils.js',
     'tests/SimpleTest/iframe-between-tests.html',
     'tests/SimpleTest/LogController.js',
     'tests/SimpleTest/MemoryStats.js',
     'tests/SimpleTest/MockObjects.js',
+    'tests/SimpleTest/MozillaLogger.js',
     'tests/SimpleTest/NativeKeyCodes.js',
     'tests/SimpleTest/paint_listener.js',
     'tests/SimpleTest/setup.js',
     'tests/SimpleTest/SimpleTest.js',
     'tests/SimpleTest/test.css',
     'tests/SimpleTest/TestRunner.js',
     'tests/SimpleTest/WindowSnapshot.js',
 ]
@@ -104,17 +100,16 @@ TEST_HARNESS_FILES.testing.mochitest += 
     'chunkifyTests.js',
     'favicon.ico',
     'harness.xul',
     'leaks.py',
     'mach_test_package_commands.py',
     'manifest.webapp',
     'manifestLibrary.js',
     'mochitest_options.py',
-    'nested_setup.js',
     'pywebsocket_wrapper.py',
     'redirect.html',
     'runjunit.py',
     'runrobocop.py',
     'runtests.py',
     'runtestsremote.py',
     'server.js',
     'start_desktop.js',
deleted file mode 100644
--- a/testing/mochitest/nested_setup.js
+++ /dev/null
@@ -1,30 +0,0 @@
-
-/* global SpecialPowers */
-
-var gTestURL = "";
-
-function addPermissions() {
-  SpecialPowers.pushPermissions(
-    [{ type: "browser", allow: true, context: document }],
-    addPreferences);
-}
-
-function addPreferences() {
-  SpecialPowers.pushPrefEnv(
-    {"set": [["dom.mozBrowserFramesEnabled", true]]},
-    insertFrame);
-}
-
-function insertFrame() {
-  SpecialPowers.nestedFrameSetup();
-
-  var iframe = document.createElement("iframe");
-  iframe.id = "nested-parent-frame";
-  iframe.width = "100%";
-  iframe.height = "100%";
-  iframe.scoring = "no";
-  iframe.setAttribute("remote", "true");
-  iframe.setAttribute("mozbrowser", "true");
-  iframe.src = gTestURL;
-  document.getElementById("holder-div").appendChild(iframe);
-}
--- a/testing/mochitest/runtests.py
+++ b/testing/mochitest/runtests.py
@@ -846,17 +846,16 @@ def update_mozinfo():
 class MochitestDesktop(object):
     """
     Mochitest class for desktop firefox.
     """
     oldcwd = os.getcwd()
 
     # Path to the test script on the server
     TEST_PATH = "tests"
-    NESTED_OOP_TEST_PATH = "nested_oop"
     CHROME_PATH = "redirect.html"
 
     certdbNew = False
     sslTunnel = None
     DEFAULT_TIMEOUT = 60.0
     mediaDevices = None
 
     patternFiles = {}
@@ -1097,18 +1096,16 @@ class MochitestDesktop(object):
                 testURL = "/".join([testURL, os.path.dirname(options.test_paths[0])])
             else:
                 testURL = "/".join([testURL, options.test_paths[0]])
 
         if options.flavor in ('a11y', 'chrome'):
             testURL = "/".join([testHost, self.CHROME_PATH])
         elif options.flavor == 'browser':
             testURL = "about:blank"
-        if options.nested_oop:
-            testURL = "/".join([testHost, self.NESTED_OOP_TEST_PATH])
         return testURL
 
     def getTestsByScheme(self, options, testsToFilter=None, disabled=True):
         """ Build the url path to the specific test harness and test file or directory
             Build a manifest of tests to run and write out a json file for the harness to read
             testsToFilter option is used to filter/keep the tests provided in the list
 
             disabled -- This allows to add all disabled tests on the build side
@@ -1918,17 +1915,16 @@ toolbar#nav-bar {
         # 3) Prefs from --setpref
 
         # Prefs from base profiles
         self.merge_base_profiles(options)
 
         # Hardcoded prefs (TODO move these into a base profile)
         prefs = {
             "browser.tabs.remote.autostart": options.e10s,
-            "dom.ipc.tabs.nested.enabled": options.nested_oop,
             # Enable tracing output for detailed failures in case of
             # failing connection attempts, and hangs (bug 1397201)
             "marionette.log.level": "Trace",
         }
 
         if options.flavor == 'browser' and options.timeout:
             prefs["testing.browserTestHarness.timeout"] = options.timeout
 
--- a/testing/mochitest/server.js
+++ b/testing/mochitest/server.js
@@ -220,17 +220,16 @@ function runServer() {
 
 /** Creates and returns an HTTP server configured to serve Mochitests. */
 function createMochitestServer(serverBasePath) {
   var server = new nsHttpServer();
 
   server.registerDirectory("/", serverBasePath);
   server.registerPathHandler("/server/shutdown", serverShutdown);
   server.registerPathHandler("/server/debug", serverDebug);
-  server.registerPathHandler("/nested_oop", nestedTest);
   server.registerContentType("sjs", "sjs"); // .sjs == CGI-like functionality
   server.registerContentType("jar", "application/x-jar");
   server.registerContentType("ogg", "application/ogg");
   server.registerContentType("pdf", "application/pdf");
   server.registerContentType("ogv", "video/ogg");
   server.registerContentType("oga", "audio/ogg");
   server.registerContentType("opus", "audio/ogg; codecs=opus");
   server.registerContentType("dat", "text/plain; charset=utf-8");
@@ -598,40 +597,16 @@ function convertManifestToTestLinks(root
                                                                manifestStream.available()));
   var paths = manifestObj.tests;
   var pathPrefix = "/" + root + "/";
   return [paths.reduce(function(t, p) { t[pathPrefix + p.path] = true; return t; }, {}),
           paths.length];
 }
 
 /**
- * Produce a test harness page that has one remote iframe
- */
-function nestedTest(metadata, response) {
-  response.setStatusLine("1.1", 200, "OK");
-  response.setHeader("Content-type", "text/html;charset=utf-8", false);
-  response.write(
-    HTML(
-      HEAD(
-        TITLE("Mochitest | ", metadata.path),
-        LINK({rel: "stylesheet",
-              type: "text/css", href: "/static/harness.css"}),
-        SCRIPT({type: "text/javascript",
-                src: "/nested_setup.js"}),
-        SCRIPT({type: "text/javascript"},
-               "window.onload = addPermissions; gTestURL = '/tests?" + metadata.queryString + "';")
-        ),
-      BODY(
-        DIV({class: "container"},
-          DIV({class: "frameholder", id: "holder-div"})
-        )
-        )));
-}
-
-/**
  * Produce a test harness page containing all the test cases
  * below it, recursively.
  */
 function testListing(metadata, response) {
   var links = {};
   var count = 0;
   if (!metadata.queryString.includes("manifestFile")) {
     [links, count] = list(metadata.path,
new file mode 100644
--- /dev/null
+++ b/testing/mochitest/tests/Harness_sanity/file_spawn.html
@@ -0,0 +1,10 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+  <meta charset="UTF-8">
+  <title></title>
+</head>
+<body>
+  <span id="span">Hello there.</span>
+</body>
+</html>
--- a/testing/mochitest/tests/Harness_sanity/mochitest.ini
+++ b/testing/mochitest/tests/Harness_sanity/mochitest.ini
@@ -17,16 +17,19 @@ support-files = empty.js
 [test_sanityWindowSnapshot.html]
 [test_SpecialPowersExtension.html]
 [test_SpecialPowersExtension2.html]
 support-files = file_SpecialPowersFrame1.html
 [test_SpecialPowersPushPermissions.html]
 support-files =
     specialPowers_framescript.js
 [test_SpecialPowersPushPrefEnv.html]
+[test_SpecialPowersSandbox.html]
+[test_SpecialPowersSpawn.html]
+support-files = file_spawn.html
 [test_SimpletestGetTestFileURL.html]
 [test_SpecialPowersLoadChromeScript.html]
 support-files = SpecialPowersLoadChromeScript.js
 [test_SpecialPowersLoadChromeScript_function.html]
 [test_SpecialPowersLoadPrivilegedScript.html]
 [test_bug649012.html]
 [test_sanity_cleanup.html]
 [test_sanity_cleanup2.html]
--- a/testing/mochitest/tests/Harness_sanity/test_SpecialPowersExtension.html
+++ b/testing/mochitest/tests/Harness_sanity/test_SpecialPowersExtension.html
@@ -26,45 +26,46 @@ function dispatchTestEvent() {
   var e = document.createEvent("Event");
   e.initEvent("TestEvent", true, true);
   window.dispatchEvent(e);
 }
 
 dump("\nSPECIALPTEST:::Test script loaded " + (new Date).getTime() + "\n");
 SimpleTest.waitForExplicitFinish();
 var startTime = new Date();
-function starttest(){
+async function starttest(){
   dump("\nSPECIALPTEST:::Test script running after load " + (new Date).getTime() + "\n");
 
   /** Test for SpecialPowers extension **/
   is(SpecialPowers.sanityCheck(), "foo", "check to see whether the Special Powers extension is installed.");
 
   // Test a sync call into chrome
-  SpecialPowers.setBoolPref('extensions.checkCompatibility', true);
+  await SpecialPowers.setBoolPref('extensions.checkCompatibility', true);
   is(SpecialPowers.getBoolPref('extensions.checkCompatibility'), true, "Check to see if we can set a preference properly");
-  SpecialPowers.clearUserPref('extensions.checkCompatibility');
+  await SpecialPowers.clearUserPref('extensions.checkCompatibility');
 
   // Test a int pref
-  SpecialPowers.setIntPref('extensions.foobar', 42);
+  await SpecialPowers.setIntPref('extensions.foobar', 42);
   is(SpecialPowers.getIntPref('extensions.foobar'), 42, "Check int pref");
-  SpecialPowers.clearUserPref('extensions.foobar');
+  await SpecialPowers.clearUserPref('extensions.foobar');
 
   // Test a string pref
-  SpecialPowers.setCharPref("extensions.foobaz", "hi there");
+  await SpecialPowers.setCharPref("extensions.foobaz", "hi there");
   is(SpecialPowers.getCharPref("extensions.foobaz"), "hi there", "Check string pref");
-  SpecialPowers.clearUserPref("extensions.foobaz");
+  await SpecialPowers.clearUserPref("extensions.foobaz");
 
   // Test an invalid pref
   var retVal = null;
   try {
     retVal = SpecialPowers.getBoolPref('extensions.checkCompat0123456789');
   } catch (ex) {
     retVal = ex;
   }
-  is(retVal.message, "Error getting pref 'extensions.checkCompat0123456789'", "received an exception trying to get an unset preference value");
+  is(retVal.result, SpecialPowers.Cr.NS_ERROR_UNEXPECTED,
+     "received an exception trying to get an unset preference value");
 
   SpecialPowers.addChromeEventListener("TestEvent", testEventListener, true, true);
   SpecialPowers.addChromeEventListener("TestEvent", testEventListener2, true, false);
   dispatchTestEvent();
   is(eventCount, 1, "Should have got an event!");
 
   SpecialPowers.removeChromeEventListener("TestEvent", testEventListener, true);
   SpecialPowers.removeChromeEventListener("TestEvent", testEventListener2, true);
--- a/testing/mochitest/tests/Harness_sanity/test_SpecialPowersLoadChromeScript.html
+++ b/testing/mochitest/tests/Harness_sanity/test_SpecialPowersLoadChromeScript.html
@@ -42,28 +42,22 @@ function endOfFirstTest() {
     });
   }, { wantGlobalProperties: ["ChromeUtils", "XMLHttpRequest"] });
 
   script2.sendAsyncMessage("valid-assert");
   script2.addMessageListener("valid-assert-done", endOfTest);
 
 }
 
-function endOfTest() {
+async function endOfTest() {
+  is(await script.sendQuery("sync-message"), "Received a synchronous message.",
+     "Check sync return value");
+
   script2.destroy();
   SimpleTest.finish();
 }
 
 script.sendAsyncMessage("foo", MESSAGE);
 
-/*
- * [0][0] is because we're using one real message listener in SpecialPowersObserverAPI.js
- * and dispatching that to multiple _chromeScriptListeners. The outer array comes
- * from the message manager since there can be multiple real listeners. The inner
- * array is for the return values of _chromeScriptListeners.
- */
-is(script.sendSyncMessage("sync-message")[0][0], "Received a synchronous message.",
-   "Check sync return value");
-
 </script>
 </pre>
 </body>
 </html>
--- a/testing/mochitest/tests/Harness_sanity/test_SpecialPowersLoadChromeScript_function.html
+++ b/testing/mochitest/tests/Harness_sanity/test_SpecialPowersLoadChromeScript_function.html
@@ -40,28 +40,22 @@ script.addMessageListener("bar", functio
   checkAssert();
 });
 
 function checkAssert() {
   script.sendAsyncMessage("valid-assert");
   script.addMessageListener("valid-assert-done", endOfTest);
 }
 
-function endOfTest() {
+async function endOfTest() {
+  is(await script.sendQuery("sync-message"), "Received a synchronous message.",
+     "Check sync return value");
+
   script.destroy();
   SimpleTest.finish();
 }
 
 script.sendAsyncMessage("foo", MESSAGE);
 
-/*
- * [0][0] is because we're using one real message listener in SpecialPowersObserverAPI.js
- * and dispatching that to multiple _chromeScriptListeners. The outer array comes
- * from the message manager since there can be multiple real listeners. The inner
- * array is for the return values of _chromeScriptListeners.
- */
-is(script.sendSyncMessage("sync-message")[0][0], "Received a synchronous message.",
-   "Check sync return value");
-
 </script>
 </pre>
 </body>
 </html>
--- a/testing/mochitest/tests/Harness_sanity/test_SpecialPowersPushPermissions.html
+++ b/testing/mochitest/tests/Harness_sanity/test_SpecialPowersPushPermissions.html
@@ -36,117 +36,117 @@ function starttest(){
   SpecialPowers.addPermission("pREMOVE", ALLOW_ACTION, document);
   SpecialPowers.addPermission("pSESSION", ACCESS_SESSION, document);
 
   setTimeout(test1, 0);
 }
 
 SimpleTest.waitForExplicitFinish();
 
-function test1() {
-  if (!SpecialPowers.testPermission('pALLOW', ALLOW_ACTION, document)) {
+async function test1() {
+  if (!await SpecialPowers.testPermission('pALLOW', ALLOW_ACTION, document)) {
     dump('/**** allow not set ****/\n');
     setTimeout(test1, 0);
-  } else if (!SpecialPowers.testPermission('pDENY', DENY_ACTION, document)) {
+  } else if (!await SpecialPowers.testPermission('pDENY', DENY_ACTION, document)) {
     dump('/**** deny not set ****/\n');
     setTimeout(test1, 0);
-  } else if (!SpecialPowers.testPermission('pPROMPT', PROMPT_ACTION, document)) {
+  } else if (!await SpecialPowers.testPermission('pPROMPT', PROMPT_ACTION, document)) {
     dump('/**** prompt not set ****/\n');
     setTimeout(test1, 0);
-  } else if (!SpecialPowers.testPermission('pREMOVE', ALLOW_ACTION, document)) {
+  } else if (!await SpecialPowers.testPermission('pREMOVE', ALLOW_ACTION, document)) {
     dump('/**** remove not set ****/\n');
     setTimeout(test1, 0);
-  } else if (!SpecialPowers.testPermission('pSESSION', ACCESS_SESSION, document)) {
+  } else if (!await SpecialPowers.testPermission('pSESSION', ACCESS_SESSION, document)) {
     dump('/**** ACCESS_SESSION not set ****/\n');
     setTimeout(test1, 0);
   } else {
     test2();
   }
 }
 
-function test2() {
-  ok(SpecialPowers.testPermission('pUNKNOWN', UNKNOWN_ACTION, document), 'pUNKNOWN value should have UNKOWN permission');
+async function test2() {
+  ok(await SpecialPowers.testPermission('pUNKNOWN', UNKNOWN_ACTION, document), 'pUNKNOWN value should have UNKOWN permission');
   SpecialPowers.pushPermissions([
     {'type': 'pUNKNOWN', 'allow': true, 'context': document},
     {'type': 'pALLOW', 'allow': false, 'context': document},
     {'type': 'pDENY', 'allow': true, 'context': document},
     {'type': 'pPROMPT', 'allow': true, 'context': document},
     {'type': 'pSESSION', 'allow': true, 'context': document},
     {'type': 'pREMOVE', 'remove': true, 'context': document},
   ], test3);
 }
 
-function test3() {
-  ok(SpecialPowers.testPermission('pUNKNOWN', ALLOW_ACTION, document), 'pUNKNOWN value should have ALLOW permission');
-  ok(SpecialPowers.testPermission('pPROMPT', ALLOW_ACTION, document), 'pPROMPT value should have ALLOW permission');
-  ok(SpecialPowers.testPermission('pALLOW', DENY_ACTION, document), 'pALLOW should have DENY permission');
-  ok(SpecialPowers.testPermission('pDENY', ALLOW_ACTION, document), 'pDENY should have ALLOW permission');
-  ok(SpecialPowers.testPermission('pREMOVE', UNKNOWN_ACTION, document), 'pREMOVE should have REMOVE permission');
-  ok(SpecialPowers.testPermission('pSESSION', ALLOW_ACTION, document), 'pSESSION should have ALLOW permission');
+async function test3() {
+  ok(await SpecialPowers.testPermission('pUNKNOWN', ALLOW_ACTION, document), 'pUNKNOWN value should have ALLOW permission');
+  ok(await SpecialPowers.testPermission('pPROMPT', ALLOW_ACTION, document), 'pPROMPT value should have ALLOW permission');
+  ok(await SpecialPowers.testPermission('pALLOW', DENY_ACTION, document), 'pALLOW should have DENY permission');
+  ok(await SpecialPowers.testPermission('pDENY', ALLOW_ACTION, document), 'pDENY should have ALLOW permission');
+  ok(await SpecialPowers.testPermission('pREMOVE', UNKNOWN_ACTION, document), 'pREMOVE should have REMOVE permission');
+  ok(await SpecialPowers.testPermission('pSESSION', ALLOW_ACTION, document), 'pSESSION should have ALLOW permission');
 
   // only pPROMPT (last one) is different, the other stuff is just to see if it doesn't cause test failures
   SpecialPowers.pushPermissions([
     {'type': 'pUNKNOWN', 'allow': true, 'context': document},
     {'type': 'pALLOW', 'allow': false, 'context': document},
     {'type': 'pDENY', 'allow': true, 'context': document},
     {'type': 'pPROMPT', 'allow': false, 'context': document},
     {'type': 'pREMOVE', 'remove': true, 'context': document},
   ], test3b);
 }
 
-function test3b() {
-  ok(SpecialPowers.testPermission('pPROMPT', DENY_ACTION, document), 'pPROMPT value should have DENY permission');
+async function test3b() {
+  ok(await SpecialPowers.testPermission('pPROMPT', DENY_ACTION, document), 'pPROMPT value should have DENY permission');
   SpecialPowers.pushPermissions([
     {'type': 'pUNKNOWN', 'allow': DENY_ACTION, 'context': document},
     {'type': 'pALLOW', 'allow': PROMPT_ACTION, 'context': document},
     {'type': 'pDENY', 'allow': PROMPT_ACTION, 'context': document},
     {'type': 'pPROMPT', 'allow': ALLOW_ACTION, 'context': document},
   ], test4);
 }
 
-function test4() {
-  ok(SpecialPowers.testPermission('pUNKNOWN', DENY_ACTION, document), 'pUNKNOWN value should have DENY permission');
-  ok(SpecialPowers.testPermission('pPROMPT', ALLOW_ACTION, document), 'pPROMPT value should have ALLOW permission');
-  ok(SpecialPowers.testPermission('pALLOW', PROMPT_ACTION, document), 'pALLOW should have PROMPT permission');
-  ok(SpecialPowers.testPermission('pDENY', PROMPT_ACTION, document), 'pDENY should have PROMPT permission');
+async function test4() {
+  ok(await SpecialPowers.testPermission('pUNKNOWN', DENY_ACTION, document), 'pUNKNOWN value should have DENY permission');
+  ok(await SpecialPowers.testPermission('pPROMPT', ALLOW_ACTION, document), 'pPROMPT value should have ALLOW permission');
+  ok(await SpecialPowers.testPermission('pALLOW', PROMPT_ACTION, document), 'pALLOW should have PROMPT permission');
+  ok(await SpecialPowers.testPermission('pDENY', PROMPT_ACTION, document), 'pDENY should have PROMPT permission');
   //this should reset all the permissions to before all the pushPermissions calls
   SpecialPowers.flushPermissions(test5);
 }
 
-function test5() {
-  ok(SpecialPowers.testPermission('pUNKNOWN', UNKNOWN_ACTION, document), 'pUNKNOWN should have UNKNOWN permission');
-  ok(SpecialPowers.testPermission('pALLOW', ALLOW_ACTION, document), 'pALLOW should have ALLOW permission');
-  ok(SpecialPowers.testPermission('pDENY', DENY_ACTION, document), 'pDENY should have DENY permission');
-  ok(SpecialPowers.testPermission('pPROMPT', PROMPT_ACTION, document), 'pPROMPT should have PROMPT permission');
-  ok(SpecialPowers.testPermission('pREMOVE', ALLOW_ACTION, document), 'pREMOVE should have ALLOW permission');
-  ok(SpecialPowers.testPermission('pSESSION', ACCESS_SESSION, document), 'pSESSION should have ACCESS_SESSION permission');
+async function test5() {
+  ok(await SpecialPowers.testPermission('pUNKNOWN', UNKNOWN_ACTION, document), 'pUNKNOWN should have UNKNOWN permission');
+  ok(await SpecialPowers.testPermission('pALLOW', ALLOW_ACTION, document), 'pALLOW should have ALLOW permission');
+  ok(await SpecialPowers.testPermission('pDENY', DENY_ACTION, document), 'pDENY should have DENY permission');
+  ok(await SpecialPowers.testPermission('pPROMPT', PROMPT_ACTION, document), 'pPROMPT should have PROMPT permission');
+  ok(await SpecialPowers.testPermission('pREMOVE', ALLOW_ACTION, document), 'pREMOVE should have ALLOW permission');
+  ok(await SpecialPowers.testPermission('pSESSION', ACCESS_SESSION, document), 'pSESSION should have ACCESS_SESSION permission');
 
   SpecialPowers.removePermission("pPROMPT", document);
   SpecialPowers.removePermission("pALLOW", document);
   SpecialPowers.removePermission("pDENY", document);
   SpecialPowers.removePermission("pREMOVE", document);
   SpecialPowers.removePermission("pSESSION", document);
 
   setTimeout(test6, 0);
 }
 
-function test6() {
-  if (!SpecialPowers.testPermission('pALLOW', UNKNOWN_ACTION, document)) {
+async function test6() {
+  if (!await SpecialPowers.testPermission('pALLOW', UNKNOWN_ACTION, document)) {
     dump('/**** allow still set ****/\n');
     setTimeout(test6, 0);
-  } else if (!SpecialPowers.testPermission('pDENY', UNKNOWN_ACTION, document)) {
+  } else if (!await SpecialPowers.testPermission('pDENY', UNKNOWN_ACTION, document)) {
     dump('/**** deny still set ****/\n');
     setTimeout(test6, 0);
-  } else if (!SpecialPowers.testPermission('pPROMPT', UNKNOWN_ACTION, document)) {
+  } else if (!await SpecialPowers.testPermission('pPROMPT', UNKNOWN_ACTION, document)) {
     dump('/**** prompt still set ****/\n');
     setTimeout(test6, 0);
-  } else if (!SpecialPowers.testPermission('pREMOVE', UNKNOWN_ACTION, document)) {
+  } else if (!await SpecialPowers.testPermission('pREMOVE', UNKNOWN_ACTION, document)) {
     dump('/**** remove still set ****/\n');
     setTimeout(test6, 0);
-  } else if (!SpecialPowers.testPermission('pSESSION', UNKNOWN_ACTION, document)) {
+  } else if (!await SpecialPowers.testPermission('pSESSION', UNKNOWN_ACTION, document)) {
     dump('/**** pSESSION still set ****/\n');
     setTimeout(test6, 0);
   } else {
     test7();
   }
 }
 
 function test7() {
@@ -181,32 +181,32 @@ function afterPermissionChanged(type, op
   gScript.addMessageListener('perm-changed', function onChange(msg) {
     if (msg.type == type && msg.op == op) {
       gScript.removeMessageListener('perm-changed', onChange);
       callback();
     }
   });
 }
 
-function permissionPollingCheck() {
+async function permissionPollingCheck() {
   var now = Number(Date.now());
   if (now < (start + PERIOD)) {
-    if (SpecialPowers.testPermission('pEXPIRE', ALLOW_ACTION, document)) {
+    if (await SpecialPowers.testPermission('pEXPIRE', ALLOW_ACTION, document)) {
       // To make sure that permission will be expired in next round,
       // the next permissionPollingCheck calling will be fired 100ms later after
       // permission is out-of-period.
       setTimeout(permissionPollingCheck, PERIOD + 100);
       return;
     }
 
     errorHandler('unexpired permission should be allowed!');
   }
 
   // The permission is already expired!
-  if (SpecialPowers.testPermission('pEXPIRE', ALLOW_ACTION, document)) {
+  if (await SpecialPowers.testPermission('pEXPIRE', ALLOW_ACTION, document)) {
     errorHandler('expired permission should be removed!');
   }
 }
 
 function getPlatformInfo() {
   var version = SpecialPowers.Services.sysinfo.getProperty('version');
   version = parseFloat(version);
 
--- a/testing/mochitest/tests/Harness_sanity/test_SpecialPowersPushPrefEnv.html
+++ b/testing/mochitest/tests/Harness_sanity/test_SpecialPowersPushPrefEnv.html
@@ -4,29 +4,29 @@
   <title>Test for SpecialPowers extension</title>
   <script src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body onload="starttest();">
 
 <pre id="test">
 <script class="testbody" type="text/javascript">
-function starttest() {
+async function starttest() {
   try {
-    SpecialPowers.setBoolPref("test.bool", 1);
+    await SpecialPowers.setBoolPref("test.bool", 1);
   } catch(e) {
-    SpecialPowers.setBoolPref("test.bool", true);
+    await SpecialPowers.setBoolPref("test.bool", true);
   }
   try {
-    SpecialPowers.setIntPref("test.int", true);
+    await SpecialPowers.setIntPref("test.int", true);
   } catch(e) {
-    SpecialPowers.setIntPref("test.int", 1);
+    await SpecialPowers.setIntPref("test.int", 1);
   }
-  SpecialPowers.setCharPref("test.char", 'test');
-  SpecialPowers.setBoolPref("test.cleanup", false);
+  await SpecialPowers.setCharPref("test.char", 'test');
+  await SpecialPowers.setBoolPref("test.cleanup", false);
 
   setTimeout(test1, 0, 0);
 }
 
 SimpleTest.waitForExplicitFinish();
 
 function test1(aCount) {
   if (aCount >= 20) {
new file mode 100644
--- /dev/null
+++ b/testing/mochitest/tests/Harness_sanity/test_SpecialPowersSandbox.html
@@ -0,0 +1,96 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test for SpecialPowers sandboxes</title>
+  <script src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+
+<iframe id="iframe"></iframe>
+
+<script>
+/**
+ * Tests that the shared sandbox functionality for cross-process script
+ * execution works as expected. In particular, ensures that Assert methods
+ * report the correct diagnostics in the caller scope.
+ */
+
+/* eslint-disable prettier/prettier */
+/* globals SpecialPowers, Assert */
+
+async function interceptDiagnostics(func) {
+  let originalRecord = SimpleTest.record;
+  try {
+    let diags = [];
+
+    SimpleTest.record = (condition, name, diag, stack) => {
+      diags.push({condition, name, diag, stack});
+    };
+
+    await func();
+
+    return diags;
+  } finally {
+    SimpleTest.record = originalRecord;
+  }
+}
+
+add_task(async function() {
+  let frame = document.getElementById("iframe");
+  frame.src = "https://example.com/tests/testing/mochitest/tests/Harness_sanity/file_spawn.html";
+
+  await new Promise(resolve => {
+    frame.addEventListener("load", resolve, {once: true});
+  });
+
+  let expected = [
+    [false, "Thing - 1 == 2", "got 1, expected 2 (operator ==)"],
+    [true, "Hmm - 1 == 1", undefined],
+    [true, "Yay. - true == true", undefined],
+    [false, "Boo!. - false == true", "got false, expected true (operator ==)"],
+  ];
+
+  // Test that a representative variety of assertions work as expected, and
+  // trigger the expected calls to the harness's reporting function.
+  //
+  // Note: Assert.jsm has its own tests, and defers all of its reporting to a
+  // single reporting function, so we don't need to test it comprehensively. We
+  // just need to make sure that the general functionality works as expected.
+  let tests = {
+    "SpecialPowers.spawn": () => {
+      return SpecialPowers.spawn(frame, [], () => {
+        Assert.equal(1, 2, "Thing");
+        Assert.equal(1, 1, "Hmm");
+        Assert.ok(true, "Yay.");
+        Assert.ok(false, "Boo!.");
+      });
+    },
+    "SpecialPowers.loadChromeScript": async () => {
+      let script = SpecialPowers.loadChromeScript(() => {
+        this.addMessageListener("ping", () => "pong");
+
+        Assert.equal(1, 2, "Thing");
+        Assert.equal(1, 1, "Hmm");
+        Assert.ok(true, "Yay.");
+        Assert.ok(false, "Boo!.");
+      });
+
+      await script.sendQuery("ping");
+      script.destroy();
+    },
+  };
+
+  for (let [name, func] of Object.entries(tests)) {
+    info(`Starting task: ${name}`);
+
+    let diags = await interceptDiagnostics(func);
+
+    let results = diags.map(diag => [diag.condition, diag.name, diag.diag]);
+
+    isDeeply(results, expected, "Got expected assertions");
+  }
+});
+</script>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/testing/mochitest/tests/Harness_sanity/test_SpecialPowersSpawn.html
@@ -0,0 +1,59 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test for SpecialPowers.spawn</title>
+  <script src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+
+<iframe id="iframe"></iframe>
+
+<span id="hello">World.</span>
+
+<script>
+/* eslint-disable prettier/prettier */
+/* globals SpecialPowers, content */
+
+  add_task(async function() {
+    let frame = document.getElementById("iframe");
+    frame.src = "https://example.com/tests/testing/mochitest/tests/Harness_sanity/file_spawn.html";
+
+    await new Promise(resolve => {
+      frame.addEventListener("load", resolve, {once: true});
+    });
+
+    let result = await SpecialPowers.spawn(frame, ["#span"], selector => {
+      let elem = content.document.querySelector(selector);
+      return elem.textContent;
+    });
+
+    is(result, "Hello there.", "Got correct element text from frame");
+
+    result = await SpecialPowers.spawn(frame, ["#hello"], selector => {
+      return SpecialPowers.spawn(content.parent, [selector], selector => {
+        let elem = content.document.querySelector(selector);
+        return elem.textContent;
+      });
+    });
+
+    is(result, "World.", "Got correct element text from frame's window.parent");
+
+    result = await SpecialPowers.spawn(frame.contentWindow, ["#span"], selector => {
+      let elem = content.document.querySelector(selector);
+      return elem.textContent;
+    });
+
+    is(result, "Hello there.", "Got correct element text from window proxy");
+
+    result = await SpecialPowers.spawn(SpecialPowers.getPrivilegedProps(frame, "browsingContext"),
+                                       ["#span"], selector => {
+      let elem = content.document.querySelector(selector);
+      return elem.textContent;
+    });
+
+    is(result, "Hello there.", "Got correct element text from browsing context");
+  });
+</script>
+</body>
+</html>
--- a/testing/mochitest/tests/Harness_sanity/test_importInMainProcess.html
+++ b/testing/mochitest/tests/Harness_sanity/test_importInMainProcess.html
@@ -6,49 +6,51 @@
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 
 <div id="content" class="testbody">
   <script type="text/javascript">
     SimpleTest.waitForExplicitFinish();
 
-    var failed = false;
-    try {
-      SpecialPowers.importInMainProcess("invalid file for import");
-    } catch (e) {
-      ok(e.toString().indexOf("NS_ERROR_MALFORMED_URI") > -1, "Exception should be for a malformed URI");
-      failed = true;
-    }
-    ok(failed, "An invalid import should throw");
+    (async () => {
+      var failed = false;
+      try {
+        await SpecialPowers.importInMainProcess("invalid file for import");
+      } catch (e) {
+        ok(e.toString().indexOf("NS_ERROR_MALFORMED_URI") > -1, "Exception should be for a malformed URI");
+        failed = true;
+      }
+      ok(failed, "An invalid import should throw");
+
+      const testingResource = "resource://testing-common/ImportTesting.jsm";
+      var script = SpecialPowers.loadChromeScript(SimpleTest.getTestFileURL('importtesting_chromescript.js'));
 
-    const testingResource = "resource://testing-common/ImportTesting.jsm";
-    var script = SpecialPowers.loadChromeScript(SimpleTest.getTestFileURL('importtesting_chromescript.js'));
+      script.addMessageListener("ImportTesting:IsModuleLoadedReply", handleFirstReply);
+      script.sendAsyncMessage("ImportTesting:IsModuleLoaded", testingResource);
 
-    script.addMessageListener("ImportTesting:IsModuleLoadedReply", handleFirstReply);
-    script.sendAsyncMessage("ImportTesting:IsModuleLoaded", testingResource);
+      async function handleFirstReply(aMsg) {
+        ok(!aMsg, "ImportTesting.jsm shouldn't be loaded before we import it");
 
-    function handleFirstReply(aMsg) {
-      ok(!aMsg, "ImportTesting.jsm shouldn't be loaded before we import it");
+        try {
+          await SpecialPowers.importInMainProcess(testingResource);
+        } catch (e) {
+          ok(false, "Unexpected exception when importing a valid resource: " + e.toString());
+        }
 
-      try {
-        SpecialPowers.importInMainProcess(testingResource);
-      } catch (e) {
-        ok(false, "Unexpected exception when importing a valid resource: " + e.toString());
+        script.removeMessageListener("ImportTesting:IsModuleLoadedReply", handleFirstReply);
+        script.addMessageListener("ImportTesting:IsModuleLoadedReply", handleSecondReply);
+        script.sendAsyncMessage("ImportTesting:IsModuleLoaded", testingResource);
       }
 
-      script.removeMessageListener("ImportTesting:IsModuleLoadedReply", handleFirstReply);
-      script.addMessageListener("ImportTesting:IsModuleLoadedReply", handleSecondReply);
-      script.sendAsyncMessage("ImportTesting:IsModuleLoaded", testingResource);
-    }
+      function handleSecondReply(aMsg) {
+        script.removeMessageListener("ImportTesting:IsModuleLoadedReply", handleSecondReply);
 
-    function handleSecondReply(aMsg) {
-      script.removeMessageListener("ImportTesting:IsModuleLoadedReply", handleSecondReply);
+        ok(aMsg, "ImportTesting.jsm should be loaded after we import it");
 
-      ok(aMsg, "ImportTesting.jsm should be loaded after we import it");
-
-      SimpleTest.finish();
-    }
+        SimpleTest.finish();
+      }
+    })();
 
   </script>
 </div>
 </body>
 </html>
--- a/testing/mochitest/tests/SimpleTest/ChromePowers.js
+++ b/testing/mochitest/tests/SimpleTest/ChromePowers.js
@@ -1,113 +1,120 @@
 /* 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/. */
 
-function ChromePowers(window) {
-  this.window = Cu.getWeakReference(window);
+const {SpecialPowersAPI, bindDOMWindowUtils} = ChromeUtils.import("resource://specialpowers/SpecialPowersAPI.jsm");
+const {SpecialPowersAPIParent} = ChromeUtils.import("resource://specialpowers/SpecialPowersAPIParent.jsm");
+
+class ChromePowers extends SpecialPowersAPI {
+  constructor(window) {
+    super();
+
+    this.window = Cu.getWeakReference(window);
+
+    this.chromeWindow = window;
+
+    this.DOMWindowUtils = bindDOMWindowUtils(window);
+
+    this.parentActor = new SpecialPowersAPIParent();
+    this.parentActor.sendAsyncMessage = this.sendReply.bind(this);
+
+    this.listeners = new Map();
+  }
+
+  toString() { return "[ChromePowers]"; }
+  sanityCheck() { return "foo"; }
 
-  if (typeof(window) == "ChromeWindow" && typeof(content.window) == "Window") {
-    this.DOMWindowUtils = bindDOMWindowUtils(content.window);
-    this.window = Cu.getWeakReference(content.window);
-  } else {
-    this.DOMWindowUtils = bindDOMWindowUtils(window);
+  get contentWindow() {
+    return window;
+  }
+
+  get document() {
+    return window.document;
+  }
+
+  get docShell() {
+    return window.docShell;
+  }
+
+  sendReply(aType, aMsg) {
+    var msg = {name: aType, json: aMsg, data: aMsg};
+    if (!this.listeners.has(aType)) {
+      throw new Error(`No listener for ${aType}`);
+    }
+    this.listeners.get(aType)(msg);
+  }
+
+  sendAsyncMessage(aType, aMsg) {
+    var msg = {name: aType, json: aMsg, data: aMsg};
+    this.receiveMessage(msg);
+  }
+
+  async sendQuery(aType, aMsg) {
+    var msg = {name: aType, json: aMsg, data: aMsg};
+    return this.receiveMessage(msg);
   }
 
-  this.spObserver = new SpecialPowersObserverAPI();
-  this.spObserver._sendReply = this._sendReply.bind(this);
-  this.listeners = new Map();
+  _addMessageListener(aType, aCallback) {
+    if (this.listeners.has(aType)) {
+      throw new Error(`unable to handle multiple listeners for ${aType}`);
+    }
+    this.listeners.set(aType, aCallback);
+  }
+  _removeMessageListener(aType, aCallback) {
+    this.listeners.delete(aType);
+  }
+
+  registerProcessCrashObservers() {
+    this._sendSyncMessage("SPProcessCrashService", { op: "register-observer" });
+  }
+
+  unregisterProcessCrashObservers() {
+    this._sendSyncMessage("SPProcessCrashService", { op: "unregister-observer" });
+  }
+
+  receiveMessage(aMessage) {
+    switch (aMessage.name) {
+      case "SpecialPowers.Quit":
+        let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"].getService(Ci.nsIAppStartup);
+        appStartup.quit(Ci.nsIAppStartup.eForceQuit);
+        break;
+      case "SPProcessCrashService":
+        if (aMessage.json.op == "register-observer" || aMessage.json.op == "unregister-observer") {
+          // Hack out register/unregister specifically for browser-chrome leaks
+          break;
+        }
+      default:
+        // All calls go here, because we need to handle SPProcessCrashService calls as well
+        return this.parentActor.receiveMessage(aMessage);
+    }
+    return undefined;
+  }
+
+  quit() {
+    // We come in here as SpecialPowers.quit, but SpecialPowers is really ChromePowers.
+    // For some reason this.<func> resolves to TestRunner, so using SpecialPowers
+    // allows us to use the ChromePowers object which we defined below.
+    SpecialPowers._sendSyncMessage("SpecialPowers.Quit", {});
+  }
+
+  focus(aWindow) {
+    // We come in here as SpecialPowers.focus, but SpecialPowers is really ChromePowers.
+    // For some reason this.<func> resolves to TestRunner, so using SpecialPowers
+    // allows us to use the ChromePowers object which we defined below.
+    if (aWindow)
+      aWindow.focus();
+  }
+
+  executeAfterFlushingMessageQueue(aCallback) {
+    aCallback();
+  }
 }
 
-ChromePowers.prototype = new SpecialPowersAPI();
-
-ChromePowers.prototype.toString = function() { return "[ChromePowers]"; };
-ChromePowers.prototype.sanityCheck = function() { return "foo"; };
-
-// This gets filled in in the constructor.
-ChromePowers.prototype.DOMWindowUtils = undefined;
-
-ChromePowers.prototype._sendReply = function(aOrigMsg, aType, aMsg) {
-  var msg = {'name':aType, 'json': aMsg, 'data': aMsg};
-  if (!this.listeners.has(aType)) {
-    throw new Error(`No listener for ${aType}`);
-  }
-  this.listeners.get(aType)(msg);
-};
-
-ChromePowers.prototype._sendSyncMessage = function(aType, aMsg) {
-  var msg = {'name':aType, 'json': aMsg, 'data': aMsg};
-  return [this._receiveMessage(msg)];
-};
-
-ChromePowers.prototype._sendAsyncMessage = function(aType, aMsg) {
-  var msg = {'name':aType, 'json': aMsg, 'data': aMsg};
-  this._receiveMessage(msg);
-};
-
-ChromePowers.prototype._addMessageListener = function(aType, aCallback) {
-  if (this.listeners.has(aType)) {
-    throw new Error(`unable to handle multiple listeners for ${aType}`);
-  }
-  this.listeners.set(aType, aCallback);
-};
-ChromePowers.prototype._removeMessageListener = function(aType, aCallback) {
-  this.listeners.delete(aType);
-};
-
-ChromePowers.prototype.registerProcessCrashObservers = function() {
-  this._sendSyncMessage("SPProcessCrashService", { op: "register-observer" });
-};
-
-ChromePowers.prototype.unregisterProcessCrashObservers = function() {
-  this._sendSyncMessage("SPProcessCrashService", { op: "unregister-observer" });
-};
+if (window.parent.SpecialPowers && !window.SpecialPowers) {
+  window.SpecialPowers = window.parent.SpecialPowers;
+} else {
+  ChromeUtils.import("resource://specialpowers/SpecialPowersAPIParent.jsm", this);
 
-ChromePowers.prototype._receiveMessage = function(aMessage) {
-  switch (aMessage.name) {
-    case "SpecialPowers.Quit":
-      let appStartup = Cc["@mozilla.org/toolkit/app-startup;1"].getService(Ci.nsIAppStartup);
-      appStartup.quit(Ci.nsIAppStartup.eForceQuit);
-      break;
-    case "SPProcessCrashService":
-      if (aMessage.json.op == "register-observer" || aMessage.json.op == "unregister-observer") {
-        // Hack out register/unregister specifically for browser-chrome leaks
-        break;
-      } else if (aMessage.type == "crash-observed") {
-        for (let e of msg.dumpIDs) {
-          this._encounteredCrashDumpFiles.push(e.id + "." + e.extension);
-        }
-      }
-    default:
-      // All calls go here, because we need to handle SPProcessCrashService calls as well
-      return this.spObserver._receiveMessageAPI(aMessage);
-  }
-  return undefined;		// Avoid warning.
-};
-
-ChromePowers.prototype.quit = function() {
-  // We come in here as SpecialPowers.quit, but SpecialPowers is really ChromePowers.
-  // For some reason this.<func> resolves to TestRunner, so using SpecialPowers
-  // allows us to use the ChromePowers object which we defined below.
-  SpecialPowers._sendSyncMessage("SpecialPowers.Quit", {});
-};
-
-ChromePowers.prototype.focus = function(aWindow) {
-  // We come in here as SpecialPowers.focus, but SpecialPowers is really ChromePowers.
-  // For some reason this.<func> resolves to TestRunner, so using SpecialPowers
-  // allows us to use the ChromePowers object which we defined below.
-  if (aWindow)
-    aWindow.focus();
-};
-
-ChromePowers.prototype.executeAfterFlushingMessageQueue = function(aCallback) {
-  aCallback();
-};
-
-if ((window.parent !== null) &&
-    (window.parent !== undefined) &&
-    (window.parent.wrappedJSObject.SpecialPowers) &&
-    !(window.wrappedJSObject.SpecialPowers)) {
-  window.wrappedJSObject.SpecialPowers = window.parent.SpecialPowers;
-} else {
-  window.wrappedJSObject.SpecialPowers = new ChromePowers(window);
+  window.SpecialPowers = new ChromePowers(window);
 }
 
rename from testing/specialpowers/content/MozillaLogger.js
rename to testing/mochitest/tests/SimpleTest/MozillaLogger.js
--- a/testing/specialpowers/content/MozillaLogger.js
+++ b/testing/mochitest/tests/SimpleTest/MozillaLogger.js
@@ -1,135 +1,94 @@
 /**
  * MozillaLogger, a base class logger that just logs to stdout.
  */
 
 "use strict";
 
-/* import-globals-from specialpowers.js */
-
-function MozillaLogger(aPath) {
+function formatLogMessage(msg) {
+  return msg.info.join(" ") + "\n";
 }
 
-function formatLogMessage(msg) {
-    return msg.info.join(" ") + "\n";
+function importJSM(jsm) {
+  if (typeof ChromeUtils === "object") {
+    return ChromeUtils.import(jsm);
+  }
+  /* globals SpecialPowers */
+  let obj = {};
+  SpecialPowers.Cu.import(jsm, obj);
+  return SpecialPowers.wrap(obj);
 }
 
-MozillaLogger.prototype = {
-  init(path) {},
+let CC = (typeof Components === "object"
+            ? Components
+            : SpecialPowers.wrap(SpecialPowers.Components)).Constructor;
+
+let ConverterOutputStream = CC("@mozilla.org/intl/converter-output-stream;1",
+                               "nsIConverterOutputStream", "init");
 
-  getLogCallback() {
-    return function(msg) {
-      var data = formatLogMessage(msg);
-      dump(data);
+class MozillaLogger {
+  get logCallback() {
+    return (msg) => {
+      this.log(formatLogMessage(msg));
     };
-  },
+  }
 
   log(msg) {
     dump(msg);
-  },
-
-  close() {},
-};
-
-
-/**
- * SpecialPowersLogger, inherits from MozillaLogger and utilizes SpecialPowers.
- * intented to be used in content scripts to write to a file
- */
-function SpecialPowersLogger(aPath) {
-  // Call the base constructor
-  MozillaLogger.call(this);
-  this.prototype = new MozillaLogger(aPath);
-  this.init(aPath);
-}
+  }
 
-SpecialPowersLogger.prototype = {
-  init(path) {
-    SpecialPowers.setLogFile(path);
-  },
-
-  getLogCallback() {
-    return function(msg) {
-      var data = formatLogMessage(msg);
-      SpecialPowers.log(data);
-
-      if (data.includes("SimpleTest FINISH")) {
-        SpecialPowers.closeLogFile();
-      }
-    };
-  },
-
-  log(msg) {
-    SpecialPowers.log(msg);
-  },
-
-  close() {
-    SpecialPowers.closeLogFile();
-  },
-};
+  close() {}
+}
 
 
 /**
  * MozillaFileLogger, a log listener that can write to a local file.
  * intended to be run from chrome space
  */
 
 /** Init the file logger with the absolute path to the file.
     It will create and append if the file already exists **/
-function MozillaFileLogger(aPath) {
-  // Call the base constructor
-  MozillaLogger.call(this);
-  this.prototype = new MozillaLogger(aPath);
-  this.init(aPath);
-}
+class MozillaFileLogger extends MozillaLogger {
+  constructor(aPath) {
+    super();
 
-MozillaFileLogger.prototype = {
+    const {FileUtils} = importJSM("resource://gre/modules/FileUtils.jsm");
+
+    this._file = FileUtils.File(aPath);
+    this._foStream = FileUtils.openFileOutputStream(
+        this._file, (FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE |
+                     FileUtils.MODE_APPEND));
 
-  init(path) {
-    var PR_WRITE_ONLY   = 0x02; // Open for writing only.
-    var PR_CREATE_FILE  = 0x08;
-    var PR_APPEND       = 0x10;
-    this._file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
-    this._file.initWithPath(path);
-    this._foStream = Cc["@mozilla.org/network/file-output-stream;1"]
-                       .createInstance(Ci.nsIFileOutputStream);
-    this._foStream.init(this._file, PR_WRITE_ONLY | PR_CREATE_FILE | PR_APPEND,
-                                     436 /* 0664 */, 0);
+    this._converter = ConverterOutputStream(this._foStream, "UTF-8");
+  }
 
-    this._converter = Cc["@mozilla.org/intl/converter-output-stream;1"]
-                        .createInstance(Ci.nsIConverterOutputStream);
-    this._converter.init(this._foStream, "UTF-8");
-  },
+  get logCallback() {
+    return (msg) => {
+      if (this._converter) {
+        var data = formatLogMessage(msg);
+        this.log(data);
 
-  getLogCallback() {
-    return function(msg) {
-      var data = formatLogMessage(msg);
-      if (MozillaFileLogger._converter) {
-        this._converter.writeString(data);
-      }
-
-      if (data.includes("SimpleTest FINISH")) {
-        MozillaFileLogger.close();
+        if (data.includes("SimpleTest FINISH")) {
+          this.close();
+        }
       }
     };
-  },
+  }
 
   log(msg) {
     if (this._converter) {
       this._converter.writeString(msg);
     }
-  },
+  }
+
   close() {
-    if (this._converter) {
-      this._converter.flush();
-      this._converter.close();
-    }
+    this._converter.flush();
+    this._converter.close();
 
     this._foStream = null;
     this._converter = null;
     this._file = null;
-  },
-};
+  }
+}
 
 this.MozillaLogger = MozillaLogger;
-this.SpecialPowersLogger = SpecialPowersLogger;
 this.MozillaFileLogger = MozillaFileLogger;
--- a/testing/mochitest/tests/SimpleTest/SimpleTest.js
+++ b/testing/mochitest/tests/SimpleTest/SimpleTest.js
@@ -1148,17 +1148,17 @@ SimpleTest.finish = function() {
 
     SimpleTest._alreadyFinished = true;
 
     if (SimpleTest._inChaosMode) {
         SpecialPowers.DOMWindowUtils.leaveChaosMode();
         SimpleTest._inChaosMode = false;
     }
 
-    var afterCleanup = function() {
+    var afterCleanup = async function() {
         SpecialPowers.removeFiles();
 
         if (SpecialPowers.DOMWindowUtils.isTestControllingRefreshes) {
             SimpleTest.ok(false, "test left refresh driver under test control");
             SpecialPowers.DOMWindowUtils.restoreNormalRefresh();
         }
         if (SimpleTest._expectingUncaughtException) {
             SimpleTest.ok(false, "expectUncaughtException was called but no uncaught exception was detected!");
@@ -1174,17 +1174,17 @@ SimpleTest.finish = function() {
         if (SimpleTest._tests.length == 0) {
             SimpleTest.ok(false, "[SimpleTest.finish()] No checks actually run. "
                                + "(You need to call ok(), is(), or similar "
                                + "functions at least once.  Make sure you use "
                                + "SimpleTest.waitForExplicitFinish() if you need "
                                + "it.)");
         }
 
-        let workers = SpecialPowers.registeredServiceWorkers();
+        let workers = await SpecialPowers.registeredServiceWorkers();
         let promise = null;
         if (SimpleTest._expectingRegisteredServiceWorker) {
             if (workers.length === 0) {
                 SimpleTest.ok(false, "This test is expected to leave a service worker registered");
             }
         } else {
             if (workers.length > 0) {
                 SimpleTest.ok(false, "This test left a service worker registered without cleaning it up");
--- a/testing/mochitest/tests/SimpleTest/TestRunner.js
+++ b/testing/mochitest/tests/SimpleTest/TestRunner.js
@@ -204,17 +204,17 @@ TestRunner.addFailedTest = function(test
 };
 
 TestRunner.setFailureFile = function(fileName) {
     TestRunner._failureFile = fileName;
 }
 
 TestRunner.generateFailureList = function () {
     if (TestRunner._failureFile) {
-        var failures = new SpecialPowersLogger(TestRunner._failureFile);
+        var failures = new MozillaFileLogger(TestRunner._failureFile);
         failures.log(JSON.stringify(TestRunner._failedTests));
         failures.close();
     }
 };
 
 /**
  * If logEnabled is true, this is the logger that will be used.
  **/
@@ -538,30 +538,30 @@ TestRunner.testFinished = function(tests
         // TODO : replace this by a function that returns the mem data as an object
         // that's dumped later with the test_end message
         MemoryStats.dump(TestRunner._currentTest,
                          TestRunner.currentTestURL,
                          TestRunner.dumpOutputDirectory,
                          TestRunner.dumpAboutMemoryAfterTest,
                          TestRunner.dumpDMDAfterTest);
 
-        function cleanUpCrashDumpFiles() {
-            if (!SpecialPowers.removeExpectedCrashDumpFiles(TestRunner._expectingProcessCrash)) {
+        async function cleanUpCrashDumpFiles() {
+            if (!await SpecialPowers.removeExpectedCrashDumpFiles(TestRunner._expectingProcessCrash)) {
                 var subtest = "expected-crash-dump-missing";
                 TestRunner.structuredLogger.testStatus(TestRunner.currentTestURL,
                                                        subtest,
                                                        "ERROR",
                                                        "PASS",
                                                        "This test did not leave any crash dumps behind, but we were expecting some!");
                 extraTests.push({ name: subtest, result: false });
                 result = "ERROR";
             }
 
             var unexpectedCrashDumpFiles =
-                SpecialPowers.findUnexpectedCrashDumpFiles();
+                await SpecialPowers.findUnexpectedCrashDumpFiles();
             TestRunner._expectingProcessCrash = false;
             if (unexpectedCrashDumpFiles.length) {
                 var subtest = "unexpected-crash-dump-found";
                 TestRunner.structuredLogger.testStatus(TestRunner.currentTestURL,
                                                        subtest,
                                                        "ERROR",
                                                        "PASS",
                                                        "This test left crash dumps behind, but we " +
@@ -572,17 +572,17 @@ TestRunner.testFinished = function(tests
                 result = "CRASH";
                 unexpectedCrashDumpFiles.sort().forEach(function(aFilename) {
                     TestRunner.structuredLogger.info("Found unexpected crash dump file " +
                                                      aFilename + ".");
                 });
             }
 
             if (TestRunner.cleanupCrashes) {
-                if (SpecialPowers.removePendingCrashDumpFiles()) {
+                if (await SpecialPowers.removePendingCrashDumpFiles()) {
                     TestRunner.structuredLogger.info("This test left pending crash dumps");
                 }
             }
         }
 
         function runNextTest() {
             if (TestRunner.currentTestURL != TestRunner.getLoadedTestURL()) {
                 TestRunner.structuredLogger.testStatus(TestRunner.currentTestURL,
@@ -635,24 +635,22 @@ TestRunner.testFinished = function(tests
                    TestRunner.structuredLogger.error(TestRunner.currentTestURL + " logged result after SimpleTest.finish(): " + wrongtestname);
                  }
                  TestRunner.updateUI([{ result: false }]);
                }
             });
             TestRunner._makeIframe(interstitialURL, 0);
         }
 
-        SpecialPowers.executeAfterFlushingMessageQueue(function() {
-            SpecialPowers.waitForCrashes(TestRunner._expectingProcessCrash)
-                         .then(() => {
-                cleanUpCrashDumpFiles();
-                SpecialPowers.flushPermissions(function () {
-                    SpecialPowers.flushPrefEnv(runNextTest);
-                });
-            });
+        SpecialPowers.executeAfterFlushingMessageQueue(async function() {
+          await SpecialPowers.waitForCrashes(TestRunner._expectingProcessCrash);
+          await cleanUpCrashDumpFiles();
+          await SpecialPowers.flushPermissions();
+          await SpecialPowers.flushPrefEnv();
+          runNextTest();
         });
     });
 };
 
 TestRunner.testUnloaded = function() {
     // If we're in a debug build, check assertion counts.  This code is
     // similar to the code in Tester_nextTest in browser-test.js used
     // for browser-chrome mochitests.
--- a/testing/mochitest/tests/SimpleTest/moz.build
+++ b/testing/mochitest/tests/SimpleTest/moz.build
@@ -1,24 +1,24 @@
 # -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
 TEST_HARNESS_FILES.testing.mochitest.tests.SimpleTest += [
     '/docshell/test/chrome/docshell_helpers.js',
-    '/testing/specialpowers/content/MozillaLogger.js',
     'ChromeTask.js',
     'EventUtils.js',
     'ExtensionTestUtils.js',
     'iframe-between-tests.html',
     'LogController.js',
     'MemoryStats.js',
     'MockObjects.js',
+    'MozillaLogger.js',
     'NativeKeyCodes.js',
     'paint_listener.js',
     'setup.js',
     'SimpleTest.js',
     'test.css',
     'TestRunner.js',
     'WindowSnapshot.js',
 ]
--- a/testing/mochitest/tests/SimpleTest/setup.js
+++ b/testing/mochitest/tests/SimpleTest/setup.js
@@ -121,18 +121,18 @@ if (params.failureFile) {
 
 // Breaks execution and enters the JS debugger on a test failure
 if (params.debugOnFailure) {
   TestRunner.debugOnFailure = true;
 }
 
 // logFile to write our results
 if (params.logFile) {
-  var spl = new SpecialPowersLogger(params.logFile);
-  TestRunner.logger.addListener("mozLogger", fileLevel + "", spl.getLogCallback());
+  var mfl = new MozillaFileLogger(params.logFile);
+  TestRunner.logger.addListener("mozLogger", fileLevel + "", mfl.logCallback);
 }
 
 // A temporary hack for android 4.0 where Fennec utilizes the pandaboard so much it reboots
 if (params.runSlower) {
   TestRunner.runSlower = true;
 }
 
 if (params.dumpOutputDirectory) {
--- a/testing/specialpowers/api.js
+++ b/testing/specialpowers/api.js
@@ -1,30 +1,43 @@
 /* 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/. */
 
 /* globals ExtensionAPI */
 
 const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+const {FileUtils} = ChromeUtils.import("resource://gre/modules/FileUtils.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "resProto",
                                    "@mozilla.org/network/protocol;1?name=resource",
                                    "nsISubstitutingProtocolHandler");
 
 this.specialpowers = class extends ExtensionAPI {
   onStartup() {
     let uri = Services.io.newURI("content/", null, this.extension.rootURI);
     resProto.setSubstitutionWithFlags("specialpowers", uri,
                                       resProto.ALLOW_CONTENT_ACCESS);
 
-    const {SpecialPowersObserver} = ChromeUtils.import("resource://specialpowers/SpecialPowersObserver.jsm");
-    this.observer = new SpecialPowersObserver();
-    this.observer.init();
+    // Register special testing modules.
+    Components.manager.QueryInterface(Ci.nsIComponentRegistrar)
+              .autoRegister(FileUtils.getFile("ProfD", ["tests.manifest"]));
+
+    ChromeUtils.registerWindowActor("SpecialPowers", {
+      allFrames: true,
+      child: {
+        moduleURI: "resource://specialpowers/SpecialPowersChild.jsm",
+        events: {
+          DOMWindowCreated: {},
+        },
+      },
+      parent: {
+        moduleURI: "resource://specialpowers/SpecialPowersParent.jsm",
+      },
+    });
   }
 
   onShutdown() {
-    this.observer.uninit();
-    this.observer = null;
+    ChromeUtils.unregisterWindowActor("SpecialPowers");
     resProto.setSubstitution("specialpowers", null);
   }
 };
rename from testing/specialpowers/content/specialpowersAPI.js
rename to testing/specialpowers/content/SpecialPowersAPI.jsm
--- a/testing/specialpowers/content/specialpowersAPI.js
+++ b/testing/specialpowers/content/SpecialPowersAPI.jsm
@@ -2,324 +2,48 @@
  * 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/. */
 /* This code is loaded in every child process that is started by mochitest in
  * order to be used as a replacement for UniversalXPConnect
  */
 
 "use strict";
 
-/* import-globals-from MozillaLogger.js */
-/* globals XPCNativeWrapper */
-
 var EXPORTED_SYMBOLS = ["SpecialPowersAPI", "bindDOMWindowUtils"];
 
 var {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
 
-Services.scriptloader.loadSubScript("resource://specialpowers/MozillaLogger.js", this);
-
-ChromeUtils.defineModuleGetter(this, "setTimeout",
-                               "resource://gre/modules/Timer.jsm");
 ChromeUtils.defineModuleGetter(this, "MockFilePicker",
                                "resource://specialpowers/MockFilePicker.jsm");
 ChromeUtils.defineModuleGetter(this, "MockColorPicker",
                                "resource://specialpowers/MockColorPicker.jsm");
 ChromeUtils.defineModuleGetter(this, "MockPermissionPrompt",
                                "resource://specialpowers/MockPermissionPrompt.jsm");
+ChromeUtils.defineModuleGetter(this, "SpecialPowersSandbox",
+                               "resource://specialpowers/SpecialPowersSandbox.jsm");
+ChromeUtils.defineModuleGetter(this, "WrapPrivileged",
+                               "resource://specialpowers/WrapPrivileged.jsm");
 ChromeUtils.defineModuleGetter(this, "PrivateBrowsingUtils",
                                "resource://gre/modules/PrivateBrowsingUtils.jsm");
 ChromeUtils.defineModuleGetter(this, "NetUtil",
                                "resource://gre/modules/NetUtil.jsm");
 ChromeUtils.defineModuleGetter(this, "AppConstants",
                                "resource://gre/modules/AppConstants.jsm");
 
 ChromeUtils.defineModuleGetter(this, "PerTestCoverageUtils",
   "resource://testing-common/PerTestCoverageUtils.jsm");
 
 // Allow stuff from this scope to be accessed from non-privileged scopes. This
 // would crash if used outside of automation.
 Cu.forcePermissiveCOWs();
 
-function SpecialPowersAPI() {
-  this._consoleListeners = [];
-  this._encounteredCrashDumpFiles = [];
-  this._unexpectedCrashDumpFiles = { };
-  this._crashDumpDir = null;
-  this._mfl = null;
-  this._prefEnvUndoStack = [];
-  this._pendingPrefs = [];
-  this._applyingPrefs = false;
-  this._permissionsUndoStack = [];
-  this._pendingPermissions = [];
-  this._applyingPermissions = false;
-  this._observingPermissions = false;
-}
-
 function bindDOMWindowUtils(aWindow) {
-  if (!aWindow)
-    return undefined;
-
-  var util = aWindow.windowUtils;
-  return wrapPrivileged(util);
-}
-
-function isWrappable(x) {
-  if (typeof x === "object")
-    return x !== null;
-  return typeof x === "function";
-}
-
-function isWrapper(x) {
-  return isWrappable(x) && (typeof x.SpecialPowers_wrappedObject !== "undefined");
-}
-
-function unwrapIfWrapped(x) {
-  return isWrapper(x) ? unwrapPrivileged(x) : x;
-}
-
-function wrapIfUnwrapped(x) {
-  return isWrapper(x) ? x : wrapPrivileged(x);
-}
-
-function isObjectOrArray(obj) {
-  if (Object(obj) !== obj)
-    return false;
-  let arrayClasses = ["Object", "Array", "Int8Array", "Uint8Array",
-                      "Int16Array", "Uint16Array", "Int32Array",
-                      "Uint32Array", "Float32Array", "Float64Array",
-                      "Uint8ClampedArray"];
-  let className = Cu.getClassName(obj, true);
-  return arrayClasses.includes(className);
-}
-
-// In general, we want Xray wrappers for content DOM objects, because waiving
-// Xray gives us Xray waiver wrappers that clamp the principal when we cross
-// compartment boundaries. However, there are some exceptions where we want
-// to use a waiver:
-//
-// * Xray adds some gunk to toString(), which has the potential to confuse
-//   consumers that aren't expecting Xray wrappers. Since toString() is a
-//   non-privileged method that returns only strings, we can just waive Xray
-//   for that case.
-//
-// * We implement Xrays to pure JS [[Object]] and [[Array]] instances that
-//   filter out tricky things like callables. This is the right thing for
-//   security in general, but tends to break tests that try to pass object
-//   literals into SpecialPowers. So we waive [[Object]] and [[Array]]
-//   instances before inspecting properties.
-//
-// * When we don't have meaningful Xray semantics, we create an Opaque
-//   XrayWrapper for security reasons. For test code, we generally want to see
-//   through that sort of thing.
-function waiveXraysIfAppropriate(obj, propName) {
-  if (propName == "toString" || isObjectOrArray(obj) ||
-      /Opaque/.test(Object.prototype.toString.call(obj))) {
-    return XPCNativeWrapper.unwrap(obj);
-}
-  return obj;
-}
-
-// We can't call apply() directy on Xray-wrapped functions, so we have to be
-// clever.
-function doApply(fun, invocant, args) {
-  // We implement Xrays to pure JS [[Object]] instances that filter out tricky
-  // things like callables. This is the right thing for security in general,
-  // but tends to break tests that try to pass object literals into
-  // SpecialPowers. So we waive [[Object]] instances when they're passed to a
-  // SpecialPowers-wrapped callable.
-  //
-  // Note that the transitive nature of Xray waivers means that any property
-  // pulled off such an object will also be waived, and so we'll get principal
-  // clamping for Xrayed DOM objects reached from literals, so passing things
-  // like {l : xoWin.location} won't work. Hopefully the rabbit hole doesn't
-  // go that deep.
-  args = args.map(x => isObjectOrArray(x) ? Cu.waiveXrays(x) : x);
-  return Reflect.apply(fun, invocant, args);
-}
-
-function wrapPrivileged(obj) {
-  // Primitives pass straight through.
-  if (!isWrappable(obj))
-    return obj;
-
-  // No double wrapping.
-  if (isWrapper(obj))
-    throw new Error("Trying to double-wrap object!");
-
-  let dummy;
-  if (typeof obj === "function")
-    dummy = function() {};
-  else
-    dummy = Object.create(null);
-
-  return new Proxy(dummy, new SpecialPowersHandler(obj));
-}
-
-function unwrapPrivileged(x) {
-  // We don't wrap primitives, so sometimes we have a primitive where we'd
-  // expect to have a wrapper. The proxy pretends to be the type that it's
-  // emulating, so we can just as easily check isWrappable() on a proxy as
-  // we can on an unwrapped object.
-  if (!isWrappable(x))
-    return x;
-
-  // If we have a wrappable type, make sure it's wrapped.
-  if (!isWrapper(x))
-    throw new Error("Trying to unwrap a non-wrapped object!");
-
-  var obj = x.SpecialPowers_wrappedObject;
-  // unwrapped.
-  return obj;
-}
-
-function specialPowersHasInstance(value) {
-  // Because we return wrapped versions of this function, when it's called its
-  // wrapper will unwrap the "this" as well as the function itself.  So our
-  // "this" is the unwrapped thing we started out with.
-  return value instanceof this;
+  return aWindow && WrapPrivileged.wrap(aWindow.windowUtils);
 }
 
-function SpecialPowersHandler(wrappedObject) {
-  this.wrappedObject = wrappedObject;
-}
-
-SpecialPowersHandler.prototype = {
-  construct(target, args) {
-    // The arguments may or may not be wrappers. Unwrap them if necessary.
-    var unwrappedArgs = Array.prototype.slice.call(args).map(unwrapIfWrapped);
-
-    // We want to invoke "obj" as a constructor, but using unwrappedArgs as
-    // the arguments.  Make sure to wrap and re-throw exceptions!
-    try {
-      return wrapIfUnwrapped(Reflect.construct(this.wrappedObject, unwrappedArgs));
-    } catch (e) {
-      throw wrapIfUnwrapped(e);
-    }
-  },
-
-  apply(target, thisValue, args) {
-    // The invocant and arguments may or may not be wrappers. Unwrap
-    // them if necessary.
-    var invocant = unwrapIfWrapped(thisValue);
-    var unwrappedArgs = Array.prototype.slice.call(args).map(unwrapIfWrapped);
-
-    try {
-      return wrapIfUnwrapped(doApply(this.wrappedObject, invocant, unwrappedArgs));
-    } catch (e) {
-      // Wrap exceptions and re-throw them.
-      throw wrapIfUnwrapped(e);
-    }
-  },
-
-  has(target, prop) {
-    if (prop === "SpecialPowers_wrappedObject")
-      return true;
-
-    return Reflect.has(this.wrappedObject, prop);
-  },
-
-  get(target, prop, receiver) {
-    if (prop === "SpecialPowers_wrappedObject")
-      return this.wrappedObject;
-
-    let obj = waiveXraysIfAppropriate(this.wrappedObject, prop);
-    let val = Reflect.get(obj, prop);
-    if (val === undefined && prop == Symbol.hasInstance) {
-      // Special-case Symbol.hasInstance to pass the hasInstance check on to our
-      // target.  We only do this when the target doesn't have its own
-      // Symbol.hasInstance already.  Once we get rid of JS engine class
-      // instance hooks (bug 1448218) and always use Symbol.hasInstance, we can
-      // remove this bit (bug 1448400).
-      return wrapPrivileged(specialPowersHasInstance);
-    }
-    return wrapIfUnwrapped(val);
-  },
-
-  set(target, prop, val, receiver) {
-    if (prop === "SpecialPowers_wrappedObject")
-      return false;
-
-    let obj = waiveXraysIfAppropriate(this.wrappedObject, prop);
-    return Reflect.set(obj, prop, unwrapIfWrapped(val));
-  },
-
-  delete(target, prop) {
-    if (prop === "SpecialPowers_wrappedObject")
-      return false;
-
-    return Reflect.deleteProperty(this.wrappedObject, prop);
-  },
-
-  defineProperty(target, prop, descriptor) {
-    throw new Error("Can't call defineProperty on SpecialPowers wrapped object");
-  },
-
-  getOwnPropertyDescriptor(target, prop) {
-    // Handle our special API.
-    if (prop === "SpecialPowers_wrappedObject") {
-      return { value: this.wrappedObject, writeable: true,
-               configurable: true, enumerable: false };
-    }
-
-    let obj = waiveXraysIfAppropriate(this.wrappedObject, prop);
-    let desc = Reflect.getOwnPropertyDescriptor(obj, prop);
-
-    if (desc === undefined) {
-      if (prop == Symbol.hasInstance) {
-        // Special-case Symbol.hasInstance to pass the hasInstance check on to
-        // our target.  We only do this when the target doesn't have its own
-        // Symbol.hasInstance already.  Once we get rid of JS engine class
-        // instance hooks (bug 1448218) and always use Symbol.hasInstance, we
-        // can remove this bit (bug 1448400).
-        return { value: wrapPrivileged(specialPowersHasInstance),
-                 writeable: true, configurable: true, enumerable: false };
-      }
-
-      return undefined;
-    }
-
-    // Transitively maintain the wrapper membrane.
-    function wrapIfExists(key) {
-      if (key in desc)
-        desc[key] = wrapIfUnwrapped(desc[key]);
-    }
-
-    wrapIfExists("value");
-    wrapIfExists("get");
-    wrapIfExists("set");
-
-    // A trapping proxy's properties must always be configurable, but sometimes
-    // we come across non-configurable properties. Tell a white lie.
-    desc.configurable = true;
-
-    return desc;
-  },
-
-  ownKeys(target) {
-    // Insert our special API. It's not enumerable, but ownKeys()
-    // includes non-enumerable properties.
-    let props = ["SpecialPowers_wrappedObject"];
-
-    // Do the normal thing.
-    let flt = (a) => !props.includes(a);
-    props = props.concat(Reflect.ownKeys(this.wrappedObject).filter(flt));
-
-    // If we've got an Xray wrapper, include the expandos as well.
-    if ("wrappedJSObject" in this.wrappedObject) {
-      props = props.concat(Reflect.ownKeys(this.wrappedObject.wrappedJSObject)
-                           .filter(flt));
-    }
-
-    return props;
-  },
-
-  preventExtensions(target) {
-    throw new Error("Can't call preventExtensions on SpecialPowers wrapped object");
-  },
-};
 
 // SPConsoleListener reflects nsIConsoleMessage objects into JS in a
 // tidy, XPCOM-hiding way.  Messages that are nsIScriptError objects
 // have their properties exposed in detail.  It also auto-unregisters
 // itself when it receives a "sentinel" message.
 function SPConsoleListener(callback) {
   this.callback = callback;
 }
@@ -382,44 +106,58 @@ SPConsoleListener.prototype = {
       Services.console.unregisterListener(this);
     }
   },
 
   QueryInterface: ChromeUtils.generateQI([Ci.nsIConsoleListener,
                                           Ci.nsIObserver]),
 };
 
-function wrapCallback(cb) {
-  return function SpecialPowersCallbackWrapper() {
-    var args = Array.prototype.map.call(arguments, wrapIfUnwrapped);
-    return cb.apply(this, args);
-  };
-}
+class SpecialPowersAPI extends JSWindowActorChild {
+  constructor() {
+    super();
+
+    this._consoleListeners = [];
+    this._encounteredCrashDumpFiles = [];
+    this._unexpectedCrashDumpFiles = { };
+    this._crashDumpDir = null;
+    this._mfl = null;
+    this._applyingPermissions = false;
+    this._observingPermissions = false;
+    this._asyncObservers = new WeakMap();
+    this._xpcomabi = null;
+    this._os = null;
+    this._pu = null;
+
+
+    this._nextExtensionID = 0;
+    this._extensionListeners = null;
+  }
 
-function wrapCallbackObject(obj) {
-  obj = Cu.waiveXrays(obj);
-  var wrapper = {};
-  for (var i in obj) {
-    if (typeof obj[i] == "function")
-      wrapper[i] = wrapCallback(obj[i]);
-    else
-      wrapper[i] = obj[i];
+  receiveMessage(message) {
+    switch (message.name) {
+      case "Assert": {
+        // An assertion has been done in a mochitest chrome script
+        let {name, passed, stack, diag} = message.data;
+
+        let SimpleTest = (
+            this.contentWindow &&
+            this.contentWindow.wrappedJSObject.SimpleTest);
+
+        if (SimpleTest) {
+          SimpleTest.record(passed, name, diag, stack && stack.formattedStack);
+        } else {
+          // Well, this is unexpected.
+          dump(name + "\n");
+        }
+      }
+      break;
+    }
+    return undefined;
   }
-  return wrapper;
-}
-
-function setWrapped(obj, prop, val) {
-  if (!isWrapper(obj))
-    throw new Error("You only need to use this for SpecialPowers wrapped objects");
-
-  obj = unwrapPrivileged(obj);
-  return Reflect.set(obj, prop, val);
-}
-
-SpecialPowersAPI.prototype = {
 
   /*
    * Privileged object wrapping API
    *
    * Usage:
    *   var wrapper = SpecialPowers.wrap(obj);
    *   wrapper.privilegedMethod(); wrapper.privilegedProperty;
    *   obj === SpecialPowers.unwrap(wrapper);
@@ -439,105 +177,159 @@ SpecialPowersAPI.prototype = {
    *
    *  - The wrapper cannot see expando properties on unprivileged DOM objects.
    *    That is to say, the wrapper uses Xray delegation.
    *
    *  - The wrapper sometimes guesses certain ES5 attributes for returned
    *    properties. This is explained in a comment in the wrapper code above,
    *    and shouldn't be a problem.
    */
-  wrap: wrapIfUnwrapped,
-  unwrap: unwrapIfWrapped,
-  isWrapper,
+  wrap(obj) { return WrapPrivileged.wrap(obj); }
+  unwrap(obj) { return WrapPrivileged.unwrap(obj); }
+  isWrapper(val) { return WrapPrivileged.isWrapper(val); }
 
   /*
    * When content needs to pass a callback or a callback object to an API
    * accessed over SpecialPowers, that API may sometimes receive arguments for
    * whom it is forbidden to create a wrapper in content scopes. As such, we
    * need a layer to wrap the values in SpecialPowers wrappers before they ever
    * reach content.
    */
-  wrapCallback,
-  wrapCallbackObject,
+  wrapCallback(func) { return WrapPrivileged.wrapCallback(func); }
+  wrapCallbackObject(obj) { return WrapPrivileged.wrapCallbackObject(obj); }
 
   /*
    * Used for assigning a property to a SpecialPowers wrapper, without unwrapping
    * the value that is assigned.
    */
-  setWrapped,
+  setWrapped(obj, prop, val) {
+    if (!WrapPrivileged.isWrapper(obj))
+      throw new Error("You only need to use this for SpecialPowers wrapped objects");
+
+    obj = WrapPrivileged.unwrap(obj);
+    return Reflect.set(obj, prop, val);
+  }
 
   /*
    * Create blank privileged objects to use as out-params for privileged functions.
    */
   createBlankObject() {
     return {};
-  },
+  }
 
   /*
    * Because SpecialPowers wrappers don't preserve identity, comparing with ==
    * can be hazardous. Sometimes we can just unwrap to compare, but sometimes
    * wrapping the underlying object into a content scope is forbidden. This
    * function strips any wrappers if they exist and compare the underlying
    * values.
    */
   compare(a, b) {
-    return unwrapIfWrapped(a) === unwrapIfWrapped(b);
-  },
+    return WrapPrivileged.unwrap(a) === WrapPrivileged.unwrap(b);
+  }
 
   get MockFilePicker() {
     return MockFilePicker;
-  },
+  }
 
   get MockColorPicker() {
     return MockColorPicker;
-  },
+  }
 
   get MockPermissionPrompt() {
     return MockPermissionPrompt;
-  },
+  }
 
   /*
    * Load a privileged script that runs same-process. This is different from
    * |loadChromeScript|, which will run in the parent process in e10s mode.
    */
   loadPrivilegedScript(aFunction) {
     var str = "(" + aFunction.toString() + ")();";
     let gGlobalObject = Cu.getGlobalForObject(this);
     let sb = Cu.Sandbox(gGlobalObject);
-    var window = this.window.get();
+    var window = this.contentWindow;
     var mc = new window.MessageChannel();
     sb.port = mc.port1;
     try {
       let blob = new Blob([str], {type: "application/javascript"});
       let blobUrl = URL.createObjectURL(blob);
       Services.scriptloader.loadSubScript(blobUrl, sb);
     } catch (e) {
-      throw wrapIfUnwrapped(e);
+      throw WrapPrivileged.wrap(e);
     }
 
     return mc.port2;
-  },
+  }
+
+  _readUrlAsString(aUrl) {
+    // Fetch script content as we can't use scriptloader's loadSubScript
+    // to evaluate http:// urls...
+    var scriptableStream = Cc["@mozilla.org/scriptableinputstream;1"]
+                             .getService(Ci.nsIScriptableInputStream);
+
+    var channel = NetUtil.newChannel({
+      uri: aUrl,
+      loadUsingSystemPrincipal: true,
+    });
+    var input = channel.open();
+    scriptableStream.init(input);
+
+    var str;
+    var buffer = [];
+
+    while ((str = scriptableStream.read(4096))) {
+      buffer.push(str);
+    }
+
+    var output = buffer.join("");
+
+    scriptableStream.close();
+    input.close();
+
+    var status;
+    if (channel instanceof Ci.nsIHttpChannel) {
+      status = channel.responseStatus;
+    }
+
+    if (status == 404) {
+      throw new Error(
+        `Error while executing chrome script '${aUrl}':\n` +
+        "The script doesn't exist. Ensure you have registered it in " +
+        "'support-files' in your mochitest.ini.");
+    }
+
+    return output;
+  }
 
   loadChromeScript(urlOrFunction, sandboxOptions) {
     // Create a unique id for this chrome script
     let uuidGenerator = Cc["@mozilla.org/uuid-generator;1"]
                           .getService(Ci.nsIUUIDGenerator);
     let id = uuidGenerator.generateUUID().toString();
 
     // Tells chrome code to evaluate this chrome script
     let scriptArgs = { id, sandboxOptions };
     if (typeof(urlOrFunction) == "function") {
       scriptArgs.function = {
         body: "(" + urlOrFunction