Bug 1454202: Part 3c - Convert add-on providers to use promise-based methods. r?aswan draft
authorKris Maglione <maglione.k@gmail.com>
Sun, 15 Apr 2018 14:13:03 -0700
changeset 782445 17d554bae08bfdc92173e25b69bca2fb658e24fd
parent 782444 548449a0a83cc11b6e1f11f381e7827a8456084f
child 782446 56fe71ba7939aac6b24e38bd63d94bd1f9ff57fe
push id106537
push usermaglione.k@gmail.com
push dateMon, 16 Apr 2018 03:19:09 +0000
reviewersaswan
bugs1454202
milestone61.0a1
Bug 1454202: Part 3c - Convert add-on providers to use promise-based methods. r?aswan MozReview-Commit-ID: 9e4CtcxWSiM
devtools/client/aboutdebugging/test/head.js
toolkit/components/telemetry/tests/unit/test_TelemetryEnvironment.js
toolkit/mozapps/extensions/AddonManager.jsm
toolkit/mozapps/extensions/LightweightThemeManager.jsm
toolkit/mozapps/extensions/content/extensions.xml
toolkit/mozapps/extensions/internal/GMPProvider.jsm
toolkit/mozapps/extensions/internal/PluginProvider.jsm
toolkit/mozapps/extensions/internal/XPIProvider.jsm
toolkit/mozapps/extensions/nsBlocklistService.js
toolkit/mozapps/extensions/test/AddonManagerTesting.jsm
toolkit/mozapps/extensions/test/browser/browser_legacy.js
toolkit/mozapps/extensions/test/browser/browser_newaddon.js
toolkit/mozapps/extensions/test/browser/browser_plugin_enabled_state_locked.js
toolkit/mozapps/extensions/test/browser/browser_pluginprefs.js
toolkit/mozapps/extensions/test/browser/head.js
toolkit/mozapps/extensions/test/xpcshell/test_temporary.js
toolkit/mozapps/extensions/test/xpcshell/test_webextension_events.js
--- a/devtools/client/aboutdebugging/test/head.js
+++ b/devtools/client/aboutdebugging/test/head.js
@@ -446,19 +446,17 @@ function installAddonWithManager(filePat
       onInstallCancelled: reject,
       onInstallEnded: resolve
     });
     install.install();
   });
 }
 
 function getAddonByID(addonId) {
-  return new Promise(resolve => {
-    AddonManager.getAddonByID(addonId, addon => resolve(addon));
-  });
+  return AddonManager.getAddonByID(addonId);
 }
 
 /**
  * Uninstall an add-on.
  */
 async function tearDownAddon(addon) {
   const onUninstalled = promiseAddonEvent("onUninstalled");
   addon.uninstall();
--- a/toolkit/components/telemetry/tests/unit/test_TelemetryEnvironment.js
+++ b/toolkit/components/telemetry/tests/unit/test_TelemetryEnvironment.js
@@ -246,18 +246,18 @@ function createMockAddonProvider(aName) 
       return aName;
     },
 
     addAddon(aAddon) {
       this._addons.push(aAddon);
       AddonManagerPrivate.callAddonListeners("onInstalled", new MockAddonWrapper(aAddon));
     },
 
-    getAddonsByTypes(aTypes, aCallback) {
-      aCallback(this._addons.map(a => new MockAddonWrapper(a)));
+    async getAddonsByTypes(aTypes) {
+      return this._addons.map(a => new MockAddonWrapper(a));
     },
 
     shutdown() {
       return Promise.resolve();
     },
   };
 
   return mockProvider;
--- a/toolkit/mozapps/extensions/AddonManager.jsm
+++ b/toolkit/mozapps/extensions/AddonManager.jsm
@@ -247,61 +247,37 @@ function callProvider(aProvider, aMethod
     reportProviderError(aProvider, aMethod, e);
     return aDefault;
   }
 }
 
 /**
  * Calls a method on a provider if it exists and consumes any thrown exception.
  * Parameters after aMethod are passed to aProvider.aMethod().
- * The last parameter must be a callback function.
  * If the provider does not implement the method, or the method throws, calls
  * the callback with 'undefined'.
  *
  * @param  aProvider
  *         The provider to call
  * @param  aMethod
  *         The method name to call
  */
-function callProviderAsync(aProvider, aMethod, ...aArgs) {
-  let callback = aArgs[aArgs.length - 1];
+async function promiseCallProvider(aProvider, aMethod, ...aArgs) {
   if (!(aMethod in aProvider)) {
-    callback(undefined);
     return undefined;
   }
   try {
     return aProvider[aMethod].apply(aProvider, aArgs);
   } catch (e) {
     reportProviderError(aProvider, aMethod, e);
-    callback(undefined);
     return undefined;
   }
 }
 
 /**
- * Calls a method on a provider if it exists and consumes any thrown exception.
- * Parameters after aMethod are passed to aProvider.aMethod() and an additional
- * callback is added for the provider to return a result to.
- *
- * @param  aProvider
- *         The provider to call
- * @param  aMethod
- *         The method name to call
- * @return {Promise}
- * @resolves The result the provider returns, or |undefined| if the provider
- *           does not implement the method or the method throws.
- * @rejects  Never
- */
-function promiseCallProvider(aProvider, aMethod, ...aArgs) {
-  return new Promise(resolve => {
-    callProviderAsync(aProvider, aMethod, ...aArgs, resolve);
-  });
-}
-
-/**
  * Gets the currently selected locale for display.
  * @return  the selected locale or "en-US" if none is selected
  */
 function getLocale() {
   return Services.locale.getRequestedLocale() || "en-US";
 }
 
 function webAPIForAddon(addon) {
--- a/toolkit/mozapps/extensions/LightweightThemeManager.jsm
+++ b/toolkit/mozapps/extensions/LightweightThemeManager.jsm
@@ -416,50 +416,43 @@ var LightweightThemeManager = {
     }
   },
 
   /**
    * Called to get an Addon with a particular ID.
    *
    * @param  aId
    *         The ID of the add-on to retrieve
-   * @param  aCallback
-   *         A callback to pass the Addon to
    */
-  getAddonByID(aId, aCallback) {
+  async getAddonByID(aId) {
     let id = _getInternalID(aId);
     if (!id) {
-      aCallback(null);
-      return;
+      return null;
      }
 
     let theme = this.getUsedTheme(id);
     if (!theme) {
-      aCallback(null);
-      return;
+      return null;
     }
 
-    aCallback(new AddonWrapper(theme));
+    return new AddonWrapper(theme);
   },
 
   /**
    * Called to get Addons of a particular type.
    *
    * @param  aTypes
    *         An array of types to fetch. Can be null to get all types.
-   * @param  aCallback
-   *         A callback to pass an array of Addons to
    */
-  getAddonsByTypes(aTypes, aCallback) {
+  getAddonsByTypes(aTypes) {
     if (aTypes && !aTypes.includes(ADDON_TYPE)) {
-      aCallback([]);
-      return;
+      return [];
     }
 
-    aCallback(this.usedThemes.map(a => new AddonWrapper(a)));
+    return this.usedThemes.map(a => new AddonWrapper(a));
   },
 };
 
 const wrapperMap = new WeakMap();
 let themeFor = wrapper => wrapperMap.get(wrapper);
 
 /**
  * The AddonWrapper wraps lightweight theme to provide the data visible to
--- a/toolkit/mozapps/extensions/content/extensions.xml
+++ b/toolkit/mozapps/extensions/content/extensions.xml
@@ -1047,17 +1047,17 @@
             isLegacyExtension(this.mAddon);
           this.setAttribute("legacy", legacyWarning);
           document.getAnonymousElementByAttribute(this, "anonid", "legacy").href = SUPPORT_URL + "webextensions";
 
           if (!("applyBackgroundUpdates" in this.mAddon) ||
               (this.mAddon.applyBackgroundUpdates == AddonManager.AUTOUPDATE_DISABLE ||
                (this.mAddon.applyBackgroundUpdates == AddonManager.AUTOUPDATE_DEFAULT &&
                 !AddonManager.autoUpdateDefault))) {
-            AddonManager.getAllInstalls(aInstallsList => {
+            AddonManager.getAllInstalls().then(aInstallsList => {
               // This can return after the binding has been destroyed,
               // so try to detect that and return early
               if (!("onNewInstall" in this))
                 return;
               for (let install of aInstallsList) {
                 if (install.existingAddon &&
                     install.existingAddon.id == this.mAddon.id &&
                     install.state == AddonManager.STATE_AVAILABLE) {
--- a/toolkit/mozapps/extensions/internal/GMPProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/GMPProvider.jsm
@@ -620,42 +620,40 @@ var GMPProvider = {
       if (!shutdownSucceeded) {
         throw new Error("Shutdown failed");
       }
     })();
 
     return shutdownTask;
   },
 
-  getAddonByID(aId, aCallback) {
+  async getAddonByID(aId) {
     if (!this.isEnabled) {
-      aCallback(null);
-      return;
+      return null;
     }
 
     let plugin = this._plugins.get(aId);
     if (plugin && !GMPUtils.isPluginHidden(plugin)) {
-      aCallback(plugin.wrapper);
+      return plugin.wrapper;
     } else {
-      aCallback(null);
+      return null;
     }
   },
 
-  getAddonsByTypes(aTypes, aCallback) {
+  async getAddonsByTypes(aTypes) {
     if (!this.isEnabled ||
         (aTypes && !aTypes.includes("plugin"))) {
-      aCallback([]);
-      return;
+      return [];
     }
 
     let results = Array.from(this._plugins.values())
       .filter(p => !GMPUtils.isPluginHidden(p))
       .map(p => p.wrapper);
 
-    aCallback(results);
+    return results;
   },
 
   get isEnabled() {
     return GMPPrefs.getBool(GMPPrefs.KEY_PROVIDER_ENABLED, false);
   },
 
   buildPluginList() {
     this._plugins = new Map();
--- a/toolkit/mozapps/extensions/internal/PluginProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/PluginProvider.jsm
@@ -87,76 +87,62 @@ var PluginProvider = {
                              aPlugin.tags);
   },
 
   /**
    * Called to get an Addon with a particular ID.
    *
    * @param  aId
    *         The ID of the add-on to retrieve
-   * @param  aCallback
-   *         A callback to pass the Addon to
    */
-  getAddonByID(aId, aCallback) {
+  async getAddonByID(aId) {
     if (!this.plugins)
       this.buildPluginList();
 
     if (aId in this.plugins)
-      aCallback(this.buildWrapper(this.plugins[aId]));
-    else
-      aCallback(null);
+      return this.buildWrapper(this.plugins[aId]);
+    return null;
   },
 
   /**
    * Called to get Addons of a particular type.
    *
    * @param  aTypes
    *         An array of types to fetch. Can be null to get all types.
-   * @param  callback
-   *         A callback to pass an array of Addons to
    */
-  getAddonsByTypes(aTypes, aCallback) {
+  async getAddonsByTypes(aTypes) {
     if (aTypes && !aTypes.includes("plugin")) {
-      aCallback([]);
-      return;
+      return [];
     }
 
     if (!this.plugins)
       this.buildPluginList();
 
-    let results = [];
-
-    for (let id in this.plugins)
-      this.getAddonByID(id, (addon) => results.push(addon));
-
-    aCallback(results);
+    return Promise.all(Object.keys(this.plugins).map(
+      id => this.getAddonByID(id)));
   },
 
   /**
    * Called to get Addons that have pending operations.
    *
    * @param  aTypes
    *         An array of types to fetch. Can be null to get all types
-   * @param  aCallback
-   *         A callback to pass an array of Addons to
    */
-  getAddonsWithOperationsByTypes(aTypes, aCallback) {
-    aCallback([]);
+  async getAddonsWithOperationsByTypes(aTypes) {
+    return [];
   },
 
   /**
    * Called to get the current AddonInstalls, optionally restricting by type.
    *
    * @param  aTypes
    *         An array of types or null to get all types
-   * @param  aCallback
-   *         A callback to pass the array of AddonInstalls to
    */
-  getInstallsByTypes(aTypes, aCallback) {
-    aCallback([]);
+  getInstallsByTypes(aTypes) {
+    return [];
   },
 
   /**
    * Builds a list of the current plugins reported by the plugin host
    *
    * @return a dictionary of plugins indexed by our generated ID
    */
   getPluginList() {
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -2449,22 +2449,16 @@ var XPIProvider = {
       logger.info("No system add-ons list was returned.");
       await systemAddonLocation.cleanDirectories();
       return;
     }
 
     let addonList = new Map(
       res.gmpAddons.map(spec => [spec.id, { spec, path: null, addon: null }]));
 
-    let getAddonsInLocation = (location) => {
-      return new Promise(resolve => {
-        XPIDatabase.getAddonsInLocation(location, resolve);
-      });
-    };
-
     let setMatches = (wanted, existing) => {
       if (wanted.size != existing.size)
         return false;
 
       for (let [id, addon] of existing) {
         let wantedInfo = wanted.get(id);
 
         if (!wantedInfo)
@@ -2472,26 +2466,26 @@ var XPIProvider = {
         if (wantedInfo.spec.version != addon.version)
           return false;
       }
 
       return true;
     };
 
     // If this matches the current set in the profile location then do nothing.
-    let updatedAddons = addonMap(await getAddonsInLocation(KEY_APP_SYSTEM_ADDONS));
+    let updatedAddons = addonMap(await XPIDatabase.getAddonsInLocation(KEY_APP_SYSTEM_ADDONS));
     if (setMatches(addonList, updatedAddons)) {
       logger.info("Retaining existing updated system add-ons.");
       await systemAddonLocation.cleanDirectories();
       return;
     }
 
     // If this matches the current set in the default location then reset the
     // updated set.
-    let defaultAddons = addonMap(await getAddonsInLocation(KEY_APP_SYSTEM_DEFAULTS));
+    let defaultAddons = addonMap(await XPIDatabase.getAddonsInLocation(KEY_APP_SYSTEM_DEFAULTS));
     if (setMatches(addonList, defaultAddons)) {
       logger.info("Resetting system add-ons.");
       systemAddonLocation.resetAddonSet();
       await systemAddonLocation.cleanDirectories();
       return;
     }
 
     // Download all the add-ons
@@ -3163,53 +3157,48 @@ var XPIProvider = {
    * @param  aName
    *         A name for the install
    * @param  aIcons
    *         Icon URLs for the install
    * @param  aVersion
    *         A version for the install
    * @param  aBrowser
    *         The browser performing the install
-   * @param  aCallback
-   *         A callback to pass the AddonInstall to
    */
-  getInstallForURL(aUrl, aHash, aName, aIcons, aVersion, aBrowser,
-                             aCallback) {
+  async getInstallForURL(aUrl, aHash, aName, aIcons, aVersion, aBrowser) {
     let location = XPIProvider.installLocationsByName[KEY_APP_PROFILE];
     let url = Services.io.newURI(aUrl);
 
     let options = {
       hash: aHash,
       browser: aBrowser,
       name: aName,
       icons: aIcons,
       version: aVersion,
     };
 
     if (url instanceof Ci.nsIFileURL) {
       let install = new LocalAddonInstall(location, url, options);
-      install.init().then(() => { aCallback(install.wrapper); });
-    } else {
-      let install = new DownloadAddonInstall(location, url, options);
-      aCallback(install.wrapper);
-    }
+      await install.init();
+      return install.wrapper;
+    }
+
+    let install = new DownloadAddonInstall(location, url, options);
+    return install.wrapper;
   },
 
   /**
    * Called to get an AddonInstall to install an add-on from a local file.
    *
    * @param  aFile
    *         The file to be installed
-   * @param  aCallback
-   *         A callback to pass the AddonInstall to
    */
-  getInstallForFile(aFile, aCallback) {
-    createLocalInstall(aFile).then(install => {
-      aCallback(install ? install.wrapper : null);
-    });
+  async getInstallForFile(aFile) {
+    let install = await createLocalInstall(aFile);
+    return install ? install.wrapper : null;
   },
 
   /**
    * Temporarily installs add-on from a local XPI file or directory.
    * As this is intended for development, the signature is not checked and
    * the add-on does not persist on application restart.
    *
    * @param aFile
@@ -3273,18 +3262,17 @@ var XPIProvider = {
       throw new Error(message);
     }
 
     if (!addon.bootstrap) {
       throw new Error("Only restartless (bootstrap) add-ons"
                     + " can be installed from sources:", addon.id);
     }
     let installReason = BOOTSTRAP_REASONS.ADDON_INSTALL;
-    let oldAddon = await new Promise(
-                   resolve => XPIDatabase.getVisibleAddonForID(addon.id, resolve));
+    let oldAddon = await XPIDatabase.getVisibleAddonForID(addon.id);
     let callUpdate = false;
 
     let extraParams = {};
     extraParams.temporarilyInstalled = aInstallLocation === TemporaryInstallLocation;
     if (oldAddon) {
       if (!oldAddon.bootstrap) {
         logger.warn("Non-restartless Add-on is already installed", addon.id);
         throw new Error("Non-restartless add-on with ID "
@@ -3390,17 +3378,17 @@ var XPIProvider = {
    */
    getAddonByInstanceID(aInstanceID) {
      if (!aInstanceID || typeof aInstanceID != "symbol")
        throw Components.Exception("aInstanceID must be a Symbol()",
                                   Cr.NS_ERROR_INVALID_ARG);
 
      for (let [id, val] of this.activeAddons) {
        if (aInstanceID == val.instanceID) {
-         return new Promise(resolve => this.getAddonByID(id, resolve));
+         return this.getAddonByID(id);
        }
      }
 
      return Promise.resolve(null);
    },
 
   /**
    * Removes an AddonInstall from the list of active installs.
@@ -3412,67 +3400,59 @@ var XPIProvider = {
     this.installs.delete(aInstall);
   },
 
   /**
    * Called to get an Addon with a particular ID.
    *
    * @param  aId
    *         The ID of the add-on to retrieve
-   * @param  aCallback
-   *         A callback to pass the Addon to
    */
-  async getAddonByID(aId, aCallback) {
+  async getAddonByID(aId) {
     let aAddon = await XPIDatabase.getVisibleAddonForID(aId);
-    aCallback(aAddon ? aAddon.wrapper : null);
+    return aAddon ? aAddon.wrapper : null;
   },
 
   /**
    * Called to get Addons of a particular type.
    *
    * @param  aTypes
    *         An array of types to fetch. Can be null to get all types.
-   * @param  aCallback
-   *         A callback to pass an array of Addons to
    */
-  async getAddonsByTypes(aTypes, aCallback) {
+  async getAddonsByTypes(aTypes) {
     let typesToGet = getAllAliasesForTypes(aTypes);
     if (typesToGet && !typesToGet.some(type => ALL_EXTERNAL_TYPES.has(type))) {
-      aCallback([]);
-      return;
-    }
-
-    let aAddons = await XPIDatabase.getVisibleAddons(typesToGet);
-    aCallback(aAddons.map(a => a.wrapper));
+      return [];
+    }
+
+    let addons = await XPIDatabase.getVisibleAddons(typesToGet);
+    return addons.map(a => a.wrapper);
   },
 
   /**
    * Called to get active Addons of a particular type
    *
    * @param  aTypes
    *         An array of types to fetch. Can be null to get all types.
    * @returns {Promise<Array<Addon>>}
    */
-  getActiveAddons(aTypes) {
+  async getActiveAddons(aTypes) {
     // If we already have the database loaded, returning full info is fast.
     if (this.isDBLoaded) {
-      return new Promise(resolve => {
-        this.getAddonsByTypes(aTypes, addons => {
-          resolve({
-            addons: addons.filter(addon => addon.isActive),
-            fullData: true,
-          });
-        });
-      });
+      let addons = await this.getAddonsByTypes(aTypes);
+      return {
+        addons: addons.filter(addon => addon.isActive),
+        fullData: true,
+      };
     }
 
     // Construct addon-like objects with the information we already
     // have in memory.
     if (!XPIStates.db) {
-      return Promise.reject(new Error("XPIStates not yet initialized"));
+      throw new Error("XPIStates not yet initialized");
     }
 
     let result = [];
     for (let addon of XPIStates.enabledAddons()) {
       if (aTypes && !aTypes.includes(addon.type)) {
         continue;
       }
       let location = this.installLocationsByName[addon.location.name];
@@ -3486,72 +3466,66 @@ var XPIProvider = {
         type: addon.type,
         updateDate: addon.lastModifiedTime,
         scope,
         isSystem,
         isWebExtension: isWebExtension(addon),
       });
     }
 
-    return Promise.resolve({addons: result, fullData: false});
+    return {addons: result, fullData: false};
   },
 
 
   /**
    * Obtain an Addon having the specified Sync GUID.
    *
    * @param  aGUID
    *         String GUID of add-on to retrieve
-   * @param  aCallback
-   *         A callback to pass the Addon to. Receives null if not found.
    */
-  async getAddonBySyncGUID(aGUID, aCallback) {
-    let aAddon = await XPIDatabase.getAddonBySyncGUID(aGUID);
-    aCallback(aAddon ? aAddon.wrapper : null);
+  async getAddonBySyncGUID(aGUID) {
+    let addon = await XPIDatabase.getAddonBySyncGUID(aGUID);
+    return addon ? addon.wrapper : null;
   },
 
   /**
    * Called to get Addons that have pending operations.
    *
    * @param  aTypes
    *         An array of types to fetch. Can be null to get all types
-   * @param  aCallback
-   *         A callback to pass an array of Addons to
    */
-  async getAddonsWithOperationsByTypes(aTypes, aCallback) {
+  async getAddonsWithOperationsByTypes(aTypes) {
     let typesToGet = getAllAliasesForTypes(aTypes);
 
     let aAddons = await XPIDatabase.getVisibleAddonsWithPendingOperations(typesToGet);
     let results = aAddons.map(a => a.wrapper);
     for (let install of XPIProvider.installs) {
       if (install.state == AddonManager.STATE_INSTALLED &&
           !(install.addon.inDatabase))
         results.push(install.addon.wrapper);
     }
-    aCallback(results);
+    return results;
   },
 
   /**
    * Called to get the current AddonInstalls, optionally limiting to a list of
    * types.
    *
    * @param  aTypes
    *         An array of types or null to get all types
-   * @param  aCallback
-   *         A callback to pass the array of AddonInstalls to
    */
-  getInstallsByTypes(aTypes, aCallback) {
+  getInstallsByTypes(aTypes) {
     let results = [...this.installs];
     if (aTypes) {
       results = results.filter(install => {
         return aTypes.includes(getExternalType(install.type));
       });
     }
 
-    aCallback(results.map(install => install.wrapper));
+    return results.map(install => install.wrapper);
   },
 
   /**
    * Called when a new add-on has been enabled when only one add-on of that type
    * can be enabled.
    *
    * @param  aId
    *         The ID of the newly enabled add-on
@@ -3580,45 +3554,29 @@ var XPIProvider = {
     let addons = XPIDatabase.getAddons();
     for (let addon of addons) {
       this.updateAddonDisabledState(addon);
     }
   },
 
   /**
    * Update the repositoryAddon property for all add-ons.
-   *
-   * @param  aCallback
-   *         Function to call when operation is complete.
    */
-  async updateAddonRepositoryData(aCallback) {
-    let aAddons = await XPIDatabase.getVisibleAddons(null);
-    let pending = aAddons.length;
-    logger.debug("updateAddonRepositoryData found " + pending + " visible add-ons");
-    if (pending == 0) {
-      aCallback();
-      return;
-    }
-
-    function notifyComplete() {
-      if (--pending == 0)
-        aCallback();
-    }
-
-    for (let addon of aAddons) {
+  async updateAddonRepositoryData() {
+    let addons = await XPIDatabase.getVisibleAddons(null);
+    logger.debug("updateAddonRepositoryData found " + addons.length + " visible add-ons");
+
+    await Promise.all(addons.map(addon =>
       AddonRepository.getCachedAddonByID(addon.id).then(aRepoAddon => {
         if (aRepoAddon || AddonRepository.getCompatibilityOverridesSync(addon.id)) {
           logger.debug("updateAddonRepositoryData got info for " + addon.id);
           addon._repositoryAddon = aRepoAddon;
           this.updateAddonDisabledState(addon);
         }
-
-        notifyComplete();
-      });
-    }
+      })));
   },
 
   onDebugConnectionChange({what, connection}) {
     if (what != "opened")
       return;
 
     for (let [id, val] of this.activeAddons) {
       connection.setAddonOptions(
@@ -6054,17 +6012,17 @@ class SystemAddonInstallLocation extends
     // Make sure the base dir exists
     await OS.File.makeDir(this._baseDir.path, { ignoreExisting: true });
 
     let addonSet = SystemAddonInstallLocation._loadAddonSet();
 
     // Remove any add-ons that are no longer part of the set.
     for (let addonID of Object.keys(addonSet.addons)) {
       if (!aAddons.includes(addonID)) {
-        AddonManager.getAddonByID(addonID, a => a.uninstall());
+        AddonManager.getAddonByID(addonID).then(a => a.uninstall());
       }
     }
 
     let newDir = this._baseDir.clone();
 
     let uuidGen = Cc["@mozilla.org/uuid-generator;1"].
                   getService(Ci.nsIUUIDGenerator);
     newDir.append("blank");
--- a/toolkit/mozapps/extensions/nsBlocklistService.js
+++ b/toolkit/mozapps/extensions/nsBlocklistService.js
@@ -1221,17 +1221,17 @@ Blocklist.prototype = {
     var addonList = [];
 
     // A helper function that reverts the prefs passed to default values.
     function resetPrefs(prefs) {
       for (let pref of prefs)
         Services.prefs.clearUserPref(pref);
     }
     const types = ["extension", "theme", "locale", "dictionary", "service"];
-    AddonManager.getAddonsByTypes(types, addons => {
+    AddonManager.getAddonsByTypes(types).then(addons => {
       for (let addon of addons) {
         let oldState = addon.blocklistState;
         if (addon.updateBlocklistState) {
           addon.updateBlocklistState(false);
         } else if (oldAddonEntries) {
           oldState = this._getAddonBlocklistState(addon, oldAddonEntries);
         } else {
           oldState = Ci.nsIBlocklistService.STATE_NOTBLOCKED;
--- a/toolkit/mozapps/extensions/test/AddonManagerTesting.jsm
+++ b/toolkit/mozapps/extensions/test/AddonManagerTesting.jsm
@@ -19,17 +19,17 @@ ChromeUtils.defineModuleGetter(this, "Ad
 var AddonManagerTesting = {
   /**
    * Get the add-on that is specified by its ID.
    *
    * @return {Promise<Object>} A promise that resolves returning the found addon or null
    *         if it is not found.
    */
   getAddonById(id) {
-    return new Promise(resolve => AddonManager.getAddonByID(id, addon => resolve(addon)));
+    return AddonManager.getAddonByID(id);
   },
 
   /**
    * Uninstall an add-on that is specified by its ID.
    *
    * The returned promise resolves on successful uninstall and rejects
    * if the add-on is not unknown.
    *
--- a/toolkit/mozapps/extensions/test/browser/browser_legacy.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_legacy.js
@@ -179,16 +179,18 @@ add_task(async function() {
 
   // Disable unsigned extensions
   SpecialPowers.pushPrefEnv({
     set: [
       ["xpinstall.signatures.required", false],
     ],
   });
 
+  await new Promise(executeSoon);
+
   // The name of the pane should go back to "Legacy Extensions"
   await mgrWin.gLegacyView.refreshVisibility();
   is(catItem.disabled, false, "Legacy category is visible");
   is(catItem.getAttribute("name"), get_string("type.legacy.name"),
      "Category label with no unsigned extensions is correct");
 
   // The unsigned extension should be present in the main extensions pane
   await catUtils.openType("extension");
--- a/toolkit/mozapps/extensions/test/browser/browser_newaddon.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_newaddon.js
@@ -84,22 +84,28 @@ add_test(function() {
     is_element_hidden(doc.getElementById("location"), "Should be no location displayed");
 
     is(doc.getElementById("buttonDeck").selectedPanel, doc.getElementById("continuePanel"),
        "Should be showing the right buttons");
 
     let aAddon = await AddonManager.getAddonByID("addon1@tests.mozilla.org");
     ok(aAddon.seen, "Add-on should have been marked as seen");
 
+    await new Promise(executeSoon);
+
     EventUtils.synthesizeMouseAtCenter(doc.getElementById("allow"),
                                        {}, aTab.linkedBrowser.contentWindow);
 
+    await new Promise(executeSoon);
+
     EventUtils.synthesizeMouseAtCenter(doc.getElementById("continue-button"),
                                        {}, aTab.linkedBrowser.contentWindow);
 
+    await new Promise(executeSoon);
+
     is(gBrowser.tabs.length, 1, "Page should have been closed");
 
     ok(!aAddon.userDisabled, "Add-on should now have been enabled");
 
     ok(aAddon.isActive, "Add-on should now be running");
 
     aAddon.userDisabled = true;
     aAddon.seen = false;
@@ -118,19 +124,23 @@ add_test(function() {
     is_element_hidden(doc.getElementById("location"), "Should be no location displayed");
 
     is(doc.getElementById("buttonDeck").selectedPanel, doc.getElementById("continuePanel"),
        "Should be showing the right buttons");
 
     let aAddon = await AddonManager.getAddonByID("addon2@tests.mozilla.org");
     ok(aAddon.seen, "Add-on should have been marked as seen");
 
+    await new Promise(executeSoon);
+
     EventUtils.synthesizeMouseAtCenter(doc.getElementById("continue-button"),
                                        {}, aTab.linkedBrowser.contentWindow);
 
+    await new Promise(executeSoon);
+
     is(gBrowser.tabs.length, 1, "Page should have been closed");
 
     ok(aAddon.userDisabled, "Add-on should not have been enabled");
 
     ok(!aAddon.isActive, "Add-on should not be running");
 
     aAddon.seen = false;
     run_next_test();
@@ -148,22 +158,28 @@ add_test(function() {
     is_element_hidden(doc.getElementById("location"), "Should be no location displayed");
 
     is(doc.getElementById("buttonDeck").selectedPanel, doc.getElementById("continuePanel"),
        "Should be showing the right buttons");
 
     let aAddon = await AddonManager.getAddonByID("addon2@tests.mozilla.org");
     ok(aAddon.seen, "Add-on should have been marked as seen");
 
+    await new Promise(executeSoon);
+
     EventUtils.synthesizeMouseAtCenter(doc.getElementById("allow"),
                                        {}, aTab.linkedBrowser.contentWindow);
 
+    await new Promise(executeSoon);
+
     EventUtils.synthesizeMouseAtCenter(doc.getElementById("continue-button"),
                                        {}, aTab.linkedBrowser.contentWindow);
 
+    await new Promise(executeSoon);
+
     is(doc.getElementById("buttonDeck").selectedPanel, doc.getElementById("restartPanel"),
        "Should be showing the right buttons");
 
     ok(!aAddon.userDisabled, "Add-on should now have been enabled");
 
     ok(!aAddon.isActive, "Add-on should not be running");
 
     ok(doc.getElementById("allow").disabled, "Should have disabled checkbox");
--- a/toolkit/mozapps/extensions/test/browser/browser_plugin_enabled_state_locked.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_plugin_enabled_state_locked.js
@@ -22,19 +22,17 @@ function getTestPluginPref() {
 }
 
 registerCleanupFunction(() => {
   Services.prefs.unlockPref(getTestPluginPref());
   Services.prefs.clearUserPref(getTestPluginPref());
 });
 
 function getPlugins() {
-  return new Promise(resolve => {
-    AddonManager.getAddonsByTypes(["plugin"], plugins => resolve(plugins));
-  });
+  return AddonManager.getAddonsByTypes(["plugin"]);
 }
 
 function getTestPlugin(aPlugins) {
   let testPluginId;
 
   for (let plugin of aPlugins) {
     if (plugin.name == "Test Plug-in") {
       testPluginId = plugin.id;
--- a/toolkit/mozapps/extensions/test/browser/browser_pluginprefs.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_pluginprefs.js
@@ -37,28 +37,28 @@ add_test(async function() {
   pluginEl.parentNode.ensureElementIsVisible(pluginEl);
 
   let button = gManagerWindow.document.getAnonymousElementByAttribute(pluginEl, "anonid", "preferences-btn");
   is_element_visible(button, "Preferences button should be visible");
 
   button = gManagerWindow.document.getAnonymousElementByAttribute(pluginEl, "anonid", "details-btn");
   EventUtils.synthesizeMouseAtCenter(button, { clickCount: 1 }, gManagerWindow);
 
-  Services.obs.addObserver(function observer(subject, topic, data) {
+  Services.obs.addObserver(async function observer(subject, topic, data) {
     Services.obs.removeObserver(observer, topic);
 
     // Wait for PluginProvider to do its stuff.
-    executeSoon(function() {
-      let doc = gManagerWindow.document.getElementById("addon-options").contentDocument;
+    await new Promise(executeSoon);
+
+    let doc = gManagerWindow.document.getElementById("addon-options").contentDocument;
 
-      let pluginLibraries = doc.getElementById("pluginLibraries");
-      ok(pluginLibraries, "Plugin file name row should be displayed");
-      // the file name depends on the platform
-      ok(pluginLibraries.textContent, testPlugin.pluginLibraries, "Plugin file name should be displayed");
+    let pluginLibraries = doc.getElementById("pluginLibraries");
+    ok(pluginLibraries, "Plugin file name row should be displayed");
+    // the file name depends on the platform
+    is(pluginLibraries.textContent, testPlugin.pluginLibraries, "Plugin file name should be displayed");
 
-      let pluginMimeTypes = doc.getElementById("pluginMimeTypes");
-      ok(pluginMimeTypes, "Plugin mime type row should be displayed");
-      ok(pluginMimeTypes.textContent, "application/x-test (tst)", "Plugin mime type should be displayed");
+    let pluginMimeTypes = doc.getElementById("pluginMimeTypes");
+    ok(pluginMimeTypes, "Plugin mime type row should be displayed");
+    is(pluginMimeTypes.textContent, "application/x-test (tst)", "Plugin mime type should be displayed");
 
-      run_next_test();
-    });
+    run_next_test();
   }, AddonManager.OPTIONS_NOTIFICATION_DISPLAYED);
 });
--- a/toolkit/mozapps/extensions/test/browser/head.js
+++ b/toolkit/mozapps/extensions/test/browser/head.js
@@ -888,84 +888,75 @@ MockProvider.prototype = {
     this.started = false;
   },
 
   /**
    * Called to get an Addon with a particular ID.
    *
    * @param  aId
    *         The ID of the add-on to retrieve
-   * @param  aCallback
-   *         A callback to pass the Addon to
    */
-  getAddonByID: function MP_getAddon(aId, aCallback) {
+  async getAddonByID(aId) {
     for (let addon of this.addons) {
       if (addon.id == aId) {
-        this._delayCallback(aCallback, addon);
-        return;
+        return addon;
       }
     }
 
-    aCallback(null);
+    return null;
   },
 
   /**
    * Called to get Addons of a particular type.
    *
    * @param  aTypes
    *         An array of types to fetch. Can be null to get all types.
-   * @param  callback
-   *         A callback to pass an array of Addons to
    */
-  getAddonsByTypes: function MP_getAddonsByTypes(aTypes, aCallback) {
+  async getAddonsByTypes(aTypes) {
     var addons = this.addons.filter(function(aAddon) {
       if (aTypes && aTypes.length > 0 && !aTypes.includes(aAddon.type))
         return false;
       return true;
     });
-    this._delayCallback(aCallback, addons);
+    return addons;
   },
 
   /**
    * Called to get Addons that have pending operations.
    *
    * @param  aTypes
    *         An array of types to fetch. Can be null to get all types
-   * @param  aCallback
-   *         A callback to pass an array of Addons to
    */
-  getAddonsWithOperationsByTypes: function MP_getAddonsWithOperationsByTypes(aTypes, aCallback) {
+  async getAddonsWithOperationsByTypes(aTypes, aCallback) {
     var addons = this.addons.filter(function(aAddon) {
       if (aTypes && aTypes.length > 0 && !aTypes.includes(aAddon.type))
         return false;
       return aAddon.pendingOperations != 0;
     });
-    this._delayCallback(aCallback, addons);
+    return addons;
   },
 
   /**
    * Called to get the current AddonInstalls, optionally restricting by type.
    *
    * @param  aTypes
    *         An array of types or null to get all types
-   * @param  aCallback
-   *         A callback to pass the array of AddonInstalls to
    */
-  getInstallsByTypes: function MP_getInstallsByTypes(aTypes, aCallback) {
+  async getInstallsByTypes(aTypes) {
     var installs = this.installs.filter(function(aInstall) {
       // Appear to have actually removed cancelled installs from the provider
       if (aInstall.state == AddonManager.STATE_CANCELLED)
         return false;
 
       if (aTypes && aTypes.length > 0 && !aTypes.includes(aInstall.type))
         return false;
 
       return true;
     });
-    this._delayCallback(aCallback, installs);
+    return installs;
   },
 
   /**
    * Called when a new add-on has been enabled when only one add-on of that type
    * can be enabled.
    *
    * @param  aId
    *         The ID of the newly enabled add-on
@@ -996,33 +987,29 @@ MockProvider.prototype = {
    * @param  aName
    *         A name for the install
    * @param  aIconURL
    *         An icon URL for the install
    * @param  aVersion
    *         A version for the install
    * @param  aLoadGroup
    *         An nsILoadGroup to associate requests with
-   * @param  aCallback
-   *         A callback to pass the AddonInstall to
    */
   getInstallForURL: function MP_getInstallForURL(aUrl, aHash, aName, aIconURL,
-                                                  aVersion, aLoadGroup, aCallback) {
+                                                  aVersion, aLoadGroup) {
     // Not yet implemented
   },
 
   /**
    * Called to get an AddonInstall to install an add-on from a local file.
    *
    * @param  aFile
    *         The file to be installed
-   * @param  aCallback
-   *         A callback to pass the AddonInstall to
    */
-  getInstallForFile: function MP_getInstallForFile(aFile, aCallback) {
+  getInstallForFile: function MP_getInstallForFile(aFile) {
     // Not yet implemented
   },
 
   /**
    * Called to test whether installing add-ons is enabled.
    *
    * @return true if installing is enabled
    */
@@ -1047,52 +1034,16 @@ MockProvider.prototype = {
    *
    * @param  aUri
    *         The URI being installed from
    * @return true if installing is allowed
    */
   isInstallAllowed: function MP_isInstallAllowed(aUri) {
     return false;
   },
-
-
-  /** *** Internal functions *****/
-
-  /**
-   * Delay calling a callback to fake a time-consuming async operation.
-   * The delay is specified by the apiDelay property, in milliseconds.
-   * Parameters to send to the callback should be specified as arguments after
-   * the aCallback argument.
-   *
-   * @param aCallback Callback to eventually call
-   */
-  _delayCallback: function MP_delayCallback(aCallback, ...aArgs) {
-    if (!this.useAsyncCallbacks) {
-      aCallback(...aArgs);
-      return;
-    }
-
-    let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
-    // Need to keep a reference to the timer, so it doesn't get GC'ed
-    this.callbackTimers.push(timer);
-    // Capture a stack trace where the timer was set
-    // needs the 'new Error' hack until bug 1007656
-    this.timerLocations.set(timer, Log.stackTrace(new Error("dummy")));
-    timer.initWithCallback(() => {
-      let idx = this.callbackTimers.indexOf(timer);
-      if (idx == -1) {
-        dump("MockProvider._delayCallback lost track of timer set at "
-             + (this.timerLocations.get(timer) || "unknown location") + "\n");
-      } else {
-        this.callbackTimers.splice(idx, 1);
-      }
-      this.timerLocations.delete(timer);
-      aCallback(...aArgs);
-    }, this.apiDelay, timer.TYPE_ONE_SHOT);
-  }
 };
 
 /** *** Mock Addon object for the Mock Provider *****/
 
 function MockAddon(aId, aName, aType, aOperationsRequiringRestart) {
   // Only set required attributes.
   this.id = aId || "";
   this.name = aName || "";
--- a/toolkit/mozapps/extensions/test/xpcshell/test_temporary.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_temporary.js
@@ -428,16 +428,17 @@ add_task(async function() {
   Assert.ok(addon.isCompatible);
   Assert.ok(!addon.appDisabled);
   Assert.ok(addon.isActive);
   Assert.equal(addon.type, "extension");
   Assert.equal(addon.signedState, mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_PRIVILEGED : AddonManager.SIGNEDSTATE_NOT_REQUIRED);
 
   addon.uninstall();
 
+  await new Promise(executeSoon);
   addon = await promiseAddonByID(ID);
 
   BootstrapMonitor.checkAddonInstalled(ID);
   BootstrapMonitor.checkAddonStarted(ID);
 
   // existing add-on is back
   Assert.notEqual(addon, null);
   Assert.equal(addon.version, "1.0");
@@ -678,16 +679,17 @@ add_task(async function() {
   Assert.ok(tempAddon.isActive);
   Assert.equal(tempAddon.type, "extension");
   Assert.equal(tempAddon.signedState, mozinfo.addon_signing ? AddonManager.SIGNEDSTATE_PRIVILEGED : AddonManager.SIGNEDSTATE_NOT_REQUIRED);
 
   tempAddon.uninstall();
   unpacked_addon.remove(true);
 
   addon.userDisabled = false;
+  await new Promise(executeSoon);
   addon = await promiseAddonByID(ID);
 
   BootstrapMonitor.checkAddonInstalled(ID, "1.0");
   BootstrapMonitor.checkAddonStarted(ID);
 
   // existing add-on is back
   Assert.notEqual(addon, null);
   Assert.equal(addon.version, "1.0");
--- a/toolkit/mozapps/extensions/test/xpcshell/test_webextension_events.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_webextension_events.js
@@ -11,16 +11,17 @@ add_task(async function() {
 
   async function expectEvents(expected, fn) {
     let events = Object.keys(expected);
     for (let event of events) {
       triggered[event] = false;
     }
 
     await fn();
+    await new Promise(executeSoon);
 
     for (let event of events) {
       equal(triggered[event], expected[event],
             `Event ${event} was${expected[event] ? "" : " not"} triggered`);
     }
   }
 
   await promiseStartupManager();