Bug 1454378 - chunk blocklist processing so it doesn't hang the main thread continuously, r?florian draft
authorGijs Kruitbosch <gijskruitbosch@gmail.com>
Wed, 13 Jun 2018 17:16:59 -0700
changeset 807835 307da375c9af61a79353d15f19c2219e4afca0ae
parent 807834 10eb9961b2f45ae488ff24921cb3350b5ba658a0
push id113224
push userbmo:gijskruitbosch+bugs@gmail.com
push dateFri, 15 Jun 2018 20:35:10 +0000
reviewersflorian
bugs1454378
milestone62.0a1
Bug 1454378 - chunk blocklist processing so it doesn't hang the main thread continuously, r?florian MozReview-Commit-ID: 70cIeuVdy3D
toolkit/mozapps/extensions/Blocklist.jsm
toolkit/mozapps/extensions/test/xpcshell/test_blocklist_gfx.js
--- a/toolkit/mozapps/extensions/Blocklist.jsm
+++ b/toolkit/mozapps/extensions/Blocklist.jsm
@@ -766,79 +766,79 @@ var Blocklist = {
     return this._addonEntries != null && this._gfxEntries != null && this._pluginEntries != null;
   },
 
   /* Used for testing */
   _clear() {
     this._addonEntries = null;
     this._gfxEntries = null;
     this._pluginEntries = null;
-    delete this._preloadPromise;
+    delete this._loadPromise;
   },
 
   /**
    * Trigger loading the blocklist content asynchronously.
    */
   async loadBlocklistAsync() {
     if (this.isLoaded) {
       return;
     }
-    if (!this._preloadPromise) {
-      this._preloadPromise = this._loadBlocklistAsyncInternal();
+    if (!this._loadPromise) {
+      this._loadPromise = this._loadBlocklistAsyncInternal();
     }
-    await this._preloadPromise;
+    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._preloadBlocklistFile(profFile);
+      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._preloadBlocklistFile(appFile);
+      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._gfxEntries = [];
     this._pluginEntries = [];
   },
 
-  async _preloadBlocklistFile(file) {
+  async _loadFileInternal(file) {
     if (this.isLoaded) {
       return;
     }
 
     if (!gBlocklistEnabled) {
-      LOG("Blocklist::_preloadBlocklistFile: blocklist is disabled");
+      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", function(err) {
         reject(err);
       });
       request.addEventListener("load", function() {
         let {status} = request;
         if (status != 200 && status != 0) {
-          LOG("_preloadBlocklistFile: there was an error during load, got status: " + status);
+          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.\r\nExpected: " + XMLURI_BLOCKLIST + "\r\n" +
               "Received: " + doc.documentElement.namespaceURI);
@@ -846,45 +846,45 @@ var Blocklist = {
           return;
         }
         resolve(doc);
       });
       request.send(null);
     });
 
     await new Promise(resolve => {
-      ChromeUtils.idleDispatch(() => {
+      ChromeUtils.idleDispatch(async () => {
         if (!this.isLoaded) {
-          this._loadBlocklistFromXML(xmlDoc);
+          await this._loadBlocklistFromXML(xmlDoc);
         }
         resolve();
       });
     });
   },
 
-  _loadBlocklistFromXML(doc) {
+  async _loadBlocklistFromXML(doc) {
     this._addonEntries = [];
     this._gfxEntries = [];
     this._pluginEntries = [];
     try {
       var children = doc.documentElement.children;
       for (let element of children) {
         switch (element.localName) {
         case "emItems":
-          this._addonEntries = this._processItemNodes(element.children, "emItem",
-                                                      this._handleEmItemNode);
+          this._addonEntries = await this._processItemNodes(element.children, "emItem",
+                                                            this._handleEmItemNode);
           break;
         case "pluginItems":
-          this._pluginEntries = this._processItemNodes(element.children, "pluginItem",
-                                                       this._handlePluginItemNode);
+          this._pluginEntries = await this._processItemNodes(element.children, "pluginItem",
+                                                             this._handlePluginItemNode);
           break;
         case "gfxItems":
           // Parse as simple list of objects.
-          this._gfxEntries = this._processItemNodes(element.children, "gfxBlacklistEntry",
-                                                    this._handleGfxBlacklistNode);
+          this._gfxEntries = await this._processItemNodes(element.children, "gfxBlacklistEntry",
+                                                          this._handleGfxBlacklistNode);
           break;
         default:
           LOG("Blocklist::_loadBlocklistFromXML: ignored entries " + element.localName);
         }
       }
       if (this._gfxEntries.length > 0) {
         this._notifyObserversBlocklistGFX();
       }
@@ -894,23 +894,29 @@ var Blocklist = {
     // 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");
     });
   },
 
-  _processItemNodes(items, itemName, handler) {
+  async _processItemNodes(items, itemName, handler) {
+    let now = Date.now();
+    const MAX_SLICE = 100;
     var result = [];
     for (let item of items) {
       if (item.localName != itemName)
         continue;
 
       handler(item, result);
+      if (Date.now() - now > MAX_SLICE) {
+        await new Promise(ChromeUtils.idleDispatch);
+        now = Date.now();
+      }
     }
     return result;
   },
 
   _handleEmItemNode(blocklistElement, result) {
     if (!matchesOSABI(blocklistElement))
       return;
 
--- a/toolkit/mozapps/extensions/test/xpcshell/test_blocklist_gfx.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_blocklist_gfx.js
@@ -53,17 +53,17 @@ add_task(async function test_parsing_fai
   "     <device>0x2,582</device>" +
   "     <device>0x2782</device>" +
   "   </devices>" +
   " </gfxBlacklistEntry>" +
   "</gfxItems>" +
   "</blocklist>";
   const blocklist = getBlocklist();
 
-  blocklist._loadBlocklistFromXML(gParser.parseFromString(input, "text/xml"));
+  await blocklist._loadBlocklistFromXML(gParser.parseFromString(input, "text/xml"));
   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>" +
@@ -71,34 +71,34 @@ add_task(async function test_empty_value
   "   <os></os>" +
   " </gfxBlacklistEntry>" +
   "</gfxItems>" +
   "</blocklist>";
   const blocklist = getBlocklist();
   let received;
   const observe = (subject, topic, data) => { received = data; };
   Services.obs.addObserver(observe, EVENT_NAME);
-  blocklist._loadBlocklistFromXML(gParser.parseFromString(input, "text/xml"));
+  await blocklist._loadBlocklistFromXML(gParser.parseFromString(input, "text/xml"));
   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._loadBlocklistFromXML(gParser.parseFromString(input, "text/xml"));
+  await blocklist._loadBlocklistFromXML(gParser.parseFromString(input, "text/xml"));
   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>" +
@@ -114,17 +114,17 @@ add_task(async function test_version_ran
   "   <versionRange minVersion=\"  \"/>" +
   " </gfxBlacklistEntry>" +
   " <gfxBlacklistEntry>" +
   "   <versionRange/>" +
   " </gfxBlacklistEntry>" +
   "</gfxItems>" +
   "</blocklist>";
   const blocklist = getBlocklist();
-  blocklist._loadBlocklistFromXML(gParser.parseFromString(input, "text/xml"));
+  await blocklist._loadBlocklistFromXML(gParser.parseFromString(input, "text/xml"));
   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, "*");
@@ -139,12 +139,12 @@ add_task(async function test_blockid_att
   "   <vendor> 0x10de </vendor>" +
   " </gfxBlacklistEntry>" +
   " <gfxBlacklistEntry>" +
   "   <feature> DIRECT3D_9_LAYERS </feature>" +
   " </gfxBlacklistEntry>" +
   "</gfxItems>" +
   "</blocklist>";
   const blocklist = getBlocklist();
-  blocklist._loadBlocklistFromXML(gParser.parseFromString(input, "text/xml"));
+  await blocklist._loadBlocklistFromXML(gParser.parseFromString(input, "text/xml"));
   equal(blocklist._gfxEntries[0].blockID, "g60");
   ok(!blocklist._gfxEntries[1].hasOwnProperty("blockID"));
 });