Bug 1456677: Make the blocklist service a JSM, with an XPCOM service stub. r=Gijs
authorKris Maglione <maglione.k@gmail.com>
Tue, 24 Apr 2018 17:46:44 -0700
changeset 469220 35fe513112642f7d2f9c99da61538db193701a94
parent 469219 2add2de30c229733a3d0f11843695fcda818fe94
child 469221 bd589df323a908de450258d46381efe19ec4afca
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)
reviewersGijs
bugs1456677
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 1456677: Make the blocklist service a JSM, with an XPCOM service stub. r=Gijs Aside from making things easier for JS callers, this also makes it harder to accidentally trigger an early load of the service, which can be expensive during startup. This also makes a slight change to nsPluginHost to initially preserve the previous blocklist state when a plugin is updated, to avoid the risk of the possible additioanl asynchrony unblocking a plugin that should stay blocked. MozReview-Commit-ID: 4EvIGJ1Ke0Z
browser/base/content/browser-plugins.js
browser/components/nsBrowserGlue.js
browser/installer/package-manifest.in
dom/plugins/base/nsPluginHost.cpp
mobile/android/installer/package-manifest.in
toolkit/mozapps/extensions/Blocklist.jsm
toolkit/mozapps/extensions/addonManager.js
toolkit/mozapps/extensions/extensions.manifest
toolkit/mozapps/extensions/internal/AddonTestUtils.jsm
toolkit/mozapps/extensions/internal/AddonUpdateChecker.jsm
toolkit/mozapps/extensions/internal/PluginProvider.jsm
toolkit/mozapps/extensions/internal/XPIDatabase.jsm
toolkit/mozapps/extensions/moz.build
toolkit/mozapps/extensions/nsBlocklistService.js
toolkit/mozapps/extensions/test/xpcshell/head_addons.js
toolkit/mozapps/extensions/test/xpcshell/test_asyncBlocklistLoad.js
toolkit/mozapps/extensions/test/xpcshell/test_blocklist_appversion.js
toolkit/mozapps/extensions/test/xpcshell/test_blocklist_gfx.js
toolkit/mozapps/extensions/test/xpcshell/test_blocklist_osabi.js
toolkit/mozapps/extensions/test/xpcshell/test_blocklist_plugin_flashonly.js
toolkit/mozapps/extensions/test/xpcshell/test_blocklist_plugin_outdated.js
toolkit/mozapps/extensions/test/xpcshell/test_blocklist_plugin_regexp.js
toolkit/mozapps/extensions/test/xpcshell/test_blocklist_plugin_severities.js
toolkit/mozapps/extensions/test/xpcshell/test_blocklist_severities.js
toolkit/mozapps/extensions/test/xpcshell/test_blocklist_url_parameters.js
toolkit/mozapps/extensions/test/xpcshell/test_blocklist_url_ping_count.js
toolkit/mozapps/extensions/test/xpcshell/test_overrideblocklist.js
toolkit/mozapps/extensions/test/xpcshell/test_pluginBlocklistCtp.js
toolkit/mozapps/extensions/test/xpcshell/test_pluginInfoURL.js
toolkit/mozapps/extensions/test/xpcshell/test_update.js
--- a/browser/base/content/browser-plugins.js
+++ b/browser/base/content/browser-plugins.js
@@ -1,13 +1,16 @@
 /* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+ChromeUtils.defineModuleGetter(this, "Blocklist",
+                               "resource://gre/modules/Blocklist.jsm");
+
 var gPluginHandler = {
   PREF_SESSION_PERSIST_MINUTES: "plugin.sessionPermissionNow.intervalInMinutes",
   PREF_PERSISTENT_DAYS: "plugin.persistentPermissionAlways.intervalInDays",
   PREF_SHOW_INFOBAR: "plugins.show_infobar",
   PREF_INFOBAR_DISMISSAL_PERMANENT: "plugins.remember_infobar_dismissal",
 
   MESSAGES: [
     "PluginContent:ShowClickToPlayNotification",
@@ -89,19 +92,19 @@ var gPluginHandler = {
   // Callback for user clicking on a disabled plugin
   managePlugins() {
     BrowserOpenAddonsMgr("addons://list/plugin");
   },
 
   // Callback for user clicking on the link in a click-to-play plugin
   // (where the plugin has an update)
   openPluginUpdatePage(pluginTag) {
-    let url = Services.blocklist.getPluginInfoURL(pluginTag);
+    let url = Blocklist.getPluginInfoURL(pluginTag);
     if (!url) {
-      url = Services.blocklist.getPluginBlocklistURL(pluginTag);
+      url = Blocklist.getPluginBlocklistURL(pluginTag);
     }
     openTrustedLinkIn(url, "tab");
   },
 
   submitReport: function submitReport(runID, keyVals, submitURLOptIn) {
     if (!AppConstants.MOZ_CRASHREPORTER) {
       return;
     }
@@ -266,21 +269,21 @@ var gPluginHandler = {
 
     for (let pluginInfo of plugins) {
       if (pluginData.has(pluginInfo.permissionString)) {
         continue;
       }
 
       // If a block contains an infoURL, we should always prefer that to the default
       // URL that we construct in-product, even for other blocklist types.
-      let url = Services.blocklist.getPluginInfoURL(pluginInfo.pluginTag);
+      let url = Blocklist.getPluginInfoURL(pluginInfo.pluginTag);
 
       if (pluginInfo.blocklistState != Ci.nsIBlocklistService.STATE_NOT_BLOCKED) {
         if (!url) {
-          url = Services.blocklist.getPluginBlocklistURL(pluginInfo.pluginTag);
+          url = Blocklist.getPluginBlocklistURL(pluginInfo.pluginTag);
         }
       } else {
         url = Services.urlFormatter.formatURLPref("app.support.baseURL") + "clicktoplay";
       }
       pluginInfo.detailsLink = url;
 
       pluginData.set(pluginInfo.permissionString, pluginInfo);
     }
@@ -300,21 +303,21 @@ var gPluginHandler = {
       }
       return;
     }
 
     if (plugins.length == 1) {
       let pluginInfo = plugins[0];
       // If a block contains an infoURL, we should always prefer that to the default
       // URL that we construct in-product, even for other blocklist types.
-      let url = Services.blocklist.getPluginInfoURL(pluginInfo.pluginTag);
+      let url = Blocklist.getPluginInfoURL(pluginInfo.pluginTag);
 
       if (pluginInfo.blocklistState != Ci.nsIBlocklistService.STATE_NOT_BLOCKED) {
         if (!url) {
-          url = Services.blocklist.getPluginBlocklistURL(pluginInfo.pluginTag);
+          url = Blocklist.getPluginBlocklistURL(pluginInfo.pluginTag);
         }
       } else {
         url = Services.urlFormatter.formatURLPref("app.support.baseURL") + "clicktoplay";
       }
       pluginInfo.detailsLink = url;
 
       let chromeWin = window.QueryInterface(Ci.nsIDOMChromeWindow);
       let isWindowPrivate = PrivateBrowsingUtils.isWindowPrivate(chromeWin);
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -86,16 +86,17 @@ XPCOMUtils.defineLazyGetter(this, "Weave
 // lazy module getters
 
 XPCOMUtils.defineLazyModuleGetters(this, {
   AddonManager: "resource://gre/modules/AddonManager.jsm",
   AppMenuNotifications: "resource://gre/modules/AppMenuNotifications.jsm",
   AsyncPrefs: "resource://gre/modules/AsyncPrefs.jsm",
   AsyncShutdown: "resource://gre/modules/AsyncShutdown.jsm",
   AutoCompletePopup: "resource://gre/modules/AutoCompletePopup.jsm",
+  Blocklist: "resource://gre/modules/Blocklist.jsm",
   BookmarkHTMLUtils: "resource://gre/modules/BookmarkHTMLUtils.jsm",
   BookmarkJSONUtils: "resource://gre/modules/BookmarkJSONUtils.jsm",
   BrowserErrorReporter: "resource:///modules/BrowserErrorReporter.jsm",
   BrowserUITelemetry: "resource:///modules/BrowserUITelemetry.jsm",
   BrowserUsageTelemetry: "resource:///modules/BrowserUsageTelemetry.jsm",
   BrowserWindowTracker: "resource:///modules/BrowserWindowTracker.jsm",
   ContentClick: "resource:///modules/ContentClick.jsm",
   ContextualIdentityService: "resource://gre/modules/ContextualIdentityService.jsm",
@@ -1218,17 +1219,17 @@ BrowserGlue.prototype = {
       });
     }
 
     Services.tm.idleDispatchToMainThread(() => {
       LanguagePrompt.init();
     });
 
     Services.tm.idleDispatchToMainThread(() => {
-      Services.blocklist.loadBlocklistAsync();
+      Blocklist.loadBlocklistAsync();
     });
   },
 
   /**
    * Use this function as an entry point to schedule tasks that need
    * to run once per session, at any arbitrary point in time.
    * This function will be called from an idle observer. Check the value of
    * LATE_TASKS_IDLE_TIME_SEC to see the current value for this idle
--- a/browser/installer/package-manifest.in
+++ b/browser/installer/package-manifest.in
@@ -225,17 +225,16 @@
 @RESPATH@/components/NetworkGeolocationProvider.js
 @RESPATH@/components/extensions.manifest
 @RESPATH@/components/EditorUtils.manifest
 @RESPATH@/components/EditorUtils.js
 @RESPATH@/components/addonManager.js
 @RESPATH@/components/amContentHandler.js
 @RESPATH@/components/amInstallTrigger.js
 @RESPATH@/components/amWebAPI.js
-@RESPATH@/components/nsBlocklistService.js
 #ifdef MOZ_UPDATER
 @RESPATH@/components/nsUpdateService.manifest
 @RESPATH@/components/nsUpdateService.js
 @RESPATH@/components/nsUpdateServiceStub.js
 #endif
 @RESPATH@/components/nsUpdateTimerManager.manifest
 @RESPATH@/components/nsUpdateTimerManager.js
 @RESPATH@/components/utils.manifest
--- a/dom/plugins/base/nsPluginHost.cpp
+++ b/dom/plugins/base/nsPluginHost.cpp
@@ -2137,19 +2137,21 @@ nsresult nsPluginHost::ScanPluginsDirect
     const bool fromExtension = GetPluginIsFromExtension(localfile, extensionDirs);
 
     // Look for it in our cache
     NS_ConvertUTF16toUTF8 filePath(utf16FilePath);
     RefPtr<nsPluginTag> pluginTag;
     RemoveCachedPluginsInfo(filePath.get(), getter_AddRefs(pluginTag));
 
     bool seenBefore = false;
+    uint32_t blocklistState = nsIBlocklistService::STATE_NOT_BLOCKED;
 
     if (pluginTag) {
       seenBefore = true;
+      blocklistState = pluginTag->GetBlocklistState();
       // If plugin changed, delete cachedPluginTag and don't use cache
       if (fileModTime != pluginTag->mLastModifiedTime) {
         // Plugins has changed. Don't use cached plugin info.
         pluginTag = nullptr;
 
         // plugin file changed, flag this fact
         *aPluginsChanged = true;
       }
@@ -2211,18 +2213,17 @@ nsresult nsPluginHost::ScanPluginsDirect
         }
         mInvalidPlugins = invalidTag;
 
         // Mark aPluginsChanged so pluginreg is rewritten
         *aPluginsChanged = true;
         continue;
       }
 
-      uint32_t state = nsIBlocklistService::STATE_NOT_BLOCKED;
-      pluginTag = new nsPluginTag(&info, fileModTime, fromExtension, state);
+      pluginTag = new nsPluginTag(&info, fileModTime, fromExtension, blocklistState);
       pluginTag->mLibrary = library;
       pluginFile.FreePluginInfo(info);
       // Pass whether we've seen this plugin before. If the plugin is
       // softblocked and new (not seen before), it will be disabled.
       UpdatePluginBlocklistState(pluginTag, !seenBefore);
 
       // Plugin unloading is tag-based. If we created a new tag and loaded
       // the library in the process then we want to attempt to unload it here.
--- a/mobile/android/installer/package-manifest.in
+++ b/mobile/android/installer/package-manifest.in
@@ -128,17 +128,16 @@
 @BINPATH@/components/TooltipTextProvider.manifest
 @BINPATH@/components/NetworkGeolocationProvider.manifest
 @BINPATH@/components/NetworkGeolocationProvider.js
 @BINPATH@/components/EditorUtils.manifest
 @BINPATH@/components/EditorUtils.js
 
 @BINPATH@/components/extensions.manifest
 @BINPATH@/components/addonManager.js
-@BINPATH@/components/nsBlocklistService.js
 
 #ifndef MOZ_GECKOVIEW_JAR
 @BINPATH@/components/utils.manifest
 @BINPATH@/components/simpleServices.js
 @BINPATH@/components/amContentHandler.js
 @BINPATH@/components/amWebAPI.js
 @BINPATH@/components/amInstallTrigger.js
 #ifndef RELEASE_OR_BETA
rename from toolkit/mozapps/extensions/nsBlocklistService.js
rename to toolkit/mozapps/extensions/Blocklist.jsm
--- a/toolkit/mozapps/extensions/nsBlocklistService.js
+++ b/toolkit/mozapps/extensions/Blocklist.jsm
@@ -3,45 +3,44 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 "use strict";
 
 /* eslint "valid-jsdoc": [2, {requireReturn: false}] */
 
+var EXPORTED_SYMBOLS = ["Blocklist"];
+
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
 Cu.importGlobalProperties(["DOMParser"]);
 
-try {
-  // AddonManager.jsm doesn't allow itself to be imported in the child
-  // process. We're used in the child process (for now), so guard against
-  // this.
-  ChromeUtils.import("resource://gre/modules/AddonManager.jsm");
-  /* globals AddonManagerPrivate*/
-} catch (e) {
-}
-
+ChromeUtils.defineModuleGetter(this, "AddonManager",
+                               "resource://gre/modules/AddonManager.jsm");
+ChromeUtils.defineModuleGetter(this, "AddonManagerPrivate",
+                               "resource://gre/modules/AddonManager.jsm");
 ChromeUtils.defineModuleGetter(this, "CertUtils",
                                "resource://gre/modules/CertUtils.jsm");
 ChromeUtils.defineModuleGetter(this, "FileUtils",
                                "resource://gre/modules/FileUtils.jsm");
 ChromeUtils.defineModuleGetter(this, "UpdateUtils",
                                "resource://gre/modules/UpdateUtils.jsm");
 ChromeUtils.defineModuleGetter(this, "OS",
                                "resource://gre/modules/osfile.jsm");
 ChromeUtils.defineModuleGetter(this, "ServiceRequest",
                                "resource://gre/modules/ServiceRequest.jsm");
 
 // The remote settings updater is the new system in charge of fetching remote data
 // securely and efficiently. It will replace the current XML-based system.
 // See Bug 1257565 and Bug 1252456.
-const BlocklistClients = ChromeUtils.import("resource://services-common/blocklist-clients.js", {});
+const BlocklistClients = {};
+ChromeUtils.defineModuleGetter(BlocklistClients, "initialize",
+                               "resource://services-common/blocklist-clients.js");
 XPCOMUtils.defineLazyGetter(this, "RemoteSettings", function() {
   // Instantiate blocklist clients.
   BlocklistClients.initialize();
   // Import RemoteSettings for ``pollChanges()``
   const { RemoteSettings } = ChromeUtils.import("resource://services-common/remote-settings.js", {});
   return RemoteSettings;
 });
 
@@ -248,29 +247,36 @@ function parseRegExp(aStr) {
 }
 
 /**
  * Manages the Blocklist. The Blocklist is a representation of the contents of
  * blocklist.xml and allows us to remotely disable / re-enable blocklisted
  * items managed by the Extension Manager with an item's appDisabled property.
  * It also blocklists plugins with data from blocklist.xml.
  */
+var Blocklist = {
+  _init() {
+    Services.obs.addObserver(this, "xpcom-shutdown");
 
-function Blocklist() {
-  Services.obs.addObserver(this, "xpcom-shutdown");
-  gLoggingEnabled = Services.prefs.getBoolPref(PREF_EM_LOGGING_ENABLED, false);
-  gBlocklistEnabled = Services.prefs.getBoolPref(PREF_BLOCKLIST_ENABLED, true);
-  gBlocklistLevel = Math.min(Services.prefs.getIntPref(PREF_BLOCKLIST_LEVEL, DEFAULT_LEVEL),
-                             MAX_BLOCK_LEVEL);
-  Services.prefs.addObserver("extensions.blocklist.", this);
-  Services.prefs.addObserver(PREF_EM_LOGGING_ENABLED, this);
-  this.wrappedJSObject = this;
-}
+    gLoggingEnabled = Services.prefs.getBoolPref(PREF_EM_LOGGING_ENABLED, false);
+    gBlocklistEnabled = Services.prefs.getBoolPref(PREF_BLOCKLIST_ENABLED, true);
+    gBlocklistLevel = Math.min(Services.prefs.getIntPref(PREF_BLOCKLIST_LEVEL, DEFAULT_LEVEL),
+                               MAX_BLOCK_LEVEL);
+    Services.prefs.addObserver("extensions.blocklist.", this);
+    Services.prefs.addObserver(PREF_EM_LOGGING_ENABLED, this);
 
-Blocklist.prototype = {
+    // If the stub blocklist service deferred any queries because we
+    // weren't loaded yet, execute them now.
+    for (let entry of Services.blocklist.pluginQueries.splice(0)) {
+      entry.resolve(this.getPluginBlocklistState(entry.plugin,
+                                                 entry.appVersion,
+                                                 entry.toolkitVersion));
+    }
+  },
+
   STATE_NOT_BLOCKED: Ci.nsIBlocklistService.STATE_NOT_BLOCKED,
   STATE_SOFTBLOCKED: Ci.nsIBlocklistService.STATE_SOFTBLOCKED,
   STATE_BLOCKED: Ci.nsIBlocklistService.STATE_BLOCKED,
   STATE_OUTDATED: Ci.nsIBlocklistService.STATE_OUTDATED,
   STATE_VULNERABLE_UPDATE_AVAILABLE: Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE,
   STATE_VULNERABLE_NO_UPDATE: Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE,
 
 
@@ -1488,21 +1494,16 @@ Blocklist.prototype = {
       }
     }
 
     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]),
 };
 
 /*
  * Helper for constructing a blocklist.
  */
 function BlocklistItemData(versionRangeElement) {
   var versionRange = this.getBlocklistVersionRange(versionRangeElement);
   this.minVersion = versionRange.minVersion;
@@ -1666,9 +1667,9 @@ BlocklistItemData.prototype = {
       minVersion = versionRangeElement.getAttribute("minVersion");
     if (versionRangeElement.hasAttribute("maxVersion"))
       maxVersion = versionRangeElement.getAttribute("maxVersion");
 
     return { minVersion, maxVersion };
   }
 };
 
-this.NSGetFactory = XPCOMUtils.generateNSGetFactory([Blocklist]);
+Blocklist._init();
--- a/toolkit/mozapps/extensions/addonManager.js
+++ b/toolkit/mozapps/extensions/addonManager.js
@@ -5,16 +5,19 @@
 /**
  * This component serves as integration between the platform and AddonManager.
  * It is responsible for initializing and shutting down the AddonManager as well
  * as passing new installs from webpages to the AddonManager.
  */
 
 "use strict";
 
+ChromeUtils.defineModuleGetter(this, "AppConstants",
+                               "resource://gre/modules/AppConstants.jsm");
+
 // The old XPInstall error codes
 const EXECUTION_ERROR   = -203;
 const CANT_READ_ARCHIVE = -207;
 const USER_CANCELLED    = -210;
 const DOWNLOAD_ERROR    = -228;
 const UNSUPPORTED_TYPE  = -244;
 const SUCCESS           = 0;
 
@@ -252,9 +255,59 @@ amManager.prototype = {
       return gSingleton.QueryInterface(aIid);
     }
   },
   QueryInterface: XPCOMUtils.generateQI([Ci.amIAddonManager,
                                          Ci.nsITimerCallback,
                                          Ci.nsIObserver])
 };
 
-this.NSGetFactory = XPCOMUtils.generateNSGetFactory([amManager]);
+const BLOCKLIST_JSM = "resource://gre/modules/Blocklist.jsm";
+ChromeUtils.defineModuleGetter(this, "Blocklist", BLOCKLIST_JSM);
+
+function BlocklistService() {
+  this.wrappedJSObject = this;
+  this.pluginQueries = [];
+}
+
+BlocklistService.prototype = {
+  STATE_NOT_BLOCKED: Ci.nsIBlocklistService.STATE_NOT_BLOCKED,
+  STATE_SOFTBLOCKED: Ci.nsIBlocklistService.STATE_SOFTBLOCKED,
+  STATE_BLOCKED: Ci.nsIBlocklistService.STATE_BLOCKED,
+  STATE_OUTDATED: Ci.nsIBlocklistService.STATE_OUTDATED,
+  STATE_VULNERABLE_UPDATE_AVAILABLE: Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE,
+  STATE_VULNERABLE_NO_UPDATE: Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE,
+
+  get isLoaded() {
+    return Cu.isModuleLoaded(BLOCKLIST_JSM) && Blocklist.isLoaded;
+  },
+
+  async getPluginBlocklistState(plugin, appVersion, toolkitVersion) {
+    if (AppConstants.platform == "android") {
+      return Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
+    }
+    if (Cu.isModuleLoaded(BLOCKLIST_JSM)) {
+      return Blocklist.getPluginBlocklistState(plugin, appVersion, toolkitVersion);
+    }
+
+    // Blocklist module isn't loaded yet. Queue the query until it is.
+    let request = {plugin, appVersion, toolkitVersion};
+    let promise = new Promise(resolve => { request.resolve = resolve; });
+
+    this.pluginQueries.push(request);
+    return promise;
+  },
+
+  observe(...args) {
+    return Blocklist.observe(...args);
+  },
+
+  notify() {
+    Blocklist.notify();
+  },
+
+  classID: Components.ID("{66354bc9-7ed1-4692-ae1d-8da97d6b205e}"),
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
+                                         Ci.nsIBlocklistService,
+                                         Ci.nsITimerCallback]),
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([amManager, BlocklistService]);
--- a/toolkit/mozapps/extensions/extensions.manifest
+++ b/toolkit/mozapps/extensions/extensions.manifest
@@ -1,9 +1,9 @@
-component {66354bc9-7ed1-4692-ae1d-8da97d6b205e} nsBlocklistService.js process=main
+component {66354bc9-7ed1-4692-ae1d-8da97d6b205e} addonManager.js process=main
 contract @mozilla.org/extensions/blocklist;1 {66354bc9-7ed1-4692-ae1d-8da97d6b205e} process=main
 #ifndef MOZ_BUILD_APP_IS_BROWSER
 category profile-after-change nsBlocklistService @mozilla.org/extensions/blocklist;1 process=main
 #endif
 
 category update-timer nsBlocklistService @mozilla.org/extensions/blocklist;1,getService,blocklist-background-update-timer,extensions.blocklist.interval,86400
 component {4399533d-08d1-458c-a87a-235f74451cfa} addonManager.js
 contract @mozilla.org/addons/integration;1 {4399533d-08d1-458c-a87a-235f74451cfa}
--- a/toolkit/mozapps/extensions/internal/AddonTestUtils.jsm
+++ b/toolkit/mozapps/extensions/internal/AddonTestUtils.jsm
@@ -112,29 +112,33 @@ class MockBlocklist {
     this.wrappedJSObject = this;
 
     // Copy blocklist constants.
     for (let [k, v] of Object.entries(Ci.nsIBlocklistService)) {
       if (typeof v === "number") {
         this[k] = v;
       }
     }
+
+    this._xpidb = ChromeUtils.import("resource://gre/modules/addons/XPIDatabase.jsm", null);
   }
 
   get contractID() {
     return "@mozilla.org/extensions/blocklist;1";
   }
 
   _reLazifyService() {
     XPCOMUtils.defineLazyServiceGetter(Services, "blocklist", this.contractID);
+    ChromeUtils.defineModuleGetter(this._xpidb, "Blocklist", "resource://gre/modules/Blocklist.jsm");
   }
 
   register() {
     this.originalCID = MockRegistrar.register(this.contractID, this);
     this._reLazifyService();
+    this._xpidb.Blocklist = this;
   }
 
   unregister() {
     MockRegistrar.unregister(this.originalCID);
     this._reLazifyService();
   }
 
   async getAddonBlocklistState(addon, appVersion, toolkitVersion) {
--- a/toolkit/mozapps/extensions/internal/AddonUpdateChecker.jsm
+++ b/toolkit/mozapps/extensions/internal/AddonUpdateChecker.jsm
@@ -20,16 +20,18 @@ ChromeUtils.import("resource://gre/modul
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
 ChromeUtils.defineModuleGetter(this, "AddonManager",
                                "resource://gre/modules/AddonManager.jsm");
 ChromeUtils.defineModuleGetter(this, "AddonManagerPrivate",
                                "resource://gre/modules/AddonManager.jsm");
 ChromeUtils.defineModuleGetter(this, "AddonRepository",
                                "resource://gre/modules/addons/AddonRepository.jsm");
+ChromeUtils.defineModuleGetter(this, "Blocklist",
+                               "resource://gre/modules/Blocklist.jsm");
 ChromeUtils.defineModuleGetter(this, "CertUtils",
                                "resource://gre/modules/CertUtils.jsm");
 ChromeUtils.defineModuleGetter(this, "ServiceRequest",
                                "resource://gre/modules/ServiceRequest.jsm");
 ChromeUtils.defineModuleGetter(this, "UpdateRDFConverter",
                                "resource://gre/modules/addons/UpdateRDFConverter.jsm");
 
 ChromeUtils.import("resource://gre/modules/Log.jsm");
@@ -503,17 +505,17 @@ var AddonUpdateChecker = {
       aAppVersion = Services.appinfo.version;
     if (!aPlatformVersion)
       aPlatformVersion = Services.appinfo.platformVersion;
 
     let newest = null;
     for (let update of aUpdates) {
       if (!update.updateURL)
         continue;
-      let state = await Services.blocklist.getAddonBlocklistState(update, aAppVersion, aPlatformVersion);
+      let state = await Blocklist.getAddonBlocklistState(update, aAppVersion, aPlatformVersion);
       if (state != Ci.nsIBlocklistService.STATE_NOT_BLOCKED)
         continue;
       if ((newest == null || (Services.vc.compare(newest.version, update.version) < 0)) &&
           matchesVersions(update, aAppVersion, aPlatformVersion,
                           aIgnoreMaxVersion, aIgnoreStrictCompat,
                           aCompatOverrides)) {
         newest = update;
       }
--- a/toolkit/mozapps/extensions/internal/PluginProvider.jsm
+++ b/toolkit/mozapps/extensions/internal/PluginProvider.jsm
@@ -7,16 +7,19 @@
 /* exported logger */
 
 var EXPORTED_SYMBOLS = [];
 
 ChromeUtils.import("resource://gre/modules/AddonManager.jsm");
 /* globals AddonManagerPrivate*/
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 
+ChromeUtils.defineModuleGetter(this, "Blocklist",
+                               "resource://gre/modules/Blocklist.jsm");
+
 const URI_EXTENSION_STRINGS  = "chrome://mozapps/locale/extensions/extensions.properties";
 const LIST_UPDATED_TOPIC     = "plugins-list-updated";
 const FLASH_MIME_TYPE        = "application/x-shockwave-flash";
 
 ChromeUtils.import("resource://gre/modules/Log.jsm");
 const LOGGER_ID = "addons.plugins";
 
 // Create a new logger for use by the Addons Plugin Provider
@@ -362,17 +365,17 @@ PluginWrapper.prototype = {
 
   get blocklistState() {
     let { tags: [tag] } = pluginFor(this);
     return tag.blocklistState;
   },
 
   get blocklistURL() {
     let { tags: [tag] } = pluginFor(this);
-    return Services.blocklist.getPluginBlocklistURL(tag);
+    return Blocklist.getPluginBlocklistURL(tag);
   },
 
   get size() {
     function getDirectorySize(aFile) {
       let size = 0;
       let entries = aFile.directoryEntries.QueryInterface(Ci.nsIDirectoryEnumerator);
       let entry;
       while ((entry = entries.nextFile)) {
--- a/toolkit/mozapps/extensions/internal/XPIDatabase.jsm
+++ b/toolkit/mozapps/extensions/internal/XPIDatabase.jsm
@@ -24,16 +24,17 @@ XPCOMUtils.defineLazyModuleGetters(this,
   AddonRepository: "resource://gre/modules/addons/AddonRepository.jsm",
   AddonSettings: "resource://gre/modules/addons/AddonSettings.jsm",
   AppConstants: "resource://gre/modules/AppConstants.jsm",
   DeferredTask: "resource://gre/modules/DeferredTask.jsm",
   FileUtils: "resource://gre/modules/FileUtils.jsm",
   OS: "resource://gre/modules/osfile.jsm",
   Services: "resource://gre/modules/Services.jsm",
 
+  Blocklist: "resource://gre/modules/Blocklist.jsm",
   UpdateChecker: "resource://gre/modules/addons/XPIInstall.jsm",
   XPIInstall: "resource://gre/modules/addons/XPIInstall.jsm",
   XPIInternal: "resource://gre/modules/addons/XPIProvider.jsm",
 });
 
 const {nsIBlocklistService} = Ci;
 
 // These are injected from XPIProvider.jsm
@@ -446,17 +447,17 @@ class AddonInternal {
     if (staticItem) {
       let url = Services.urlFormatter.formatURLPref(PREF_BLOCKLIST_ITEM_URL);
       return {
         state: staticItem.level,
         url: url.replace(/%blockID%/g, staticItem.blockID)
       };
     }
 
-    return Services.blocklist.getAddonBlocklistEntry(this.wrapper);
+    return Blocklist.getAddonBlocklistEntry(this.wrapper);
   }
 
   async updateBlocklistState(options = {}) {
     let {applySoftBlock = true, oldAddon = null, updateDatabase = true} = options;
 
     if (oldAddon) {
       this.userDisabled = oldAddon.userDisabled;
       this.softDisabled = oldAddon.softDisabled;
--- a/toolkit/mozapps/extensions/moz.build
+++ b/toolkit/mozapps/extensions/moz.build
@@ -25,25 +25,25 @@ XPIDL_SOURCES += [
 
 XPIDL_MODULE = 'extensions'
 
 EXTRA_COMPONENTS += [
     'addonManager.js',
     'amContentHandler.js',
     'amInstallTrigger.js',
     'amWebAPI.js',
-    'nsBlocklistService.js',
 ]
 
 EXTRA_PP_COMPONENTS += [
     'extensions.manifest',
 ]
 
 EXTRA_JS_MODULES += [
     'AddonManager.jsm',
+    'Blocklist.jsm',
     'LightweightThemeManager.jsm',
 ]
 
 JAR_MANIFESTS += ['jar.mn']
 
 EXPORTS.mozilla += [
     'AddonContentPolicy.h',
     'AddonManagerStartup.h',
--- a/toolkit/mozapps/extensions/test/xpcshell/head_addons.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/head_addons.js
@@ -41,16 +41,18 @@ ChromeUtils.import("resource://gre/modul
 ChromeUtils.import("resource://gre/modules/NetUtil.jsm");
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 ChromeUtils.import("resource://gre/modules/addons/AddonRepository.jsm");
 ChromeUtils.import("resource://gre/modules/osfile.jsm");
 
 ChromeUtils.import("resource://testing-common/AddonTestUtils.jsm");
 
+ChromeUtils.defineModuleGetter(this, "Blocklist",
+                               "resource://gre/modules/Blocklist.jsm");
 ChromeUtils.defineModuleGetter(this, "Extension",
                                "resource://gre/modules/Extension.jsm");
 ChromeUtils.defineModuleGetter(this, "ExtensionTestUtils",
                                "resource://testing-common/ExtensionXPCShellUtils.jsm");
 ChromeUtils.defineModuleGetter(this, "ExtensionTestCommon",
                                "resource://testing-common/ExtensionTestCommon.jsm");
 ChromeUtils.defineModuleGetter(this, "HttpServer",
                                "resource://testing-common/httpd.js");
@@ -1641,17 +1643,17 @@ class MockPluginTag {
       ...opts,
     });
     this.pluginTag.enabledState = enabledState;
 
     this.name = opts.name;
     this.version = opts.version;
   }
   async isBlocklisted() {
-    let state = await Services.blocklist.getPluginBlocklistState(this.pluginTag);
+    let state = await Blocklist.getPluginBlocklistState(this.pluginTag);
     return state == Services.blocklist.STATE_BLOCKED;
   }
   get disabled() {
     return this.pluginTag.enabledState == Ci.nsIPluginTag.STATE_DISABLED;
   }
   set disabled(val) {
     this.enabledState = Ci.nsIPluginTag[val ? "STATE_DISABLED" : "STATE_ENABLED"];
   }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_asyncBlocklistLoad.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_asyncBlocklistLoad.js
@@ -1,15 +1,14 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
 add_task(async function() {
-  let blocklist = Cc["@mozilla.org/extensions/blocklist;1"].
-                  getService().wrappedJSObject;
+  let blocklist = Blocklist;
   let scope = ChromeUtils.import("resource://gre/modules/osfile.jsm", {});
 
   // sync -> async. Check that async code doesn't try to read the file
   // once it's already been read synchronously.
   let read = scope.OS.File.read;
   let triedToRead = false;
   scope.OS.File.read = () => triedToRead = true;
   blocklist._loadBlocklist();
--- a/toolkit/mozapps/extensions/test/xpcshell/test_blocklist_appversion.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_blocklist_appversion.js
@@ -237,17 +237,17 @@ var BlocklistPrompt = {
 };
 
 
 async function loadBlocklist(file) {
   let blocklistUpdated = TestUtils.topicObserved("blocklist-updated");
 
   Services.prefs.setCharPref("extensions.blocklist.url",
                              "http://example.com/data/" + file);
-  Services.blocklist.QueryInterface(Ci.nsITimerCallback).notify(null);
+  Blocklist.notify();
 
   await blocklistUpdated;
 }
 
 let factory = XPCOMUtils.generateSingletonFactory(function() { return BlocklistPrompt; });
 Cm.registerFactory(Components.ID("{26d32654-30c7-485d-b983-b4d2568aebba}"),
                    "Blocklist Prompt",
                    "@mozilla.org/addons/blocklist-prompt;1", factory);
--- a/toolkit/mozapps/extensions/test/xpcshell/test_blocklist_gfx.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_blocklist_gfx.js
@@ -14,26 +14,24 @@ const SAMPLE_GFX_RECORD = {
   "featureStatus": "BLOCKED_DRIVER_VERSION",
   "last_modified": 1458035931837,
   "os": "WINNT 6.1",
   "id": "3f947f16-37c2-4e96-d356-78b26363729b",
   "versionRange": {"minVersion": 0, "maxVersion": "*"}
 };
 
 
-function Blocklist() {
-  let blocklist = Cc["@mozilla.org/extensions/blocklist;1"].
-                  getService().wrappedJSObject;
-  blocklist._clear();
-  return blocklist;
+function getBlocklist() {
+  Blocklist._clear();
+  return Blocklist;
 }
 
 
 add_task(async function test_sends_serialized_data() {
-  const blocklist = Blocklist();
+  const blocklist = getBlocklist();
   blocklist._gfxEntries = [SAMPLE_GFX_RECORD];
 
   const expected = "blockID:g36\tdevices:0x0a6c,geforce\tdriverVersion:8.17.12.5896\t" +
                    "driverVersionComparator:LESS_THAN_OR_EQUAL\tfeature:DIRECT3D_9_LAYERS\t" +
                    "featureStatus:BLOCKED_DRIVER_VERSION\tos:WINNT 6.1\tvendor:0x10de\t" +
                    "versionRange:0,*";
   let received;
   const observe = (subject, topic, data) => { received = data; };
@@ -50,49 +48,49 @@ add_task(async function test_parsing_fai
   " <gfxBlacklistEntry>" +
   "   <devices>" +
   "     <device>0x2,582</device>" +
   "     <device>0x2782</device>" +
   "   </devices>" +
   " </gfxBlacklistEntry>" +
   "</gfxItems>" +
   "</blocklist>";
-  const blocklist = Blocklist();
+  const blocklist = getBlocklist();
   blocklist._loadBlocklistFromString(input);
   equal(blocklist._gfxEntries[0].devices.length, 1);
   equal(blocklist._gfxEntries[0].devices[0], "0x2782");
 });
 
 
 add_task(async function test_empty_values_are_ignored() {
   const input = "<blocklist xmlns=\"http://www.mozilla.org/2006/addons-blocklist\">" +
   "<gfxItems>" +
   " <gfxBlacklistEntry>" +
   "   <os></os>" +
   " </gfxBlacklistEntry>" +
   "</gfxItems>" +
   "</blocklist>";
-  const blocklist = Blocklist();
+  const blocklist = getBlocklist();
   let received;
   const observe = (subject, topic, data) => { received = data; };
   Services.obs.addObserver(observe, EVENT_NAME);
   blocklist._loadBlocklistFromString(input);
   ok(received.indexOf("os" < 0));
   Services.obs.removeObserver(observe, EVENT_NAME);
 });
 
 add_task(async function test_empty_devices_are_ignored() {
   const input = "<blocklist xmlns=\"http://www.mozilla.org/2006/addons-blocklist\">" +
   "<gfxItems>" +
   " <gfxBlacklistEntry>" +
   "   <devices></devices>" +
   " </gfxBlacklistEntry>" +
   "</gfxItems>" +
   "</blocklist>";
-  const blocklist = Blocklist();
+  const blocklist = getBlocklist();
   let received;
   const observe = (subject, topic, data) => { received = data; };
   Services.obs.addObserver(observe, EVENT_NAME);
   blocklist._loadBlocklistFromString(input);
   ok(received.indexOf("devices" < 0));
   Services.obs.removeObserver(observe, EVENT_NAME);
 });
 
@@ -111,17 +109,17 @@ add_task(async function test_version_ran
   " <gfxBlacklistEntry>" +
   "   <versionRange minVersion=\"  \"/>" +
   " </gfxBlacklistEntry>" +
   " <gfxBlacklistEntry>" +
   "   <versionRange/>" +
   " </gfxBlacklistEntry>" +
   "</gfxItems>" +
   "</blocklist>";
-  const blocklist = Blocklist();
+  const blocklist = getBlocklist();
   blocklist._loadBlocklistFromString(input);
   equal(blocklist._gfxEntries[0].versionRange.minVersion, "13.0b2");
   equal(blocklist._gfxEntries[0].versionRange.maxVersion, "42.0");
   equal(blocklist._gfxEntries[1].versionRange.minVersion, "0");
   equal(blocklist._gfxEntries[1].versionRange.maxVersion, "2.0");
   equal(blocklist._gfxEntries[2].versionRange.minVersion, "1.0");
   equal(blocklist._gfxEntries[2].versionRange.maxVersion, "*");
   equal(blocklist._gfxEntries[3].versionRange.minVersion, "0");
@@ -136,13 +134,13 @@ add_task(async function test_blockid_att
   " <gfxBlacklistEntry blockID=\"g60\">" +
   "   <vendor> 0x10de </vendor>" +
   " </gfxBlacklistEntry>" +
   " <gfxBlacklistEntry>" +
   "   <feature> DIRECT3D_9_LAYERS </feature>" +
   " </gfxBlacklistEntry>" +
   "</gfxItems>" +
   "</blocklist>";
-  const blocklist = Blocklist();
+  const blocklist = getBlocklist();
   blocklist._loadBlocklistFromString(input);
   equal(blocklist._gfxEntries[0].blockID, "g60");
   ok(!blocklist._gfxEntries[1].hasOwnProperty("blockID"));
 });
--- a/toolkit/mozapps/extensions/test/xpcshell/test_blocklist_osabi.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_blocklist_osabi.js
@@ -256,17 +256,17 @@ const ADDONS = {
 
 const ADDON_IDS = Object.keys(ADDONS);
 
 async function loadBlocklist(file) {
   let blocklistUpdated = TestUtils.topicObserved("blocklist-updated");
 
   Services.prefs.setCharPref("extensions.blocklist.url",
                              "http://example.com/data/" + file);
-  Services.blocklist.QueryInterface(Ci.nsITimerCallback).notify(null);
+  Blocklist.notify();
 
   return blocklistUpdated;
 }
 
 
 add_task(async function setup() {
   createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9");
   await promiseStartupManager();
@@ -282,17 +282,17 @@ add_task(async function setup() {
   }
 });
 
 add_task(async function test_1() {
   await loadBlocklist("test_bug393285.xml");
 
   let addons = await getAddons(ADDON_IDS);
   async function isBlocklisted(addon, appVer, toolkitVer) {
-    let state = await Services.blocklist.getAddonBlocklistState(addon, appVer, toolkitVer);
+    let state = await Blocklist.getAddonBlocklistState(addon, appVer, toolkitVer);
     return state != Services.blocklist.STATE_NOT_BLOCKED;
   }
   for (let [id, options] of Object.entries(ADDONS)) {
     for (let blocklisted of options.blocklisted || []) {
       ok(await isBlocklisted(addons.get(id), ...blocklisted),
          `Add-on ${id} should be blocklisted in app/platform version ${blocklisted}`);
     }
     for (let notBlocklisted of options.notBlocklisted || []) {
--- a/toolkit/mozapps/extensions/test/xpcshell/test_blocklist_plugin_flashonly.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_blocklist_plugin_flashonly.js
@@ -25,10 +25,10 @@ add_task(async function checkFlashOnlyPl
   var plugin = get_test_plugintag();
   if (!plugin)
     do_throw("Plugin tag not found");
 
   // run the code after the blocklist is closed
   Services.obs.notifyObservers(null, "addon-blocklist-closed");
   await new Promise(executeSoon);
   // should be marked as outdated by the blocklist
-  Assert.equal(await Services.blocklist.getPluginBlocklistState(plugin, "1", "1.9"), nsIBLS.STATE_OUTDATED);
+  Assert.equal(await Blocklist.getPluginBlocklistState(plugin, "1", "1.9"), nsIBLS.STATE_OUTDATED);
 });
--- a/toolkit/mozapps/extensions/test/xpcshell/test_blocklist_plugin_outdated.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_blocklist_plugin_outdated.js
@@ -51,17 +51,17 @@ var BlocklistPrompt = {
 };
 
 
 async function loadBlocklist(file) {
   let blocklistUpdated = TestUtils.topicObserved("blocklist-updated");
 
   Services.prefs.setCharPref("extensions.blocklist.url",
                              "http://example.com/data/" + file);
-  Services.blocklist.QueryInterface(Ci.nsITimerCallback).notify(null);
+  Blocklist.notify();
 
   await blocklistUpdated;
 }
 
 let factory = XPCOMUtils.generateSingletonFactory(function() { return BlocklistPrompt; });
 Cm.registerFactory(Components.ID("{26d32654-30c7-485d-b983-b4d2568aebba}"),
                    "Blocklist Prompt",
                    "@mozilla.org/addons/blocklist-prompt;1", factory);
@@ -71,16 +71,22 @@ add_task(async function setup() {
 
   // initialize the blocklist with no entries
   copyBlocklistToProfile(do_get_file("data/test_bug514327_3_empty.xml"));
 
   await promiseStartupManager();
 
   gBlocklist = Services.blocklist;
 
+  // The blocklist service defers plugin request until the Blocklist
+  // module loads. Make sure it loads, or we'll wait forever.
+  executeSoon(() => {
+    void Blocklist;
+  });
+
   // should NOT be marked as outdated by the blocklist
   Assert.equal(await gBlocklist.getPluginBlocklistState(PLUGINS[0], "1", "1.9"), nsIBLS.STATE_NOT_BLOCKED);
 });
 
 add_task(async function test_part_1() {
   // update blocklist with data that marks the plugin as outdated
   await loadBlocklist("test_bug514327_3_outdated_1.xml");
 
--- a/toolkit/mozapps/extensions/test/xpcshell/test_blocklist_plugin_regexp.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_blocklist_plugin_regexp.js
@@ -35,16 +35,22 @@ var PLUGINS = [{
 add_task(async function checkBlocklistForRegexes() {
   createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9");
 
   // We cannot force the blocklist to update so just copy our test list to the profile
   copyBlocklistToProfile(do_get_file("data/test_bug468528.xml"));
 
   var {blocklist} = Services;
 
+  // The blocklist service defers plugin request until the Blocklist
+  // module loads. Make sure it loads, or we'll wait forever.
+  executeSoon(() => {
+    void Blocklist;
+  });
+
   // blocked (sanity check)
   Assert.equal(await blocklist.getPluginBlocklistState(PLUGINS[0], "1", "1.9"), blocklist.STATE_BLOCKED);
 
   // not blocked - won't match due to invalid regexp
   Assert.equal(await blocklist.getPluginBlocklistState(PLUGINS[1], "1", "1.9"), blocklist.STATE_NOT_BLOCKED);
 
   // blocked - the invalid regexp for the previous item shouldn't affect this one
   Assert.equal(await blocklist.getPluginBlocklistState(PLUGINS[2], "1", "1.9"), blocklist.STATE_BLOCKED);
--- a/toolkit/mozapps/extensions/test/xpcshell/test_blocklist_plugin_severities.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_blocklist_plugin_severities.js
@@ -35,16 +35,22 @@ var PLUGINS = [{
 
 add_task(async function checkBlocklistSeverities() {
   createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9");
 
   copyBlocklistToProfile(do_get_file("data/test_bug514327_1.xml"));
 
   var {blocklist} = Services;
 
+  // The blocklist service defers plugin request until the Blocklist
+  // module loads. Make sure it loads, or we'll wait forever.
+  executeSoon(() => {
+    void Blocklist;
+  });
+
   // blocked (sanity check)
   Assert.equal(await blocklist.getPluginBlocklistState(PLUGINS[0], "1", "1.9"), blocklist.STATE_BLOCKED);
 
   // outdated
   Assert.equal(await blocklist.getPluginBlocklistState(PLUGINS[1], "1", "1.9"), blocklist.STATE_OUTDATED);
 
   // outdated
   Assert.equal(await blocklist.getPluginBlocklistState(PLUGINS[2], "1", "1.9"), blocklist.STATE_OUTDATED);
--- a/toolkit/mozapps/extensions/test/xpcshell/test_blocklist_severities.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_blocklist_severities.js
@@ -10,17 +10,17 @@ 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%");
 
 async function getAddonBlocklistURL(addon) {
-  let entry = await Services.blocklist.getAddonBlocklistEntry(addon);
+  let entry = await 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",
@@ -135,23 +135,23 @@ function createAddon(addon) {
 
 async function loadBlocklist(file, callback) {
   let blocklistUpdated = TestUtils.topicObserved("blocklist-updated");
 
   gNotificationCheck = callback;
 
   Services.prefs.setCharPref("extensions.blocklist.url",
                              "http://example.com/data/" + file);
-  Services.blocklist.QueryInterface(Ci.nsITimerCallback).notify(null);
+  Blocklist.notify();
 
   await blocklistUpdated;
 }
 
 async function check_plugin_state(plugin) {
-  let blocklistState = await Services.blocklist.getPluginBlocklistState(plugin);
+  let blocklistState = await Blocklist.getPluginBlocklistState(plugin);
   return `${plugin.disabled},${blocklistState == Services.blocklist.STATE_BLOCKED}`;
 }
 
 function create_blocklistURL(blockID) {
   let url = Services.urlFormatter.formatURLPref(PREF_BLOCKLIST_ITEM_URL);
   url = url.replace(/%blockID%/g, blockID);
   return url;
 }
@@ -348,21 +348,21 @@ add_task(async function test_pt3() {
   // Check blockIDs are correct
   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"));
+  equal(Blocklist.getPluginBlocklistURL(PLUGINS[0]), create_blocklistURL("test_bug455906_plugin"));
+  equal(Blocklist.getPluginBlocklistURL(PLUGINS[1]), create_blocklistURL("test_bug455906_plugin"));
+  equal(Blocklist.getPluginBlocklistURL(PLUGINS[2]), create_blocklistURL("test_bug455906_plugin"));
+  equal(Blocklist.getPluginBlocklistURL(PLUGINS[3]), create_blocklistURL("test_bug455906_plugin"));
+  equal(Blocklist.getPluginBlocklistURL(PLUGINS[4]), create_blocklistURL("test_bug455906_plugin"));
 
   // Shouldn't be changed
   checkAddonState(addons[5], {userDisabled: false, softDisabled: false, appDisabled: true});
   checkAddonState(addons[6], {userDisabled: false, softDisabled: false, appDisabled: true});
   equal(await check_plugin_state(PLUGINS[5]), "false,true");
 
   // Back to starting state
   await loadBlocklist("bug455906_start.xml");
--- a/toolkit/mozapps/extensions/test/xpcshell/test_blocklist_url_parameters.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_blocklist_url_parameters.js
@@ -11,17 +11,17 @@ const PREF_APP_UPDATE_CHANNEL         = 
 
 Cu.importGlobalProperties(["URLSearchParams"]);
 
 // Get the HTTP server.
 var testserver = AddonTestUtils.createHttpServer({hosts: ["example.com"]});
 
 async function updateBlocklist(file) {
   let blocklistUpdated = TestUtils.topicObserved("blocklist-updated");
-  Services.blocklist.QueryInterface(Ci.nsITimerCallback).notify(null);
+  Blocklist.notify();
   return blocklistUpdated;
 }
 
 add_task(async function setup() {
   createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9");
 
   await promiseStartupManager();
 });
--- a/toolkit/mozapps/extensions/test/xpcshell/test_blocklist_url_ping_count.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_blocklist_url_ping_count.js
@@ -17,17 +17,17 @@ gTestserver.registerPathHandler("/", fun
   resolveQuery(metadata.queryString);
 });
 
 async function checkQuery(expected) {
   let promise = new Promise(resolve => {
     resolveQuery = resolve;
   });
 
-  Services.blocklist.QueryInterface(Ci.nsITimerCallback).notify(null);
+  Blocklist.notify();
 
   equal(await promise, expected, "Got expected blocklist query string");
 }
 
 add_task(async function setup() {
   createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1");
 
   gTestserver = new HttpServer();
--- a/toolkit/mozapps/extensions/test/xpcshell/test_overrideblocklist.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_overrideblocklist.js
@@ -80,17 +80,17 @@ function run_test() {
       appBlocklist.moveTo(gAppDir, FILE_BLOCKLIST);
     });
   }
 
   run_next_test();
 }
 
 async function isBlocklisted(addon, appVer, toolkitVer) {
-  let state = await Services.blocklist.getAddonBlocklistState(addon, appVer, toolkitVer);
+  let state = await Blocklist.getAddonBlocklistState(addon, appVer, toolkitVer);
   return state != Services.blocklist.STATE_NOT_BLOCKED;
 }
 
 // On first run whataver is in the app dir should get copied to the profile
 add_test(async function test_copy() {
   clearBlocklists();
   copyToApp(OLD);
 
--- a/toolkit/mozapps/extensions/test/xpcshell/test_pluginBlocklistCtp.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_pluginBlocklistCtp.js
@@ -111,51 +111,51 @@ function get_test_plugin() {
   Assert.ok(false);
   return null;
 }
 
 // At this time, the blocklist does not have an entry for the test plugin,
 // so it shouldn't be click-to-play.
 add_task(async function test_is_not_clicktoplay() {
   var plugin = get_test_plugin();
-  var blocklistState = await Services.blocklist.getPluginBlocklistState(plugin, "1", "1.9");
+  var blocklistState = await Blocklist.getPluginBlocklistState(plugin, "1", "1.9");
   Assert.notEqual(blocklistState, Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE);
   Assert.notEqual(blocklistState, Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE);
 });
 
 // Here, we've updated the blocklist to have a block for the test plugin,
 // so it should be click-to-play.
 add_task(async function test_is_clicktoplay() {
   await updateBlocklist("http://example.com/data/test_pluginBlocklistCtpUndo.xml");
   var plugin = get_test_plugin();
-  var blocklistState = await Services.blocklist.getPluginBlocklistState(plugin, "1", "1.9");
+  var blocklistState = await Blocklist.getPluginBlocklistState(plugin, "1", "1.9");
   Assert.equal(blocklistState, Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE);
 });
 
 // But now we've removed that entry from the blocklist (really we've gone back
 // to the old one), so the plugin shouldn't be click-to-play any more.
 add_task(async function test_is_not_clicktoplay2() {
   await updateBlocklist("http://example.com/data/test_pluginBlocklistCtp.xml");
   var plugin = get_test_plugin();
-  var blocklistState = await Services.blocklist.getPluginBlocklistState(plugin, "1", "1.9");
+  var blocklistState = await Blocklist.getPluginBlocklistState(plugin, "1", "1.9");
   Assert.notEqual(blocklistState, Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE);
   Assert.notEqual(blocklistState, Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE);
 
 });
 
 // Test that disabling the blocklist when a plugin is ctp-blocklisted will
 // result in the plugin not being click-to-play.
 add_task(async function test_disable_blocklist() {
   await updateBlocklist("http://example.com/data/test_pluginBlocklistCtpUndo.xml");
   var plugin = get_test_plugin();
-  var blocklistState = await Services.blocklist.getPluginBlocklistState(plugin, "1", "1.9");
+  var blocklistState = await Blocklist.getPluginBlocklistState(plugin, "1", "1.9");
   Assert.equal(blocklistState, Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE);
 
   Services.prefs.setBoolPref("extensions.blocklist.enabled", false);
-  blocklistState = await Services.blocklist.getPluginBlocklistState(plugin, "1", "1.9");
+  blocklistState = await Blocklist.getPluginBlocklistState(plugin, "1", "1.9");
   Assert.notEqual(blocklistState, Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE);
   Assert.notEqual(blocklistState, Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE);
 
   // it should still be possible to make a plugin click-to-play via the pref
   // and setting that plugin's enabled state to click-to-play
   Services.prefs.setBoolPref("plugins.click_to_play", true);
   let previousEnabledState = plugin.enabledState;
   plugin.enabledState = Ci.nsIPluginTag.STATE_CLICKTOPLAY;
--- a/toolkit/mozapps/extensions/test/xpcshell/test_pluginInfoURL.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_pluginInfoURL.js
@@ -46,39 +46,39 @@ function run_test() {
  * Test that the blocklist service correctly loads and returns the infoURL for
  * a plugin that matches the first entry in the blocklist.
  */
 add_task(async function test_infoURL() {
   // The testInfoURL must match the value within the
   // <infoURL> tag in pluginInfoURL_block.xml.
   let testInfoURL = "http://test.url.com/";
 
-  Assert.strictEqual(Services.blocklist.getPluginInfoURL(PLUGINS[0]),
+  Assert.strictEqual(Blocklist.getPluginInfoURL(PLUGINS[0]),
     testInfoURL, "Should be the provided url when an infoURL tag is available");
 });
 
 /**
  * Test that the blocklist service correctly loads and returns the infoURL for
  * a plugin that partially matches an earlier entry in the blocklist.
  */
 add_task(async function test_altInfoURL() {
   let altTestInfoURL = "http://alt.test.url.com/";
 
-  Assert.strictEqual(Services.blocklist.getPluginInfoURL(PLUGINS[1]),
+  Assert.strictEqual(Blocklist.getPluginInfoURL(PLUGINS[1]),
     altTestInfoURL, "Should be the alternative infoURL");
 });
 
 /**
  * Test that the blocklist service correctly returns null
  * if the infoURL tag is missing in the blocklist.xml file.
  */
 add_task(async function test_infoURL_missing() {
-  Assert.strictEqual(Services.blocklist.getPluginInfoURL(PLUGINS[2]), null,
+  Assert.strictEqual(Blocklist.getPluginInfoURL(PLUGINS[2]), null,
     "Should be null when no infoURL tag is available.");
 });
 
 add_task(async function test_intoURL_newVersion() {
   let testInfoURL = "http://test.url2.com/";
-  Assert.strictEqual(Services.blocklist.getPluginInfoURL(PLUGINS[3]),
+  Assert.strictEqual(Blocklist.getPluginInfoURL(PLUGINS[3]),
     testInfoURL, "Old plugin should match");
-  Assert.strictEqual(Services.blocklist.getPluginInfoURL(PLUGINS[4]),
+  Assert.strictEqual(Blocklist.getPluginInfoURL(PLUGINS[4]),
     null, "New plugin should not match");
 });
--- a/toolkit/mozapps/extensions/test/xpcshell/test_update.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_update.js
@@ -614,18 +614,16 @@ add_task(async function test_8() {
   let blocklistAddons = new Map();
   for (let [id, options] of Object.entries(PARAM_ADDONS)) {
     if (options.blocklistState) {
       blocklistAddons.set(id, Ci.nsIBlocklistService[options.blocklistState]);
     }
   }
   let mockBlocklist = await AddonTestUtils.overrideBlocklist(blocklistAddons);
 
-  await promiseRestartManager();
-
   for (let [id, options] of Object.entries(PARAM_ADDONS)) {
     await promiseInstallXPI(options["install.rdf"], profileDir);
 
     if (options.initialState) {
       let addon = await AddonManager.getAddonByID(id);
       Object.assign(addon, options.initialState);
     }
   }