Backed out 34 changesets (bug 1561150, bug 1541557, bug 1561724, bug 1561999, bug 1558298, bug 1561061, bug 1532795, bug 1560400, bug 1561122) for beta simulation failures (bug 1563905, bug 1564001). a=backout
☠☠ backed out by e596664275d5 ☠ ☠
authorSebastian Hengst <archaeopteryx@coole-files.de>
Mon, 08 Jul 2019 10:37:28 +0200
changeset 481652 7e6657f88b7694ecd841088963ff71d767e4ec22
parent 481585 483d687212fbf08e9b103c9b8565674b02fed827
child 481653 09d43bf72fa67c0ead7e60ace2b2f0bac5696c0f
push id89346
push usercsabou@mozilla.com
push dateMon, 08 Jul 2019 15:17:04 +0000
treeherderautoland@addfd7491603 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbackout
bugs1561150, 1541557, 1561724, 1561999, 1558298, 1561061, 1532795, 1560400, 1561122, 1563905, 1564001
milestone69.0a1
backs out210d6d52e8b0677448cb8e84befeb637ca87eb29
3115db154c45ee1b5b2187d25772d9ddfbebe0c0
b42748878b6e8cd676ab9e0f9fc963b89423cf24
266160ca8e9d27918623f096cde1a6f998e8900c
00e935828f418b46a391c3b47bb53163ce91247a
4aaf4882780dd12f420fe4f9d0a36d9f52ce6856
6644e38a669212b4e933cdb0a5f3badf6cba0b5f
72cd895b16137490bbc29d8e6f19f12551fc15f0
f0bac27bad8a6541611fb0cf59f666b45c574cc2
95da39224eab4e9aa1352c2d1d9288f458a89ab5
3fe4d4942fd20b0e81425e90a57c33238f814844
23e90c6fec2be482ed33cddcd09f5dfe11de2df1
a7f093fbef061b89cd1b9b3c166d76e59256340c
c873f0eb94be3ec8f6592020f76f2d7a1a20c3a6
cf359a8ec753d0eaeeb6f5d20eafaa4436cad2c3
f2c260cae4b51280b18ff67936c5a9ac79aed5eb
054a0b7aa81d2d29d9c46522f2512767bb123909
f808ec45ff9cf6a00847257e44f910a6c4c2b2ef
1025eeef095472d05bfb93499b501b1aff5cc758
fe88b250e4180b8a1898b42b6f3339cd8e6a3ec8
6680278c231bcbbbde62bf9ea974eac5a378c6fc
255735c1ff63336c5ccead020d798436697f8d15
51969e1c9c4473ff8dd4bbed1a32d1b9bbe4fc39
d125259905654822b9b38364a50b9fee03bf968e
ef4ec8f0f886254c5ef20aa0865ff837badda664
45a9599d96417beb8b032e9a95c8b119929dd54b
4ccecdba1c3430a7e605329425020e43b2e9bc39
0e91fc9541c294b8c3133aae81f45d0bcab8155c
edd1cc6badf788f950ee68168aed0a14aa191411
ba24251835fbbd7348e96b7f2956f32811d3577e
ca88016511bb3f7aa335ee6531b17e75c08477a6
c95e6e5998364d70a97651dea785d0ad5c31eff5
9b1a9d80243437eb87d63a10b62d7b25eb2a6664
f859e4de00079fca494cbee9ee339e3c006215bb
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 34 changesets (bug 1561150, bug 1541557, bug 1561724, bug 1561999, bug 1558298, bug 1561061, bug 1532795, bug 1560400, bug 1561122) for beta simulation failures (bug 1563905, bug 1564001). a=backout Backed out changeset 210d6d52e8b0 (bug 1541557) Backed out changeset 3115db154c45 (bug 1561122) Backed out changeset b42748878b6e (bug 1561122) Backed out changeset 266160ca8e9d (bug 1561999) Backed out changeset 00e935828f41 (bug 1561724) Backed out changeset 4aaf4882780d (bug 1561150) Backed out changeset 6644e38a6692 (bug 1561150) Backed out changeset 72cd895b1613 (bug 1561061) Backed out changeset f0bac27bad8a (bug 1560400) Backed out changeset 95da39224eab (bug 1560400) Backed out changeset 3fe4d4942fd2 (bug 1532795) Backed out changeset 23e90c6fec2b (bug 1532795) Backed out changeset a7f093fbef06 (bug 1532795) Backed out changeset c873f0eb94be (bug 1532795) Backed out changeset cf359a8ec753 (bug 1532795) Backed out changeset f2c260cae4b5 (bug 1541557) Backed out changeset 054a0b7aa81d (bug 1541557) Backed out changeset f808ec45ff9c (bug 1541557) Backed out changeset 1025eeef0954 (bug 1541557) Backed out changeset fe88b250e418 (bug 1541557) Backed out changeset 6680278c231b (bug 1541557) Backed out changeset 255735c1ff63 (bug 1541557) Backed out changeset 51969e1c9c44 (bug 1558298) Backed out changeset d12525990565 (bug 1558298) Backed out changeset ef4ec8f0f886 (bug 1558298) Backed out changeset 45a9599d9641 (bug 1558298) Backed out changeset 4ccecdba1c34 (bug 1558298) Backed out changeset 0e91fc9541c2 (bug 1558298) Backed out changeset edd1cc6badf7 (bug 1558298) Backed out changeset ba24251835fb (bug 1558298) Backed out changeset ca88016511bb (bug 1558298) Backed out changeset c95e6e599836 (bug 1558298) Backed out changeset 9b1a9d802434 (bug 1558298) Backed out changeset f859e4de0007 (bug 1558298)
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,21 +186,25 @@ function setTestPluginEnabledState(newEn
   var oldEnabledState = plugin.enabledState;
   plugin.enabledState = newEnabledState;
   SimpleTest.registerCleanupFunction(function() {
     getTestPlugin(pluginName).enabledState = oldEnabledState;
   });
 }
 
 function pushPrefs(...aPrefs) {
-  return SpecialPowers.pushPrefEnv({ set: aPrefs });
+  return new Promise(resolve => {
+    SpecialPowers.pushPrefEnv({ set: aPrefs }, resolve);
+  });
 }
 
 function popPrefs() {
-  return SpecialPowers.popPrefEnv();
+  return new Promise(resolve => {
+    SpecialPowers.popPrefEnv(resolve);
+  });
 }
 
 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,19 +15,18 @@
 "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/SpecialPowersChild.jsm",
-    "resource://specialpowers/SpecialPowersAPI.jsm",
-    "resource://specialpowers/WrapPrivileged.jsm",
+    "resource://specialpowers/specialpowers.js",
+    "resource://specialpowers/specialpowersAPI.js",
 
     "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",
@@ -60,16 +59,18 @@ 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(async () => {
+            promiseGranted.then(() => {
               const promisePrompt = this.promiseStateChanged(permission, "prompt");
-              await SpecialPowers.popPermissions();
+              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,80 +219,121 @@ 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, payload = {}) {
+function invokeAsyncChromeTask(message, response, payload = {}) {
   info(`expecting the chrome task finished: ${message}`);
-  return formFillChromeScript.sendQuery(message, payload);
+  return new Promise(resolve => {
+    formFillChromeScript.sendAsyncMessage(message, payload);
+    formFillChromeScript.addMessageListener(response, function onReceived(
+      data
+    ) {
+      formFillChromeScript.removeMessageListener(response, onReceived);
+
+      resolve(data);
+    });
+  });
 }
 
 async function addAddress(address) {
-  await invokeAsyncChromeTask("FormAutofillTest:AddAddress", { address });
+  await invokeAsyncChromeTask(
+    "FormAutofillTest:AddAddress",
+    "FormAutofillTest:AddressAdded",
+    { address }
+  );
   await sleep();
 }
 
 async function removeAddress(guid) {
-  return invokeAsyncChromeTask("FormAutofillTest:RemoveAddress", { guid });
+  return invokeAsyncChromeTask(
+    "FormAutofillTest:RemoveAddress",
+    "FormAutofillTest:AddressRemoved",
+    { guid }
+  );
 }
 
 async function updateAddress(guid, address) {
-  return invokeAsyncChromeTask("FormAutofillTest:UpdateAddress", {
-    address,
-    guid,
-  });
+  return invokeAsyncChromeTask(
+    "FormAutofillTest:UpdateAddress",
+    "FormAutofillTest:AddressUpdated",
+    { address, guid }
+  );
 }
 
 async function checkAddresses(expectedAddresses) {
-  return invokeAsyncChromeTask("FormAutofillTest:CheckAddresses", {
-    expectedAddresses,
-  });
+  return invokeAsyncChromeTask(
+    "FormAutofillTest:CheckAddresses",
+    "FormAutofillTest:areAddressesMatching",
+    { expectedAddresses }
+  );
 }
 
 async function cleanUpAddresses() {
-  return invokeAsyncChromeTask("FormAutofillTest:CleanUpAddresses");
+  return invokeAsyncChromeTask(
+    "FormAutofillTest:CleanUpAddresses",
+    "FormAutofillTest:AddressesCleanedUp"
+  );
 }
 
 async function addCreditCard(creditcard) {
-  await invokeAsyncChromeTask("FormAutofillTest:AddCreditCard", { creditcard });
+  await invokeAsyncChromeTask(
+    "FormAutofillTest:AddCreditCard",
+    "FormAutofillTest:CreditCardAdded",
+    { creditcard }
+  );
   await sleep();
 }
 
 async function removeCreditCard(guid) {
-  return invokeAsyncChromeTask("FormAutofillTest:RemoveCreditCard", { guid });
+  return invokeAsyncChromeTask(
+    "FormAutofillTest:RemoveCreditCard",
+    "FormAutofillTest:CreditCardRemoved",
+    { guid }
+  );
 }
 
 async function checkCreditCards(expectedCreditCards) {
-  return invokeAsyncChromeTask("FormAutofillTest:CheckCreditCards", {
-    expectedCreditCards,
-  });
+  return invokeAsyncChromeTask(
+    "FormAutofillTest:CheckCreditCards",
+    "FormAutofillTest:areCreditCardsMatching",
+    { expectedCreditCards }
+  );
 }
 
 async function cleanUpCreditCards() {
-  return invokeAsyncChromeTask("FormAutofillTest:CleanUpCreditCards");
+  return invokeAsyncChromeTask(
+    "FormAutofillTest:CleanUpCreditCards",
+    "FormAutofillTest:CreditCardsCleanedUp"
+  );
 }
 
 async function cleanUpStorage() {
   await cleanUpAddresses();
   await cleanUpCreditCards();
 }
 
 async function canTestOSKeyStoreLogin() {
   let { canTest } = await invokeAsyncChromeTask(
-    "FormAutofillTest:CanTestOSKeyStoreLogin"
+    "FormAutofillTest:CanTestOSKeyStoreLogin",
+    "FormAutofillTest:CanTestOSKeyStoreLoginResult"
   );
   return canTest;
 }
 
 async function waitForOSKeyStoreLogin(login = false) {
-  await invokeAsyncChromeTask("FormAutofillTest:OSKeyStoreLogin", { login });
+  await invokeAsyncChromeTask(
+    "FormAutofillTest:OSKeyStoreLogin",
+    "FormAutofillTest:OSKeyStoreLoggedIn",
+    { login }
+  );
 }
 
 function patchRecordCCNumber(record) {
   const number = record["cc-number"];
   const ccNumberFmt = {
     affix: "****",
     label: number.substr(-4),
   };
@@ -351,23 +392,25 @@ 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.sendQuery("setup");
+    await formFillChromeScript.promiseOneMessage("setup-finished");
   });
 
   SimpleTest.registerCleanupFunction(async () => {
+    formFillChromeScript.sendAsyncMessage("cleanup");
     info(`expecting the storage cleanup`);
-    await formFillChromeScript.sendQuery("cleanup");
+    await formFillChromeScript.promiseOneMessage("cleanup-finished");
 
     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,18 +19,16 @@ 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",
@@ -55,39 +53,35 @@ 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.
-        // 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");
-          }
+        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) {
+  async _operateRecord(collectionName, type, msgData, contentMsg) {
     let times, topic;
 
     if (collectionName == ADDRESSES_COLLECTION_NAME) {
       switch (type) {
         case "add": {
           Services.cpmm.sendAsyncMessage("FormAutofill:SaveAddress", msgData);
           break;
         }
@@ -122,32 +116,34 @@ var ParentUtils = {
             msgData
           );
           break;
         }
       }
     }
 
     await this._storageChangeObserved({ type, times, topic });
+    sendAsyncMessage(contentMsg);
   },
 
-  async operateAddress(type, msgData) {
+  async operateAddress(type, msgData, contentMsg) {
     await this._operateRecord(ADDRESSES_COLLECTION_NAME, ...arguments);
   },
 
-  async operateCreditCard(type, msgData) {
+  async operateCreditCard(type, msgData, contentMsg) {
     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"
     );
@@ -157,16 +153,17 @@ 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"
     );
@@ -223,85 +220,102 @@ var ParentUtils = {
         return false;
       }
     }
 
     return true;
   },
 
   async checkAddresses({ expectedAddresses }) {
-    return this._checkRecords(ADDRESSES_COLLECTION_NAME, expectedAddresses);
+    const areMatched = await this._checkRecords(
+      ADDRESSES_COLLECTION_NAME,
+      expectedAddresses
+    );
+
+    sendAsyncMessage("FormAutofillTest:areAddressesMatching", areMatched);
   },
 
   async checkCreditCards({ expectedCreditCards }) {
-    return this._checkRecords(CREDITCARDS_COLLECTION_NAME, expectedCreditCards);
+    const areMatched = await this._checkRecords(
+      CREDITCARDS_COLLECTION_NAME,
+      expectedCreditCards
+    );
+
+    sendAsyncMessage("FormAutofillTest:areCreditCardsMatching", areMatched);
   },
 
   observe(subject, topic, data) {
-    if (!destroyed) {
-      assert.ok(topic === "formautofill-storage-changed");
-    }
+    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", () => {
-  return null;
+  sendAsyncMessage("FormAutofillTest:FieldsIdentified");
 });
 
 addMessageListener("FormAutofillTest:AddAddress", msg => {
-  return ParentUtils.operateAddress("add", msg);
+  ParentUtils.operateAddress("add", msg, "FormAutofillTest:AddressAdded");
 });
 
 addMessageListener("FormAutofillTest:RemoveAddress", msg => {
-  return ParentUtils.operateAddress("remove", msg);
+  ParentUtils.operateAddress("remove", msg, "FormAutofillTest:AddressRemoved");
 });
 
 addMessageListener("FormAutofillTest:UpdateAddress", msg => {
-  return ParentUtils.operateAddress("update", msg);
+  ParentUtils.operateAddress("update", msg, "FormAutofillTest:AddressUpdated");
 });
 
 addMessageListener("FormAutofillTest:CheckAddresses", msg => {
-  return ParentUtils.checkAddresses(msg);
+  ParentUtils.checkAddresses(msg);
 });
 
 addMessageListener("FormAutofillTest:CleanUpAddresses", msg => {
-  return ParentUtils.cleanUpAddresses();
+  ParentUtils.cleanUpAddresses();
 });
 
 addMessageListener("FormAutofillTest:AddCreditCard", msg => {
-  return ParentUtils.operateCreditCard("add", msg);
+  ParentUtils.operateCreditCard("add", msg, "FormAutofillTest:CreditCardAdded");
 });
 
 addMessageListener("FormAutofillTest:RemoveCreditCard", msg => {
-  return ParentUtils.operateCreditCard("remove", msg);
+  ParentUtils.operateCreditCard(
+    "remove",
+    msg,
+    "FormAutofillTest:CreditCardRemoved"
+  );
 });
 
 addMessageListener("FormAutofillTest:CheckCreditCards", msg => {
-  return ParentUtils.checkCreditCards(msg);
+  ParentUtils.checkCreditCards(msg);
 });
 
 addMessageListener("FormAutofillTest:CleanUpCreditCards", msg => {
-  return ParentUtils.cleanUpCreditCards();
+  ParentUtils.cleanUpCreditCards();
 });
 
 addMessageListener("FormAutofillTest:CanTestOSKeyStoreLogin", msg => {
-  return { canTest: OSKeyStoreTestUtils.canTestOSKeyStoreLogin() };
+  sendAsyncMessage("FormAutofillTest:CanTestOSKeyStoreLoginResult", {
+    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 = await chromeScript.sendQuery("CheckSubKeys");
-  ok(result, "Check that there are no sub_keys for the test country");
+  let result = chromeScript.sendSyncMessage("CheckSubKeys");
+  ok(result[0][0], "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,16 +11,17 @@
 #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,18 +478,16 @@ 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.
  */
@@ -626,18 +624,20 @@ 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) {
-  const options = { set: [[preferenceName, value]] };
-  return SpecialPowers.pushPrefEnv(options);
+  return new Promise(resolve => {
+    const options = { set: [[preferenceName, value]] };
+    SpecialPowers.pushPrefEnv(options, resolve);
+  });
 }
 
 /**
  * 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);
+let {gBrowser} = SpecialPowers._getTopChromeWindow(SpecialPowers.window.get());
 
 SimpleTest.registerCleanupFunction(() => {
   while (gBrowser.tabs.length > 1) {
     gBrowser.removeCurrentTab();
   }
 });
 
 function addTab(url) {
--- a/docshell/base/BrowsingContext.cpp
+++ b/docshell/base/BrowsingContext.cpp
@@ -11,17 +11,16 @@
 #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"
@@ -86,22 +85,16 @@ 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) {
@@ -553,55 +546,16 @@ 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,17 +46,16 @@ 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();
@@ -100,23 +99,16 @@ 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();
@@ -269,22 +261,16 @@ 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,23 +3,25 @@
 
 <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[
-    var {NetUtil} = ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
+    const {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,16 +3,18 @@
 
 <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,17 +4,18 @@
 <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/ChromePowers.js"/>
+  <script src="chrome://mochikit/content/tests/SimpleTest/SpecialPowersObserverAPI.js"/>
+  <script src="chrome://mochikit/content/tests/SimpleTest/specialpowers.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,30 +39,28 @@ 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;
 }
 
-async function checkNavFrame1() {
+function checkNavFrame1() {
   myFrame1.removeEventListener("load", checkNavFrame1);
-  is(await SpecialPowers.spawn(myFrame1, [], () => this.content.location.href),
-     CROSS_ORIGIN_URI,
+  is(SpecialPowers.wrap(myFrame1.contentWindow).location.href, CROSS_ORIGIN_URI,
     "cross origin dummy loaded into frame1");
 
   myFrame1.addEventListener("load", checkBackNavFrame1);
   myFrame1.src = SAME_ORIGIN_URI + "#bar";
 }
 
-async function checkBackNavFrame1() {
+function checkBackNavFrame1() {
   myFrame1.removeEventListener("load", checkBackNavFrame1);
-  is(await SpecialPowers.spawn(myFrame1, [], () => this.content.location.href),
-     SAME_ORIGIN_URI + "#bar",
+  is(SpecialPowers.wrap(myFrame1.contentWindow).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,22 +51,20 @@ function navigateByHyperlink(name) {
   document.body.appendChild(link);
   sendMouseEvent({ type: "click" }, link.id);
 }
 
 // /////////////////////////////////////////////////////////////////////////
 // Functions that call into Mochitest framework
 // /////////////////////////////////////////////////////////////////////////
 
-async function isNavigated(wnd, message) {
+function isNavigated(wnd, message) {
   var result = null;
   try {
-    result = await SpecialPowers.spawn(wnd, [], () =>
-      this.content.document.body.innerHTML.trim()
-    );
+    result = SpecialPowers.wrap(wnd).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(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.");
+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.");
 
   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(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.");
+  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.");
 
     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(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.");
+  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.");
 
     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(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.");
+  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.");
 
     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(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.");
+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.");
 
   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(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.");
+  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.");
 
     xpcCleanupWindows();
     SimpleTest.finish();
   }, 4);
 };
 </script>
 </head>
 <body>
deleted file mode 100644
--- a/docshell/test/unit/test_browsing_context_structured_clone.js
+++ /dev/null
@@ -1,71 +0,0 @@
-"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,13 +1,12 @@
 [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,18 +5,16 @@
  * 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"
@@ -907,20 +905,16 @@ 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;
@@ -974,25 +968,16 @@ 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,18 +132,16 @@ 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,35 +4,37 @@
  * 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(const nsString& aNameBeingResolved,
+static bool ShouldExposeChildWindow(nsString& aNameBeingResolved,
                                     BrowsingContext* aChild) {
-  Element* e = aChild->GetEmbedderElement();
+  nsPIDOMWindowOuter* child = aChild->GetDOMWindow();
+  Element* e = child->GetFrameElementInternal();
   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);
-  if (sop && nsContentUtils::SubjectPrincipal()->Equals(sop->GetPrincipal())) {
+  NS_ENSURE_TRUE(sop, false);
+  if (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
@@ -162,22 +164,33 @@ 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) {
-    if (BrowsingContext* bc = outer->GetBrowsingContext()) {
-      for (const auto& child : bc->GetChildren()) {
-        const nsString& name = child->Name();
-        if (!name.IsEmpty() && !names.Contains(name)) {
+    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)) {
           // Make sure we really would expose it from getOwnPropDescriptor.
-          if (ShouldExposeChildWindow(name, child)) {
+          RefPtr<BrowsingContext> child = win->GetChildWindow(name);
+          if (child && 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,16 +331,17 @@ 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',
new file mode 100644
--- /dev/null
+++ b/dom/base/nsDOMWindowList.cpp
@@ -0,0 +1,82 @@
+/* -*- 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();
+}
new file mode 100644
--- /dev/null
+++ b/dom/base/nsDOMWindowList.h
@@ -0,0 +1,46 @@
+/* -*- 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,16 +45,17 @@
 #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"
@@ -2702,17 +2703,21 @@ 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);
 }
 
-Nullable<WindowProxyHolder> nsGlobalWindowInner::IndexedGetter(
+nsDOMWindowList* nsGlobalWindowInner::GetFrames() {
+  FORWARD_TO_OUTER(GetFrames, (), nullptr);
+}
+
+already_AddRefed<nsPIDOMWindowOuter> 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,16 +72,17 @@ 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;
 
@@ -375,18 +376,17 @@ 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.
-  mozilla::dom::Nullable<mozilla::dom::WindowProxyHolder> IndexedGetter(
-      uint32_t aIndex);
+  already_AddRefed<nsPIDOMWindowOuter> 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,16 +607,17 @@ 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,16 +43,17 @@
 #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"
@@ -504,19 +505,18 @@ 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.
-  Nullable<WindowProxyHolder> GetSubframeWindow(JSContext* cx,
-                                                JS::Handle<JSObject*> proxy,
-                                                JS::Handle<jsid> id) const;
+  already_AddRefed<nsPIDOMWindowOuter> 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 (!GetSubframeWindow(cx, proxy, id).IsNull()) {
+  if (nsCOMPtr<nsPIDOMWindowOuter> frame = GetSubframeWindow(cx, proxy, id)) {
     // 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 (!GetSubframeWindow(cx, proxy, id).IsNull()) {
+  if (nsCOMPtr<nsPIDOMWindowOuter> frame = GetSubframeWindow(cx, proxy, id)) {
     *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 (!GetSubframeWindow(cx, proxy, id).IsNull()) {
+  if (nsCOMPtr<nsPIDOMWindowOuter> frame = GetSubframeWindow(cx, proxy, id)) {
     *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,27 +977,38 @@ 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 {
-  Nullable<WindowProxyHolder> frame = GetSubframeWindow(cx, proxy, id);
-  if (frame.IsNull()) {
+  nsCOMPtr<nsPIDOMWindowOuter> frame = GetSubframeWindow(cx, proxy, id);
+  if (!frame) {
     found = false;
     return true;
   }
 
   found = true;
-  return WrapObject(cx, frame.Value(), vp);
-}
-
-Nullable<WindowProxyHolder> nsOuterWindowProxy::GetSubframeWindow(
+  // 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(
     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);
@@ -1284,16 +1295,17 @@ 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
   }
@@ -2363,16 +2375,20 @@ 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
@@ -2454,16 +2470,20 @@ 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) {
@@ -3171,26 +3191,30 @@ 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(); }
 
-Nullable<WindowProxyHolder> nsGlobalWindowOuter::IndexedGetterOuter(
+nsDOMWindowList* nsGlobalWindowOuter::GetFrames() {
+  if (!mFrames && mDocShell) {
+    mFrames = new nsDOMWindowList(mDocShell);
+  }
+
+  return mFrames;
+}
+
+already_AddRefed<nsPIDOMWindowOuter> nsGlobalWindowOuter::IndexedGetterOuter(
     uint32_t 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;
+  nsDOMWindowList* windows = GetFrames();
+  NS_ENSURE_TRUE(windows, nullptr);
+
+  return windows->IndexedGetter(aIndex);
 }
 
 nsIControllers* nsGlobalWindowOuter::GetControllersOuter(ErrorResult& aError) {
   if (!mControllers) {
     mControllers = new nsXULControllers();
     if (!mControllers) {
       aError.Throw(NS_ERROR_FAILURE);
       return nullptr;
@@ -3933,18 +3957,19 @@ 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() {
-  BrowsingContext* bc = GetBrowsingContext();
-  return bc ? bc->GetChildren().Length() : 0;
+  nsDOMWindowList* windows = GetFrames();
+
+  return windows ? windows->GetLength() : 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,16 +69,17 @@ 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;
@@ -348,18 +349,17 @@ class nsGlobalWindowOuter final : public
   bool Fullscreen() const;
 
   // nsIInterfaceRequestor
   NS_DECL_NSIINTERFACEREQUESTOR
 
   // nsIObserver
   NS_DECL_NSIOBSERVER
 
-  mozilla::dom::Nullable<mozilla::dom::WindowProxyHolder> IndexedGetterOuter(
-      uint32_t aIndex);
+  already_AddRefed<nsPIDOMWindowOuter> 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,16 +530,17 @@ 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:
@@ -913,18 +914,18 @@ class nsGlobalWindowOuter final : public
   virtual void SetFocusedElement(mozilla::dom::Element* aElement,
                                  uint32_t aFocusMethod = 0,
                                  bool aNeedsFocus = false) override;
 
   virtual uint32_t GetFocusMethod() override;
 
   virtual bool ShouldShowFocusRing() override;
 
-  virtual void SetKeyboardIndicators(
-      UIStateChangeType aShowFocusRings) override;
+  virtual void SetKeyboardIndicators(UIStateChangeType aShowFocusRings)
+      override;
 
  public:
   virtual already_AddRefed<nsPIWindowRoot> GetTopWindowRoot() override;
 
  protected:
   void NotifyWindowIDDestroyed(const char* aTopic);
 
   void ClearStatus();
@@ -1094,16 +1095,17 @@ 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,16 +20,17 @@
 #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;
@@ -540,16 +541,18 @@ 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;
@@ -1037,16 +1040,18 @@ 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,24 +2,23 @@
 /* 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"
@@ -57,26 +56,29 @@ static nsresult AddNonJSSizeOfWindowAndI
   // Measure the inner window, if there is one.
   nsGlobalWindowInner* inner = aWindow->GetCurrentInnerWindowInternal();
   if (inner) {
     inner->AddSizeOfIncludingThis(windowSizes);
   }
 
   windowSizes.addToTabSizes(aSizes);
 
-  BrowsingContext* bc = aWindow->GetBrowsingContext();
-  if (!bc) {
-    return NS_OK;
-  }
+  nsDOMWindowList* frames = aWindow->GetFrames();
+
+  uint32_t length = frames->GetLength();
 
   // Measure this window's descendents.
-  for (const auto& frame : bc->GetChildren()) {
-    if (auto* childWin = nsGlobalWindowOuter::Cast(frame->GetDOMWindow())) {
-      MOZ_TRY(AddNonJSSizeOfWindowAndItsDescendents(childWin, aSizes));
-    }
+  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);
   }
   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,18 +5,16 @@
 <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);
     }
@@ -397,33 +395,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";
-      Services.prefs.setBoolPref(PREF_UNSAFE_FORBIDDEN, true);
+      opener.wrappedJSObject.SpecialPowers.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";
-      Services.prefs.setBoolPref(PREF_UNSAFE_FORBIDDEN, true);
+      opener.wrappedJSObject.SpecialPowers.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", true]]})
+SpecialPowers.pushPrefEnv({ set: [ "dom.file.createInChild" ]})
 .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,24 +8,22 @@
 
   <!-- 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";
-  Services.prefs.setBoolPref(PREF_UNSAFE_FORBIDDEN, false);
+  SpecialPowers.setBoolPref(PREF_UNSAFE_FORBIDDEN, false);
   SimpleTest.registerCleanupFunction(() => {
-    Services.prefs.clearUserPref(PREF_UNSAFE_FORBIDDEN);
+    SpecialPowers.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,18 +18,17 @@
 </div>
 <pre id="test">
 </pre>
 </body>
 
 <script class="testbody" type="application/javascript">
 <![CDATA[
 
-const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
-
+var SpecialPowers = window.opener.wrappedJSObject.SpecialPowers;
 var SimpleTest = window.opener.wrappedJSObject.SimpleTest;
 
 SimpleTest.waitForFocus(runTests, window);
 
 function ok(aCondition, aMessage)
 {
   SimpleTest.ok(aCondition, aMessage);
 }
@@ -1476,17 +1475,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.
-  Cu.forceGC();
+  SpecialPowers.gc();
 
   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");
 }
@@ -1766,17 +1765,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 });
 
-  Services.prefs.setBoolPref("dom.keyboardevent.dispatch_during_composition", false);
+  SpecialPowers.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");
@@ -2000,17 +1999,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");
 
-  Services.prefs.setBoolPref("dom.keyboardevent.dispatch_during_composition", true);
+  SpecialPowers.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)");
@@ -2104,17 +2103,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)");
 
-  Services.prefs.clearUserPref("dom.keyboardevent.dispatch_during_composition");
+  SpecialPowers.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);
 }
@@ -2151,17 +2150,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 });
 
-  Services.prefs.setBoolPref("dom.keyboardevent.dispatch_during_composition", false);
+  SpecialPowers.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",
@@ -2201,17 +2200,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");
 
-  Services.prefs.setBoolPref("dom.keyboardevent.dispatch_during_composition", true);
+  SpecialPowers.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);
@@ -2269,17 +2268,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");
 
-  Services.prefs.clearUserPref("dom.keyboardevent.dispatch_during_composition");
+  SpecialPowers.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);
 }
@@ -2590,47 +2589,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 {
-    Services.prefs.setBoolPref("dom.keyboardevent.dispatch_during_composition", false);
+    SpecialPowers.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");
 
-    Services.prefs.setBoolPref("dom.keyboardevent.dispatch_during_composition", true);
+    SpecialPowers.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();
-    Services.prefs.clearUserPref("dom.keyboardevent.dispatch_during_composition");
+    SpecialPowers.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
-async function setTestPluginEnabledState(newEnabledState, pluginName) {
-  var oldEnabledState = await SpecialPowers.setTestPluginEnabledState(
+function setTestPluginEnabledState(newEnabledState, pluginName) {
+  var oldEnabledState = 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() {
-    return SpecialPowers.setTestPluginEnabledState(oldEnabledState, pluginName);
+    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.  */
-    if (SpecialPowers.getBoolPref("javascript.options.asyncstack")) {
+    var isE10S = !SpecialPowers.isMainProcess();
+    if (!isE10S && 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 ? `Async*@${ourFile}:130:17
+    var parentFrame = (asyncStack && !isE10S) ? `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]]}).then(function() {
+SpecialPowers.pushPrefEnv({"set": [["canvas.hitregions.enabled", true]]}, 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,18 +4,16 @@
  * 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,36 +18,35 @@ 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,17 +147,16 @@ 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");
-  is(getGlobal(file), window,
-     "File from MessageManager is not wrapped");
+  isnot(getGlobal(file), window,
+        "File from MessageManager is 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,46 +7,44 @@ 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 **/
 
-var {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
-
-add_task(async function() {
-  await SpecialPowers.pushPrefEnv({"set": [["browser.enable_automatic_image_resizing", true]]});
+SimpleTest.waitForExplicitFinish();
 
-  return new Promise(resolve => {
-    var testWin = document.querySelector("iframe");
-    testWin.src = "image.png";
-    testWin.onload = function() {
-      var testDoc = testWin.contentDocument;
+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;
 
-      // 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");
+    // testDoc should be a image document.
+    ok(testDoc.imageIsOverflowing, "image is overflowing");
+    ok(testDoc.imageIsResized, "image is resized to fit visible area by default");
 
-      // 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");
+    // Restore image to original size.
+    testDoc.restoreImage();
+    ok(testDoc.imageIsOverflowing, "image is overflowing");
+    ok(!testDoc.imageIsResized, "image is restored to original size");
 
-      resolve();
-    };
-  })
+    // 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();
+  };
 });
 
 </script>
 </head>
 
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1414077">Mozilla Bug 1414077</a>
-<iframe width="0" height="0"></iframe>
+<iframe></iframe>
 </body>
 </html>
--- a/dom/ipc/ContentChild.cpp
+++ b/dom/ipc/ContentChild.cpp
@@ -883,31 +883,22 @@ 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<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;
-      }
+    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,18 +3,16 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #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
@@ -32,26 +30,31 @@ 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(GetParentObject(), "JSWindowActor destroy callback");
+  AutoEntryScript aes(xpc::PrivilegedJunkScope(),
+                      "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;
   }
 
@@ -83,36 +86,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 (!nsFrameMessageManager::GetParamsForMessage(aCx, aObj, aTransfers,
-                                                  data)) {
+  if (!aObj.isUndefined() && !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 (!nsFrameMessageManager::GetParamsForMessage(aCx, aObj, aTransfers,
-                                                  data)) {
+  if (!aObj.isUndefined() && !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;
@@ -132,29 +135,25 @@ 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(GetParentObject(), "JSWindowActor message handler");
+  AutoEntryScript aes(xpc::PrivilegedJunkScope(),
+                      "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);
@@ -231,25 +230,18 @@ 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, data);
+    promise->MaybeResolve(aCx, aData);
   } 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(
@@ -265,19 +257,18 @@ 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.
-  ipc::StructuredCloneData data;
-  data.Write(aCx, JS::UndefinedHandleValue, IgnoredErrorResult());
-  SendReply(aCx, JSWindowActorMessageKind::QueryReject, std::move(data));
+  SendReply(aCx, JSWindowActorMessageKind::QueryReject,
+            ipc::StructuredCloneData());
 }
 
 void JSWindowActor::QueryHandler::ResolvedCallback(
     JSContext* aCx, JS::Handle<JS::Value> aValue) {
   if (!mActor) {
     return;
   }
 
@@ -293,19 +284,18 @@ 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);
 
-    ipc::StructuredCloneData data;
-    data.Write(aCx, JS::UndefinedHandleValue, IgnoredErrorResult());
-    SendReply(aCx, JSWindowActorMessageKind::QueryReject, std::move(data));
+    SendReply(aCx, JSWindowActorMessageKind::QueryReject,
+              ipc::StructuredCloneData());
     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);
 
-  virtual nsIGlobalObject* GetParentObject() const = 0;
+  nsIGlobalObject* GetParentObject() const;
 
   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,20 +12,16 @@
 #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,27 +32,22 @@ 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) {
-    nsCOMPtr<nsIGlobalObject> global(do_QueryInterface(aGlobal.GetAsSupports()));
-    return MakeAndAddRef<JSWindowActorChild>(global);
+    return MakeAndAddRef<JSWindowActorChild>();
   }
 
   WindowGlobalChild* GetManager() const;
   void Init(const nsAString& aName, WindowGlobalChild* aManager);
   void StartDestroy();
   void AfterDestroy();
   Document* GetDocument(ErrorResult& aRv);
   BrowsingContext* GetBrowsingContext(ErrorResult& aRv);
@@ -64,16 +59,14 @@ 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,20 +9,16 @@
 #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,18 +35,16 @@ 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,17 +157,16 @@ 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
-    return SpecialPowers.setBoolPref("notification.prompt.testing", true);
+    SpecialPowers.setBoolPref("notification.prompt.testing", true);
   }
 
-  async function teardown_testing_env() {
-    await SpecialPowers.clearUserPref("notification.prompt.testing");
-    await SpecialPowers.clearUserPref("notification.prompt.testing.allow");
+  function teardown_testing_env() {
+    SpecialPowers.clearUserPref("notification.prompt.testing");
+    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,39 +74,32 @@ var NotificationTest = (function() {
       map,
       set,
     };
   })();
 
   // NotificationTest API
   return {
     run(tests, callback) {
-      let ready = setup_testing_env();
+      setup_testing_env();
 
-      addLoadEvent(async function() {
-        await ready;
+      addLoadEvent(function() {
         executeTests(tests, function() {
           teardown_testing_env();
           callback && callback();
         });
       });
     },
 
     allowNotifications() {
-      return SpecialPowers.setBoolPref(
-        "notification.prompt.testing.allow",
-        true
-      );
+      SpecialPowers.setBoolPref("notification.prompt.testing.allow", true);
     },
 
     denyNotifications() {
-      return SpecialPowers.setBoolPref(
-        "notification.prompt.testing.allow",
-        false
-      );
+      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();
     },
 
-    async function(done) {
+    function(done) {
       info("Test requestPermission deny");
       function assertPermissionDenied(perm) {
         is(perm, "denied", "Permission should be denied.");
         is(Notification.permission, "denied", "Permission should be denied.");
       }
-      await NotificationTest.denyNotifications();
+      NotificationTest.denyNotifications();
       Notification.requestPermission()
         .then(assertPermissionDenied)
         .then(_ => Notification.requestPermission(assertPermissionDenied))
         .catch(err => {
           ok(!err, "requestPermission should not reject promise");
         })
         .then(done);
     },
 
-    async function(done) {
+    function(done) {
       info("Test requestPermission grant");
       function assertPermissionGranted(perm) {
         is(perm, "granted", "Permission should be granted.");
         is(Notification.permission, "granted", "Permission should be granted");
       }
-      await NotificationTest.allowNotifications();
+      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.sendAsyncMessage("destroy");
+    script.sendSyncMessage("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(async () => {
+            promiseGranted.then(() => {
               const promisePrompt = this.promiseStateChanged(permission, 'prompt');
-              await SpecialPowers.popPermissions();
+              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,21 +115,17 @@ 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";
-  let resolved = false;
-  SpecialPowers.setTestPluginEnabledState(aState, name).then(() => {
-    resolved = true;
-  });
-  SpecialPowers.Services.tm.spinEventLoopUntil(() => resolved);
+  SpecialPowers.setTestPluginEnabledState(aState, name);
 }
 
 /**
  * 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,21 +40,18 @@ 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(async function() {
-    return SpecialPowers.setTestPluginEnabledState(
-      await oldEnabledState,
-      pluginName
-    );
+  SimpleTest.registerCleanupFunction(function() {
+    SpecialPowers.setTestPluginEnabledState(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,16 +5,21 @@
     <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;
             }
           }
@@ -35,23 +40,17 @@
           let obj = document.createElement("object");
           obj.type = type;
           obj.id = id;
           obj.width = 200;
           obj.height = 200;
           document.body.appendChild(obj);
       }
 
-      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);
-        });
-
+      function run() {
         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");
 
@@ -66,11 +65,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="run()">
+  <body onload="addPerms()">
   </body>
 </html>
--- a/dom/plugins/test/mochitest/test_hangui.xul
+++ b/dom/plugins/test/mochitest/test_hangui.xul
@@ -14,18 +14,16 @@
   <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";
@@ -86,19 +84,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;
   }
-  Services.prefs.clearUserPref(hangUITimeoutPref);
-  Services.prefs.clearUserPref(hangUIMinDisplayPref);
-  Services.prefs.clearUserPref(timeoutPref);
+  SpecialPowers.clearUserPref(hangUITimeoutPref);
+  SpecialPowers.clearUserPref(hangUIMinDisplayPref);
+  SpecialPowers.clearUserPref(timeoutPref);
   SimpleTest.finish();
 }
 
 function runTests() {
   resetVars();
 
   hanguiOperation("Prime ChromeWorker", 0, false, false, HANGUIOP_NOTHING, 0,
                   false, "test1");
@@ -148,32 +146,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();
-  Services.prefs.setIntPref(hangUITimeoutPref, 1);
-  Services.prefs.setIntPref(hangUIMinDisplayPref, 1);
-  Services.prefs.setIntPref(timeoutPref, 45);
+  SpecialPowers.setIntPref(hangUITimeoutPref, 1);
+  SpecialPowers.setIntPref(hangUIMinDisplayPref, 1);
+  SpecialPowers.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();
-  Services.prefs.setIntPref(hangUITimeoutPref, 1);
-  Services.prefs.setIntPref(hangUIMinDisplayPref, 4);
+  SpecialPowers.setIntPref(hangUITimeoutPref, 1);
+  SpecialPowers.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");
@@ -181,36 +179,36 @@ function test8a() {
 
 function test8() {
   window.frameLoaded = test8a;
   iframe.contentWindow.location.reload();
 }
 
 function test7a() {
   resetVars();
-  Services.prefs.setIntPref(hangUITimeoutPref, 0);
+  SpecialPowers.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() {
-  Services.prefs.setIntPref(hangUITimeoutPref, 1);
-  Services.prefs.setIntPref(hangUIMinDisplayPref, 1);
-  Services.prefs.setIntPref(timeoutPref, 3);
+  SpecialPowers.setIntPref(hangUITimeoutPref, 1);
+  SpecialPowers.setIntPref(hangUIMinDisplayPref, 1);
+  SpecialPowers.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)");
@@ -247,18 +245,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() {
-  Services.prefs.setIntPref(hangUITimeoutPref, 1);
-  Services.prefs.setIntPref(hangUIMinDisplayPref, 1);
-  Services.prefs.setIntPref(timeoutPref, 45);
+  SpecialPowers.setIntPref(hangUITimeoutPref, 1);
+  SpecialPowers.setIntPref(hangUIMinDisplayPref, 1);
+  SpecialPowers.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(async function(aAvailability) {
+  return request.getAvailability().then(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;
-    await gScript.sendQuery("trigger-device-add", testDevice);
+    gScript.sendAsyncMessage("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.sendAsyncMessage("socket-setup");
+  chromeScript.sendSyncMessage("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.sendAsyncMessage("socket-teardown");
+      chromeScript.sendSyncMessage("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;
-  async function checkTestsCompleted() {
+  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++;
-      await SpecialPowers.setBoolPref("security.mixed_content.block_active_content", false);
+      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,75 +59,78 @@
 
     unregisterArray.push(navigator.serviceWorker.getRegistration("scope/").then(function (reg) {
       return reg.unregister();
     }));
 
     return Promise.all(unregisterArray);
   }
 
-  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 };
-        }
-      });
-    }
+  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 };
+          }
+        });
+      }
 
-    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 = 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);
-    }
+        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);
+      }
 
-    var base = new URL(".", document.baseURI);
+      var base = new URL(".", document.baseURI);
 
-    function p(s) {
-      return base + s;
-    }
+      function p(s) {
+        return base + s;
+      }
 
-    async function fail(fn) {
-      try {
-        await getScope(p("index.html"));
-        ok(false, "No registration");
-      } catch(e) {
-        ok(true, "No registration");
+      function fail(fn) {
+        try {
+          getScope(p("index.html"));
+          ok(false, "No registration");
+        } catch(e) {
+          ok(true, "No registration");
+        }
       }
-    }
 
-    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");
+      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();
+    });
   }
 
   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;
   });
 });
 
-async function onMozillaIFrameLoaded() {
-  let chromeBuildID = await chromeScript.sendQuery("test:getBuildID");
-  chromeScript.destroy();
+let chromeBuildID = chromeScript.sendSyncMessage("test:getBuildID")[0][0];
+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">
 
-  async function checkForFindDialog() {
+  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 = await chromeScript.sendQuery("test:check");
+    let sawFind = chromeScript.sendSyncMessage("test:check")[0][0];
     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 = await checkForFindDialog();
+      let sawWindow = 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,19 +6,17 @@
   <iframe type="content"></iframe>
 
   <script type="application/javascript">
   <![CDATA[
   /** Test for Bug 799299 **/
   var SimpleTest = opener.wrappedJSObject.SimpleTest;
   var ok = opener.wrappedJSObject.ok;
 
-  // 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;
+  var doc = frames[0].document;
   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]]}).then(function () {
+SpecialPowers.pushPrefEnv({"set":[["dom.background_loading_iframe", true]]}, 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,19 +1,20 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 var GamepadService;
 
-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 setGamepadPreferenceAndCreateIframe(iframeSrc) {
+  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);
 }
 
-async function startTest() {
-  await SpecialPowers.pushPrefEnv({ "set": [
-                                    ["dom.gamepad.extensions.enabled", true],
-                                    ["dom.gamepad.extensions.lightindicator", true],
-                                    ["dom.gamepad.extensions.multitouch", true]] });
+function startTest() {
+  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,24 +47,23 @@ window.addEventListener("gamepadbuttondo
 });
 
 function pressButton() {
   GamepadService.newButtonEvent(gamepad_index, 0, true, true);
   GamepadService.newButtonEvent(gamepad_index, 0, false, false);
 }
 
 let frames_loaded = 0;
-async function startTest() {
+function startTest() {
   frames_loaded++;
-  let promise = SpecialPowers.pushPrefEnv({ "set": [
+  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.sendAsyncMessage("destroy");
+  script.sendSyncMessage("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);
 }
 
-async function* runTests() {
+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 @@ async 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");
   };
 
-  await SpecialPowers.pushPrefEnv({set: [["layout.word_select.eat_space_to_next_word", false]]});
+  yield SpecialPowers.pushPrefEnv({set: [["layout.word_select.eat_space_to_next_word", false]]});
   runSelectionTests(body, 1);
-  await SpecialPowers.pushPrefEnv({set: [["layout.word_select.eat_space_to_next_word", true]]});
+  yield 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 ((await curTest.next()).done) {
+    if (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 = async function(evt) {
+var loadListener = 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 = async 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 = await script.sendQuery("check-existence");
+    var existenceChecks = script.sendSyncMessage("check-existence")[0][0];
     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.sendAsyncMessage("destroy");
+      script.sendSyncMessage("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 = async function(evt) {
+var loadListener = 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 = async 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(await script.sendQuery("en_GB-exists"), true,
+    is(script.sendSyncMessage("en_GB-exists")[0][0], 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 = async 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.sendAsyncMessage("destroy");
+      script.sendSyncMessage("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(async function() {
+SimpleTest.waitForFocus(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(async 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(await script.sendQuery("de_DE-exists"), true,
+  is(script.sendSyncMessage("de_DE-exists")[0][0], 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.sendAsyncMessage("destroy");
+    script.sendSyncMessage("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(async function() {
-  /* global actorParent */
+SimpleTest.waitForFocus(function() {
+  /* global browserElement */
   /* eslint-env mozilla/frame-script */
   script = SpecialPowers.loadChromeScript(function() {
-    var chromeWin = actorParent.rootFrameLoader
-                    .ownerElement.ownerGlobal.docShell
-                    .rootTreeItem.domWindow;
+    var chromeWin = browserElement.ownerGlobal.docShell
+                    .rootTreeItem.domWindow
+                    .QueryInterface(Ci.nsIDOMChromeWindow);
     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(async 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(await script.sendQuery("contextMenu-not-null"), true,
+  is(script.sendSyncMessage("contextMenu-not-null")[0][0], true,
      "Got context menu XUL");
-  is(await script.sendQuery("de_DE-exists"), true,
+  is(script.sendSyncMessage("de_DE-exists")[0][0], 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(async 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);
     });
   });
 });
 
-async function handlePopup() {
-  var state = await script.sendQuery("hidepopup");
+function handlePopup() {
+  var state = script.sendSyncMessage("hidepopup")[0][0];
   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.sendAsyncMessage("destroy");
+    script.sendSyncMessage("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 = async function(evt) {
+var loadListener = 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 = async 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(await script.sendQuery("en_GB-exists"), true,
+    is(script.sendSyncMessage("en_GB-exists")[0][0], 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 = async 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.sendAsyncMessage("destroy");
+      script.sendSyncMessage("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(async function() {
+SimpleTest.waitForFocus(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(async 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(await script.sendQuery("de_DE-exists"), true,
+  is(script.sendSyncMessage("de_DE-exists")[0][0], 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.sendAsyncMessage("destroy");
+    script.sendSyncMessage("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 = async function(evt) {
+var loadListener = 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 = async 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(await script.sendQuery("en_GB-exists"), true,
+    is(script.sendSyncMessage("en_GB-exists")[0][0], 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 = async 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.sendAsyncMessage("destroy");
+      script.sendSyncMessage("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.sendAsyncMessage("cleanup", null);
+        waitUntilApzStable.chromeHelper.sendSyncMessage("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,17 +689,19 @@ function getSnapshot(rect) {
     getSnapshot.chromeHelper = SpecialPowers.loadChromeScript(
       parentProcessSnapshot
     );
     ApzCleanup.register(function() {
       getSnapshot.chromeHelper.destroy();
     });
   }
 
-  return getSnapshot.chromeHelper.sendQuery("snapshot", JSON.stringify(rect));
+  return getSnapshot.chromeHelper
+    .sendSyncMessage("snapshot", JSON.stringify(rect))
+    .toString();
 }
 
 // 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,75 +28,64 @@ 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() {
-    const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
-
-    const PREFIX = "apz:ctec:";
+    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 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]++;
-        };
+      topWin.addEventListener("touchstart", topWin.counter, { passive: true });
+      topWin.addEventListener("touchmove", topWin.counter, { passive: true });
+      topWin.addEventListener("touchend", topWin.counter, { passive: true });
 
-        topWin.addEventListener("touchstart", topWin.counter, { passive: true });
-        topWin.addEventListener("touchmove", topWin.counter, { passive: true });
-        topWin.addEventListener("touchend", topWin.counter, { passive: true });
-
-        return true;
-      },
+      return true;
+    });
 
-      "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("report", function() {
+      const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+      var topWin = Services.wm.getMostRecentWindow("navigator:browser");
+      return JSON.stringify(topWin.eventCounts);
+    });
 
-        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);
-    }
+    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;
+    });
   }
 
   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 SpecialPowers.Services.cpmm.sendSyncMessage(`apz:ctec:${operation}`, "")[0];
+  return chromeTouchEventCounter.chromeHelper.sendSyncMessage(operation, "");
 }
 
 // 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,9 +1,8 @@
 [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,18 +15,16 @@ 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;
@@ -50,17 +48,16 @@ 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,40 +8,37 @@ 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();
-  (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();
+  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;
-      }
-      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();
+  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();
+  }
 
   </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,43 +9,41 @@
   <!-- 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[
-    SimpleTest.waitForExplicitFinish();
-
+    SpecialPowers.pushPrefEnv({"set": [["security.allow_eval_with_system_principal",
+                                        true]]});
     function init() {
       var f = new Function("let test = 'let is ok'; return test;");
       is(f(), 'let is ok', 'let should be ok');
       SimpleTest.finish();
     }
 
-    (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');
-        }
-      };
+    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);
-    })();
+    // If init is called directly, it works.
+    setTimeout('init();', 0);
+
+    SimpleTest.waitForExplicitFinish();
 
   ]]></script>
 </window>
--- a/js/xpconnect/tests/chrome/test_bug732665.xul
+++ b/js/xpconnect/tests/chrome/test_bug732665.xul
@@ -13,19 +13,18 @@ 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[
 
-add_task(async () => {
-  await SpecialPowers.pushPrefEnv({"set": [["security.allow_eval_with_system_principal",
-                                            true]]});
+  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
@@ -72,12 +71,12 @@ add_task(async () => {
   //
   // 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,23 +12,22 @@ 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;
 
-await SpecialPowers.pushPrefEnv({"set": [["security.allow_eval_with_system_principal",
-                                          true]]});
+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));
@@ -220,11 +219,10 @@ 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");
 
-async function go() {
+function go() {
+    SpecialPowers.pushPrefEnv({"set": [["security.allow_eval_with_system_principal",
+                                        true]]});
     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,16 +13,19 @@ 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);
@@ -471,80 +474,83 @@ 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.
-  function arraysEqual(arr1, arr2, reason) {
-    is(arr1.length, arr2.length, `${reason}; lengths should be equal`)
+  // 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\`)
     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);
 
-  function testArrayIterators(arrayLike, equivalentArray, reason) {
-    arraysEqual([...arrayLike], equivalentArray, `${reason}; spread operator`);
+  var testArrayIteratorsSource = `
+    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`);
-  }
+    arraysEqual(someCopy, equivalentArray, \`\${reason}; some() copy\`);
+  }`;
+  eval(testArrayIteratorsSource);
 
   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);
 
@@ -789,31 +795,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);
-      new window[c](buffer);
+      eval(`new ${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(String(arraysEqual));
-      wesb.eval(String(testArrayIterators));
+      wesb.eval(arraysEqualSource);
+      wesb.eval(testArrayIteratorsSource);
       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,16 +2,17 @@
 /* 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,22 +1644,26 @@ 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) {
-      Nullable<WindowProxyHolder> subframe = win->IndexedGetter(index);
-      if (!subframe.IsNull()) {
-        if (MOZ_UNLIKELY(!WrapObject(cx, subframe.Value(), desc.value()))) {
+      nsCOMPtr<nsPIDOMWindowOuter> subframe = win->IndexedGetter(index);
+      if (subframe) {
+        subframe->EnsureInnerWindow();
+        nsGlobalWindowOuter* global = nsGlobalWindowOuter::Cast(subframe);
+        JSObject* obj = global->GetGlobalJSObject();
+        if (MOZ_UNLIKELY(!obj)) {
           // 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,27 +6,23 @@
 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 = frameElts[1].contentWindow.docShell.initOrReusePrintPreviewViewer();
+  gWbp = window.frames[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) { },
@@ -38,22 +34,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, frameElts[0].contentWindow, listener);
+  gWbp.printPreview(gWbp.globalPrintSettings, window.frames[0], listener);
   prefs.clearUserPref('print.show_print_progress');
 }
 
 function exitprintpreview() {
-  frameElts[1].contentWindow.docShell.exitPrintPreview();
+  window.frames[1].docShell.exitPrintPreview();
 }
 
 function finish() {
   SimpleTest.finish();
   window.close();
 }
 
 function run()
@@ -85,21 +81,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);
-  frameElts[0].contentWindow.location.reload();
+  window.frames[0].location.reload();
 }
 
 function run3() {
-  gWbp = frameElts[1].contentWindow.docShell.initOrReusePrintPreviewViewer();
+  gWbp = window.frames[1].docShell.initOrReusePrintPreviewViewer();
   ok(gWbp.doingPrintPreview, "Should be doing print preview");
   exitprintpreview();
   setTimeout(run4, 0);
 }
 
 function run4() {
   var i = document.getElementById("i");
   i.remove();
@@ -108,17 +104,17 @@ function run4() {
     setTimeout(run5, 0);
   };
   i.addEventListener("load", loadhandler, true);
   document.documentElement.getBoundingClientRect();
   document.documentElement.prepend(i);
 }
 
 function run5() {
-  gWbp = frameElts[1].contentWindow.docShell.initOrReusePrintPreviewViewer();
+  gWbp = window.frames[1].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,27 +6,23 @@
 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 = frameElts[1].contentWindow.docShell.initOrReusePrintPreviewViewer();
+  gWbp = window.frames[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) { },
@@ -38,22 +34,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, frameElts[0].contentWindow, listener);
+  gWbp.printPreview(gWbp.globalPrintSettings, window.frames[0], listener);
   prefs.clearUserPref('print.show_print_progress');
 }
 
 function exitprintpreview() {
-  frameElts[1].contentWindow.docShell.exitPrintPreview();
+  window.frames[1].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,37 +3,33 @@
 <?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 = frameElts[1].docShell.initOrReusePrintPreviewViewer();
+  gWbp = window.frames[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) { },
@@ -49,32 +45,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; }
-  frameElts[0].contentWindow.addEventListener("beforeprint", beforeprint, true);
-  frameElts[0].contentWindow.addEventListener("afterprint", afterprint, true);
-  gWbp.printPreview(gWbp.globalPrintSettings, frameElts[0].contentWindow, listener);
+  window.frames[0].addEventListener("beforeprint", beforeprint, true);
+  window.frames[0].addEventListener("afterprint", afterprint, true);
+  gWbp.printPreview(gWbp.globalPrintSettings, window.frames[0], 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!");
   }
-  frameElts[0].contentWindow.removeEventListener("beforeprint", beforeprint, true);
-  frameElts[0].contentWindow.removeEventListener("afterprint", afterprint, true);
+  window.frames[0].removeEventListener("beforeprint", beforeprint, true);
+  window.frames[0].removeEventListener("afterprint", afterprint, true);
   prefs.clearUserPref('print.show_print_progress');
 }
 
 function exitprintpreview() {
-  frameElts[1].contentWindow.docShell.exitPrintPreview();
+  window.frames[1].docShell.exitPrintPreview();
 }
 
 function finish() {
   SimpleTest.finish();
   window.close();
 }
 
 function runTests()
@@ -116,55 +112,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");
-  frameElts[0].contentDocument.body.innerHTML = "<div> </div><div>" + counter + " timers</div><div> </div>";
+  window.frames[0].document.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.
-  frameElts[0].contentWindow.wrappedJSObject.counter = counter;
-  frameElts[0].contentWindow.counterTimeout = "document.body.firstChild.nextSibling.innerHTML = ++counter + ' timers';" +
+  window.frames[0].wrappedJSObject.counter = counter;
+  window.frames[0].counterTimeout = "document.body.firstChild.nextSibling.innerHTML = ++counter + ' timers';" +
                                     "window.setTimeout(counterTimeout, 0);";
-  frameElts[0].contentWindow.setTimeout(frameElts[0].contentWindow.counterTimeout, 0);
-  frameElts[0].contentDocument.body.firstChild.innerHTML = "Print preview";
+  window.frames[0].setTimeout(window.frames[0].counterTimeout, 0);
+  window.frames[0].document.body.firstChild.innerHTML = "Print preview";
 
   printpreview();
-  ctx1.drawWindow(frameElts[1].contentWindow, 0, 0, 400, 400, "rgb(256,256,256)");
-  frameElts[0].contentDocument.body.firstChild.innerHTML = "Galley presentation";
+  ctx1.drawWindow(window.frames[1], 0, 0, 400, 400, "rgb(256,256,256)");
+  window.frames[0].document.body.firstChild.innerHTML = "Galley presentation";
 
   // Add some elements.
-  addHTMLContent(frameElts[0].contentDocument.body.lastChild);
+  addHTMLContent(window.frames[0].document.body.lastChild);
   // Delete them.
-  frameElts[0].contentDocument.body.lastChild.innerHTML = "";
+  window.frames[0].document.body.lastChild.innerHTML = "";
   // And readd.
-  addHTMLContent(frameElts[0].contentDocument.body.lastChild);
+  addHTMLContent(window.frames[0].document.body.lastChild);
 
   setTimeout(finalizeTest1, 1000);
 }
 
 function finalizeTest1() {
-  ctx2.drawWindow(frameElts[1].contentWindow, 0, 0, 400, 400, "rgb(256,256,256)");
+  ctx2.drawWindow(window.frames[1], 0, 0, 400, 400, "rgb(256,256,256)");
   exitprintpreview();
   ok(compareCanvases(), "Canvas should be the same!");
-  counter = frameElts[0].contentWindow.counter;
+  counter = window.frames[0].counter;
   // This timeout is needed so that we can check that timers do run after
   // print preview.
   setTimeout(runTest2, 1000);
 }
 
 function runTest2() {
-  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 = "";
+  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 = "";
   setTimeout(runTest3, 0);
 }
 
 var elementIndex = 0;
 var compareEmptyElement = true;
 var emptyFormElements =
   ["<input type='text'>",
    "<input type='password'>",
@@ -207,154 +203,154 @@ function runTest3() {
     compareFormElementPrint(emptyFormElements[currentIndex], formElements[currentIndex], false);
     return;
   }
 
   setTimeout(runTest4, 0)
 }
 
 function compareFormElementPrint(el1, el2, equals) {
-  frameElts[0].contentDocument.body.innerHTML = el1;
-  frameElts[0].contentDocument.body.firstChild.value =
-    frameElts[0].contentDocument.body.firstChild.getAttribute('value');
+  window.frames[0].document.body.innerHTML = el1;
+  window.frames[0].document.body.firstChild.value =
+    window.frames[0].document.body.firstChild.getAttribute('value');
   printpreview();
-  ctx1.drawWindow(frameElts[1].contentWindow, 0, 0, 400, 400, "rgb(256,256,256)");
+  ctx1.drawWindow(window.frames[1], 0, 0, 400, 400, "rgb(256,256,256)");
   exitprintpreview();
-  frameElts[0].contentDocument.body.innerHTML = el2;
-  frameElts[0].contentDocument.body.firstChild.value =
-    frameElts[0].contentDocument.body.firstChild.getAttribute('value');
+  window.frames[0].document.body.innerHTML = el2;
+  window.frames[0].document.body.firstChild.value =
+    window.frames[0].document.body.firstChild.getAttribute('value');
   printpreview();
-  ctx2.drawWindow(frameElts[1].contentWindow, 0, 0, 400, 400, "rgb(256,256,256)");
+  ctx2.drawWindow(window.frames[1], 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() {
-  frameElts[0].contentDocument.body.innerHTML =
+  window.frames[0].document.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() {
-  frameElts[0].contentDocument.body.innerHTML =
+  window.frames[0].document.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() {
-  frameElts[0].contentDocument.body.innerHTML =
+  window.frames[0].document.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
-  frameElts[0].contentDocument.body.innerHTML =
+  window.frames[0].document.body.innerHTML =
     "<div>" + contentText + "</div>";
-  frameElts[0].contentDocument.body.firstChild.value =
-    frameElts[0].contentDocument.body.firstChild.getAttribute('value');
+  window.frames[0].document.body.firstChild.value =
+    window.frames[0].document.body.firstChild.getAttribute('value');
   printpreview();
-  ctx1.drawWindow(frameElts[1].contentWindow, 0, 0, 400, 400, "rgb(255,255,255)");
+  ctx1.drawWindow(window.frames[1], 0, 0, 400, 400, "rgb(255,255,255)");
   exitprintpreview();
 
-  frameElts[0].contentDocument.body.innerHTML = "<div></div>";
-  var sr = frameElts[0].contentDocument.body.firstChild.attachShadow({mode: "open"});
+  window.frames[0].document.body.innerHTML = "<div></div>";
+  var sr = window.frames[0].document.body.firstChild.attachShadow({mode: "open"});
   sr.innerHTML = contentText;
   printpreview();
-  ctx2.drawWindow(frameElts[1].contentWindow, 0, 0, 400, 400, "rgb(255,255,255)");
+  ctx2.drawWindow(window.frames[1], 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(frameElts[1].contentWindow, 0, 0, 400, 400, "rgb(255,255,255)");
+  ctx1.drawWindow(window.frames[1], 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(frameElts[1].contentWindow, 0, 0, 400, 400, "rgb(255,255,255)");
+  ctx2.drawWindow(window.frames[1], 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() {
-  frameElts[0].contentDocument.body.innerHTML = `
+  window.frames[0].document.body.innerHTML = `
     <svg width="100" height="100">
       <rect width='100' height='100' fill='lime'/>
     </svg>
   `;
 
   printpreview();
-  ctx1.drawWindow(frameElts[1].contentWindow, 0, 0, 400, 400, "rgb(255,255,255)");
+  ctx1.drawWindow(window.frames[1], 0, 0, 400, 400, "rgb(255,255,255)");
   exitprintpreview();
 
-  frameElts[0].contentDocument.body.innerHTML = `
+  window.frames[0].document.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.
-  frameElts[0].contentDocument.querySelector("use").setAttribute("href", "#useme");
+  window.frames[0].document.querySelector("use").setAttribute("href", "#useme");
 
   // Ensure the <use> shadow tree is created so we test what we want to test.
-  frameElts[0].contentDocument.body.offsetTop;
+  window.frames[0].document.body.offsetTop;
 
   printpreview();
-  ctx2.drawWindow(frameElts[1].contentWindow, 0, 0, 400, 400, "rgb(255,255,255)");
+  ctx2.drawWindow(window.frames[1], 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() {
@@ -367,31 +363,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(frameElts[1].contentWindow, 0, 0, 400, 400, "rgb(255,255,255)");
+  ctx1.drawWindow(window.frames[1], 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(frameElts[1].contentWindow, 0, 0, 400, 400, "rgb(255,255,255)");
+  ctx2.drawWindow(window.frames[1], 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]]]}).then(step2);
+SpecialPowers.pushPrefEnv({'set': [['font.name.serif.x-western', serifFonts[serifIdx]]]}, step2);
 
 var serifWidthFromPref;
 function step2() {
     serifWidthFromPref = tableElement.offsetWidth;
-    SpecialPowers.pushPrefEnv({'set': [['font.name.serif.x-western', monospaceFonts[monospaceIdx]]]}).then(step3);
+    SpecialPowers.pushPrefEnv({'set': [['font.name.serif.x-western', monospaceFonts[monospaceIdx]]]}, 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,29 +527,26 @@ 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) {
-            try {
-                flushWindow(win.frames[i]);
-            } catch (e) {
-                Cu.reportError(e);
-            }
+            flushWindow(win.frames[i]);
         }
     }
 
     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;
 
-async function doTest()
+function doTest()
 {
-  await SpecialPowers.setBoolPref("network.http.debug-observations", true);
+  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.sendAsyncMessage("set-overrides", OVERRIDES);
+  chromeScript.sendSyncMessage("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(async () => {
+      }).then(() => {
         currentround = "";
 
         if (!isPrivate) {
-          await clean_up_sts_state(isPrivate);
+          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");
     });
   }
 
-  async function clean_up_sts_state(isPrivate) {
+  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;
-    await SpecialPowers.cleanUpSTSData("http://example.com", flags);
+    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,16 +454,24 @@ 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,16 +416,21 @@ 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",
@@ -617,16 +622,19 @@ 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")
@@ -814,16 +822,19 @@ 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,16 +24,17 @@ 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',
@@ -41,25 +42,28 @@ 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',
 ]
@@ -100,16 +104,17 @@ 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',
new file mode 100644
--- /dev/null
+++ b/testing/mochitest/nested_setup.js
@@ -0,0 +1,31 @@
+/* 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,16 +846,17 @@ 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 = {}
@@ -1096,16 +1097,18 @@ 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
@@ -1915,16 +1918,17 @@ 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,16 +311,17 @@ 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");
@@ -721,16 +722,49 @@ 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(
deleted file mode 100644
--- a/testing/mochitest/tests/Harness_sanity/file_spawn.html
+++ /dev/null
@@ -1,10 +0,0 @@
-<!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,19 +17,16 @@ 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,46 +26,45 @@ 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();
-async function starttest(){
+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
-  await SpecialPowers.setBoolPref('extensions.checkCompatibility', true);
+  SpecialPowers.setBoolPref('extensions.checkCompatibility', true);
   is(SpecialPowers.getBoolPref('extensions.checkCompatibility'), true, "Check to see if we can set a preference properly");
-  await SpecialPowers.clearUserPref('extensions.checkCompatibility');
+  SpecialPowers.clearUserPref('extensions.checkCompatibility');
 
   // Test a int pref
-  await SpecialPowers.setIntPref('extensions.foobar', 42);
+  SpecialPowers.setIntPref('extensions.foobar', 42);
   is(SpecialPowers.getIntPref('extensions.foobar'), 42, "Check int pref");
-  await SpecialPowers.clearUserPref('extensions.foobar');
+  SpecialPowers.clearUserPref('extensions.foobar');
 
   // Test a string pref
-  await SpecialPowers.setCharPref("extensions.foobaz", "hi there");
+  SpecialPowers.setCharPref("extensions.foobaz", "hi there");
   is(SpecialPowers.getCharPref("extensions.foobaz"), "hi there", "Check string pref");
-  await SpecialPowers.clearUserPref("extensions.foobaz");
+  SpecialPowers.clearUserPref("extensions.foobaz");
 
   // Test an invalid pref
   var retVal = null;
   try {
     retVal = SpecialPowers.getBoolPref('extensions.checkCompat0123456789');
   } catch (ex) {
     retVal = ex;
   }
-  is(retVal.result, SpecialPowers.Cr.NS_ERROR_UNEXPECTED,
-     "received an exception trying to get an unset preference value");
+  is(retVal.message, "Error getting pref 'extensions.checkCompat0123456789'", "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,22 +42,28 @@ function endOfFirstTest() {
     });
   }, { wantGlobalProperties: ["ChromeUtils", "XMLHttpRequest"] });
 
   script2.sendAsyncMessage("valid-assert");
   script2.addMessageListener("valid-assert-done", endOfTest);
 
 }
 
-async function endOfTest() {
-  is(await script.sendQuery("sync-message"), "Received a synchronous message.",
-     "Check sync return value");
-
+function endOfTest() {
   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,22 +40,28 @@ script.addMessageListener("bar", functio
   checkAssert();
 });
 
 function checkAssert() {
   script.sendAsyncMessage("valid-assert");
   script.addMessageListener("valid-assert-done", endOfTest);
 }
 
-async function endOfTest() {
-  is(await script.sendQuery("sync-message"), "Received a synchronous message.",
-     "Check sync return value");
-
+function endOfTest() {
   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();
 
-async function test1() {
-  if (!await SpecialPowers.testPermission('pALLOW', ALLOW_ACTION, document)) {
+function test1() {
+  if (!SpecialPowers.testPermission('pALLOW', ALLOW_ACTION, document)) {
     dump('/**** allow not set ****/\n');
     setTimeout(test1, 0);
-  } else if (!await SpecialPowers.testPermission('pDENY', DENY_ACTION, document)) {
+  } else if (!SpecialPowers.testPermission('pDENY', DENY_ACTION, document)) {
     dump('/**** deny not set ****/\n');
     setTimeout(test1, 0);
-  } else if (!await SpecialPowers.testPermission('pPROMPT', PROMPT_ACTION, document)) {
+  } else if (!SpecialPowers.testPermission('pPROMPT', PROMPT_ACTION, document)) {
     dump('/**** prompt not set ****/\n');
     setTimeout(test1, 0);
-  } else if (!await SpecialPowers.testPermission('pREMOVE', ALLOW_ACTION, document)) {
+  } else if (!SpecialPowers.testPermission('pREMOVE', ALLOW_ACTION, document)) {
     dump('/**** remove not set ****/\n');
     setTimeout(test1, 0);
-  } else if (!await SpecialPowers.testPermission('pSESSION', ACCESS_SESSION, document)) {
+  } else if (!SpecialPowers.testPermission('pSESSION', ACCESS_SESSION, document)) {
     dump('/**** ACCESS_SESSION not set ****/\n');
     setTimeout(test1, 0);
   } else {
     test2();
   }
 }
 
-async function test2() {
-  ok(await SpecialPowers.testPermission('pUNKNOWN', UNKNOWN_ACTION, document), 'pUNKNOWN value should have UNKOWN permission');
+function test2() {
+  ok(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);
 }
 
-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');
+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');
 
   // 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);
 }
 
-async function test3b() {
-  ok(await SpecialPowers.testPermission('pPROMPT', DENY_ACTION, document), 'pPROMPT value should have DENY permission');
+function test3b() {
+  ok(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);
 }
 
-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');
+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');
   //this should reset all the permissions to before all the pushPermissions calls
   SpecialPowers.flushPermissions(test5);
 }
 
-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');
+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');
 
   SpecialPowers.removePermission("pPROMPT", document);
   SpecialPowers.removePermission("pALLOW", document);
   SpecialPowers.removePermission("pDENY", document);
   SpecialPowers.removePermission("pREMOVE", document);
   SpecialPowers.removePermission("pSESSION", document);
 
   setTimeout(test6, 0);
 }
 
-async function test6() {
-  if (!await SpecialPowers.testPermission('pALLOW', UNKNOWN_ACTION, document)) {
+function test6() {
+  if (!SpecialPowers.testPermission('pALLOW', UNKNOWN_ACTION, document)) {
     dump('/**** allow still set ****/\n');
     setTimeout(test6, 0);
-  } else if (!await SpecialPowers.testPermission('pDENY', UNKNOWN_ACTION, document)) {
+  } else if (!SpecialPowers.testPermission('pDENY', UNKNOWN_ACTION, document)) {
     dump('/**** deny still set ****/\n');
     setTimeout(test6, 0);
-  } else if (!await SpecialPowers.testPermission('pPROMPT', UNKNOWN_ACTION, document)) {
+  } else if (!SpecialPowers.testPermission('pPROMPT', UNKNOWN_ACTION, document)) {
     dump('/**** prompt still set ****/\n');
     setTimeout(test6, 0);
-  } else if (!await SpecialPowers.testPermission('pREMOVE', UNKNOWN_ACTION, document)) {
+  } else if (!SpecialPowers.testPermission('pREMOVE', UNKNOWN_ACTION, document)) {
     dump('/**** remove still set ****/\n');
     setTimeout(test6, 0);
-  } else if (!await SpecialPowers.testPermission('pSESSION', UNKNOWN_ACTION, document)) {
+  } else if (!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();
     }
   });
 }
 
-async function permissionPollingCheck() {
+function permissionPollingCheck() {
   var now = Number(Date.now());
   if (now < (start + PERIOD)) {
-    if (await SpecialPowers.testPermission('pEXPIRE', ALLOW_ACTION, document)) {
+    if (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 (await SpecialPowers.testPermission('pEXPIRE', ALLOW_ACTION, document)) {
+  if (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">
-async function starttest() {
+function starttest() {
   try {
-    await SpecialPowers.setBoolPref("test.bool", 1);
+    SpecialPowers.setBoolPref("test.bool", 1);
   } catch(e) {
-    await SpecialPowers.setBoolPref("test.bool", true);
+    SpecialPowers.setBoolPref("test.bool", true);
   }
   try {
-    await SpecialPowers.setIntPref("test.int", true);
+    SpecialPowers.setIntPref("test.int", true);
   } catch(e) {
-    await SpecialPowers.setIntPref("test.int", 1);
+    SpecialPowers.setIntPref("test.int", 1);
   }
-  await SpecialPowers.setCharPref("test.char", 'test');
-  await SpecialPowers.setBoolPref("test.cleanup", false);
+  SpecialPowers.setCharPref("test.char", 'test');
+  SpecialPowers.setBoolPref("test.cleanup", false);
 
   setTimeout(test1, 0, 0);
 }
 
 SimpleTest.waitForExplicitFinish();
 
 function test1(aCount) {
   if (aCount >= 20) {
deleted file mode 100644
--- a/testing/mochitest/tests/Harness_sanity/test_SpecialPowersSandbox.html
+++ /dev/null
@@ -1,96 +0,0 @@
-<!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>
deleted file mode 100644
--- a/testing/mochitest/tests/Harness_sanity/test_SpecialPowersSpawn.html
+++ /dev/null
@@ -1,59 +0,0 @@
-<!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,51 +6,49 @@
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 
 <div id="content" class="testbody">
   <script type="text/javascript">
     SimpleTest.waitForExplicitFinish();
 
-    (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'));
+    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");
 
-      script.addMessageListener("ImportTesting:IsModuleLoadedReply", handleFirstReply);
-      script.sendAsyncMessage("ImportTesting:IsModuleLoaded", testingResource);
+    const testingResource = "resource://testing-common/ImportTesting.jsm";
+    var script = SpecialPowers.loadChromeScript(SimpleTest.getTestFileURL('importtesting_chromescript.js'));
 
-      async function handleFirstReply(aMsg) {
-        ok(!aMsg, "ImportTesting.jsm shouldn't be loaded before we import it");
+    script.addMessageListener("ImportTesting:IsModuleLoadedReply", handleFirstReply);
+    script.sendAsyncMessage("ImportTesting:IsModuleLoaded", testingResource);
 
-        try {
-          await SpecialPowers.importInMainProcess(testingResource);
-        } catch (e) {
-          ok(false, "Unexpected exception when importing a valid resource: " + e.toString());
-        }
+    function handleFirstReply(aMsg) {
+      ok(!aMsg, "ImportTesting.jsm shouldn't be loaded before we import it");
 
-        script.removeMessageListener("ImportTesting:IsModuleLoadedReply", handleFirstReply);
-        script.addMessageListener("ImportTesting:IsModuleLoadedReply", handleSecondReply);
-        script.sendAsyncMessage("ImportTesting:IsModuleLoaded", testingResource);
+      try {
+        SpecialPowers.importInMainProcess(testingResource);
+      } catch (e) {
+        ok(false, "Unexpected exception when importing a valid resource: " + e.toString());
       }
 
-      function handleSecondReply(aMsg) {
-        script.removeMessageListener("ImportTesting:IsModuleLoadedReply", handleSecondReply);
+      script.removeMessageListener("ImportTesting:IsModuleLoadedReply", handleFirstReply);
+      script.addMessageListener("ImportTesting:IsModuleLoadedReply", handleSecondReply);
+      script.sendAsyncMessage("ImportTesting:IsModuleLoaded", testingResource);
+    }
 
-        ok(aMsg, "ImportTesting.jsm should be loaded after we import it");
+    function handleSecondReply(aMsg) {
+      script.removeMessageListener("ImportTesting:IsModuleLoadedReply", handleSecondReply);
 
-        SimpleTest.finish();
-      }
-    })();
+      ok(aMsg, "ImportTesting.jsm should be loaded after we import it");
+
+      SimpleTest.finish();
+    }
 
   </script>
 </div>
 </body>
 </html>
--- a/testing/mochitest/tests/SimpleTest/ChromePowers.js
+++ b/testing/mochitest/tests/SimpleTest/ChromePowers.js
@@ -1,120 +1,113 @@
 /* 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/. */
 
-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"; }
+function ChromePowers(window) {
+  this.window = Cu.getWeakReference(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);
+  if (typeof(window) == "ChromeWindow" && typeof(content.window) == "Window") {
+    this.DOMWindowUtils = bindDOMWindowUtils(content.window);
+    this.window = Cu.getWeakReference(content.window);
+  } else {
+    this.DOMWindowUtils = bindDOMWindowUtils(window);
   }
 
-  _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();
-  }
+  this.spObserver = new SpecialPowersObserverAPI();
+  this.spObserver._sendReply = this._sendReply.bind(this);
+  this.listeners = new Map();
 }
 
-if (window.parent.SpecialPowers && !window.SpecialPowers) {
-  window.SpecialPowers = window.parent.SpecialPowers;
+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" });
+};
+
+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 {
-  ChromeUtils.import("resource://specialpowers/SpecialPowersAPIParent.jsm", this);
-
-  window.SpecialPowers = new ChromePowers(window);
+  window.wrappedJSObject.SpecialPowers = new ChromePowers(window);
 }
 
--- 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 = async function() {
+    var afterCleanup = 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 = await SpecialPowers.registeredServiceWorkers();
+        let workers = 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 MozillaFileLogger(TestRunner._failureFile);
+        var failures = new SpecialPowersLogger(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);
 
-        async function cleanUpCrashDumpFiles() {
-            if (!await SpecialPowers.removeExpectedCrashDumpFiles(TestRunner._expectingProcessCrash)) {
+        function cleanUpCrashDumpFiles() {
+            if (!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 =
-                await SpecialPowers.findUnexpectedCrashDumpFiles();
+                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 (await SpecialPowers.removePendingCrashDumpFiles()) {
+                if (SpecialPowers.removePendingCrashDumpFiles()) {
                     TestRunner.structuredLogger.info("This test left pending crash dumps");
                 }
             }
         }
 
         function runNextTest() {
             if (TestRunner.currentTestURL != TestRunner.getLoadedTestURL()) {
                 TestRunner.structuredLogger.testStatus(TestRunner.currentTestURL,
@@ -635,22 +635,24 @@ 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(async function() {
-          await SpecialPowers.waitForCrashes(TestRunner._expectingProcessCrash);
-          await cleanUpCrashDumpFiles();
-          await SpecialPowers.flushPermissions();
-          await SpecialPowers.flushPrefEnv();
-          runNextTest();
+        SpecialPowers.executeAfterFlushingMessageQueue(function() {
+            SpecialPowers.waitForCrashes(TestRunner._expectingProcessCrash)
+                         .then(() => {
+                cleanUpCrashDumpFiles();
+                SpecialPowers.flushPermissions(function () {
+                    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 mfl = new MozillaFileLogger(params.logFile);
-  TestRunner.logger.addListener("mozLogger", fileLevel + "", mfl.logCallback);
+  var spl = new SpecialPowersLogger(params.logFile);
+  TestRunner.logger.addListener("mozLogger", fileLevel + "", spl.getLogCallback());
 }
 
 // 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,19 +3,16 @@
  * 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"
 );
 
@@ -23,32 +20,21 @@ this.specialpowers = class extends Exten
   onStartup() {
     let uri = Services.io.newURI("content/", null, this.extension.rootURI);
     resProto.setSubstitutionWithFlags(
       "specialpowers",
       uri,
       resProto.ALLOW_CONTENT_ACCESS
     );
 
-    // 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",
-      },
-    });
+    const { SpecialPowersObserver } = ChromeUtils.import(
+      "resource://specialpowers/SpecialPowersObserver.jsm"
+    );
+    this.observer = new SpecialPowersObserver();
+    this.observer.init();
   }
 
   onShutdown() {
-    ChromeUtils.unregisterWindowActor("SpecialPowers");
+    this.observer.uninit();
+    this.observer = null;
     resProto.setSubstitution("specialpowers", null);
   }
 };
rename from testing/mochitest/tests/SimpleTest/MozillaLogger.js
rename to testing/specialpowers/content/MozillaLogger.js
--- a/testing/mochitest/tests/SimpleTest/MozillaLogger.js
+++ b/testing/specialpowers/content/MozillaLogger.js
@@ -1,94 +1,137 @@
 /**
  * MozillaLogger, a base class logger that just logs to stdout.
  */
 
 "use strict";
 
+/* import-globals-from specialpowers.js */
+
+function MozillaLogger(aPath) {}
+
 function formatLogMessage(msg) {
   return msg.info.join(" ") + "\n";
 }
 
-function importJSM(jsm) {
-  if (typeof ChromeUtils === "object") {
-    return ChromeUtils.import(jsm);
-  }
-  /* globals SpecialPowers */
-  let obj = {};
-  SpecialPowers.Cu.import(jsm, obj);
-  return SpecialPowers.wrap(obj);
-}
+MozillaLogger.prototype = {
+  init(path) {},
 
-let CC = (typeof Components === "object"
-            ? Components
-            : SpecialPowers.wrap(SpecialPowers.Components)).Constructor;
-
-let ConverterOutputStream = CC("@mozilla.org/intl/converter-output-stream;1",
-                               "nsIConverterOutputStream", "init");
-
-class MozillaLogger {
-  get logCallback() {
-    return (msg) => {
-      this.log(formatLogMessage(msg));
+  getLogCallback() {
+    return function(msg) {
+      var data = formatLogMessage(msg);
+      dump(data);
     };
-  }
+  },
 
   log(msg) {
     dump(msg);
-  }
+  },
+
+  close() {},
+};
 
-  close() {}
+/**
+ * SpecialPowersLogger, inherits from MozillaLogger and utilizes SpecialPowers.
+ * intented to be used in content scripts to write to a file
+ */
+function SpecialPowersLogger(aPath) {
+  // Call the base constructor
+  MozillaLogger.call(this);
+  this.prototype = new MozillaLogger(aPath);
+  this.init(aPath);
 }
 
+SpecialPowersLogger.prototype = {
+  init(path) {
+    SpecialPowers.setLogFile(path);
+  },
+
+  getLogCallback() {
+    return function(msg) {
+      var data = formatLogMessage(msg);
+      SpecialPowers.log(data);
+
+      if (data.includes("SimpleTest FINISH")) {
+        SpecialPowers.closeLogFile();
+      }
+    };
+  },
+
+  log(msg) {
+    SpecialPowers.log(msg);
+  },
+
+  close() {
+    SpecialPowers.closeLogFile();
+  },
+};
 
 /**
  * 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 **/
-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));
+function MozillaFileLogger(aPath) {
+  // Call the base constructor
+  MozillaLogger.call(this);
+  this.prototype = new MozillaLogger(aPath);
+  this.init(aPath);
+}
 
-    this._converter = ConverterOutputStream(this._foStream, "UTF-8");
-  }
+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
+    );
 
-  get logCallback() {
-    return (msg) => {
-      if (this._converter) {
-        var data = formatLogMessage(msg);
-        this.log(data);
+    this._converter = Cc[
+      "@mozilla.org/intl/converter-output-stream;1"
+    ].createInstance(Ci.nsIConverterOutputStream);
+    this._converter.init(this._foStream, "UTF-8");
+  },
 
-        if (data.includes("SimpleTest FINISH")) {
-          this.close();
-        }
+  getLogCallback() {
+    return function(msg) {
+      var data = formatLogMessage(msg);
+      if (MozillaFileLogger._converter) {
+        this._converter.writeString(data);
+      }
+
+      if (data.includes("SimpleTest FINISH")) {
+        MozillaFileLogger.close();
       }
     };
-  }
+  },
 
   log(msg) {
     if (this._converter) {
       this._converter.writeString(msg);
     }
-  }
-
+  },
   close() {
-    this._converter.flush();
-    this._converter.close();
+    if (this._converter) {
+      this._converter.flush();
+      this._converter.close();
+    }
 
     this._foStream = null;
     this._converter = null;
     this._file = null;
-  }
-}
+  },
+};
 
 this.MozillaLogger = MozillaLogger;
+this.SpecialPowersLogger = SpecialPowersLogger;
 this.MozillaFileLogger = MozillaFileLogger;
rename from testing/specialpowers/content/SpecialPowersParent.jsm
rename to testing/specialpowers/content/SpecialPowersObserver.jsm
--- a/testing/specialpowers/content/SpecialPowersParent.jsm
+++ b/testing/specialpowers/content/SpecialPowersObserver.jsm
@@ -4,218 +4,376 @@
 "use strict";
 
 // Based on:
 // https://bugzilla.mozilla.org/show_bug.cgi?id=549539
 // https://bug549539.bugzilla.mozilla.org/attachment.cgi?id=429661
 // https://developer.mozilla.org/en/XPCOM/XPCOM_changes_in_Gecko_1.9.3
 // https://developer.mozilla.org/en/how_to_build_an_xpcom_component_in_javascript
 
-var EXPORTED_SYMBOLS = ["SpecialPowersParent"];
+/* import-globals-from SpecialPowersObserverAPI.js */
+
+var EXPORTED_SYMBOLS = ["SpecialPowersObserver"];
 
 var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
 
-const { SpecialPowersAPIParent } = ChromeUtils.import(
-  "resource://specialpowers/SpecialPowersAPIParent.jsm"
+const CHILD_SCRIPT_API = "resource://specialpowers/specialpowersFrameScript.js";
+const CHILD_LOGGER_SCRIPT = "resource://specialpowers/MozillaLogger.js";
+
+// Glue to add in the observer API to this object.  This allows us to share code with chrome tests
+Services.scriptloader.loadSubScript(
+  "resource://specialpowers/SpecialPowersObserverAPI.js"
 );
 
-class SpecialPowersParent extends SpecialPowersAPIParent {
-  constructor() {
-    super();
-    this._messageManager = Services.mm;
-    this._serviceWorkerListener = null;
+/* XPCOM gunk */
+function SpecialPowersObserver() {
+  this._isFrameScriptLoaded = false;
+  this._messageManager = Services.mm;
+  this._serviceWorkerListener = null;
+}
 
-    this._observer = this.observe.bind(this);
-
-    this.didDestroy = this.uninit.bind(this);
+SpecialPowersObserver.prototype = new SpecialPowersObserverAPI();
 
-    this._registerObservers = {
-      _self: this,
-      _topics: [],
-      _add(topic) {
-        if (!this._topics.includes(topic)) {
-          this._topics.push(topic);
-          Services.obs.addObserver(this, topic);
-        }
-      },
-      observe(aSubject, aTopic, aData) {
-        var msg = { aData };
-        switch (aTopic) {
-          case "perm-changed":
-            var permission = aSubject.QueryInterface(Ci.nsIPermission);
+SpecialPowersObserver.prototype.QueryInterface = ChromeUtils.generateQI([
+  Ci.nsIObserver,
+]);
 
-            // specialPowersAPI will consume this value, and it is used as a
-            // fake permission, but only type will be used.
-            //
-            // We need to ensure that it looks the same as a real permission,
-            // so we fake these properties.
-            msg.permission = {
-              principal: {
-                originAttributes: {},
-              },
-              type: permission.type,
-            };
-          default:
-            this._self.sendAsyncMessage("specialpowers-" + aTopic, msg);
-        }
-      },
-    };
+SpecialPowersObserver.prototype.observe = function(aSubject, aTopic, aData) {
+  switch (aTopic) {
+    case "chrome-document-global-created":
+      this._loadFrameScript();
+      break;
 
-    this.init();
-  }
-
-  observe(aSubject, aTopic, aData) {
-    if (aTopic == "http-on-modify-request") {
+    case "http-on-modify-request":
       if (aSubject instanceof Ci.nsIChannel) {
         let uri = aSubject.URI.spec;
-        this.sendAsyncMessage("specialpowers-http-notify-request", { uri });
+        this._sendAsyncMessage("specialpowers-http-notify-request", { uri });
       }
-    } else {
+      break;
+
+    default:
       this._observe(aSubject, aTopic, aData);
-    }
+      break;
   }
-
-  init() {
-    Services.obs.addObserver(this._observer, "http-on-modify-request");
+};
 
-    // We would like to check that tests don't leave service workers around
-    // after they finish, but that information lives in the parent process.
-    // Ideally, we'd be able to tell the child processes whenever service
-    // workers are registered or unregistered so they would know at all times,
-    // but service worker lifetimes are complicated enough to make that
-    // difficult. For the time being, let the child process know when a test
-    // registers a service worker so it can ask, synchronously, at the end if
-    // the service worker had unregister called on it.
-    let swm = Cc["@mozilla.org/serviceworkers/manager;1"].getService(
-      Ci.nsIServiceWorkerManager
+SpecialPowersObserver.prototype._loadFrameScript = function() {
+  if (!this._isFrameScriptLoaded) {
+    // Register for any messages our API needs us to handle
+    this._messageManager.addMessageListener("SPPrefService", this);
+    this._messageManager.addMessageListener("SPProcessCrashManagerWait", this);
+    this._messageManager.addMessageListener("SPProcessCrashService", this);
+    this._messageManager.addMessageListener("SPPingService", this);
+    this._messageManager.addMessageListener("SpecialPowers.Quit", this);
+    this._messageManager.addMessageListener("SpecialPowers.Focus", this);
+    this._messageManager.addMessageListener("SpecialPowers.CreateFiles", this);
+    this._messageManager.addMessageListener("SpecialPowers.RemoveFiles", this);
+    this._messageManager.addMessageListener("SPPermissionManager", this);
+    this._messageManager.addMessageListener("SPObserverService", this);
+    this._messageManager.addMessageListener("SPLoadChromeScript", this);
+    this._messageManager.addMessageListener("SPImportInMainProcess", this);
+    this._messageManager.addMessageListener("SPChromeScriptMessage", this);
+    this._messageManager.addMessageListener("SPQuotaManager", this);
+    this._messageManager.addMessageListener(
+      "SPSetTestPluginEnabledState",
+      this
+    );
+    this._messageManager.addMessageListener("SPLoadExtension", this);
+    this._messageManager.addMessageListener("SPStartupExtension", this);
+    this._messageManager.addMessageListener("SPUnloadExtension", this);
+    this._messageManager.addMessageListener("SPExtensionMessage", this);
+    this._messageManager.addMessageListener("SPCleanUpSTSData", this);
+    this._messageManager.addMessageListener(
+      "SPRequestDumpCoverageCounters",
+      this
+    );
+    this._messageManager.addMessageListener(
+      "SPRequestResetCoverageCounters",
+      this
+    );
+    this._messageManager.addMessageListener("SPCheckServiceWorkers", this);
+    this._messageManager.addMessageListener("SPRemoveAllServiceWorkers", this);
+    this._messageManager.addMessageListener(
+      "SPRemoveServiceWorkerDataForExampleDomain",
+      this
     );
-    let self = this;
-    this._serviceWorkerListener = {
-      onRegister() {
-        self.onRegister();
-      },
+
+    this._messageManager.loadFrameScript(CHILD_LOGGER_SCRIPT, true);
+    this._messageManager.loadFrameScript(CHILD_SCRIPT_API, true);
+    this._isFrameScriptLoaded = true;
+    this._createdFiles = null;
+  }
+};
+
+SpecialPowersObserver.prototype._sendAsyncMessage = function(msgname, msg) {
+  this._messageManager.broadcastAsyncMessage(msgname, msg);
+};
+
+SpecialPowersObserver.prototype._receiveMessage = function(aMessage) {
+  return this._receiveMessageAPI(aMessage);
+};
+
+SpecialPowersObserver.prototype.init = function() {
+  var obs = Services.obs;
+  obs.addObserver(this, "chrome-document-global-created");
+
+  // Register special testing modules.
+  var testsURI = Services.dirsvc.get("ProfD", Ci.nsIFile);
+  testsURI.append("tests.manifest");
+  var manifestFile = Services.io
+    .newFileURI(testsURI)
+    .QueryInterface(Ci.nsIFileURL).file;
 
-      onUnregister() {
-        // no-op
-      },
-    };
-    swm.addListener(this._serviceWorkerListener);
-  }
+  Components.manager
+    .QueryInterface(Ci.nsIComponentRegistrar)
+    .autoRegister(manifestFile);
+
+  obs.addObserver(this, "http-on-modify-request");
+
+  // We would like to check that tests don't leave service workers around
+  // after they finish, but that information lives in the parent process.
+  // Ideally, we'd be able to tell the child processes whenever service
+  // workers are registered or unregistered so they would know at all times,
+  // but service worker lifetimes are complicated enough to make that
+  // difficult. For the time being, let the child process know when a test
+  // registers a service worker so it can ask, synchronously, at the end if
+  // the service worker had unregister called on it.
+  let swm = Cc["@mozilla.org/serviceworkers/manager;1"].getService(
+    Ci.nsIServiceWorkerManager
+  );
+  let self = this;
+  this._serviceWorkerListener = {
+    onRegister() {
+      self.onRegister();
+    },
+
+    onUnregister() {
+      // no-op
+    },
+  };
+  swm.addListener(this._serviceWorkerListener);
+
+  this._loadFrameScript();
+};
 
-  uninit() {
-    var obs = Services.obs;
-    obs.removeObserver(this._observer, "http-on-modify-request");
-    this._registerObservers._topics.splice(0).forEach(element => {
-      obs.removeObserver(this._registerObservers, element);
-    });
-    this._removeProcessCrashObservers();
+SpecialPowersObserver.prototype.uninit = function() {
+  var obs = Services.obs;
+  obs.removeObserver(this, "chrome-document-global-created");
+  obs.removeObserver(this, "http-on-modify-request");
+  this._registerObservers._topics.forEach(element => {
+    obs.removeObserver(this._registerObservers, element);
+  });
+  this._removeProcessCrashObservers();
+
+  let swm = Cc["@mozilla.org/serviceworkers/manager;1"].getService(
+    Ci.nsIServiceWorkerManager
+  );
+  swm.removeListener(this._serviceWorkerListener);
 
-    let swm = Cc["@mozilla.org/serviceworkers/manager;1"].getService(
-      Ci.nsIServiceWorkerManager
+  if (this._isFrameScriptLoaded) {
+    this._messageManager.removeMessageListener("SPPrefService", this);
+    this._messageManager.removeMessageListener(
+      "SPProcessCrashManagerWait",
+      this
+    );
+    this._messageManager.removeMessageListener("SPProcessCrashService", this);
+    this._messageManager.removeMessageListener("SPPingService", this);
+    this._messageManager.removeMessageListener("SpecialPowers.Quit", this);
+    this._messageManager.removeMessageListener("SpecialPowers.Focus", this);
+    this._messageManager.removeMessageListener(
+      "SpecialPowers.CreateFiles",
+      this
+    );
+    this._messageManager.removeMessageListener(
+      "SpecialPowers.RemoveFiles",
+      this
+    );
+    this._messageManager.removeMessageListener("SPPermissionManager", this);
+    this._messageManager.removeMessageListener("SPObserverService", this);
+    this._messageManager.removeMessageListener("SPLoadChromeScript", this);
+    this._messageManager.removeMessageListener("SPImportInMainProcess", this);
+    this._messageManager.removeMessageListener("SPChromeScriptMessage", this);
+    this._messageManager.removeMessageListener("SPQuotaManager", this);
+    this._messageManager.removeMessageListener(
+      "SPSetTestPluginEnabledState",
+      this
     );
-    swm.removeListener(this._serviceWorkerListener);
+    this._messageManager.removeMessageListener("SPLoadExtension", this);
+    this._messageManager.removeMessageListener("SPStartupExtension", this);
+    this._messageManager.removeMessageListener("SPUnloadExtension", this);
+    this._messageManager.removeMessageListener("SPExtensionMessage", this);
+    this._messageManager.removeMessageListener("SPCleanUpSTSData", this);
+    this._messageManager.removeMessageListener(
+      "SPRequestDumpCoverageCounters",
+      this
+    );
+    this._messageManager.removeMessageListener(
+      "SPRequestResetCoverageCounters",
+      this
+    );
+    this._messageManager.removeMessageListener("SPCheckServiceWorkers", this);
+    this._messageManager.removeMessageListener(
+      "SPRemoveAllServiceWorkers",
+      this
+    );
+    this._messageManager.removeMessageListener(
+      "SPRemoveServiceWorkerDataForExampleDomain",
+      this
+    );
+
+    this._messageManager.removeDelayedFrameScript(CHILD_LOGGER_SCRIPT);
+    this._messageManager.removeDelayedFrameScript(CHILD_SCRIPT_API);
+    this._isFrameScriptLoaded = false;
+  }
+};
+
+SpecialPowersObserver.prototype._addProcessCrashObservers = function() {
+  if (this._processCrashObserversRegistered) {
+    return;
   }
 
-  _addProcessCrashObservers() {
-    if (this._processCrashObserversRegistered) {
-      return;
-    }
+  Services.obs.addObserver(this, "plugin-crashed");
+  Services.obs.addObserver(this, "ipc:content-shutdown");
+  this._processCrashObserversRegistered = true;
+};
 
-    Services.obs.addObserver(this._observer, "plugin-crashed");
-    Services.obs.addObserver(this._observer, "ipc:content-shutdown");
-    this._processCrashObserversRegistered = true;
-  }
-
-  _removeProcessCrashObservers() {
-    if (!this._processCrashObserversRegistered) {
-      return;
-    }
-
-    Services.obs.removeObserver(this._observer, "plugin-crashed");
-    Services.obs.removeObserver(this._observer, "ipc:content-shutdown");
-    this._processCrashObserversRegistered = false;
+SpecialPowersObserver.prototype._removeProcessCrashObservers = function() {
+  if (!this._processCrashObserversRegistered) {
+    return;
   }
 
-  /**
-   * messageManager callback function
-   * This will get requests from our API in the window and process them in chrome for it
-   **/
-  receiveMessage(aMessage) {
-    switch (aMessage.name) {
-      case "Ping":
-        return undefined;
-      case "SpecialPowers.Quit":
-        Services.startup.quit(Ci.nsIAppStartup.eForceQuit);
-        break;
-      case "SpecialPowers.Focus":
-        this.manager.rootFrameLoader.ownerElement.focus();
-        break;
-      case "SpecialPowers.CreateFiles":
-        return (async () => {
-          let filePaths = [];
-          if (!this._createdFiles) {
-            this._createdFiles = [];
-          }
-          let createdFiles = this._createdFiles;
+  Services.obs.removeObserver(this, "plugin-crashed");
+  Services.obs.removeObserver(this, "ipc:content-shutdown");
+  this._processCrashObserversRegistered = false;
+};
+
+SpecialPowersObserver.prototype._registerObservers = {
+  _self: null,
+  _topics: [],
+  _add(topic) {
+    if (!this._topics.includes(topic)) {
+      this._topics.push(topic);
+      Services.obs.addObserver(this, topic);
+    }
+  },
+  observe(aSubject, aTopic, aData) {
+    var msg = { aData };
+    switch (aTopic) {
+      case "perm-changed":
+        var permission = aSubject.QueryInterface(Ci.nsIPermission);
+
+        // specialPowersAPI will consume this value, and it is used as a
+        // fake permission, but only type will be used.
+        //
+        // We need to ensure that it looks the same as a real permission,
+        // so we fake these properties.
+        msg.permission = {
+          principal: {
+            originAttributes: {},
+          },
+          type: permission.type,
+        };
+      default:
+        this._self._sendAsyncMessage("specialpowers-" + aTopic, msg);
+    }
+  },
+};
 
-          let promises = [];
-          aMessage.data.forEach(function(request) {
-            const filePerms = 0o666;
-            let testFile = Services.dirsvc.get("ProfD", Ci.nsIFile);
-            if (request.name) {
-              testFile.appendRelativePath(request.name);
-            } else {
-              testFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, filePerms);
-            }
-            let outStream = Cc[
-              "@mozilla.org/network/file-output-stream;1"
-            ].createInstance(Ci.nsIFileOutputStream);
-            outStream.init(
-              testFile,
-              0x02 | 0x08 | 0x20, // PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE
-              filePerms,
-              0
-            );
-            if (request.data) {
-              outStream.write(request.data, request.data.length);
-            }
-            outStream.close();
-            promises.push(
-              File.createFromFileName(testFile.path, request.options).then(
-                function(file) {
-                  filePaths.push(file);
-                }
-              )
-            );
-            createdFiles.push(testFile);
-          });
-
-          await Promise.all(promises);
-          return filePaths;
-        })().catch(e => {
-          Cu.reportError(e);
-          return Promise.reject(String(e));
+/**
+ * messageManager callback function
+ * This will get requests from our API in the window and process them in chrome for it
+ **/
+SpecialPowersObserver.prototype.receiveMessage = function(aMessage) {
+  switch (aMessage.name) {
+    case "SPPingService":
+      if (aMessage.json.op == "ping") {
+        aMessage.target.frameLoader.messageManager.sendAsyncMessage(
+          "SPPingService",
+          { op: "pong" }
+        );
+      }
+      break;
+    case "SpecialPowers.Quit":
+      Services.startup.quit(Ci.nsIAppStartup.eForceQuit);
+      break;
+    case "SpecialPowers.Focus":
+      aMessage.target.focus();
+      break;
+    case "SpecialPowers.CreateFiles":
+      let filePaths = [];
+      if (!this._createdFiles) {
+        this._createdFiles = [];
+      }
+      let createdFiles = this._createdFiles;
+      try {
+        let promises = [];
+        aMessage.data.forEach(function(request) {
+          const filePerms = 0o666;
+          let testFile = Services.dirsvc.get("ProfD", Ci.nsIFile);
+          if (request.name) {
+            testFile.appendRelativePath(request.name);
+          } else {
+            testFile.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, filePerms);
+          }
+          let outStream = Cc[
+            "@mozilla.org/network/file-output-stream;1"
+          ].createInstance(Ci.nsIFileOutputStream);
+          outStream.init(
+            testFile,
+            0x02 | 0x08 | 0x20, // PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE
+            filePerms,
+            0
+          );
+          if (request.data) {
+            outStream.write(request.data, request.data.length);
+          }
+          outStream.close();
+          promises.push(
+            File.createFromFileName(testFile.path, request.options).then(
+              function(file) {
+                filePaths.push(file);
+              }
+            )
+          );
+          createdFiles.push(testFile);
         });
 
-      case "SpecialPowers.RemoveFiles":
-        if (this._createdFiles) {
-          this._createdFiles.forEach(function(testFile) {
-            try {
-              testFile.remove(false);
-            } catch (e) {}
-          });
-          this._createdFiles = null;
-        }
-        break;
+        Promise.all(promises).then(
+          function() {
+            aMessage.target.frameLoader.messageManager.sendAsyncMessage(
+              "SpecialPowers.FilesCreated",
+              filePaths
+            );
+          },
+          function(e) {
+            aMessage.target.frameLoader.messageManager.sendAsyncMessage(
+              "SpecialPowers.FilesError",
+              e.toString()
+            );
+          }
+        );
+      } catch (e) {
+        aMessage.target.frameLoader.messageManager.sendAsyncMessage(
+          "SpecialPowers.FilesError",
+          e.toString()
+        );
+      }
 
-      case "Wakeup":
-        break;
+      break;
+    case "SpecialPowers.RemoveFiles":
+      if (this._createdFiles) {
+        this._createdFiles.forEach(function(testFile) {
+          try {
+            testFile.remove(false);
+          } catch (e) {}
+        });
+        this._createdFiles = null;
+      }
+      break;
+    default:
+      return this._receiveMessage(aMessage);
+  }
+  return undefined;
+};
 
-      default:
-        return super.receiveMessage(aMessage);
-    }
-    return undefined;
-  }
-
-  onRegister() {
-    this.sendAsyncMessage("SPServiceWorkerRegistered", { registered: true });
-  }
-}
+SpecialPowersObserver.prototype.onRegister = function() {
+  this._sendAsyncMessage("SPServiceWorkerRegistered", { registered: true });
+};
rename from testing/specialpowers/content/SpecialPowersAPIParent.jsm
rename to testing/specialpowers/content/SpecialPowersObserverAPI.js
--- a/testing/specialpowers/content/SpecialPowersAPIParent.jsm
+++ b/testing/specialpowers/content/SpecialPowersObserverAPI.js
@@ -1,34 +1,45 @@
 /* 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/. */
 
 "use strict";
 
-var EXPORTED_SYMBOLS = ["SpecialPowersAPIParent", "SpecialPowersError"];
-
 var { XPCOMUtils } = ChromeUtils.import(
   "resource://gre/modules/XPCOMUtils.jsm"
 );
+var { NetUtil } = ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
 var { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
 
 XPCOMUtils.defineLazyModuleGetters(this, {
   ExtensionData: "resource://gre/modules/Extension.jsm",
   ExtensionTestCommon: "resource://testing-common/ExtensionTestCommon.jsm",
   PerTestCoverageUtils: "resource://testing-common/PerTestCoverageUtils.jsm",
   ServiceWorkerCleanUp: "resource://gre/modules/ServiceWorkerCleanUp.jsm",
-  SpecialPowersSandbox: "resource://specialpowers/SpecialPowersSandbox.jsm",
 });
 
-class SpecialPowersError extends Error {
-  get name() {
-    return "SpecialPowersError";
-  }
-}
+this.SpecialPowersError = function(aMsg) {
+  Error.call(this);
+  // let {stack} = new Error();
+  this.message = aMsg;
+  this.name = "SpecialPowersError";
+};
+SpecialPowersError.prototype = Object.create(Error.prototype);
+
+SpecialPowersError.prototype.toString = function() {
+  return `${this.name}: ${this.message}`;
+};
+
+this.SpecialPowersObserverAPI = function SpecialPowersObserverAPI() {
+  this._crashDumpDir = null;
+  this._processCrashObserversRegistered = false;
+  this._chromeScriptListeners = [];
+  this._extensions = new Map();
+};
 
 function parseKeyValuePairs(text) {
   var lines = text.split("\n");
   var data = {};
   for (let i = 0; i < lines.length; i++) {
     if (lines[i] == "") {
       continue;
     }
@@ -80,59 +91,17 @@ function getTestPlugin(pluginName) {
     if (tag.name == name) {
       return tag;
     }
   }
 
   return null;
 }
 
-const PREF_TYPES = {
-  [Ci.nsIPrefBranch.PREF_INVALID]: "INVALID",
-  [Ci.nsIPrefBranch.PREF_INT]: "INT",
-  [Ci.nsIPrefBranch.PREF_BOOL]: "BOOL",
-  [Ci.nsIPrefBranch.PREF_STRING]: "CHAR",
-  number: "INT",
-  boolean: "BOOL",
-  string: "CHAR",
-};
-
-// We share a single preference environment stack between all
-// SpecialPowers instances, across all processes.
-let prefUndoStack = [];
-let inPrefEnvOp = false;
-
-function doPrefEnvOp(fn) {
-  if (inPrefEnvOp) {
-    throw new Error(
-      "Reentrant preference environment operations not supported"
-    );
-  }
-  inPrefEnvOp = true;
-  try {
-    return fn();
-  } finally {
-    inPrefEnvOp = false;
-  }
-}
-
-// Supplies the unique IDs for tasks created by SpecialPowers.spawn(),
-// used to bounce assertion messages back down to the correct child.
-let nextTaskID = 1;
-
-class SpecialPowersAPIParent extends JSWindowActorParent {
-  constructor() {
-    super();
-    this._crashDumpDir = null;
-    this._processCrashObserversRegistered = false;
-    this._chromeScriptListeners = [];
-    this._extensions = new Map();
-    this._taskActors = new Map();
-  }
-
+SpecialPowersObserverAPI.prototype = {
   _observe(aSubject, aTopic, aData) {
     function addDumpIDToMessage(propertyName) {
       try {
         var id = aSubject.getPropertyAsAString(propertyName);
       } catch (ex) {
         id = null;
       }
       if (id) {
@@ -164,46 +133,46 @@ class SpecialPowersAPIParent extends JSW
         } else {
           // ipc:content-shutdown
           if (!aSubject.hasKey("abnormal")) {
             return; // This is a normal shutdown, ignore it
           }
 
           addDumpIDToMessage("dumpID");
         }
-        this.sendAsyncMessage("SPProcessCrashService", message);
+        this._sendAsyncMessage("SPProcessCrashService", message);
         break;
     }
-  }
+  },
 
   _getCrashDumpDir() {
     if (!this._crashDumpDir) {
       this._crashDumpDir = Services.dirsvc.get("ProfD", Ci.nsIFile);
       this._crashDumpDir.append("minidumps");
     }
     return this._crashDumpDir;
-  }
+  },
 
   _getPendingCrashDumpDir() {
     if (!this._pendingCrashDumpDir) {
       this._pendingCrashDumpDir = Services.dirsvc.get("UAppData", Ci.nsIFile);
       this._pendingCrashDumpDir.append("Crash Reports");
       this._pendingCrashDumpDir.append("pending");
     }
     return this._pendingCrashDumpDir;
-  }
+  },
 
   _getExtraData(dumpId) {
     let extraFile = this._getCrashDumpDir().clone();
     extraFile.append(dumpId + ".extra");
     if (!extraFile.exists()) {
       return null;
     }
     return parseKeyValuePairsFromFile(extraFile);
-  }
+  },
 
   _deleteCrashDumpFiles(aFilenames) {
     var crashDumpDir = this._getCrashDumpDir();
     if (!crashDumpDir.exists()) {
       return false;
     }
 
     var success = aFilenames.length != 0;
@@ -212,17 +181,17 @@ class SpecialPowersAPIParent extends JSW
       file.append(crashFilename);
       if (file.exists()) {
         file.remove(false);
       } else {
         success = false;
       }
     });
     return success;
-  }
+  },
 
   _findCrashDumpFiles(aToIgnore) {
     var crashDumpDir = this._getCrashDumpDir();
     var entries = crashDumpDir.exists() && crashDumpDir.directoryEntries;
     if (!entries) {
       return [];
     }
 
@@ -230,37 +199,87 @@ class SpecialPowersAPIParent extends JSW
     while (entries.hasMoreElements()) {
       var file = entries.nextFile;
       var path = String(file.path);
       if (path.match(/\.(dmp|extra)$/) && !aToIgnore[path]) {
         crashDumpFiles.push(path);
       }
     }
     return crashDumpFiles.concat();
-  }
+  },
 
   _deletePendingCrashDumpFiles() {
     var crashDumpDir = this._getPendingCrashDumpDir();
     var removed = false;
     if (crashDumpDir.exists()) {
       let entries = crashDumpDir.directoryEntries;
       while (entries.hasMoreElements()) {
         let file = entries.nextFile;
         if (file.isFile()) {
           file.remove(false);
           removed = true;
         }
       }
     }
     return removed;
-  }
+  },
 
   _getURI(url) {
     return Services.io.newURI(url);
-  }
+  },
+
+  _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 SpecialPowersError(
+        "Error while executing chrome script '" +
+          aUrl +
+          "':\n" +
+          "The script doesn't exists. Ensure you have registered it in " +
+          "'support-files' in your mochitest.ini."
+      );
+    }
+
+    return output;
+  },
+
+  _sendReply(aMessage, aReplyName, aReplyMsg) {
+    let mm = aMessage.target.frameLoader.messageManager;
+    mm.sendAsyncMessage(aReplyName, aReplyMsg);
+  },
+
   _notifyCategoryAndObservers(subject, topic, data) {
     const serviceMarker = "service,";
 
     // First create observers from the category manager.
 
     let observers = [];
 
     for (let { value: contractID } of Services.catMan.enumerateCategory(
@@ -290,188 +309,28 @@ class SpecialPowersAPIParent extends JSW
       }
     }
 
     observers.forEach(function(observer) {
       try {
         observer.observe(subject, topic, data);
       } catch (e) {}
     });
-  }
-
-  /*
-    Iterate through one atomic set of pref actions and perform sets/clears as appropriate.
-    All actions performed must modify the relevant pref.
-  */
-  _applyPrefs(actions) {
-    for (let pref of actions) {
-      if (pref.action == "set") {
-        this._setPref(pref.name, pref.type, pref.value, pref.iid);
-      } else if (pref.action == "clear") {
-        Services.prefs.clearUserPref(pref.name);
-      }
-    }
-  }
-
-  /**
-   * Take in a list of pref changes to make, pushes their current values
-   * onto the restore stack, and makes the changes.  When the test
-   * finishes, these changes are reverted.
-   *
-   * |inPrefs| must be an object with up to two properties: "set" and "clear".
-   * pushPrefEnv will set prefs as indicated in |inPrefs.set| and will unset
-   * the prefs indicated in |inPrefs.clear|.
-   *
-   * For example, you might pass |inPrefs| as:
-   *
-   *  inPrefs = {'set': [['foo.bar', 2], ['magic.pref', 'baz']],
-   *             'clear': [['clear.this'], ['also.this']] };
-   *
-   * Notice that |set| and |clear| are both an array of arrays.  In |set|, each
-   * of the inner arrays must have the form [pref_name, value] or [pref_name,
-   * value, iid].  (The latter form is used for prefs with "complex" values.)
-   *
-   * In |clear|, each inner array should have the form [pref_name].
-   *
-   * If you set the same pref more than once (or both set and clear a pref),
-   * the behavior of this method is undefined.
-   */
-  pushPrefEnv(inPrefs) {
-    return doPrefEnvOp(() => {
-      let pendingActions = [];
-      let cleanupActions = [];
-
-      for (let [action, prefs] of Object.entries(inPrefs)) {
-        for (let pref of prefs) {
-          let name = pref[0];
-          let value = null;
-          let iid = null;
-          let type = PREF_TYPES[Services.prefs.getPrefType(name)];
-          let originalValue = null;
-
-          if (pref.length == 3) {
-            value = pref[1];
-            iid = pref[2];
-          } else if (pref.length == 2) {
-            value = pref[1];
-          }
-
-          /* If pref is not found or invalid it doesn't exist. */
-          if (type !== "INVALID") {
-            if (
-              (Services.prefs.prefHasUserValue(name) && action == "clear") ||
-              action == "set"
-            ) {
-              originalValue = this._getPref(name, type);
-            }
-          } else if (action == "set") {
-            /* name doesn't exist, so 'clear' is pointless */
-            if (iid) {
-              type = "COMPLEX";
-            }
-          }
-
-          if (type === "INVALID") {
-            type = PREF_TYPES[typeof value];
-          }
-          if (type === "INVALID") {
-            throw new Error("Unexpected preference type");
-          }
-
-          pendingActions.push({ action, type, name, value, iid });
-
-          /* Push original preference value or clear into cleanup array */
-          var cleanupTodo = { type, name, value: originalValue, iid };
-          if (originalValue == null) {
-            cleanupTodo.action = "clear";
-          } else {
-            cleanupTodo.action = "set";
-          }
-          cleanupActions.push(cleanupTodo);
-        }
-      }
-
-      prefUndoStack.push(cleanupActions);
-      this._applyPrefs(pendingActions);
-    });
-  }
-
-  async popPrefEnv() {
-    return doPrefEnvOp(() => {
-      let env = prefUndoStack.pop();
-      if (env) {
-        this._applyPrefs(env);
-        return true;
-      }
-      return false;
-    });
-  }
-
-  flushPrefEnv() {
-    while (prefUndoStack.length) {
-      this.popPrefEnv();
-    }
-  }
-
-  _setPref(name, type, value, iid) {
-    switch (type) {
-      case "BOOL":
-        return Services.prefs.setBoolPref(name, value);
-      case "INT":
-        return Services.prefs.setIntPref(name, value);
-      case "CHAR":
-        return Services.prefs.setCharPref(name, value);
-      case "COMPLEX":
-        return Services.prefs.setComplexValue(name, iid, value);
-    }
-    throw new Error(`Unexpected preference type: ${type}`);
-  }
-
-  _getPref(name, type, defaultValue, iid) {
-    switch (type) {
-      case "BOOL":
-        if (defaultValue !== undefined) {
-          return Services.prefs.getBoolPref(name, defaultValue);
-        }
-        return Services.prefs.getBoolPref(name);
-      case "INT":
-        if (defaultValue !== undefined) {
-          return Services.prefs.getIntPref(name, defaultValue);
-        }
-        return Services.prefs.getIntPref(name);
-      case "CHAR":
-        if (defaultValue !== undefined) {
-          return Services.prefs.getCharPref(name, defaultValue);
-        }
-        return Services.prefs.getCharPref(name);
-