Bug 1541557: Part 5 - Update callers of ChromeScript.sendSyncMessage to use sendQuery instead. r=nika
authorKris Maglione <maglione.k@gmail.com>
Wed, 12 Jun 2019 12:40:51 -0700
changeset 540922 f808ec45ff9cf6a00847257e44f910a6c4c2b2ef
parent 540921 1025eeef095472d05bfb93499b501b1aff5cc758
child 540923 054a0b7aa81d2d29d9c46522f2512767bb123909
push id11533
push userarchaeopteryx@coole-files.de
push dateMon, 08 Jul 2019 18:18:03 +0000
treeherdermozilla-beta@f4452e031aed [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnika
bugs1541557
milestone69.0a1
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
Bug 1541557: Part 5 - Update callers of ChromeScript.sendSyncMessage to use sendQuery instead. r=nika Since JSWindowActors don't have direct access to synchronous messaging, ChromeScript callers are going to need to migrate to asynchronous messaging and queries instead. Since there's no comparable API to sendQuery for frame message managers, this patch adds a stub that uses synchronous messaging, but makes the API appear asynchronous, and migrates callers to use it instead of direct synchronous messaging. This will be replaced with a true synchronous API in the actor migration. Fortunately, most of the time, this actually leads to simpler code. The `sendQuery` API doesn't have the odd return value semantics of `sendSyncMessage`, and can usually just be used as a drop-in replacement. Many of the `sendSyncMessage` callers don't actually use the result, and can just be changed to `sendAsyncMessage`. And many of the existing async messaging users can be changed to just use `sendQuery` rather than sending messages and adding response listeners. However, the APZ code is an exception. It relies on intricate properties of the event loop, and doesn't have an easy way to slot in promise handlers, so I migrated it to using sync messaging via process message managers instead. Differential Revision: https://phabricator.services.mozilla.com/D35055
browser/extensions/formautofill/test/mochitest/test_address_level_1_submission.html
dom/media/test/test_info_leak.html
dom/notification/test/mochitest/test_notification_insecure_context.html
dom/presentation/tests/mochitest/test_presentation_availability_iframe.html
dom/push/test/test_utils.js
dom/serviceworkers/test/test_scopes.html
dom/tests/mochitest/bugs/test_navigator_buildID.html
dom/tests/mochitest/bugs/test_no_find_showDialog.html
editor/libeditor/tests/test_bug569988.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
netwerk/test/mochitests/test_user_agent_updates.html
testing/mochitest/tests/Harness_sanity/test_SpecialPowersLoadChromeScript.html
testing/mochitest/tests/Harness_sanity/test_SpecialPowersLoadChromeScript_function.html
testing/specialpowers/content/SpecialPowersAPI.jsm
toolkit/components/alerts/test/test_alerts_noobserve.html
toolkit/components/alerts/test/test_principal.html
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/prompts/test/prompt_common.js
toolkit/components/url-classifier/tests/mochitest/classifierHelper.js
toolkit/components/url-classifier/tests/mochitest/test_bug1254766.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
--- a/browser/extensions/formautofill/test/mochitest/test_address_level_1_submission.html
+++ b/browser/extensions/formautofill/test/mochitest/test_address_level_1_submission.html
@@ -39,18 +39,18 @@ add_task(async function test_DE_is_valid
       return !data.defaultLocale.sub_keys;
     });
   });
 
   SimpleTest.registerCleanupFunction(() => {
     chromeScript.destroy();
   });
 
-  let result = chromeScript.sendSyncMessage("CheckSubKeys");
-  ok(result[0][0], "Check that there are no sub_keys for the test country");
+  let result = await chromeScript.sendQuery("CheckSubKeys");
+  ok(result, "Check that there are no sub_keys for the test country");
 });
 
 add_task(async function test_form_will_submit_without_sub_keys() {
   await SpecialPowers.pushPrefEnv({
     set: [
       // This needs to match the country in the previous test and must have no sub_keys.
       ["browser.search.region", "DE"],
       // We already verified the first time use case in browser test
--- a/dom/media/test/test_info_leak.html
+++ b/dom/media/test/test_info_leak.html
@@ -157,16 +157,17 @@ function checkState(v) {
 
 
 function startTest(test, token) {
   manager.started(token);
   log("Testing: " + test.type + " @ " + test.name);
   createMedia(test.type, test.name, token);  
 }
 
+SimpleTest.waitForExplicitFinish();
 createTestArray().then(testArray => {
   manager.runTests(testArray, startTest);
 });
 
 </script>
 </pre>
 
 </body>
--- a/dom/notification/test/mochitest/test_notification_insecure_context.html
+++ b/dom/notification/test/mochitest/test_notification_insecure_context.html
@@ -32,17 +32,17 @@ https://bugzilla.mozilla.org/show_bug.cg
     let response = await Notification.requestPermission();
     is(response, "denied", "Denied permission in insecure context");
 
     await SpecialPowers.pushPrefEnv({"set": [["dom.webnotifications.allowinsecure", true]]});
 
     response = await Notification.requestPermission();
     is(response, "granted", "Granted permission in insecure context with pref set");
 
-    script.sendSyncMessage("destroy");
+    script.sendAsyncMessage("destroy");
     script.destroy();
 
     SimpleTest.finish();
   })();
   </script>
   </pre>
 </body>
 </html>
--- a/dom/presentation/tests/mochitest/test_presentation_availability_iframe.html
+++ b/dom/presentation/tests/mochitest/test_presentation_availability_iframe.html
@@ -35,24 +35,24 @@ function testSetup() {
     });
     gScript.sendAsyncMessage("setup");
   });
 }
 
 function testInitialUnavailable() {
   request = new PresentationRequest("https://example.com");
 
-  return request.getAvailability().then(function(aAvailability) {
+  return request.getAvailability().then(async function(aAvailability) {
     is(aAvailability.value, false, "Should have no available device after setup");
     aAvailability.onchange = function() {
       aAvailability.onchange = null;
       ok(aAvailability.value, "Device should be available.");
     };
     availability = aAvailability;
-    gScript.sendAsyncMessage("trigger-device-add", testDevice);
+    await gScript.sendQuery("trigger-device-add", testDevice);
   }).catch(function(aError) {
     ok(false, "Error occurred when getting availability: " + aError);
     teardown();
   });
 }
 
 function testInitialAvailable() {
   let anotherRequest = new PresentationRequest("https://example.net");
--- a/dom/push/test/test_utils.js
+++ b/dom/push/test/test_utils.js
@@ -57,28 +57,28 @@ let currentMockSocket = null;
  * Sets up a mock connection for the WebSocket backend. This only replaces
  * the transport layer; `PushService.jsm` still handles DOM API requests,
  * observes permission changes, writes to IndexedDB, and notifies service
  * workers of incoming push messages.
  */
 function setupMockPushSocket(mockWebSocket) {
   currentMockSocket = mockWebSocket;
   currentMockSocket._isActive = true;
-  chromeScript.sendSyncMessage("socket-setup");
+  chromeScript.sendAsyncMessage("socket-setup");
   chromeScript.addMessageListener("socket-client-msg", function(msg) {
     mockWebSocket.handleMessage(msg);
   });
 }
 
 function teardownMockPushSocket() {
   if (currentMockSocket) {
     return new Promise(resolve => {
       currentMockSocket._isActive = false;
       chromeScript.addMessageListener("socket-server-teardown", resolve);
-      chromeScript.sendSyncMessage("socket-teardown");
+      chromeScript.sendAsyncMessage("socket-teardown");
     });
   }
   return Promise.resolve();
 }
 
 /**
  * Minimal implementation of web sockets for use in testing. Forwards
  * messages to a mock web socket in the parent process that is used
--- a/dom/serviceworkers/test/test_scopes.html
+++ b/dom/serviceworkers/test/test_scopes.html
@@ -59,78 +59,75 @@
 
     unregisterArray.push(navigator.serviceWorker.getRegistration("scope/").then(function (reg) {
       return reg.unregister();
     }));
 
     return Promise.all(unregisterArray);
   }
 
-  function testScopes() {
-    return new Promise(function(resolve, reject) {
-      function chromeScriptSource() {
-        let swm = Cc["@mozilla.org/serviceworkers/manager;1"]
-                    .getService(Ci.nsIServiceWorkerManager);
-        let secMan = Cc["@mozilla.org/scriptsecuritymanager;1"]
-                       .getService(Ci.nsIScriptSecurityManager);
-        addMessageListener("getScope", (msg) => {
-          let principal = secMan.createCodebasePrincipalFromOrigin(msg.principal);
-          try {
-            return { scope: swm.getScopeForUrl(principal, msg.path) };
-          } catch (e) {
-            return { exception: e.message };
-          }
-        });
-      }
+  async function testScopes() {
+    function chromeScriptSource() {
+      let swm = Cc["@mozilla.org/serviceworkers/manager;1"]
+                  .getService(Ci.nsIServiceWorkerManager);
+      let secMan = Cc["@mozilla.org/scriptsecuritymanager;1"]
+                     .getService(Ci.nsIScriptSecurityManager);
+      addMessageListener("getScope", (msg) => {
+        let principal = secMan.createCodebasePrincipalFromOrigin(msg.principal);
+        try {
+          return { scope: swm.getScopeForUrl(principal, msg.path) };
+        } catch (e) {
+          return { exception: e.message };
+        }
+      });
+    }
 
-      let getScope;
-      let parent_intercept_enabled =
-        SpecialPowers.getBoolPref("dom.serviceWorkers.parent_intercept");
+    let getScope;
+    let parent_intercept_enabled =
+      SpecialPowers.getBoolPref("dom.serviceWorkers.parent_intercept");
 
-      if (parent_intercept_enabled) {
-        let chromeScript = SpecialPowers.loadChromeScript(chromeScriptSource);
-        let docPrincipal = SpecialPowers.wrap(document).nodePrincipal.URI.spec;
+    if (parent_intercept_enabled) {
+      let chromeScript = SpecialPowers.loadChromeScript(chromeScriptSource);
+      let docPrincipal = SpecialPowers.wrap(document).nodePrincipal.URI.spec;
 
-        getScope = (path) => {
-          let rv = chromeScript.sendSyncMessage("getScope", { principal: docPrincipal, path })[0][0];
-          if (rv.exception)
-            throw rv.exception;
-          return rv.scope;
-        };
-      } else {
-        getScope = navigator.serviceWorker.getScopeForUrl.bind(navigator.serviceWorker);
-      }
+      getScope = async (path) => {
+        let rv = await chromeScript.sendQuery("getScope", { principal: docPrincipal, path });
+        if (rv.exception)
+          throw rv.exception;
+        return rv.scope;
+      };
+    } else {
+      getScope = navigator.serviceWorker.getScopeForUrl.bind(navigator.serviceWorker);
+    }
 
-      var base = new URL(".", document.baseURI);
+    var base = new URL(".", document.baseURI);
 
-      function p(s) {
-        return base + s;
-      }
+    function p(s) {
+      return base + s;
+    }
 
-      function fail(fn) {
-        try {
-          getScope(p("index.html"));
-          ok(false, "No registration");
-        } catch(e) {
-          ok(true, "No registration");
-        }
+    async function fail(fn) {
+      try {
+        await getScope(p("index.html"));
+        ok(false, "No registration");
+      } catch(e) {
+        ok(true, "No registration");
       }
+    }
 
-      is(getScope(p("sub.html")), p("sub"), "Scope should match");
-      is(getScope(p("sub/dir.html")), p("sub/dir.html"), "Scope should match");
-      is(getScope(p("sub/dir")), p("sub/dir"), "Scope should match");
-      is(getScope(p("sub/dir/foo")), p("sub/dir/"), "Scope should match");
-      is(getScope(p("sub/dir/afoo")), p("sub/dir/a"), "Scope should match");
-      is(getScope(p("star*wars")), p("star*"), "Scope should match");
-      is(getScope(p("scope/some_file.html")), p("scope/"), "Scope should match");
-      fail("index.html");
-      fail("sua.html");
-      fail("star/a.html");
-      resolve();
-    });
+    is(await getScope(p("sub.html")), p("sub"), "Scope should match");
+    is(await getScope(p("sub/dir.html")), p("sub/dir.html"), "Scope should match");
+    is(await getScope(p("sub/dir")), p("sub/dir"), "Scope should match");
+    is(await getScope(p("sub/dir/foo")), p("sub/dir/"), "Scope should match");
+    is(await getScope(p("sub/dir/afoo")), p("sub/dir/a"), "Scope should match");
+    is(await getScope(p("star*wars")), p("star*"), "Scope should match");
+    is(await getScope(p("scope/some_file.html")), p("scope/"), "Scope should match");
+    await fail("index.html");
+    await fail("sua.html");
+    await fail("star/a.html");
   }
 
   function runTest() {
     registerWorkers()
       .then(testScopes)
       .then(unregisterWorkers)
       .then(function() {
         SimpleTest.finish();
--- a/dom/tests/mochitest/bugs/test_navigator_buildID.html
+++ b/dom/tests/mochitest/bugs/test_navigator_buildID.html
@@ -42,23 +42,23 @@ is(+contentBuildID, LEGACY_BUILD_ID,
 let chromeScript = SpecialPowers.loadChromeScript(() => {
   const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
   addMessageListener("test:getBuildID", nav => {
     const browser = Services.wm.getMostRecentBrowserWindow();
     return browser.navigator.buildID;
   });
 });
 
-let chromeBuildID = chromeScript.sendSyncMessage("test:getBuildID")[0][0];
-chromeScript.destroy();
+async function onMozillaIFrameLoaded() {
+  let chromeBuildID = await chromeScript.sendQuery("test:getBuildID");
+  chromeScript.destroy();
 
-ok(+chromeBuildID > LEGACY_BUILD_ID,
-   `navigator.buildID should be exposed in chrome - got "${chromeBuildID}"`);
+  ok(+chromeBuildID > LEGACY_BUILD_ID,
+     `navigator.buildID should be exposed in chrome - got "${chromeBuildID}"`);
 
-function onMozillaIFrameLoaded() {
   //
   // Access navigator.buildID from mozilla.org.
   //
   let mozillaBuildID = getMozillaBuildID();
 
   ok(+mozillaBuildID > LEGACY_BUILD_ID,
      `navigator.buildID should be exposed on mozilla.org ` +
      `- got "${mozillaBuildID}"`);
--- a/dom/tests/mochitest/bugs/test_no_find_showDialog.html
+++ b/dom/tests/mochitest/bugs/test_no_find_showDialog.html
@@ -3,34 +3,34 @@
 <head>
   <meta charset="utf-8">
   <title>Test for Bug 1348409</title>
   <script src="/tests/SimpleTest/SimpleTest.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
   <iframe src="about:blank"></iframe>
   <script type="text/javascript">
 
-  function checkForFindDialog() {
+  async function checkForFindDialog() {
     let chromeScript = SpecialPowers.loadChromeScript(_ => {
       addMessageListener("test:check", () => {
         const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
 
         let sawFind = false;
         let findDialog = Services.wm.getMostRecentWindow("findInPage");
         if (findDialog) {
           findDialog.close();
           sawFind = true;
         }
 
         return sawFind;
       });
 
     });
 
-    let sawFind = chromeScript.sendSyncMessage("test:check")[0][0];
+    let sawFind = await chromeScript.sendQuery("test:check");
     chromeScript.destroy();
     return sawFind;
   }
 
   function ensureFinished(chromeScript) {
     return new Promise(resolve => {
       chromeScript.addMessageListener("test:disarm:done", (sawWindow) => {
         resolve(sawWindow);
@@ -62,17 +62,17 @@
       ok(doWraparoundFind("fhqwhgads", true),
          "Should return true and not show a dialog if the string exists in the page.");
       ok(!doWraparoundFind(null, true),
          "Should return false and not show a dialog if we pass a null string.");
       ok(!doWraparoundFind("", true),
          "Should return false and not show a dialog if we pass an empty string.");
 
       // Double check to ensure that the parent didn't open a find dialog
-      let sawWindow = checkForFindDialog();
+      let sawWindow = await checkForFindDialog();
       ok(!sawWindow, "Should never have seen the dialog.");
     });
   }
   </script>
 </head>
 <body onload="startTest()">
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=1348409">Mozilla Bug 1348409</a>
 
--- a/editor/libeditor/tests/test_bug569988.html
+++ b/editor/libeditor/tests/test_bug569988.html
@@ -88,17 +88,17 @@ function runTest() {
   });
   script.addMessageListener("ok", ([val, msg]) => ok(val, msg));
   script.addMessageListener("info", msg => info(msg));
 
   info("opening prompt...");
   prompt("summary", "text");
   info("prompt is closed");
 
-  script.sendSyncMessage("destroy");
+  script.sendAsyncMessage("destroy");
 
   SimpleTest.finish();
 }
 
 </script>
 </pre>
 </body>
 </html>
--- a/editor/spellchecker/tests/test_bug1200533.html
+++ b/editor/spellchecker/tests/test_bug1200533.html
@@ -45,17 +45,17 @@ var tests = [
     [ "en-generic",      "de-DE", "*" ],
     // Result: Preference value.
     [ "ko-not-avail",    "de-DE", "de-DE" ],
   ];
 
 var loadCount = 0;
 var script;
 
-var loadListener = function(evt) {
+var loadListener = async function(evt) {
   if (loadCount == 0) {
     /* eslint-env mozilla/frame-script */
     script = SpecialPowers.loadChromeScript(function() {
       // eslint-disable-next-line mozilla/use-services
       var dir = Cc["@mozilla.org/file/directory_service;1"]
                   .getService(Ci.nsIProperties)
                   .get("CurWorkD", Ci.nsIFile);
       dir.append("tests");
@@ -81,17 +81,17 @@ var loadListener = function(evt) {
                          () => [en_GB.exists(), en_AU.exists(),
                                 de_DE.exists()]);
       addMessageListener("destroy", () => {
         hunspell.removeDirectory(en_GB);
         hunspell.removeDirectory(en_AU);
         hunspell.removeDirectory(de_DE);
       });
     });
-    var existenceChecks = script.sendSyncMessage("check-existence")[0][0];
+    var existenceChecks = await script.sendQuery("check-existence");
     is(existenceChecks[0], true, "true expected (en-GB directory should exist)");
     is(existenceChecks[1], true, "true expected (en-AU directory should exist)");
     is(existenceChecks[2], true, "true expected (de-DE directory should exist)");
   }
 
   SpecialPowers.pushPrefEnv({set: [["spellchecker.dictionary", tests[loadCount][1]]]},
                             function() { continueTest(evt); });
 };
@@ -119,17 +119,17 @@ function continueTest(evt) {
     }
 
     loadCount++;
     if (loadCount < tests.length) {
       // Load the iframe again.
       content.src = "http://mochi.test:8888/tests/editor/spellchecker/tests/bug1200533_subframe.html?firstload=false";
     } else {
       // Remove the fake  dictionaries again, since it's otherwise picked up by later tests.
-      script.sendSyncMessage("destroy");
+      script.sendAsyncMessage("destroy");
 
       SimpleTest.finish();
     }
   });
 }
 
 content.addEventListener("load", loadListener);
 
--- a/editor/spellchecker/tests/test_bug1204147.html
+++ b/editor/spellchecker/tests/test_bug1204147.html
@@ -24,17 +24,17 @@ var content = document.getElementById("c
 // load, it will set the dictionary to "en-GB". The bug was that a content preference
 // was also created. At second load, we check the dictionary for another element,
 // one that should use "en-US". With the bug corrected, we get "en-US", before
 // we got "en-GB" from the content preference.
 
 var firstLoad = true;
 var script;
 
-var loadListener = function(evt) {
+var loadListener = async function(evt) {
   if (firstLoad) {
     /* eslint-env mozilla/frame-script */
     script = SpecialPowers.loadChromeScript(function() {
       // eslint-disable-next-line mozilla/use-services
       var dir = Cc["@mozilla.org/file/directory_service;1"]
                   .getService(Ci.nsIProperties)
                   .get("CurWorkD", Ci.nsIFile);
       dir.append("tests");
@@ -48,17 +48,17 @@ var loadListener = function(evt) {
       // Install en-GB dictionary.
       let en_GB = dir.clone();
       en_GB.append("en-GB");
       hunspell.addDirectory(en_GB);
 
       addMessageListener("en_GB-exists", () => en_GB.exists());
       addMessageListener("destroy", () => hunspell.removeDirectory(en_GB));
     });
-    is(script.sendSyncMessage("en_GB-exists")[0][0], true,
+    is(await script.sendQuery("en_GB-exists"), true,
        "true expected (en-GB directory should exist)");
   }
 
   var doc = evt.target.contentDocument;
   var elem;
   if (firstLoad) {
     elem = doc.getElementById("en-GB");
   } else {
@@ -92,17 +92,17 @@ var loadListener = function(evt) {
       content.src = "http://mochi.test:8888/tests/editor/spellchecker/tests/bug1204147_subframe2.html?firstload=false";
     } else {
       // Second time around, the element should default to en-US.
       // Without the fix, the first run sets the content preference to en-GB for the whole site.
       is(currentDictonary, "en-US", "unexpected lang " + currentDictonary + " instead of en-US");
       content.removeEventListener("load", loadListener);
 
       // Remove the fake en-GB dictionary again, since it's otherwise picked up by later tests.
-      script.sendSyncMessage("destroy");
+      script.sendAsyncMessage("destroy");
 
       // Reset the preference, so the last value we set doesn't collide with the next test.
       SimpleTest.finish();
     }
   });
 };
 
 content.addEventListener("load", loadListener);
--- a/editor/spellchecker/tests/test_bug1205983.html
+++ b/editor/spellchecker/tests/test_bug1205983.html
@@ -29,17 +29,17 @@ var selcon_de;
 var script;
 
 var onSpellCheck =
   SpecialPowers.Cu.import(
     "resource://testing-common/AsyncSpellCheckTestHelper.jsm").onSpellCheck;
 
 /** Test for Bug 1205983 **/
 SimpleTest.waitForExplicitFinish();
-SimpleTest.waitForFocus(function() {
+SimpleTest.waitForFocus(async function() {
   /* eslint-env mozilla/frame-script */
   script = SpecialPowers.loadChromeScript(function() {
     // eslint-disable-next-line mozilla/use-services
     var dir = Cc["@mozilla.org/file/directory_service;1"]
                 .getService(Ci.nsIProperties)
                 .get("CurWorkD", Ci.nsIFile);
     dir.append("tests");
     dir.append("editor");
@@ -52,17 +52,17 @@ SimpleTest.waitForFocus(function() {
     // Install de-DE dictionary.
     var de_DE = dir.clone();
     de_DE.append("de-DE");
     hunspell.addDirectory(de_DE);
 
     addMessageListener("de_DE-exists", () => de_DE.exists());
     addMessageListener("destroy", () => hunspell.removeDirectory(de_DE));
   });
-  is(script.sendSyncMessage("de_DE-exists")[0][0], true,
+  is(await script.sendQuery("de_DE-exists"), true,
      "true expected (de_DE directory should exist)");
 
   document.getElementById("de-DE").focus();
 });
 
 function deFocus() {
   elem_de = document.getElementById("de-DE");
 
@@ -101,17 +101,17 @@ function enFocus() {
 
     // So far all was boring. The important thing is whether the spell check result
     // in the de-DE editor is still the same. After losing focus, no spell check
     // updates should take place there.
     var sel = selcon_de.getSelection(selcon_de.SELECTION_SPELLCHECK);
     is(sel.toString(), "German", "one misspelled word expected: German");
 
     // Remove the fake de_DE dictionary again.
-    script.sendSyncMessage("destroy");
+    script.sendAsyncMessage("destroy");
 
     // Focus again, so the spelling gets updated, but before we need to kill the focus handler.
     elem_de.onfocus = null;
     elem_de.blur();
     elem_de.focus();
 
     // After removal, the de_DE editor should refresh the spelling with en-US.
     onSpellCheck(elem_de, function() {
--- a/editor/spellchecker/tests/test_bug1209414.html
+++ b/editor/spellchecker/tests/test_bug1209414.html
@@ -36,17 +36,17 @@ var script;
  * user behaviour.
  */
 
 var onSpellCheck =
   SpecialPowers.Cu.import(
     "resource://testing-common/AsyncSpellCheckTestHelper.jsm").onSpellCheck;
 
 SimpleTest.waitForExplicitFinish();
-SimpleTest.waitForFocus(function() {
+SimpleTest.waitForFocus(async function() {
   /* global browserElement */
   /* eslint-env mozilla/frame-script */
   script = SpecialPowers.loadChromeScript(function() {
     var chromeWin = browserElement.ownerGlobal.docShell
                     .rootTreeItem.domWindow
                     .QueryInterface(Ci.nsIDOMChromeWindow);
     var contextMenu = chromeWin.document.getElementById("contentAreaContextMenu");
     contextMenu.addEventListener("popupshown",
@@ -80,19 +80,19 @@ SimpleTest.waitForFocus(function() {
       contextMenu.hidePopup();
 
       return state;
     });
     addMessageListener("destroy", () => hunspell.removeDirectory(de_DE));
     addMessageListener("contextMenu-not-null", () => contextMenu != null);
     addMessageListener("de_DE-exists", () => de_DE.exists());
   });
-  is(script.sendSyncMessage("contextMenu-not-null")[0][0], true,
+  is(await script.sendQuery("contextMenu-not-null"), true,
      "Got context menu XUL");
-  is(script.sendSyncMessage("de_DE-exists")[0][0], true,
+  is(await script.sendQuery("de_DE-exists"), true,
      "true expected (de_DE directory should exist)");
   script.addMessageListener("popupshown", handlePopup);
 
   elem_de = document.getElementById("de-DE");
   editor_de = SpecialPowers.wrap(elem_de).editor;
   editor_de.setSpellcheckUserOverride(true);
 
   onSpellCheck(elem_de, function() {
@@ -112,35 +112,35 @@ SimpleTest.waitForFocus(function() {
 
     // Make sure all spell checking action is done before right-click to select the en-US dictionary.
     onSpellCheck(elem_de, function() {
       synthesizeMouse(elem_de, 2, 2, { type: "contextmenu", button: 2 }, window);
     });
   });
 });
 
-function handlePopup() {
-  var state = script.sendSyncMessage("hidepopup")[0][0];
+async function handlePopup() {
+  var state = await script.sendQuery("hidepopup");
   is(state, "open", "checking if popup is open");
 
   onSpellCheck(elem_de, function() {
     var inlineSpellChecker = editor_de.getInlineSpellChecker(true);
     var spellchecker = inlineSpellChecker.spellChecker;
     let currentDictonary;
     try {
       currentDictonary = spellchecker.GetCurrentDictionary();
     } catch (e) {}
 
     // Check that the English dictionary is loaded and that the spell check has worked.
     is(currentDictonary, "en-US", "expected en-US");
     // eslint-disable-next-line no-useless-concat
     is(getMisspelledWords(editor_de), "heute" + "ist" + "ein" + "guter", "some misspelled words expected: heute ist ein guter");
 
     // Remove the fake de_DE dictionary again.
-    script.sendSyncMessage("destroy");
+    script.sendAsyncMessage("destroy");
 
     // This will clear the content preferences and reset "spellchecker.dictionary".
     spellchecker.SetCurrentDictionary("");
     SimpleTest.finish();
   });
 }
 
 </script>
--- a/editor/spellchecker/tests/test_bug678842.html
+++ b/editor/spellchecker/tests/test_bug678842.html
@@ -23,17 +23,17 @@ var content = document.getElementById("c
 // load a subframe containing an editor with a defined unknown lang. At first
 // load, it will set dictionary to en-US. At second load, it will return current
 // dictionary. So, we can check, dictionary is correctly remembered between
 // loads.
 
 var firstLoad = true;
 var script;
 
-var loadListener = function(evt) {
+var loadListener = async function(evt) {
   if (firstLoad) {
     /* eslint-env mozilla/frame-script */
     script = SpecialPowers.loadChromeScript(function() {
       // eslint-disable-next-line mozilla/use-services
       var dir = Cc["@mozilla.org/file/directory_service;1"]
                   .getService(Ci.nsIProperties)
                   .get("CurWorkD", Ci.nsIFile);
       dir.append("tests");
@@ -47,17 +47,17 @@ var loadListener = function(evt) {
       // Install en-GB dictionary.
       let en_GB = dir.clone();
       en_GB.append("en-GB");
       hunspell.addDirectory(en_GB);
 
       addMessageListener("en_GB-exists", () => en_GB.exists());
       addMessageListener("destroy", () => hunspell.removeDirectory(en_GB));
     });
-    is(script.sendSyncMessage("en_GB-exists")[0][0], true,
+    is(await script.sendQuery("en_GB-exists"), true,
        "true expected (en-GB directory should exist)");
   }
 
   var doc = evt.target.contentDocument;
   var elem = doc.getElementById("textarea");
   var editor = SpecialPowers.wrap(elem).editor;
   editor.setSpellcheckUserOverride(true);
   var inlineSpellChecker = editor.getInlineSpellChecker(true);
@@ -84,17 +84,17 @@ var loadListener = function(evt) {
       spellchecker.SetCurrentDictionary("en-GB");
 
       content.src = "http://mochi.test:8888/tests/editor/spellchecker/tests/bug678842_subframe.html?firstload=false";
     } else {
       is(currentDictonary, "en-GB", "unexpected lang " + currentDictonary + " instead of en-GB");
       content.removeEventListener("load", loadListener);
 
       // Remove the fake en-GB dictionary again, since it's otherwise picked up by later tests.
-      script.sendSyncMessage("destroy");
+      script.sendAsyncMessage("destroy");
 
       // This will clear the content preferences and reset "spellchecker.dictionary".
       spellchecker.SetCurrentDictionary("");
       SimpleTest.finish();
     }
   });
 };
 
--- a/editor/spellchecker/tests/test_bug697981.html
+++ b/editor/spellchecker/tests/test_bug697981.html
@@ -29,17 +29,17 @@ var script;
 
 var onSpellCheck =
   SpecialPowers.Cu.import(
     "resource://testing-common/AsyncSpellCheckTestHelper.jsm")
                .onSpellCheck;
 
 /** Test for Bug 697981 **/
 SimpleTest.waitForExplicitFinish();
-SimpleTest.waitForFocus(function() {
+SimpleTest.waitForFocus(async function() {
   /* eslint-env mozilla/frame-script */
   script = SpecialPowers.loadChromeScript(function() {
     // eslint-disable-next-line mozilla/use-services
     var dir = Cc["@mozilla.org/file/directory_service;1"]
                 .getService(Ci.nsIProperties)
                 .get("CurWorkD", Ci.nsIFile);
     dir.append("tests");
     dir.append("editor");
@@ -52,17 +52,17 @@ SimpleTest.waitForFocus(function() {
     // Install de-DE dictionary.
     var de_DE = dir.clone();
     de_DE.append("de-DE");
     hunspell.addDirectory(de_DE);
 
     addMessageListener("de_DE-exists", () => de_DE.exists());
     addMessageListener("destroy", () => hunspell.removeDirectory(de_DE));
   });
-  is(script.sendSyncMessage("de_DE-exists")[0][0], true,
+  is(await script.sendQuery("de_DE-exists"), true,
      "true expected (de_DE directory should exist)");
 
   document.getElementById("de-DE").focus();
 });
 
 function deFocus() {
   elem_de = document.getElementById("de-DE");
   editor_de = SpecialPowers.wrap(elem_de).editor;
@@ -102,17 +102,17 @@ function enFocus() {
     is(getMisspelledWords(editor_en), "Nogoodword", "one misspelled word expected: Nogoodword");
 
     // So far all was boring. The important thing is whether the spell check result
     // in the de-DE editor is still the same. After losing focus, no spell check
     // updates should take place there.
     is(getMisspelledWords(editor_de), "German", "one misspelled word expected: German");
 
     // Remove the fake de_DE dictionary again.
-    script.sendSyncMessage("destroy");
+    script.sendAsyncMessage("destroy");
 
     // Focus again, so the spelling gets updated, but before we need to kill the focus handler.
     elem_de.onfocus = null;
     elem_de.blur();
     elem_de.focus();
 
     // After removal, the de_DE editor should refresh the spelling with en-US.
     onSpellCheck(elem_de, function() {
--- a/editor/spellchecker/tests/test_bug717433.html
+++ b/editor/spellchecker/tests/test_bug717433.html
@@ -24,17 +24,17 @@ var content = document.getElementById("c
 // load, it will set the dictionary to en-GB or en-US. We set the other one.
 // At second load, it will return the current dictionary. We can check that the
 // dictionary is correctly remembered between loads.
 
 var firstLoad = true;
 var expected = "";
 var script;
 
-var loadListener = function(evt) {
+var loadListener = async function(evt) {
   if (firstLoad) {
     /* eslint-env mozilla/frame-script */
     script = SpecialPowers.loadChromeScript(function() {
       // eslint-disable-next-line mozilla/use-services
       var dir = Cc["@mozilla.org/file/directory_service;1"]
                   .getService(Ci.nsIProperties)
                   .get("CurWorkD", Ci.nsIFile);
       dir.append("tests");
@@ -48,17 +48,17 @@ var loadListener = function(evt) {
       // Install en-GB dictionary.
       var en_GB = dir.clone();
       en_GB.append("en-GB");
       hunspell.addDirectory(en_GB);
 
       addMessageListener("en_GB-exists", () => en_GB.exists());
       addMessageListener("destroy", () => hunspell.removeDirectory(en_GB));
     });
-    is(script.sendSyncMessage("en_GB-exists")[0][0], true,
+    is(await script.sendQuery("en_GB-exists"), true,
        "true expected (en-GB directory should exist)");
   }
 
   var doc = evt.target.contentDocument;
   var elem = doc.getElementById("textarea");
   var editor = SpecialPowers.wrap(elem).editor;
   editor.setSpellcheckUserOverride(true);
   var inlineSpellChecker = editor.getInlineSpellChecker(true);
@@ -85,17 +85,17 @@ var loadListener = function(evt) {
         is(true, false, "Neither en-US nor en-GB are current");
       }
       content.src = "http://mochi.test:8888/tests/editor/spellchecker/tests/bug717433_subframe.html?firstload=false";
     } else {
       is(currentDictonary, expected, expected + " expected");
       content.removeEventListener("load", loadListener);
 
       // Remove the fake en-GB dictionary again, since it's otherwise picked up by later tests.
-      script.sendSyncMessage("destroy");
+      script.sendAsyncMessage("destroy");
 
       // This will clear the content preferences and reset "spellchecker.dictionary".
       spellchecker.SetCurrentDictionary("");
       SimpleTest.finish();
     }
   });
 };
 
--- a/gfx/layers/apz/test/mochitest/apz_test_utils.js
+++ b/gfx/layers/apz/test/mochitest/apz_test_utils.js
@@ -417,17 +417,17 @@ async function waitUntilApzStable() {
       addMessageListener("apz-flush", apzFlush);
       addMessageListener("cleanup", cleanup);
     }
 
     // This is the first time waitUntilApzStable is being called, do initialization
     if (typeof waitUntilApzStable.chromeHelper == "undefined") {
       waitUntilApzStable.chromeHelper = SpecialPowers.loadChromeScript(parentProcessFlush);
       ApzCleanup.register(() => {
-        waitUntilApzStable.chromeHelper.sendSyncMessage("cleanup", null);
+        waitUntilApzStable.chromeHelper.sendAsyncMessage("cleanup", null);
         waitUntilApzStable.chromeHelper.destroy();
         delete waitUntilApzStable.chromeHelper;
       });
     }
 
     // Actually trigger the parent-process flush and wait for it to finish
     waitUntilApzStable.chromeHelper.sendAsyncMessage("apz-flush", null);
     await waitUntilApzStable.chromeHelper.promiseOneMessage("apz-flush-done");
@@ -575,17 +575,17 @@ function getSnapshot(rect) {
   }
 
   if (typeof getSnapshot.chromeHelper == "undefined") {
     // This is the first time getSnapshot is being called; do initialization
     getSnapshot.chromeHelper = SpecialPowers.loadChromeScript(parentProcessSnapshot);
     ApzCleanup.register(function() { getSnapshot.chromeHelper.destroy(); });
   }
 
-  return getSnapshot.chromeHelper.sendSyncMessage("snapshot", JSON.stringify(rect)).toString();
+  return getSnapshot.chromeHelper.sendQuery("snapshot", JSON.stringify(rect));
 }
 
 // Takes the document's query string and parses it, assuming the query string
 // is composed of key-value pairs where the value is in JSON format. The object
 // returned contains the various values indexed by their respective keys. In
 // case of duplicate keys, the last value be used.
 // Examples:
 //   ?key="value"&key2=false&key3=500
--- a/gfx/layers/apz/test/mochitest/helper_touch_action_regions.html
+++ b/gfx/layers/apz/test/mochitest/helper_touch_action_regions.html
@@ -28,64 +28,75 @@ function listener(callback) {
 // This returns true on success, and false on failure.
 // The 'report' invocation can be invoked multiple times, and returns an object
 // (in JSON string format) containing the counters.
 // The 'end' invocation tears down the listeners, and should be invoked once
 // at the end to clean up. Returns true on success, false on failure.
 /* eslint-env mozilla/frame-script */
 function chromeTouchEventCounter(operation) {
   function chromeProcessCounter() {
-    addMessageListener("start", function() {
-      const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
-      var topWin = Services.wm.getMostRecentWindow("navigator:browser");
-      if (typeof topWin.eventCounts != "undefined") {
-        dump("Found pre-existing eventCounts object on the top window!\n");
-        return false;
-      }
-      topWin.eventCounts = { "touchstart": 0, "touchmove": 0, "touchend": 0 };
-      topWin.counter = function(e) {
-        topWin.eventCounts[e.type]++;
-      };
+    const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
+
+    const PREFIX = "apz:ctec:";
 
-      topWin.addEventListener("touchstart", topWin.counter, { passive: true });
-      topWin.addEventListener("touchmove", topWin.counter, { passive: true });
-      topWin.addEventListener("touchend", topWin.counter, { passive: true });
+    const LISTENERS = {
+      "start": function() {
+        var topWin = Services.wm.getMostRecentWindow("navigator:browser");
+        if (typeof topWin.eventCounts != "undefined") {
+          dump("Found pre-existing eventCounts object on the top window!\n");
+          return false;
+        }
+        topWin.eventCounts = { "touchstart": 0, "touchmove": 0, "touchend": 0 };
+        topWin.counter = function(e) {
+          topWin.eventCounts[e.type]++;
+        };
 
-      return true;
-    });
+        topWin.addEventListener("touchstart", topWin.counter, { passive: true });
+        topWin.addEventListener("touchmove", topWin.counter, { passive: true });
+        topWin.addEventListener("touchend", topWin.counter, { passive: true });
+
+        return true;
+      },
 
-    addMessageListener("report", function() {
-      const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
-      var topWin = Services.wm.getMostRecentWindow("navigator:browser");
-      return JSON.stringify(topWin.eventCounts);
-    });
+      "report": function() {
+        var topWin = Services.wm.getMostRecentWindow("navigator:browser");
+        return JSON.stringify(topWin.eventCounts);
+      },
+
+      "end": function() {
+        for (let [msg, func] of Object.entries(LISTENERS)) {
+          Services.ppmm.removeMessageListener(PREFIX + msg, func);
+        }
 
-    addMessageListener("end", function() {
-      const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
-      var topWin = Services.wm.getMostRecentWindow("navigator:browser");
-      if (typeof topWin.eventCounts == "undefined") {
-        dump("The eventCounts object was not found on the top window!\n");
-        return false;
-      }
-      topWin.removeEventListener("touchstart", topWin.counter);
-      topWin.removeEventListener("touchmove", topWin.counter);
-      topWin.removeEventListener("touchend", topWin.counter);
-      delete topWin.counter;
-      delete topWin.eventCounts;
-      return true;
-    });
+        var topWin = Services.wm.getMostRecentWindow("navigator:browser");
+        if (typeof topWin.eventCounts == "undefined") {
+          dump("The eventCounts object was not found on the top window!\n");
+          return false;
+        }
+        topWin.removeEventListener("touchstart", topWin.counter);
+        topWin.removeEventListener("touchmove", topWin.counter);
+        topWin.removeEventListener("touchend", topWin.counter);
+        delete topWin.counter;
+        delete topWin.eventCounts;
+        return true;
+      },
+    };
+
+    for (let [msg, func] of Object.entries(LISTENERS)) {
+      Services.ppmm.addMessageListener(PREFIX + msg, func);
+    }
   }
 
   if (typeof chromeTouchEventCounter.chromeHelper == "undefined") {
     // This is the first time chromeTouchEventCounter is being called; do initialization
     chromeTouchEventCounter.chromeHelper = SpecialPowers.loadChromeScript(chromeProcessCounter);
     ApzCleanup.register(function() { chromeTouchEventCounter.chromeHelper.destroy(); });
   }
 
-  return chromeTouchEventCounter.chromeHelper.sendSyncMessage(operation, "");
+  return SpecialPowers.Services.cpmm.sendSyncMessage(`apz:ctec:${operation}`, "")[0];
 }
 
 // Simple wrapper that waits until the chrome process has seen |count| instances
 // of the |eventType| event. Returns true on success, and false if 10 seconds
 // go by without the condition being satisfied.
 function waitFor(eventType, count) {
   var start = Date.now();
   while (JSON.parse(chromeTouchEventCounter("report"))[eventType] != count) {
--- a/netwerk/test/mochitests/test_user_agent_updates.html
+++ b/netwerk/test/mochitests/test_user_agent_updates.html
@@ -197,17 +197,17 @@ SimpleTest.requestFlakyTimeout("Test set
 
 SpecialPowers.pushPrefEnv({
   set: [
     [PREF_APP_UPDATE_TIMERMINIMUMDELAY, 0]
   ]
 }, function () {
   // Sets the OVERRIDES var in the chrome script.
   // We do this to avoid code duplication.
-  chromeScript.sendSyncMessage("set-overrides", OVERRIDES);
+  chromeScript.sendAsyncMessage("set-overrides", OVERRIDES);
 
   // testProfileLoad, testDownload, and testProfileSave must run in this order
   // because testDownload depends on testProfileLoad and testProfileSave depends
   // on testDownload to save overrides to the profile
   chromeScript.sendAsyncMessage("testProfileLoad", location.hostname);
 });
 
 
--- a/testing/mochitest/tests/Harness_sanity/test_SpecialPowersLoadChromeScript.html
+++ b/testing/mochitest/tests/Harness_sanity/test_SpecialPowersLoadChromeScript.html
@@ -42,28 +42,22 @@ function endOfFirstTest() {
     });
   }, { wantGlobalProperties: ["ChromeUtils", "XMLHttpRequest"] });
 
   script2.sendAsyncMessage("valid-assert");
   script2.addMessageListener("valid-assert-done", endOfTest);
 
 }
 
-function endOfTest() {
+async function endOfTest() {
+  is(await script.sendQuery("sync-message"), "Received a synchronous message.",
+     "Check sync return value");
+
   script2.destroy();
   SimpleTest.finish();
 }
 
 script.sendAsyncMessage("foo", MESSAGE);
 
-/*
- * [0][0] is because we're using one real message listener in SpecialPowersObserverAPI.js
- * and dispatching that to multiple _chromeScriptListeners. The outer array comes
- * from the message manager since there can be multiple real listeners. The inner
- * array is for the return values of _chromeScriptListeners.
- */
-is(script.sendSyncMessage("sync-message")[0][0], "Received a synchronous message.",
-   "Check sync return value");
-
 </script>
 </pre>
 </body>
 </html>
--- a/testing/mochitest/tests/Harness_sanity/test_SpecialPowersLoadChromeScript_function.html
+++ b/testing/mochitest/tests/Harness_sanity/test_SpecialPowersLoadChromeScript_function.html
@@ -40,28 +40,22 @@ script.addMessageListener("bar", functio
   checkAssert();
 });
 
 function checkAssert() {
   script.sendAsyncMessage("valid-assert");
   script.addMessageListener("valid-assert-done", endOfTest);
 }
 
-function endOfTest() {
+async function endOfTest() {
+  is(await script.sendQuery("sync-message"), "Received a synchronous message.",
+     "Check sync return value");
+
   script.destroy();
   SimpleTest.finish();
 }
 
 script.sendAsyncMessage("foo", MESSAGE);
 
-/*
- * [0][0] is because we're using one real message listener in SpecialPowersObserverAPI.js
- * and dispatching that to multiple _chromeScriptListeners. The outer array comes
- * from the message manager since there can be multiple real listeners. The inner
- * array is for the return values of _chromeScriptListeners.
- */
-is(script.sendSyncMessage("sync-message")[0][0], "Received a synchronous message.",
-   "Check sync return value");
-
 </script>
 </pre>
 </body>
 </html>
--- a/testing/specialpowers/content/SpecialPowersAPI.jsm
+++ b/testing/specialpowers/content/SpecialPowersAPI.jsm
@@ -275,19 +275,22 @@ class SpecialPowersAPI {
         );
       },
 
       sendAsyncMessage: (name, message) => {
         this._sendSyncMessage("SPChromeScriptMessage",
                               { id, name, message });
       },
 
-      sendSyncMessage: (name, message) => {
+      sendQuery: async (name, message) => {
+        // Send a sync query and pretend it's async. This will become a
+        // real async `sendQuery` call after the JSWindowActor
+        // migration.
         return this._sendSyncMessage("SPChromeScriptMessage",
-                                     { id, name, message });
+                                     { id, name, message })[0][0];
       },
 
       destroy: () => {
         listeners = [];
         this._removeMessageListener("SPChromeScriptMessage", chromeScript);
         this._removeMessageListener("SPChromeScriptAssert", chromeScript);
       },
 
--- a/toolkit/components/alerts/test/test_alerts_noobserve.html
+++ b/toolkit/components/alerts/test/test_alerts_noobserve.html
@@ -47,17 +47,20 @@ function waitForAlertsThenFinish() {
   chromeScript.addMessageListener("waitedForAlerts", function waitedForAlerts() {
     chromeScript.removeMessageListener("waitedForAlerts", waitedForAlerts);
     ok(true, "Alert disappeared.");
     SimpleTest.finish();
   });
   chromeScript.sendAsyncMessage("waitForAlerts");
 }
 
-function runTest() {
+async function runTest() {
+  var alertsVisible = await chromeScript.sendQuery("anyXULAlertsVisible");
+  ok(!alertsVisible, "Alerts should not be present at the start of the test.");
+
   if (!("@mozilla.org/alerts-service;1" in Cc)) {
     todo(false, "Alerts service does not exist in this application");
   } else {
     ok(true, "Alerts service exists in this application");
 
     var notifier;
     try {
       notifier = Cc["@mozilla.org/alerts-service;1"].
@@ -77,19 +80,14 @@ function runTest() {
       }
     }
   }
 }
 
 SimpleTest.waitForExplicitFinish();
 SimpleTest.requestFlakyTimeout("untriaged");
 
-// sendSyncMessage returns an array of arrays: the outer array is from the
-// message manager, and the inner array is from the chrome script's listeners.
-// See the comment in test_SpecialPowersLoadChromeScript.html.
-var [[alertsVisible]] = chromeScript.sendSyncMessage("anyXULAlertsVisible");
-ok(!alertsVisible, "Alerts should not be present at the start of the test.");
 runTest();
 setTimeout(waitForAlertsThenFinish, 1000);
 </script>
 </pre>
 </body>
 </html>
--- a/toolkit/components/alerts/test/test_principal.html
+++ b/toolkit/components/alerts/test/test_principal.html
@@ -37,21 +37,21 @@ const chromeScript = SpecialPowers.loadC
     var alertWindow = alertWindows.getNext();
     return alertWindow.document.getElementById("alertSourceLabel").getAttribute("value");
   });
 });
 
 function notify(alertName, principal) {
   return new Promise((resolve, reject) => {
     var source;
-    function observe(subject, topic, data) {
+    async function observe(subject, topic, data) {
       if (topic == "alertclickcallback") {
         reject(new Error("Alerts should not be clicked during test"));
       } else if (topic == "alertshow") {
-        source = chromeScript.sendSyncMessage("getAlertSource")[0][0];
+        source = await chromeScript.sendQuery("getAlertSource");
         notifier.closeAlert(alertName);
       } else {
         is(topic, "alertfinished", "Should hide alert");
         resolve(source);
       }
     }
     notifier.showAlertNotification(null, "Notification test",
                                    "Surprise! I'm here to test notifications!",
@@ -102,18 +102,20 @@ function runTest() {
     todo(false, "Native alerts service exists in this application");
     return;
   }
 
   ok(true, "Alerts service exists in this application");
 
   // sendSyncMessage returns an array of arrays. See the comments in
   // test_alerts_noobserve.html and test_SpecialPowersLoadChromeScript.html.
-  var [[alertsVisible]] = chromeScript.sendSyncMessage("anyXULAlertsVisible");
-  ok(!alertsVisible, "Alerts should not be present at the start of the test.");
+  add_task(async () => {
+    var alertsVisible = await chromeScript.sendQuery("anyXULAlertsVisible");
+    ok(!alertsVisible, "Alerts should not be present at the start of the test.");
+  });
 
   add_task(testNoPrincipal);
   add_task(testSystemPrincipal);
   add_task(testNullPrincipal);
   add_task(testNodePrincipal);
 }
 
 runTest();
--- a/toolkit/components/passwordmgr/test/mochitest/pwmgr_common.js
+++ b/toolkit/components/passwordmgr/test/mochitest/pwmgr_common.js
@@ -202,21 +202,21 @@ function enableMasterPassword() {
   setMasterPassword(true);
 }
 
 function disableMasterPassword() {
   setMasterPassword(false);
 }
 
 function setMasterPassword(enable) {
-  PWMGR_COMMON_PARENT.sendSyncMessage("setMasterPassword", { enable });
+  PWMGR_COMMON_PARENT.sendAsyncMessage("setMasterPassword", { enable });
 }
 
 function isLoggedIn() {
-  return PWMGR_COMMON_PARENT.sendSyncMessage("isLoggedIn")[0][0];
+  return PWMGR_COMMON_PARENT.sendQuery("isLoggedIn");
 }
 
 function logoutMasterPassword() {
   runInParent(function parent_logoutMasterPassword() {
     var sdr = Cc["@mozilla.org/security/sdr;1"].getService(Ci.nsISecretDecoderRing);
     sdr.logoutAndTeardown();
   });
 }
@@ -343,26 +343,29 @@ function setFormAndWaitForFieldFilled(fo
  * @param {Function} aFunction The test function to run
  */
 function runChecksAfterCommonInit(aFunction = null) {
   SimpleTest.waitForExplicitFinish();
   if (aFunction) {
     window.addEventListener("runTests", aFunction);
     PWMGR_COMMON_PARENT.addMessageListener("registerRunTests", () => registerRunTests());
   }
-  PWMGR_COMMON_PARENT.sendSyncMessage("setupParent", {testDependsOnDeprecatedLogin: gTestDependsOnDeprecatedLogin});
+  PWMGR_COMMON_PARENT.sendAsyncMessage("setupParent", {testDependsOnDeprecatedLogin: gTestDependsOnDeprecatedLogin});
   return PWMGR_COMMON_PARENT;
 }
 
 // Begin code that runs immediately for all tests that include this file.
 
 const PWMGR_COMMON_PARENT = runInParent(SimpleTest.getTestFileURL("pwmgr_common_parent.js"));
 
 SimpleTest.registerCleanupFunction(() => {
   SpecialPowers.popPrefEnv();
+
+  PWMGR_COMMON_PARENT.sendAsyncMessage("cleanup");
+
   runInParent(function cleanupParent() {
     // eslint-disable-next-line no-shadow
     const {Services} = ChromeUtils.import("resource://gre/modules/Services.jsm");
     // eslint-disable-next-line no-shadow
     const {LoginManagerParent} = ChromeUtils.import("resource://gre/modules/LoginManagerParent.jsm");
 
     // Remove all logins and disabled hosts
     Services.logins.removeAllLogins();
@@ -388,16 +391,17 @@ SimpleTest.registerCleanupFunction(() =>
       for (let note of notes) {
         note.remove();
       }
     }
   });
 });
 
 let { LoginHelper } = SpecialPowers.Cu.import("resource://gre/modules/LoginHelper.jsm", {});
+let { Services } = SpecialPowers.Cu.import("resource://gre/modules/Services.jsm", {});
 /**
  * Proxy for Services.logins (nsILoginManager).
  * Only supports arguments which support structured clone plus {nsILoginInfo}
  * Assumes properties are methods.
  */
 this.LoginManager = new Proxy({}, {
   get(target, prop, receiver) {
     return (...args) => {
@@ -406,20 +410,17 @@ this.LoginManager = new Proxy({}, {
         if (SpecialPowers.call_Instanceof(val, SpecialPowers.Ci.nsILoginInfo)) {
           loginInfoIndices.push(index);
           return LoginHelper.loginToVanillaObject(val);
         }
 
         return val;
       });
 
-      let messageRV = PWMGR_COMMON_PARENT.sendSyncMessage("proxyLoginManager", {
+      return PWMGR_COMMON_PARENT.sendQuery("proxyLoginManager", {
         args: cloneableArgs,
         loginInfoIndices,
         methodName: prop,
-      })[0];
-
-      // Handle methods with no return value such as removeLogin.
-      return messageRV ? messageRV[0] : undefined;
+      });
     };
   },
 });
 
--- a/toolkit/components/passwordmgr/test/mochitest/pwmgr_common_parent.js
+++ b/toolkit/components/passwordmgr/test/mochitest/pwmgr_common_parent.js
@@ -98,16 +98,22 @@ function onPrompt(subject, topic, data) 
   sendAsyncMessage("promptShown", {
     topic,
     data,
   });
 }
 Services.obs.addObserver(onPrompt, "passwordmgr-prompt-change");
 Services.obs.addObserver(onPrompt, "passwordmgr-prompt-save");
 
+addMessageListener("cleanup", () => {
+  Services.obs.removeObserver(onStorageChanged, "passwordmgr-storage-changed");
+  Services.obs.removeObserver(onPrompt, "passwordmgr-prompt-change");
+  Services.obs.removeObserver(onPrompt, "passwordmgr-prompt-save");
+});
+
 
 // Begin message listeners
 
 addMessageListener("setupParent", ({selfFilling = false, testDependsOnDeprecatedLogin = false} = {}) => {
   commonInit(selfFilling, testDependsOnDeprecatedLogin);
   sendAsyncMessage("doneSetup");
 });
 
@@ -173,11 +179,16 @@ addMessageListener("isLoggedIn", () => {
 addMessageListener("setMasterPassword", ({ enable }) => {
   if (enable) {
     LoginTestUtils.masterPassword.enable();
   } else {
     LoginTestUtils.masterPassword.disable();
   }
 });
 
-Services.mm.addMessageListener("PasswordManager:onFormSubmit", function onFormSubmit(message) {
+function onFormSubmit(message) {
   sendAsyncMessage("formSubmissionProcessed", message.data, message.objects);
+}
+
+Services.mm.addMessageListener("PasswordManager:onFormSubmit", onFormSubmit);
+addMessageListener("cleanup", () => {
+  Services.mm.removeMessageListener("PasswordManager:onFormSubmit", onFormSubmit);
 });
--- a/toolkit/components/passwordmgr/test/mochitest/test_autocomplete_https_upgrade.html
+++ b/toolkit/components/passwordmgr/test/mochitest/test_autocomplete_https_upgrade.html
@@ -85,18 +85,19 @@ async function setup(formUrl = HTTPS_FOR
   uname = iframeDoc.getElementById("form-basic-username");
   pword = iframeDoc.getElementById("form-basic-password");
 }
 
 add_task(async function test_autocomplete_http() {
   info("test_autocomplete_http, setup with " + HTTP_FORM_URL);
   await setup(HTTP_FORM_URL);
 
-  let logins = LoginManager.getAllLogins();
-  info("got logins: " + logins.map(l => l.origin));
+  LoginManager.getAllLogins().then(logins => {
+    info("got logins: " + logins.map(l => l.origin));
+  });
   // from a HTTP page, look for matching logins, we should never offer a login with an HTTPS scheme
   // we're expecting just login2 as a match
   // Make sure initial form is empty.
   checkLoginForm(uname, "", pword, "");
   // Trigger autocomplete popup
   restoreForm();
   let popupState = await getPopupState();
   is(popupState.open, false, "Check popup is initially closed");
@@ -210,19 +211,19 @@ add_task(async function test_delete_dupl
 
   let deletionPromise = promiseStorageChanged(["removeLogin"]);
   // On OS X, shift-backspace and shift-delete work, just delete does not.
   // On Win/Linux, shift-backspace does not work, delete and shift-delete do.
   synthesizeKey("KEY_Delete", {shiftKey: true});
   await deletionPromise;
   checkLoginForm(uname, "", pword, "");
 
-  is(LoginManager.countLogins("http://example.org", "http://example.org", null), 1,
+  is(await LoginManager.countLogins("http://example.org", "http://example.org", null), 1,
      "Check that the HTTP login remains");
-  is(LoginManager.countLogins("https://example.org", "https://example.org", null), 0,
+  is(await LoginManager.countLogins("https://example.org", "https://example.org", null), 0,
      "Check that the HTTPS login was deleted");
 
   // Two menu items should remain as the HTTPS login should have been deleted but
   // the HTTP would remain.
   let results = await notifyMenuChanged(2, "name2");
 
   checkAutoCompleteResults(results, ["name2"], hostname, "one login should remain after deleting the HTTPS name1");
   let popupState = await getPopupState();
--- a/toolkit/components/passwordmgr/test/mochitest/test_autocomplete_new_password.html
+++ b/toolkit/components/passwordmgr/test/mochitest/test_autocomplete_new_password.html
@@ -182,17 +182,17 @@ add_task(async function test_autofillAut
   await SimpleTest.promiseWaitForCondition(() => pword.value == "pass1", "Check pw filled");
   checkForm(2, "", "pass1");
 
   // No autocomplete results should appear for non-empty pw fields.
   synthesizeKey("KEY_ArrowDown");
   await promiseNoUnexpectedPopupShown();
 
   info("Removing all logins to test auto-saving of generated passwords");
-  LoginManager.removeAllLogins();
+  await LoginManager.removeAllLogins();
 
   while (pword.value) {
     synthesizeKey("KEY_Backspace");
   }
 
   info("This time select the generated password");
   shownPromise = promiseACShown();
   synthesizeKey("KEY_ArrowDown");
@@ -211,17 +211,17 @@ add_task(async function test_autofillAut
   await storageAddPromise;
 
   let expectedCount = 1;
   info(`filled generated password, check there are now ${expectedCount} generatedpassword telemetry events`);
   await waitForTelemetryEventsCondition(events => {
     return events.length == expectedCount;
   }, { process: "parent", filterProps: TelemetryFilterProps }, `Wait for there to be ${expectedCount} telemetry events`);
 
-  let logins = LoginManager.getAllLogins();
+  let logins = await LoginManager.getAllLogins();
   let timePasswordChanged = logins[logins.length - 1].timePasswordChanged;
   let time = dateAndTimeFormatter.format(new Date(timePasswordChanged));
   const LABEL_NO_USERNAME = "No username (" + time + ")";
 
   let generatedPW = pword.value;
   is(generatedPW.length, GENERATED_PASSWORD_LENGTH, "Check generated password length");
   ok(generatedPW.match(GENERATED_PASSWORD_REGEX), "Check generated password format");
 
@@ -249,17 +249,17 @@ add_task(async function test_autofillAut
   expectedCount = 2;
   try {
     telemetryEvents = await waitForTelemetryEventsCondition(events => events.length == expectedCount,
                                                             { process: "parent", filterProps: TelemetryFilterProps },
                                                             `Wait for there to be ${expectedCount} events`, 50);
   } catch (ex) {}
   ok(!telemetryEvents, "Expected to timeout waiting for there to be 2 events");
 
-  logins = LoginManager.getAllLogins();
+  logins = await LoginManager.getAllLogins();
   is(logins.length, 1, "Still 1 login after filling the generated password a 2nd time");
   is(logins[0].timePasswordChanged, timePasswordChanged, "Saved login wasn't changed");
   is(logins[0].password, generatedPW, "Password is the same");
 
   info("reset initial login state with login1");
   runInParent(initLogins);
   info("invalidate the autocomplete cache after updating storage above");
   synthesizeKey("KEY_Backspace");
@@ -267,30 +267,30 @@ add_task(async function test_autofillAut
 });
 
 add_task(async function test_autofillAutocompletePassword_saveLoginDisabled() {
   // form should not be filled
   checkForm(2, "", "");
   let formOrigin = new URL(document.documentURI).origin;
   is(formOrigin, "https://example.com", "Expected form origin");
 
-  LoginManager.setLoginSavingEnabled("https://example.com", false);
+  await LoginManager.setLoginSavingEnabled("https://example.com", false);
 
   let pword = $_(2, "pword");
   pword.focus();
   let shownPromise = promiseACShown();
   synthesizeKey("KEY_ArrowDown");
   let results = await shownPromise;
   let expectedACLabels = [
     // when login-saving is disabled for an origin, we expect no generated password row here
     "user1",
   ];
   info("ac results: " + results.join(", "));
   checkAutoCompleteResults(results, expectedACLabels, "example.com", "Check all rows are correct");
 
   document.getElementById("form2").reset();
-  LoginManager.setLoginSavingEnabled("https://example.com", true);
+  await LoginManager.setLoginSavingEnabled("https://example.com", true);
 });
 
 </script>
 </pre>
 </body>
 </html>
--- a/toolkit/components/passwordmgr/test/mochitest/test_autocomplete_password_open.html
+++ b/toolkit/components/passwordmgr/test/mochitest/test_autocomplete_password_open.html
@@ -72,17 +72,17 @@ add_task(async function test_two_logins(
   let expectedMenuItems = [
     "tempuser1",
     "tempuser2",
   ];
   checkAutoCompleteResults(results, expectedMenuItems, "example.com", "Check all menuitems are displayed correctly.");
 
   checkLoginForm(uname, "", pword, "");
   let removedPromise = promiseStorageChanged(["removeAllLogins"]);
-  LoginManager.removeAllLogins();
+  await LoginManager.removeAllLogins();
   await removedPromise;
 });
 
 add_task(async function test_zero_logins() {
   uname.focus();
 
   let shownPromise = promiseACShown().then(() => ok(false, "Should not have shown"));
   // Popup on the password field should NOT automatically open upon focus when there are no saved logins.
--- a/toolkit/components/passwordmgr/test/mochitest/test_autofill_different_formActionOrigin.html
+++ b/toolkit/components/passwordmgr/test/mochitest/test_autofill_different_formActionOrigin.html
@@ -25,24 +25,24 @@ let nsLoginInfo = SpecialPowers.wrap(Spe
   <iframe></iframe>
 </div>
 
 <pre id="test">
 <script class="testbody" type="text/javascript">
 let iframe = SpecialPowers.wrap(document.getElementsByTagName("iframe")[0]);
 
 async function prepareLoginsAndProcessForm(url, logins = []) {
-  LoginManager.removeAllLogins();
+  await LoginManager.removeAllLogins();
 
   let dates = Date.now();
   for (let login of logins) {
     SpecialPowers.do_QueryInterface(login, SpecialPowers.Ci.nsILoginMetaInfo);
     // Force all dates to be the same so they don't affect things like deduping.
     login.timeCreated = login.timePasswordChanged = login.timeLastUsed = dates;
-    LoginManager.addLogin(login);
+    await LoginManager.addLogin(login);
   }
 
   iframe.src = url;
   await promiseFormsProcessed();
 }
 
 add_task(async function test_formActionOrigin_wildcard_should_autofill() {
   await prepareLoginsAndProcessForm("https://example.com" + MISSING_ACTION_PATH, [
--- a/toolkit/components/passwordmgr/test/mochitest/test_autofill_different_subdomain.html
+++ b/toolkit/components/passwordmgr/test/mochitest/test_autofill_different_subdomain.html
@@ -32,24 +32,24 @@ let iframe = SpecialPowers.wrap(document
 
 function checkIframeLoginForm(expectedUsername, expectedPassword) {
   let iframeDoc = iframe.contentDocument;
   let uname = iframeDoc.getElementById("form-basic-username");
   let pword = iframeDoc.getElementById("form-basic-password");
   checkLoginForm(uname, expectedUsername, pword, expectedPassword);
 }
 async function prepareLoginsAndProcessForm(url, logins = []) {
-  LoginManager.removeAllLogins();
+  await LoginManager.removeAllLogins();
 
   let dates = Date.now();
   for (let login of logins) {
     SpecialPowers.do_QueryInterface(login, SpecialPowers.Ci.nsILoginMetaInfo);
     // Force all dates to be the same so they don't affect things like deduping.
     login.timeCreated = login.timePasswordChanged = login.timeLastUsed = dates;
-    LoginManager.addLogin(login);
+    await LoginManager.addLogin(login);
   }
 
   iframe.src = url;
   await promiseFormsProcessed();
 }
 
 add_task(async function test_login_with_different_subdomain_shouldnt_autofill_wildcard_formActionOrigin() {
   await prepareLoginsAndProcessForm("https://example.com" + MISSING_ACTION_PATH, [
--- a/toolkit/components/passwordmgr/test/mochitest/test_autofill_https_downgrade.html
+++ b/toolkit/components/passwordmgr/test/mochitest/test_autofill_https_downgrade.html
@@ -32,18 +32,18 @@ let iframe = SpecialPowers.wrap(document
 
 async function prepareAndProcessForm(url, login) {
   iframe.src = url;
   info("prepareAndProcessForm, assigned iframe.src: " + url);
   await promiseFormsProcessed();
 }
 
 async function checkFormsWithLogin(formUrls, login, expectedUsername, expectedPassword) {
-  LoginManager.removeAllLogins();
-  LoginManager.addLogin(login);
+  await LoginManager.removeAllLogins();
+  await LoginManager.addLogin(login);
 
   for (let url of formUrls) {
     info("start test_checkNoAutofillOnDowngrade w. url: " + url);
 
     await prepareAndProcessForm(url);
     info("form was processed");
     let iframeDoc = iframe.contentDocument;
     let uname = iframeDoc.getElementById("form-basic-username");
--- a/toolkit/components/passwordmgr/test/mochitest/test_autofill_https_upgrade.html
+++ b/toolkit/components/passwordmgr/test/mochitest/test_autofill_https_upgrade.html
@@ -26,24 +26,24 @@ let nsLoginInfo = SpecialPowers.wrap(Spe
   <iframe></iframe>
 </div>
 
 <pre id="test">
 <script class="testbody" type="text/javascript">
 let iframe = SpecialPowers.wrap(document.getElementsByTagName("iframe")[0]);
 
 async function prepareLoginsAndProcessForm(url, logins = []) {
-  LoginManager.removeAllLogins();
+  await LoginManager.removeAllLogins();
 
   let dates = Date.now();
   for (let login of logins) {
     SpecialPowers.do_QueryInterface(login, SpecialPowers.Ci.nsILoginMetaInfo);
     // Force all dates to be the same so they don't affect things like deduping.
     login.timeCreated = login.timePasswordChanged = login.timeLastUsed = dates;
-    LoginManager.addLogin(login);
+    await LoginManager.addLogin(login);
   }
 
   iframe.src = url;
   await promiseFormsProcessed();
 }
 
 add_task(async function setup() {
   await SpecialPowers.pushPrefEnv({"set": [["signon.schemeUpgrades", true]]});
--- a/toolkit/components/passwordmgr/test/mochitest/test_autofill_password-only.html
+++ b/toolkit/components/passwordmgr/test/mochitest/test_autofill_password-only.html
@@ -5,17 +5,17 @@
   <title>Test password-only forms should prefer a password-only login when present</title>
   <script src="/tests/SimpleTest/SimpleTest.js"></script>
   <script type="text/javascript" src="pwmgr_common.js"></script>
   <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
 </head>
 <body>
 Login Manager test: Bug 444968
 <script>
-PWMGR_COMMON_PARENT.sendSyncMessage("setupParent", { selfFilling: true });
+PWMGR_COMMON_PARENT.sendAsyncMessage("setupParent", { selfFilling: true });
 
 SimpleTest.waitForExplicitFinish();
 
 let chromeScript = runInParent(function chromeSetup() {
   const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm");
 
   let login1A  = Cc["@mozilla.org/login-manager/loginInfo;1"].
                  createInstance(Ci.nsILoginInfo);
@@ -50,17 +50,17 @@ let chromeScript = runInParent(function 
     Services.logins.removeLogin(login1A);
     Services.logins.removeLogin(login1B);
     Services.logins.removeLogin(login2A);
     Services.logins.removeLogin(login2B);
     Services.logins.removeLogin(login2C);
   });
 });
 
-SimpleTest.registerCleanupFunction(() => chromeScript.sendSyncMessage("removeLogins"));
+SimpleTest.registerCleanupFunction(() => chromeScript.sendAsyncMessage("removeLogins"));
 
 registerRunTests();
 </script>
 
 <p id="display"></p>
 <div id="content" style="display: none">
   <!-- first 3 forms have matching user+pass and pass-only logins -->
 
--- a/toolkit/components/passwordmgr/test/mochitest/test_basic_form_2pw_2.html
+++ b/toolkit/components/passwordmgr/test/mochitest/test_basic_form_2pw_2.html
@@ -51,22 +51,24 @@ function startTest() {
 // Called by each form's onsubmit handler.
 function checkSubmit(formNum) {
   numSubmittedForms++;
 
   // End the test at the last form.
   if (formNum == 999) {
     is(numSubmittedForms, 999, "Ensuring all forms submitted for testing.");
 
-    var numEndingLogins = LoginManager.countLogins("", "", "");
+    (async () => {
+      var numEndingLogins = await LoginManager.countLogins("", "", "");
 
-    ok(numEndingLogins > 0, "counting logins at end");
-    is(numStartingLogins, numEndingLogins + 222, "counting logins at end");
+      ok(numEndingLogins > 0, "counting logins at end");
+      is(numStartingLogins, numEndingLogins + 222, "counting logins at end");
 
-    SimpleTest.finish();
+      SimpleTest.finish();
+    })();
     return false; // return false to cancel current form submission
   }
 
   // submit the next form.
   var button = getFormSubmitButton(formNum + 1);
   button.click();
 
   return false; // return false to cancel current form submission
--- a/toolkit/components/passwordmgr/test/mochitest/test_basic_form_autocomplete.html
+++ b/toolkit/components/passwordmgr/test/mochitest/test_basic_form_autocomplete.html
@@ -381,28 +381,28 @@ add_task(async function test_form1_delet
 
   // XXX tried sending character "t" before/during dropdown to test
   // filtering, but had no luck. Seemed like the character was getting lost.
   // Setting uname.value didn't seem to work either. This works with a human
   // driver, so I'm not sure what's up.
 
   // Delete the first entry (of 4), "tempuser1"
   synthesizeKey("KEY_ArrowDown");
-  let numLogins = LoginManager.countLogins("https://example.com", "https://autocomplete:8888", null);
+  let numLogins = await LoginManager.countLogins("https://example.com", "https://autocomplete:8888", null);
   is(numLogins, 5, "Correct number of logins before deleting one");
 
   let countChangedPromise = notifyMenuChanged(4);
   var deletionPromise = promiseStorageChanged(["removeLogin"]);
   // On OS X, shift-backspace and shift-delete work, just delete does not.
   // On Win/Linux, shift-backspace does not work, delete and shift-delete do.
   synthesizeKey("KEY_Delete", {shiftKey: true});
   await deletionPromise;
 
   checkLoginForm(uname, "", pword, "");
-  numLogins = LoginManager.countLogins("https://example.com", "https://autocomplete:8888", null);
+  numLogins = await LoginManager.countLogins("https://example.com", "https://autocomplete:8888", null);
   is(numLogins, 4, "Correct number of logins after deleting one");
   await countChangedPromise;
   synthesizeKey("KEY_Enter");
   await promiseFormsProcessed();
   checkLoginForm(uname, "testuser2", pword, "testpass2");
 });
 
 add_task(async function test_form1_first_after_deletion() {
@@ -424,17 +424,17 @@ add_task(async function test_form1_delet
   synthesizeKey("KEY_ArrowDown"); // open
   await shownPromise;
 
   // Delete the second entry (of 3), "testuser3"
   synthesizeKey("KEY_ArrowDown");
   synthesizeKey("KEY_ArrowDown");
   synthesizeKey("KEY_Delete", {shiftKey: true});
   checkLoginForm(uname, "", pword, "");
-  let numLogins = LoginManager.countLogins("https://example.com", "https://autocomplete:8888", null);
+  let numLogins = await LoginManager.countLogins("https://example.com", "https://autocomplete:8888", null);
   is(numLogins, 3, "Correct number of logins after deleting one");
   synthesizeKey("KEY_Enter");
   await promiseFormsProcessed();
   checkLoginForm(uname, "zzzuser4", pword, "zzzpass4");
 });
 
 add_task(async function test_form1_first_after_deletion2() {
   restoreForm();
@@ -456,17 +456,17 @@ add_task(async function test_form1_delet
   await shownPromise;
 
   /* test 54 */
   // Delete the last entry (of 2), "zzzuser4"
   synthesizeKey("KEY_ArrowDown");
   synthesizeKey("KEY_ArrowDown");
   synthesizeKey("KEY_Delete", {shiftKey: true});
   checkLoginForm(uname, "", pword, "");
-  let numLogins = LoginManager.countLogins("https://example.com", "https://autocomplete:8888", null);
+  let numLogins = await LoginManager.countLogins("https://example.com", "https://autocomplete:8888", null);
   is(numLogins, 2, "Correct number of logins after deleting one");
   synthesizeKey("KEY_ArrowDown");
   synthesizeKey("KEY_Enter");
   await promiseFormsProcessed();
   checkLoginForm(uname, "testuser2", pword, "testpass2");
 });
 
 add_task(async function test_form1_first_after_3_deletions() {
@@ -489,22 +489,22 @@ add_task(async function test_form1_check
   await shownPromise;
 
   /* test 56 */
   // Delete the only remaining entry, "testuser2"
   synthesizeKey("KEY_ArrowDown");
   let storageChanged = promiseStorageChanged(["removeLogin", "removeLogin", "addLogin"]);
   synthesizeKey("KEY_Delete", {shiftKey: true});
   checkLoginForm(uname, "", pword, "");
-  let numLogins = LoginManager.countLogins("https://example.com", "https://autocomplete:8888", null);
+  let numLogins = await LoginManager.countLogins("https://example.com", "https://autocomplete:8888", null);
   is(numLogins, 1, "Correct number of logins after deleting one");
 
   // remove the logins for the previous tests
-  setupScript.sendSyncMessage("removeLogin", "login0");
-  setupScript.sendSyncMessage("addLogin", "login5");
+  setupScript.sendAsyncMessage("removeLogin", "login0");
+  setupScript.sendAsyncMessage("addLogin", "login5");
   await storageChanged;
 });
 
 /* Tests for single-user forms for ignoring autocomplete=off */
 add_task(async function test_form2() {
   await setFormAndWaitForFieldFilled(`
     <form id="form2" action="https://autocomplete2" onsubmit="return false;">
       <input  type="text"       name="uname">
@@ -626,24 +626,24 @@ add_task(async function test_form6_chang
   synthesizeKey("X", {shiftKey: true});
   // Trigger the 'blur' event on uname
   pword.focus();
   await spinEventLoop();
   checkLoginForm(uname, "singleuser5X", pword, "singlepass5");
   uname.focus();
 
   let storageChanged = promiseStorageChanged(["removeLogin"]);
-  setupScript.sendSyncMessage("removeLogin", "login5");
+  setupScript.sendAsyncMessage("removeLogin", "login5");
   await storageChanged;
 });
 
 add_task(async function test_form7() {
   let storageChanged = promiseStorageChanged(["addLogin", "addLogin"]);
-  setupScript.sendSyncMessage("addLogin", "login6A");
-  setupScript.sendSyncMessage("addLogin", "login6B");
+  setupScript.sendAsyncMessage("addLogin", "login6A");
+  setupScript.sendAsyncMessage("addLogin", "login6B");
   await storageChanged;
   await setFormAndWaitForFieldFilled(`
     <!-- This form will be manipulated to insert a different username field. -->
     <form id="form7" action="https://autocomplete3" onsubmit="return false;">
       <input  type="text"       name="uname">
       <input  type="password"   name="pword">
       <button type="submit">Submit</button>
     </form>`, {fieldSelector: `input[name="uname"]`, fieldValue: ""});
@@ -659,17 +659,17 @@ add_task(async function test_form7() {
   newField.setAttribute("name", "uname2");
   pword.parentNode.insertBefore(newField, pword);
   is($_(7, "uname2").value, "", "Verifying empty uname2");
 
   // Delete login6B. It was created just to prevent filling in a login
   // automatically, removing it makes it more likely that we'll catch a
   // future regression with form filling here.
   storageChanged = promiseStorageChanged(["removeLogin"]);
-  setupScript.sendSyncMessage("removeLogin", "login6B");
+  setupScript.sendAsyncMessage("removeLogin", "login6B");
   await storageChanged;
 });
 
 add_task(async function test_form7_2() {
   restoreForm();
   let shownPromise = promiseACShown();
   synthesizeKey("KEY_ArrowDown"); // open
   let results = await shownPromise;
@@ -688,23 +688,23 @@ add_task(async function test_form7_2() {
   // the password field should have any values filled in.
   await SimpleTest.promiseWaitForCondition(() => uname.value == "form7user1",
                                            "Wait for username to get filled");
   checkLoginForm(uname, "form7user1", pword, "");
   is($_(7, "uname2").value, "", "Verifying empty uname2");
   restoreForm(); // clear field, so reloading test doesn't fail
 
   let storageChanged = promiseStorageChanged(["removeLogin"]);
-  setupScript.sendSyncMessage("removeLogin", "login6A");
+  setupScript.sendAsyncMessage("removeLogin", "login6A");
   await storageChanged;
 });
 
 add_task(async function test_form8() {
   let storageChanged = promiseStorageChanged(["addLogin"]);
-  setupScript.sendSyncMessage("addLogin", "login7");
+  setupScript.sendAsyncMessage("addLogin", "login7");
   await storageChanged;
   await setFormAndWaitForFieldFilled(`
     <!-- This form will be manipulated to insert a different username field. -->
     <form id="form7" action="https://autocomplete3" onsubmit="return false;">
       <input  type="text"       name="uname">
       <input  type="password"   name="pword">
       <button type="submit">Submit</button>
     </form>
@@ -730,19 +730,19 @@ add_task(async function test_form8_blur(
 add_task(async function test_form8_2() {
   checkLoginForm(uname, "", pword, "");
   restoreForm();
 });
 
 add_task(async function test_form8_3() {
   checkLoginForm(uname, "", pword, "");
   let storageChanged = promiseStorageChanged(["removeLogin", "addLogin", "addLogin"]);
-  setupScript.sendSyncMessage("removeLogin", "login7");
-  setupScript.sendSyncMessage("addLogin", "login8A");
-  setupScript.sendSyncMessage("addLogin", "login8B");
+  setupScript.sendAsyncMessage("removeLogin", "login7");
+  setupScript.sendAsyncMessage("addLogin", "login8A");
+  setupScript.sendAsyncMessage("addLogin", "login8B");
   await storageChanged;
 });
 
 add_task(async function test_form9_filtering() {
   await setFormAndWaitForFieldFilled(`
     <!-- test autocomplete dropdown -->
     <form id="form9" action="https://autocomplete5" onsubmit="return false;">
       <input  type="text"       name="uname">
@@ -785,37 +785,37 @@ add_task(async function test_form9_filte
   checkLoginForm(uname, "form9userAAB", pword, "form9pass");
 });
 
 add_task(async function test_form9_autocomplete_cache() {
   // Note that this addLogin call will only be seen by the autocomplete
   // attempt for the synthesizeKey if we do not successfully cache the
   // autocomplete results.
   let storageChanged = promiseStorageChanged(["addLogin"]);
-  setupScript.sendSyncMessage("addLogin", "login8C");
+  setupScript.sendAsyncMessage("addLogin", "login8C");
   await storageChanged;
   uname.focus();
   let promise1 = notifyMenuChanged(1);
   sendString("z");
   let results = await promise1;
   checkAutoCompleteResults(results, [], "example.com", "Check popup does not have any login items");
 
   // check that empty results are cached - bug 496466
   promise1 = notifyMenuChanged(1);
   sendString("z");
   results = await promise1;
   checkAutoCompleteResults(results, [], "example.com", "Check popup only has the footer when it opens");
 });
 
 add_task(async function test_form11_formless() {
   let storageChanged = promiseStorageChanged(["removeLogin", "removeLogin", "removeLogin", "addLogin"]);
-  setupScript.sendSyncMessage("removeLogin", "login8A");
-  setupScript.sendSyncMessage("removeLogin", "login8B");
-  setupScript.sendSyncMessage("removeLogin", "login8C");
-  setupScript.sendSyncMessage("addLogin", "login11");
+  setupScript.sendAsyncMessage("removeLogin", "login8A");
+  setupScript.sendAsyncMessage("removeLogin", "login8B");
+  setupScript.sendAsyncMessage("removeLogin", "login8C");
+  setupScript.sendAsyncMessage("addLogin", "login11");
   await storageChanged;
   await setFormAndWaitForFieldFilled(`
     <!-- tests <form>-less autocomplete -->
     <div id="form11">
       <input  type="text"       name="uname" id="uname">
       <input  type="password"   name="pword" id="pword">
       <button type="submit">Submit</button>
     </div>`, {fieldSelector: `input[name="uname"]`, fieldValue: "testuser11"});
@@ -859,23 +859,23 @@ add_task(async function test_form11_open
   await firePrivEventPromise;
   await shownPromise;
   synthesizeKey("KEY_ArrowDown");
   const processedPromise = promiseFormsProcessed();
   synthesizeKey("KEY_Enter");
   await processedPromise;
   checkLoginForm(uname, "testuser11", pword, "testpass11");
   let storageChanged = promiseStorageChanged(["removeLogin"]);
-  setupScript.sendSyncMessage("removeLogin", "login11");
+  setupScript.sendAsyncMessage("removeLogin", "login11");
   await storageChanged;
 });
 
 add_task(async function test_form12_recipes() {
   let storageChanged = promiseStorageChanged(["addLogin"]);
-  setupScript.sendSyncMessage("addLogin", "login10");
+  setupScript.sendAsyncMessage("addLogin", "login10");
   await storageChanged;
   await setFormAndWaitForFieldFilled(`
     <!-- test for onUsernameInput recipe testing -->
     <form id="form12" action="https://autocomplete7" onsubmit="return false;">
       <input  type="text"   name="1">
       <input  type="text"   name="2">
       <button type="submit">Submit</button>
     </form>`, {fieldSelector: `input[name="1"]`, fieldValue: ""});
--- a/toolkit/components/passwordmgr/test/mochitest/test_basic_form_autocomplete_subdomain.html
+++ b/toolkit/components/passwordmgr/test/mochitest/test_basic_form_autocomplete_subdomain.html
@@ -102,17 +102,17 @@ add_task(async function test_form1_menu_
   synthesizeKey("KEY_ArrowDown"); // open
   let results = await shownPromise;
 
   let popupState = await getPopupState();
   is(popupState.selectedIndex, -1, "Check no entries are selected upon opening");
 
   // The logins are added "today" and since they are duplicates, the date that they were last
   // changed will be appended.
-  let dateString = setupScript.sendSyncMessage("getDateString");
+  let dateString = await setupScript.sendQuery("getDateString");
   let username = `dsdu1 (${dateString})`;
 
   checkAutoCompleteResults(results, [username, username], "example.com", "Check all menuitems are displayed correctly.");
 
   synthesizeKey("KEY_ArrowDown"); // first item
   checkLoginForm(uname, "", pword, ""); // value shouldn't update just by selecting
 
   synthesizeKey("KEY_Enter");
--- a/toolkit/components/passwordmgr/test/mochitest/test_insecure_form_field_autocomplete.html
+++ b/toolkit/components/passwordmgr/test/mochitest/test_insecure_form_field_autocomplete.html
@@ -388,28 +388,28 @@ add_task(async function test_form1_delet
   // filtering, but had no luck. Seemed like the character was getting lost.
   // Setting uname.value didn't seem to work either. This works with a human
   // driver, so I'm not sure what's up.
 
   synthesizeKey("KEY_ArrowDown"); // skip insecure warning
   // Delete the first entry (of 4), "tempuser1"
   synthesizeKey("KEY_ArrowDown");
   var numLogins;
-  numLogins = LoginManager.countLogins("http://mochi.test:8888", "", null);
+  numLogins = await LoginManager.countLogins("http://mochi.test:8888", "", null);
   is(numLogins, 5, "Correct number of logins before deleting one");
 
   let countChangedPromise = notifyMenuChanged(5);
   var deletionPromise = promiseStorageChanged(["removeLogin"]);
   // On OS X, shift-backspace and shift-delete work, just delete does not.
   // On Win/Linux, shift-backspace does not work, delete and shift-delete do.
   synthesizeKey("KEY_Delete", {shiftKey: true});
   await deletionPromise;
 
   checkLoginForm(uname, "", pword, "");
-  numLogins = LoginManager.countLogins("http://mochi.test:8888", "", null);
+  numLogins = await LoginManager.countLogins("http://mochi.test:8888", "", null);
   is(numLogins, 4, "Correct number of logins after deleting one");
   await countChangedPromise;
   synthesizeKey("KEY_Enter");
   await promiseFormsProcessed();
   checkLoginForm(uname, "testuser2", pword, "testpass2");
 });
 
 add_task(async function test_form1_first_after_deletion() {
@@ -435,17 +435,17 @@ add_task(async function test_form1_delet
   synthesizeKey("KEY_ArrowDown"); // skip insecure warning
   // Delete the second entry (of 3), "testuser3"
   synthesizeKey("KEY_ArrowDown");
   synthesizeKey("KEY_ArrowDown");
   let storageChanged = promiseStorageChanged(["removeLogin"]);
   synthesizeKey("KEY_Delete", {shiftKey: true});
   await storageChanged;
   checkLoginForm(uname, "", pword, "");
-  let numLogins = LoginManager.countLogins("http://mochi.test:8888", "", null);
+  let numLogins = await LoginManager.countLogins("http://mochi.test:8888", "", null);
   is(numLogins, 3, "Correct number of logins after deleting one");
   synthesizeKey("KEY_Enter");
   await promiseFormsProcessed();
   checkLoginForm(uname, "zzzuser4", pword, "zzzpass4");
 });
 
 add_task(async function test_form1_first_after_deletion2() {
   restoreForm();
@@ -471,17 +471,17 @@ add_task(async function test_form1_delet
   // test 54
   // Delete the last entry (of 2), "zzzuser4"
   synthesizeKey("KEY_ArrowDown");
   synthesizeKey("KEY_ArrowDown");
   let storageChanged = promiseStorageChanged(["removeLogin"]);
   synthesizeKey("KEY_Delete", {shiftKey: true});
   await storageChanged;
   checkLoginForm(uname, "", pword, "");
-  let numLogins = LoginManager.countLogins("http://mochi.test:8888", "", null);
+  let numLogins = await LoginManager.countLogins("http://mochi.test:8888", "", null);
   is(numLogins, 2, "Correct number of logins after deleting one");
   synthesizeKey("KEY_ArrowDown"); // skip insecure warning
   synthesizeKey("KEY_ArrowDown");
   synthesizeKey("KEY_Enter");
   await promiseFormsProcessed();
   checkLoginForm(uname, "testuser2", pword, "testpass2");
 });
 
@@ -507,28 +507,28 @@ add_task(async function test_form1_check
 
   synthesizeKey("KEY_ArrowDown"); // skip insecure warning
   // test 56
   // Delete the only remaining entry, "testuser2"
   synthesizeKey("KEY_ArrowDown");
   let storageChanged = promiseStorageChanged(["removeLogin", "removeLogin"]);
   synthesizeKey("KEY_Delete", {shiftKey: true});
   checkLoginForm(uname, "", pword, "");
-  let numLogins = LoginManager.countLogins("http://mochi.test:8888", "", null);
+  let numLogins = await LoginManager.countLogins("http://mochi.test:8888", "", null);
   is(numLogins, 1, "Correct number of logins after deleting one");
 
   // remove the login that's not shown in the list.
-  setupScript.sendSyncMessage("removeLogin", "login0");
+  setupScript.sendAsyncMessage("removeLogin", "login0");
   await storageChanged;
 });
 
 // Tests for single-user forms for ignoring autocomplete=off
 add_task(async function test_form2() {
   let storageChanged = promiseStorageChanged(["addLogin"]);
-  setupScript.sendSyncMessage("addLogin", "login5");
+  setupScript.sendAsyncMessage("addLogin", "login5");
   await storageChanged;
   await setFormAndWaitForFieldFilled(`
     <!-- other forms test single logins, with autocomplete=off set -->
     <form id="form2" action="http://autocomplete2" onsubmit="return false;">
       <input  type="text"       name="uname">
       <input  type="password"   name="pword" autocomplete="off">
       <button type="submit">Submit</button>
     </form>`, {fieldSelector: `input[name="uname"]`, fieldValue: "singleuser5"});
@@ -651,24 +651,24 @@ add_task(async function test_form6_chang
   synthesizeKey("X", {shiftKey: true});
   // Trigger the 'blur' event on uname
   pword.focus();
   await spinEventLoop();
   checkLoginForm(uname, "singleuser5X", pword, "singlepass5");
   uname.focus();
 
   let storageChanged = promiseStorageChanged(["removeLogin"]);
-  setupScript.sendSyncMessage("removeLogin", "login5");
+  setupScript.sendAsyncMessage("removeLogin", "login5");
   await storageChanged;
 });
 
 add_task(async function test_form7() {
   let storageChanged = promiseStorageChanged(["addLogin", "addLogin"]);
-  setupScript.sendSyncMessage("addLogin", "login6A");
-  setupScript.sendSyncMessage("addLogin", "login6B");
+  setupScript.sendAsyncMessage("addLogin", "login6A");
+  setupScript.sendAsyncMessage("addLogin", "login6B");
   await storageChanged;
   await setFormAndWaitForFieldFilled(`
     <!-- This form will be manipulated to insert a different username field. -->
     <form id="form7" action="http://autocomplete3" onsubmit="return false;">
       <input  type="text"       name="uname">
       <input  type="password"   name="pword">
       <button type="submit">Submit</button>
     </form>`, {fieldSelector: `input[name="uname"]`, fieldValue: ""});
@@ -684,17 +684,17 @@ add_task(async function test_form7() {
   newField.setAttribute("name", "uname2");
   pword.parentNode.insertBefore(newField, pword);
   is($_(7, "uname2").value, "", "Verifying empty uname2");
 
   // Delete login6B. It was created just to prevent filling in a login
   // automatically, removing it makes it more likely that we'll catch a
   // future regression with form filling here.
   storageChanged = promiseStorageChanged(["removeLogin"]);
-  setupScript.sendSyncMessage("removeLogin", "login6B");
+  setupScript.sendAsyncMessage("removeLogin", "login6B");
   await storageChanged;
 });
 
 add_task(async function test_form7_2() {
   restoreForm();
   let shownPromise = promiseACShown();
   synthesizeKey("KEY_ArrowDown"); // open
   await shownPromise;
@@ -708,23 +708,23 @@ add_task(async function test_form7_2() {
   // selected autocomplete value, but neither the new username field nor
   // the password field should have any values filled in.
   await spinEventLoop();
   checkLoginForm(uname, "form7user1", pword, "");
   is($_(7, "uname2").value, "", "Verifying empty uname2");
   restoreForm(); // clear field, so reloading test doesn't fail
 
   let storageChanged = promiseStorageChanged(["removeLogin"]);
-  setupScript.sendSyncMessage("removeLogin", "login6A");
+  setupScript.sendAsyncMessage("removeLogin", "login6A");
   await storageChanged;
 });
 
 add_task(async function test_form8() {
   let storageChanged = promiseStorageChanged(["addLogin"]);
-  setupScript.sendSyncMessage("addLogin", "login7");
+  setupScript.sendAsyncMessage("addLogin", "login7");
   await storageChanged;
   await setFormAndWaitForFieldFilled(`
     <!-- This form will be manipulated to insert a different username field. -->
     <form id="form7" action="http://autocomplete3" onsubmit="return false;">
       <input  type="text"       name="uname">
       <input  type="password"   name="pword">
       <button type="submit">Submit</button>
     </form>
@@ -754,19 +754,19 @@ add_task(async function test_form8_2() {
 });
 
 add_task(async function test_form8_3() {
   checkLoginForm(uname, "", pword, "");
 });
 
 add_task(async function test_form9_filtering() {
   let storageChanged = promiseStorageChanged(["removeLogin", "addLogin", "addLogin"]);
-  setupScript.sendSyncMessage("removeLogin", "login7");
-  setupScript.sendSyncMessage("addLogin", "login8A");
-  setupScript.sendSyncMessage("addLogin", "login8B");
+  setupScript.sendAsyncMessage("removeLogin", "login7");
+  setupScript.sendAsyncMessage("addLogin", "login8A");
+  setupScript.sendAsyncMessage("addLogin", "login8B");
   await storageChanged;
   await setFormAndWaitForFieldFilled(`
     <!-- test autocomplete dropdown -->
     <form id="form9" action="http://autocomplete5" onsubmit="return false;">
       <input  type="text"       name="uname">
       <input  type="password"   name="pword">
       <button type="submit">Submit</button>
     </form>`, {fieldSelector: `input[name="uname"]`, fieldValue: ""});
@@ -809,17 +809,17 @@ add_task(async function test_form9_filte
   checkLoginForm(uname, "form9userAAB", pword, "form9pass");
 });
 
 add_task(async function test_form9_autocomplete_cache() {
   // Note that this addLogin call will only be seen by the autocomplete
   // attempt for the synthesizeKey if we do not successfully cache the
   // autocomplete results.
   let storageChanged = promiseStorageChanged(["addLogin"]);
-  setupScript.sendSyncMessage("addLogin", "login8C");
+  setupScript.sendAsyncMessage("addLogin", "login8C");
   await storageChanged;
   uname.focus();
   let promise2 = notifyMenuChanged(2);
   let shownPromise = promiseACShown();
   sendString("z");
   let results = await promise2;
   await shownPromise;
   checkAutoCompleteResults(results,
@@ -830,25 +830,25 @@ add_task(async function test_form9_autoc
   promise2 = notifyMenuChanged(2);
   sendString("z");
   results = await promise2;
   checkAutoCompleteResults(results,
                            ["This connection is not secure. Logins entered here could be compromised. Learn More"],
                            "mochi.test",
                            "Check popup only has the footer and insecure warning");
   storageChanged = promiseStorageChanged(["removeLogin", "removeLogin", "removeLogin"]);
-  setupScript.sendSyncMessage("removeLogin", "login8A");
-  setupScript.sendSyncMessage("removeLogin", "login8B");
-  setupScript.sendSyncMessage("removeLogin", "login8C");
+  setupScript.sendAsyncMessage("removeLogin", "login8A");
+  setupScript.sendAsyncMessage("removeLogin", "login8B");
+  setupScript.sendAsyncMessage("removeLogin", "login8C");
   await storageChanged;
 });
 
 add_task(async function test_form11_recipes() {
   let storageChanged = promiseStorageChanged(["addLogin"]);
-  setupScript.sendSyncMessage("addLogin", "login10");
+  setupScript.sendAsyncMessage("addLogin", "login10");
   await storageChanged;
   await loadRecipes({
     siteRecipes: [{
       "hosts": ["mochi.test:8888"],
       "usernameSelector": "input[name='1']",
       "passwordSelector": "input[name='2']",
     }],
   });
--- a/toolkit/components/passwordmgr/test/mochitest/test_master_password.html
+++ b/toolkit/components/passwordmgr/test/mochitest/test_master_password.html
@@ -47,19 +47,19 @@ runInParent(() => {
   Services.logins.addLogin(login1);
   Services.logins.addLogin(login2);
 });
 
 var iframe1 = document.getElementById("iframe1");
 var iframe2 = document.getElementById("iframe2");
 
 add_task(async function test_1() {
-  ok(isLoggedIn(), "should be initially logged in (no MP)");
+  ok(await isLoggedIn(), "should be initially logged in (no MP)");
   enableMasterPassword();
-  ok(!isLoggedIn(), "should be logged out after setting MP");
+  ok(!await isLoggedIn(), "should be logged out after setting MP");
 
   // Trigger a MP prompt via the API
   var state = {
     msg: "Please enter your master password.",
     title: "the title",
     textValue: "",
     passValue: "",
     iconClass: "authentication-icon question-icon",
@@ -73,24 +73,24 @@ add_task(async function test_1() {
     defButton: "button0",
   };
   var action = {
     buttonClick: "ok",
     passField: MASTER_PASSWORD,
   };
   var promptDone = handlePrompt(state, action);
 
-  var logins = LoginManager.getAllLogins();
+  var logins = await LoginManager.getAllLogins();
 
   await promptDone;
   is(logins.length, 3, "expected number of logins");
 
-  ok(isLoggedIn(), "should be logged in after MP prompt");
+  ok(await isLoggedIn(), "should be logged in after MP prompt");
   logoutMasterPassword();
-  ok(!isLoggedIn(), "should be logged out");
+  ok(!await isLoggedIn(), "should be logged out");
 });
 
 add_task(async function test_2() {
   // Try again but click cancel.
   var state = {
     msg: "Please enter your master password.",
     title: "the title",
     textValue: "",
@@ -105,20 +105,20 @@ add_task(async function test_2() {
     focused: "passField",
     defButton: "button0",
   };
   var action = {
     buttonClick: "cancel",
   };
   var promptDone = handlePrompt(state, action);
 
-  var logins = LoginManager.getAllLogins();
+  var logins = await LoginManager.getAllLogins().catch(() => {});
   await promptDone;
   is(logins, undefined, "shouldn't have gotten logins");
-  ok(!isLoggedIn(), "should still be logged out");
+  ok(!await isLoggedIn(), "should still be logged out");
 });
 
 add_task(async function test_3() {
   var state = {
     msg: "Please enter your master password.",
     title: "the title",
     textValue: "",
     passValue: "",
@@ -152,19 +152,19 @@ add_task(async function test_3() {
   var u = SpecialPowers.wrap(iframe1).contentDocument.getElementById("userfield");
   var p = SpecialPowers.wrap(iframe1).contentDocument.getElementById("passfield");
   is(u.value, "user1", "checking expected user to have been filled in");
   is(p.value, "pass1", "checking expected pass to have been filled in");
   info("clearing fields to not cause a submission when the next document is loaded");
   u.value = "";
   p.value = "";
 
-  ok(isLoggedIn(), "should be logged in");
+  ok(await isLoggedIn(), "should be logged in");
   logoutMasterPassword();
-  ok(!isLoggedIn(), "should be logged out");
+  ok(!await isLoggedIn(), "should be logged out");
 });
 
 add_task(async function test_4() {
   var state = {
     msg: "Please enter your master password.",
     title: "the title",
     textValue: "",
     passValue: "",
@@ -189,17 +189,17 @@ add_task(async function test_4() {
   await promptDone;
 
   // check contents of iframe1 fields
   var u = SpecialPowers.wrap(iframe1).contentDocument.getElementById("userfield");
   var p = SpecialPowers.wrap(iframe1).contentDocument.getElementById("passfield");
   is(u.value, "", "checking expected empty user");
   is(p.value, "", "checking expected empty pass");
 
-  ok(!isLoggedIn(), "should be logged out");
+  ok(!await isLoggedIn(), "should be logged out");
 
   // XXX check that there's 1 MP window open
 
   // Load another iframe with a login form
   // This should detect that there's already a pending MP prompt, and not
   // put up a second one.
   var loadPromise = new Promise(resolve => {
     iframe2.addEventListener("load", function onload() {
@@ -228,17 +228,17 @@ add_task(async function test_4() {
 
   // check contents of iframe2 fields
   u = SpecialPowers.wrap(iframe2).contentDocument.getElementById("userfield");
   p = SpecialPowers.wrap(iframe2).contentDocument.getElementById("passfield");
   is(u.value, "", "checking expected empty user");
   is(p.value, "", "checking expected empty pass");
 
   // XXX check that there's 1 MP window open
-  ok(!isLoggedIn(), "should be logged out");
+  ok(!await isLoggedIn(), "should be logged out");
 
   // Ok, now enter the MP. The MP prompt is already up.
 
   var fillPromise = promiseFormsProcessed(2);
 
   // fill existing MP dialog with MP.
   action = {
     buttonClick: "ok",
@@ -246,17 +246,17 @@ add_task(async function test_4() {
   };
   await handlePrompt(state, action);
   await fillPromise;
 
   // We shouldn't have to worry about iframe1's load event racing with
   // filling of iframe2's data. We notify observers synchronously, so
   // iframe2's observer will process iframe2 before iframe1 even finishes
   // processing the form.
-  ok(isLoggedIn(), "should be logged in");
+  ok(await isLoggedIn(), "should be logged in");
 
   // check contents of iframe1 fields
   u = SpecialPowers.wrap(iframe1).contentDocument.getElementById("userfield");
   p = SpecialPowers.wrap(iframe1).contentDocument.getElementById("passfield");
   is(u.value, "user2", "checking expected user to have been filled in");
   is(p.value, "pass2", "checking expected pass to have been filled in");
 
   info("clearing fields to not cause a submission when the next document is loaded");
--- a/toolkit/components/passwordmgr/test/mochitest/test_password_field_autocomplete.html
+++ b/toolkit/components/passwordmgr/test/mochitest/test_password_field_autocomplete.html
@@ -88,20 +88,16 @@ var setupScript = runInParent(function s
     <input  type="password"   name="pword" disabled="true">
     <button type="submit">Submit</button>
   </form>
 
 </div>
 
 <pre id="test">
 <script class="testbody" type="text/javascript">
-
-var { Services } =
-  SpecialPowers.Cu.import("resource://gre/modules/Services.jsm");
-
 /** Test for Login Manager: multiple login autocomplete. **/
 
 var uname = $_(1, "uname");
 var pword = $_(1, "pword");
 
 // Restore the form to the default state.
 async function reinitializeForm(index) {
   // Using innerHTML is for creating the autocomplete popup again, so the
--- a/toolkit/components/passwordmgr/test/mochitest/test_prompt_async.html
+++ b/toolkit/components/passwordmgr/test/mochitest/test_prompt_async.html
@@ -101,18 +101,17 @@
               return this;
             },
 
             onProxyAvailable(req, uri, pi, status) {
               // Add logins using the proxy host and port used by the mochitest harness.
               mozproxyOrigin = "moz-proxy://" + SpecialPowers.wrap(pi).host + ":" +
                 SpecialPowers.wrap(pi).port;
 
-              pwmgrParent.sendSyncMessage("initLogins", {mozproxyOrigin});
-              resolve();
+              resolve(pwmgrParent.sendQuery("initLogins", {mozproxyOrigin}));
             },
           });
 
           // Need to allow for arbitrary network servers defined in PAC instead of a hardcoded moz-proxy.
           let channel = NetUtil.newChannel({
             uri: "http://example.com",
             loadUsingSystemPrincipal: true,
           });
--- a/toolkit/components/prompts/test/prompt_common.js
+++ b/toolkit/components/prompts/test/prompt_common.js
@@ -152,20 +152,24 @@ function PrompterProxy(chromeScript) {
             outParams = [/* user */ 4, /* pwd */ 5];
             break;
           }
           default: {
             throw new Error("Unknown nsIAuthPrompt method");
           }
         }
 
-        let result = chromeScript.sendSyncMessage("proxyPrompter", {
+        let result;
+        chromeScript.sendQuery("proxyPrompter", {
           args,
           methodName: prop,
-        })[0][0];
+        }).then(val => {
+          result = val;
+        });
+        SpecialPowers.Services.tm.spinEventLoopUntil(() => result);
 
         for (let outParam of outParams) {
           // Copy the out or inout param value over the original
           args[outParam].value = result.args[outParam].value;
         }
 
         if (prop == "promptAuth") {
           args[2].username = result.args[2].username;
--- a/toolkit/components/url-classifier/tests/mochitest/classifierHelper.js
+++ b/toolkit/components/url-classifier/tests/mochitest/classifierHelper.js
@@ -31,31 +31,31 @@ classifierHelper.waitForInit = function(
     gScript.sendAsyncMessage("waitForInit");
   });
 };
 
 // This function is used to allow completion for specific "list",
 // some lists like "test-malware-simple" is default disabled to ask for complete.
 // "list" is the db we would like to allow it
 // "url" is the completion server
-classifierHelper.allowCompletion = function(lists, url) {
+classifierHelper.allowCompletion = async function(lists, url) {
   for (var list of lists) {
     // Add test db to provider
     var pref = await SpecialPowers.getParentCharPref(PREFS.PROVIDER_LISTS);
     pref += "," + list;
-    SpecialPowers.setCharPref(PREFS.PROVIDER_LISTS, pref);
+    await SpecialPowers.setCharPref(PREFS.PROVIDER_LISTS, pref);
 
     // Rename test db so we will not disallow it from completions
     pref = await SpecialPowers.getParentCharPref(PREFS.DISALLOW_COMPLETIONS);
     pref = pref.replace(list, list + "-backup");
-    SpecialPowers.setCharPref(PREFS.DISALLOW_COMPLETIONS, pref);
+    await SpecialPowers.setCharPref(PREFS.DISALLOW_COMPLETIONS, pref);
   }
 
   // Set get hash url
-  SpecialPowers.setCharPref(PREFS.PROVIDER_GETHASHURL, url);
+  await SpecialPowers.setCharPref(PREFS.PROVIDER_GETHASHURL, url);
 };
 
 // Pass { url: ..., db: ... } to add url to database,
 // onsuccess/onerror will be called when update complete.
 classifierHelper.addUrlToDB = function(updateData) {
   return new Promise(function(resolve, reject) {
     var testUpdate = "";
     for (var update of updateData) {
--- a/toolkit/components/url-classifier/tests/mochitest/test_bug1254766.html
+++ b/toolkit/components/url-classifier/tests/mochitest/test_bug1254766.html
@@ -61,18 +61,18 @@ function getGethashCounter() {
       }
     };
     xhr.send();
   });
 }
 
 // setup function allows classifier send gethash request for test database
 // also it calculate to fullhash for url and store those hashes in gethash sjs.
-function setup() {
-  classifierHelper.allowCompletion([MALWARE_LIST, UNWANTED_LIST], GETHASH_URL);
+async function setup() {
+  await classifierHelper.allowCompletion([MALWARE_LIST, UNWANTED_LIST], GETHASH_URL);
 
   return Promise.all([
     addCompletionToServer(MALWARE_LIST, MALWARE_HOST1, GETHASH_URL),
     addCompletionToServer(MALWARE_LIST, MALWARE_HOST2, GETHASH_URL),
     addCompletionToServer(UNWANTED_LIST, UNWANTED_HOST1, GETHASH_URL),
     addCompletionToServer(UNWANTED_LIST, UNWANTED_HOST2, GETHASH_URL),
   ]);
 }
--- a/toolkit/components/url-classifier/tests/mochitest/test_cryptomining.html
+++ b/toolkit/components/url-classifier/tests/mochitest/test_cryptomining.html
@@ -65,40 +65,31 @@ async function runTest(test) {
   is(result, test.loadExpected, "The loading happened correctly (by table)");
 }
 
 async function runTests() {
   let chromeScript = SpecialPowers.loadChromeScript(_ => {
     const {UrlClassifierTestUtils} = ChromeUtils.import("resource://testing-common/UrlClassifierTestUtils.jsm");
 
     addMessageListener("loadTrackers", __ => {
-      UrlClassifierTestUtils.addTestTrackers().then(___ => {
-        sendAsyncMessage("trackersLoaded");
-      });
+      return UrlClassifierTestUtils.addTestTrackers();
     });
 
     addMessageListener("unloadTrackers", __ => {
       UrlClassifierTestUtils.cleanupTestTrackers();
-      sendAsyncMessage("trackersUnloaded");
     });
   });
 
-  await new Promise(resolve => {
-    chromeScript.addMessageListener("trackersLoaded", resolve);
-    chromeScript.sendAsyncMessage("loadTrackers");
-  });
+  await chromeScript.sendQuery("loadTrackers");
 
   for (let test in tests) {
     await runTest(tests[test]);
   }
 
-  await new Promise(resolve => {
-    chromeScript.addMessageListener("trackersUnloaded", resolve);
-    chromeScript.sendSyncMessage("unloadTrackers");
-  });
+  await chromeScript.sendQuery("unloadTrackers");
 
   chromeScript.destroy();
   SimpleTest.finish();
 }
 
 SimpleTest.waitForExplicitFinish();
 runTests();
 
--- a/toolkit/components/url-classifier/tests/mochitest/test_fingerprinting.html
+++ b/toolkit/components/url-classifier/tests/mochitest/test_fingerprinting.html
@@ -65,40 +65,31 @@ async function runTest(test) {
   is(result, test.loadExpected, "The loading happened correctly (by table)");
 }
 
 async function runTests() {
   let chromeScript = SpecialPowers.loadChromeScript(_ => {
     const {UrlClassifierTestUtils} = ChromeUtils.import("resource://testing-common/UrlClassifierTestUtils.jsm");
 
     addMessageListener("loadTrackers", __ => {
-      UrlClassifierTestUtils.addTestTrackers().then(___ => {
-        sendAsyncMessage("trackersLoaded");
-      });
+      return UrlClassifierTestUtils.addTestTrackers();
     });
 
     addMessageListener("unloadTrackers", __ => {
       UrlClassifierTestUtils.cleanupTestTrackers();
-      sendAsyncMessage("trackersUnloaded");
     });
   });
 
-  await new Promise(resolve => {
-    chromeScript.addMessageListener("trackersLoaded", resolve);
-    chromeScript.sendAsyncMessage("loadTrackers");
-  });
+  await chromeScript.sendQuery("loadTrackers");
 
   for (let test in tests) {
     await runTest(tests[test]);
   }
 
-  await new Promise(resolve => {
-    chromeScript.addMessageListener("trackersUnloaded", resolve);
-    chromeScript.sendSyncMessage("unloadTrackers");
-  });
+  await chromeScript.sendQuery("unloadTrackers");
 
   chromeScript.destroy();
   SimpleTest.finish();
 }
 
 SimpleTest.waitForExplicitFinish();
 runTests();
 
--- a/toolkit/components/url-classifier/tests/mochitest/test_socialtracking.html
+++ b/toolkit/components/url-classifier/tests/mochitest/test_socialtracking.html
@@ -87,17 +87,17 @@ async function runTests() {
   });
 
   for (let test in tests) {
     await runTest(tests[test]);
   }
 
   await new Promise(resolve => {
     chromeScript.addMessageListener("trackersUnloaded", resolve);
-    chromeScript.sendSyncMessage("unloadTrackers");
+    chromeScript.sendAsyncMessage("unloadTrackers");
   });
 
   chromeScript.destroy();
   SimpleTest.finish();
 }
 
 SimpleTest.waitForExplicitFinish();
 runTests();