Bug 1452618 - make getAddonBlocklistEntry asynchronous, r=kmag
☠☠ backed out by e63607c0931b ☠ ☠
authorGijs Kruitbosch <gijskruitbosch@gmail.com>
Mon, 09 Apr 2018 16:00:38 +0100
changeset 466962 5bf3bfedd867cc3504fccf7b17b4272851861fba
parent 466961 fdcb9f2ec9d9b6d1a291d9969249d5a3c0cb054e
child 466963 e63607c0931b04d353a1021237360ffa0a1a0cab
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)
reviewerskmag
bugs1452618
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 1452618 - make getAddonBlocklistEntry asynchronous, r=kmag MozReview-Commit-ID: 4Kpx7M57404
toolkit/mozapps/extensions/internal/AddonTestUtils.jsm
toolkit/mozapps/extensions/internal/XPIInstall.jsm
toolkit/mozapps/extensions/internal/XPIProvider.jsm
toolkit/mozapps/extensions/internal/XPIProviderUtils.js
toolkit/mozapps/extensions/nsBlocklistService.js
toolkit/mozapps/extensions/test/xpcshell/test_blocklist_severities.js
toolkit/mozapps/extensions/test/xpcshell/test_blocklistchange.js
xpcom/system/nsIBlocklistService.idl
--- a/toolkit/mozapps/extensions/internal/AddonTestUtils.jsm
+++ b/toolkit/mozapps/extensions/internal/AddonTestUtils.jsm
@@ -121,17 +121,18 @@ class MockBlocklist {
   unregister() {
     MockRegistrar.unregister(this.originalCID);
   }
 
   getAddonBlocklistState(addon, appVersion, toolkitVersion) {
     return this.addons.get(addon.id, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
   }
 
-  getAddonBlocklistEntry(addon, appVersion, toolkitVersion) {
+  async getAddonBlocklistEntry(addon, appVersion, toolkitVersion) {
+    await Promise.resolve();
     let state = this.getAddonBlocklistState(addon, appVersion, toolkitVersion);
     if (state != Ci.nsIBlocklistService.STATE_NOT_BLOCKED) {
       return {
         state,
         url: "http://example.com/",
       };
     }
     return null;
--- a/toolkit/mozapps/extensions/internal/XPIInstall.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIInstall.jsm
@@ -862,17 +862,17 @@ function generateTemporaryInstallID(aFil
   const sess = TEMP_INSTALL_ID_GEN_SESSION;
   hasher.update(sess, sess.length);
   hasher.update(data, data.length);
   let id = `${getHashStringForCrypto(hasher)}${TEMPORARY_ADDON_SUFFIX}`;
   logger.info(`Generated temp id ${id} (${sess.join("")}) for ${aFile.path}`);
   return id;
 }
 
-var loadManifest = async function(aPackage, aInstallLocation) {
+var loadManifest = async function(aPackage, aInstallLocation, aOldAddon) {
   async function loadFromRDF(aUri) {
     let manifest = await aPackage.readString("install.rdf");
     let addon = await loadManifestFromRDF(aUri, manifest);
 
     if (await aPackage.hasResource("icon.png")) {
       addon.icons[32] = "icon.png";
       addon.icons[48] = "icon.png";
     }
@@ -915,28 +915,28 @@ var loadManifest = async function(aPacka
         throw new Error(`Webextension is signed with an invalid id (${addon.id})`);
       }
     }
     if (!addon.id && aInstallLocation.name == KEY_APP_TEMPORARY) {
       addon.id = generateTemporaryInstallID(aPackage.file);
     }
   }
 
-  addon.updateBlocklistState();
+  await addon.updateBlocklistState({oldAddon: aOldAddon});
   addon.appDisabled = !isUsableAddon(addon);
 
   defineSyncGUID(addon);
 
   return addon;
 };
 
-var loadManifestFromFile = async function(aFile, aInstallLocation) {
+var loadManifestFromFile = async function(aFile, aInstallLocation, aOldAddon) {
   let pkg = Package.get(aFile);
   try {
-    let addon = await loadManifest(pkg, aInstallLocation);
+    let addon = await loadManifest(pkg, aInstallLocation, aOldAddon);
     return addon;
   } finally {
     pkg.close();
   }
 };
 
 function flushChromeCaches() {
   // Init this, so it will get the notification.
@@ -1436,17 +1436,17 @@ class AddonInstall {
     try {
       pkg = Package.get(file);
     } catch (e) {
       return Promise.reject([AddonManager.ERROR_CORRUPT_FILE, e]);
     }
 
     try {
       try {
-        this.addon = await loadManifest(pkg, this.installLocation);
+        this.addon = await loadManifest(pkg, this.installLocation, this.existingAddon);
       } catch (e) {
         return Promise.reject([AddonManager.ERROR_CORRUPT_FILE, e]);
       }
 
       if (!this.addon.id) {
         let err = new Error(`Cannot find id for addon ${file.path}`);
         return Promise.reject([AddonManager.ERROR_CORRUPT_FILE, err]);
       }
@@ -1904,17 +1904,17 @@ var LocalAddonInstall = class extends Ad
       return;
     }
 
     let addon = await new Promise(resolve => {
       XPIDatabase.getVisibleAddonForID(this.addon.id, resolve);
     });
 
     this.existingAddon = addon;
-    this.addon.updateBlocklistState({oldAddon: this.existingAddon});
+    await this.addon.updateBlocklistState({oldAddon: this.existingAddon});
     this.addon.updateDate = Date.now();
     this.addon.installDate = addon ? addon.installDate : this.addon.updateDate;
 
     if (!this.addon.isCompatible) {
       this.state = AddonManager.STATE_CHECKING;
 
       await new Promise(resolve => {
         new UpdateChecker(this.addon, {
@@ -2297,30 +2297,30 @@ var DownloadAddonInstall = class extends
       logger.debug("downloadFailed: listener changed AddonInstall state for " +
           this.sourceURI.spec + " to " + this.state);
   }
 
   /**
    * Notify listeners that the download completed.
    */
   downloadCompleted() {
-    XPIDatabase.getVisibleAddonForID(this.addon.id, aAddon => {
+    XPIDatabase.getVisibleAddonForID(this.addon.id, async aAddon => {
       if (aAddon)
         this.existingAddon = aAddon;
 
       this.state = AddonManager.STATE_DOWNLOADED;
       this.addon.updateDate = Date.now();
 
       if (this.existingAddon) {
         this.addon.existingAddonID = this.existingAddon.id;
         this.addon.installDate = this.existingAddon.installDate;
       } else {
         this.addon.installDate = this.addon.updateDate;
       }
-      this.addon.updateBlocklistState({oldAddon: this.existingAddon});
+      await this.addon.updateBlocklistState({oldAddon: this.existingAddon});
 
       if (AddonManagerPrivate.callInstallListeners("onDownloadEnded",
                                                    this.listeners,
                                                    this.wrapper)) {
         // If a listener changed our state then do not proceed with the install
         if (this.state != AddonManager.STATE_DOWNLOADED)
           return;
 
--- a/toolkit/mozapps/extensions/internal/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIProvider.jsm
@@ -917,21 +917,21 @@ function getAllAliasesForTypes(aTypes) {
 
   return [...typeset];
 }
 
 /**
  * A synchronous method for loading an add-on's manifest. This should only ever
  * be used during startup or a sync load of the add-ons DB
  */
-function syncLoadManifestFromFile(aFile, aInstallLocation) {
+function syncLoadManifestFromFile(aFile, aInstallLocation, aOldAddon) {
   let success = undefined;
   let result = null;
 
-  loadManifestFromFile(aFile, aInstallLocation).then(val => {
+  loadManifestFromFile(aFile, aInstallLocation, aOldAddon).then(val => {
     success = true;
     result = val;
   }, val => {
     success = false;
     result = val;
   });
 
   Services.tm.spinEventLoopUntil(() => success !== undefined);
@@ -4506,40 +4506,40 @@ AddonInternal.prototype = {
       if (targetApp.id == Services.appinfo.ID)
         return targetApp;
       if (targetApp.id == TOOLKIT_ID)
         app = targetApp;
     }
     return app;
   },
 
-  findBlocklistEntry() {
+  async findBlocklistEntry() {
     let staticItem = findMatchingStaticBlocklistItem(this);
     if (staticItem) {
       let url = Services.urlFormatter.formatURLPref(PREF_BLOCKLIST_ITEM_URL);
       return {
         state: staticItem.level,
         url: url.replace(/%blockID%/g, staticItem.blockID)
       };
     }
 
     return Blocklist.getAddonBlocklistEntry(this.wrapper);
   },
 
-  updateBlocklistState(options = {}) {
+  async updateBlocklistState(options = {}) {
     let {applySoftBlock = true, oldAddon = null, updateDatabase = true} = options;
 
     if (oldAddon) {
       this.userDisabled = oldAddon.userDisabled;
       this.softDisabled = oldAddon.softDisabled;
       this.blocklistState = oldAddon.blocklistState;
     }
     let oldState = this.blocklistState;
 
-    let entry = this.findBlocklistEntry();
+    let entry = await this.findBlocklistEntry();
     let newState = entry ? entry.state : Blocklist.STATE_NOT_BLOCKED;
 
     this.blocklistState = newState;
     this.blocklistURL = entry && entry.url;
 
     let userDisabled, softDisabled;
     // After a blocklist update, the blocklist service manually applies
     // new soft blocks after displaying a UI, in which cases we need to
@@ -4957,17 +4957,17 @@ AddonWrapper.prototype = {
 
     let activeAddon = XPIProvider.activeAddons.get(addon.id);
     if (activeAddon)
       return activeAddon.startupPromise || null;
     return null;
   },
 
   updateBlocklistState(applySoftBlock = true) {
-    addonFor(this).updateBlocklistState({applySoftBlock});
+    return addonFor(this).updateBlocklistState({applySoftBlock});
   },
 
   get userDisabled() {
     let addon = addonFor(this);
     return addon.softDisabled || addon.userDisabled;
   },
   set userDisabled(val) {
     let addon = addonFor(this);
--- a/toolkit/mozapps/extensions/internal/XPIProviderUtils.js
+++ b/toolkit/mozapps/extensions/internal/XPIProviderUtils.js
@@ -1229,19 +1229,17 @@ this.XPIDatabaseReconcile = {
    */
   updateMetadata(aInstallLocation, aOldAddon, aAddonState, aNewAddon) {
     logger.debug("Add-on " + aOldAddon.id + " modified in " + aInstallLocation.name);
 
     try {
       // If there isn't an updated install manifest for this add-on then load it.
       if (!aNewAddon) {
         let file = new nsIFile(aAddonState.path);
-        aNewAddon = syncLoadManifestFromFile(file, aInstallLocation);
-
-        aNewAddon.updateBlocklistState({oldAddon: aOldAddon});
+        aNewAddon = syncLoadManifestFromFile(file, aInstallLocation, aOldAddon);
       }
 
       // The ID in the manifest that was loaded must match the ID of the old
       // add-on.
       if (aNewAddon.id != aOldAddon.id)
         throw new Error("Incorrect id in install manifest for existing add-on " + aOldAddon.id);
     } catch (e) {
       logger.warn("updateMetadata: Add-on " + aOldAddon.id + " is invalid", e);
@@ -1290,29 +1288,22 @@ this.XPIDatabaseReconcile = {
    *
    * @param  aInstallLocation
    *         The install location containing the add-on
    * @param  aOldAddon
    *         The AddonInternal as it appeared the last time the application
    *         ran
    * @param  aAddonState
    *         The new state of the add-on
-   * @param  aOldAppVersion
-   *         The version of the application last run with this profile or null
-   *         if it is a new profile or the version is unknown
-   * @param  aOldPlatformVersion
-   *         The version of the platform last run with this profile or null
-   *         if it is a new profile or the version is unknown
    * @param  aReloadMetadata
    *         A boolean which indicates whether metadata should be reloaded from
    *         the addon manifests. Default to false.
    * @return the new addon.
    */
-  updateCompatibility(aInstallLocation, aOldAddon, aAddonState, aOldAppVersion,
-                      aOldPlatformVersion, aReloadMetadata) {
+  updateCompatibility(aInstallLocation, aOldAddon, aAddonState, aReloadMetadata) {
     logger.debug("Updating compatibility for add-on " + aOldAddon.id + " in " + aInstallLocation.name);
 
     let checkSigning = aOldAddon.signedState === undefined && ADDON_SIGNING &&
                        SIGNED_TYPES.has(aOldAddon.type);
 
     let manifest = null;
     if (checkSigning || aReloadMetadata) {
       try {
@@ -1341,17 +1332,16 @@ this.XPIDatabaseReconcile = {
       let remove = ["syncGUID", "foreignInstall", "visible", "active",
                     "userDisabled", "applyBackgroundUpdates", "sourceURI",
                     "releaseNotesURI", "targetApplications"];
 
       let props = PROP_JSON_FIELDS.filter(a => !remove.includes(a));
       copyProperties(manifest, props, aOldAddon);
     }
 
-    aOldAddon.updateBlocklistState({updateDatabase: false});
     aOldAddon.appDisabled = !isUsableAddon(aOldAddon);
 
     return aOldAddon;
   },
 
   /**
    * Compares the add-ons that are currently installed to those that were
    * known to be installed when the application last ran and applies any
@@ -1404,16 +1394,20 @@ this.XPIDatabaseReconcile = {
       let locationAddonMap = previousAddons.get(a.location);
       if (!locationAddonMap) {
         locationAddonMap = new Map();
         previousAddons.set(a.location, locationAddonMap);
       }
       locationAddonMap.set(a.id, a);
     }
 
+    // Keep track of add-ons whose blocklist status may have changed. We'll check this
+    // after everything else.
+    let addonsToCheckAgainstBlocklist = [];
+
     // Build the list of current add-ons into similar maps. When add-ons are still
     // present we re-use the add-on objects from the database and update their
     // details directly
     let currentAddons = new Map();
     for (let installLocation of XPIProvider.installLocations) {
       let locationAddonMap = new Map();
       currentAddons.set(installLocation.name, locationAddonMap);
 
@@ -1458,18 +1452,20 @@ this.XPIDatabaseReconcile = {
               newAddon = this.updateMetadata(installLocation, oldAddon, xpiState, newAddon);
             } else if (oldPath != xpiState.path) {
               newAddon = this.updatePath(installLocation, oldAddon, xpiState);
             } else if (aUpdateCompatibility || aSchemaChange) {
               // Check compatility when the application version and/or schema
               // version has changed. A schema change also reloads metadata from
               // the manifests.
               newAddon = this.updateCompatibility(installLocation, oldAddon, xpiState,
-                                                  aOldAppVersion, aOldPlatformVersion,
                                                   aSchemaChange);
+              // We need to do a blocklist check later, but the add-on may have changed by then.
+              // Avoid storing the current copy and just get one when we need one instead.
+              addonsToCheckAgainstBlocklist.push(newAddon.id);
             } else {
               // No change
               newAddon = oldAddon;
             }
 
             if (newAddon)
               locationAddonMap.set(newAddon.id, newAddon);
           } else {
@@ -1664,11 +1660,31 @@ this.XPIDatabaseReconcile = {
       }
     }
     XPIStates.save();
 
     // Clear out any cached migration data.
     XPIDatabase.migrateData = null;
     XPIDatabase.saveChanges();
 
+    // Do some blocklist checks. These will happen after we've just saved everything,
+    // because they're async and depend on the blocklist loading. When we're done, save
+    // the data if any of the add-ons' blocklist state has changed.
+    AddonManager.shutdown.addBlocker(
+      "Update add-on blocklist state into add-on DB",
+      (async () => {
+        // Avoid querying the AddonManager immediately to give startup a chance
+        // to complete.
+        await Promise.resolve();
+        let addons = await AddonManager.getAddonsByIDs(addonsToCheckAgainstBlocklist);
+        await Promise.all(addons.map(addon => {
+          if (addon) {
+            return addon.updateBlocklistState({updateDatabase: false});
+          }
+          return null;
+        }));
+        XPIDatabase.saveChanges();
+      })().catch(Cu.reportError)
+    );
+
     return true;
   },
 };
--- a/toolkit/mozapps/extensions/nsBlocklistService.js
+++ b/toolkit/mozapps/extensions/nsBlocklistService.js
@@ -339,19 +339,18 @@ Blocklist.prototype = {
                   Ci.nsIBlocklistService.STATE_BLOCKED : Ci.nsIBlocklistService.STATE_SOFTBLOCKED),
           url: blItem.blockID && this._createBlocklistURL(blItem.blockID),
         };
       }
     }
     return null;
   },
 
-  getAddonBlocklistEntry(addon, appVersion, toolkitVersion) {
-    if (!this.isLoaded)
-      this._loadBlocklist();
+  async getAddonBlocklistEntry(addon, appVersion, toolkitVersion) {
+    await this.loadBlocklistAsync();
     return this._getAddonBlocklistEntry(addon, this._addonEntries,
                                         appVersion, toolkitVersion);
   },
 
   /**
    * Private version of getAddonBlocklistState that allows the caller to pass in
    * the add-on blocklist entries to compare against.
    *
@@ -761,19 +760,30 @@ Blocklist.prototype = {
     return this._addonEntries != null && this._gfxEntries != null && this._pluginEntries != null;
   },
 
   /* Used for testing */
   _clear() {
     this._addonEntries = null;
     this._gfxEntries = null;
     this._pluginEntries = null;
+    delete this._preloadPromise;
   },
 
   async loadBlocklistAsync() {
+    if (this.isLoaded) {
+      return;
+    }
+    if (!this._preloadPromise) {
+      this._preloadPromise = this._loadBlocklistAsyncInternal();
+    }
+    await this._preloadPromise;
+  },
+
+  async _loadBlocklistAsyncInternal() {
     let profPath = OS.Path.join(OS.Constants.Path.profileDir, FILE_BLOCKLIST);
     try {
       await this._preloadBlocklistFile(profPath);
       return;
     } catch (e) {
       LOG("Blocklist::loadBlocklistAsync: Failed to load XML file " + e);
     }
 
@@ -784,18 +794,17 @@ Blocklist.prototype = {
     } catch (e) {
       LOG("Blocklist::loadBlocklistAsync: Failed to load XML file " + e);
     }
 
     LOG("Blocklist::loadBlocklistAsync: no XML File found");
   },
 
   async _preloadBlocklistFile(path) {
-    if (this._addonEntries) {
-      // The file has been already loaded.
+    if (this.isLoaded) {
       return;
     }
 
     if (!gBlocklistEnabled) {
       LOG("Blocklist::_preloadBlocklistFile: blocklist is disabled");
       return;
     }
 
@@ -1212,197 +1221,196 @@ Blocklist.prototype = {
     Services.obs.notifyObservers(null, "blocklist-data-gfxItems", payload);
   },
 
   _notifyObserversBlocklistUpdated() {
     Services.obs.notifyObservers(this, "blocklist-updated");
     Services.ppmm.broadcastAsyncMessage("Blocklist:blocklistInvalidated", {});
   },
 
-  _blocklistUpdated(oldAddonEntries, oldPluginEntries) {
+  async _blocklistUpdated(oldAddonEntries, oldPluginEntries) {
     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 => {
-      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;
-        }
-        let state = addon.blocklistState;
+    let addons = await AddonManager.getAddonsByTypes(types);
+    for (let addon of addons) {
+      let oldState = addon.blocklistState;
+      if (addon.updateBlocklistState) {
+        await addon.updateBlocklistState(false);
+      } else if (oldAddonEntries) {
+        oldState = this._getAddonBlocklistState(addon, oldAddonEntries);
+      } else {
+        oldState = Ci.nsIBlocklistService.STATE_NOTBLOCKED;
+      }
+      let state = addon.blocklistState;
+
+      LOG("Blocklist state for " + addon.id + " changed from " +
+          oldState + " to " + state);
+
+      // We don't want to re-warn about add-ons
+      if (state == oldState)
+        continue;
+
+      if (state === Ci.nsIBlocklistService.STATE_BLOCKED) {
+        // It's a hard block. We must reset certain preferences.
+        let prefs = this._getAddonPrefs(addon);
+        resetPrefs(prefs);
+      }
+
+      // Ensure that softDisabled is false if the add-on is not soft blocked
+      if (state != Ci.nsIBlocklistService.STATE_SOFTBLOCKED)
+        addon.softDisabled = false;
+
+      // Don't warn about add-ons becoming unblocked.
+      if (state == Ci.nsIBlocklistService.STATE_NOT_BLOCKED)
+        continue;
 
-        LOG("Blocklist state for " + addon.id + " changed from " +
-            oldState + " to " + state);
+      // If an add-on has dropped from hard to soft blocked just mark it as
+      // soft disabled and don't warn about it.
+      if (state == Ci.nsIBlocklistService.STATE_SOFTBLOCKED &&
+          oldState == Ci.nsIBlocklistService.STATE_BLOCKED) {
+        addon.softDisabled = true;
+        continue;
+      }
+
+      // If the add-on is already disabled for some reason then don't warn
+      // about it
+      if (!addon.isActive) {
+        // But mark it as softblocked if necessary. Note that we avoid setting
+        // softDisabled at the same time as userDisabled to make it clear
+        // which was the original cause of the add-on becoming disabled in a
+        // way that the user can change.
+        if (state == Ci.nsIBlocklistService.STATE_SOFTBLOCKED && !addon.userDisabled)
+          addon.softDisabled = true;
+        continue;
+      }
+
+      let entry = this._getAddonBlocklistEntry(addon, this._addonEntries);
+      addonList.push({
+        name: addon.name,
+        version: addon.version,
+        icon: addon.iconURL,
+        disable: false,
+        blocked: state == Ci.nsIBlocklistService.STATE_BLOCKED,
+        item: addon,
+        url: entry && entry.url,
+      });
+    }
+
+    AddonManagerPrivate.updateAddonAppDisabledStates();
 
-        // We don't want to re-warn about add-ons
-        if (state == oldState)
-          continue;
+    var phs = Cc["@mozilla.org/plugin/host;1"].
+              getService(Ci.nsIPluginHost);
+    var plugins = phs.getPluginTags();
+
+    for (let plugin of plugins) {
+      let oldState = -1;
+      if (oldPluginEntries)
+        oldState = this._getPluginBlocklistState(plugin, oldPluginEntries);
+      let state = this.getPluginBlocklistState(plugin);
+      LOG("Blocklist state for " + plugin.name + " changed from " +
+          oldState + " to " + state);
+      // We don't want to re-warn about items
+      if (state == oldState)
+        continue;
 
-        if (state === Ci.nsIBlocklistService.STATE_BLOCKED) {
-          // It's a hard block. We must reset certain preferences.
-          let prefs = this._getAddonPrefs(addon);
-          resetPrefs(prefs);
+      if (oldState == Ci.nsIBlocklistService.STATE_BLOCKED) {
+        if (state == Ci.nsIBlocklistService.STATE_SOFTBLOCKED)
+          plugin.enabledState = Ci.nsIPluginTag.STATE_DISABLED;
+      } else if (!plugin.disabled && state != Ci.nsIBlocklistService.STATE_NOT_BLOCKED) {
+        if (state != Ci.nsIBlocklistService.STATE_OUTDATED &&
+            state != Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE &&
+            state != Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE) {
+          addonList.push({
+            name: plugin.name,
+            version: plugin.version,
+            icon: "chrome://mozapps/skin/plugins/pluginGeneric.svg",
+            disable: false,
+            blocked: state == Ci.nsIBlocklistService.STATE_BLOCKED,
+            item: plugin,
+            url: this.getPluginBlocklistURL(plugin),
+          });
         }
+      }
+    }
+
+    if (addonList.length == 0) {
+      this._notifyObserversBlocklistUpdated();
+      return;
+    }
 
-        // Ensure that softDisabled is false if the add-on is not soft blocked
-        if (state != Ci.nsIBlocklistService.STATE_SOFTBLOCKED)
-          addon.softDisabled = false;
+    if ("@mozilla.org/addons/blocklist-prompt;1" in Cc) {
+      try {
+        let blockedPrompter = Cc["@mozilla.org/addons/blocklist-prompt;1"]
+                               .getService(Ci.nsIBlocklistPrompt);
+        blockedPrompter.prompt(addonList);
+      } catch (e) {
+        LOG(e);
+      }
+      this._notifyObserversBlocklistUpdated();
+      return;
+    }
 
-        // Don't warn about add-ons becoming unblocked.
-        if (state == Ci.nsIBlocklistService.STATE_NOT_BLOCKED)
+    var args = {
+      restart: false,
+      list: addonList
+    };
+    // This lets the dialog get the raw js object
+    args.wrappedJSObject = args;
+
+    /*
+      Some tests run without UI, so the async code listens to a message
+      that can be sent programatically
+    */
+    let applyBlocklistChanges = () => {
+      for (let addon of addonList) {
+        if (!addon.disable)
           continue;
 
-        // If an add-on has dropped from hard to soft blocked just mark it as
-        // soft disabled and don't warn about it.
-        if (state == Ci.nsIBlocklistService.STATE_SOFTBLOCKED &&
-            oldState == Ci.nsIBlocklistService.STATE_BLOCKED) {
-          addon.softDisabled = true;
-          continue;
-        }
-
-        // If the add-on is already disabled for some reason then don't warn
-        // about it
-        if (!addon.isActive) {
-          // But mark it as softblocked if necessary. Note that we avoid setting
-          // softDisabled at the same time as userDisabled to make it clear
-          // which was the original cause of the add-on becoming disabled in a
-          // way that the user can change.
-          if (state == Ci.nsIBlocklistService.STATE_SOFTBLOCKED && !addon.userDisabled)
-            addon.softDisabled = true;
-          continue;
-        }
-
-        let entry = this._getAddonBlocklistEntry(addon, this._addonEntries);
-        addonList.push({
-          name: addon.name,
-          version: addon.version,
-          icon: addon.iconURL,
-          disable: false,
-          blocked: state == Ci.nsIBlocklistService.STATE_BLOCKED,
-          item: addon,
-          url: entry && entry.url,
-        });
-      }
-
-      AddonManagerPrivate.updateAddonAppDisabledStates();
-
-      var phs = Cc["@mozilla.org/plugin/host;1"].
-                getService(Ci.nsIPluginHost);
-      var plugins = phs.getPluginTags();
-
-      for (let plugin of plugins) {
-        let oldState = -1;
-        if (oldPluginEntries)
-          oldState = this._getPluginBlocklistState(plugin, oldPluginEntries);
-        let state = this.getPluginBlocklistState(plugin);
-        LOG("Blocklist state for " + plugin.name + " changed from " +
-            oldState + " to " + state);
-        // We don't want to re-warn about items
-        if (state == oldState)
-          continue;
-
-        if (oldState == Ci.nsIBlocklistService.STATE_BLOCKED) {
-          if (state == Ci.nsIBlocklistService.STATE_SOFTBLOCKED)
-            plugin.enabledState = Ci.nsIPluginTag.STATE_DISABLED;
-        } else if (!plugin.disabled && state != Ci.nsIBlocklistService.STATE_NOT_BLOCKED) {
-          if (state != Ci.nsIBlocklistService.STATE_OUTDATED &&
-              state != Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE &&
-              state != Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE) {
-            addonList.push({
-              name: plugin.name,
-              version: plugin.version,
-              icon: "chrome://mozapps/skin/plugins/pluginGeneric.svg",
-              disable: false,
-              blocked: state == Ci.nsIBlocklistService.STATE_BLOCKED,
-              item: plugin,
-              url: this.getPluginBlocklistURL(plugin),
-            });
-          }
+        if (addon.item instanceof Ci.nsIPluginTag)
+          addon.item.enabledState = Ci.nsIPluginTag.STATE_DISABLED;
+        else {
+          // This add-on is softblocked.
+          addon.item.softDisabled = true;
+          // We must revert certain prefs.
+          let prefs = this._getAddonPrefs(addon.item);
+          resetPrefs(prefs);
         }
       }
 
-      if (addonList.length == 0) {
-        this._notifyObserversBlocklistUpdated();
-        return;
-      }
+      if (args.restart)
+        restartApp();
 
-      if ("@mozilla.org/addons/blocklist-prompt;1" in Cc) {
-        try {
-          let blockedPrompter = Cc["@mozilla.org/addons/blocklist-prompt;1"]
-                                 .getService(Ci.nsIBlocklistPrompt);
-          blockedPrompter.prompt(addonList);
-        } catch (e) {
-          LOG(e);
-        }
-        this._notifyObserversBlocklistUpdated();
-        return;
-      }
+      this._notifyObserversBlocklistUpdated();
+      Services.obs.removeObserver(applyBlocklistChanges, "addon-blocklist-closed");
+    };
 
-      var args = {
-        restart: false,
-        list: addonList
-      };
-      // This lets the dialog get the raw js object
-      args.wrappedJSObject = args;
-
-      /*
-        Some tests run without UI, so the async code listens to a message
-        that can be sent programatically
-      */
-      let applyBlocklistChanges = () => {
-        for (let addon of addonList) {
-          if (!addon.disable)
-            continue;
+    Services.obs.addObserver(applyBlocklistChanges, "addon-blocklist-closed");
 
-          if (addon.item instanceof Ci.nsIPluginTag)
-            addon.item.enabledState = Ci.nsIPluginTag.STATE_DISABLED;
-          else {
-            // This add-on is softblocked.
-            addon.item.softDisabled = true;
-            // We must revert certain prefs.
-            let prefs = this._getAddonPrefs(addon.item);
-            resetPrefs(prefs);
-          }
-        }
-
-        if (args.restart)
-          restartApp();
-
-        this._notifyObserversBlocklistUpdated();
-        Services.obs.removeObserver(applyBlocklistChanges, "addon-blocklist-closed");
-      };
+    if (Services.prefs.getBoolPref(PREF_BLOCKLIST_SUPPRESSUI, false)) {
+      applyBlocklistChanges();
+      return;
+    }
 
-      Services.obs.addObserver(applyBlocklistChanges, "addon-blocklist-closed");
-
-      if (Services.prefs.getBoolPref(PREF_BLOCKLIST_SUPPRESSUI, false)) {
+    function blocklistUnloadHandler(event) {
+      if (event.target.location == URI_BLOCKLIST_DIALOG) {
         applyBlocklistChanges();
-        return;
+        blocklistWindow.removeEventListener("unload", blocklistUnloadHandler);
       }
+    }
 
-      function blocklistUnloadHandler(event) {
-        if (event.target.location == URI_BLOCKLIST_DIALOG) {
-          applyBlocklistChanges();
-          blocklistWindow.removeEventListener("unload", blocklistUnloadHandler);
-        }
-      }
-
-      let blocklistWindow = Services.ww.openWindow(null, URI_BLOCKLIST_DIALOG, "",
-                              "chrome,centerscreen,dialog,titlebar", args);
-      if (blocklistWindow)
-        blocklistWindow.addEventListener("unload", blocklistUnloadHandler);
-    });
+    let blocklistWindow = Services.ww.openWindow(null, URI_BLOCKLIST_DIALOG, "",
+                            "chrome,centerscreen,dialog,titlebar", args);
+    if (blocklistWindow)
+      blocklistWindow.addEventListener("unload", blocklistUnloadHandler);
   },
 
   classID: Components.ID("{66354bc9-7ed1-4692-ae1d-8da97d6b205e}"),
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
                                          Ci.nsIBlocklistService,
                                          Ci.nsITimerCallback]),
 };
 
--- a/toolkit/mozapps/extensions/test/xpcshell/test_blocklist_severities.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_blocklist_severities.js
@@ -9,18 +9,18 @@ ChromeUtils.import("resource://testing-c
 
 var gTestserver = AddonTestUtils.createHttpServer({hosts: ["example.com"]});
 gTestserver.registerDirectory("/data/", do_get_file("data"));
 
 // Workaround for Bug 658720 - URL formatter can leak during xpcshell tests
 const PREF_BLOCKLIST_ITEM_URL = "extensions.blocklist.itemURL";
 Services.prefs.setCharPref(PREF_BLOCKLIST_ITEM_URL, "http://example.com/blocklist/%blockID%");
 
-function getAddonBlocklistURL(addon) {
-  let entry = Services.blocklist.getAddonBlocklistEntry(addon);
+async function getAddonBlocklistURL(addon) {
+  let entry = await Services.blocklist.getAddonBlocklistEntry(addon);
   return entry && entry.url;
 }
 
 var ADDONS = [{
   // Tests how the blocklist affects a disabled add-on
   id: "test_bug455906_1@tests.mozilla.org",
   name: "Bug 455906 Addon Test 1",
   version: "5",
@@ -361,21 +361,21 @@ add_task(async function test_pt3() {
   equal(check_plugin_state(PLUGINS[2]), "false,true");
   equal(check_plugin_state(PLUGINS[3]), "true,true");
   equal(check_plugin_state(PLUGINS[4]), "false,true");
 
   // Should have gained the blocklist state but no longer be soft disabled
   checkAddonState(addons[3], {userDisabled: false, softDisabled: false, appDisabled: true});
 
   // Check blockIDs are correct
-  equal(getAddonBlocklistURL(addons[0]), create_blocklistURL(addons[0].id));
-  equal(getAddonBlocklistURL(addons[1]), create_blocklistURL(addons[1].id));
-  equal(getAddonBlocklistURL(addons[2]), create_blocklistURL(addons[2].id));
-  equal(getAddonBlocklistURL(addons[3]), create_blocklistURL(addons[3].id));
-  equal(getAddonBlocklistURL(addons[4]), create_blocklistURL(addons[4].id));
+  equal(await getAddonBlocklistURL(addons[0]), create_blocklistURL(addons[0].id));
+  equal(await getAddonBlocklistURL(addons[1]), create_blocklistURL(addons[1].id));
+  equal(await getAddonBlocklistURL(addons[2]), create_blocklistURL(addons[2].id));
+  equal(await getAddonBlocklistURL(addons[3]), create_blocklistURL(addons[3].id));
+  equal(await getAddonBlocklistURL(addons[4]), create_blocklistURL(addons[4].id));
 
   // All plugins have the same blockID on the test
   equal(Services.blocklist.getPluginBlocklistURL(PLUGINS[0]), create_blocklistURL("test_bug455906_plugin"));
   equal(Services.blocklist.getPluginBlocklistURL(PLUGINS[1]), create_blocklistURL("test_bug455906_plugin"));
   equal(Services.blocklist.getPluginBlocklistURL(PLUGINS[2]), create_blocklistURL("test_bug455906_plugin"));
   equal(Services.blocklist.getPluginBlocklistURL(PLUGINS[3]), create_blocklistURL("test_bug455906_plugin"));
   equal(Services.blocklist.getPluginBlocklistURL(PLUGINS[4]), create_blocklistURL("test_bug455906_plugin"));
 
--- a/toolkit/mozapps/extensions/test/xpcshell/test_blocklistchange.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_blocklistchange.js
@@ -799,17 +799,17 @@ add_task(async function run_app_update_s
   check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
 });
 
 add_task(async function update_schema_2() {
   await promiseShutdownManager();
 
   await changeXPIDBVersion(100);
   gAppInfo.version = "2";
-  startupManager(true);
+  await promiseStartupManager(true);
 
   let [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS);
 
   check_addon(s1, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
   check_addon(s2, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
   check_addon(s3, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
   check_addon(s4, "1.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
   check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED);
@@ -823,17 +823,17 @@ add_task(async function update_schema_2(
 });
 
 add_task(async function update_schema_3() {
   await promiseRestartManager();
 
   await promiseShutdownManager();
   await changeXPIDBVersion(100);
   gAppInfo.version = "2.5";
-  startupManager(true);
+  await promiseStartupManager(true);
 
   let [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS);
 
   check_addon(s1, "1.0", true, true, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
   check_addon(s2, "1.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
   check_addon(s3, "1.0", false, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
   check_addon(s4, "1.0", true, false, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
   check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED);
@@ -856,17 +856,17 @@ add_task(async function update_schema_4(
   check_addon(r, "1.0", false, false, Ci.nsIBlocklistService.STATE_BLOCKED);
 });
 
 add_task(async function update_schema_5() {
   await promiseShutdownManager();
 
   await changeXPIDBVersion(100);
   gAppInfo.version = "1";
-  startupManager(true);
+  await promiseStartupManager(true);
 
   let [s1, s2, s3, s4, h, r] = await promiseAddonsByIDs(ADDON_IDS);
 
   check_addon(s1, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
   check_addon(s2, "1.0", true, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
   check_addon(s3, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
   check_addon(s4, "1.0", true, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
   check_addon(h, "1.0", false, false, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
@@ -1057,17 +1057,17 @@ add_task(async function run_addon_change
 
   getFileForAddon(profileDir, softblock1_1.id).remove(true);
   getFileForAddon(profileDir, softblock2_1.id).remove(true);
   getFileForAddon(profileDir, softblock3_1.id).remove(true);
   getFileForAddon(profileDir, softblock4_1.id).remove(true);
   getFileForAddon(profileDir, hardblock_1.id).remove(true);
   getFileForAddon(profileDir, regexpblock_1.id).remove(true);
 
-  startupManager(false);
+  await promiseStartupManager(false);
   await promiseShutdownManager();
 
   writeInstallRDFForExtension(softblock1_2, profileDir);
   writeInstallRDFForExtension(softblock2_2, profileDir);
   writeInstallRDFForExtension(softblock3_2, profileDir);
   writeInstallRDFForExtension(softblock4_2, profileDir);
   writeInstallRDFForExtension(hardblock_2, profileDir);
   writeInstallRDFForExtension(regexpblock_2, profileDir);
@@ -1183,17 +1183,17 @@ add_task(async function run_background_u
 
   getFileForAddon(profileDir, softblock1_1.id).remove(true);
   getFileForAddon(profileDir, softblock2_1.id).remove(true);
   getFileForAddon(profileDir, softblock3_1.id).remove(true);
   getFileForAddon(profileDir, softblock4_1.id).remove(true);
   getFileForAddon(profileDir, hardblock_1.id).remove(true);
   getFileForAddon(profileDir, regexpblock_1.id).remove(true);
 
-  startupManager(false);
+  await promiseStartupManager(false);
   await promiseShutdownManager();
 
   writeInstallRDFForExtension(softblock1_3, profileDir);
   writeInstallRDFForExtension(softblock2_3, profileDir);
   writeInstallRDFForExtension(softblock3_3, profileDir);
   writeInstallRDFForExtension(softblock4_3, profileDir);
   writeInstallRDFForExtension(hardblock_3, profileDir);
   writeInstallRDFForExtension(regexpblock_3, profileDir);
@@ -1289,17 +1289,17 @@ add_task(async function run_manual_updat
 
   getFileForAddon(profileDir, softblock1_1.id).remove(true);
   getFileForAddon(profileDir, softblock2_1.id).remove(true);
   getFileForAddon(profileDir, softblock3_1.id).remove(true);
   getFileForAddon(profileDir, softblock4_1.id).remove(true);
   getFileForAddon(profileDir, hardblock_1.id).remove(true);
   getFileForAddon(profileDir, regexpblock_1.id).remove(true);
 
-  startupManager(false);
+  await promiseStartupManager(false);
   await promiseShutdownManager();
 
   writeInstallRDFForExtension(softblock1_1, profileDir);
   writeInstallRDFForExtension(softblock2_1, profileDir);
   writeInstallRDFForExtension(softblock3_1, profileDir);
   writeInstallRDFForExtension(softblock4_1, profileDir);
   writeInstallRDFForExtension(hardblock_1, profileDir);
   writeInstallRDFForExtension(regexpblock_1, profileDir);
--- a/xpcom/system/nsIBlocklistService.idl
+++ b/xpcom/system/nsIBlocklistService.idl
@@ -59,19 +59,20 @@ interface nsIBlocklistService : nsISuppo
    *          is used.
    * @returns The STATE constant.
    */
   unsigned long getPluginBlocklistState(in nsIPluginTag plugin,
                                         [optional] in AString appVersion,
                                         [optional] in AString toolkitVersion);
 
   /**
-   * Returns the blocklist entry, as an object with `state` and `url`
+   * Returns a promise that resolves to the blocklist entry.
+   * The blocklist entry is an object with `state` and `url`
    * properties, if a blocklist entry for the add-on exists, or null
-   * othereise.
+   * otherwise.
 
    * @param   addon
    *          The addon object to match.
    * @param   appVersion
    *          The version of the application we are checking in the blocklist.
    *          If this parameter is null, the version of the running application
    *          is used.
    * @param   toolkitVersion