Bug 1257565 - wip - move plugin and addon stuff to kinto draft
authorGijs Kruitbosch <gijskruitbosch@gmail.com>
Wed, 16 May 2018 23:40:09 +0100
changeset 812460 bc2d34a71f8dab7c267b322555e5c0558b7f3963
parent 812459 56a0084646d98f1872258498192a8742924460e2
push id114555
push userbmo:gijskruitbosch+bugs@gmail.com
push dateFri, 29 Jun 2018 11:55:35 +0000
bugs1257565
milestone63.0a1
Bug 1257565 - wip - move plugin and addon stuff to kinto MozReview-Commit-ID: AiGycyhGUta
browser/components/nsBrowserGlue.js
dom/plugins/base/nsPluginHost.cpp
services/common/blocklist-clients.js
toolkit/components/telemetry/TelemetryEnvironment.jsm
toolkit/mozapps/extensions/AddonManager.jsm
toolkit/mozapps/extensions/Blocklist.jsm
toolkit/mozapps/extensions/internal/AddonTestUtils.jsm
toolkit/mozapps/extensions/test/browser/browser_CTP_plugins.js
toolkit/mozapps/extensions/test/xpcshell/data/test_bug449027_app-extensions.json
toolkit/mozapps/extensions/test/xpcshell/data/test_bug449027_app-plugins.json
toolkit/mozapps/extensions/test/xpcshell/data/test_bug449027_app.xml
toolkit/mozapps/extensions/test/xpcshell/data/test_bug449027_toolkit-extensions.json
toolkit/mozapps/extensions/test/xpcshell/data/test_bug449027_toolkit-plugins.json
toolkit/mozapps/extensions/test/xpcshell/data/test_bug449027_toolkit.xml
toolkit/mozapps/extensions/test/xpcshell/data/test_pluginBlocklistCtp-plugins.json
toolkit/mozapps/extensions/test/xpcshell/data/test_pluginBlocklistCtpUndo-plugins.json
toolkit/mozapps/extensions/test/xpcshell/test_blocklist_appversion.js
toolkit/mozapps/extensions/test/xpcshell/test_blocklist_metadata_filters.js
toolkit/mozapps/extensions/test/xpcshell/test_blocklist_plugin_outdated.js
toolkit/mozapps/extensions/test/xpcshell/test_blocklist_prefs.js
toolkit/mozapps/extensions/test/xpcshell/test_blocklist_regexp.js
toolkit/mozapps/extensions/test/xpcshell/test_blocklist_severities.js
toolkit/mozapps/extensions/test/xpcshell/test_blocklistchange.js
toolkit/mozapps/extensions/test/xpcshell/test_pluginBlocklistCtp.js
toolkit/mozapps/extensions/test/xpcshell/test_softblocked.js
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -1271,19 +1271,16 @@ BrowserGlue.prototype = {
       });
     }
 
     Services.tm.idleDispatchToMainThread(() => {
       LanguagePrompt.init();
     });
 
     ChromeUtils.idleDispatch(() => {
-      Blocklist.loadBlocklistAsync();
-    });
-    ChromeUtils.idleDispatch(() => {
       Blocklist.checkForGfxBlocklistEntries();
     });
 
     Services.tm.idleDispatchToMainThread(() => {
       SavantShieldStudy.init();
     });
   },
 
--- a/dom/plugins/base/nsPluginHost.cpp
+++ b/dom/plugins/base/nsPluginHost.cpp
@@ -273,28 +273,36 @@ class BlocklistPromiseHandler final : pu
       // sentinel, setting it to nullptr when we run.
       if (!mTag) {
         return;
       }
       mTag = nullptr;
       sPendingBlocklistStateRequests--;
       // If this was the only remaining pending request, check if we need to write
       // state and if so update the child processes.
-      if (!sPendingBlocklistStateRequests &&
-          sPluginBlocklistStatesChangedSinceLastWrite) {
-        sPluginBlocklistStatesChangedSinceLastWrite = false;
-
-        RefPtr<nsPluginHost> host = nsPluginHost::GetInst();
-        // Write the changed list to disk:
-        host->WritePluginInfo();
-
-        // We update blocklist info in content processes asynchronously
-        // by just sending a new plugin list to content.
-        host->IncrementChromeEpoch();
-        host->SendPluginsToContent();
+      if (!sPendingBlocklistStateRequests) {
+        if (sPluginBlocklistStatesChangedSinceLastWrite) {
+          sPluginBlocklistStatesChangedSinceLastWrite = false;
+
+          RefPtr<nsPluginHost> host = nsPluginHost::GetInst();
+          // Write the changed list to disk:
+          host->WritePluginInfo();
+
+          // We update blocklist info in content processes asynchronously
+          // by just sending a new plugin list to content.
+          host->IncrementChromeEpoch();
+          host->SendPluginsToContent();
+        }
+
+        // Now notify observers that we're done updating plugin state.
+        nsCOMPtr<nsIObserverService> obsService =
+          mozilla::services::GetObserverService();
+        if (obsService) {
+          obsService->NotifyObservers(nullptr, "plugin-blocklist-updates-finished", nullptr);
+        }
       }
     }
 
     void
     ResolvedCallback(JSContext *aCx, JS::Handle<JS::Value> aValue) override
     {
       if (!aValue.isInt32()) {
         MOZ_ASSERT(false, "Blocklist should always return int32");
@@ -368,17 +376,17 @@ nsPluginHost::nsPluginHost()
 
   Preferences::AddStrongObserver(this, "plugin.disable");
 
   nsCOMPtr<nsIObserverService> obsService =
     mozilla::services::GetObserverService();
   if (obsService) {
     obsService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false);
     if (XRE_IsParentProcess()) {
-      obsService->AddObserver(this, "blocklist-updated", false);
+      obsService->AddObserver(this, "plugin-blocklist-updated", false);
     }
   }
 
 #ifdef PLUGIN_LOGGING
   MOZ_LOG(nsPluginLogging::gNPNLog, PLUGIN_LOG_ALWAYS,("NPN Logging Active!\n"));
   MOZ_LOG(nsPluginLogging::gPluginLog, PLUGIN_LOG_ALWAYS,("General Plugin Logging Active! (nsPluginHost::ctor)\n"));
   MOZ_LOG(nsPluginLogging::gNPPLog, PLUGIN_LOG_ALWAYS,("NPP Logging Active!\n"));
 
@@ -3524,17 +3532,17 @@ NS_IMETHODIMP nsPluginHost::Observe(nsIS
     mPluginsDisabled = Preferences::GetBool("plugin.disable", false);
     // Unload or load plugins as needed
     if (mPluginsDisabled) {
       UnloadPlugins();
     } else {
       LoadPlugins();
     }
   }
-  if (XRE_IsParentProcess() && !strcmp("blocklist-updated", aTopic)) {
+  if (XRE_IsParentProcess() && !strcmp("plugin-blocklist-updated", aTopic)) {
     // The blocklist has updated. Asynchronously get blocklist state for all items.
     // The promise resolution handler takes care of checking if anything changed,
     // and writing an updated state to file, as well as sending data to child processes.
     nsPluginTag* plugin = mPlugins;
     while (plugin) {
       UpdatePluginBlocklistState(plugin);
       plugin = plugin->mNext;
     }
--- a/services/common/blocklist-clients.js
+++ b/services/common/blocklist-clients.js
@@ -13,22 +13,16 @@ const { OS } = ChromeUtils.import("resou
 
 ChromeUtils.defineModuleGetter(this, "RemoteSettings", "resource://services-settings/remote-settings.js");
 ChromeUtils.defineModuleGetter(this, "jexlFilterFunc", "resource://services-settings/remote-settings.js");
 
 const PREF_BLOCKLIST_BUCKET                  = "services.blocklist.bucket";
 const PREF_BLOCKLIST_ONECRL_COLLECTION       = "services.blocklist.onecrl.collection";
 const PREF_BLOCKLIST_ONECRL_CHECKED_SECONDS  = "services.blocklist.onecrl.checked";
 const PREF_BLOCKLIST_ONECRL_SIGNER           = "services.blocklist.onecrl.signer";
-const PREF_BLOCKLIST_ADDONS_COLLECTION       = "services.blocklist.addons.collection";
-const PREF_BLOCKLIST_ADDONS_CHECKED_SECONDS  = "services.blocklist.addons.checked";
-const PREF_BLOCKLIST_ADDONS_SIGNER           = "services.blocklist.addons.signer";
-const PREF_BLOCKLIST_PLUGINS_COLLECTION      = "services.blocklist.plugins.collection";
-const PREF_BLOCKLIST_PLUGINS_CHECKED_SECONDS = "services.blocklist.plugins.checked";
-const PREF_BLOCKLIST_PLUGINS_SIGNER          = "services.blocklist.plugins.signer";
 const PREF_BLOCKLIST_PINNING_ENABLED         = "services.blocklist.pinning.enabled";
 const PREF_BLOCKLIST_PINNING_BUCKET          = "services.blocklist.pinning.bucket";
 const PREF_BLOCKLIST_PINNING_COLLECTION      = "services.blocklist.pinning.collection";
 const PREF_BLOCKLIST_PINNING_CHECKED_SECONDS = "services.blocklist.pinning.checked";
 const PREF_BLOCKLIST_PINNING_SIGNER          = "services.blocklist.pinning.signer";
 
 /**
  * Revoke the appropriate certificates based on the records from the blocklist.
@@ -96,41 +90,16 @@ async function updatePinningList({ data:
       // prevent errors relating to individual preload entries from causing
       // sync to fail. We will accumulate telemetry for such failures in bug
       // 1254099.
     }
   }
 }
 
 /**
- * Write list of records into JSON file, and notify nsBlocklistService.
- *
- * @param {Object} client   RemoteSettingsClient instance
- * @param {Object} data      Current records in the local db.
- */
-async function updateJSONBlocklist(client, { data: { current: records } }) {
-  // Write JSON dump for synchronous load at startup.
-  const path = OS.Path.join(OS.Constants.Path.profileDir, client.filename);
-  const blocklistFolder = OS.Path.dirname(path);
-
-  await OS.File.makeDir(blocklistFolder, {from: OS.Constants.Path.profileDir});
-
-  const serialized = JSON.stringify({data: records}, null, 2);
-  try {
-    await OS.File.writeAtomic(path, serialized, {tmpPath: path + ".tmp"});
-    // Notify change to `nsBlocklistService`
-    const eventData = {filename: client.filename};
-    Services.cpmm.sendAsyncMessage("Blocklist:reload-from-disk", eventData);
-  } catch (e) {
-    Cu.reportError(e);
-  }
-}
-
-
-/**
  * This custom filter function is used to limit the entries returned
  * by `RemoteSettings("...").get()` depending on the target app information
  * defined on entries.
  */
 async function targetAppFilter(entry, environment) {
   // If the entry has a JEXL filter expression, it should prevail.
   // The legacy target app mechanism will be kept in place for old entries.
   // See https://bugzilla.mozilla.org/show_bug.cgi?id=1463377
@@ -186,42 +155,25 @@ async function targetAppFilter(entry, en
   }
   // Skip this entry.
   return null;
 }
 
 var AddonBlocklistClient;
 var OneCRLBlocklistClient;
 var PinningBlocklistClient;
-var PluginBlocklistClient;
 
 function initialize() {
   OneCRLBlocklistClient = RemoteSettings(Services.prefs.getCharPref(PREF_BLOCKLIST_ONECRL_COLLECTION), {
     bucketName: Services.prefs.getCharPref(PREF_BLOCKLIST_BUCKET),
     lastCheckTimePref: PREF_BLOCKLIST_ONECRL_CHECKED_SECONDS,
     signerName: Services.prefs.getCharPref(PREF_BLOCKLIST_ONECRL_SIGNER),
   });
   OneCRLBlocklistClient.on("sync", updateCertBlocklist);
 
-  AddonBlocklistClient = RemoteSettings(Services.prefs.getCharPref(PREF_BLOCKLIST_ADDONS_COLLECTION), {
-    bucketName: Services.prefs.getCharPref(PREF_BLOCKLIST_BUCKET),
-    lastCheckTimePref: PREF_BLOCKLIST_ADDONS_CHECKED_SECONDS,
-    signerName: Services.prefs.getCharPref(PREF_BLOCKLIST_ADDONS_SIGNER),
-    filterFunc: targetAppFilter,
-  });
-  AddonBlocklistClient.on("sync", updateJSONBlocklist.bind(null, AddonBlocklistClient));
-
-  PluginBlocklistClient = RemoteSettings(Services.prefs.getCharPref(PREF_BLOCKLIST_PLUGINS_COLLECTION), {
-    bucketName: Services.prefs.getCharPref(PREF_BLOCKLIST_BUCKET),
-    lastCheckTimePref: PREF_BLOCKLIST_PLUGINS_CHECKED_SECONDS,
-    signerName: Services.prefs.getCharPref(PREF_BLOCKLIST_PLUGINS_SIGNER),
-    filterFunc: targetAppFilter,
-  });
-  PluginBlocklistClient.on("sync", updateJSONBlocklist.bind(null, PluginBlocklistClient));
-
   PinningBlocklistClient = RemoteSettings(Services.prefs.getCharPref(PREF_BLOCKLIST_PINNING_COLLECTION), {
     bucketName: Services.prefs.getCharPref(PREF_BLOCKLIST_PINNING_BUCKET),
     lastCheckTimePref: PREF_BLOCKLIST_PINNING_CHECKED_SECONDS,
     signerName: Services.prefs.getCharPref(PREF_BLOCKLIST_PINNING_SIGNER),
   });
   PinningBlocklistClient.on("sync", updatePinningList);
 }
 
--- a/toolkit/components/telemetry/TelemetryEnvironment.jsm
+++ b/toolkit/components/telemetry/TelemetryEnvironment.jsm
@@ -277,17 +277,17 @@ const PREF_SEARCH_COHORT = "browser.sear
 const COMPOSITOR_CREATED_TOPIC = "compositor:created";
 const COMPOSITOR_PROCESS_ABORTED_TOPIC = "compositor:process-aborted";
 const DISTRIBUTION_CUSTOMIZATION_COMPLETE_TOPIC = "distribution-customization-complete";
 const GFX_FEATURES_READY_TOPIC = "gfx-features-ready";
 const SEARCH_ENGINE_MODIFIED_TOPIC = "browser-search-engine-modified";
 const SEARCH_SERVICE_TOPIC = "browser-search-service";
 const SESSIONSTORE_WINDOWS_RESTORED_TOPIC = "sessionstore-windows-restored";
 const PREF_CHANGED_TOPIC = "nsPref:changed";
-const BLOCKLIST_LOADED_TOPIC = "blocklist-loaded";
+const BLOCKLIST_LOADED_TOPIC = "plugin-blocklist-loaded";
 
 /**
  * Enforces the parameter to a boolean value.
  * @param aValue The input value.
  * @return {Boolean|Object} If aValue is a boolean or a number, returns its truthfulness
  *         value. Otherwise, return null.
  */
 function enforceBoolean(aValue) {
--- a/toolkit/mozapps/extensions/AddonManager.jsm
+++ b/toolkit/mozapps/extensions/AddonManager.jsm
@@ -31,22 +31,16 @@ const PREF_EM_CHECK_UPDATE_SECURITY   = 
 const PREF_SYS_ADDON_UPDATE_ENABLED   = "extensions.systemAddon.update.enabled";
 
 const PREF_MIN_WEBEXT_PLATFORM_VERSION = "extensions.webExtensionsMinPlatformVersion";
 const PREF_WEBAPI_TESTING             = "extensions.webapi.testing";
 const PREF_WEBEXT_PERM_PROMPTS        = "extensions.webextPermissionPrompts";
 
 const UPDATE_REQUEST_VERSION          = 2;
 
-const XMLURI_BLOCKLIST                = "http://www.mozilla.org/2006/addons-blocklist";
-
-const KEY_PROFILEDIR                  = "ProfD";
-const KEY_APPDIR                      = "XCurProcD";
-const FILE_BLOCKLIST                  = "blocklist.xml";
-
 const DEFAULT_THEME_ID                = "default-theme@mozilla.org";
 
 const BRANCH_REGEXP                   = /^([^\.]+\.[0-9]+[a-z]*).*/gi;
 const PREF_EM_CHECK_COMPATIBILITY_BASE = "extensions.checkCompatibility";
 var PREF_EM_CHECK_COMPATIBILITY = MOZ_COMPATIBILITY_NIGHTLY ?
                                   PREF_EM_CHECK_COMPATIBILITY_BASE + ".nightly" :
                                   undefined;
 
@@ -60,22 +54,21 @@ const WEBAPI_TEST_INSTALL_HOSTS = [
 ];
 
 const URI_XPINSTALL_DIALOG = "chrome://mozapps/content/xpinstall/xpinstallConfirm.xul";
 
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 ChromeUtils.import("resource://gre/modules/AsyncShutdown.jsm");
 
-XPCOMUtils.defineLazyGlobalGetters(this, ["DOMParser", "Element"]);
+XPCOMUtils.defineLazyGlobalGetters(this, ["Element"]);
 
 XPCOMUtils.defineLazyModuleGetters(this, {
   AddonRepository: "resource://gre/modules/addons/AddonRepository.jsm",
   Extension: "resource://gre/modules/Extension.jsm",
-  FileUtils: "resource://gre/modules/FileUtils.jsm",
   LightweightThemeManager: "resource://gre/modules/LightweightThemeManager.jsm",
   PromptUtils: "resource://gre/modules/SharedPromptUtils.jsm",
 });
 
 XPCOMUtils.defineLazyPreferenceGetter(this, "WEBEXT_PERMISSION_PROMPTS",
                                       PREF_WEBEXT_PERM_PROMPTS, false);
 
 // Initialize the WebExtension process script service as early as possible,
@@ -554,92 +547,16 @@ var AddonManagerInternal = {
   // Store telemetry details per addon provider
   telemetryDetails: {},
   upgradeListeners: new Map(),
 
   recordTimestamp(name, value) {
     this.TelemetryTimestamps.add(name, value);
   },
 
-  validateBlocklist() {
-    let appBlocklist = FileUtils.getFile(KEY_APPDIR, [FILE_BLOCKLIST]);
-
-    // If there is no application shipped blocklist then there is nothing to do
-    if (!appBlocklist.exists())
-      return;
-
-    let profileBlocklist = FileUtils.getFile(KEY_PROFILEDIR, [FILE_BLOCKLIST]);
-
-    // If there is no blocklist in the profile then copy the application shipped
-    // one there
-    if (!profileBlocklist.exists()) {
-      try {
-        appBlocklist.copyTo(profileBlocklist.parent, FILE_BLOCKLIST);
-      } catch (e) {
-        logger.warn("Failed to copy the application shipped blocklist to the profile", e);
-      }
-      return;
-    }
-
-    let fileStream = Cc["@mozilla.org/network/file-input-stream;1"].
-                     createInstance(Ci.nsIFileInputStream);
-    try {
-      let cstream = Cc["@mozilla.org/intl/converter-input-stream;1"].
-                    createInstance(Ci.nsIConverterInputStream);
-      fileStream.init(appBlocklist, FileUtils.MODE_RDONLY, FileUtils.PERMS_FILE, 0);
-      cstream.init(fileStream, "UTF-8", 0, 0);
-
-      let data = "";
-      let str = {};
-      let read = 0;
-      do {
-        read = cstream.readString(0xffffffff, str);
-        data += str.value;
-      } while (read != 0);
-
-      let parser = new DOMParser();
-      var doc = parser.parseFromString(data, "text/xml");
-    } catch (e) {
-      logger.warn("Application shipped blocklist could not be loaded", e);
-      return;
-    } finally {
-      try {
-        fileStream.close();
-      } catch (e) {
-        logger.warn("Unable to close blocklist file stream", e);
-      }
-    }
-
-    // If the namespace is incorrect then ignore the application shipped
-    // blocklist
-    if (doc.documentElement.namespaceURI != XMLURI_BLOCKLIST) {
-      logger.warn("Application shipped blocklist has an unexpected namespace (" +
-                  doc.documentElement.namespaceURI + ")");
-      return;
-    }
-
-    // If there is no lastupdate information then ignore the application shipped
-    // blocklist
-    if (!doc.documentElement.hasAttribute("lastupdate"))
-      return;
-
-    // If the application shipped blocklist is older than the profile blocklist
-    // then do nothing
-    if (doc.documentElement.getAttribute("lastupdate") <=
-        profileBlocklist.lastModifiedTime)
-      return;
-
-    // Otherwise copy the application shipped blocklist to the profile
-    try {
-      appBlocklist.copyTo(profileBlocklist.parent, FILE_BLOCKLIST);
-    } catch (e) {
-      logger.warn("Failed to copy the application shipped blocklist to the profile", e);
-    }
-  },
-
   /**
    * Start up a provider, and register its shutdown hook if it has one
    *
    * @param {string} aProvider - An add-on provider.
    * @param {boolean} aAppChanged - Whether or not the app version has changed since last session.
    * @param {string} aOldAppVersion - Previous application version, if changed.
    * @param {string} aOldPlatformVersion - Previous platform version, if changed.
    *
@@ -721,17 +638,16 @@ var AddonManagerInternal = {
       if (appChanged !== false) {
         logger.debug("Application has been upgraded");
         Services.prefs.setCharPref(PREF_EM_LAST_APP_VERSION,
                                    Services.appinfo.version);
         Services.prefs.setCharPref(PREF_EM_LAST_PLATFORM_VERSION,
                                    Services.appinfo.platformVersion);
         Services.prefs.setIntPref(PREF_BLOCKLIST_PINGCOUNTVERSION,
                                   (appChanged === undefined ? 0 : -1));
-        this.validateBlocklist();
       }
 
       if (!MOZ_COMPATIBILITY_NIGHTLY) {
         PREF_EM_CHECK_COMPATIBILITY = PREF_EM_CHECK_COMPATIBILITY_BASE + "." +
                                       Services.appinfo.version.replace(BRANCH_REGEXP, "$1");
       }
 
       gCheckCompatibility = Services.prefs.getBoolPref(PREF_EM_CHECK_COMPATIBILITY,
--- a/toolkit/mozapps/extensions/Blocklist.jsm
+++ b/toolkit/mozapps/extensions/Blocklist.jsm
@@ -8,135 +8,216 @@
 
 /* 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");
-XPCOMUtils.defineLazyGlobalGetters(this, ["XMLHttpRequest"]);
 
 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 blocklist XML file looks something like this:
-#
-#    <blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist">
-#      <emItems>
-#        <emItem id="item_1@domain" blockID="i1">
-#          <prefs>
-#            <pref>accessibility.accesskeycausesactivation</pref>
-#            <pref>accessibility.blockautorefresh</pref>
-#          </prefs>
-#          <versionRange minVersion="1.0" maxVersion="2.0.*">
-#            <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
-#              <versionRange minVersion="1.5" maxVersion="1.5.*"/>
-#              <versionRange minVersion="1.7" maxVersion="1.7.*"/>
-#            </targetApplication>
-#            <targetApplication id="toolkit@mozilla.org">
-#              <versionRange minVersion="1.9" maxVersion="1.9.*"/>
-#            </targetApplication>
-#          </versionRange>
-#          <versionRange minVersion="3.0" maxVersion="3.0.*">
-#            <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
-#              <versionRange minVersion="1.5" maxVersion="1.5.*"/>
-#            </targetApplication>
-#            <targetApplication id="toolkit@mozilla.org">
-#              <versionRange minVersion="1.9" maxVersion="1.9.*"/>
-#            </targetApplication>
-#          </versionRange>
-#        </emItem>
-#        <emItem id="item_2@domain" blockID="i2">
-#          <versionRange minVersion="3.1" maxVersion="4.*"/>
-#        </emItem>
-#        <emItem id="item_3@domain">
-#          <versionRange>
-#            <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
-#              <versionRange minVersion="1.5" maxVersion="1.5.*"/>
-#            </targetApplication>
-#          </versionRange>
-#        </emItem>
-#        <emItem id="item_4@domain" blockID="i3">
-#          <versionRange>
-#            <targetApplication>
-#              <versionRange minVersion="1.5" maxVersion="1.5.*"/>
-#            </targetApplication>
-#          </versionRange>
-#        <emItem id="/@badperson\.com$/"/>
-#      </emItems>
-#      <pluginItems>
-#        <pluginItem blockID="i4">
-#          <!-- All match tags must match a plugin to blocklist a plugin -->
-#          <match name="name" exp="some plugin"/>
-#          <match name="description" exp="1[.]2[.]3"/>
-#        </pluginItem>
-#      </pluginItems>
-#    </blocklist>
-   */
-
 // 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.
 ChromeUtils.defineModuleGetter(this, "BlocklistRemoteSettings",
                                "resource://services-common/blocklist-clients.js");
 XPCOMUtils.defineLazyGetter(this, "RemoteSettings", function() {
   // Instantiate blocklist clients.
   BlocklistRemoteSettings.initialize();
   // Import RemoteSettings for ``pollChanges()``
   const { RemoteSettings } = ChromeUtils.import("resource://services-settings/remote-settings.js", {});
   return RemoteSettings;
 });
 
 const TOOLKIT_ID                      = "toolkit@mozilla.org";
-const KEY_PROFILEDIR                  = "ProfD";
-const KEY_APPDIR                      = "XCurProcD";
-const FILE_BLOCKLIST                  = "blocklist.xml";
 const PREF_BLOCKLIST_LASTUPDATETIME   = "app.update.lastUpdateTime.blocklist-background-update-timer";
 const PREF_BLOCKLIST_URL              = "extensions.blocklist.url";
 const PREF_BLOCKLIST_ITEM_URL         = "extensions.blocklist.itemURL";
 const PREF_BLOCKLIST_ENABLED          = "extensions.blocklist.enabled";
 const PREF_BLOCKLIST_LAST_MODIFIED    = "extensions.blocklist.lastModified";
 const PREF_BLOCKLIST_LEVEL            = "extensions.blocklist.level";
 const PREF_BLOCKLIST_PINGCOUNTTOTAL   = "extensions.blocklist.pingCountTotal";
 const PREF_BLOCKLIST_PINGCOUNTVERSION = "extensions.blocklist.pingCountVersion";
 const PREF_BLOCKLIST_SUPPRESSUI       = "extensions.blocklist.suppressUI";
 const PREF_BLOCKLIST_UPDATE_ENABLED   = "services.blocklist.update_enabled";
 const PREF_APP_DISTRIBUTION           = "distribution.id";
 const PREF_APP_DISTRIBUTION_VERSION   = "distribution.version";
 const PREF_EM_LOGGING_ENABLED         = "extensions.logging.enabled";
-const XMLURI_BLOCKLIST                = "http://www.mozilla.org/2006/addons-blocklist";
 const XMLURI_PARSE_ERROR              = "http://www.mozilla.org/newlayout/xml/parsererror.xml";
 const URI_BLOCKLIST_DIALOG            = "chrome://mozapps/content/extensions/blocklist.xul";
 const DEFAULT_SEVERITY                = 3;
 const DEFAULT_LEVEL                   = 2;
 const MAX_BLOCK_LEVEL                 = 3;
 const SEVERITY_OUTDATED               = 0;
 const VULNERABILITYSTATUS_NONE             = 0;
 const VULNERABILITYSTATUS_UPDATE_AVAILABLE = 1;
 const VULNERABILITYSTATUS_NO_UPDATE        = 2;
 
 // Kinto blocklist constants
 const PREF_BLOCKLIST_BUCKET                  = "services.blocklist.bucket";
 const PREF_BLOCKLIST_GFX_COLLECTION          = "services.blocklist.gfx.collection";
 const PREF_BLOCKLIST_GFX_CHECKED_SECONDS     = "services.blocklist.gfx.checked";
 const PREF_BLOCKLIST_GFX_SIGNER              = "services.blocklist.gfx.signer";
+const PREF_BLOCKLIST_PLUGINS_COLLECTION      = "services.blocklist.plugins.collection";
+const PREF_BLOCKLIST_PLUGINS_CHECKED_SECONDS = "services.blocklist.plugins.checked";
+const PREF_BLOCKLIST_PLUGINS_SIGNER          = "services.blocklist.plugins.signer";
+const PREF_BLOCKLIST_ADDONS_COLLECTION       = "services.blocklist.addons.collection";
+const PREF_BLOCKLIST_ADDONS_CHECKED_SECONDS  = "services.blocklist.addons.checked";
+const PREF_BLOCKLIST_ADDONS_SIGNER           = "services.blocklist.addons.signer";
+
+const Utils = {
+  /**
+   * Checks whether this entry is valid for the current OS and ABI.
+   * If the entry has an "os" property then the current OS must appear in
+   * its comma separated list for it to be valid. Similarly for the
+   * xpcomabi property.
+   *
+   * @param {Object} item
+   *        The blocklist item.
+   * @returns {bool}
+   *        Whether the entry matches the current OS.
+   */
+  matchesOSABI(item) {
+    if (item.os) {
+      let os = item.os.split(",");
+      if (!os.includes(gAppOS)) {
+        return false;
+      }
+    }
+
+    if (item.xpcomabi) {
+      let xpcomabi = item.xpcomabi.split(",");
+      if (!xpcomabi.includes(gApp.XPCOMABI)) {
+        return false;
+      }
+    }
+    return true;
+  },
+
+  /**
+   * Checks if a version is higher than or equal to the minVersion (if provided)
+   * and lower than or equal to the maxVersion (if provided).
+   * @param {string} version
+   *        The version to test.
+   * @param {string?} minVersion
+   *        The minimum version. If null it is assumed that version is always
+   *        larger.
+   * @param {string?} maxVersion
+   *        The maximum version. If null it is assumed that version is always
+   *        smaller.
+   * @returns {boolean}
+   *        Whether the item matches the range.
+   */
+  versionInRange(version, minVersion, maxVersion) {
+    if (minVersion && Services.vc.compare(version, minVersion) < 0)
+      return false;
+    if (maxVersion && Services.vc.compare(version, maxVersion) > 0)
+      return false;
+    return true;
+  },
+
+  /**
+   * Tests if this versionRange matches the item specified, and has a matching
+   * targetApplication id and version.
+   * @param {Object} versionRange
+   *        The versionRange to check against
+   * @param {string} itemVersion
+   *        The version of the actual addon/plugin to test for.
+   * @param {string} appVersion
+   *        The version of the application to test for.
+   * @param {string} toolkitVersion
+   *        The version of toolkit to check for.
+   * @returns {boolean}
+   *        True if this version range covers the item and app/toolkit version given.
+   */
+  versionsMatch(versionRange, itemVersion, appVersion, toolkitVersion) {
+    // Some platforms have no version for plugins, these don't match if there
+    // was a min/maxVersion provided
+    if (!itemVersion && (versionRange.minVersion || versionRange.maxVersion))
+      return false;
+
+    // Check if the item version matches
+    if (!this.versionInRange(itemVersion, versionRange.minVersion, versionRange.maxVersion))
+      return false;
+
+    // Check if the application or toolkit version matches
+    for (let tA of versionRange.targetApplication) {
+      if (tA.guid == gAppID && this.versionInRange(appVersion, tA.minVersion, tA.maxVersion)) {
+        return true;
+      }
+      if (tA.guid == TOOLKIT_ID &&
+          this.versionInRange(toolkitVersion, tA.minVersion, tA.maxVersion)) {
+        return true;
+      }
+    }
+    return false;
+  },
+
+  /**
+   * Given a blocklist JS object entry, ensure it has a versionRange property, where
+   * each versionRange property has valid severity and vulnerabilityStatus properties,
+   * and at least 1 valid targetApplication.
+   * If it didn't have a valid targetApplication array before and/or it was empty,
+   * fill it with an entry with null min/maxVersion properties, which will match
+   * every version.
+   *
+   * If there *are* targetApplications, if any of them don't have a guid property,
+   * assign them the current app's guid.
+   *
+   * @param {Object} entry
+   *                 blocklist entry object.
+   */
+  ensureVersionRangeIsSane(entry) {
+    if (!entry.versionRange.length) {
+      entry.versionRange.push({});
+    }
+    for (let vr of entry.versionRange) {
+      if (!vr.hasOwnProperty("severity")) {
+        vr.severity = DEFAULT_SEVERITY;
+      }
+      if (!vr.hasOwnProperty("vulnerabilityStatus")) {
+        vr.vulnerabilityStatus = VULNERABILITYSTATUS_NONE;
+      }
+
+      if (!Array.isArray(vr.targetApplication)) {
+        vr.targetApplication = [];
+      }
+      if (!vr.targetApplication.length) {
+        vr.targetApplication.push({minVersion: null, maxVersion: null});
+      }
+      vr.targetApplication.forEach(tA => {
+        if (!tA.guid) {
+          tA.guid = gAppID;
+        }
+      });
+    }
+  },
+
+  /**
+   * Create a blocklist URL for the given blockID
+   * @param {String} id the blockID to use
+   * @returns {String} the blocklist URL.
+   */
+  _createBlocklistURL(id) {
+    let url = Services.urlFormatter.formatURLPref(PREF_BLOCKLIST_ITEM_URL);
+    return url.replace(/%blockID%/g, id);
+  },
+
+};
 
 /**
  * The Graphics blocklist implementation. The JSON objects for graphics blocks look
  * something like:
  *
  * {
  *  "blockID": "g35",
  *  "os": "WINNT 6.1",
@@ -250,16 +331,586 @@ this.GfxBlocklist = {
       }
       Services.obs.notifyObservers(null, "blocklist-data-gfxItems", payload.join("\n"));
     }
     // The return value is only used by tests.
     return entries;
   },
 };
 
+/**
+ * The plugins blocklist implementation. The JSON objects for plugin blocks look
+ * something like:
+ *
+ *  {
+ *    "blockID":"p906",
+ *    "details": {
+ *      "bug":"https://bugzilla.mozilla.org/show_bug.cgi?id=1159917",
+ *      "who":"Which users it affects",
+ *      "why":"Why it's being blocklisted",
+ *      "name":"Java Plugin 7 update 45 to 78 (click-to-play), Windows",
+ *      "created":"2015-05-19T09:02:45Z"
+ *    },
+ *    "enabled":true,
+ *    "infoURL":"https://java.com/",
+ *    "matchName":"Java\\(TM\\) Platform SE 7 U(4[5-9]|(5|6)\\d|7[0-8])(\\s[^\\d\\._U]|$)",
+ *    "versionRange":[
+ *      {
+ *        "severity":0,
+ *        "targetApplication":[
+ *          {
+ *            "guid":"{ec8030f7-c20a-464f-9b0e-13a3a9e97384}",
+ *            "maxVersion":"57.0.*",
+ *            "minVersion":"0"
+ *          }
+ *        ],
+ *        "vulnerabilityStatus":1
+ *      }
+ *    ],
+ *    "matchFilename":"npjp2\\.dll",
+ *    "id":"f254e5bc-12c7-7954-fe6b-8f1fdab0ae88",
+ *    "last_modified":1519390914542,
+ *  }
+ *
+ * Note: we assign to the global to allow tests to reach the object directly.
+ */
+this.PluginBlocklist = {
+  _matchProps: {
+    "matchDescription": "description",
+    "matchFilename": "filename",
+    "matchName": "name",
+  },
+
+  async _ensureEntries() {
+    await this._ensureInitialized();
+    if (!this._entries && gBlocklistEnabled) {
+      await this._updateEntries();
+
+      // Dispatch to mainthread because consumers may try to construct nsIPluginHost
+      // again based on this notification, while we were called from nsIPluginHost
+      // anyway, leading to re-entrancy.
+      Services.tm.dispatchToMainThread(function() {
+        Services.obs.notifyObservers(null, "plugin-blocklist-loaded");
+      });
+    }
+  },
+
+  async _updateEntries() {
+    this._entries = await this._client.get().catch(ex => Cu.reportError(ex));
+    // Handle error silently. This can happen if our request to fetch data is aborted,
+    // e.g. by application shutdown.
+    if (!this._entries) {
+      this._entries = [];
+      return;
+    }
+    this._entries.forEach(entry => {
+      entry.matches = {};
+      for (let k of Object.keys(this._matchProps)) {
+        if (entry[k]) {
+          try {
+            entry.matches[this._matchProps[k]] = new RegExp(entry[k], "m");
+          } catch (ex) { /* Ignore invalid regexes */ }
+        }
+      }
+      Utils.ensureVersionRangeIsSane(entry);
+    });
+  },
+
+  async _filterItem(entry) {
+    if (!(await BlocklistRemoteSettings.targetAppFilter(entry, {appID: gAppID, version: gApp.version}))) {
+      return null;
+    }
+    if (!Utils.matchesOSABI(entry)) {
+      Cu.reportError("OSABI mismatch " + entry.matchName);
+      return null;
+    }
+    if (!entry.matchFilename && !entry.matchName && !entry.matchDescription) {
+      Cu.reportError("Nothing to filter on");
+      return null;
+    }
+    return entry;
+  },
+
+  async _ensureInitialized() {
+    if (!gBlocklistEnabled || this._initialized) {
+      return;
+    }
+    this._initialized = true;
+    this._client = RemoteSettings(Services.prefs.getCharPref(PREF_BLOCKLIST_PLUGINS_COLLECTION), {
+      bucketName: Services.prefs.getCharPref(PREF_BLOCKLIST_BUCKET),
+      lastCheckTimePref: PREF_BLOCKLIST_PLUGINS_CHECKED_SECONDS,
+      signerName: Services.prefs.getCharPref(PREF_BLOCKLIST_PLUGINS_SIGNER),
+      filterFunc: this._filterItem,
+    });
+    this._client.on("sync", () => {
+      this._onUpdate();
+    });
+  },
+
+  async _onUpdate() {
+    let oldEntries = this._entries || [];
+    await this._updateEntries();
+    const pluginHost = Cc["@mozilla.org/plugin/host;1"].
+                         getService(Ci.nsIPluginHost);
+    const plugins = pluginHost.getPluginTags();
+
+    let blockedItems = [];
+
+    for (let plugin of plugins) {
+      let oldState = this._getState(plugin, oldEntries);
+      let state = this._getState(plugin, this._entries);
+      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) {
+          blockedItems.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: await this.getURL(plugin),
+          });
+        }
+      }
+    }
+
+    if (blockedItems.length) {
+      this._showBlockedPluginsPrompt(blockedItems);
+    } else {
+      this._notifyUpdate();
+    }
+  },
+
+  _showBlockedPluginsPrompt(blockedPlugins) {
+    if ("@mozilla.org/addons/blocklist-prompt;1" in Cc) {
+      try {
+        let blockedPrompter = Cc["@mozilla.org/addons/blocklist-prompt;1"]
+                               .getService().wrappedJSObject;
+        blockedPrompter.prompt(blockedPlugins);
+      } catch (e) {
+        LOG(e);
+      }
+      this._notifyUpdate();
+      return;
+    }
+
+    let args = {
+      restart: false,
+      list: blockedPlugins
+    };
+    // 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 = async () => {
+      Services.obs.removeObserver(applyBlocklistChanges, "addon-blocklist-closed");
+
+      for (let blockedData of blockedPlugins) {
+        if (!blockedData.disable)
+          continue;
+
+        // This will disable all the plugins immediately.
+        if (blockedData.item instanceof Ci.nsIPluginTag) {
+          blockedData.item.enabledState = Ci.nsIPluginTag.STATE_DISABLED;
+        }
+      }
+
+      if (!args.restart) {
+        this._notifyUpdate();
+        return;
+      }
+
+      // We need to ensure the new blocklist state is written to disk before restarting.
+      // We'll notify about the blocklist update, then wait for nsIPluginHost
+      // to finish processing it, then restart the browser.
+      let pluginUpdatesFinishedPromise = new Promise(resolve => {
+        Services.obs.addObserver(function updatesFinished() {
+          Services.obs.removeObserver(updatesFinished, "plugin-blocklist-updates-finished");
+          resolve();
+        }, "plugin-blocklist-updates-finished");
+      });
+      this._notifyUpdate();
+      await pluginUpdatesFinishedPromise;
+
+      // Notify all windows that an application quit has been requested.
+      var cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].
+                       createInstance(Ci.nsISupportsPRBool);
+      Services.obs.notifyObservers(cancelQuit, "quit-application-requested");
+
+      // Something aborted the quit process.
+      if (cancelQuit.data)
+        return;
+
+      Services.startup.quit(Ci.nsIAppStartup.eRestart | Ci.nsIAppStartup.eAttemptQuit);
+    };
+
+    Services.obs.addObserver(applyBlocklistChanges, "addon-blocklist-closed");
+
+    if (Services.prefs.getBoolPref(PREF_BLOCKLIST_SUPPRESSUI, false)) {
+      applyBlocklistChanges();
+      return;
+    }
+
+    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);
+  },
+
+  _notifyUpdate() {
+    Services.obs.notifyObservers(null, "plugin-blocklist-updated");
+  },
+
+  async getURL(plugin) {
+    await this._ensureEntries();
+    let r = this._getEntry(plugin, this._entries);
+    if (!r) {
+      return null;
+    }
+    let blockEntry = r.entry;
+    if (!blockEntry.blockID) {
+      return null;
+    }
+
+    return blockEntry.infoURL || Utils._createBlocklistURL(blockEntry.blockID);
+  },
+
+  async getState(plugin, appVersion, toolkitVersion) {
+    if (AppConstants.platform == "android") {
+      return Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
+    }
+    await this._ensureEntries();
+    return this._getState(plugin, this._entries, appVersion, toolkitVersion);
+  },
+
+  async getEntry(plugin, appVersion, toolkitVersion) {
+    await this._ensureEntries();
+    return this._getEntry(plugin, this._entries, appVersion, toolkitVersion);
+  },
+
+  /**
+   * Private helper to get the blocklist entry for a plugin given a set of
+   * blocklist entries and versions.
+   *
+   * @param {nsIPluginTag} plugin
+   *        The nsIPluginTag to get the blocklist state for.
+   * @param {object[]} pluginEntries
+   *        The plugin blocklist entries to compare against.
+   * @param {string?} appVersion
+   *        The application version to compare to, will use the current
+   *        version if null.
+   * @param {string?} toolkitVersion
+   *        The toolkit version to compare to, will use the current version if
+   *        null.
+   * @returns {object?}
+   *        {entry: blocklistEntry, version: blocklistEntryVersion},
+   *        or null if there is no matching entry.
+   */
+  _getEntry(plugin, pluginEntries, appVersion, toolkitVersion) {
+    if (!gBlocklistEnabled)
+      return null;
+
+    // Not all applications implement nsIXULAppInfo (e.g. xpcshell doesn't).
+    if (!appVersion && !gApp.version)
+      return Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
+
+    if (!appVersion)
+      appVersion = gApp.version;
+    if (!toolkitVersion)
+      toolkitVersion = gApp.platformVersion;
+
+    const pluginProperties = {
+      description: plugin.description,
+      filename: plugin.filename,
+      name: plugin.name,
+      version: plugin.version,
+    };
+    if (!pluginEntries) {
+      Cu.reportError(new Error("Hi"));
+    }
+    for (let blockEntry of pluginEntries) {
+      var matchFailed = false;
+      for (var name in blockEntry.matches) {
+        let pluginProperty = pluginProperties[name];
+        if (typeof pluginProperty != "string" ||
+            !blockEntry.matches[name].test(pluginProperty)) {
+          matchFailed = true;
+          break;
+        }
+      }
+
+      if (matchFailed)
+        continue;
+
+      for (let versionRange of blockEntry.versionRange) {
+        if (Utils.versionsMatch(versionRange, pluginProperties.version,
+                                appVersion, toolkitVersion)) {
+          return {entry: blockEntry, version: versionRange};
+        }
+      }
+    }
+
+    return null;
+  },
+
+
+  /**
+   * Private version of getState that allows the caller to pass in
+   * the plugin blocklist entries.
+   *
+   * @param {nsIPluginTag} plugin
+   *        The nsIPluginTag to get the blocklist state for.
+   * @param {object[]} pluginEntries
+   *        The plugin blocklist entries to compare against.
+   * @param {string?} appVersion
+   *        The application version to compare to, will use the current
+   *        version if null.
+   * @param {string?} toolkitVersion
+   *        The toolkit version to compare to, will use the current version if
+   *        null.
+   * @returns {integer}
+   *        The blocklist state for the item, one of the STATE constants as
+   *        defined in nsIBlocklistService.
+   */
+  _getState(plugin, pluginEntries, appVersion, toolkitVersion) {
+    let r = this._getEntry(plugin, pluginEntries, appVersion, toolkitVersion);
+    if (!r) {
+      return Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
+    }
+
+    let {version: versionRange} = r;
+
+    if (versionRange.severity >= gBlocklistLevel)
+      return Ci.nsIBlocklistService.STATE_BLOCKED;
+    if (versionRange.severity == SEVERITY_OUTDATED) {
+      let vulnerabilityStatus = versionRange.vulnerabilityStatus;
+      if (vulnerabilityStatus == VULNERABILITYSTATUS_UPDATE_AVAILABLE)
+        return Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE;
+      if (vulnerabilityStatus == VULNERABILITYSTATUS_NO_UPDATE)
+        return Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE;
+      return Ci.nsIBlocklistService.STATE_OUTDATED;
+    }
+    return Ci.nsIBlocklistService.STATE_SOFTBLOCKED;
+  },
+
+};
+
+/**
+ * The extensions blocklist implementation. The JSON objects for extension
+ * blocks look something like:
+ *
+ * FIXME needs example.
+ *
+ * Note: we assign to the global to allow tests to reach the object directly.
+ */
+this.ExtensionBlocklist = {
+  async _ensureEntries() {
+    await this._ensureInitialized();
+    if (!this._entries && gBlocklistEnabled) {
+      await this._updateEntries();
+    }
+  },
+
+  async _updateEntries() {
+    this._entries = await this._client.get().catch(ex => Cu.reportError(ex));
+    // Handle error silently. This can happen if our request to fetch data is aborted,
+    // e.g. by application shutdown.
+    if (!this._entries) {
+      this._entries = [];
+      return;
+    }
+    this._entries.forEach(entry => {
+      function getCriteria(str) {
+        if (!str.startsWith("/")) {
+          return str;
+        }
+        let lastSlash = str.lastIndexOf("/");
+        let pattern = str.slice(1, lastSlash);
+        let flags = str.slice(lastSlash + 1);
+        return new RegExp(pattern, flags);
+      }
+      entry.matches = {};
+      if (entry.guid) {
+        entry.matches.id = getCriteria(entry.guid);
+      }
+      for (let key of EXTENSION_BLOCK_FILTERS) {
+        if (key == "id" || !entry[key]) {
+          continue;
+        }
+        entry.matches[key] = getCriteria(entry[key]);
+      }
+      Utils.ensureVersionRangeIsSane(entry);
+    });
+  },
+
+  async _filterItem(entry) {
+    if (!(await BlocklistRemoteSettings.targetAppFilter(entry, {appID: gAppID, version: gApp.version}))) {
+      Cu.reportError("RS ditched " + entry.guid);
+      return null;
+    }
+    if (!Utils.matchesOSABI(entry)) {
+      Cu.reportError("OSABI mismatch " + entry.guid);
+      return null;
+    }
+    // Need something to filter on - at least a guid or name (either could be a regex):
+    if (!entry.guid && !entry.name) {
+      Cu.reportError("No guid/name");
+      return null;
+    }
+    return entry;
+  },
+
+  async _ensureInitialized() {
+    if (!gBlocklistEnabled || this._initialized) {
+      return;
+    }
+    this._initialized = true;
+    this._client = RemoteSettings(Services.prefs.getCharPref(PREF_BLOCKLIST_ADDONS_COLLECTION), {
+      bucketName: Services.prefs.getCharPref(PREF_BLOCKLIST_BUCKET),
+      lastCheckTimePref: PREF_BLOCKLIST_ADDONS_CHECKED_SECONDS,
+      signerName: Services.prefs.getCharPref(PREF_BLOCKLIST_ADDONS_SIGNER),
+      filterFunc: this._filterItem,
+    });
+    this._client.on("sync", () => {
+      this._onUpdate();
+    });
+  },
+
+  async _onUpdate() {
+    let oldEntries = this._entries || [];
+    await this._updateEntries();
+
+    const types = ["extension", "theme", "locale", "dictionary", "service"];
+    let addons = await AddonManager.getAddonsByTypes(types);
+    for (let addon of addons) {
+      let oldState = addon.blocklistState;
+      if (addon.updateBlocklistState) {
+        await addon.updateBlocklistState(false);
+      } else if (oldEntries) {
+        let oldEntry = this._getEntry(addon, oldEntries);
+        oldState = oldEntry ? oldEntry.state : Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
+      } else {
+        oldState = Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
+      }
+      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;
+
+      // Ensure that softDisabled is false if the add-on is not soft blocked
+      if (state != Ci.nsIBlocklistService.STATE_SOFTBLOCKED)
+        addon.softDisabled = false;
+
+      // 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;
+      }
+
+      if (state == Ci.nsIBlocklistService.STATE_BLOCKED ||
+          state == Ci.nsIBlocklistService.STATE_SOFTBLOCKED) {
+        // 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;
+        // It's a block. We must reset certain preferences.
+        let entry = this._getEntry(addon, this._entries);
+        if (entry.prefs && entry.prefs.length) {
+          for (let pref of entry.prefs) {
+            Services.prefs.clearUserPref(pref);
+          }
+        }
+      }
+    }
+
+    AddonManagerPrivate.updateAddonAppDisabledStates();
+  },
+
+  async getState(addon, appVersion, toolkitVersion) {
+    let entry = await this.getEntry(addon, appVersion, toolkitVersion);
+    return entry ? entry.state : Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
+  },
+
+  async getEntry(addon, appVersion, toolkitVersion) {
+    await this._ensureEntries();
+    return this._getEntry(addon, this._entries, appVersion, toolkitVersion);
+  },
+
+  _getEntry(addon, addonEntries, appVersion, toolkitVersion) {
+    if (!gBlocklistEnabled || !addon)
+      return null;
+
+    // Not all applications implement nsIXULAppInfo (e.g. xpcshell doesn't).
+    if (!appVersion && !gApp.version)
+      return null;
+
+    if (!appVersion)
+      appVersion = gApp.version;
+    if (!toolkitVersion)
+      toolkitVersion = gApp.platformVersion;
+
+    let addonProps = {};
+    for (let key of EXTENSION_BLOCK_FILTERS) {
+      addonProps[key] = addon[key];
+    }
+    if (addonProps.creator)
+      addonProps.creator = addonProps.creator.name;
+
+    let propMatches = ([k, v]) => {
+      return !v || addonProps[k] == v || ((v instanceof RegExp) && v.test(addonProps[k]));
+    };
+    for (let entry of addonEntries) {
+      // First check if it matches our properties. If not, just skip to the next item.
+      if (!Object.entries(entry.matches).every(propMatches)) {
+        continue;
+      }
+      // If those match, check the app or toolkit version works:
+      for (let versionRange of entry.versionRange) {
+        if (Utils.versionsMatch(versionRange, addon.version,
+                                appVersion, toolkitVersion)) {
+          return {
+            state: versionRange.severity >= gBlocklistLevel ?
+                   Ci.nsIBlocklistService.STATE_BLOCKED : Ci.nsIBlocklistService.STATE_SOFTBLOCKED,
+            url: entry.blockID && Utils._createBlocklistURL(entry.blockID),
+          };
+        }
+      }
+    }
+    return null;
+  },
+};
+
 const EXTENSION_BLOCK_FILTERS = ["id", "name", "creator", "homepageURL", "updateURL"];
 
 var gLoggingEnabled = null;
 var gBlocklistEnabled = true;
 var gBlocklistLevel = DEFAULT_LEVEL;
 
 /**
  * @class nsIBlocklistPrompt
@@ -303,29 +954,25 @@ XPCOMUtils.defineLazyGetter(this, "gApp"
       throw ex;
   }
   return appinfo;
 });
 
 XPCOMUtils.defineLazyGetter(this, "gAppID", function() {
   return gApp.ID;
 });
-
 XPCOMUtils.defineLazyGetter(this, "gAppVersion", function() {
   return gApp.version;
 });
+XPCOMUtils.defineLazyGetter(this, "gAppOS", function() {
+  return gApp.OS;
+});
 
-XPCOMUtils.defineLazyGetter(this, "gABI", function() {
-  let abi = null;
-  try {
-    abi = gApp.XPCOMABI;
-  } catch (e) {
-    LOG("BlockList Global gABI: XPCOM ABI unknown.");
-  }
-  return abi;
+XPCOMUtils.defineLazyGetter(this, "gXPCOMABI", function() {
+  return gApp.XPCOMABI;
 });
 
 XPCOMUtils.defineLazyGetter(this, "gOSVersion", function() {
   let osVersion;
   try {
     osVersion = Services.sysinfo.getProperty("name") + " " + Services.sysinfo.getProperty("version");
   } catch (e) {
     LOG("BlockList Global gOSVersion: OS Version unknown.");
@@ -349,70 +996,16 @@ XPCOMUtils.defineLazyGetter(this, "gOSVe
  */
 function LOG(string) {
   if (gLoggingEnabled) {
     dump("*** " + string + "\n");
     Services.console.logStringMessage(string);
   }
 }
 
-// Restarts the application checking in with observers first
-function restartApp() {
-  // Notify all windows that an application quit has been requested.
-  var cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].
-                   createInstance(Ci.nsISupportsPRBool);
-  Services.obs.notifyObservers(cancelQuit, "quit-application-requested");
-
-  // Something aborted the quit process.
-  if (cancelQuit.data)
-    return;
-
-  Services.startup.quit(Ci.nsIAppStartup.eRestart | Ci.nsIAppStartup.eAttemptQuit);
-}
-
-/**
- * Checks whether this blocklist element is valid for the current OS and ABI.
- * If the element has an "os" attribute then the current OS must appear in
- * its comma separated list for the element to be valid. Similarly for the
- * xpcomabi attribute.
- *
- * @param {Element} blocklistElement
- *        The blocklist element from an XML blocklist.
- * @returns {bool}
- *        Whether the entry matches the current OS.
- */
-function matchesOSABI(blocklistElement) {
-  let os = blocklistElement.getAttribute("os");
-  if (os) {
-    let choices = os.split(",");
-    if (choices.length > 0 && !choices.includes(gApp.OS))
-      return false;
-  }
-
-  let xpcomabi = blocklistElement.getAttribute("xpcomabi");
-  if (xpcomabi) {
-    let choices = xpcomabi.split(",");
-    if (choices.length > 0 && !choices.includes(gApp.XPCOMABI))
-      return false;
-  }
-
-  return true;
-}
-
-/**
- * Gets the current value of the locale.  It's possible for this preference to
- * be localized, so we have to do a little extra work here.  Similar code
- * exists in nsHttpHandler.cpp when building the UA string.
- *
- * @returns {string} The current requested locale.
- */
-function getLocale() {
-  return Services.locale.getRequestedLocale();
-}
-
 /* Get the distribution pref values, from defaults only */
 function getDistributionPrefValue(aPrefName) {
   return Services.prefs.getDefaultBranch(null).getCharPref(aPrefName, "default");
 }
 
 /**
  * Manages the Blocklist. The Blocklist is a representation of the contents of
  * blocklist.xml and allows us to remotely disable / re-enable blocklisted
@@ -428,93 +1021,62 @@ var Blocklist = {
     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);
 
     // 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));
+      entry.resolve(PluginBlocklist.getState(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,
 
-
-  /**
-   * Extension ID -> array of Version Ranges
-   * Each value in the version range array is a JS Object that has the
-   * following properties:
-   *   "minVersion"  The minimum version in a version range (default = 0)
-   *   "maxVersion"  The maximum version in a version range (default = *)
-   *   "targetApps"  Application ID -> array of Version Ranges
-   *                 (default = current application ID)
-   *                 Each value in the version range array is a JS Object that
-   *                 has the following properties:
-   *                   "minVersion"  The minimum version in a version range
-   *                                 (default = 0)
-   *                   "maxVersion"  The maximum version in a version range
-   *                                 (default = *)
-   */
-  _addonEntries: null,
-  _pluginEntries: null,
-
   shutdown() {
     Services.obs.removeObserver(this, "xpcom-shutdown");
     Services.prefs.removeObserver("extensions.blocklist.", this);
     Services.prefs.removeObserver(PREF_EM_LOGGING_ENABLED, this);
   },
 
   observe(aSubject, aTopic, aData) {
     switch (aTopic) {
     case "xpcom-shutdown":
       this.shutdown();
       break;
     case "profile-after-change":
-      // We're only called here on non-Desktop-Firefox, and use this opportunity to try to
-      // load the blocklist asynchronously. On desktop Firefox, we load the list from
+      // We're only called here on non-Desktop-Firefox, and use this opportunity to
+      // start gfx blocklist checks. On desktop Firefox, we do so from
       // nsBrowserGlue after sessionstore-windows-restored.
-      this.loadBlocklistAsync();
       this.checkForGfxBlocklistEntries();
       break;
     case "nsPref:changed":
       switch (aData) {
         case PREF_EM_LOGGING_ENABLED:
           gLoggingEnabled = Services.prefs.getBoolPref(PREF_EM_LOGGING_ENABLED, false);
           break;
         case PREF_BLOCKLIST_ENABLED:
           gBlocklistEnabled = Services.prefs.getBoolPref(PREF_BLOCKLIST_ENABLED, true);
-          // This is a bit messy. Especially in tests, but in principle also by users toggling
-          // this preference repeatedly, plugin loads could race with each other if we don't
-          // enforce that they are applied sequentially.
-          // So we only update once the previous `_blocklistUpdated` call finishes running.
-          let lastUpdate = this._lastUpdate || undefined;
-          let newUpdate = this._lastUpdate = (async () => {
-            await lastUpdate;
-            this._clear();
-            await this.loadBlocklistAsync();
-            await this._blocklistUpdated(null, null);
-            this.checkForGfxBlocklistEntries();
-            if (newUpdate == this._lastUpdate) {
-              delete this._lastUpdate;
-            }
-          })().catch(Cu.reportError);
+          // Re-run gfx initialization in case we're now enabled. If we were disabled,
+          // the kinto component will continue running, but we'll not do anything when
+          // updates happen. We'll stop initializing next time Firefox is restarted.
+          this.checkForGfxBlocklistEntries();
+          // We don't need to do this for plugin/addon items, they'll just start
+          // returning data the next time they're called (or not, as the case may be).
           break;
         case PREF_BLOCKLIST_LEVEL:
           gBlocklistLevel = Math.min(Services.prefs.getIntPref(PREF_BLOCKLIST_LEVEL, DEFAULT_LEVEL),
                                      MAX_BLOCK_LEVEL);
-          this._blocklistUpdated(null, null);
+          // FIXME need to do something here?
           break;
       }
       break;
     }
   },
 
   checkForGfxBlocklistEntries() {
     GfxBlocklist.checkForEntries();
@@ -530,67 +1092,17 @@ var Blocklist = {
    *        is used.
    * @param {string?} toolkitVersion
    *        The version of the toolkit we are checking in the blocklist.
    *        If this parameter is null, the version of the running toolkit
    *        is used.
    * @returns {integer} The STATE constant.
    */
   async getAddonBlocklistState(addon, appVersion, toolkitVersion) {
-    await this.loadBlocklistAsync();
-    return this._getAddonBlocklistState(addon, this._addonEntries,
-                                        appVersion, toolkitVersion);
-  },
-
-  /**
-   * Returns a matching blocklist entry for the given add-on, if one
-   * exists.
-   *
-   * @param {Addon} addon
-   *        The add-on object of the item to get the blocklist state for.
-   * @param {object[]} addonEntries
-   *        The add-on blocklist entries to compare against.
-   * @param {string?} appVersion
-   *        The application version to compare to, will use the current
-   *        version if null.
-   * @param {string?} toolkitVersion
-   *        The toolkit version to compare to, will use the current version if
-   *        null.
-   * @returns {object?}
-   *          A blocklist entry for this item, with `state` and `url`
-   *          properties indicating the block state and URL, if there is
-   *          a matching blocklist entry, or null otherwise.
-   */
-  _getAddonBlocklistEntry(addon, addonEntries, appVersion, toolkitVersion) {
-    if (!gBlocklistEnabled)
-      return null;
-
-    // Not all applications implement nsIXULAppInfo (e.g. xpcshell doesn't).
-    if (!appVersion && !gAppVersion)
-      return null;
-
-    if (!appVersion)
-      appVersion = gAppVersion;
-    if (!toolkitVersion)
-      toolkitVersion = gApp.platformVersion;
-
-    var blItem = this._findMatchingAddonEntry(addonEntries, addon);
-    if (!blItem)
-      return null;
-
-    for (let currentblItem of blItem.versions) {
-      if (currentblItem.includesItem(addon.version, appVersion, toolkitVersion)) {
-        return {
-          state: (currentblItem.severity >= gBlocklistLevel ?
-                  Ci.nsIBlocklistService.STATE_BLOCKED : Ci.nsIBlocklistService.STATE_SOFTBLOCKED),
-          url: blItem.blockID && this._createBlocklistURL(blItem.blockID),
-        };
-      }
-    }
-    return null;
+    return ExtensionBlocklist.getState(addon, appVersion, toolkitVersion);
   },
 
   /**
    * 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
    * otherwise.
 
@@ -604,101 +1116,17 @@ var Blocklist = {
    *        The version of the toolkit we are checking in the blocklist.
    *        If this parameter is null, the version of the running toolkit
    *        is used.
    * @returns {Promise<object?>}
    *        The blocklist entry for the add-on, if one exists, or null
    *        otherwise.
    */
   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.
-   *
-   * @param {Addon} addon
-   *        The add-on object of the item to get the blocklist state for.
-   * @param {object[]} addonEntries
-   *        The add-on blocklist entries to compare against.
-   * @param {string?} appVersion
-   *        The application version to compare to, will use the current
-   *        version if null.
-   * @param {string?} toolkitVersion
-   *        The toolkit version to compare to, will use the current version if
-   *        null.
-   * @returns {integer}
-   *        The blocklist state for the item, one of the STATE constants as
-   *        defined in nsIBlocklistService.
-   */
-  _getAddonBlocklistState(addon, addonEntries, appVersion, toolkitVersion) {
-    let entry = this._getAddonBlocklistEntry(addon, addonEntries, appVersion, toolkitVersion);
-    if (entry)
-      return entry.state;
-    return Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
-  },
-
-  /**
-   * Returns the set of prefs of the add-on stored in the blocklist file
-   * (probably to revert them on disabling).
-   * @param {Addon} addon
-   *        The add-on whose to-be-reset prefs are to be found.
-   * @returns {string[]}
-   *        An array of preference names.
-   */
-  _getAddonPrefs(addon) {
-    let entry = this._findMatchingAddonEntry(this._addonEntries, addon);
-    return entry.prefs.slice(0);
-  },
-
-  _findMatchingAddonEntry(aAddonEntries, aAddon) {
-    if (!aAddon)
-      return null;
-    // Returns true if the params object passes the constraints set by entry.
-    // (For every non-null property in entry, the same key must exist in
-    // params and value must be the same)
-    function checkEntry(entry, params) {
-      for (let [key, value] of Object.entries(entry)) {
-        if (value === null || value === undefined)
-          continue;
-        if (params[key]) {
-          if (value instanceof RegExp) {
-            if (!value.test(params[key])) {
-              return false;
-            }
-          } else if (value !== params[key]) {
-            return false;
-          }
-        } else {
-          return false;
-        }
-      }
-      return true;
-    }
-
-    let params = {};
-    for (let filter of EXTENSION_BLOCK_FILTERS) {
-      params[filter] = aAddon[filter];
-    }
-    if (params.creator)
-      params.creator = params.creator.name;
-    for (let entry of aAddonEntries) {
-      if (checkEntry(entry.attributes, params)) {
-         return entry;
-       }
-     }
-     return null;
-  },
-
-  _createBlocklistURL(id) {
-    let url = Services.urlFormatter.formatURLPref(PREF_BLOCKLIST_ITEM_URL);
-    return url.replace(/%blockID%/g, id);
+    return ExtensionBlocklist.getEntry(addon, appVersion, toolkitVersion);
   },
 
   notify(aTimer) {
     if (!gBlocklistEnabled)
       return;
 
     try {
       var dsURI = Services.prefs.getCharPref(PREF_BLOCKLIST_URL);
@@ -734,19 +1162,19 @@ var Blocklist = {
       pingCountVersion = 1;
     if (pingCountTotal < 1)
       pingCountTotal = 1;
 
     let replacements = {
       APP_ID: gAppID,
       PRODUCT: gApp.name,
       BUILD_ID: gApp.appBuildID,
-      BUILD_TARGET: gApp.OS + "_" + gABI,
+      BUILD_TARGET: gAppOS + "_" + gXPCOMABI,
       OS_VERSION: gOSVersion,
-      LOCALE: getLocale(),
+      LOCALE: Services.locale.getRequestedLocale(),
       CHANNEL: UpdateUtils.UpdateChannel,
       PLATFORM_VERSION: gApp.platformVersion,
       DISTRIBUTION: getDistributionPrefValue(PREF_APP_DISTRIBUTION),
       DISTRIBUTION_VERSION: getDistributionPrefValue(PREF_APP_DISTRIBUTION_VERSION),
       PING_COUNT: pingCountVersion,
       TOTAL_PING_COUNT: pingCountTotal,
       DAYS_SINCE_LAST_PING: daysSinceLastPing,
     };
@@ -848,35 +1276,16 @@ var Blocklist = {
     if (!responseXML || responseXML.documentElement.namespaceURI == XMLURI_PARSE_ERROR) {
       LOG("Blocklist::onXMLLoad: there was an error during load, we got invalid XML");
       return;
     }
 
     // Save current blocklist timestamp to pref.
     const lastModified = request.getResponseHeader("Last-Modified") || "";
     Services.prefs.setCharPref(PREF_BLOCKLIST_LAST_MODIFIED, lastModified);
-
-    if (!this.isLoaded) {
-      await this.loadBlocklistAsync();
-    }
-
-    var oldAddonEntries = this._addonEntries;
-    var oldPluginEntries = this._pluginEntries;
-
-    await this._loadBlocklistFromXML(responseXML);
-    // We don't inform the users when the graphics blocklist changed at runtime.
-    // However addons and plugins blocking status is refreshed.
-    this._blocklistUpdated(oldAddonEntries, oldPluginEntries);
-
-    try {
-      let path = OS.Path.join(OS.Constants.Path.profileDir, FILE_BLOCKLIST);
-      await OS.File.writeAtomic(path, request.responseText, {tmpPath: path + ".tmp"});
-    } catch (e) {
-      LOG("Blocklist::onXMLLoad: " + e);
-    }
   },
 
   onXMLError(aEvent) {
     try {
       var request = aEvent.target;
       // the following may throw (e.g. a local file or timeout)
       var status = request.status;
     } catch (e) {
@@ -894,719 +1303,22 @@ var Blocklist = {
     LOG("Blocklist:onError: There was an error loading the blocklist file\r\n" +
         statusText);
   },
 
   /**
    * Whether or not we've finished loading the blocklist.
    */
   get isLoaded() {
-    return this._addonEntries != null && this._pluginEntries != null;
-  },
-
-  /* Used for testing */
-  _clear() {
-    this._addonEntries = null;
-    this._pluginEntries = null;
-    delete this._loadPromise;
-  },
-
-  /**
-   * Trigger loading the blocklist content asynchronously.
-   */
-  async loadBlocklistAsync() {
-    if (this.isLoaded) {
-      return;
-    }
-    if (!this._loadPromise) {
-      this._loadPromise = this._loadBlocklistAsyncInternal();
-    }
-    await this._loadPromise;
-  },
-
-  async _loadBlocklistAsyncInternal() {
-    try {
-      // Get the path inside the try...catch because there's no profileDir in e.g. xpcshell tests.
-      let profFile = FileUtils.getFile(KEY_PROFILEDIR, [FILE_BLOCKLIST]);
-      await this._loadFileInternal(profFile);
-      return;
-    } catch (e) {
-      LOG("Blocklist::loadBlocklistAsync: Failed to load XML file " + e);
-    }
-
-    var appFile = FileUtils.getFile(KEY_APPDIR, [FILE_BLOCKLIST]);
-    try {
-      await this._loadFileInternal(appFile);
-      return;
-    } catch (e) {
-      LOG("Blocklist::loadBlocklistAsync: Failed to load XML file " + e);
-    }
-
-    LOG("Blocklist::loadBlocklistAsync: no XML File found");
-    // Neither file is present, so we just add empty lists, to avoid JS errors fetching
-    // blocklist information otherwise.
-    this._addonEntries = [];
-    this._pluginEntries = [];
-  },
-
-  async _loadFileInternal(file) {
-    if (this.isLoaded) {
-      return;
-    }
-
-    if (!gBlocklistEnabled) {
-      LOG("Blocklist::_loadFileInternal: blocklist is disabled");
-      return;
-    }
-
-    let xmlDoc = await new Promise((resolve, reject) => {
-      let request = new XMLHttpRequest();
-      request.open("GET", Services.io.newFileURI(file).spec, true);
-      request.overrideMimeType("text/xml");
-      request.addEventListener("error", reject);
-      request.addEventListener("load", function() {
-        let {status} = request;
-        if (status != 200 && status != 0) {
-          LOG("_loadFileInternal: there was an error during load, got status: " + status);
-          reject(new Error("Couldn't load blocklist file"));
-          return;
-        }
-        let doc = request.responseXML;
-        if (doc.documentElement.namespaceURI != XMLURI_BLOCKLIST) {
-          LOG("Blocklist::_loadBlocklistFromString: aborting due to incorrect " +
-              "XML Namespace.\nExpected: " + XMLURI_BLOCKLIST + "\n" +
-              "Received: " + doc.documentElement.namespaceURI);
-          reject(new Error("Local blocklist file has the wrong namespace!"));
-          return;
-        }
-        resolve(doc);
-      });
-      request.send(null);
-    });
-
-    await new Promise(resolve => {
-      ChromeUtils.idleDispatch(async () => {
-        if (!this.isLoaded) {
-          await this._loadBlocklistFromXML(xmlDoc);
-        }
-        resolve();
-      });
-    });
-  },
-
-  async _loadBlocklistFromXML(doc) {
-    this._addonEntries = [];
-    this._pluginEntries = [];
-    try {
-      var children = doc.documentElement.children;
-      for (let element of children) {
-        switch (element.localName) {
-        case "emItems":
-          this._addonEntries = await this._processItemNodes(element.children, "emItem",
-                                                            this._handleEmItemNode);
-          break;
-        case "pluginItems":
-          this._pluginEntries = await this._processItemNodes(element.children, "pluginItem",
-                                                             this._handlePluginItemNode);
-          break;
-        default:
-          LOG("Blocklist::_loadBlocklistFromXML: ignored entries " + element.localName);
-        }
-      }
-    } catch (e) {
-      LOG("Blocklist::_loadBlocklistFromXML: Error constructing blocklist " + e);
-    }
-    // Dispatch to mainthread because consumers may try to construct nsIPluginHost
-    // again based on this notification, while we were called from nsIPluginHost
-    // anyway, leading to re-entrancy.
-    Services.tm.dispatchToMainThread(function() {
-      Services.obs.notifyObservers(null, "blocklist-loaded");
-    });
-  },
-
-  async _processItemNodes(items, itemName, handler) {
-    var result = [];
-    let deadline = await new Promise(ChromeUtils.idleDispatch);
-    for (let item of items) {
-      if (item.localName == itemName) {
-        handler(item, result);
-      }
-      if (!deadline || deadline.didTimeout || deadline.timeRemaining() < 1) {
-        deadline = await new Promise(ChromeUtils.idleDispatch);
-      }
-    }
-    return result;
-  },
-
-  _handleEmItemNode(blocklistElement, result) {
-    if (!matchesOSABI(blocklistElement))
-      return;
-
-    let blockEntry = {
-      versions: [],
-      prefs: [],
-      blockID: null,
-      attributes: {},
-      // Atleast one of EXTENSION_BLOCK_FILTERS must get added to attributes
-    };
-
-    for (let filter of EXTENSION_BLOCK_FILTERS) {
-      let attr = blocklistElement.getAttribute(filter);
-      if (attr) {
-        // Any filter starting with '/' is interpreted as a regex. So if an attribute
-        // starts with a '/' it must be checked via a regex.
-        if (attr.startsWith("/")) {
-          let lastSlash = attr.lastIndexOf("/");
-          let pattern = attr.slice(1, lastSlash);
-          let flags = attr.slice(lastSlash + 1);
-          blockEntry.attributes[filter] = new RegExp(pattern, flags);
-        } else {
-          blockEntry.attributes[filter] = attr;
-        }
-      }
-    }
-
-    var children = blocklistElement.children;
-
-    for (let childElement of children) {
-      let localName = childElement.localName;
-      if (localName == "prefs" && childElement.hasChildNodes) {
-        let prefElements = childElement.children;
-        for (let prefElement of prefElements) {
-          if (prefElement.localName == "pref") {
-            blockEntry.prefs.push(prefElement.textContent);
-          }
-        }
-      } else if (localName == "versionRange") {
-        blockEntry.versions.push(new BlocklistItemData(childElement));
-      }
-    }
-    // if only the extension ID is specified block all versions of the
-    // extension for the current application.
-    if (blockEntry.versions.length == 0)
-      blockEntry.versions.push(new BlocklistItemData(null));
-
-    blockEntry.blockID = blocklistElement.getAttribute("blockID");
-
-    result.push(blockEntry);
-  },
-
-  _handlePluginItemNode(blocklistElement, result) {
-    if (!matchesOSABI(blocklistElement))
-      return;
-
-    let children = blocklistElement.children;
-    var blockEntry = {
-      matches: {},
-      versions: [],
-      blockID: null,
-      infoURL: null,
-    };
-    var hasMatch = false;
-    for (let childElement of children) {
-      switch (childElement.localName) {
-        case "match":
-          var name = childElement.getAttribute("name");
-          var exp = childElement.getAttribute("exp");
-          try {
-            blockEntry.matches[name] = new RegExp(exp, "m");
-            hasMatch = true;
-          } catch (e) {
-            // Ignore invalid regular expressions
-          }
-          break;
-        case "versionRange":
-          blockEntry.versions.push(new BlocklistItemData(childElement));
-          break;
-        case "infoURL":
-          blockEntry.infoURL = childElement.textContent;
-          break;
-      }
-    }
-    // Plugin entries require *something* to match to an actual plugin
-    if (!hasMatch)
-      return;
-    // Add a default versionRange if there wasn't one specified
-    if (blockEntry.versions.length == 0)
-      blockEntry.versions.push(new BlocklistItemData(null));
-
-    blockEntry.blockID = blocklistElement.getAttribute("blockID");
-
-    result.push(blockEntry);
+    return PluginBlocklist.isLoaded;
   },
 
   /* See nsIBlocklistService */
   async getPluginBlocklistState(plugin, appVersion, toolkitVersion) {
-    if (AppConstants.platform == "android") {
-      return Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
-    }
-    await this.loadBlocklistAsync();
-    return this._getPluginBlocklistState(plugin, this._pluginEntries,
-                                         appVersion, toolkitVersion);
-  },
-
-  /**
-   * Private helper to get the blocklist entry for a plugin given a set of
-   * blocklist entries and versions.
-   *
-   * @param {nsIPluginTag} plugin
-   *        The nsIPluginTag to get the blocklist state for.
-   * @param {object[]} pluginEntries
-   *        The plugin blocklist entries to compare against.
-   * @param {string?} appVersion
-   *        The application version to compare to, will use the current
-   *        version if null.
-   * @param {string?} toolkitVersion
-   *        The toolkit version to compare to, will use the current version if
-   *        null.
-   * @returns {object?}
-   *        {entry: blocklistEntry, version: blocklistEntryVersion},
-   *        or null if there is no matching entry.
-   */
-  _getPluginBlocklistEntry(plugin, pluginEntries, appVersion, toolkitVersion) {
-    if (!gBlocklistEnabled)
-      return null;
-
-    // Not all applications implement nsIXULAppInfo (e.g. xpcshell doesn't).
-    if (!appVersion && !gAppVersion)
-      return Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
-
-    if (!appVersion)
-      appVersion = gAppVersion;
-    if (!toolkitVersion)
-      toolkitVersion = gApp.platformVersion;
-
-    const pluginProperties = {
-      description: plugin.description,
-      filename: plugin.filename,
-      name: plugin.name,
-      version: plugin.version,
-    };
-    for (var blockEntry of pluginEntries) {
-      var matchFailed = false;
-      for (var name in blockEntry.matches) {
-        let pluginProperty = pluginProperties[name];
-        if (typeof(pluginProperty) !== "string" ||
-            !blockEntry.matches[name].test(pluginProperty)) {
-          matchFailed = true;
-          break;
-        }
-      }
-
-      if (matchFailed)
-        continue;
-
-      for (let blockEntryVersion of blockEntry.versions) {
-        if (blockEntryVersion.includesItem(pluginProperties.version, appVersion,
-                                           toolkitVersion)) {
-          return {entry: blockEntry, version: blockEntryVersion};
-        }
-      }
-    }
-
-    return null;
-  },
-
-  /**
-   * Private version of getPluginBlocklistState that allows the caller to pass in
-   * the plugin blocklist entries.
-   *
-   * @param {nsIPluginTag} plugin
-   *        The nsIPluginTag to get the blocklist state for.
-   * @param {object[]} pluginEntries
-   *        The plugin blocklist entries to compare against.
-   * @param {string?} appVersion
-   *        The application version to compare to, will use the current
-   *        version if null.
-   * @param {string?} toolkitVersion
-   *        The toolkit version to compare to, will use the current version if
-   *        null.
-   * @returns {integer}
-   *        The blocklist state for the item, one of the STATE constants as
-   *        defined in nsIBlocklistService.
-   */
-  _getPluginBlocklistState(plugin, pluginEntries, appVersion, toolkitVersion) {
-
-    let r = this._getPluginBlocklistEntry(plugin, pluginEntries,
-                                          appVersion, toolkitVersion);
-    if (!r) {
-      return Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
-    }
-
-    let {version: blockEntryVersion} = r;
-
-    if (blockEntryVersion.severity >= gBlocklistLevel)
-      return Ci.nsIBlocklistService.STATE_BLOCKED;
-    if (blockEntryVersion.severity == SEVERITY_OUTDATED) {
-      let vulnerabilityStatus = blockEntryVersion.vulnerabilityStatus;
-      if (vulnerabilityStatus == VULNERABILITYSTATUS_UPDATE_AVAILABLE)
-        return Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE;
-      if (vulnerabilityStatus == VULNERABILITYSTATUS_NO_UPDATE)
-        return Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE;
-      return Ci.nsIBlocklistService.STATE_OUTDATED;
-    }
-    return Ci.nsIBlocklistService.STATE_SOFTBLOCKED;
+    return PluginBlocklist.getState(plugin, appVersion, toolkitVersion);
   },
 
   async getPluginBlockURL(plugin) {
-    await this.loadBlocklistAsync();
-
-    let r = this._getPluginBlocklistEntry(plugin, this._pluginEntries);
-    if (!r) {
-      return null;
-    }
-    let blockEntry = r.entry;
-    if (!blockEntry.blockID) {
-      return null;
-    }
-
-    return blockEntry.infoURL || this._createBlocklistURL(blockEntry.blockID);
-  },
-
-  _notifyObserversBlocklistUpdated() {
-    Services.obs.notifyObservers(this, "blocklist-updated");
-  },
-
-  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"];
-    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_NOT_BLOCKED;
-      }
-      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;
-
-      // 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, this._pluginEntries);
-      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: await this.getPluginBlockURL(plugin),
-          });
-        }
-      }
-    }
-
-    if (addonList.length == 0) {
-      this._notifyObserversBlocklistUpdated();
-      return;
-    }
-
-    if ("@mozilla.org/addons/blocklist-prompt;1" in Cc) {
-      try {
-        let blockedPrompter = Cc["@mozilla.org/addons/blocklist-prompt;1"]
-                               .getService().wrappedJSObject;
-        blockedPrompter.prompt(addonList);
-      } catch (e) {
-        LOG(e);
-      }
-      this._notifyObserversBlocklistUpdated();
-      return;
-    }
-
-    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 (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");
-    };
-
-    Services.obs.addObserver(applyBlocklistChanges, "addon-blocklist-closed");
-
-    if (Services.prefs.getBoolPref(PREF_BLOCKLIST_SUPPRESSUI, false)) {
-      applyBlocklistChanges();
-      return;
-    }
-
-    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);
+    return PluginBlocklist.getURL(plugin);
   },
 };
 
-/*
- * Helper for constructing a blocklist.
- */
-function BlocklistItemData(versionRangeElement) {
-  this.targetApps = {};
-  let foundTarget = false;
-  this.severity = DEFAULT_SEVERITY;
-  this.vulnerabilityStatus = VULNERABILITYSTATUS_NONE;
-  if (versionRangeElement) {
-    let versionRange = this.getBlocklistVersionRange(versionRangeElement);
-    this.minVersion = versionRange.minVersion;
-    this.maxVersion = versionRange.maxVersion;
-    if (versionRangeElement.hasAttribute("severity"))
-      this.severity = versionRangeElement.getAttribute("severity");
-    if (versionRangeElement.hasAttribute("vulnerabilitystatus")) {
-      this.vulnerabilityStatus = versionRangeElement.getAttribute("vulnerabilitystatus");
-    }
-    for (let targetAppElement of versionRangeElement.children) {
-      if (targetAppElement.localName == "targetApplication") {
-        foundTarget = true;
-        // default to the current application if id is not provided.
-        let appID = targetAppElement.id || gAppID;
-        this.targetApps[appID] = this.getBlocklistAppVersions(targetAppElement);
-      }
-    }
-  } else {
-    this.minVersion = this.maxVersion = null;
-  }
-
-  // Default to all versions of the current application when no targetApplication
-  // elements were found
-  if (!foundTarget)
-    this.targetApps[gAppID] = [{minVersion: null, maxVersion: null}];
-}
-
-BlocklistItemData.prototype = {
-  /**
-   * Tests if a version of an item is included in the version range and target
-   * application information represented by this BlocklistItemData using the
-   * provided application and toolkit versions.
-   * @param {string} version
-   *        The version of the item being tested.
-   * @param {string} appVersion
-   *        The application version to test with.
-   * @param {string} toolkitVersion
-   *        The toolkit version to test with.
-   * @returns {boolean}
-   *        True if the version range covers the item version and application
-   *        or toolkit version.
-   */
-  includesItem(version, appVersion, toolkitVersion) {
-    // Some platforms have no version for plugins, these don't match if there
-    // was a min/maxVersion provided
-    if (!version && (this.minVersion || this.maxVersion))
-      return false;
-
-    // Check if the item version matches
-    if (!this.matchesRange(version, this.minVersion, this.maxVersion))
-      return false;
-
-    // Check if the application version matches
-    if (this.matchesTargetRange(gAppID, appVersion))
-      return true;
-
-    // Check if the toolkit version matches
-    return this.matchesTargetRange(TOOLKIT_ID, toolkitVersion);
-  },
-
-  /**
-   * Checks if a version is higher than or equal to the minVersion (if provided)
-   * and lower than or equal to the maxVersion (if provided).
-   * @param {string} version
-   *        The version to test.
-   * @param {string?} minVersion
-   *        The minimum version. If null it is assumed that version is always
-   *        larger.
-   * @param {string?} maxVersion
-   *        The maximum version. If null it is assumed that version is always
-   *        smaller.
-   * @returns {boolean}
-   *        Whether the item matches the range.
-   */
-  matchesRange(version, minVersion, maxVersion) {
-    if (minVersion && Services.vc.compare(version, minVersion) < 0)
-      return false;
-    if (maxVersion && Services.vc.compare(version, maxVersion) > 0)
-      return false;
-    return true;
-  },
-
-  /**
-   * Tests if there is a matching range for the given target application id and
-   * version.
-   * @param {string} appID
-   *        The application ID to test for, may be for an application or toolkit
-   * @param {string} appVersion
-   *        The version of the application to test for.
-   * @returns {boolean}
-   *        True if this version range covers the application version given.
-   */
-  matchesTargetRange(appID, appVersion) {
-    var blTargetApp = this.targetApps[appID];
-    if (!blTargetApp)
-      return false;
-
-    for (let app of blTargetApp) {
-      if (this.matchesRange(appVersion, app.minVersion, app.maxVersion))
-        return true;
-    }
-
-    return false;
-  },
-
-  /**
-   * Retrieves a version range (e.g. minVersion and maxVersion) for a
-   * blocklist item's targetApplication element.
-   * @param {Element} targetAppElement
-   *        A targetApplication blocklist element.
-   * @returns {object[]}
-   *        An array of JS objects with the following properties:
-   *          "minVersion"  The minimum version in a version range (default = null).
-   *          "maxVersion"  The maximum version in a version range (default = null).
-   */
-  getBlocklistAppVersions(targetAppElement) {
-    var appVersions = [ ];
-
-    if (targetAppElement) {
-      for (let versionRangeElement of targetAppElement.children) {
-        if (versionRangeElement.localName == "versionRange") {
-          appVersions.push(this.getBlocklistVersionRange(versionRangeElement));
-        }
-      }
-    }
-    // return minVersion = null and maxVersion = null if no specific versionRange
-    // elements were found
-    if (appVersions.length == 0)
-      appVersions.push({minVersion: null, maxVersion: null});
-    return appVersions;
-  },
-
-  /**
-   * Retrieves a version range (e.g. minVersion and maxVersion) for a blocklist
-   * versionRange element.
-   *
-   * @param {Element} versionRangeElement
-   *        The versionRange blocklist element.
-   *
-   * @returns {Object}
-   *        A JS object with the following properties:
-   *          "minVersion"  The minimum version in a version range (default = null).
-   *          "maxVersion"  The maximum version in a version range (default = null).
-   */
-  getBlocklistVersionRange(versionRangeElement) {
-    // getAttribute returns null if the attribute is not present.
-    let minVersion = versionRangeElement.getAttribute("minVersion");
-    let maxVersion = versionRangeElement.getAttribute("maxVersion");
-
-    return { minVersion, maxVersion };
-  }
-};
-
 Blocklist._init();
--- a/toolkit/mozapps/extensions/internal/AddonTestUtils.jsm
+++ b/toolkit/mozapps/extensions/internal/AddonTestUtils.jsm
@@ -727,16 +727,69 @@ var AddonTestUtils = {
    */
   overrideBlocklist(addons) {
     let mock = new MockBlocklist(addons);
     mock.register();
     return mock;
   },
 
   /**
+   * Load the following data into the *real* blocklist providers.
+   * While `overrideBlocklist` replaces the blocklist entirely with a mock
+   * that returns dummy data, this method instead loads data into the actual
+   * blocklist, fires update methods as would happen if this data came from
+   * an actual blocklist update, etc.
+   *
+   * @param {nsIFile} dir
+   *        The directory in which the files live.
+   * @param {string} prefix
+   *        a prefix for the files which ought to be loaded.
+   *        This method will suffix -extensions.json and -plugins.json
+   *        to the prefix it is given, and attempt to load both.
+   *        Insofar as either exists, their data will be dumped into
+   *        the respective store, and the respective update handlers
+   *        will be called.
+   */
+  async loadBlocklistData(dir, prefix) {
+    const bsPass = ChromeUtils.import("resource://gre/modules/Blocklist.jsm", {});
+    const blocklistMapping = {
+      "extensions": bsPass.ExtensionBlocklist,
+      "plugins": bsPass.PluginBlocklist,
+    };
+
+    for (const [fileSuffix, blocklistObj] of Object.entries(blocklistMapping)) {
+      const fileName = prefix + "-" + fileSuffix + ".json";
+      let jsonStr = await OS.File.read(OS.Path.join(dir.path, fileName), {encoding: "UTF-8"}).catch(() => "");
+      if (!jsonStr) {
+        continue;
+      }
+      Cu.reportError("Loading " + prefix + " " + fileSuffix);
+
+      let newData = JSON.parse(jsonStr);
+      if (!Array.isArray(newData)) {
+        throw new Error("Expected an array of new items to put in the " + fileSuffix + " blocklist!");
+      }
+      for (let item of newData) {
+        if (!item.id) {
+          item.id = uuidGen.generateUUID().number.slice(1, -1);
+        }
+        if (!item.last_modified) {
+          item.last_modified = Date.now();
+        }
+      }
+      let collection = await blocklistObj._client.openCollection();
+      await collection.clear();
+      await collection.loadDump(newData);
+      // We manually call _onUpdate... which is evil, but at the moment kinto doesn't have
+      // a better abstraction unless you want to mock your own http server to do the update.
+      await blocklistObj._onUpdate();
+    }
+  },
+
+  /**
    * Starts up the add-on manager as if it was started by the application.
    *
    * @param {string} [newVersion]
    *        If provided, the application version is changed to this string
    *        before the AddonManager is started.
    */
   async promiseStartupManager(newVersion) {
     if (this.addonIntegrationService)
--- a/toolkit/mozapps/extensions/test/browser/browser_CTP_plugins.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_CTP_plugins.js
@@ -1,23 +1,24 @@
 const gHttpTestRoot = "http://127.0.0.1:8888/" + RELATIVE_DIR + "/";
 
 function updateBlocklist(aCallback) {
   var blocklistNotifier = Cc["@mozilla.org/extensions/blocklist;1"]
                           .getService(Ci.nsITimerCallback);
   var observer = function() {
-    Services.obs.removeObserver(observer, "blocklist-updated");
+    Services.obs.removeObserver(observer, "plugin-blocklist-updated");
     SimpleTest.executeSoon(aCallback);
   };
-  Services.obs.addObserver(observer, "blocklist-updated");
+  Services.obs.addObserver(observer, "plugin-blocklist-updated");
   blocklistNotifier.notify(null);
 }
 
 var _originalBlocklistURL = null;
 function setAndUpdateBlocklist(aURL, aCallback) {
+  // FIXME needs to change blocklist differently.
   if (!_originalBlocklistURL) {
     _originalBlocklistURL = Services.prefs.getCharPref("extensions.blocklist.url");
   }
   Services.prefs.setCharPref("extensions.blocklist.url", aURL);
   updateBlocklist(aCallback);
 }
 
 function resetBlocklist(aCallback) {
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_bug449027_app-extensions.json
@@ -0,0 +1,336 @@
+[
+  {
+    "_comment": "Always blocked",
+    "guid": "test_bug449027_2@tests.mozilla.org",
+    "versionRange": []
+  },
+  {
+    "_comment": "Always blocked",
+    "guid": "test_bug449027_3@tests.mozilla.org",
+    "versionRange": [
+      {}
+    ]
+  },
+  {
+    "_comment": "Not blocked since neither version range matches",
+    "guid": "test_bug449027_4@tests.mozilla.org",
+    "versionRange": [
+      {
+        "minVersion": "6"
+      },
+      {
+        "maxVersion": "4"
+      }
+    ]
+  },
+  {
+    "_comment": "Invalid version range, should not block",
+    "guid": "test_bug449027_5@tests.mozilla.org",
+    "versionRange": [
+      {
+        "maxVersion": "4",
+        "minVersion": "6"
+      }
+    ]
+  },
+  {
+    "_comment": "Should block all of these",
+    "guid": "test_bug449027_6@tests.mozilla.org",
+    "versionRange": [
+      {
+        "maxVersion": "8",
+        "minVersion": "7"
+      },
+      {
+        "maxVersion": "6",
+        "minVersion": "5"
+      },
+      {
+        "maxVersion": "4"
+      }
+    ]
+  },
+  {
+    "guid": "test_bug449027_7@tests.mozilla.org",
+    "versionRange": [
+      {
+        "maxVersion": "4"
+      },
+      {
+        "maxVersion": "5",
+        "minVersion": "4"
+      },
+      {
+        "maxVersion": "7",
+        "minVersion": "6"
+      }
+    ]
+  },
+  {
+    "guid": "test_bug449027_8@tests.mozilla.org",
+    "versionRange": [
+      {
+        "maxVersion": "2",
+        "minVersion": "2"
+      },
+      {
+        "maxVersion": "6",
+        "minVersion": "4"
+      },
+      {
+        "maxVersion": "8",
+        "minVersion": "7"
+      }
+    ]
+  },
+  {
+    "guid": "test_bug449027_9@tests.mozilla.org",
+    "versionRange": [
+      {
+        "minVersion": "4"
+      }
+    ]
+  },
+  {
+    "guid": "test_bug449027_10@tests.mozilla.org",
+    "versionRange": [
+      {
+        "minVersion": "5"
+      }
+    ]
+  },
+  {
+    "guid": "test_bug449027_11@tests.mozilla.org",
+    "versionRange": [
+      {
+        "maxVersion": "6"
+      }
+    ]
+  },
+  {
+    "guid": "test_bug449027_12@tests.mozilla.org",
+    "versionRange": [
+      {
+        "maxVersion": "5"
+      }
+    ]
+  },
+  {
+    "_comment": "This should block all versions for any application",
+    "guid": "test_bug449027_13@tests.mozilla.org",
+    "versionRange": [
+      {
+        "targetApplication": [
+          {}
+        ]
+      }
+    ]
+  },
+  {
+    "_comment": "Shouldn't block",
+    "guid": "test_bug449027_14@tests.mozilla.org",
+    "versionRange": [
+      {
+        "targetApplication": [
+          {
+            "guid": "foo@bar.com"
+          }
+        ]
+      }
+    ]
+  },
+  {
+    "_comment": "Should block for any version of the app",
+    "guid": "test_bug449027_15@tests.mozilla.org",
+    "versionRange": [
+      {
+        "targetApplication": [
+          {
+            "guid": "xpcshell@tests.mozilla.org"
+          }
+        ]
+      }
+    ]
+  },
+  {
+    "_comment": "Should still block",
+    "guid": "test_bug449027_16@tests.mozilla.org",
+    "versionRange": [
+      {
+        "targetApplication": [
+          {
+            "guid": "xpcshell@tests.mozilla.org"
+          }
+        ]
+      }
+    ]
+  },
+  {
+    "_comment": "Not blocked since neither version range matches",
+    "guid": "test_bug449027_17@tests.mozilla.org",
+    "versionRange": [
+      {
+        "targetApplication": [
+          {
+            "guid": "xpcshell@tests.mozilla.org",
+            "minVersion": "4"
+          },
+          {
+            "guid": "xpcshell@tests.mozilla.org",
+            "maxVersion": "2"
+          }
+        ]
+      }
+    ]
+  },
+  {
+    "_comment": "Invalid version range, should not block",
+    "guid": "test_bug449027_18@tests.mozilla.org",
+    "versionRange": [
+      {
+        "targetApplication": [
+          {
+            "guid": "xpcshell@tests.mozilla.org",
+            "maxVersion": "4",
+            "minVersion": "6"
+          }
+        ]
+      }
+    ]
+  },
+  {
+    "_comment": "Should block all of these",
+    "guid": "test_bug449027_19@tests.mozilla.org",
+    "versionRange": [
+      {
+        "targetApplication": [
+          {
+            "guid": "foo@bar.com"
+          },
+          {
+            "guid": "xpcshell@tests.mozilla.org",
+            "maxVersion": "6",
+            "minVersion": "5"
+          },
+          {
+            "guid": "xpcshell@tests.mozilla.org",
+            "maxVersion": "4",
+            "minVersion": "3"
+          },
+          {
+            "guid": "xpcshell@tests.mozilla.org",
+            "maxVersion": "2"
+          }
+        ]
+      }
+    ]
+  },
+  {
+    "guid": "test_bug449027_20@tests.mozilla.org",
+    "versionRange": [
+      {
+        "targetApplication": [
+          {
+            "guid": "xpcshell@tests.mozilla.org",
+            "maxVersion": "2"
+          },
+          {
+            "guid": "xpcshell@tests.mozilla.org",
+            "maxVersion": "3",
+            "minVersion": "2"
+          },
+          {
+            "guid": "xpcshell@tests.mozilla.org",
+            "maxVersion": "5",
+            "minVersion": "4"
+          },
+          {
+            "guid": "foo@bar.com"
+          }
+        ]
+      }
+    ]
+  },
+  {
+    "guid": "test_bug449027_21@tests.mozilla.org",
+    "versionRange": [
+      {
+        "targetApplication": [
+          {
+            "guid": "xpcshell@tests.mozilla.org",
+            "maxVersion": "1",
+            "minVersion": "1"
+          },
+          {
+            "guid": "xpcshell@tests.mozilla.org",
+            "maxVersion": "4",
+            "minVersion": "2"
+          },
+          {
+            "guid": "xpcshell@tests.mozilla.org",
+            "maxVersion": "6",
+            "minVersion": "5"
+          }
+        ]
+      }
+    ]
+  },
+  {
+    "guid": "test_bug449027_22@tests.mozilla.org",
+    "versionRange": [
+      {
+        "targetApplication": [
+          {
+            "guid": "foo@bar.com"
+          },
+          {
+            "guid": "xpcshell@tests.mozilla.org",
+            "minVersion": "3"
+          }
+        ]
+      }
+    ]
+  },
+  {
+    "guid": "test_bug449027_23@tests.mozilla.org",
+    "versionRange": [
+      {
+        "targetApplication": [
+          {
+            "guid": "xpcshell@tests.mozilla.org",
+            "minVersion": "2"
+          },
+          {
+            "guid": "foo@bar.com"
+          }
+        ]
+      }
+    ]
+  },
+  {
+    "guid": "test_bug449027_24@tests.mozilla.org",
+    "versionRange": [
+      {
+        "targetApplication": [
+          {
+            "guid": "xpcshell@tests.mozilla.org",
+            "maxVersion": "3"
+          }
+        ]
+      }
+    ]
+  },
+  {
+    "guid": "test_bug449027_25@tests.mozilla.org",
+    "versionRange": [
+      {
+        "targetApplication": [
+          {
+            "guid": "xpcshell@tests.mozilla.org",
+            "maxVersion": "4"
+          }
+        ]
+      }
+    ]
+  }
+]
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_bug449027_app-plugins.json
@@ -0,0 +1,336 @@
+[
+  {
+    "_comment": "Always blocked",
+    "matchName": "^test_bug449027_2$",
+    "versionRange": []
+  },
+  {
+    "_comment": "Always blocked",
+    "matchName": "^test_bug449027_3$",
+    "versionRange": [
+      {}
+    ]
+  },
+  {
+    "_comment": "Not blocked since neither version range matches",
+    "matchName": "^test_bug449027_4$",
+    "versionRange": [
+      {
+        "minVersion": "6"
+      },
+      {
+        "maxVersion": "4"
+      }
+    ]
+  },
+  {
+    "_comment": "Invalid version range, should not block",
+    "matchName": "^test_bug449027_5$",
+    "versionRange": [
+      {
+        "maxVersion": "4",
+        "minVersion": "6"
+      }
+    ]
+  },
+  {
+    "_comment": "Should block all of these",
+    "matchName": "^test_bug449027_6$",
+    "versionRange": [
+      {
+        "maxVersion": "8",
+        "minVersion": "7"
+      },
+      {
+        "maxVersion": "6",
+        "minVersion": "5"
+      },
+      {
+        "maxVersion": "4"
+      }
+    ]
+  },
+  {
+    "matchName": "^test_bug449027_7$",
+    "versionRange": [
+      {
+        "maxVersion": "4"
+      },
+      {
+        "maxVersion": "5",
+        "minVersion": "4"
+      },
+      {
+        "maxVersion": "7",
+        "minVersion": "6"
+      }
+    ]
+  },
+  {
+    "matchName": "^test_bug449027_8$",
+    "versionRange": [
+      {
+        "maxVersion": "2",
+        "minVersion": "2"
+      },
+      {
+        "maxVersion": "6",
+        "minVersion": "4"
+      },
+      {
+        "maxVersion": "8",
+        "minVersion": "7"
+      }
+    ]
+  },
+  {
+    "matchName": "^test_bug449027_9$",
+    "versionRange": [
+      {
+        "minVersion": "4"
+      }
+    ]
+  },
+  {
+    "matchName": "^test_bug449027_10$",
+    "versionRange": [
+      {
+        "minVersion": "5"
+      }
+    ]
+  },
+  {
+    "matchName": "^test_bug449027_11$",
+    "versionRange": [
+      {
+        "maxVersion": "6"
+      }
+    ]
+  },
+  {
+    "matchName": "^test_bug449027_12$",
+    "versionRange": [
+      {
+        "maxVersion": "5"
+      }
+    ]
+  },
+  {
+    "_comment": "This should block all versions for any application",
+    "matchName": "^test_bug449027_13$",
+    "versionRange": [
+      {
+        "targetApplication": [
+          {}
+        ]
+      }
+    ]
+  },
+  {
+    "_comment": "Shouldn't block",
+    "matchName": "^test_bug449027_14$",
+    "versionRange": [
+      {
+        "targetApplication": [
+          {
+            "guid": "foo@bar.com"
+          }
+        ]
+      }
+    ]
+  },
+  {
+    "_comment": "Should block for any version of the app",
+    "matchName": "^test_bug449027_15$",
+    "versionRange": [
+      {
+        "targetApplication": [
+          {
+            "guid": "xpcshell@tests.mozilla.org"
+          }
+        ]
+      }
+    ]
+  },
+  {
+    "_comment": "Should still block",
+    "matchName": "^test_bug449027_16$",
+    "versionRange": [
+      {
+        "targetApplication": [
+          {
+            "guid": "xpcshell@tests.mozilla.org"
+          }
+        ]
+      }
+    ]
+  },
+  {
+    "_comment": "Not blocked since neither version range matches",
+    "matchName": "^test_bug449027_17$",
+    "versionRange": [
+      {
+        "targetApplication": [
+          {
+            "guid": "xpcshell@tests.mozilla.org",
+            "minVersion": "4"
+          },
+          {
+            "guid": "xpcshell@tests.mozilla.org",
+            "maxVersion": "2"
+          }
+        ]
+      }
+    ]
+  },
+  {
+    "_comment": "Invalid version range, should not block",
+    "matchName": "^test_bug449027_18$",
+    "versionRange": [
+      {
+        "targetApplication": [
+          {
+            "guid": "xpcshell@tests.mozilla.org",
+            "maxVersion": "4",
+            "minVersion": "6"
+          }
+        ]
+      }
+    ]
+  },
+  {
+    "_comment": "Should block all of these",
+    "matchName": "^test_bug449027_19$",
+    "versionRange": [
+      {
+        "targetApplication": [
+          {
+            "guid": "foo@bar.com"
+          },
+          {
+            "guid": "xpcshell@tests.mozilla.org",
+            "maxVersion": "6",
+            "minVersion": "5"
+          },
+          {
+            "guid": "xpcshell@tests.mozilla.org",
+            "maxVersion": "4",
+            "minVersion": "3"
+          },
+          {
+            "guid": "xpcshell@tests.mozilla.org",
+            "maxVersion": "2"
+          }
+        ]
+      }
+    ]
+  },
+  {
+    "matchName": "^test_bug449027_20$",
+    "versionRange": [
+      {
+        "targetApplication": [
+          {
+            "guid": "xpcshell@tests.mozilla.org",
+            "maxVersion": "2"
+          },
+          {
+            "guid": "xpcshell@tests.mozilla.org",
+            "maxVersion": "3",
+            "minVersion": "2"
+          },
+          {
+            "guid": "xpcshell@tests.mozilla.org",
+            "maxVersion": "5",
+            "minVersion": "4"
+          },
+          {
+            "guid": "foo@bar.com"
+          }
+        ]
+      }
+    ]
+  },
+  {
+    "matchName": "^test_bug449027_21$",
+    "versionRange": [
+      {
+        "targetApplication": [
+          {
+            "guid": "xpcshell@tests.mozilla.org",
+            "maxVersion": "1",
+            "minVersion": "1"
+          },
+          {
+            "guid": "xpcshell@tests.mozilla.org",
+            "maxVersion": "4",
+            "minVersion": "2"
+          },
+          {
+            "guid": "xpcshell@tests.mozilla.org",
+            "maxVersion": "6",
+            "minVersion": "5"
+          }
+        ]
+      }
+    ]
+  },
+  {
+    "matchName": "^test_bug449027_22$",
+    "versionRange": [
+      {
+        "targetApplication": [
+          {
+            "guid": "foo@bar.com"
+          },
+          {
+            "guid": "xpcshell@tests.mozilla.org",
+            "minVersion": "3"
+          }
+        ]
+      }
+    ]
+  },
+  {
+    "matchName": "^test_bug449027_23$",
+    "versionRange": [
+      {
+        "targetApplication": [
+          {
+            "guid": "xpcshell@tests.mozilla.org",
+            "minVersion": "2"
+          },
+          {
+            "guid": "foo@bar.com"
+          }
+        ]
+      }
+    ]
+  },
+  {
+    "matchName": "^test_bug449027_24$",
+    "versionRange": [
+      {
+        "targetApplication": [
+          {
+            "guid": "xpcshell@tests.mozilla.org",
+            "maxVersion": "3"
+          }
+        ]
+      }
+    ]
+  },
+  {
+    "matchName": "^test_bug449027_25$",
+    "versionRange": [
+      {
+        "targetApplication": [
+          {
+            "guid": "xpcshell@tests.mozilla.org",
+            "maxVersion": "4"
+          }
+        ]
+      }
+    ]
+  }
+]
\ No newline at end of file
deleted file mode 100644
--- a/toolkit/mozapps/extensions/test/xpcshell/data/test_bug449027_app.xml
+++ /dev/null
@@ -1,333 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-
-<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist">
-  <emItems>
-    <!-- All extensions are version 5 and tests run against appVersion 3 -->
-
-    <!-- Test 1 not listed, should never get blocked -->
-    <!-- Always blocked -->
-    <emItem id="test_bug449027_2@tests.mozilla.org"/>
-    <!-- Always blocked -->
-    <emItem id="test_bug449027_3@tests.mozilla.org">
-      <versionRange/>
-    </emItem>
-    <!-- Not blocked since neither version range matches -->
-    <emItem id="test_bug449027_4@tests.mozilla.org">
-      <versionRange minVersion="6"/>
-      <versionRange maxVersion="4"/>
-    </emItem>
-    <!-- Invalid version range, should not block -->
-    <emItem id="test_bug449027_5@tests.mozilla.org">
-      <versionRange minVersion="6" maxVersion="4"/>
-    </emItem>
-    <!-- Should block all of these -->
-    <emItem id="test_bug449027_6@tests.mozilla.org">
-      <versionRange minVersion="7" maxVersion="8"/>
-      <versionRange minVersion="5" maxVersion="6"/>
-      <versionRange maxVersion="4"/>
-    </emItem>
-    <emItem id="test_bug449027_7@tests.mozilla.org">
-      <versionRange maxVersion="4"/>
-      <versionRange minVersion="4" maxVersion="5"/>
-      <versionRange minVersion="6" maxVersion="7"/>
-    </emItem>
-    <emItem id="test_bug449027_8@tests.mozilla.org">
-      <versionRange minVersion="2" maxVersion="2"/>
-      <versionRange minVersion="4" maxVersion="6"/>
-      <versionRange minVersion="7" maxVersion="8"/>
-    </emItem>
-    <emItem id="test_bug449027_9@tests.mozilla.org">
-      <versionRange minVersion="4"/>
-    </emItem>
-    <emItem id="test_bug449027_10@tests.mozilla.org">
-      <versionRange minVersion="5"/>
-    </emItem>
-    <emItem id="test_bug449027_11@tests.mozilla.org">
-      <versionRange maxVersion="6"/>
-    </emItem>
-    <emItem id="test_bug449027_12@tests.mozilla.org">
-      <versionRange maxVersion="5"/>
-    </emItem>
-
-    <!-- This should block all versions for any application -->
-    <emItem id="test_bug449027_13@tests.mozilla.org">
-      <versionRange>
-        <targetApplication/>
-      </versionRange>
-    </emItem>
-    <!-- Shouldn't block -->
-    <emItem id="test_bug449027_14@tests.mozilla.org">
-      <versionRange>
-        <targetApplication id="foo@bar.com"/>
-      </versionRange>
-    </emItem>
-    <!-- Should block for any version of the app -->
-    <emItem id="test_bug449027_15@tests.mozilla.org">
-      <versionRange>
-        <targetApplication id="xpcshell@tests.mozilla.org"/>
-      </versionRange>
-    </emItem>
-    <!-- Should still block -->
-    <emItem id="test_bug449027_16@tests.mozilla.org">
-      <versionRange>
-        <targetApplication id="xpcshell@tests.mozilla.org">
-          <versionRange/>
-        </targetApplication>
-      </versionRange>
-    </emItem>
-    <!-- Not blocked since neither version range matches -->
-    <emItem id="test_bug449027_17@tests.mozilla.org">
-      <versionRange>
-        <targetApplication id="xpcshell@tests.mozilla.org">
-          <versionRange minVersion="4"/>
-          <versionRange maxVersion="2"/>
-        </targetApplication>
-      </versionRange>
-    </emItem>
-    <!-- Invalid version range, should not block -->
-    <emItem id="test_bug449027_18@tests.mozilla.org">
-      <versionRange>
-        <targetApplication id="xpcshell@tests.mozilla.org">
-          <versionRange minVersion="6" maxVersion="4"/>
-        </targetApplication>
-      </versionRange>
-    </emItem>
-    <!-- Should block all of these -->
-    <emItem id="test_bug449027_19@tests.mozilla.org">
-      <versionRange>
-        <targetApplication id="foo@bar.com"/>
-        <targetApplication id="xpcshell@tests.mozilla.org">
-          <versionRange minVersion="5" maxVersion="6"/>
-          <versionRange minVersion="3" maxVersion="4"/>
-          <versionRange maxVersion="2"/>
-        </targetApplication>
-      </versionRange>
-    </emItem>
-    <emItem id="test_bug449027_20@tests.mozilla.org">
-      <versionRange>
-        <targetApplication id="xpcshell@tests.mozilla.org">
-          <versionRange maxVersion="2"/>
-          <versionRange minVersion="2" maxVersion="3"/>
-          <versionRange minVersion="4" maxVersion="5"/>
-        </targetApplication>
-        <targetApplication id="foo@bar.com"/>
-      </versionRange>
-    </emItem>
-    <emItem id="test_bug449027_21@tests.mozilla.org">
-      <versionRange>
-        <targetApplication id="xpcshell@tests.mozilla.org">
-          <versionRange minVersion="1" maxVersion="1"/>
-          <versionRange minVersion="2" maxVersion="4"/>
-          <versionRange minVersion="5" maxVersion="6"/>
-        </targetApplication>
-      </versionRange>
-    </emItem>
-    <emItem id="test_bug449027_22@tests.mozilla.org">
-      <versionRange>
-        <targetApplication id="foo@bar.com"/>
-        <targetApplication id="xpcshell@tests.mozilla.org">
-          <versionRange minVersion="3"/>
-        </targetApplication>
-      </versionRange>
-    </emItem>
-    <emItem id="test_bug449027_23@tests.mozilla.org">
-      <versionRange>
-        <targetApplication id="xpcshell@tests.mozilla.org">
-          <versionRange minVersion="2"/>
-        </targetApplication>
-        <targetApplication id="foo@bar.com"/>
-      </versionRange>
-    </emItem>
-    <emItem id="test_bug449027_24@tests.mozilla.org">
-      <versionRange>
-        <targetApplication id="xpcshell@tests.mozilla.org">
-          <versionRange maxVersion="3"/>
-        </targetApplication>
-      </versionRange>
-    </emItem>
-    <emItem id="test_bug449027_25@tests.mozilla.org">
-      <versionRange>
-        <targetApplication id="xpcshell@tests.mozilla.org">
-          <versionRange maxVersion="4"/>
-        </targetApplication>
-      </versionRange>
-    </emItem>
-  </emItems>
-  <pluginItems>
-    <!-- All plugins are version 5 and tests run against appVersion 3 -->
-
-    <!-- Test 1 not listed, should never get blocked -->
-    <!-- Always blocked -->
-    <pluginItem>
-      <match name="name" exp="^test_bug449027_2$"/>
-    </pluginItem>
-    <!-- Always blocked -->
-    <pluginItem>
-      <match name="name" exp="^test_bug449027_3$"/>
-      <versionRange/>
-    </pluginItem>
-    <!-- Not blocked since neither version range matches -->
-    <pluginItem>
-      <match name="name" exp="^test_bug449027_4$"/>
-      <versionRange minVersion="6"/>
-      <versionRange maxVersion="4"/>
-    </pluginItem>
-    <!-- Invalid version range, should not block -->
-    <pluginItem>
-      <match name="name" exp="^test_bug449027_5$"/>
-      <versionRange minVersion="6" maxVersion="4"/>
-    </pluginItem>
-    <!-- Should block all of these -->
-    <pluginItem>
-      <match name="name" exp="^test_bug449027_6$"/>
-      <versionRange minVersion="7" maxVersion="8"/>
-      <versionRange minVersion="5" maxVersion="6"/>
-      <versionRange maxVersion="4"/>
-    </pluginItem>
-    <pluginItem>
-      <match name="name" exp="^test_bug449027_7$"/>
-      <versionRange maxVersion="4"/>
-      <versionRange minVersion="4" maxVersion="5"/>
-      <versionRange minVersion="6" maxVersion="7"/>
-    </pluginItem>
-    <pluginItem>
-      <match name="name" exp="^test_bug449027_8$"/>
-      <versionRange minVersion="2" maxVersion="2"/>
-      <versionRange minVersion="4" maxVersion="6"/>
-      <versionRange minVersion="7" maxVersion="8"/>
-    </pluginItem>
-    <pluginItem>
-      <match name="name" exp="^test_bug449027_9$"/>
-      <versionRange minVersion="4"/>
-    </pluginItem>
-    <pluginItem>
-      <match name="name" exp="^test_bug449027_10$"/>
-      <versionRange minVersion="5"/>
-    </pluginItem>
-    <pluginItem>
-      <match name="name" exp="^test_bug449027_11$"/>
-      <versionRange maxVersion="6"/>
-    </pluginItem>
-    <pluginItem>
-      <match name="name" exp="^test_bug449027_12$"/>
-      <versionRange maxVersion="5"/>
-    </pluginItem>
-
-    <!-- This should block all versions for any application -->
-    <pluginItem>
-      <match name="name" exp="^test_bug449027_13$"/>
-      <versionRange>
-        <targetApplication/>
-      </versionRange>
-    </pluginItem>
-    <!-- Shouldn't block -->
-    <pluginItem>
-      <match name="name" exp="^test_bug449027_14$"/>
-      <versionRange>
-        <targetApplication id="foo@bar.com"/>
-      </versionRange>
-    </pluginItem>
-    <!-- Should block for any version of the app -->
-    <pluginItem>
-      <match name="name" exp="^test_bug449027_15$"/>
-      <versionRange>
-        <targetApplication id="xpcshell@tests.mozilla.org"/>
-      </versionRange>
-    </pluginItem>
-    <!-- Should still block -->
-    <pluginItem>
-      <match name="name" exp="^test_bug449027_16$"/>
-      <versionRange>
-        <targetApplication id="xpcshell@tests.mozilla.org">
-          <versionRange/>
-        </targetApplication>
-      </versionRange>
-    </pluginItem>
-    <!-- Not blocked since neither version range matches -->
-    <pluginItem>
-      <match name="name" exp="^test_bug449027_17$"/>
-      <versionRange>
-        <targetApplication id="xpcshell@tests.mozilla.org">
-          <versionRange minVersion="4"/>
-          <versionRange maxVersion="2"/>
-        </targetApplication>
-      </versionRange>
-    </pluginItem>
-    <!-- Invalid version range, should not block -->
-    <pluginItem>
-      <match name="name" exp="^test_bug449027_18$"/>
-      <versionRange>
-        <targetApplication id="xpcshell@tests.mozilla.org">
-          <versionRange minVersion="6" maxVersion="4"/>
-        </targetApplication>
-      </versionRange>
-    </pluginItem>
-    <!-- Should block all of these -->
-    <pluginItem>
-      <match name="name" exp="^test_bug449027_19$"/>
-      <versionRange>
-        <targetApplication id="foo@bar.com"/>
-        <targetApplication id="xpcshell@tests.mozilla.org">
-          <versionRange minVersion="5" maxVersion="6"/>
-          <versionRange minVersion="3" maxVersion="4"/>
-          <versionRange maxVersion="2"/>
-        </targetApplication>
-      </versionRange>
-    </pluginItem>
-    <pluginItem>
-      <match name="name" exp="^test_bug449027_20$"/>
-      <versionRange>
-        <targetApplication id="xpcshell@tests.mozilla.org">
-          <versionRange maxVersion="2"/>
-          <versionRange minVersion="2" maxVersion="3"/>
-          <versionRange minVersion="4" maxVersion="5"/>
-        </targetApplication>
-        <targetApplication id="foo@bar.com"/>
-      </versionRange>
-    </pluginItem>
-    <pluginItem>
-      <match name="name" exp="^test_bug449027_21$"/>
-      <versionRange>
-        <targetApplication id="xpcshell@tests.mozilla.org">
-          <versionRange minVersion="1" maxVersion="1"/>
-          <versionRange minVersion="2" maxVersion="4"/>
-          <versionRange minVersion="5" maxVersion="6"/>
-        </targetApplication>
-      </versionRange>
-    </pluginItem>
-    <pluginItem>
-      <match name="name" exp="^test_bug449027_22$"/>
-      <versionRange>
-        <targetApplication id="foo@bar.com"/>
-        <targetApplication id="xpcshell@tests.mozilla.org">
-          <versionRange minVersion="3"/>
-        </targetApplication>
-      </versionRange>
-    </pluginItem>
-    <pluginItem>
-      <match name="name" exp="^test_bug449027_23$"/>
-      <versionRange>
-        <targetApplication id="xpcshell@tests.mozilla.org">
-          <versionRange minVersion="2"/>
-        </targetApplication>
-        <targetApplication id="foo@bar.com"/>
-      </versionRange>
-    </pluginItem>
-    <pluginItem>
-      <match name="name" exp="^test_bug449027_24$"/>
-      <versionRange>
-        <targetApplication id="xpcshell@tests.mozilla.org">
-          <versionRange maxVersion="3"/>
-        </targetApplication>
-      </versionRange>
-    </pluginItem>
-    <pluginItem>
-      <match name="name" exp="^test_bug449027_25$"/>
-      <versionRange>
-        <targetApplication id="xpcshell@tests.mozilla.org">
-          <versionRange maxVersion="4"/>
-        </targetApplication>
-      </versionRange>
-    </pluginItem>
-  </pluginItems>
-</blocklist>
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_bug449027_toolkit-extensions.json
@@ -0,0 +1,154 @@
+[
+  {
+    "_general_comment": "All extensions are version 5 and tests run against toolkitVersion 8",
+    "_general_comment2": "Test 1-14 not listed, should never get blocked",
+
+    "_comment": "Should block for any version of the app",
+    "guid": "test_bug449027_15@tests.mozilla.org",
+    "versionRange": [{
+      "targetApplication": [{"guid": "toolkit@mozilla.org"}]
+    }]
+  },
+  {
+    "_comment": "Should still block",
+    "guid": "test_bug449027_16@tests.mozilla.org",
+    "versionRange": [{
+      "targetApplication": [{"guid": "toolkit@mozilla.org"}]
+    }]
+  },
+  {
+    "_comment": "Not blocked since neither version range matches",
+    "guid": "test_bug449027_17@tests.mozilla.org",
+    "versionRange": [{
+      "targetApplication": [{
+        "minVersion": "9",
+        "guid": "toolkit@mozilla.org"
+      },{
+        "maxVersion": "7",
+        "guid": "toolkit@mozilla.org"
+      }]
+    }]
+  },
+  {
+    "_comment": "Invalid version range, should not block",
+    "guid": "test_bug449027_18@tests.mozilla.org",
+    "versionRange": [{
+      "targetApplication": [{
+        "minVersion": "11",
+        "maxVersion": "9",
+        "guid": "toolkit@mozilla.org"
+      }]
+    }]
+  },
+  {
+    "_comment": "Should block all of the following",
+    "guid": "test_bug449027_19@tests.mozilla.org",
+    "versionRange": [{
+      "targetApplication": [
+        {
+          "guid": "foo@bar.com"
+        },
+        {
+          "guid": "toolkit@mozilla.org",
+          "minVersion": "10",
+          "maxVersion": "11"
+        },
+        {
+          "minVersion": "8",
+          "maxVersion": "9"
+        },
+        {
+          "maxVersion": "7"
+        }
+      ]
+    }]
+  },
+  {
+    "guid": "test_bug449027_20@tests.mozilla.org",
+    "versionRange": [{
+      "targetApplication": [
+        {
+          "guid": "toolkit@mozilla.org",
+          "maxVersion": "7"
+        },
+        {
+          "guid": "toolkit@mozilla.org",
+          "minVersion": "7",
+          "maxVersion": "8"
+        },
+        {
+          "guid": "toolkit@mozilla.org",
+          "minVersion": "9",
+          "maxVersion": "10"
+        },
+        {
+          "guid": "foo@bar.com"
+        }
+      ]
+    }]
+  },
+  {
+    "guid": "test_bug449027_21@tests.mozilla.org",
+    "versionRange": [{
+      "targetApplication": [
+        {
+          "guid": "toolkit@mozilla.org",
+          "minVersion": "6",
+          "maxVersion": "6"
+        },
+        {
+          "guid": "toolkit@mozilla.org",
+          "minVersion": "7",
+          "maxVersion": "9"
+        },
+        {
+          "guid": "toolkit@mozilla.org",
+          "minVersion": "10",
+          "maxVersion": "11"
+        }
+      ]
+    }]
+  },
+  {
+    "guid": "test_bug449027_22@tests.mozilla.org",
+    "versionRange": [{
+      "targetApplication": [{
+        "guid": "foo@bar.com"
+      },
+      {
+        "guid": "toolkit@mozilla.org",
+        "minVersion": "8"
+      }]
+    }]
+  },
+  {
+    "guid": "test_bug449027_23@tests.mozilla.org",
+    "versionRange": [{
+      "targetApplication": [{
+        "guid": "toolkit@mozilla.org",
+        "minVersion": "7"
+      },
+      {
+        "guid": "foo@bar.com"
+      }]
+    }]
+  },
+  {
+    "guid": "test_bug449027_24@tests.mozilla.org",
+    "versionRange": [{
+      "targetApplication": [{
+        "maxVersion": "8",
+        "guid": "toolkit@mozilla.org"
+      }]
+    }]
+  },
+  {
+    "guid": "test_bug449027_25@tests.mozilla.org",
+    "versionRange": [{
+      "targetApplication": [{
+        "guid": "toolkit@mozilla.org",
+        "maxVersion": "9"
+      }]
+    }]
+  }
+]
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_bug449027_toolkit-plugins.json
@@ -0,0 +1,155 @@
+[
+  {
+    "_general_comment": "All plugins are version 5 and tests run against appVersion 3",
+    "_general_comment2": "Test 1-14 not listed, should never get blocked",
+
+    "_comment": "Should block for any version of the app",
+    "matchName": "^test_bug449027_15$",
+    "versionRange": [{
+      "targetApplication": [{"guid": "toolkit@mozilla.org"}]
+    }]
+  },
+  {
+    "_comment": "Should still block",
+    "matchName": "^test_bug449027_16$",
+    "versionRange": [{
+      "targetApplication": [{"guid": "toolkit@mozilla.org"}]
+    }]
+  },
+  {
+    "_comment": "Not blocked since neither version range matches",
+    "matchName": "^test_bug449027_17$",
+    "versionRange": [{
+      "targetApplication": [{
+        "minVersion": "9",
+        "guid": "toolkit@mozilla.org"
+      },{
+        "maxVersion": "7",
+        "guid": "toolkit@mozilla.org"
+      }]
+    }]
+  },
+  {
+    "_comment": "Invalid version range, should not block",
+    "matchName": "^test_bug449027_18$",
+    "versionRange": [{
+      "targetApplication": [{
+        "minVersion": "11",
+        "maxVersion": "9",
+        "guid": "toolkit@mozilla.org"
+      }]
+    }]
+  },
+  {
+    "_comment": "Should block all of the following",
+    "matchName": "^test_bug449027_19$",
+    "versionRange": [{
+      "targetApplication": [
+        {
+          "guid": "foo@bar.com"
+        },
+        {
+          "guid": "toolkit@mozilla.org",
+          "minVersion": "10",
+          "maxVersion": "11"
+        },
+        {
+          "minVersion": "8",
+          "maxVersion": "9"
+        },
+        {
+          "maxVersion": "7"
+        }
+      ]
+    }]
+  },
+  {
+    "matchName": "^test_bug449027_20$",
+    "versionRange": [{
+      "targetApplication": [
+        {
+          "guid": "toolkit@mozilla.org",
+          "maxVersion": "7"
+        },
+        {
+          "guid": "toolkit@mozilla.org",
+          "minVersion": "7",
+          "maxVersion": "8"
+        },
+        {
+          "guid": "toolkit@mozilla.org",
+          "minVersion": "9",
+          "maxVersion": "10"
+        },
+        {
+          "guid": "foo@bar.com"
+        }
+      ]
+    }]
+  },
+  {
+    "matchName": "^test_bug449027_21$",
+    "versionRange": [{
+      "targetApplication": [
+        {
+          "guid": "toolkit@mozilla.org",
+          "minVersion": "6",
+          "maxVersion": "6"
+        },
+        {
+          "guid": "toolkit@mozilla.org",
+          "minVersion": "7",
+          "maxVersion": "9"
+        },
+        {
+          "guid": "toolkit@mozilla.org",
+          "minVersion": "10",
+          "maxVersion": "11"
+        }
+      ]
+    }]
+  },
+  {
+    "matchName": "^test_bug449027_22$",
+    "versionRange": [{
+      "targetApplication": [{
+        "guid": "foo@bar.com"
+      },
+      {
+        "guid": "toolkit@mozilla.org",
+        "minVersion": "8"
+      }]
+    }]
+  },
+  {
+    "matchName": "^test_bug449027_23$",
+    "versionRange": [{
+      "targetApplication": [{
+        "guid": "toolkit@mozilla.org",
+        "minVersion": "7"
+      },
+      {
+        "guid": "foo@bar.com"
+      }]
+    }]
+  },
+  {
+    "matchName": "^test_bug449027_24$",
+    "versionRange": [{
+      "targetApplication": [{
+        "maxVersion": "8",
+        "guid": "toolkit@mozilla.org"
+      }]
+    }]
+  },
+  {
+    "matchName": "^test_bug449027_25$",
+    "versionRange": [{
+      "targetApplication": [{
+        "guid": "toolkit@mozilla.org",
+        "maxVersion": "9"
+      }]
+    }]
+  }
+]
+
deleted file mode 100644
--- a/toolkit/mozapps/extensions/test/xpcshell/data/test_bug449027_toolkit.xml
+++ /dev/null
@@ -1,208 +0,0 @@
-<?xml version="1.0" encoding="UTF-8"?>
-
-<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist">
-  <emItems>
-    <!-- All extensions are version 5 and tests run against toolkitVersion 8 -->
-
-    <!-- Test 1-14 not listed, should never get blocked -->
-
-    <!-- Should block for any version of the app -->
-    <emItem id="test_bug449027_15@tests.mozilla.org">
-      <versionRange>
-        <targetApplication id="toolkit@mozilla.org"/>
-      </versionRange>
-    </emItem>
-    <!-- Should still block -->
-    <emItem id="test_bug449027_16@tests.mozilla.org">
-      <versionRange>
-        <targetApplication id="toolkit@mozilla.org">
-          <versionRange/>
-        </targetApplication>
-      </versionRange>
-    </emItem>
-    <!-- Not blocked since neither version range matches -->
-    <emItem id="test_bug449027_17@tests.mozilla.org">
-      <versionRange>
-        <targetApplication id="toolkit@mozilla.org">
-          <versionRange minVersion="9"/>
-          <versionRange maxVersion="7"/>
-        </targetApplication>
-      </versionRange>
-    </emItem>
-    <!-- Invalid version range, should not block -->
-    <emItem id="test_bug449027_18@tests.mozilla.org">
-      <versionRange>
-        <targetApplication id="toolkit@mozilla.org">
-          <versionRange minVersion="11" maxVersion="9"/>
-        </targetApplication>
-      </versionRange>
-    </emItem>
-    <!-- Should block all of these -->
-    <emItem id="test_bug449027_19@tests.mozilla.org">
-      <versionRange>
-        <targetApplication id="foo@bar.com"/>
-        <targetApplication id="toolkit@mozilla.org">
-          <versionRange minVersion="10" maxVersion="11"/>
-          <versionRange minVersion="8" maxVersion="9"/>
-          <versionRange maxVersion="7"/>
-        </targetApplication>
-      </versionRange>
-    </emItem>
-    <emItem id="test_bug449027_20@tests.mozilla.org">
-      <versionRange>
-        <targetApplication id="toolkit@mozilla.org">
-          <versionRange maxVersion="7"/>
-          <versionRange minVersion="7" maxVersion="8"/>
-          <versionRange minVersion="9" maxVersion="10"/>
-        </targetApplication>
-        <targetApplication id="foo@bar.com"/>
-      </versionRange>
-    </emItem>
-    <emItem id="test_bug449027_21@tests.mozilla.org">
-      <versionRange>
-        <targetApplication id="toolkit@mozilla.org">
-          <versionRange minVersion="6" maxVersion="6"/>
-          <versionRange minVersion="7" maxVersion="9"/>
-          <versionRange minVersion="10" maxVersion="11"/>
-        </targetApplication>
-      </versionRange>
-    </emItem>
-    <emItem id="test_bug449027_22@tests.mozilla.org">
-      <versionRange>
-        <targetApplication id="foo@bar.com"/>
-        <targetApplication id="toolkit@mozilla.org">
-          <versionRange minVersion="8"/>
-        </targetApplication>
-      </versionRange>
-    </emItem>
-    <emItem id="test_bug449027_23@tests.mozilla.org">
-      <versionRange>
-        <targetApplication id="toolkit@mozilla.org">
-          <versionRange minVersion="7"/>
-        </targetApplication>
-        <targetApplication id="foo@bar.com"/>
-      </versionRange>
-    </emItem>
-    <emItem id="test_bug449027_24@tests.mozilla.org">
-      <versionRange>
-        <targetApplication id="toolkit@mozilla.org">
-          <versionRange maxVersion="8"/>
-        </targetApplication>
-      </versionRange>
-    </emItem>
-    <emItem id="test_bug449027_25@tests.mozilla.org">
-      <versionRange>
-        <targetApplication id="toolkit@mozilla.org">
-          <versionRange maxVersion="9"/>
-        </targetApplication>
-      </versionRange>
-    </emItem>
-  </emItems>
-  <pluginItems>
-    <!-- All plugins are version 5 and tests run against appVersion 3 -->
-
-    <!-- Test 1-14 not listed, should never get blocked -->
-    <!-- Should block for any version of the app -->
-    <pluginItem>
-      <match name="name" exp="^test_bug449027_15$"/>
-      <versionRange>
-        <targetApplication id="toolkit@mozilla.org"/>
-      </versionRange>
-    </pluginItem>
-    <!-- Should still block -->
-    <pluginItem>
-      <match name="name" exp="^test_bug449027_16$"/>
-      <versionRange>
-        <targetApplication id="toolkit@mozilla.org">
-          <versionRange/>
-        </targetApplication>
-      </versionRange>
-    </pluginItem>
-    <!-- Not blocked since neither version range matches -->
-    <pluginItem>
-      <match name="name" exp="^test_bug449027_17$"/>
-      <versionRange>
-        <targetApplication id="toolkit@mozilla.org">
-          <versionRange minVersion="9"/>
-          <versionRange maxVersion="7"/>
-        </targetApplication>
-      </versionRange>
-    </pluginItem>
-    <!-- Invalid version range, should not block -->
-    <pluginItem>
-      <match name="name" exp="^test_bug449027_18$"/>
-      <versionRange>
-        <targetApplication id="toolkit@mozilla.org">
-          <versionRange minVersion="11" maxVersion="9"/>
-        </targetApplication>
-      </versionRange>
-    </pluginItem>
-    <!-- Should block all of these -->
-    <pluginItem>
-      <match name="name" exp="^test_bug449027_19$"/>
-      <versionRange>
-        <targetApplication id="foo@bar.com"/>
-        <targetApplication id="toolkit@mozilla.org">
-          <versionRange minVersion="10" maxVersion="11"/>
-          <versionRange minVersion="8" maxVersion="9"/>
-          <versionRange maxVersion="7"/>
-        </targetApplication>
-      </versionRange>
-    </pluginItem>
-    <pluginItem>
-      <match name="name" exp="^test_bug449027_20$"/>
-      <versionRange>
-        <targetApplication id="toolkit@mozilla.org">
-          <versionRange maxVersion="7"/>
-          <versionRange minVersion="7" maxVersion="8"/>
-          <versionRange minVersion="9" maxVersion="10"/>
-        </targetApplication>
-        <targetApplication id="foo@bar.com"/>
-      </versionRange>
-    </pluginItem>
-    <pluginItem>
-      <match name="name" exp="^test_bug449027_21$"/>
-      <versionRange>
-        <targetApplication id="toolkit@mozilla.org">
-          <versionRange minVersion="6" maxVersion="6"/>
-          <versionRange minVersion="7" maxVersion="9"/>
-          <versionRange minVersion="10" maxVersion="11"/>
-        </targetApplication>
-      </versionRange>
-    </pluginItem>
-    <pluginItem>
-      <match name="name" exp="^test_bug449027_22$"/>
-      <versionRange>
-        <targetApplication id="foo@bar.com"/>
-        <targetApplication id="toolkit@mozilla.org">
-          <versionRange minVersion="8"/>
-        </targetApplication>
-      </versionRange>
-    </pluginItem>
-    <pluginItem>
-      <match name="name" exp="^test_bug449027_23$"/>
-      <versionRange>
-        <targetApplication id="toolkit@mozilla.org">
-          <versionRange minVersion="7"/>
-        </targetApplication>
-        <targetApplication id="foo@bar.com"/>
-      </versionRange>
-    </pluginItem>
-    <pluginItem>
-      <match name="name" exp="^test_bug449027_24$"/>
-      <versionRange>
-        <targetApplication id="toolkit@mozilla.org">
-          <versionRange maxVersion="8"/>
-        </targetApplication>
-      </versionRange>
-    </pluginItem>
-    <pluginItem>
-      <match name="name" exp="^test_bug449027_25$"/>
-      <versionRange>
-        <targetApplication id="toolkit@mozilla.org">
-          <versionRange maxVersion="9"/>
-        </targetApplication>
-      </versionRange>
-    </pluginItem>
-  </pluginItems>
-</blocklist>
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_pluginBlocklistCtp-plugins.json
@@ -0,0 +1,56 @@
+[
+  {
+    "matchName": "^test_plugin_0",
+    "versionRange": [
+      {
+        "maxVersion": "*",
+        "minVersion": "0",
+        "severity": "0",
+        "vulnerabilityStatus": "0"
+      }
+    ]
+  },
+  {
+    "matchName": "^test_plugin_1",
+    "versionRange": [
+      {
+        "maxVersion": "*",
+        "minVersion": "0",
+        "severity": "0",
+        "vulnerabilityStatus": "1"
+      }
+    ]
+  },
+  {
+    "matchName": "^test_plugin_2",
+    "versionRange": [
+      {
+        "maxVersion": "*",
+        "minVersion": "0",
+        "severity": "0",
+        "vulnerabilityStatus": "2"
+      }
+    ]
+  },
+  {
+    "matchName": "^test_plugin_3",
+    "versionRange": [
+      {
+        "maxVersion": "*",
+        "minVersion": "0",
+        "vulnerabilityStatus": "2"
+      }
+    ]
+  },
+  {
+    "matchName": "^test_plugin_4",
+    "versionRange": [
+      {
+        "maxVersion": "*",
+        "minVersion": "0",
+        "severity": "1",
+        "vulnerabilityStatus": "2"
+      }
+    ]
+  }
+]
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_pluginBlocklistCtpUndo-plugins.json
@@ -0,0 +1,13 @@
+[
+  {
+    "matchName": "^Test Plug-in",
+    "versionRange": [
+      {
+        "maxVersion": "*",
+        "minVersion": "0",
+        "severity": "0",
+        "vulnerabilityStatus": "2"
+      }
+    ]
+  }
+]
\ No newline at end of file
--- a/toolkit/mozapps/extensions/test/xpcshell/test_blocklist_appversion.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_blocklist_appversion.js
@@ -1,15 +1,16 @@
 /* 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/.
  */
 
 const Cm = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
 
+// FIXME need to change how these blocklists are updated
 var testserver = AddonTestUtils.createHttpServer({hosts: ["example.com"]});
 testserver.registerDirectory("/data/", do_get_file("data"));
 
 var ADDONS = [{
   id: "test_bug449027_1@tests.mozilla.org",
   name: "Bug 449027 Addon Test 1",
   version: "5",
   start: false,
@@ -232,26 +233,16 @@ var BlocklistPrompt = {
   prompt(list) {
     gNewBlocks = list.map(item => `${item.name} ${item.version}`);
   },
 
   QueryInterface: ChromeUtils.generateQI([]),
 };
 
 
-async function loadBlocklist(file) {
-  let blocklistUpdated = TestUtils.topicObserved("blocklist-updated");
-
-  Services.prefs.setCharPref("extensions.blocklist.url",
-                             "http://example.com/data/" + file);
-  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);
 
 function createAddon(addon) {
   return promiseInstallXPI({
     name: addon.name,
@@ -319,21 +310,21 @@ add_task(async function test() {
 
   await checkState("start");
 });
 
 /**
  * Load the toolkit based blocks
  */
 add_task(async function test_pt2() {
-  await loadBlocklist("test_bug449027_toolkit.xml");
+  await AddonTestUtils.loadBlocklistData(do_get_file("data/"), "test_bug449027_toolkit");
 
   await checkState("toolkitBlocks", "start");
 });
 
 /**
  * Load the application based blocks
  */
 add_task(async function test_pt3() {
-  await loadBlocklist("test_bug449027_app.xml");
+  await AddonTestUtils.loadBlocklistData(do_get_file("data/"), "test_bug449027_app");
 
   await checkState("appBlocks", "toolkitBlocks");
 });
--- a/toolkit/mozapps/extensions/test/xpcshell/test_blocklist_metadata_filters.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_blocklist_metadata_filters.js
@@ -1,50 +1,23 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
 // Tests blocking of extensions by ID, name, creator, homepageURL, updateURL
 // and RegExps for each. See bug 897735.
 
-const URI_EXTENSION_BLOCKLIST_DIALOG = "chrome://mozapps/content/extensions/blocklist.xul";
-
-ChromeUtils.import("resource://testing-common/MockRegistrar.jsm");
 var testserver = AddonTestUtils.createHttpServer({hosts: ["example.com"]});
 gPort = testserver.identity.primaryPort;
 
 testserver.registerDirectory("/data/", do_get_file("data"));
 
 const profileDir = gProfD.clone();
 profileDir.append("extensions");
 
-// Don't need the full interface, attempts to call other methods will just
-// throw which is just fine
-var WindowWatcher = {
-  openWindow(parent, url, name, features, args) {
-    // Should be called to list the newly blocklisted items
-    Assert.equal(url, URI_EXTENSION_BLOCKLIST_DIALOG);
-
-    // Simulate auto-disabling any softblocks
-    var list = args.wrappedJSObject.list;
-    list.forEach(function(aItem) {
-      if (!aItem.blocked)
-        aItem.disable = true;
-    });
-
-    // run the code after the blocklist is closed
-    Services.obs.notifyObservers(null, "addon-blocklist-closed");
-
-  },
-
-  QueryInterface: ChromeUtils.generateQI(["nsIWindowWatcher"])
-};
-
-MockRegistrar.register("@mozilla.org/embedcomp/window-watcher;1", WindowWatcher);
-
 function load_blocklist(aFile, aCallback) {
   Services.obs.addObserver(function observer() {
     Services.obs.removeObserver(observer, "blocklist-updated");
 
     executeSoon(aCallback);
   }, "blocklist-updated");
 
   Services.prefs.setCharPref("extensions.blocklist.url", "http://localhost:" +
--- a/toolkit/mozapps/extensions/test/xpcshell/test_blocklist_plugin_outdated.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_blocklist_plugin_outdated.js
@@ -47,17 +47,18 @@ var BlocklistPrompt = {
     Assert.notEqual(item.name, "test_bug514327_outdated");
   },
 
   QueryInterface: ChromeUtils.generateQI([]),
 };
 
 
 async function loadBlocklist(file) {
-  let blocklistUpdated = TestUtils.topicObserved("blocklist-updated");
+  // FIXME needs to load blocklist differently.
+  let blocklistUpdated = TestUtils.topicObserved("plugin-blocklist-updated");
 
   Services.prefs.setCharPref("extensions.blocklist.url",
                              "http://example.com/data/" + file);
   Blocklist.notify();
 
   await blocklistUpdated;
 }
 
--- a/toolkit/mozapps/extensions/test/xpcshell/test_blocklist_prefs.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_blocklist_prefs.js
@@ -1,51 +1,23 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
 // Tests resetting of preferences in blocklist entry when an add-on is blocked.
 // See bug 802434.
 
-const URI_EXTENSION_BLOCKLIST_DIALOG = "chrome://mozapps/content/extensions/blocklist.xul";
-
-ChromeUtils.import("resource://testing-common/MockRegistrar.jsm");
 var testserver = AddonTestUtils.createHttpServer({hosts: ["example.com"]});
 gPort = testserver.identity.primaryPort;
 
 testserver.registerDirectory("/data/", do_get_file("data"));
 
 const profileDir = gProfD.clone();
 profileDir.append("extensions");
 
-// A window watcher to handle the blocklist UI.
-// Don't need the full interface, attempts to call other methods will just
-// throw which is just fine
-var WindowWatcher = {
-  openWindow(parent, url, name, features, args) {
-    // Should be called to list the newly blocklisted items
-    Assert.equal(url, URI_EXTENSION_BLOCKLIST_DIALOG);
-
-    // Simulate auto-disabling any softblocks
-    var list = args.wrappedJSObject.list;
-    list.forEach(function(aItem) {
-      if (!aItem.blocked)
-        aItem.disable = true;
-    });
-
-    // run the code after the blocklist is closed
-    Services.obs.notifyObservers(null, "addon-blocklist-closed");
-
-  },
-
-  QueryInterface: ChromeUtils.generateQI(["nsIWindowWatcher"])
-};
-
-MockRegistrar.register("@mozilla.org/embedcomp/window-watcher;1", WindowWatcher);
-
 function load_blocklist(aFile, aCallback) {
   Services.obs.addObserver(function observer() {
     Services.obs.removeObserver(observer, "blocklist-updated");
 
     executeSoon(aCallback);
   }, "blocklist-updated");
 
   Services.prefs.setCharPref("extensions.blocklist.url", "http://localhost:" +
--- a/toolkit/mozapps/extensions/test/xpcshell/test_blocklist_regexp.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_blocklist_regexp.js
@@ -1,52 +1,24 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
 // Checks that blocklist entries using RegExp work as expected. This only covers
 // behavior specific to RegExp entries - general behavior is already tested
 // in test_blocklistchange.js.
 
-const URI_EXTENSION_BLOCKLIST_DIALOG = "chrome://mozapps/content/extensions/blocklist.xul";
-
-ChromeUtils.import("resource://testing-common/MockRegistrar.jsm");
 var testserver = AddonTestUtils.createHttpServer({hosts: ["example.com"]});
 gPort = testserver.identity.primaryPort;
 
 testserver.registerDirectory("/data/", do_get_file("data"));
 
 const profileDir = gProfD.clone();
 profileDir.append("extensions");
 
-// Don't need the full interface, attempts to call other methods will just
-// throw which is just fine
-var WindowWatcher = {
-  openWindow(parent, url, name, features, args) {
-    // Should be called to list the newly blocklisted items
-    Assert.equal(url, URI_EXTENSION_BLOCKLIST_DIALOG);
-
-    // Simulate auto-disabling any softblocks
-    var list = args.wrappedJSObject.list;
-    list.forEach(function(aItem) {
-      if (!aItem.blocked)
-        aItem.disable = true;
-    });
-
-    // run the code after the blocklist is closed
-    Services.obs.notifyObservers(null, "addon-blocklist-closed");
-
-  },
-
-  QueryInterface: ChromeUtils.generateQI(["nsIWindowWatcher"])
-};
-
-MockRegistrar.register("@mozilla.org/embedcomp/window-watcher;1", WindowWatcher);
-
-
 function load_blocklist(aFile, aCallback) {
   Services.obs.addObserver(function observer() {
     Services.obs.removeObserver(observer, "blocklist-updated");
 
     executeSoon(aCallback);
   }, "blocklist-updated");
 
   Services.prefs.setCharPref("extensions.blocklist.url", "http://localhost:" +
--- a/toolkit/mozapps/extensions/test/xpcshell/test_blocklist_severities.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_blocklist_severities.js
@@ -5,16 +5,17 @@
 
 const URI_EXTENSION_BLOCKLIST_DIALOG = "chrome://mozapps/content/extensions/blocklist.xul";
 
 ChromeUtils.import("resource://testing-common/MockRegistrar.jsm");
 
 var gTestserver = AddonTestUtils.createHttpServer({hosts: ["example.com"]});
 gTestserver.registerDirectory("/data/", do_get_file("data"));
 
+// FIXME gather updates differently
 // 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 Blocklist.getAddonBlocklistEntry(addon);
   return entry && entry.url;
 }
@@ -129,17 +130,17 @@ function createAddon(addon) {
     targetApplications: [{
       id: "xpcshell@tests.mozilla.org",
       minVersion: addon.appVersion,
       maxVersion: addon.appVersion}],
   });
 }
 
 async function loadBlocklist(file, callback) {
-  let blocklistUpdated = TestUtils.topicObserved("blocklist-updated");
+  let blocklistUpdated = TestUtils.topicObserved("plugin-blocklist-updated");
 
   gNotificationCheck = callback;
 
   Services.prefs.setCharPref("extensions.blocklist.url",
                              "http://example.com/data/" + file);
   Blocklist.notify();
 
   await blocklistUpdated;
--- a/toolkit/mozapps/extensions/test/xpcshell/test_blocklistchange.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_blocklistchange.js
@@ -487,16 +487,17 @@ var regexpblock_3 = {
   updateURL: "http://example.com/data/blocklistchange/addon_update3.json",
   targetApplications: [{
     id: "xpcshell@tests.mozilla.org",
     minVersion: "1",
     maxVersion: "3"
   }]
 };
 
+// FIXME this window-watchers for add-ons.
 const ADDON_IDS = ["softblock1@tests.mozilla.org",
                    "softblock2@tests.mozilla.org",
                    "softblock3@tests.mozilla.org",
                    "softblock4@tests.mozilla.org",
                    "hardblock@tests.mozilla.org",
                    "regexpblock@tests.mozilla.org"];
 
 // Don't need the full interface, attempts to call other methods will just
--- a/toolkit/mozapps/extensions/test/xpcshell/test_pluginBlocklistCtp.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_pluginBlocklistCtp.js
@@ -1,15 +1,14 @@
 /* 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/. */
 
 const nsIBLS = Ci.nsIBlocklistService;
 
-var gNotifier = null;
 var gPluginHost = null;
 
 var gTestserver = AddonTestUtils.createHttpServer({hosts: ["example.com"]});
 gTestserver.registerDirectory("/data/", do_get_file("data"));
 
 var PLUGINS = [{
   // severity=0, vulnerabilitystatus=0 -> outdated
   name: "test_plugin_0",
@@ -48,44 +47,39 @@ var PLUGINS = [{
 {
   // not in the blocklist -> not blocked
   name: "test_plugin_5",
   version: "5",
   disabled: false,
   blocklisted: false
 }];
 
-async function updateBlocklist(blocklistURL) {
-  if (blocklistURL) {
-    Services.prefs.setCharPref("extensions.blocklist.url", blocklistURL);
-  }
-  let blocklistUpdated = TestUtils.topicObserved("blocklist-updated");
-  gNotifier.notify(null);
+async function updateBlocklist(file) {
+  let blocklistUpdated = TestUtils.topicObserved("plugin-blocklist-updated");
+  AddonTestUtils.loadBlocklistData(do_get_file("data/"), file);
   return blocklistUpdated;
 }
 
 add_task(async function setup() {
   createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9");
 
-  Services.prefs.setCharPref("extensions.blocklist.url", "http://example.com/data/test_pluginBlocklistCtp.xml");
   Services.prefs.setBoolPref("plugin.load_flash_only", false);
+  gPluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
   await promiseStartupManager();
 
-  gPluginHost = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
-  gNotifier = Cc["@mozilla.org/extensions/blocklist;1"].getService(Ci.nsITimerCallback);
 
   registerCleanupFunction(function() {
-    Services.prefs.clearUserPref("extensions.blocklist.url");
     Services.prefs.clearUserPref("extensions.blocklist.enabled");
+    Services.prefs.clearUserPref("plugin.load_flash_only");
     Services.prefs.clearUserPref("plugins.click_to_play");
   });
 });
 
 add_task(async function basic() {
-  await updateBlocklist();
+  await updateBlocklist("test_pluginBlocklistCtp");
   var {blocklist} = Services;
 
   Assert.equal(await blocklist.getPluginBlocklistState(PLUGINS[0], "1", "1.9"),
                nsIBLS.STATE_OUTDATED);
 
   Assert.equal(await blocklist.getPluginBlocklistState(PLUGINS[1], "1", "1.9"),
                nsIBLS.STATE_VULNERABLE_UPDATE_AVAILABLE);
 
@@ -103,53 +97,53 @@ add_task(async function basic() {
 
 });
 
 function get_test_plugin() {
   for (var plugin of gPluginHost.getPluginTags()) {
     if (plugin.name == "Test Plug-in")
       return plugin;
   }
-  Assert.ok(false);
+  Assert.ok(false, "Should have found the test plugin!");
   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 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");
+  await updateBlocklist("test_pluginBlocklistCtpUndo");
   var plugin = get_test_plugin();
   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");
+  await updateBlocklist("test_pluginBlocklistCtp");
   var plugin = get_test_plugin();
   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");
+  await updateBlocklist("test_pluginBlocklistCtpUndo");
   var plugin = get_test_plugin();
   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 Blocklist.getPluginBlocklistState(plugin, "1", "1.9");
   Assert.notEqual(blocklistState, Ci.nsIBlocklistService.STATE_VULNERABLE_NO_UPDATE);
   Assert.notEqual(blocklistState, Ci.nsIBlocklistService.STATE_VULNERABLE_UPDATE_AVAILABLE);
--- a/toolkit/mozapps/extensions/test/xpcshell/test_softblocked.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_softblocked.js
@@ -9,16 +9,17 @@ ChromeUtils.import("resource://testing-c
 
 // Allow insecure updates
 Services.prefs.setBoolPref("extensions.checkUpdateSecurity", false);
 
 const testserver = createHttpServer();
 gPort = testserver.identity.primaryPort;
 testserver.registerDirectory("/data/", do_get_file("data"));
 
+// FIXME addon window watcher
 // Don't need the full interface, attempts to call other methods will just
 // throw which is just fine
 var WindowWatcher = {
   openWindow(parent, url, name, features, openArgs) {
     // Should be called to list the newly blocklisted items
     Assert.equal(url, URI_EXTENSION_BLOCKLIST_DIALOG);
 
     // Simulate auto-disabling any softblocks