Bug 1454202: Part 4 - Convert add-on providers to use promise-based methods. r=aswan
authorKris Maglione <maglione.k@gmail.com>
Sat, 21 Apr 2018 15:30:40 -0700
changeset 468494 112ff1e321a11fac3895742f1f35fe12d2acf903
parent 468493 773b6d28b99067395102db127a526b6d08871812
child 468495 ebacf44a86dc27644717e72a2a302d586d5d81a1
push id9165
push userasasaki@mozilla.com
push dateThu, 26 Apr 2018 21:04:54 +0000
treeherdermozilla-beta@064c3804de2e [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersaswan
bugs1454202
milestone61.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 1454202: Part 4 - 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/test/AddonManagerTesting.jsm
toolkit/mozapps/extensions/test/browser/browser_legacy.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
@@ -248,61 +248,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
@@ -929,17 +929,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,39 @@ 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);
-    } else {
-      aCallback(null);
+      return plugin.wrapper;
     }
+    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
@@ -41,39 +41,38 @@ var PluginProvider = {
    * to be able to simulate a shutdown.
    */
   shutdown() {
     this.plugins = null;
     Services.obs.removeObserver(this, AddonManager.OPTIONS_NOTIFICATION_DISPLAYED);
     Services.obs.removeObserver(this, LIST_UPDATED_TOPIC);
   },
 
-  observe(aSubject, aTopic, aData) {
+  async observe(aSubject, aTopic, aData) {
     switch (aTopic) {
     case AddonManager.OPTIONS_NOTIFICATION_DISPLAYED:
-      this.getAddonByID(aData, function(plugin) {
-        if (!plugin)
-          return;
+      let plugin = await this.getAddonByID(aData);
+      if (!plugin)
+        return;
 
-        let document = aSubject.getElementById("addon-options").contentDocument;
+      let document = aSubject.getElementById("addon-options").contentDocument;
 
-        let libLabel = document.getElementById("pluginLibraries");
-        libLabel.textContent = plugin.pluginLibraries.join(", ");
+      let libLabel = document.getElementById("pluginLibraries");
+      libLabel.textContent = plugin.pluginLibraries.join(", ");
 
-        let typeLabel = document.getElementById("pluginMimeTypes"), types = [];
-        for (let type of plugin.pluginMimeTypes) {
-          let extras = [type.description.trim(), type.suffixes].
-                       filter(x => x).join(": ");
-          types.push(type.type + (extras ? " (" + extras + ")" : ""));
-        }
-        typeLabel.textContent = types.join(",\n");
-        let showProtectedModePref = canDisableFlashProtectedMode(plugin);
-        document.getElementById("pluginEnableProtectedMode")
-          .setAttribute("collapsed", showProtectedModePref ? "" : "true");
-      });
+      let typeLabel = document.getElementById("pluginMimeTypes"), types = [];
+      for (let type of plugin.pluginMimeTypes) {
+        let extras = [type.description.trim(), type.suffixes].
+                     filter(x => x).join(": ");
+        types.push(type.type + (extras ? " (" + extras + ")" : ""));
+      }
+      typeLabel.textContent = types.join(",\n");
+      let showProtectedModePref = canDisableFlashProtectedMode(plugin);
+      document.getElementById("pluginEnableProtectedMode")
+        .setAttribute("collapsed", showProtectedModePref ? "" : "true");
       break;
     case LIST_UPDATED_TOPIC:
       if (this.plugins)
         this.updatePluginList();
       break;
     }
   },
 
@@ -87,76 +86,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
@@ -2453,22 +2453,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)
@@ -2476,26 +2470,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
@@ -3167,53 +3161,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
@@ -3277,18 +3266,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 "
@@ -3394,17 +3382,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.
@@ -3416,67 +3404,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];
@@ -3490,72 +3470,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
@@ -3584,45 +3558,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(
@@ -6058,17 +6016,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/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_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 (Test \u2122 mimetype: 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();