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 810885 cc7b04c1d78bf1884e210c2103a286d47e8c7c55
parent 810884 2d58c4a8e5820eeff80f6f626897bd121fd7f25a
push id114146
push userbmo:gijskruitbosch+bugs@gmail.com
push dateTue, 26 Jun 2018 17:15:11 +0000
reviewersflorian
bugs1454378
milestone63.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
--- a/toolkit/mozapps/extensions/Blocklist.jsm
+++ b/toolkit/mozapps/extensions/Blocklist.jsm
@@ -727,17 +727,17 @@ var Blocklist = {
 
     if (!this.isLoaded) {
       await this.loadBlocklistAsync();
     }
 
     var oldAddonEntries = this._addonEntries;
     var oldPluginEntries = this._pluginEntries;
 
-    this._loadBlocklistFromXML(responseXML);
+    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) {
@@ -773,77 +773,77 @@ 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", reject);
       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.\nExpected: " + XMLURI_BLOCKLIST + "\n" +
               "Received: " + doc.documentElement.namespaceURI);
@@ -851,45 +851,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();
       }
@@ -899,22 +899,26 @@ 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) {
     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;