Backed out 43 changesets (bug 1561724, bug 1561150, bug 1541557, bug 1561122, bug 1560400, bug 1558298, bug 1561999, bug 1532795, bug 1561061) for multiple failures. CLOSED TREE
authorDorel Luca <dluca@mozilla.com>
Tue, 02 Jul 2019 10:51:56 +0300
changeset 543755 f9bf5e4b0b4f48302d560afb5307d57fbbab7c73
parent 543754 1a4fe24a016c5530f94892c707a8a08343a4695a
child 543759 a15e5a44b7ed0a285c92839055efd83671b49552
push id2131
push userffxbld-merge
push dateMon, 26 Aug 2019 18:30:20 +0000
treeherdermozilla-release@b19ffb3ca153 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
bugs1561724, 1561150, 1541557, 1561122, 1560400, 1558298, 1561999, 1532795, 1561061
milestone69.0a1
backs out1a4fe24a016c5530f94892c707a8a08343a4695a
6fc41e51bcee6e1854bcacbf7356f30a9f47463b
d916d89a5c90af020cd5bc06f744f1ac8814ef1c
757d285aafdd9df1d939edb8265948277b6bfe79
a7eab5ca061da40d0a4e882064bc90ec257576ac
d972bbdfe03933e00f4a7ecbea2dc1f8ac85fd12
8802daac6779f4707edb59d7d578735b40870716
92c01418b96fb0da7920eca573e944116accaeb6
fa5e186e16357ff48a58b2035e4432f8688b00fc
aa2bee0b18c3a1ee1416d0fbf625e0ccdb74bf4e
adf832af8e4878c77b7c100a44004bf7730fd126
72630a7c6e6742388107a77afc26c9c191722705
c35aff2a933694722bc4ef64a3b53e787c662d6a
19e0edc9207746c7987b98f3c7787b7c363d651d
0b3e2164f1283b639782b41b7fd640986d3feca5
43211ebfe7384909dd95cb44196eea4359156d2a
c2d0956f41d82e76c682f829807e818863cd802a
bf0f0e95c61c2a57d176699f05e71e967a13d3e8
84633034590f2d1a4c336f9653e6c542f3a09039
d5415970da5fd83eb870b397b8db7fdf6c57ad23
119caddcb0660754f9bce5e7153bfc92dc211d57
fbbe113aeef2f44741248ef15be66562e66adf6c
8a3d311c7fac75b5902f2fa9dc651137a499fc18
1471732eca80f6fa44ae50b39c0317965cd29671
46ff845a7b0cdabf640bb2e3c783735ab68b7cd1
c2697f04d38cf0b01b1f3e227910ab5890926a33
75ebd6fce136ab3bd0e591c2b8b2d06d3b5bf923
189dc8a359815e059a4a217f788d183260bb2bfe
b4ed40bea2698802ef562a0931c0b560737fb89d
158a4000c44b9b17a7935340db79431d544fb556
61fa2745733f3631488a3ecccc144823683b7b6d
d2ee912c518913c33e4c63b5a5eddf6e10b0c9f4
7a0aab00327b089c82476bea30ea17373e0a1103
fddf2808fedf7ca16097af3b5c7c46ed44fc2053
0f6b382f06261055566d91b84866a51a80e474bd
6ccaa25367f28c27c27746593db3bd5cfaed5f57
d27574cfbb0ed580612d7f468f804aa7819fed21
162bc1fc273035ca47ad28947f6b339d2101c29b
f94500dd11e38ba34310216d958943d6ef07ce96
fb67ac962bc5bcaaed595f4232b33497f2b38bf7
c634099abb9dc0196877e626c0df0c168d78876f
8d4419c439e1d92209f237e0d9f1e6c19fa71872
d8b7ed5e149f4d4724c3999b3b0fed9bfd3f8f96
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 43 changesets (bug 1561724, bug 1561150, bug 1541557, bug 1561122, bug 1560400, bug 1558298, bug 1561999, bug 1532795, bug 1561061) for multiple failures. CLOSED TREE Backed out changeset 1a4fe24a016c (bug 1541557) Backed out changeset 6fc41e51bcee (bug 1561061) Backed out changeset d916d89a5c90 (bug 1561122) Backed out changeset 757d285aafdd (bug 1560400) Backed out changeset a7eab5ca061d (bug 1541557) Backed out changeset d972bbdfe039 (bug 1541557) Backed out changeset 8802daac6779 (bug 1541557) Backed out changeset 92c01418b96f (bug 1561150) Backed out changeset fa5e186e1635 (bug 1561061) Backed out changeset aa2bee0b18c3 (bug 1560400) Backed out changeset adf832af8e48 (bug 1561150) Backed out changeset 72630a7c6e67 (bug 1561999) Backed out changeset c35aff2a9336 (bug 1561724) Backed out changeset 19e0edc92077 (bug 1561150) Backed out changeset 0b3e2164f128 (bug 1561150) Backed out changeset 43211ebfe738 (bug 1561122) Backed out changeset c2d0956f41d8 (bug 1561061) Backed out changeset bf0f0e95c61c (bug 1560400) Backed out changeset 84633034590f (bug 1560400) Backed out changeset d5415970da5f (bug 1532795) Backed out changeset 119caddcb066 (bug 1532795) Backed out changeset fbbe113aeef2 (bug 1532795) Backed out changeset 8a3d311c7fac (bug 1532795) Backed out changeset 1471732eca80 (bug 1532795) Backed out changeset 46ff845a7b0c (bug 1541557) Backed out changeset c2697f04d38c (bug 1541557) Backed out changeset 75ebd6fce136 (bug 1541557) Backed out changeset 189dc8a35981 (bug 1541557) Backed out changeset b4ed40bea269 (bug 1541557) Backed out changeset 158a4000c44b (bug 1541557) Backed out changeset 61fa2745733f (bug 1541557) Backed out changeset d2ee912c5189 (bug 1558298) Backed out changeset 7a0aab00327b (bug 1558298) Backed out changeset fddf2808fedf (bug 1558298) Backed out changeset 0f6b382f0626 (bug 1558298) Backed out changeset 6ccaa25367f2 (bug 1558298) Backed out changeset d27574cfbb0e (bug 1558298) Backed out changeset 162bc1fc2730 (bug 1558298) Backed out changeset f94500dd11e3 (bug 1558298) Backed out changeset fb67ac962bc5 (bug 1558298) Backed out changeset c634099abb9d (bug 1558298) Backed out changeset 8d4419c439e1 (bug 1558298) Backed out changeset d8b7ed5e149f (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/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
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/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_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
@@ -156,21 +156,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");
     SimpleTest.executeSoon(aCallback);
--- 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
@@ -169,71 +169,78 @@ function checkMenuEntries(expectedValues
   let expectedLength = isFormAutofillResult ? expectedValues.length + 1 : expectedValues.length;
 
   is(actualValues.length, expectedLength, " Checking length of expected menu");
   for (let i = 0; i < expectedValues.length; i++) {
     is(actualValues[i], expectedValues[i], " Checking menu entry #" + i);
   }
 }
 
-function invokeAsyncChromeTask(message, 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");
+  let {canTest} = await invokeAsyncChromeTask("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),
   };
@@ -290,23 +297,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() {
     defaultTextColor = window.getComputedStyle(document.querySelector("input"))
       .getPropertyValue("color");
--- a/browser/extensions/formautofill/test/mochitest/formautofill_parent_utils.js
+++ b/browser/extensions/formautofill/test/mochitest/formautofill_parent_utils.js
@@ -8,18 +8,16 @@ const {Services} = ChromeUtils.import("r
 const {FormAutofill} = ChromeUtils.import("resource://formautofill/FormAutofill.jsm");
 const {FormAutofillUtils} = ChromeUtils.import("resource://formautofill/FormAutofillUtils.jsm");
 const {OSKeyStoreTestUtils} = ChromeUtils.import("resource://testing-common/OSKeyStoreTestUtils.jsm");
 
 let {formAutofillStorage} = ChromeUtils.import("resource://formautofill/FormAutofillStorage.jsm");
 
 const {ADDRESSES_COLLECTION_NAME, CREDITCARDS_COLLECTION_NAME} = FormAutofillUtils;
 
-let destroyed = false;
-
 var ParentUtils = {
   async _getRecords(collectionName) {
     return new Promise(resolve => {
       Services.cpmm.addMessageListener("FormAutofill:Records", function getResult({data}) {
         Services.cpmm.removeMessageListener("FormAutofill:Records", getResult);
         resolve(data);
       });
       Services.cpmm.sendAsyncMessage("FormAutofill:GetRecords", {searchString: "", collectionName});
@@ -31,34 +29,30 @@ 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;
         }
@@ -84,43 +78,46 @@ var ParentUtils = {
           times = msgData.guids.length;
           Services.cpmm.sendAsyncMessage("FormAutofill:RemoveCreditCards", msgData);
           break;
         }
       }
     }
 
     await this._storageChangeObserved({type, times, topic});
+    sendAsyncMessage(contentMsg);
   },
 
-  async operateAddress(type, msgData) {
+  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");
   },
 
   async cleanUpCreditCards() {
     if (!FormAutofill.isAutofillCreditCardsAvailable) {
       return;
     }
     const guids = (await this._getRecords(CREDITCARDS_COLLECTION_NAME)).map(record => record.guid);
 
     if (guids.length == 0) {
+      sendAsyncMessage("FormAutofillTest:CreditCardsCleanedUp");
       return;
     }
 
     await this.operateCreditCard("remove", {guids}, "FormAutofillTest:CreditCardsCleanedUp");
   },
 
   setup() {
     OSKeyStoreTestUtils.setup();
@@ -165,81 +162,87 @@ 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
@@ -268,17 +268,17 @@ add_task(async function teardownExtensio
   extension = null;
 });
 
 add_task(async function testActiveTabOnNonExistingSidebar() {
   // Set a fake non existing sidebar id in the activeSidebar pref,
   // to simulate the scenario where an extension has installed a sidebar
   // which has been saved in the preference but it doesn't exist anymore.
   await SpecialPowers.pushPrefEnv({
-    set: [["devtools.inspector.activeSidebar", "unexisting-sidebar-id"]],
+    set: [["devtools.inspector.activeSidebar"], "unexisting-sidebar-id"],
   });
 
   const res = await openInspectorForURL("about:blank");
   inspector = res.inspector;
   toolbox = res.toolbox;
 
   const onceSidebarCreated = toolbox.once(`extension-sidebar-created-${SIDEBAR_ID}`);
   toolbox.registerInspectorExtensionSidebar(SIDEBAR_ID, {title: SIDEBAR_TITLE});
--- a/devtools/client/shared/test/shared-head.js
+++ b/devtools/client/shared/test/shared-head.js
@@ -451,18 +451,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.
  */
@@ -593,18 +591,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"
@@ -80,22 +79,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) {
@@ -552,55 +545,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
@@ -50,21 +50,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,62 +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
@@ -332,16 +332,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:
   nsPIDOMWindowOuter* GetOpenerWindowOuter();
@@ -912,18 +913,18 @@ class nsGlobalWindowOuter final : public
   virtual void SetFocusedElement(mozilla::dom::Element* aElement,
                                  uint32_t aFocusMethod = 0,
                                  bool aNeedsFocus = false) override;
 
   virtual uint32_t GetFocusMethod() override;
 
   virtual bool ShouldShowFocusRing() override;
 
-  virtual void SetKeyboardIndicators(
-      UIStateChangeType aShowFocusRings) override;
+  virtual void SetKeyboardIndicators(UIStateChangeType aShowFocusRings)
+      override;
 
  public:
   virtual already_AddRefed<nsPIWindowRoot> GetTopWindowRoot() override;
 
  protected:
   void NotifyWindowIDDestroyed(const char* aTopic);
 
   void ClearStatus();
@@ -1093,16 +1094,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
@@ -9,24 +9,24 @@ function getTestPlugin(pluginName) {
       return tag;
     }
   }
 
   ok(false, "Could not find plugin tag with plugin name '" + name + "'");
   return null;
 }
 // Copied from /dom/plugins/test/mochitest/utils.js
-async function setTestPluginEnabledState(newEnabledState, pluginName) {
-  var oldEnabledState = await SpecialPowers.setTestPluginEnabledState(newEnabledState, pluginName);
+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
@@ -9,21 +9,17 @@ 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://mochikit/content/tests/SimpleTest/test.css"/>
 <script type="application/javascript">
 
 /** Test for Bug 1414077 **/
 
 SimpleTest.waitForExplicitFinish();
 
-SimpleTest.requestFlakyTimeout("This test is flaky.");
-
-SpecialPowers.pushPrefEnv({"set":[["browser.enable_automatic_image_resizing", true]]}).then(async function() {
-  await new Promise(resolve => setTimeout(resolve, 2000));
-
+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.
--- 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 = {};
@@ -73,33 +73,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(new CustomEvent("mock-notification-close-event", {
--- a/dom/notification/test/mochitest/test_notification_basics.html
+++ b/dom/notification/test/mochitest/test_notification_basics.html
@@ -27,39 +27,39 @@
       ok(Notification.get, "Notification.get exists");
     },
 
     function() {
       info("Test requestPermission without callback");
       Notification.requestPermission();
     },
 
-    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
@@ -103,21 +103,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
@@ -28,18 +28,18 @@ function getTestPlugin(pluginName) {
 function setTestPluginEnabledState(newEnabledState, pluginName) {
   var oldEnabledState = SpecialPowers.setTestPluginEnabledState(newEnabledState, pluginName);
   var plugin = getTestPlugin(pluginName);
   // Run a nested event loop to wait for the preference change to
   // propagate to the child. Yuck!
   SpecialPowers.Services.tm.spinEventLoopUntil(() => {
     return plugin.enabledState == newEnabledState;
   });
-  SimpleTest.registerCleanupFunction(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;
 
--- 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/push/test/test_utils.js
+++ b/dom/push/test/test_utils.js
@@ -57,28 +57,28 @@ let currentMockSocket = null;
  * Sets up a mock connection for the WebSocket backend. This only replaces
  * the transport layer; `PushService.jsm` still handles DOM API requests,
  * observes permission changes, writes to IndexedDB, and notifies service
  * workers of incoming push messages.
  */
 function setupMockPushSocket(mockWebSocket) {
   currentMockSocket = mockWebSocket;
   currentMockSocket._isActive = true;
-  chromeScript.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,15 +1,15 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 var GamepadService;
 
 function setGamepadPreferenceAndCreateIframe(iframeSrc) {
-  SpecialPowers.pushPrefEnv({"set" : [["dom.gamepad.test.enabled", true]]}).then(() => {
+  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();
--- 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/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
@@ -417,17 +417,17 @@ async function waitUntilApzStable() {
       addMessageListener("apz-flush", apzFlush);
       addMessageListener("cleanup", cleanup);
     }
 
     // This is the first time waitUntilApzStable is being called, do initialization
     if (typeof waitUntilApzStable.chromeHelper == "undefined") {
       waitUntilApzStable.chromeHelper = SpecialPowers.loadChromeScript(parentProcessFlush);
       ApzCleanup.register(() => {
-        waitUntilApzStable.chromeHelper.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");
@@ -575,17 +575,17 @@ function getSnapshot(rect) {
   }
 
   if (typeof getSnapshot.chromeHelper == "undefined") {
     // This is the first time getSnapshot is being called; do initialization
     getSnapshot.chromeHelper = SpecialPowers.loadChromeScript(parentProcessSnapshot);
     ApzCleanup.register(function() { getSnapshot.chromeHelper.destroy(); });
   }
 
-  return getSnapshot.chromeHelper.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/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, 1000));
   // 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
@@ -1643,22 +1643,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
@@ -519,21 +519,17 @@ 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!");
--- 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
@@ -391,16 +391,18 @@ function Tester(aTests, structuredLogger
     sandboxPrototype: window,
   });
   Cu.permitCPOWsInScope(this.cpowSandbox);
 
   this.cpowEventUtils = new this.cpowSandbox.Object();
   this._scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/EventUtils.js", this.cpowEventUtils);
 
   var simpleTestScope = {};
+  this._scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/specialpowersAPI.js", simpleTestScope);
+  this._scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/SpecialPowersObserverAPI.js", simpleTestScope);
   this._scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/ChromePowers.js", simpleTestScope);
   this._scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/SimpleTest.js", simpleTestScope);
   this._scriptLoader.loadSubScript("chrome://mochikit/content/tests/SimpleTest/MemoryStats.js", simpleTestScope);
   this._scriptLoader.loadSubScript("chrome://mochikit/content/chrome-harness.js", simpleTestScope);
   this.SimpleTest = simpleTestScope.SimpleTest;
 
   var extensionUtilsScope = {
     registerCleanupFunction: (fn) => {
--- a/testing/mochitest/mochitest_options.py
+++ b/testing/mochitest/mochitest_options.py
@@ -416,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,30 @@
+
+/* 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
@@ -220,16 +220,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");
@@ -597,16 +598,40 @@ function convertManifestToTestLinks(root
                                                                manifestStream.available()));
   var paths = manifestObj.tests;
   var pathPrefix = "/" + root + "/";
   return [paths.reduce(function(t, p) { t[pathPrefix + p.path] = true; return t; }, {}),
           paths.length];
 }
 
 /**
+ * Produce a test harness page that has one remote iframe
+ */
+function nestedTest(metadata, response) {
+  response.setStatusLine("1.1", 200, "OK");
+  response.setHeader("Content-type", "text/html;charset=utf-8", false);
+  response.write(
+    HTML(
+      HEAD(
+        TITLE("Mochitest | ", metadata.path),
+        LINK({rel: "stylesheet",
+              type: "text/css", href: "/static/harness.css"}),
+        SCRIPT({type: "text/javascript",
+                src: "/nested_setup.js"}),
+        SCRIPT({type: "text/javascript"},
+               "window.onload = addPermissions; gTestURL = '/tests?" + metadata.queryString + "';")
+        ),
+      BODY(
+        DIV({class: "container"},
+          DIV({class: "frameholder", id: "holder-div"})
+        )
+        )));
+}
+
+/**
  * Produce a test harness page containing all the test cases
  * below it, recursively.
  */
 function testListing(metadata, response) {
   var links = {};
   var count = 0;
   if (!metadata.queryString.includes("manifestFile")) {
     [links, count] = list(metadata.path,
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
@@ -1,43 +1,30 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* globals ExtensionAPI */
 
 const {XPCOMUtils} = ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
-const {FileUtils} = ChromeUtils.import("resource://gre/modules/FileUtils.jsm");
 
 XPCOMUtils.defineLazyServiceGetter(this, "resProto",
                                    "@mozilla.org/network/protocol;1?name=resource",
                                    "nsISubstitutingProtocolHandler");
 
 this.specialpowers = class extends ExtensionAPI {
   onStartup() {
     let uri = Services.io.newURI("content/", null, this.extension.rootURI);
     resProto.setSubstitutionWithFlags("specialpowers", uri,
                                       resProto.ALLOW_CONTENT_ACCESS);
 
-    // 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,79 +1,135 @@
 /**
  * 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";
+    return msg.info.join(" ") + "\n";
 }
 
-class MozillaLogger {
-  get logCallback() {
-    return (msg) => {
-      this.log(formatLogMessage(msg));
+MozillaLogger.prototype = {
+  init(path) {},
+
+  getLogCallback() {
+    return function(msg) {
+      var data = formatLogMessage(msg);
+      dump(data);
     };
-  }
+  },
 
   log(msg) {
     dump(msg);
-  }
+  },
+
+  close() {},
+};
+
+
+/**
+ * SpecialPowersLogger, inherits from MozillaLogger and utilizes SpecialPowers.
+ * intented to be used in content scripts to write to a file
+ */
+function SpecialPowersLogger(aPath) {
+  // Call the base constructor
+  MozillaLogger.call(this);
+  this.prototype = new MozillaLogger(aPath);
+  this.init(aPath);
+}
 
-  close() {}
-}
+SpecialPowersLogger.prototype = {
+  init(path) {
+    SpecialPowers.setLogFile(path);
+  },
+
+  getLogCallback() {
+    return function(msg) {
+      var data = formatLogMessage(msg);
+      SpecialPowers.log(data);
+
+      if (data.includes("SimpleTest FINISH")) {
+        SpecialPowers.closeLogFile();
+      }
+    };
+  },
+
+  log(msg) {
+    SpecialPowers.log(msg);
+  },
+
+  close() {
+    SpecialPowers.closeLogFile();
+  },
+};
 
 
 /**
  * MozillaFileLogger, a log listener that can write to a local file.
  * intended to be run from chrome space
  */
 
 /** Init the file logger with the absolute path to the file.
     It will create and append if the file already exists **/
-class MozillaFileLogger extends MozillaLogger {
-  constructor(aPath) {
-    super();
+function MozillaFileLogger(aPath) {
+  // Call the base constructor
+  MozillaLogger.call(this);
+  this.prototype = new MozillaLogger(aPath);
+  this.init(aPath);
+}
+
+MozillaFileLogger.prototype = {
 
-    const {FileUtils} = ChromeUtils.import("resource://gre/modules/FileUtils.jsm");
-
-    this._file = FileUtils.File(aPath);
-    this._foStream = FileUtils.openFileOutputStream(
-        this._file, (FileUtils.MODE_WRONLY | FileUtils.MODE_CREATE |
-                     FileUtils.MODE_APPEND));
+  init(path) {
+    var PR_WRITE_ONLY   = 0x02; // Open for writing only.
+    var PR_CREATE_FILE  = 0x08;
+    var PR_APPEND       = 0x10;
+    this._file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+    this._file.initWithPath(path);
+    this._foStream = Cc["@mozilla.org/network/file-output-stream;1"]
+                       .createInstance(Ci.nsIFileOutputStream);
+    this._foStream.init(this._file, PR_WRITE_ONLY | PR_CREATE_FILE | PR_APPEND,
+                                     436 /* 0664 */, 0);
 
     this._converter = Cc["@mozilla.org/intl/converter-output-stream;1"]
                         .createInstance(Ci.nsIConverterOutputStream);
     this._converter.init(this._foStream, "UTF-8");
-  }
+  },
 
-  get logCallback() {
-    return (msg) => {
-      if (this._converter) {
-        var data = formatLogMessage(msg);
-        this.log(data);
+  getLogCallback() {
+    return function(msg) {
+      var data = formatLogMessage(msg);
+      if (MozillaFileLogger._converter) {
+        this._converter.writeString(data);
+      }
 
-        if (data.includes("SimpleTest FINISH")) {
-          this.close();
-        }
+      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,205 +4,318 @@
 "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";
 
-class SpecialPowersParent extends SpecialPowersAPIParent {
-  constructor() {
-    super();
-    this._messageManager = Services.mm;
-    this._serviceWorkerListener = null;
 
-    this._observer = this.observe.bind(this);
-
-    this.didDestroy = this.uninit.bind(this);
+// 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");
 
-    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);
+/* XPCOM gunk */
+function SpecialPowersObserver() {
+  this._isFrameScriptLoaded = false;
+  this._messageManager = Services.mm;
+  this._serviceWorkerListener = null;
+}
+
+
+SpecialPowersObserver.prototype = new SpecialPowersObserverAPI();
 
-            // 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.QueryInterface = ChromeUtils.generateQI([Ci.nsIObserver]);
 
-    this.init();
-  }
+SpecialPowersObserver.prototype.observe = function(aSubject, aTopic, aData) {
+  switch (aTopic) {
+    case "chrome-document-global-created":
+      this._loadFrameScript();
+      break;
 
-  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");
+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);
+
+    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;
+
+  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();
-      },
+  // 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();
+};
+
+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();
 
-      onUnregister() {
-        // no-op
-      },
-    };
-    swm.addListener(this._serviceWorkerListener);
-  }
+  let swm = Cc["@mozilla.org/serviceworkers/manager;1"]
+              .getService(Ci.nsIServiceWorkerManager);
+  swm.removeListener(this._serviceWorkerListener);
 
-  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();
+  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);
+    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);
 
-    let swm = Cc["@mozilla.org/serviceworkers/manager;1"]
-                .getService(Ci.nsIServiceWorkerManager);
-    swm.removeListener(this._serviceWorkerListener);
+    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,32 +1,43 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "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;
 
@@ -68,56 +79,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;
       }
@@ -146,46 +118,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;
@@ -194,17 +166,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 [];
     }
 
@@ -212,37 +184,84 @@ 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(topic)) {
@@ -271,213 +290,85 @@ 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);
-      case "COMPLEX":
-        return Services.prefs.getComplexValue(name, iid);
-    }
-    throw new Error(`Unexpected preference type: ${type}`);
-  }
+  },
 
   /**
    * messageManager callback function
    * This will get requests from our API in the window and process them in chrome for it
    **/
-  receiveMessage(aMessage) { // eslint-disable-line complexity
+  _receiveMessageAPI(aMessage) { // eslint-disable-line complexity
     // We explicitly return values in the below code so that this function
     // doesn't trigger a flurry of warnings about "does not always return
     // a value".
     switch (aMessage.name) {
-      case "PushPrefEnv":
-        return this.pushPrefEnv(aMessage.data);
-
-      case "PopPrefEnv":
-        return this.popPrefEnv();
-
-      case "FlushPrefEnv":
-        return this.flushPrefEnv();
-
       case "SPPrefService": {
         let prefs = Services.prefs;
         let prefType = aMessage.json.prefType.toUpperCase();
         let { prefName, prefValue, iid, defaultValue } = aMessage.json;
 
         if (aMessage.json.op == "get") {
           if (!prefName || !prefType)
             throw new SpecialPowersError("Invalid parameters for get in SPPrefService");
 
           // return null if the pref doesn't exist
           if (defaultValue === undefined && prefs.getPrefType(prefName) == prefs.PREF_INVALID)
             return null;
-          return this._getPref(prefName, prefType, defaultValue, iid);
         } else if (aMessage.json.op == "set") {
           if (!prefName || !prefType || prefValue === undefined)
             throw new SpecialPowersError("Invalid parameters for set in SPPrefService");
-
-          return this._setPref(prefName, prefType, prefValue, iid);
         } else if (aMessage.json.op == "clear") {
           if (!prefName)
             throw new SpecialPowersError("Invalid parameters for clear in SPPrefService");
-
-          prefs.clearUserPref(prefName);
         } else {
           throw new SpecialPowersError("Invalid operation for SPPrefService");
         }
 
+        // Now we make the call
+        switch (prefType) {
+          case "BOOL":
+            if (aMessage.json.op == "get") {
+              if (defaultValue !== undefined) {
+                return prefs.getBoolPref(prefName, defaultValue);
+              }
+              return prefs.getBoolPref(prefName);
+            }
+            return prefs.setBoolPref(prefName, prefValue);
+          case "INT":
+            if (aMessage.json.op == "get") {
+              if (defaultValue !== undefined) {
+                return prefs.getIntPref(prefName, defaultValue);
+              }
+              return prefs.getIntPref(prefName);
+            }
+            return prefs.setIntPref(prefName, prefValue);
+          case "CHAR":
+            if (aMessage.json.op == "get") {
+              if (defaultValue !== undefined) {
+                return prefs.getCharPref(prefName, defaultValue);
+              }
+              return prefs.getCharPref(prefName);
+            }
+            return prefs.setCharPref(prefName, prefValue);
+          case "COMPLEX":
+            if (aMessage.json.op == "get")
+              return prefs.getComplexValue(prefName, iid);
+            return prefs.setComplexValue(prefName, iid, prefValue);
+          case "":
+            if (aMessage.json.op == "clear") {
+              prefs.clearUserPref(prefName);
+              return undefined;
+            }
+        }
         return undefined; // See comment at the beginning of this function.
       }
 
       case "SPProcessCrashService": {
         switch (aMessage.json.op) {
           case "register-observer":
             this._addProcessCrashObservers();
             break;
@@ -495,17 +386,21 @@ class SpecialPowersAPIParent extends JSW
         }
         return undefined; // See comment at the beginning of this function.
       }
 
       case "SPProcessCrashManagerWait": {
         let promises = aMessage.json.crashIds.map((crashId) => {
           return Services.crashmanager.ensureCrashIsPresent(crashId);
         });
-        return Promise.all(promises);
+
+        Promise.all(promises).then(() => {
+          this._sendReply(aMessage, "SPProcessCrashManagerWait", {});
+        });
+        return undefined; // See comment at the beginning of this function.
       }
 
       case "SPPermissionManager": {
         let msg = aMessage.json;
         let principal = msg.principal;
 
         switch (msg.op) {
           case "add":
@@ -540,90 +435,106 @@ class SpecialPowersAPIParent extends JSW
       case "SPObserverService": {
         let topic = aMessage.json.observerTopic;
         switch (aMessage.json.op) {
           case "notify":
             let data = aMessage.json.observerData;
             Services.obs.notifyObservers(null, topic, data);
             break;
           case "add":
+            this._registerObservers._self = this;
             this._registerObservers._add(topic);
             break;
           default:
             throw new SpecialPowersError("Invalid operation for SPObserverervice");
         }
         return undefined; // See comment at the beginning of this function.
       }
 
       case "SPLoadChromeScript": {
         let id = aMessage.json.id;
+        let jsScript;
         let scriptName;
 
-        let jsScript = aMessage.json.function.body;
         if (aMessage.json.url) {
+          jsScript = this._readUrlAsString(aMessage.json.url);
           scriptName = aMessage.json.url;
         } else if (aMessage.json.function) {
+          jsScript = aMessage.json.function.body;
           scriptName = aMessage.json.function.name
             || "<loadChromeScript anonymous function>";
         } else {
           throw new SpecialPowersError("SPLoadChromeScript: Invalid script");
         }
 
         // Setup a chrome sandbox that has access to sendAsyncMessage
         // and {add,remove}MessageListener in order to communicate with
         // the mochitest.
-        let sb = new SpecialPowersSandbox(
-          scriptName,
-          data => {
-            this.sendAsyncMessage("Assert", data);
-          },
-          aMessage.data);
+        let systemPrincipal = Services.scriptSecurityManager.getSystemPrincipal();
+        let sandboxOptions = Object.assign({wantGlobalProperties: ["ChromeUtils"]},
+                                           aMessage.json.sandboxOptions);
+        let sb = Cu.Sandbox(systemPrincipal, sandboxOptions);
+        let mm = aMessage.target.frameLoader
+                         .messageManager;
+        sb.sendAsyncMessage = (name, message) => {
+          mm.sendAsyncMessage("SPChromeScriptMessage",
+                              { id, name, message });
+        };
+        sb.addMessageListener = (name, listener) => {
+          this._chromeScriptListeners.push({ id, name, listener });
+        };
+        sb.removeMessageListener = (name, listener) => {
+          let index = this._chromeScriptListeners.findIndex(function(obj) {
+            return obj.id == id && obj.name == name && obj.listener == listener;
+          });
+          if (index >= 0) {
+            this._chromeScriptListeners.splice(index, 1);
+          }
+        };
+        sb.browserElement = aMessage.target;
 
-        Object.assign(sb.sandbox, {
-          sendAsyncMessage: (name, message) => {
-            this.sendAsyncMessage("SPChromeScriptMessage",
-                                  { id, name, message });
+        // Also expose assertion functions
+        let reporter = function(err, message, stack) {
+          // Pipe assertions back to parent process
+          mm.sendAsyncMessage("SPChromeScriptAssert",
+                              { id, name: scriptName, err, message,
+                                stack });
+        };
+        Object.defineProperty(sb, "assert", {
+          get() {
+            let scope = Cu.createObjectIn(sb);
+            Services.scriptloader.loadSubScript("resource://specialpowers/Assert.jsm",
+                                                scope);
+
+            let assert = new scope.Assert(reporter);
+            delete sb.assert;
+            return sb.assert = assert;
           },
-          addMessageListener: (name, listener) => {
-            this._chromeScriptListeners.push({ id, name, listener });
-          },
-          removeMessageListener: (name, listener) => {
-            let index = this._chromeScriptListeners.findIndex(function(obj) {
-              return obj.id == id && obj.name == name && obj.listener == listener;
-            });
-            if (index >= 0) {
-              this._chromeScriptListeners.splice(index, 1);
-            }
-          },
-          actorParent: this.manager,
+          configurable: true,
         });
 
         // Evaluate the chrome script
         try {
-          Cu.evalInSandbox(jsScript, sb.sandbox, "1.8", scriptName, 1);
+          Cu.evalInSandbox(jsScript, sb, "1.8", scriptName, 1);
         } catch (e) {
           throw new SpecialPowersError(
             "Error while executing chrome script '" + scriptName + "':\n" +
             e + "\n" +
             e.fileName + ":" + e.lineNumber);
         }
         return undefined; // See comment at the beginning of this function.
       }
 
       case "SPChromeScriptMessage": {
         let id = aMessage.json.id;
         let name = aMessage.json.name;
         let message = aMessage.json.message;
-        let result;
-        for (let listener of this._chromeScriptListeners) {
-          if (listener.name == name && listener.id == id) {
-            result = listener.listener(message);
-          }
-        }
-        return result;
+        return this._chromeScriptListeners
+                   .filter(o => (o.name == name && o.id == id))
+                   .map(o => o.listener(message));
       }
 
       case "SPImportInMainProcess": {
         var message = { hadError: false, errorMessage: null };
         try {
           ChromeUtils.import(aMessage.data);
         } catch (e) {
           message.hadError = true;
@@ -638,21 +549,27 @@ class SpecialPowersAPIParent extends JSW
         let uri = Services.io.newURI(origin);
         let sss = Cc["@mozilla.org/ssservice;1"].
                   getService(Ci.nsISiteSecurityService);
         sss.removeState(Ci.nsISiteSecurityService.HEADER_HSTS, uri, flags);
         return undefined;
       }
 
       case "SPRequestDumpCoverageCounters": {
-        return PerTestCoverageUtils.afterTest();
+        PerTestCoverageUtils.afterTest().then(() =>
+          this._sendReply(aMessage, "SPRequestDumpCoverageCounters", {})
+        );
+        return undefined; // See comment at the beginning of this function.
       }
 
       case "SPRequestResetCoverageCounters": {
-        return PerTestCoverageUtils.beforeTest();
+        PerTestCoverageUtils.beforeTest().then(() =>
+          this._sendReply(aMessage, "SPRequestResetCoverageCounters", {})
+        );
+        return undefined; // See comment at the beginning of this function.
       }
 
       case "SPCheckServiceWorkers": {
         let swm = Cc["@mozilla.org/serviceworkers/manager;1"]
                     .getService(Ci.nsIServiceWorkerManager);
         let regs = swm.getAllRegistrations();
 
         // XXX This code is shared with specialpowers.js.
@@ -665,22 +582,22 @@ class SpecialPowersAPIParent extends JSW
       }
 
       case "SPLoadExtension": {
         let id = aMessage.data.id;
         let ext = aMessage.data.ext;