Bug 1454378 - remove last sync XML blocklist reading code, r?florian draft
authorGijs Kruitbosch <gijskruitbosch@gmail.com>
Wed, 13 Jun 2018 11:51:45 -0700
changeset 810881 766c1fadd8500ea64de77990e4282b0cf4a2d5f3
parent 810712 ca98b6f47b4e33673291a4dc2a4ebca28ce6db00
child 810882 c7e45544d379cafab230c67213ff6f0ce193996e
push id114146
push userbmo:gijskruitbosch+bugs@gmail.com
push dateTue, 26 Jun 2018 17:15:11 +0000
reviewersflorian
bugs1454378
milestone63.0a1
Bug 1454378 - remove last sync XML blocklist reading code, r?florian MozReview-Commit-ID: 3YYVy0ubdj8
toolkit/mozapps/extensions/Blocklist.jsm
toolkit/mozapps/extensions/test/xpcshell/test_asyncBlocklistLoad.js
toolkit/mozapps/extensions/test/xpcshell/test_blocklist_gfx.js
toolkit/mozapps/extensions/test/xpcshell/test_overrideblocklist.js
toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini
--- a/toolkit/mozapps/extensions/Blocklist.jsm
+++ b/toolkit/mozapps/extensions/Blocklist.jsm
@@ -8,33 +8,92 @@
 
 /* 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, ["DOMParser"]);
+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>
+#      <gfxItems>
+#        <gfxItem ... />
+#      </gfxItems>
+#    </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.
 const BlocklistClients = {};
 ChromeUtils.defineModuleGetter(BlocklistClients, "initialize",
                                "resource://services-common/blocklist-clients.js");
 XPCOMUtils.defineLazyGetter(this, "RemoteSettings", function() {
   // Instantiate blocklist clients.
@@ -308,18 +367,30 @@ var Blocklist = {
       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._loadBlocklist();
-          this._blocklistUpdated(null, null);
+          // 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);
+            if (newUpdate == this._lastUpdate) {
+              delete this._lastUpdate;
+            }
+          })().catch(Cu.reportError);
           break;
         case PREF_BLOCKLIST_LEVEL:
           gBlocklistLevel = Math.min(Services.prefs.getIntPref(PREF_BLOCKLIST_LEVEL, DEFAULT_LEVEL),
                                      MAX_BLOCK_LEVEL);
           this._blocklistUpdated(null, null);
           break;
       }
       break;
@@ -610,21 +681,16 @@ var Blocklist = {
     } else {
       request.setRequestHeader("Cache-Control", "no-cache");
     }
 
     request.addEventListener("error", event => this.onXMLError(event));
     request.addEventListener("load", event => this.onXMLLoad(event));
     request.send(null);
 
-    // When the blocklist loads we need to compare it to the current copy so
-    // make sure we have loaded it.
-    if (!this.isLoaded)
-      this._loadBlocklist();
-
     // If blocklist update via Kinto is enabled, poll for changes and sync.
     // Currently certificates blocklist relies on it by default.
     if (Services.prefs.getBoolPref(PREF_BLOCKLIST_UPDATE_ENABLED)) {
       RemoteSettings.pollChanges().catch(() => {
         // Bug 1254099 - Telemetry (success or errors) will be collected during this process.
       });
     }
   },
@@ -654,16 +720,20 @@ var Blocklist = {
       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;
 
     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);
 
@@ -692,145 +762,16 @@ var Blocklist = {
       } catch (e) {
       }
     }
     LOG("Blocklist:onError: There was an error loading the blocklist file\r\n" +
         statusText);
   },
 
   /**
-   * Finds the newest blocklist file from the application and the profile and
-   * load it or does nothing if neither exist.
-   */
-  _loadBlocklist() {
-    this._addonEntries = [];
-    this._gfxEntries = [];
-    this._pluginEntries = [];
-
-    Services.telemetry.getHistogramById("BLOCKLIST_SYNC_FILE_LOAD").add(true);
-
-    var profFile = FileUtils.getFile(KEY_PROFILEDIR, [FILE_BLOCKLIST]);
-    try {
-      this._loadBlocklistFromFile(profFile);
-    } catch (ex) {
-      LOG("Blocklist::_loadBlocklist: couldn't load file from profile, trying app dir");
-      try {
-        var appFile = FileUtils.getFile(KEY_APPDIR, [FILE_BLOCKLIST]);
-        this._loadBlocklistFromFile(appFile);
-      } catch (ex) {
-        LOG("Blocklist::_loadBlocklist: no XML File found");
-      }
-    }
-  },
-
-  /**
-#    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>
-#      <gfxItems>
-#        <gfxItem ... />
-#      </gfxItems>
-#    </blocklist>
-   */
-
-  _loadBlocklistFromFile(file) {
-    if (!gBlocklistEnabled) {
-      LOG("Blocklist::_loadBlocklistFromFile: blocklist is disabled");
-      return;
-    }
-
-    let text = "";
-    let fstream = null;
-    let cstream = null;
-
-    try {
-      fstream = Cc["@mozilla.org/network/file-input-stream;1"]
-                  .createInstance(Ci.nsIFileInputStream);
-      cstream = Cc["@mozilla.org/intl/converter-input-stream;1"]
-                  .createInstance(Ci.nsIConverterInputStream);
-
-      fstream.init(file, FileUtils.MODE_RDONLY, FileUtils.PERMS_FILE, 0);
-      cstream.init(fstream, "UTF-8", 0, 0);
-
-      let str = {};
-      let read = 0;
-
-      do {
-        read = cstream.readString(0xffffffff, str); // read as much as we can and put it in str.value
-        text += str.value;
-      } while (read != 0);
-    } finally {
-      // There's no catch block because the callers will catch exceptions,
-      // and may try to read another file if reading this file failed.
-      if (cstream) {
-        try {
-          cstream.close();
-        } catch (ex) {}
-      }
-      if (fstream) {
-        try {
-          fstream.close();
-        } catch (ex) {}
-      }
-    }
-
-    if (text)
-      this._loadBlocklistFromString(text);
-  },
-
-  /**
    * Whether or not we've finished loading the blocklist.
    */
   get isLoaded() {
     return this._addonEntries != null && this._gfxEntries != null && this._pluginEntries != null;
   },
 
   /* Used for testing */
   _clear() {
@@ -851,79 +792,84 @@ var Blocklist = {
       this._preloadPromise = this._loadBlocklistAsyncInternal();
     }
     await this._preloadPromise;
   },
 
   async _loadBlocklistAsyncInternal() {
     try {
       // Get the path inside the try...catch because there's no profileDir in e.g. xpcshell tests.
-      let profPath = OS.Path.join(OS.Constants.Path.profileDir, FILE_BLOCKLIST);
-      await this._preloadBlocklistFile(profPath);
+      let profFile = FileUtils.getFile(KEY_PROFILEDIR, [FILE_BLOCKLIST]);
+      await this._preloadBlocklistFile(profFile);
       return;
     } catch (e) {
       LOG("Blocklist::loadBlocklistAsync: Failed to load XML file " + e);
     }
 
     var appFile = FileUtils.getFile(KEY_APPDIR, [FILE_BLOCKLIST]);
     try {
-      await this._preloadBlocklistFile(appFile.path);
+      await this._preloadBlocklistFile(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._gfxEntries = [];
     this._pluginEntries = [];
   },
 
-  async _preloadBlocklistFile(path) {
+  async _preloadBlocklistFile(file) {
     if (this.isLoaded) {
       return;
     }
 
     if (!gBlocklistEnabled) {
       LOG("Blocklist::_preloadBlocklistFile: blocklist is disabled");
       return;
     }
 
-    let text = await OS.File.read(path, { encoding: "utf-8" });
+    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("_preloadBlocklistFile: 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, reject) => {
+    await new Promise(resolve => {
       ChromeUtils.idleDispatch(() => {
         if (!this.isLoaded) {
-          Services.telemetry.getHistogramById("BLOCKLIST_SYNC_FILE_LOAD").add(false);
-          try {
-            this._loadBlocklistFromString(text);
-          } catch (ex) {
-            // Loading the blocklist failed. Ensure the caller knows.
-            reject(ex);
-          }
+          this._loadBlocklistFromXML(xmlDoc);
         }
         resolve();
       });
     });
   },
 
-  _loadBlocklistFromString(text) {
-    var parser = new DOMParser();
-    var doc = parser.parseFromString(text, "text/xml");
-    if (doc.documentElement.namespaceURI != XMLURI_BLOCKLIST) {
-      LOG("Blocklist::_loadBlocklistFromString: aborting due to incorrect " +
-          "XML Namespace.\r\nExpected: " + XMLURI_BLOCKLIST + "\r\n" +
-          "Received: " + doc.documentElement.namespaceURI);
-      throw new Error("Couldn't find an XML doc with the right namespace!");
-    }
-    this._loadBlocklistFromXML(doc);
-  },
-
   _loadBlocklistFromXML(doc) {
     this._addonEntries = [];
     this._gfxEntries = [];
     this._pluginEntries = [];
     try {
       var childNodes = doc.documentElement.childNodes;
       for (let element of childNodes) {
         if (element.nodeType != doc.ELEMENT_NODE)
deleted file mode 100644
--- a/toolkit/mozapps/extensions/test/xpcshell/test_asyncBlocklistLoad.js
+++ /dev/null
@@ -1,46 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- * http://creativecommons.org/publicdomain/zero/1.0/
- */
-
-add_task(async function() {
-  let blocklist = Blocklist;
-  let scope = ChromeUtils.import("resource://gre/modules/osfile.jsm", {});
-
-  // sync -> async. Check that async code doesn't try to read the file
-  // once it's already been read synchronously.
-  let read = scope.OS.File.read;
-  let triedToRead = false;
-  scope.OS.File.read = () => triedToRead = true;
-  blocklist._loadBlocklist();
-  Assert.ok(blocklist.isLoaded);
-  await blocklist.loadBlocklistAsync();
-  Assert.ok(!triedToRead);
-  scope.OS.File.read = read;
-  blocklist._clear();
-
-  info("sync -> async complete");
-
-  // async first. Check that once we preload the content, that is sufficient.
-  await blocklist.loadBlocklistAsync();
-  Assert.ok(blocklist.isLoaded);
-  // Calling _loadBlocklist now would just re-load the list sync.
-
-  info("async test complete");
-  blocklist._clear();
-
-  // async -> sync -> async
-  scope.OS.File.read = function(...args) {
-    return new Promise((resolve, reject) => {
-      executeSoon(() => {
-        blocklist._loadBlocklist();
-        // Now do the async bit after all:
-        resolve(read(...args));
-      });
-    });
-  };
-
-  await blocklist.loadBlocklistAsync();
-  // We're mostly just checking this doesn't error out.
-  Assert.ok(blocklist.isLoaded);
-  info("mixed async/sync test complete");
-});
--- a/toolkit/mozapps/extensions/test/xpcshell/test_blocklist_gfx.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_blocklist_gfx.js
@@ -1,11 +1,14 @@
 ChromeUtils.import("resource://gre/modules/Services.jsm");
 ChromeUtils.import("resource://gre/modules/XPCOMUtils.jsm");
 
+XPCOMUtils.defineLazyGlobalGetters(this, ["DOMParser"]);
+
+const gParser = new DOMParser();
 
 const EVENT_NAME = "blocklist-data-gfxItems";
 
 const SAMPLE_GFX_RECORD = {
   "driverVersionComparator": "LESS_THAN_OR_EQUAL",
   "driverVersion": "8.17.12.5896",
   "vendor": "0x10de",
   "blockID": "g36",
@@ -19,84 +22,80 @@ const SAMPLE_GFX_RECORD = {
 };
 
 
 function getBlocklist() {
   Blocklist._clear();
   return Blocklist;
 }
 
+async function updateBlocklistWithInput(input) {
+  let blocklist = getBlocklist();
+  let promiseObserved = TestUtils.topicObserved(EVENT_NAME);
+  blocklist._loadBlocklistFromXML(gParser.parseFromString(input, "text/xml"));
+  let [, received] = await promiseObserved;
+  return [blocklist, received];
+}
+
 
 add_task(async function test_sends_serialized_data() {
   const blocklist = getBlocklist();
   blocklist._gfxEntries = [SAMPLE_GFX_RECORD];
 
   const expected = "blockID:g36\tdevices:0x0a6c,geforce\tdriverVersion:8.17.12.5896\t" +
                    "driverVersionComparator:LESS_THAN_OR_EQUAL\tfeature:DIRECT3D_9_LAYERS\t" +
                    "featureStatus:BLOCKED_DRIVER_VERSION\tos:WINNT 6.1\tvendor:0x10de\t" +
                    "versionRange:0,*";
-  let received;
-  const observe = (subject, topic, data) => { received = data; };
-  Services.obs.addObserver(observe, EVENT_NAME);
+  let promiseObserved = TestUtils.topicObserved(EVENT_NAME);
   blocklist._notifyObserversBlocklistGFX();
+  let [, received] = await promiseObserved;
   equal(received, expected);
-  Services.obs.removeObserver(observe, EVENT_NAME);
 });
 
 
 add_task(async function test_parsing_fails_if_devices_contains_comma() {
   const input = "<blocklist xmlns=\"http://www.mozilla.org/2006/addons-blocklist\">" +
   "<gfxItems>" +
   " <gfxBlacklistEntry>" +
   "   <devices>" +
   "     <device>0x2,582</device>" +
   "     <device>0x2782</device>" +
   "   </devices>" +
   " </gfxBlacklistEntry>" +
   "</gfxItems>" +
   "</blocklist>";
-  const blocklist = getBlocklist();
-  blocklist._loadBlocklistFromString(input);
+  let [blocklist] = await updateBlocklistWithInput(input);
+
   equal(blocklist._gfxEntries[0].devices.length, 1);
   equal(blocklist._gfxEntries[0].devices[0], "0x2782");
 });
 
 
 add_task(async function test_empty_values_are_ignored() {
   const input = "<blocklist xmlns=\"http://www.mozilla.org/2006/addons-blocklist\">" +
   "<gfxItems>" +
   " <gfxBlacklistEntry>" +
   "   <os></os>" +
   " </gfxBlacklistEntry>" +
   "</gfxItems>" +
   "</blocklist>";
-  const blocklist = getBlocklist();
-  let received;
-  const observe = (subject, topic, data) => { received = data; };
-  Services.obs.addObserver(observe, EVENT_NAME);
-  blocklist._loadBlocklistFromString(input);
+  let [, received] = await updateBlocklistWithInput(input);
   ok(received.indexOf("os" < 0));
-  Services.obs.removeObserver(observe, EVENT_NAME);
 });
 
 add_task(async function test_empty_devices_are_ignored() {
   const input = "<blocklist xmlns=\"http://www.mozilla.org/2006/addons-blocklist\">" +
   "<gfxItems>" +
   " <gfxBlacklistEntry>" +
   "   <devices></devices>" +
   " </gfxBlacklistEntry>" +
   "</gfxItems>" +
   "</blocklist>";
-  const blocklist = getBlocklist();
-  let received;
-  const observe = (subject, topic, data) => { received = data; };
-  Services.obs.addObserver(observe, EVENT_NAME);
-  blocklist._loadBlocklistFromString(input);
+  let [, received] = await updateBlocklistWithInput(input);
   ok(received.indexOf("devices" < 0));
-  Services.obs.removeObserver(observe, EVENT_NAME);
 });
 
 add_task(async function test_version_range_default_values() {
   const input = "<blocklist xmlns=\"http://www.mozilla.org/2006/addons-blocklist\">" +
   "<gfxItems>" +
   " <gfxBlacklistEntry>" +
   "   <versionRange minVersion=\"13.0b2\" maxVersion=\"42.0\"/>" +
   " </gfxBlacklistEntry>" +
@@ -109,18 +108,17 @@ add_task(async function test_version_ran
   " <gfxBlacklistEntry>" +
   "   <versionRange minVersion=\"  \"/>" +
   " </gfxBlacklistEntry>" +
   " <gfxBlacklistEntry>" +
   "   <versionRange/>" +
   " </gfxBlacklistEntry>" +
   "</gfxItems>" +
   "</blocklist>";
-  const blocklist = getBlocklist();
-  blocklist._loadBlocklistFromString(input);
+  let [blocklist] = await updateBlocklistWithInput(input);
   equal(blocklist._gfxEntries[0].versionRange.minVersion, "13.0b2");
   equal(blocklist._gfxEntries[0].versionRange.maxVersion, "42.0");
   equal(blocklist._gfxEntries[1].versionRange.minVersion, "0");
   equal(blocklist._gfxEntries[1].versionRange.maxVersion, "2.0");
   equal(blocklist._gfxEntries[2].versionRange.minVersion, "1.0");
   equal(blocklist._gfxEntries[2].versionRange.maxVersion, "*");
   equal(blocklist._gfxEntries[3].versionRange.minVersion, "0");
   equal(blocklist._gfxEntries[3].versionRange.maxVersion, "*");
@@ -134,13 +132,12 @@ add_task(async function test_blockid_att
   " <gfxBlacklistEntry blockID=\"g60\">" +
   "   <vendor> 0x10de </vendor>" +
   " </gfxBlacklistEntry>" +
   " <gfxBlacklistEntry>" +
   "   <feature> DIRECT3D_9_LAYERS </feature>" +
   " </gfxBlacklistEntry>" +
   "</gfxItems>" +
   "</blocklist>";
-  const blocklist = getBlocklist();
-  blocklist._loadBlocklistFromString(input);
+  let [blocklist] = await updateBlocklistWithInput(input);
   equal(blocklist._gfxEntries[0].blockID, "g60");
   ok(!blocklist._gfxEntries[1].hasOwnProperty("blockID"));
 });
--- a/toolkit/mozapps/extensions/test/xpcshell/test_overrideblocklist.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_overrideblocklist.js
@@ -42,19 +42,20 @@ function clearBlocklists() {
   if (blocklist.exists())
     blocklist.remove(true);
 
   blocklist = FileUtils.getFile(KEY_PROFILEDIR, [FILE_BLOCKLIST]);
   if (blocklist.exists())
     blocklist.remove(true);
 }
 
-function reloadBlocklist() {
+async function reloadBlocklist() {
   Services.prefs.setBoolPref(PREF_BLOCKLIST_ENABLED, false);
   Services.prefs.setBoolPref(PREF_BLOCKLIST_ENABLED, true);
+  await Blocklist._lastUpdate;
 }
 
 function copyToApp(file) {
   file.clone().copyTo(gAppDir, FILE_BLOCKLIST);
 }
 
 function copyToProfile(file, tstamp) {
   file = file.clone();
@@ -92,17 +93,17 @@ async function isBlocklisted(addon, appV
 // On first run whataver is in the app dir should get copied to the profile
 add_test(async function test_copy() {
   clearBlocklists();
   copyToApp(OLD);
 
   incrementAppVersion();
   await promiseStartupManager();
 
-  reloadBlocklist();
+  await reloadBlocklist();
   Assert.ok(!(await isBlocklisted(invalidAddon)));
   Assert.ok(!(await isBlocklisted(ancientAddon)));
   Assert.ok(await isBlocklisted(oldAddon));
   Assert.ok(!(await isBlocklisted(newAddon)));
 
   await promiseShutdownManager();
 
   run_next_test();
@@ -112,17 +113,17 @@ add_test(async function test_copy() {
 add_test(async function test_ancient() {
   clearBlocklists();
   copyToApp(ANCIENT);
   copyToProfile(OLD, OLD_TSTAMP);
 
   incrementAppVersion();
   await promiseStartupManager();
 
-  reloadBlocklist();
+  await reloadBlocklist();
 
   Assert.ok(!(await isBlocklisted(invalidAddon)));
   Assert.ok(!(await isBlocklisted(ancientAddon)));
   Assert.ok(await isBlocklisted(oldAddon));
   Assert.ok(!(await isBlocklisted(newAddon)));
 
   await promiseShutdownManager();
 
@@ -133,17 +134,17 @@ add_test(async function test_ancient() {
 add_test(async function test_override() {
   clearBlocklists();
   copyToApp(NEW);
   copyToProfile(OLD, OLD_TSTAMP);
 
   incrementAppVersion();
   await promiseStartupManager();
 
-  reloadBlocklist();
+  await reloadBlocklist();
 
   Assert.ok(!(await isBlocklisted(invalidAddon)));
   Assert.ok(!(await isBlocklisted(ancientAddon)));
   Assert.ok(!(await isBlocklisted(oldAddon)));
   Assert.ok(await isBlocklisted(newAddon));
 
   await promiseShutdownManager();
 
@@ -154,17 +155,17 @@ add_test(async function test_override() 
 add_test(async function test_retain() {
   clearBlocklists();
   copyToApp(OLD);
   copyToProfile(NEW, NEW_TSTAMP);
 
   incrementAppVersion();
   await promiseStartupManager();
 
-  reloadBlocklist();
+  await reloadBlocklist();
 
   Assert.ok(!(await isBlocklisted(invalidAddon)));
   Assert.ok(!(await isBlocklisted(ancientAddon)));
   Assert.ok(!(await isBlocklisted(oldAddon)));
   Assert.ok(await isBlocklisted(newAddon));
 
   await promiseShutdownManager();
 
@@ -180,17 +181,17 @@ add_test(async function test_missing() {
   incrementAppVersion();
   await promiseStartupManager();
   await promiseShutdownManager();
 
   let blocklist = FileUtils.getFile(KEY_PROFILEDIR, [FILE_BLOCKLIST]);
   blocklist.remove(true);
   await promiseStartupManager();
 
-  reloadBlocklist();
+  await reloadBlocklist();
 
   Assert.ok(!(await isBlocklisted(invalidAddon)));
   Assert.ok(!(await isBlocklisted(ancientAddon)));
   Assert.ok(await isBlocklisted(oldAddon));
   Assert.ok(!(await isBlocklisted(newAddon)));
 
   await promiseShutdownManager();
 
--- a/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini
+++ b/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini
@@ -12,18 +12,16 @@ support-files =
 # Bug 676992: test consistently hangs on Android
 skip-if = os == "android"
 [test_AddonRepository_paging.js]
 [test_LightweightThemeManager.js]
 [test_ProductAddonChecker.js]
 [test_XPIStates.js]
 [test_XPIcancel.js]
 [test_addonStartup.js]
-[test_asyncBlocklistLoad.js]
-tags = blocklist
 [test_backgroundupdate.js]
 [test_bad_json.js]
 [test_badschema.js]
 [test_blocklist_appversion.js]
 # Bug 676992: test consistently hangs on Android
 skip-if = os == "android"
 tags = blocklist
 [test_blocklist_gfx.js]