Bug 1257565 - Refactor to (pre)load multiple files r=mossop draft
authorMathieu Leplatre <mathieu@mozilla.com>
Thu, 15 Jun 2017 16:19:41 +0200
changeset 655874 c4c5b6d928adb599a1dbf74b50c73e130d8006ff
parent 649871 c7c96eebbcb91e5e0c8ef0dbbb5324812fa1e476
child 655875 a6e455380cdbffe70f270203921765eae3c16f33
push id76975
push usermleplatre@mozilla.com
push dateWed, 30 Aug 2017 12:43:51 +0000
reviewersmossop
bugs1257565
milestone57.0a1
Bug 1257565 - Refactor to (pre)load multiple files r=mossop Refactor to (pre)load multiple filesns/nsBlocklistService.js MozReview-Commit-ID: 15DsdekCdze
toolkit/mozapps/extensions/nsBlocklistService.js
toolkit/mozapps/extensions/test/xpcshell/test_asyncBlocklistLoad.js
toolkit/mozapps/extensions/test/xpcshell/test_blocklist_gfx.js
--- a/toolkit/mozapps/extensions/nsBlocklistService.js
+++ b/toolkit/mozapps/extensions/nsBlocklistService.js
@@ -273,16 +273,18 @@ function parseRegExp(aStr) {
 /**
  * Manages the Blocklist. The Blocklist is a representation of the contents of
  * blocklist.xml and allows us to remotely disable / re-enable blocklisted
  * items managed by the Extension Manager with an item's appDisabled property.
  * It also blocklists plugins with data from blocklist.xml.
  */
 
 function Blocklist() {
+  this._preloadedBlocklistContent = new Map();
+
   Services.obs.addObserver(this, "xpcom-shutdown");
   Services.obs.addObserver(this, "sessionstore-windows-restored");
   gLoggingEnabled = getPref("getBoolPref", PREF_EM_LOGGING_ENABLED, false);
   gBlocklistEnabled = getPref("getBoolPref", PREF_BLOCKLIST_ENABLED, true);
   gBlocklistLevel = Math.min(getPref("getIntPref", PREF_BLOCKLIST_LEVEL, DEFAULT_LEVEL),
                                      MAX_BLOCK_LEVEL);
   gPref.addObserver("extensions.blocklist.", this);
   gPref.addObserver(PREF_EM_LOGGING_ENABLED, this);
@@ -658,21 +660,18 @@ Blocklist.prototype = {
     if (!responseXML || responseXML.documentElement.namespaceURI == XMLURI_PARSE_ERROR ||
         (request.status != 200 && request.status != 0)) {
       LOG("Blocklist::onXMLLoad: there was an error during load");
       return;
     }
 
     var oldAddonEntries = this._addonEntries;
     var oldPluginEntries = this._pluginEntries;
-    this._addonEntries = [];
-    this._gfxEntries = [];
-    this._pluginEntries = [];
 
-    this._loadBlocklistFromString(request.responseText);
+    this._loadBlocklistFromXMLString(request.responseText);
     // 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) {
@@ -701,30 +700,134 @@ Blocklist.prototype = {
         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 = [];
-    var profFile = FileUtils.getFile(KEY_PROFILEDIR, [FILE_BLOCKLIST]);
-    if (profFile.exists()) {
-      this._loadBlocklistFromFile(profFile);
+    if (!gBlocklistEnabled) {
+      LOG("Blocklist::_loadBlocklistFromFile: blocklist is disabled");
+      return;
+    }
+    const content = this._loadBlocklistFromFile(FILE_BLOCKLIST);
+    this._loadBlocklistFromXMLString(content);
+  },
+
+  _loadBlocklistFromFile(filename) {
+    let file = FileUtils.getFile(KEY_PROFILEDIR, [filename]);
+    if (!file.exists()) {
+      let appFile = FileUtils.getFile(KEY_APPDIR, [filename]);
+      if (appFile.exists()) {
+        file = appFile;
+      }
+    }
+
+    let telemetry = Services.telemetry;
+
+    // Check if preloaded content exists for this file.
+    if (this._preloadedBlocklistContent.has(file.path)) {
+      telemetry.getHistogramById("BLOCKLIST_SYNC_FILE_LOAD").add(false);
+      const text = this._preloadedBlocklistContent.get(file.path);
+      this._preloadedBlocklistContent.delete(file.path);
+      return text;
+    }
+
+    if (!file.exists()) {
+      LOG("Blocklist::_loadBlocklistFromFile: File does not exist " + file.path);
       return;
     }
-    var appFile = FileUtils.getFile(KEY_APPDIR, [FILE_BLOCKLIST]);
-    if (appFile.exists()) {
-      this._loadBlocklistFromFile(appFile);
+
+    telemetry.getHistogramById("BLOCKLIST_SYNC_FILE_LOAD").add(true);
+
+    let text = "";
+    let fstream = null;
+    let cstream = null;
+
+    try {
+      fstream = Components.classes["@mozilla.org/network/file-input-stream;1"]
+                          .createInstance(Components.interfaces.nsIFileInputStream);
+      cstream = Components.classes["@mozilla.org/intl/converter-input-stream;1"]
+                          .createInstance(Components.interfaces.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);
+    } catch (e) {
+      LOG("Blocklist::_loadBlocklistFromFile: Failed to load file " + e);
+    } finally {
+      if (cstream)
+        cstream.close();
+      if (fstream)
+        fstream.close();
+    }
+
+    return text;
+  },
+
+  _isBlocklistLoaded() {
+    return this._addonEntries != null && this._gfxEntries != null && this._pluginEntries != null;
+  },
+
+  /* Used for testing */
+  _clear() {
+    this._addonEntries = null;
+    this._gfxEntries = null;
+    this._pluginEntries = null;
+    this._preloadedBlocklistContent.clear();
+  },
+
+  async _preloadBlocklist() {
+    if (!gBlocklistEnabled) {
+      LOG("Blocklist::_preloadBlocklist: blocklist is disabled");
       return;
     }
-    LOG("Blocklist::_loadBlocklist: no XML File found");
+
+    await this._preloadBlocklistFile(FILE_BLOCKLIST);
+  }),
+
+  async _preloadBlocklistFile(filename) {
+    let file = FileUtils.getFile(KEY_PROFILEDIR, [filename]);
+    if (!file.exists()) {
+      let appFile = FileUtils.getFile(KEY_APPDIR, [filename]);
+      if (appFile.exists()) {
+        file = appFile;
+      } else {
+        LOG(`Blocklist::_preloadBlocklistFile: no ${filename} file found`);
+        return;
+      }
+    }
+    const path = file.path;
+    if (this._preloadedBlocklistContent.has(path)) {
+      // The file has been already loaded.
+      return;
+    }
+
+    try {
+      let content = await OS.File.read(path, { encoding: "utf-8" });
+
+      if (!this._isBlocklistLoaded()) {
+        // Store the content only if a sync load has not been performed in the meantime.
+        this._preloadedBlocklistContent.set(path, content);
+      }
+    } catch (e) {
+      LOG(`Blocklist::_preloadBlocklistFile: Failed to load ${path} file : ${e}`);
+    }
+  },
+
+  _isBlocklistLoaded() {
+    return this._addonEntries != null && this._gfxEntries != null && this._pluginEntries != null;
   },
 
   /**
 #    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">
@@ -782,134 +885,27 @@ Blocklist.prototype = {
 #          <serialNumber>AkHVNA==</serialNumber>
 #        </certItem>
 #        <!-- subject is the DER subject name data base64 encoded... -->
 #        <certItem subject="MA0xCzAJBgNVBAMMAmNh" pubKeyHash="/xeHA5s+i9/z9d8qy6JEuE1xGoRYIwgJuTE/lmaGJ7M=">
 #        </certItem>
 #      </certItems>
 #    </blocklist>
    */
-
-  _loadBlocklistFromFile(file) {
-    if (!gBlocklistEnabled) {
-      LOG("Blocklist::_loadBlocklistFromFile: blocklist is disabled");
-      return;
-    }
-
-    let telemetry = Services.telemetry;
-
-    if (this._isBlocklistPreloaded()) {
-      telemetry.getHistogramById("BLOCKLIST_SYNC_FILE_LOAD").add(false);
-      this._loadBlocklistFromString(this._preloadedBlocklistContent);
-      delete this._preloadedBlocklistContent;
-      return;
-    }
-
-    if (!file.exists()) {
-      LOG("Blocklist::_loadBlocklistFromFile: XML File does not exist " + file.path);
-      return;
-    }
-
-    telemetry.getHistogramById("BLOCKLIST_SYNC_FILE_LOAD").add(true);
-
-    let text = "";
-    let fstream = null;
-    let cstream = null;
-
-    try {
-      fstream = Components.classes["@mozilla.org/network/file-input-stream;1"]
-                          .createInstance(Components.interfaces.nsIFileInputStream);
-      cstream = Components.classes["@mozilla.org/intl/converter-input-stream;1"]
-                          .createInstance(Components.interfaces.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);
-    } catch (e) {
-      LOG("Blocklist::_loadBlocklistFromFile: Failed to load XML file " + e);
-    } finally {
-      if (cstream)
-        cstream.close();
-      if (fstream)
-        fstream.close();
-    }
-
-    if (text)
-        this._loadBlocklistFromString(text);
-  },
+  _loadBlocklistFromXMLString(text) {
+    this._addonEntries = [];
+    this._gfxEntries = [];
+    this._pluginEntries = [];
 
-  _isBlocklistLoaded() {
-    return this._addonEntries != null && this._gfxEntries != null && this._pluginEntries != null;
-  },
-
-  _isBlocklistPreloaded() {
-    return this._preloadedBlocklistContent != null;
-  },
-
-  /* Used for testing */
-  _clear() {
-    this._addonEntries = null;
-    this._gfxEntries = null;
-    this._pluginEntries = null;
-    this._preloadedBlocklistContent = null;
-  },
-
-  async _preloadBlocklist() {
-    let profPath = OS.Path.join(OS.Constants.Path.profileDir, FILE_BLOCKLIST);
-    try {
-      await this._preloadBlocklistFile(profPath);
-      return;
-    } catch (e) {
-      LOG("Blocklist::_preloadBlocklist: Failed to load XML file " + e)
-    }
-
-    var appFile = FileUtils.getFile(KEY_APPDIR, [FILE_BLOCKLIST]);
-    try {
-      await this._preloadBlocklistFile(appFile.path);
-      return;
-    } catch (e) {
-      LOG("Blocklist::_preloadBlocklist: Failed to load XML file " + e)
-    }
-
-    LOG("Blocklist::_preloadBlocklist: no XML File found");
-  },
-
-  async _preloadBlocklistFile(path) {
-    if (this._addonEntries) {
-      // The file has been already loaded.
-      return;
-    }
-
-    if (!gBlocklistEnabled) {
-      LOG("Blocklist::_preloadBlocklistFile: blocklist is disabled");
-      return;
-    }
-
-    let text = await OS.File.read(path, { encoding: "utf-8" });
-
-    if (!this._addonEntries) {
-      // Store the content only if a sync load has not been performed in the meantime.
-      this._preloadedBlocklistContent = text;
-    }
-  },
-
-  _loadBlocklistFromString(text) {
     try {
       var parser = Cc["@mozilla.org/xmlextras/domparser;1"].
                    createInstance(Ci.nsIDOMParser);
       var doc = parser.parseFromString(text, "text/xml");
       if (doc.documentElement.namespaceURI != XMLURI_BLOCKLIST) {
-        LOG("Blocklist::_loadBlocklistFromFile: aborting due to incorrect " +
+        LOG("Blocklist::_loadBlocklistFromXMLString: aborting due to incorrect " +
             "XML Namespace.\r\nExpected: " + XMLURI_BLOCKLIST + "\r\n" +
             "Received: " + doc.documentElement.namespaceURI);
         return;
       }
 
       var populateCertBlocklist = getPref("getBoolPref", PREF_ONECRL_VIA_AMO, true);
 
       var childNodes = doc.documentElement.childNodes;
@@ -932,27 +928,27 @@ Blocklist.prototype = {
           }
           break;
         case "gfxItems":
           // Parse as simple list of objects.
           this._gfxEntries = this._processItemNodes(element.childNodes, "gfxBlacklistEntry",
                                                     this._handleGfxBlacklistNode);
           break;
         default:
-          LOG("Blocklist::_loadBlocklistFromString: ignored entries " + element.localName);
+          LOG("Blocklist::_loadBlocklistFromXMLString: ignored entries " + element.localName);
         }
       }
       if (populateCertBlocklist) {
         gCertBlocklistService.saveEntries();
       }
       if (this._gfxEntries.length > 0) {
         this._notifyObserversBlocklistGFX();
       }
     } catch (e) {
-      LOG("Blocklist::_loadBlocklistFromFile: Error constructing blocklist " + e);
+      LOG("Blocklist::_loadBlocklistFromXMLString: Error constructing blocklist " + e);
     }
   },
 
   _processItemNodes(itemNodes, itemName, handler) {
     var result = [];
     for (var i = 0; i < itemNodes.length; ++i) {
       var blocklistElement = itemNodes.item(i);
       if (!(blocklistElement instanceof Ci.nsIDOMElement) ||
--- a/toolkit/mozapps/extensions/test/xpcshell/test_asyncBlocklistLoad.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_asyncBlocklistLoad.js
@@ -1,44 +1,51 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/
  */
 
+XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
+                                  "resource://gre/modules/FileUtils.jsm");
+
+const KEY_PROFILEDIR = "ProfD";
+
 function run_test() {
   run_next_test();
 }
 
 add_task(async function() {
   let blocklist = AM_Cc["@mozilla.org/extensions/blocklist;1"].
                   getService().wrappedJSObject;
   let scope = Components.utils.import("resource://gre/modules/osfile.jsm", {});
 
+  const blocklistPath = FileUtils.getFile(KEY_PROFILEDIR, ["blocklist.xml"]).path;
+
   // sync -> async
   blocklist._loadBlocklist();
   do_check_true(blocklist._isBlocklistLoaded());
   await blocklist._preloadBlocklist();
-  do_check_false(blocklist._isBlocklistPreloaded());
+  do_check_false(blocklist._preloadedBlocklistContent.has(blocklistPath));
   blocklist._clear();
 
   // async -> sync
   await blocklist._preloadBlocklist();
   do_check_false(blocklist._isBlocklistLoaded());
-  do_check_true(blocklist._isBlocklistPreloaded());
+  do_check_true(blocklist._preloadedBlocklistContent.has(blocklistPath));
   blocklist._loadBlocklist();
   do_check_true(blocklist._isBlocklistLoaded());
-  do_check_false(blocklist._isBlocklistPreloaded());
+  do_check_false(blocklist._preloadedBlocklistContent.has(blocklistPath));
   blocklist._clear();
 
   // async -> sync -> async
   let read = scope.OS.File.read;
   scope.OS.File.read = function(...args) {
     return new Promise((resolve, reject) => {
       do_execute_soon(() => {
         blocklist._loadBlocklist();
         resolve(read(...args));
       });
     });
   }
 
   await blocklist._preloadBlocklist();
   do_check_true(blocklist._isBlocklistLoaded());
-  do_check_false(blocklist._isBlocklistPreloaded());
+  do_check_false(blocklist._preloadedBlocklistContent.has(blocklistPath));
 });
--- a/toolkit/mozapps/extensions/test/xpcshell/test_blocklist_gfx.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_blocklist_gfx.js
@@ -58,17 +58,17 @@ add_task(async function test_parsing_fai
   "   <devices>" +
   "     <device>0x2,582</device>" +
   "     <device>0x2782</device>" +
   "   </devices>" +
   " </gfxBlacklistEntry>" +
   "</gfxItems>" +
   "</blocklist>";
   const blocklist = Blocklist();
-  blocklist._loadBlocklistFromString(input);
+  blocklist._loadBlocklistFromXMLString(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>" +
@@ -76,34 +76,34 @@ add_task(async function test_empty_value
   "   <os></os>" +
   " </gfxBlacklistEntry>" +
   "</gfxItems>" +
   "</blocklist>";
   const blocklist = Blocklist();
   let received;
   const observe = (subject, topic, data) => { received = data };
   Services.obs.addObserver(observe, EVENT_NAME);
-  blocklist._loadBlocklistFromString(input);
+  blocklist._loadBlocklistFromXMLString(input);
   ok(received.indexOf("os" < 0));
   Services.obs.removeObserver(observe, EVENT_NAME);
 });
 
 add_task(async function test_empty_devices_are_ignored() {
   const input = "<blocklist xmlns=\"http://www.mozilla.org/2006/addons-blocklist\">" +
   "<gfxItems>" +
   " <gfxBlacklistEntry>" +
   "   <devices></devices>" +
   " </gfxBlacklistEntry>" +
   "</gfxItems>" +
   "</blocklist>";
   const blocklist = Blocklist();
   let received;
   const observe = (subject, topic, data) => { received = data };
   Services.obs.addObserver(observe, EVENT_NAME);
-  blocklist._loadBlocklistFromString(input);
+  blocklist._loadBlocklistFromXMLString(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>" +
@@ -119,17 +119,17 @@ add_task(async function test_version_ran
   "   <versionRange minVersion=\"  \"/>" +
   " </gfxBlacklistEntry>" +
   " <gfxBlacklistEntry>" +
   "   <versionRange/>" +
   " </gfxBlacklistEntry>" +
   "</gfxItems>" +
   "</blocklist>";
   const blocklist = Blocklist();
-  blocklist._loadBlocklistFromString(input);
+  blocklist._loadBlocklistFromXMLString(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, "*");
@@ -144,12 +144,12 @@ add_task(async function test_blockid_att
   "   <vendor> 0x10de </vendor>" +
   " </gfxBlacklistEntry>" +
   " <gfxBlacklistEntry>" +
   "   <feature> DIRECT3D_9_LAYERS </feature>" +
   " </gfxBlacklistEntry>" +
   "</gfxItems>" +
   "</blocklist>";
   const blocklist = Blocklist();
-  blocklist._loadBlocklistFromString(input);
+  blocklist._loadBlocklistFromXMLString(input);
   equal(blocklist._gfxEntries[0].blockID, "g60");
   ok(!blocklist._gfxEntries[1].hasOwnProperty("blockID"));
 });