Backed out 3 changesets (bug 1541557) for failures in SpecialPowersObserverAPI.js. a=backout CLOSED TREE FIREFOX_NIGHTLY_69_END
authorSebastian Hengst <archaeopteryx@coole-files.de>
Mon, 08 Jul 2019 14:38:45 +0200
changeset 541247 e596664275d5e3e2fdcb7fa8d1447289f99269c3
parent 541246 5b91c8869f4275d2a2857114cf12fa7b7bf4190e
child 541248 f4452e031aedcf554ecb556d372bf6a5c773048b
child 542086 25d8751dc5fb5901dff9e1ab5d2daaf3a00fb09c
child 542150 addfd74916031a1a8bb926774a5c7856cf87ca7a
child 542258 8ad40297f1e96f3a5fecff528c9f2a5507883cc1
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)
reviewersbackout
bugs1541557, 1561150
milestone69.0a1
backs out5b91c8869f4275d2a2857114cf12fa7b7bf4190e
a636725ad217d000f040846b91f5d2d2fea50449
7e6657f88b7694ecd841088963ff71d767e4ec22
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Backed out 3 changesets (bug 1541557) for failures in SpecialPowersObserverAPI.js. a=backout CLOSED TREE Backed out changeset 5b91c8869f42 (bug 1541557) Backed out changeset a636725ad217 (bug 1541557) Backed out changeset 7e6657f88b76 (bug 1561150)
browser/base/content/test/general/head.js
browser/base/content/test/performance/browser_startup_content.js
browser/components/originattributes/test/mochitest/test_permissions_api.html
browser/extensions/formautofill/test/mochitest/formautofill_common.js
browser/extensions/formautofill/test/mochitest/formautofill_parent_utils.js
browser/extensions/formautofill/test/mochitest/test_address_level_1_submission.html
chrome/nsChromeRegistry.cpp
devtools/client/inspector/extensions/test/browser_inspector_extension_sidebar.js
devtools/client/shared/test/shared-head.js
devtools/shared/webconsole/test/test_console_serviceworker_cached.html
docshell/base/BrowsingContext.cpp
docshell/base/BrowsingContext.h
docshell/test/chrome/bug293235_window.xul
docshell/test/chrome/bug396649_window.xul
docshell/test/chrome/bug89419_window.xul
docshell/test/mochitest/test_triggeringprincipal_location_seturi.html
docshell/test/navigation/NavigationUtils.js
docshell/test/navigation/test_bug270414.html
docshell/test/navigation/test_child.html
docshell/test/navigation/test_grandchild.html
docshell/test/navigation/test_opener.html
docshell/test/navigation/test_popup-navigates-children.html
docshell/test/navigation/test_sibling-matching-parent.html
docshell/test/unit/test_browsing_context_structured_clone.js
docshell/test/unit/xpcshell.ini
dom/base/StructuredCloneHolder.cpp
dom/base/StructuredCloneTags.h
dom/base/WindowNamedPropertiesHandler.cpp
dom/base/moz.build
dom/base/nsDOMWindowList.cpp
dom/base/nsDOMWindowList.h
dom/base/nsGlobalWindowInner.cpp
dom/base/nsGlobalWindowInner.h
dom/base/nsGlobalWindowOuter.cpp
dom/base/nsGlobalWindowOuter.h
dom/base/nsPIDOMWindow.h
dom/base/nsWindowMemoryReporter.cpp
dom/base/test/chrome/cpows_parent.xul
dom/base/test/chrome/test_bug914381.html
dom/base/test/chrome/test_cpows.xul
dom/base/test/chrome/window_nsITextInputProcessor.xul
dom/base/test/plugin.js
dom/bindings/test/test_exception_options_from_jsimplemented.html
dom/bindings/test/test_promise_rejections_from_jsimplemented.html
dom/canvas/test/test_hitregion_event.html
dom/chrome-webidl/BrowsingContext.webidl
dom/events/test/test_legacy_non-primary_click.html
dom/html/test/file_fullscreen-denied.html
dom/html/test/test_bug1146116.html
dom/html/test/test_bug1414077.html
dom/ipc/ContentChild.cpp
dom/ipc/JSWindowActor.cpp
dom/ipc/JSWindowActor.h
dom/ipc/JSWindowActorChild.cpp
dom/ipc/JSWindowActorChild.h
dom/ipc/JSWindowActorParent.cpp
dom/ipc/JSWindowActorParent.h
dom/media/test/test_eme_request_notifications.html
dom/media/test/test_info_leak.html
dom/notification/test/mochitest/NotificationTest.js
dom/notification/test/mochitest/test_notification_basics.html
dom/notification/test/mochitest/test_notification_insecure_context.html
dom/permission/tests/test_permissions_api.html
dom/plugins/test/mochitest/head.js
dom/plugins/test/mochitest/plugin-utils.js
dom/plugins/test/mochitest/test_bug1165981.html
dom/plugins/test/mochitest/test_hangui.xul
dom/presentation/tests/mochitest/test_presentation_availability_iframe.html
dom/push/test/test_utils.js
dom/security/test/mixedcontentblocker/test_frameNavigation.html
dom/serviceworkers/test/test_scopes.html
dom/tests/mochitest/bugs/test_navigator_buildID.html
dom/tests/mochitest/bugs/test_no_find_showDialog.html
dom/tests/mochitest/chrome/file_DOM_element_instanceof.xul
dom/tests/mochitest/dom-level0/test_background_loading_iframes.html
dom/tests/mochitest/gamepad/mock_gamepad.js
dom/tests/mochitest/gamepad/test_gamepad_extensions_iframe.html
dom/tests/mochitest/gamepad/test_gamepad_multitouch_crossorigin_iframe.html
editor/libeditor/tests/test_bug569988.html
editor/libeditor/tests/test_selection_move_commands.html
editor/spellchecker/tests/test_bug1200533.html
editor/spellchecker/tests/test_bug1204147.html
editor/spellchecker/tests/test_bug1205983.html
editor/spellchecker/tests/test_bug1209414.html
editor/spellchecker/tests/test_bug678842.html
editor/spellchecker/tests/test_bug697981.html
editor/spellchecker/tests/test_bug717433.html
gfx/layers/apz/test/mochitest/apz_test_utils.js
gfx/layers/apz/test/mochitest/helper_touch_action_regions.html
gfx/tests/mochitest/mochitest.ini
gfx/tests/mochitest/test_font_whitelist.html
js/xpconnect/tests/chrome/test_bug1124898.html
js/xpconnect/tests/chrome/test_bug596580.xul
js/xpconnect/tests/chrome/test_bug732665.xul
js/xpconnect/tests/chrome/test_cows.xul
js/xpconnect/tests/chrome/test_windowProxyDeadWrapper.html
js/xpconnect/tests/chrome/test_xrayToJS.xul
js/xpconnect/wrappers/AccessCheck.cpp
js/xpconnect/wrappers/XrayWrapper.cpp
layout/base/tests/chrome/printpreview_bug396024_helper.xul
layout/base/tests/chrome/printpreview_bug482976_helper.xul
layout/base/tests/chrome/printpreview_helper.xul
layout/base/tests/test_bug394057.html
layout/tools/reftest/reftest-content.js
netwerk/test/mochitests/test_rel_preconnect.html
netwerk/test/mochitests/test_user_agent_updates.html
security/manager/ssl/tests/mochitest/stricttransportsecurity/test_sts_privatebrowsing_perwindowpb.html
testing/mochitest/browser-test.js
testing/mochitest/mochitest_options.py
testing/mochitest/moz.build
testing/mochitest/nested_setup.js
testing/mochitest/runtests.py
testing/mochitest/server.js
testing/mochitest/tests/Harness_sanity/file_spawn.html
testing/mochitest/tests/Harness_sanity/mochitest.ini
testing/mochitest/tests/Harness_sanity/test_SpecialPowersExtension.html
testing/mochitest/tests/Harness_sanity/test_SpecialPowersLoadChromeScript.html
testing/mochitest/tests/Harness_sanity/test_SpecialPowersLoadChromeScript_function.html
testing/mochitest/tests/Harness_sanity/test_SpecialPowersPushPermissions.html
testing/mochitest/tests/Harness_sanity/test_SpecialPowersPushPrefEnv.html
testing/mochitest/tests/Harness_sanity/test_SpecialPowersSandbox.html
testing/mochitest/tests/Harness_sanity/test_SpecialPowersSpawn.html
testing/mochitest/tests/Harness_sanity/test_importInMainProcess.html
testing/mochitest/tests/SimpleTest/ChromePowers.js
testing/mochitest/tests/SimpleTest/MozillaLogger.js
testing/mochitest/tests/SimpleTest/SimpleTest.js
testing/mochitest/tests/SimpleTest/TestRunner.js
testing/mochitest/tests/SimpleTest/moz.build
testing/mochitest/tests/SimpleTest/setup.js
testing/specialpowers/api.js
testing/specialpowers/content/MozillaLogger.js
testing/specialpowers/content/SpecialPowersAPI.jsm
testing/specialpowers/content/SpecialPowersAPIParent.jsm
testing/specialpowers/content/SpecialPowersChild.jsm
testing/specialpowers/content/SpecialPowersObserver.jsm
testing/specialpowers/content/SpecialPowersObserverAPI.js
testing/specialpowers/content/SpecialPowersParent.jsm
testing/specialpowers/content/SpecialPowersSandbox.jsm
testing/specialpowers/content/WrapPrivileged.jsm
testing/specialpowers/content/specialpowers.js
testing/specialpowers/content/specialpowersAPI.js
testing/specialpowers/content/specialpowersFrameScript.js
testing/specialpowers/moz.build
toolkit/components/alerts/test/test_alerts_noobserve.html
toolkit/components/alerts/test/test_principal.html
toolkit/components/extensions/MessageChannel.jsm
toolkit/components/extensions/test/mochitest/test_ext_storage_cleanup.html
toolkit/components/normandy/test/browser/browser_actions_AddonStudyAction.js
toolkit/components/normandy/test/browser/browser_actions_BranchedAddonStudyAction.js
toolkit/components/passwordmgr/test/mochitest/pwmgr_common.js
toolkit/components/passwordmgr/test/mochitest/pwmgr_common_parent.js
toolkit/components/passwordmgr/test/mochitest/test_autocomplete_https_upgrade.html
toolkit/components/passwordmgr/test/mochitest/test_autocomplete_new_password.html
toolkit/components/passwordmgr/test/mochitest/test_autocomplete_password_open.html
toolkit/components/passwordmgr/test/mochitest/test_autofill_different_formActionOrigin.html
toolkit/components/passwordmgr/test/mochitest/test_autofill_different_subdomain.html
toolkit/components/passwordmgr/test/mochitest/test_autofill_https_downgrade.html
toolkit/components/passwordmgr/test/mochitest/test_autofill_https_upgrade.html
toolkit/components/passwordmgr/test/mochitest/test_autofill_password-only.html
toolkit/components/passwordmgr/test/mochitest/test_basic_form_2pw_2.html
toolkit/components/passwordmgr/test/mochitest/test_basic_form_autocomplete.html
toolkit/components/passwordmgr/test/mochitest/test_basic_form_autocomplete_subdomain.html
toolkit/components/passwordmgr/test/mochitest/test_insecure_form_field_autocomplete.html
toolkit/components/passwordmgr/test/mochitest/test_master_password.html
toolkit/components/passwordmgr/test/mochitest/test_password_field_autocomplete.html
toolkit/components/passwordmgr/test/mochitest/test_prompt_async.html
toolkit/components/passwordmgr/test/mochitest/test_xhr.html
toolkit/components/perfmonitoring/PerformanceUtils.cpp
toolkit/components/prompts/test/prompt_common.js
toolkit/components/url-classifier/tests/mochitest/allowlistAnnotatedFrame.html
toolkit/components/url-classifier/tests/mochitest/classifierHelper.js
toolkit/components/url-classifier/tests/mochitest/test_allowlisted_annotations.html
toolkit/components/url-classifier/tests/mochitest/test_bug1254766.html
toolkit/components/url-classifier/tests/mochitest/test_cachemiss.html
toolkit/components/url-classifier/tests/mochitest/test_classify_ping.html
toolkit/components/url-classifier/tests/mochitest/test_cryptomining.html
toolkit/components/url-classifier/tests/mochitest/test_fingerprinting.html
toolkit/components/url-classifier/tests/mochitest/test_socialtracking.html
toolkit/content/tests/browser/head.js
toolkit/content/tests/chrome/test_preferences_onsyncfrompreference.xul
toolkit/mozapps/extensions/test/browser/browser.ini
widget/tests/window_composition_text_querycontent.xul
--- a/browser/base/content/test/general/head.js
+++ b/browser/base/content/test/general/head.js
@@ -186,25 +186,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");
--- 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
@@ -219,121 +219,80 @@ function checkMenuEntries(expectedValues
     : 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"
+    "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),
   };
@@ -392,25 +351,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() {
--- a/browser/extensions/formautofill/test/mochitest/formautofill_parent_utils.js
+++ b/browser/extensions/formautofill/test/mochitest/formautofill_parent_utils.js
@@ -19,16 +19,18 @@ let { formAutofillStorage } = ChromeUtil
   "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",
@@ -53,35 +55,39 @@ 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;
         }
@@ -116,34 +122,32 @@ var ParentUtils = {
             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"
     );
@@ -153,17 +157,16 @@ var ParentUtils = {
     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"
     );
@@ -220,102 +223,85 @@ 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
@@ -326,17 +326,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}`
--- a/devtools/client/shared/test/shared-head.js
+++ b/devtools/client/shared/test/shared-head.js
@@ -478,16 +478,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.
  */
@@ -624,20 +626,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
@@ -51,20 +51,22 @@ 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,71 @@
+"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/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:
@@ -914,18 +913,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();
@@ -1095,17 +1094,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/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
@@ -10,27 +10,27 @@ 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(
+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 = {};
@@ -74,32 +74,39 @@ 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(
--- 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
@@ -115,17 +115,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
@@ -40,18 +40,21 @@ function setTestPluginEnabledState(newEn
     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;
 
   // First, clear the crash record store.
--- 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
@@ -60,28 +60,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,20 +1,19 @@
 /* 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/layers/apz/test/mochitest/apz_test_utils.js
+++ b/gfx/layers/apz/test/mochitest/apz_test_utils.js
@@ -508,17 +508,17 @@ async function waitUntilApzStable() {
     }
 
     // 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");
@@ -689,19 +689,17 @@ function getSnapshot(rect) {
     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/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
@@ -1644,26 +1644,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
@@ -527,26 +527,29 @@ 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!");
-
     }
 }
 
 function WaitForTestEnd(contentRootElement, inPrintMode, spellCheckedElements) {
     var stopAfterPaintReceived = false;
     var currentDoc = content.document;
     var state = STATE_WAITING_TO_FIRE_INVALIDATE_EVENT;
 
--- 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/testing/mochitest/browser-test.js
+++ b/testing/mochitest/browser-test.js
@@ -454,24 +454,16 @@ function Tester(aTests, structuredLogger
   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(
--- 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,31 +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
@@ -311,17 +311,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");
@@ -722,49 +721,16 @@ function convertManifestToTestLinks(root
       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(
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,137 +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";
 }
 
-MozillaLogger.prototype = {
-  init(path) {},
+function importJSM(jsm) {
+  if (typeof ChromeUtils === "object") {
+    return ChromeUtils.import(jsm);
+  }
+  /* globals SpecialPowers */
+  let obj = {};
+  SpecialPowers.Cu.import(jsm, obj);
+  return SpecialPowers.wrap(obj);
+}
 
-  getLogCallback() {
-    return function(msg) {
-      var data = formatLogMessage(msg);
-      dump(data);
+let CC = (typeof Components === "object"
+            ? Components
+            : SpecialPowers.wrap(SpecialPowers.Components)).Constructor;
+
+let ConverterOutputStream = CC("@mozilla.org/intl/converter-output-stream;1",
+                               "nsIConverterOutputStream", "init");
+
+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);
+  close() {}
 }
 
-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();
-  },
-};
 
 /**
  * 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();
+
+    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));
 
-MozillaFileLogger.prototype = {
-  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
@@ -3,16 +3,19 @@
  * 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"
 );
 
@@ -20,21 +23,32 @@ this.specialpowers = class extends Exten
   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,33 +2,20 @@
  * 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",
@@ -36,16 +23,26 @@ ChromeUtils.defineModuleGetter(
 );
 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"
 );
@@ -60,336 +57,20 @@ ChromeUtils.defineModuleGetter(
   "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;
 }
 
@@ -455,48 +136,63 @@ SPConsoleListener.prototype = {
   },
 
   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();
 
-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];
-    }
-  }
-  return wrapper;
-}
+    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;
 
-function setWrapped(obj, prop, val) {
-  if (!isWrapper(obj)) {
-    throw new Error(
-      "You only need to use this for SpecialPowers wrapped objects"
-    );
+    this._nextExtensionID = 0;
+    this._extensionListeners = null;
   }
 
-  obj = unwrapPrivileged(obj);
-  return Reflect.set(obj, prop, val);
-}
+  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;
 
-SpecialPowersAPI.prototype = {
+          if (SimpleTest) {
+            SimpleTest.record(
+              passed,
+              name,
+              diag,
+              stack && stack.formattedStack
+            );
+          } else {
+            // Well, this is unexpected.
+            dump(name + "\n");
+          }
+        }
+        break;
+    }
+    return undefined;
+  }
+
   /*
    * Privileged object wrapping API
    *
    * Usage:
    *   var wrapper = SpecialPowers.wrap(obj);
    *   wrapper.privilegedMethod(); wrapper.privilegedProperty;
    *   obj === SpecialPowers.unwrap(wrapper);
    *
@@ -515,106 +211,175 @@ 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.toString() + ")();",
         name: urlOrFunction.name,
       };
     } else {
+      // Note: We need to do this in the child since, even though
+      // `_readUrlAsString` pretends to be synchronous, its channel
+      // winds up spinning the event loop when loading HTTP URLs. That
+      // leads to unexpected out-of-order operations if the child sends
+      // a message immediately after loading the script.
+      scriptArgs.function = {
+        body: this._readUrlAsString(urlOrFunction),
+      };
       scriptArgs.url = urlOrFunction;
     }
-    this._sendSyncMessage("SPLoadChromeScript", scriptArgs);
+    this.sendAsyncMessage("SPLoadChromeScript", scriptArgs);
 
     // Returns a MessageManager like API in order to be
     // able to communicate with this chrome script
     let listeners = [];
     let chromeScript = {
       addMessageListener: (name, listener) => {
         listeners.push({ name, listener });
       },
@@ -629,326 +394,247 @@ SpecialPowersAPI.prototype = {
 
       removeMessageListener: (name, listener) => {
         listeners = listeners.filter(
           o => o.name != name || o.listener != listener
         );
       },
 
       sendAsyncMessage: (name, message) => {
-        this._sendSyncMessage("SPChromeScriptMessage", { id, name, message });
+        this.sendAsyncMessage("SPChromeScriptMessage", { id, name, message });
       },
 
-      sendSyncMessage: (name, message) => {
-        return this._sendSyncMessage("SPChromeScriptMessage", {
-          id,
-          name,
-          message,
-        });
+      sendQuery: (name, message) => {
+        return this.sendQuery("SPChromeScriptMessage", { id, name, message });
       },
 
       destroy: () => {
         listeners = [];
         this._removeMessageListener("SPChromeScriptMessage", chromeScript);
-        this._removeMessageListener("SPChromeScriptAssert", chromeScript);
       },
 
       receiveMessage: aMessage => {
         let messageId = aMessage.json.id;
         let name = aMessage.json.name;
         let message = aMessage.json.message;
-        if (this.mm) {
+        if (this.contentWindow) {
           message = new StructuredCloneHolder(message).deserialize(
-            this.mm.content
+            this.contentWindow
           );
         }
         // Ignore message from other chrome script
         if (messageId != id) {
           return null;
         }
 
+        let result;
         if (aMessage.name == "SPChromeScriptMessage") {
-          listeners
-            .filter(o => o.name == name)
-            .forEach(o => o.listener(message));
-        } else if (aMessage.name == "SPChromeScriptAssert") {
-          assert(aMessage.json);
+          for (let listener of listeners.filter(o => o.name == name)) {
+            result = listener.listener(message);
+          }
         }
-        return null;
+        return result;
       },
     };
     this._addMessageListener("SPChromeScriptMessage", chromeScript);
-    this._addMessageListener("SPChromeScriptAssert", chromeScript);
-
-    let assert = json => {
-      // An assertion has been done in a mochitest chrome script
-      let { name, err, message, stack } = json;
-
-      // Try to fetch a test runner from the mochitest
-      // in order to properly log these assertions and notify
-      // all usefull log observers
-      let window = this.window.get();
-      let parentRunner,
-        repr = o => o;
-      if (window) {
-        window = window.wrappedJSObject;
-        parentRunner = window.TestRunner;
-        if (window.repr) {
-          repr = window.repr;
-        }
-      }
-
-      // Craft a mochitest-like report string
-      var resultString = err ? "TEST-UNEXPECTED-FAIL" : "TEST-PASS";
-      var diagnostic = message
-        ? message
-        : "assertion @ " + stack.filename + ":" + stack.lineNumber;
-      if (err) {
-        diagnostic +=
-          " - got " +
-          repr(err.actual) +
-          ", expected " +
-          repr(err.expected) +
-          " (operator " +
-          err.operator +
-          ")";
-      }
-      var msg = [resultString, name, diagnostic].join(" | ");
-      if (parentRunner) {
-        if (err) {
-          parentRunner.addFailedTest(name);
-          parentRunner.error(msg);
-        } else {
-          parentRunner.log(msg);
-        }
-      } else {
-        // When we are running only a single mochitest, there is no test runner
-        dump(msg + "\n");
-      }
-    };
 
     return this.wrap(chromeScript);
-  },
+  }
 
-  importInMainProcess(importString) {
-    var message = this._sendSyncMessage(
-      "SPImportInMainProcess",
-      importString
-    )[0];
+  async importInMainProcess(importString) {
+    var message = await this.sendQuery("SPImportInMainProcess", importString);
     if (message.hadError) {
       throw new Error(
         "SpecialPowers.importInMainProcess failed with error " +
           message.errorMessage
       );
     }
-  },
+  }
 
   get Services() {
-    return wrapPrivileged(Services);
-  },
+    return WrapPrivileged.wrap(Services);
+  }
 
   /*
    * A getter for the privileged Components object we have.
    */
   getFullComponents() {
     return Components;
-  },
+  }
 
   /*
    * Convenient shortcuts to the standard Components abbreviations.
    */
   get Cc() {
-    return wrapPrivileged(this.getFullComponents().classes);
-  },
+    return WrapPrivileged.wrap(this.getFullComponents().classes);
+  }
   get Ci() {
-    return wrapPrivileged(this.getFullComponents().interfaces);
-  },
+    return WrapPrivileged.wrap(this.getFullComponents().interfaces);
+  }
   get Cu() {
-    return wrapPrivileged(this.getFullComponents().utils);
-  },
+    return WrapPrivileged.wrap(this.getFullComponents().utils);
+  }
   get Cr() {
-    return wrapPrivileged(this.getFullComponents().results);
-  },
+    return WrapPrivileged.wrap(this.getFullComponents().results);
+  }
 
   getDOMWindowUtils(aWindow) {
     if (aWindow == this.contentWindow && this.DOMWindowUtils != null) {
       return this.DOMWindowUtils;
     }
 
     return bindDOMWindowUtils(aWindow);
-  },
+  }
 
   /*
    * A method to get a DOMParser that can't parse XUL.
    */
   getNoXULDOMParser() {
     // If we create it with a system subject principal (so it gets a
     // nullprincipal), it won't be able to parse XUL by default.
-    return wrapPrivileged(new DOMParser());
-  },
+    return WrapPrivileged.wrap(new DOMParser());
+  }
 
   get InspectorUtils() {
-    return wrapPrivileged(InspectorUtils);
-  },
+    return WrapPrivileged.wrap(InspectorUtils);
+  }
 
   get PromiseDebugging() {
-    return wrapPrivileged(PromiseDebugging);
-  },
+    return WrapPrivileged.wrap(PromiseDebugging);
+  }
 
-  waitForCrashes(aExpectingProcessCrash) {
-    return new Promise((resolve, reject) => {
-      if (!aExpectingProcessCrash) {
-        resolve();
-      }
-
-      var crashIds = this._encounteredCrashDumpFiles
-        .filter(filename => {
-          return filename.length === 40 && filename.endsWith(".dmp");
-        })
-        .map(id => {
-          return id.slice(0, -4); // Strip the .dmp extension to get the ID
-        });
+  async waitForCrashes(aExpectingProcessCrash) {
+    if (!aExpectingProcessCrash) {
+      return;
+    }
 
-      let self = this;
-      function messageListener(msg) {
-        self._removeMessageListener(
-          "SPProcessCrashManagerWait",
-          messageListener
-        );
-        resolve();
-      }
+    var crashIds = this._encounteredCrashDumpFiles
+      .filter(filename => {
+        return filename.length === 40 && filename.endsWith(".dmp");
+      })
+      .map(id => {
+        return id.slice(0, -4); // Strip the .dmp extension to get the ID
+      });
 
-      this._addMessageListener("SPProcessCrashManagerWait", messageListener);
-      this._sendAsyncMessage("SPProcessCrashManagerWait", {
-        crashIds,
-      });
+    await this.sendQuery("SPProcessCrashManagerWait", {
+      crashIds,
     });
-  },
+  }
 
-  removeExpectedCrashDumpFiles(aExpectingProcessCrash) {
+  async removeExpectedCrashDumpFiles(aExpectingProcessCrash) {
     var success = true;
     if (aExpectingProcessCrash) {
       var message = {
         op: "delete-crash-dump-files",
         filenames: this._encounteredCrashDumpFiles,
       };
-      if (!this._sendSyncMessage("SPProcessCrashService", message)[0]) {
+      if (!(await this.sendQuery("SPProcessCrashService", message))) {
         success = false;
       }
     }
     this._encounteredCrashDumpFiles.length = 0;
     return success;
-  },
+  }
 
-  findUnexpectedCrashDumpFiles() {
+  async findUnexpectedCrashDumpFiles() {
     var self = this;
     var message = {
       op: "find-crash-dump-files",
       crashDumpFilesToIgnore: this._unexpectedCrashDumpFiles,
     };
-    var crashDumpFiles = this._sendSyncMessage(
-      "SPProcessCrashService",
-      message
-    )[0];
+    var crashDumpFiles = await this.sendQuery("SPProcessCrashService", message);
     crashDumpFiles.forEach(function(aFilename) {
       self._unexpectedCrashDumpFiles[aFilename] = true;
     });
     return crashDumpFiles;
-  },
+  }
 
   removePendingCrashDumpFiles() {
     var message = {
       op: "delete-pending-crash-dump-files",
     };
-    var removed = this._sendSyncMessage("SPProcessCrashService", message)[0];
-    return removed;
-  },
+    return this.sendQuery("SPProcessCrashService", message);
+  }
 
   _setTimeout(callback) {
     // for mochitest-browser
     if (typeof this.chromeWindow != "undefined") {
       this.chromeWindow.setTimeout(callback, 0);
     }
     // for mochitest-plain
     else {
       this.contentWindow.setTimeout(callback, 0);
     }
-  },
+  }
 
   promiseTimeout(delay) {
     return new Promise(resolve => {
       this._setTimeout(resolve, delay);
     });
-  },
+  }
 
   _delayCallbackTwice(callback) {
     let delayedCallback = () => {
       let delayAgain = aCallback => {
         // Using this._setTimeout doesn't work here
         // It causes failures in mochtests that use
         // multiple pushPrefEnv calls
         // For chrome/browser-chrome mochitests
-        if (typeof window != "undefined") {
-          setTimeout(aCallback, 0);
-        }
-        // For mochitest-plain
-        else {
-          this.mm.content.setTimeout(aCallback, 0);
-        }
+        this._setTimeout(aCallback);
       };
       delayAgain(delayAgain.bind(this, callback));
     };
     return delayedCallback;
-  },
+  }
 
   /* apply permissions to the system and when the test case is finished (SimpleTest.finish())
      we will revert the permission back to the original.
 
      inPermissions is an array of objects where each object has a type, action, context, ex:
      [{'type': 'SystemXHR', 'allow': 1, 'context': document},
       {'type': 'SystemXHR', 'allow': Ci.nsIPermissionManager.PROMPT_ACTION, 'context': document}]
 
      Allow can be a boolean value of true/false or ALLOW_ACTION/DENY_ACTION/PROMPT_ACTION/UNKNOWN_ACTION
   */
-  pushPermissions(inPermissions, callback) {
+  async pushPermissions(inPermissions, callback) {
     inPermissions = Cu.waiveXrays(inPermissions);
     var pendingPermissions = [];
     var cleanupPermissions = [];
 
     for (var p in inPermissions) {
       var permission = inPermissions[p];
       var originalValue = Ci.nsIPermissionManager.UNKNOWN_ACTION;
       var context = Cu.unwaiveXrays(permission.context); // Sometimes |context| is a DOM object on which we expect
       // to be able to access .nodePrincipal, so we need to unwaive.
       if (
-        this.testPermission(
+        await this.testPermission(
           permission.type,
           Ci.nsIPermissionManager.ALLOW_ACTION,
           context
         )
       ) {
         originalValue = Ci.nsIPermissionManager.ALLOW_ACTION;
       } else if (
-        this.testPermission(
+        await this.testPermission(
           permission.type,
           Ci.nsIPermissionManager.DENY_ACTION,
           context
         )
       ) {
         originalValue = Ci.nsIPermissionManager.DENY_ACTION;
       } else if (
-        this.testPermission(
+        await this.testPermission(
           permission.type,
           Ci.nsIPermissionManager.PROMPT_ACTION,
           context
         )
       ) {
         originalValue = Ci.nsIPermissionManager.PROMPT_ACTION;
       } else if (
-        this.testPermission(
+        await this.testPermission(
           permission.type,
           Ci.nsICookiePermission.ACCESS_SESSION,
           context
         )
       ) {
         originalValue = Ci.nsICookiePermission.ACCESS_SESSION;
       }
 
@@ -1037,17 +723,17 @@ SpecialPowersAPI.prototype = {
       this._pendingPermissions.push([
         pendingPermissions,
         this._delayCallbackTwice(callback),
       ]);
       this._applyPermissions();
     } else {
       this._setTimeout(callback);
     }
-  },
+  }
 
   /*
    * This function should be used when specialpowers is in content process but
    * it want to get the notification from chrome space.
    *
    * This function will call Services.obs.addObserver in SpecialPowersObserver
    * (that is in chrome process) and forward the data received to SpecialPowers
    * via messageManager.
@@ -1061,118 +747,64 @@ SpecialPowersAPI.prototype = {
    * NOTICE: there is no implementation of _addMessageListener in
    * ChromePowers.js
    */
   registerObservers(topic) {
     var msg = {
       op: "add",
       observerTopic: topic,
     };
-    this._sendSyncMessage("SPObserverService", msg);
-  },
+    return this.sendQuery("SPObserverService", msg);
+  }
 
   permChangedProxy(aMessage) {
     let permission = aMessage.json.permission;
     let aData = aMessage.json.aData;
     this._permissionObserver.observe(permission, aData);
-  },
-
-  permissionObserverProxy: {
-    // 'this' in permChangedObserverProxy is the permChangedObserverProxy
-    // object itself. The '_specialPowersAPI' will be set to the 'SpecialPowersAPI'
-    // object to call the member function in SpecialPowersAPI.
-    _specialPowersAPI: null,
-    observe(aSubject, aTopic, aData) {
-      if (aTopic == "perm-changed") {
-        var permission = aSubject.QueryInterface(Ci.nsIPermission);
-        this._specialPowersAPI._permissionObserver.observe(permission, aData);
-      }
-    },
-  },
+  }
 
   popPermissions(callback) {
-    if (this._permissionsUndoStack.length > 0) {
-      // See pushPermissions comment regarding delay.
-      let cb = callback ? this._delayCallbackTwice(callback) : null;
-      /* Each pop from the stack will yield an object {op/type/permission/value/url/appid/isInIsolatedMozBrowserElement} or null */
-      this._pendingPermissions.push([this._permissionsUndoStack.pop(), cb]);
-      this._applyPermissions();
-    } else {
-      if (this._observingPermissions) {
-        this._observingPermissions = false;
-        this._removeMessageListener(
-          "specialpowers-perm-changed",
-          this.permChangedProxy.bind(this)
-        );
+    let promise = new Promise(resolve => {
+      if (this._permissionsUndoStack.length > 0) {