Bug 1257547 - Remove coupling of Gfx code to XML file blocklist.xml draft
authorMathieu Leplatre <mathieu@mozilla.com>
Wed, 04 May 2016 16:12:19 +0200
changeset 363324 fac1310c919aa9c7a290a88e36da1490ebbc1406
parent 362943 0a25833062a880f369e6f9f622413a94cc671bf4
child 520007 1733ebe0389638ab16f3b49ec68c5af3672263bd
push id17171
push usermleplatre@mozilla.com
push dateWed, 04 May 2016 14:22:48 +0000
bugs1257547
milestone49.0a1
Bug 1257547 - Remove coupling of Gfx code to XML file blocklist.xml MozReview-Commit-ID: HDVVvsk077x
toolkit/mozapps/extensions/nsBlocklistService.js
toolkit/mozapps/extensions/test/xpcshell/test_blocklist_gfx.js
toolkit/mozapps/extensions/test/xpcshell/test_gfxBlacklist_prefs.js
toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini
widget/GfxInfoBase.cpp
--- a/toolkit/mozapps/extensions/nsBlocklistService.js
+++ b/toolkit/mozapps/extensions/nsBlocklistService.js
@@ -323,16 +323,17 @@ Blocklist.prototype = {
    *                 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,
+  _gfxEntries: null,
   _pluginEntries: null,
 
   shutdown: function() {
     Services.obs.removeObserver(this, "xpcom-shutdown");
     Services.ppmm.removeMessageListener("Blocklist:getPluginBlocklistState", this);
     Services.ppmm.removeMessageListener("Blocklist:content-blocklist-updated", this);
     gPref.removeObserver("extensions.blocklist.", this);
     gPref.removeObserver(PREF_EM_LOGGING_ENABLED, this);
@@ -655,19 +656,22 @@ Blocklist.prototype = {
         (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);
+    // 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);
       yield OS.File.writeAtomic(path, request.responseText, {tmpPath: path + ".tmp"});
     } catch (e) {
       LOG("Blocklist::onXMLLoad: " + e);
     }
@@ -696,16 +700,17 @@ Blocklist.prototype = {
   },
 
   /**
    * Finds the newest blocklist file from the application and the profile and
    * load it or does nothing if neither exist.
    */
   _loadBlocklist: function() {
     this._addonEntries = [];
+    this._gfxEntries = [];
     this._pluginEntries = [];
     var profFile = FileUtils.getFile(KEY_PROFILEDIR, [FILE_BLOCKLIST]);
     if (profFile.exists()) {
       this._loadBlocklistFromFile(profFile);
       return;
     }
     var appFile = FileUtils.getFile(KEY_APPDIR, [FILE_BLOCKLIST]);
     if (appFile.exists()) {
@@ -832,26 +837,27 @@ Blocklist.prototype = {
         fstream.close();
     }
 
     if (text)
         this._loadBlocklistFromString(text);
   },
 
   _isBlocklistLoaded: function() {
-    return this._addonEntries != null && this._pluginEntries != null;
+    return this._addonEntries != null && this._gfxEntries != null && this._pluginEntries != null;
   },
 
   _isBlocklistPreloaded: function() {
     return this._preloadedBlocklistContent != null;
   },
 
   /* Used for testing */
   _clear: function() {
     this._addonEntries = null;
+    this._gfxEntries = null;
     this._pluginEntries = null;
     this._preloadedBlocklistContent = null;
   },
 
   _preloadBlocklist: Task.async(function*() {
     let profPath = OS.Path.join(OS.Constants.Path.profileDir, FILE_BLOCKLIST);
     try {
       yield this._preloadBlocklistFile(profPath);
@@ -907,57 +913,62 @@ Blocklist.prototype = {
       var childNodes = doc.documentElement.childNodes;
       for (let element of childNodes) {
         if (!(element instanceof Ci.nsIDOMElement))
           continue;
         switch (element.localName) {
         case "emItems":
           // Special case for b2g, since we don't use the addon manager.
           if (AppConstants.MOZ_B2G) {
-            let extensions = this._processItemNodes(element.childNodes, "em",
+            let extensions = this._processItemNodes(element.childNodes, "emItem",
                                                     this._handleEmItemNode);
             DOMApplicationRegistry.blockExtensions(extensions);
             return;
           }
-          this._addonEntries = this._processItemNodes(element.childNodes, "em",
+          this._addonEntries = this._processItemNodes(element.childNodes, "emItem",
                                                       this._handleEmItemNode);
           break;
         case "pluginItems":
           // We don't support plugins on b2g.
           if (AppConstants.MOZ_B2G) {
             return;
           }
-          this._pluginEntries = this._processItemNodes(element.childNodes, "plugin",
+          this._pluginEntries = this._processItemNodes(element.childNodes, "pluginItem",
                                                        this._handlePluginItemNode);
           break;
         case "certItems":
           if (populateCertBlocklist) {
-            this._processItemNodes(element.childNodes, "cert",
+            this._processItemNodes(element.childNodes, "certItem",
                                    this._handleCertItemNode.bind(this));
           }
           break;
+        case "gfxItems":
+          // Parse as simple list of objects.
+          this._gfxEntries = this._processItemNodes(element.childNodes, "gfxBlacklistEntry",
+                                                    this._handleGfxBlacklistNode);
+          break;
         default:
-          Services.obs.notifyObservers(element,
-                                       "blocklist-data-" + element.localName,
-                                       null);
+          LOG("Blocklist::_loadBlocklistFromString: ignored entries " + element.localName);
         }
       }
       if (populateCertBlocklist) {
         gCertBlocklistService.saveEntries();
       }
+      if (this._gfxEntries.length > 0) {
+        this._notifyObserversBlocklistGFX();
+      }
     }
     catch (e) {
       LOG("Blocklist::_loadBlocklistFromFile: Error constructing blocklist " + e);
       return;
     }
   },
 
-  _processItemNodes: function(itemNodes, prefix, handler) {
+  _processItemNodes: function(itemNodes, itemName, handler) {
     var result = [];
-    var itemName = prefix + "Item";
     for (var i = 0; i < itemNodes.length; ++i) {
       var blocklistElement = itemNodes.item(i);
       if (!(blocklistElement instanceof Ci.nsIDOMElement) ||
           blocklistElement.localName != itemName)
         continue;
 
       handler(blocklistElement, result);
     }
@@ -1084,16 +1095,85 @@ Blocklist.prototype = {
     if (blockEntry.versions.length == 0)
       blockEntry.versions.push(new BlocklistItemData(null));
 
     blockEntry.blockID = blocklistElement.getAttribute("blockID");
 
     result.push(blockEntry);
   },
 
+  // <gfxBlacklistEntry blockID="g60">
+  //   <os>WINNT 6.0</os>
+  //   <osversion>14</osversion> currently only used for Android
+  //   <versionRange minVersion="42.0" maxVersion="13.0b2"/>
+  //   <vendor>0x8086</vendor>
+  //   <devices>
+  //     <device>0x2582</device>
+  //     <device>0x2782</device>
+  //   </devices>
+  //   <feature> DIRECT3D_10_LAYERS </feature>
+  //   <featureStatus> BLOCKED_DRIVER_VERSION </featureStatus>
+  //   <driverVersion> 8.52.322.2202 </driverVersion>
+  //   <driverVersionMax> 8.52.322.2202 </driverVersionMax>
+  //   <driverVersionComparator> LESS_THAN_OR_EQUAL </driverVersionComparator>
+  //   <model>foo</model>
+  //   <product>foo</product>
+  //   <manufacturer>foo</manufacturer>
+  //   <hardware>foo</hardware>
+  // </gfxBlacklistEntry>
+  _handleGfxBlacklistNode: function (blocklistElement, result) {
+    const blockEntry = {};
+
+    // The blockID attribute is always present in the actual data produced on server
+    // (see https://github.com/mozilla/addons-server/blob/2016.05.05/src/olympia/blocklist/templates/blocklist/blocklist.xml#L74)
+    // But it is sometimes missing in test fixtures.
+    if (blocklistElement.hasAttribute("blockID")) {
+      blockEntry.blockID = blocklistElement.getAttribute("blockID");
+    }
+
+    // Trim helper (spaces, tabs, no-break spaces..)
+    const trim = (s) => (s || '').replace(/(^[\s\uFEFF\xA0]+)|([\s\uFEFF\xA0]+$)/g, "");
+
+    for (let i = 0; i < blocklistElement.childNodes.length; ++i) {
+      var matchElement = blocklistElement.childNodes.item(i);
+      if (!(matchElement instanceof Ci.nsIDOMElement))
+        continue;
+
+      let value;
+      if (matchElement.localName == "devices") {
+        value = [];
+        for (let j = 0; j < matchElement.childNodes.length; j++) {
+          const childElement = matchElement.childNodes.item(j);
+          const childValue = trim(childElement.textContent);
+          // Make sure no empty value is added.
+          if (childValue) {
+            if (/,/.test(childValue)) {
+              // Devices can't contain comma.
+              // (c.f serialization in _notifyObserversBlocklistGFX)
+              const e = new Error(`Unsupported device name ${childValue}`);
+              Components.utils.reportError(e);
+            }
+            else {
+              value.push(childValue);
+            }
+          }
+        }
+      } else if (matchElement.localName == "versionRange") {
+        value = {minVersion: trim(matchElement.getAttribute("minVersion")) || "0",
+                 maxVersion: trim(matchElement.getAttribute("maxVersion")) || "*"};
+      } else {
+        value = trim(matchElement.textContent);
+      }
+      if (value) {
+        blockEntry[matchElement.localName] = value;
+      }
+    }
+    result.push(blockEntry);
+  },
+
   /* See nsIBlocklistService */
   getPluginBlocklistState: function(plugin, appVersion, toolkitVersion) {
     if (AppConstants.platform == "android" ||
         AppConstants.MOZ_B2G) {
       return Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
     } else {
       if (!this._isBlocklistLoaded())
         this._loadBlocklist();
@@ -1226,16 +1306,34 @@ Blocklist.prototype = {
     let {entry: blockEntry, version: blockEntryVersion} = r;
     if (!blockEntry.blockID) {
       return null;
     }
 
     return blockEntry.infoURL;
   },
 
+  _notifyObserversBlocklistGFX: function () {
+    // Notify `GfxInfoBase`, by passing a string serialization.
+    // This way we avoid spreading XML structure logics there.
+    const payload = this._gfxEntries.map((r) => {
+      return Object.keys(r).sort().filter((k) => !/id|last_modified/.test(k)).map((key) => {
+        let value = r[key];
+        if (Array.isArray(value)) {
+          value = value.join(",");
+        } else if (value.hasOwnProperty("minVersion")) {
+          // When XML is parsed, both minVersion and maxVersion are set.
+          value = `${value.minVersion},${value.maxVersion}`;
+        }
+        return `${key}:${value}`;
+      }).join("\t");
+    }).join("\n");
+    Services.obs.notifyObservers(null, "blocklist-data-gfxItems", payload);
+  },
+
   _notifyObserversBlocklistUpdated: function() {
     Services.obs.notifyObservers(this, "blocklist-updated", "");
     Services.ppmm.broadcastAsyncMessage("Blocklist:blocklistInvalidated", {});
   },
 
   _blocklistUpdated: function(oldAddonEntries, oldPluginEntries) {
     if (AppConstants.MOZ_B2G) {
       return;
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_blocklist_gfx.js
@@ -0,0 +1,157 @@
+const { classes: Cc, interfaces: Ci, utils: Cu } = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+const TEST_APP_ID            = "xpcshell@tests.mozilla.org";
+
+
+const EVENT_NAME = "blocklist-data-gfxItems";
+
+const SAMPLE_GFX_RECORD = {
+  "driverVersionComparator": "LESS_THAN_OR_EQUAL",
+  "driverVersion": "8.17.12.5896",
+  "vendor": "0x10de",
+  "blockID": "g36",
+  "feature": "DIRECT3D_9_LAYERS",
+  "devices": ["0x0a6c", "geforce"],
+  "featureStatus": "BLOCKED_DRIVER_VERSION",
+  "last_modified": 1458035931837,
+  "os": "WINNT 6.1",
+  "id": "3f947f16-37c2-4e96-d356-78b26363729b",
+  "versionRange": {"minVersion": 0, "maxVersion": "*"}
+};
+
+
+function Blocklist() {
+  let blocklist = Cc["@mozilla.org/extensions/blocklist;1"].
+                  getService().wrappedJSObject;
+  blocklist._clear();
+  return blocklist;
+}
+
+
+function run_test() {
+  run_next_test();
+}
+
+
+add_task(function* test_sends_serialized_data() {
+  const blocklist = Blocklist();
+  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, false);
+  blocklist._notifyObserversBlocklistGFX();
+  equal(received, expected);
+  Services.obs.removeObserver(observe, EVENT_NAME);
+});
+
+
+add_task(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 = Blocklist();
+  blocklist._loadBlocklistFromString(input);
+  equal(blocklist._gfxEntries[0].devices.length, 1);
+  equal(blocklist._gfxEntries[0].devices[0], "0x2782");
+});
+
+
+add_task(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 = Blocklist();
+  let received;
+  const observe = (subject, topic, data) => { received = data };
+  Services.obs.addObserver(observe, EVENT_NAME, false);
+  blocklist._loadBlocklistFromString(input);
+  ok(received.indexOf("os" < 0));
+  Services.obs.removeObserver(observe, EVENT_NAME);
+});
+
+add_task(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, false);
+  blocklist._loadBlocklistFromString(input);
+  ok(received.indexOf("devices" < 0));
+  Services.obs.removeObserver(observe, EVENT_NAME);
+});
+
+add_task(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>" +
+  " <gfxBlacklistEntry>" +
+  "   <versionRange maxVersion=\"2.0\"/>" +
+  " </gfxBlacklistEntry>" +
+  " <gfxBlacklistEntry>" +
+  "   <versionRange minVersion=\"1.0\"/>" +
+  " </gfxBlacklistEntry>" +
+  " <gfxBlacklistEntry>" +
+  "   <versionRange minVersion=\"  \"/>" +
+  " </gfxBlacklistEntry>" +
+  " <gfxBlacklistEntry>" +
+  "   <versionRange/>" +
+  " </gfxBlacklistEntry>" +
+  "</gfxItems>" +
+  "</blocklist>";
+  const blocklist = Blocklist();
+  blocklist._loadBlocklistFromString(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, "*");
+  equal(blocklist._gfxEntries[4].versionRange.minVersion, "0");
+  equal(blocklist._gfxEntries[4].versionRange.maxVersion, "*");
+});
+
+add_task(function* test_blockid_attribute() {
+  const input = "<blocklist xmlns=\"http://www.mozilla.org/2006/addons-blocklist\">" +
+  "<gfxItems>" +
+  " <gfxBlacklistEntry blockID=\"g60\">" +
+  "   <vendor> 0x10de </vendor>" +
+  " </gfxBlacklistEntry>" +
+  " <gfxBlacklistEntry>" +
+  "   <feature> DIRECT3D_9_LAYERS </feature>" +
+  " </gfxBlacklistEntry>" +
+  "</gfxItems>" +
+  "</blocklist>";
+  const blocklist = Blocklist();
+  blocklist._loadBlocklistFromString(input);
+  equal(blocklist._gfxEntries[0].blockID, "g60");
+  ok(!blocklist._gfxEntries[1].hasOwnProperty("blockID"));
+});
--- a/toolkit/mozapps/extensions/test/xpcshell/test_gfxBlacklist_prefs.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_gfxBlacklist_prefs.js
@@ -9,16 +9,17 @@ var { classes: Cc, interfaces: Ci } = Co
 // Uses test_gfxBlacklist.xml and test_gfxBlacklist2.xml
 
 Components.utils.import("resource://testing-common/httpd.js");
 
 var gTestserver = new HttpServer();
 gTestserver.start(-1);
 gPort = gTestserver.identity.primaryPort;
 mapFile("/data/test_gfxBlacklist.xml", gTestserver);
+mapFile("/data/test_gfxBlacklist2.xml", gTestserver);
 
 function get_platform() {
   var xulRuntime = Components.classes["@mozilla.org/xre/app-info;1"]
                              .getService(Components.interfaces.nsIXULRuntime);
   return xulRuntime.OS;
 }
 
 function load_blocklist(file) {
--- a/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini
+++ b/toolkit/mozapps/extensions/test/xpcshell/xpcshell.ini
@@ -6,16 +6,17 @@ tail =
 firefox-appdir = browser
 dupe-manifest =
 support-files =
   data/**
   xpcshell-shared.ini
 
 [test_addon_path_service.js]
 [test_asyncBlocklistLoad.js]
+[test_blocklist_gfx.js]
 [test_cache_certdb.js]
 run-if = addon_signing
 [test_cacheflush.js]
 [test_DeferredSave.js]
 [test_gmpProvider.js]
 skip-if = appname != "firefox"
 [test_hotfix_cert.js]
 [test_isReady.js]
--- a/widget/GfxInfoBase.cpp
+++ b/widget/GfxInfoBase.cpp
@@ -233,52 +233,16 @@ SetPrefValueForDriverVersion(const nsASt
 }
 
 static void
 RemovePrefForDriverVersion()
 {
   Preferences::ClearUser(SUGGESTED_VERSION_PREF);
 }
 
-// <foo>Hello</foo> - "Hello" is stored as a child text node of the foo node.
-static bool
-BlacklistNodeToTextValue(nsIDOMNode *aBlacklistNode, nsAString& aValue)
-{
-  nsAutoString value;
-  if (NS_FAILED(aBlacklistNode->GetTextContent(value)))
-    return false;
-
-  value.Trim(" \t\r\n");
-  aValue = value;
-
-  return true;
-}
-
-// <foo attr=Hello/> finds "Hello" if the aAttrName is "attr".
-static bool
-BlacklistAttrToTextValue(nsIDOMNode *aBlacklistNode,
-                         const nsAString& aAttrName,
-                         nsAString& aValue)
-{
-  nsCOMPtr<nsIDOMElement> element = do_QueryInterface(aBlacklistNode);
-  if (!element) {
-    return false;
-  }
-
-  nsAutoString value;
-  if (NS_FAILED(element->GetAttribute(aAttrName, value))) {
-    return false;
-  }
-
-  value.Trim(" \t\r\n");
-  aValue = value;
-
-  return true;
-}
-
 
 static OperatingSystem
 BlacklistOSToOperatingSystem(const nsAString& os)
 {
   if (os.EqualsLiteral("WINNT 5.1"))
     return DRIVER_OS_WINDOWS_XP;
   else if (os.EqualsLiteral("WINNT 5.2"))
     return DRIVER_OS_WINDOWS_SERVER_2003;
@@ -312,36 +276,29 @@ BlacklistOSToOperatingSystem(const nsASt
     return DRIVER_OS_ANDROID;
   else if (os.EqualsLiteral("All"))
     return DRIVER_OS_ALL;
 
   return DRIVER_OS_UNKNOWN;
 }
 
 static GfxDeviceFamily*
-BlacklistDevicesToDeviceFamily(nsIDOMHTMLCollection* aDevices)
+BlacklistDevicesToDeviceFamily(nsTArray<nsCString>& devices)
 {
-  uint32_t length;
-  if (NS_FAILED(aDevices->GetLength(&length)))
+  if (devices.Length() == 0)
     return nullptr;
 
-  // For each <device>, get its device ID, and return a freshly-allocated
+  // For each device, get its device ID, and return a freshly-allocated
   // GfxDeviceFamily with the contents of that array.
   GfxDeviceFamily* deviceIds = new GfxDeviceFamily;
 
-  for (uint32_t i = 0; i < length; ++i) {
-    nsCOMPtr<nsIDOMNode> node;
-    if (NS_FAILED(aDevices->Item(i, getter_AddRefs(node))) || !node)
-      continue;
-
-    nsAutoString deviceValue;
-    if (!BlacklistNodeToTextValue(node, deviceValue))
-      continue;
-
-    deviceIds->AppendElement(deviceValue);
+  for (uint32_t i = 0; i < devices.Length(); ++i) {
+    // We make sure we don't add any "empty" device entries to the array, so
+    // we don't need to check if devices[i] is empty.
+    deviceIds->AppendElement(NS_ConvertUTF8toUTF16(devices[i]));
   }
 
   return deviceIds;
 }
 
 static int32_t
 BlacklistFeatureToGfxFeature(const nsAString& aFeature)
 {
@@ -427,278 +384,172 @@ BlacklistComparatorToComparisonOp(const 
   else if (op.EqualsLiteral("BETWEEN_INCLUSIVE"))
     return DRIVER_BETWEEN_INCLUSIVE;
   else if (op.EqualsLiteral("BETWEEN_INCLUSIVE_START"))
     return DRIVER_BETWEEN_INCLUSIVE_START;
 
   return DRIVER_COMPARISON_IGNORED;
 }
 
-// Arbitrarily returns the first |tagname| child of |element|.
-static bool
-BlacklistNodeGetChildByName(nsIDOMElement *element,
-                            const nsAString& tagname,
-                            nsIDOMNode** firstchild)
-{
-  nsCOMPtr<nsIDOMHTMLCollection> nodelist;
-  if (NS_FAILED(element->GetElementsByTagName(tagname,
-                                              getter_AddRefs(nodelist))) ||
-      !nodelist) {
-    return false;
-  }
-
-  nsCOMPtr<nsIDOMNode> node;
-  if (NS_FAILED(nodelist->Item(0, getter_AddRefs(node))) || !node)
-    return false;
-
-  node.forget(firstchild);
-  return true;
-}
 
 /*
-
-<gfxBlacklistEntry>
-  <os>WINNT 6.0</os>
-  <vendor>0x8086</vendor>
-  <devices>
-    <device>0x2582</device>
-    <device>0x2782</device>
-  </devices>
-  <feature> DIRECT3D_10_LAYERS </feature>
-  <featureStatus> BLOCKED_DRIVER_VERSION </featureStatus>
-  <driverVersion> 8.52.322.2202 </driverVersion>
-  <driverVersionComparator> LESS_THAN_OR_EQUAL </driverVersionComparator>
-</gfxBlacklistEntry>
-
+  Deserialize Blacklist entries from string.
+  e.g:
+  os:WINNT 6.0\tvendor:0x8086\tdevices:0x2582,0x2782\tfeature:DIRECT3D_10_LAYERS\tfeatureStatus:BLOCKED_DRIVER_VERSION\tdriverVersion:8.52.322.2202\tdriverVersionComparator:LESS_THAN_OR_EQUAL
 */
 static bool
-BlacklistEntryToDriverInfo(nsIDOMNode* aBlacklistEntry,
+BlacklistEntryToDriverInfo(nsCString& aBlacklistEntry,
                            GfxDriverInfo& aDriverInfo)
 {
-  nsAutoString nodename;
-  if (NS_FAILED(aBlacklistEntry->GetNodeName(nodename)) ||
-      nodename != NS_LITERAL_STRING(BLACKLIST_ENTRY_TAG_NAME)) {
-    return false;
-  }
-
-  nsCOMPtr<nsIDOMElement> element = do_QueryInterface(aBlacklistEntry);
-  if (!element)
-    return false;
-
-  nsCOMPtr<nsIDOMNode> dataNode;
-  nsAutoString dataValue;
-
   // If we get an application version to be zero, something is not working
   // and we are not going to bother checking the blocklist versions.
   // See TestGfxWidgets.cpp for how version comparison works.
   // <versionRange minVersion="42.0a1" maxVersion="45.0"></versionRange>
   static mozilla::Version zeroV("0");
   static mozilla::Version appV(GfxInfoBase::GetApplicationVersion().get());
   if (appV <= zeroV) {
       gfxCriticalErrorOnce(gfxCriticalError::DefaultOptions(false)) << "Invalid application version " << GfxInfoBase::GetApplicationVersion().get();
-  } else if (BlacklistNodeGetChildByName(element,
-                                         NS_LITERAL_STRING("versionRange"),
-                                         getter_AddRefs(dataNode))) {
-    if (BlacklistAttrToTextValue(dataNode,
-                                 NS_LITERAL_STRING("minVersion"),
-                                 dataValue)) {
-      mozilla::Version minV(NS_ConvertUTF16toUTF8(dataValue).get());
-      if (minV > zeroV && appV < minV) {
+  }
+
+  nsTArray<nsCString> keyValues;
+  ParseString(aBlacklistEntry, '\t', keyValues);
+
+  aDriverInfo.mRuleId = NS_LITERAL_CSTRING("FEATURE_FAILURE_DL_BLACKLIST_NO_ID");
+
+  for (uint32_t i = 0; i < keyValues.Length(); ++i) {
+    nsCString keyValue = keyValues[i];
+    nsTArray<nsCString> splitted;
+    ParseString(keyValue, ':', splitted);
+    if (splitted.Length() != 2) {
+      // If we don't recognize the input data, we do not want to proceed.
+      gfxCriticalErrorOnce(CriticalLog::DefaultOptions(false)) << "Unrecognized data " << keyValue.get();
+      return false;
+    }
+    nsCString key = splitted[0];
+    nsCString value = splitted[1];
+    NS_ConvertUTF8toUTF16 dataValue(value);
+
+    if (value.Length() == 0) {
+      // Safety check for empty values.
+      gfxCriticalErrorOnce(CriticalLog::DefaultOptions(false)) << "Empty value for " << key.get();
+      return false;
+    }
+
+    if (key.EqualsLiteral("blockID")) {
+       nsCString blockIdStr = NS_LITERAL_CSTRING("FEATURE_FAILURE_DL_BLACKLIST_") + value;
+       aDriverInfo.mRuleId = blockIdStr.get();
+    } else if (key.EqualsLiteral("os")) {
+      aDriverInfo.mOperatingSystem = BlacklistOSToOperatingSystem(dataValue);
+    } else if (key.EqualsLiteral("osversion")) {
+      aDriverInfo.mOperatingSystemVersion = strtoul(value.get(), nullptr, 10);
+    } else if (key.EqualsLiteral("vendor")) {
+      aDriverInfo.mAdapterVendor = dataValue;
+    } else if (key.EqualsLiteral("feature")) {
+      aDriverInfo.mFeature = BlacklistFeatureToGfxFeature(dataValue);
+      if (aDriverInfo.mFeature < 0) {
+        // If we don't recognize the feature, we do not want to proceed.
+        gfxCriticalErrorOnce(CriticalLog::DefaultOptions(false)) << "Unrecognized feature " << value.get();
+        return false;
+      }
+    } else if (key.EqualsLiteral("featureStatus")) {
+      aDriverInfo.mFeatureStatus = BlacklistFeatureStatusToGfxFeatureStatus(dataValue);
+    } else if (key.EqualsLiteral("driverVersion")) {
+      uint64_t version;
+      if (ParseDriverVersion(dataValue, &version))
+        aDriverInfo.mDriverVersion = version;
+    } else if (key.EqualsLiteral("driverVersionMax")) {
+      uint64_t version;
+      if (ParseDriverVersion(dataValue, &version))
+        aDriverInfo.mDriverVersionMax = version;
+    } else if (key.EqualsLiteral("driverVersionMax")) {
+      uint64_t version;
+      if (ParseDriverVersion(dataValue, &version))
+        aDriverInfo.mDriverVersionMax = version;
+    } else if (key.EqualsLiteral("driverVersionComparator")) {
+      aDriverInfo.mComparisonOp = BlacklistComparatorToComparisonOp(dataValue);
+    } else if (key.EqualsLiteral("model")) {
+      aDriverInfo.mModel = dataValue;
+    } else if (key.EqualsLiteral("product")) {
+      aDriverInfo.mProduct = dataValue;
+    } else if (key.EqualsLiteral("manufacturer")) {
+      aDriverInfo.mManufacturer = dataValue;
+    } else if (key.EqualsLiteral("hardware")) {
+      aDriverInfo.mHardware = dataValue;
+    } else if (key.EqualsLiteral("versionRange")) {
+      nsTArray<nsCString> versionRange;
+      ParseString(value, ',', versionRange);
+      if (versionRange.Length() != 2) {
+        gfxCriticalErrorOnce(CriticalLog::DefaultOptions(false)) << "Unrecognized versionRange " << value.get();
+        return false;
+      }
+      nsCString minValue = versionRange[0];
+      nsCString maxValue = versionRange[1];
+
+      mozilla::Version minV(minValue.get());
+      mozilla::Version maxV(maxValue.get());
+
+      if (minV > zeroV && !(appV >= minV)) {
         // The version of the application is less than the minimal version
         // this blocklist entry applies to, so we can just ignore it by
         // returning false and letting the caller deal with it.
         return false;
       }
-    }
-
-    if (BlacklistAttrToTextValue(dataNode,
-                                 NS_LITERAL_STRING("maxVersion"),
-                                 dataValue)) {
-      mozilla::Version maxV(NS_ConvertUTF16toUTF8(dataValue).get());
-      if (maxV > zeroV && appV > maxV) {
+      if (maxV > zeroV && !(appV <= maxV)) {
         // The version of the application is more than the maximal version
         // this blocklist entry applies to, so we can just ignore it by
         // returning false and letting the caller deal with it.
         return false;
       }
-    }
-  }
-
-  // <os>WINNT 6.0</os>
-  if (BlacklistNodeGetChildByName(element, NS_LITERAL_STRING("os"),
-                                  getter_AddRefs(dataNode))) {
-    BlacklistNodeToTextValue(dataNode, dataValue);
-    aDriverInfo.mOperatingSystem = BlacklistOSToOperatingSystem(dataValue);
-  }
-
-  // <osversion>14</osversion> currently only used for Android
-  if (BlacklistNodeGetChildByName(element, NS_LITERAL_STRING("osversion"),
-                                  getter_AddRefs(dataNode))) {
-    BlacklistNodeToTextValue(dataNode, dataValue);
-    aDriverInfo.mOperatingSystemVersion = strtoul(NS_LossyConvertUTF16toASCII(dataValue).get(),
-                                                  nullptr, 10);
-  }
-
-  // <vendor>0x8086</vendor>
-  if (BlacklistNodeGetChildByName(element, NS_LITERAL_STRING("vendor"),
-                                  getter_AddRefs(dataNode))) {
-    BlacklistNodeToTextValue(dataNode, dataValue);
-    aDriverInfo.mAdapterVendor = dataValue;
-  }
-
-  // <devices>
-  //   <device>0x2582</device>
-  //   <device>0x2782</device>
-  // </devices>
-  if (BlacklistNodeGetChildByName(element, NS_LITERAL_STRING("devices"),
-                                  getter_AddRefs(dataNode))) {
-    nsCOMPtr<nsIDOMElement> devicesElement = do_QueryInterface(dataNode);
-    if (devicesElement) {
-
-      // Get only the <device> nodes, because BlacklistDevicesToDeviceFamily
-      // assumes it is passed no other nodes.
-      nsCOMPtr<nsIDOMHTMLCollection> devices;
-      if (NS_SUCCEEDED(devicesElement->GetElementsByTagName(NS_LITERAL_STRING("device"),
-                                                            getter_AddRefs(devices)))) {
-        GfxDeviceFamily* deviceIds = BlacklistDevicesToDeviceFamily(devices);
-        if (deviceIds) {
-          // Get GfxDriverInfo to adopt the devices array we created.
-          aDriverInfo.mDeleteDevices = true;
-          aDriverInfo.mDevices = deviceIds;
-        }
+    } else if (key.EqualsLiteral("devices")) {
+      nsTArray<nsCString> devices;
+      ParseString(value, ',', devices);
+      GfxDeviceFamily* deviceIds = BlacklistDevicesToDeviceFamily(devices);
+      if (deviceIds) {
+        // Get GfxDriverInfo to adopt the devices array we created.
+        aDriverInfo.mDeleteDevices = true;
+        aDriverInfo.mDevices = deviceIds;
       }
     }
-  }
-
-  // <feature> DIRECT3D_10_LAYERS </feature>
-  if (BlacklistNodeGetChildByName(element, NS_LITERAL_STRING("feature"),
-                                  getter_AddRefs(dataNode))) {
-    BlacklistNodeToTextValue(dataNode, dataValue);
-    aDriverInfo.mFeature = BlacklistFeatureToGfxFeature(dataValue);
-    if (aDriverInfo.mFeature < 0) {
-      // If we don't recognize the feature, we do not want to proceed.
-      gfxWarning() << "Unrecognized feature " << NS_ConvertUTF16toUTF8(dataValue).get();
-      return false;
-    }
-  }
-
-  // <featureStatus> BLOCKED_DRIVER_VERSION </featureStatus>
-  if (BlacklistNodeGetChildByName(element, NS_LITERAL_STRING("featureStatus"),
-                                  getter_AddRefs(dataNode))) {
-    BlacklistNodeToTextValue(dataNode, dataValue);
-    aDriverInfo.mFeatureStatus = BlacklistFeatureStatusToGfxFeatureStatus(dataValue);
-  }
-
-  // <driverVersion> 8.52.322.2202 </driverVersion>
-  if (BlacklistNodeGetChildByName(element, NS_LITERAL_STRING("driverVersion"),
-                                  getter_AddRefs(dataNode))) {
-    BlacklistNodeToTextValue(dataNode, dataValue);
-    uint64_t version;
-    if (ParseDriverVersion(dataValue, &version))
-      aDriverInfo.mDriverVersion = version;
-  }
-
-  // <driverVersionMax> 8.52.322.2202 </driverVersionMax>
-  if (BlacklistNodeGetChildByName(element, NS_LITERAL_STRING("driverVersionMax"),
-                                  getter_AddRefs(dataNode))) {
-    BlacklistNodeToTextValue(dataNode, dataValue);
-    uint64_t version;
-    if (ParseDriverVersion(dataValue, &version))
-      aDriverInfo.mDriverVersionMax = version;
+    // We explicitly ignore unknown elements.
   }
 
-  // <driverVersionComparator> LESS_THAN_OR_EQUAL </driverVersionComparator>
-  if (BlacklistNodeGetChildByName(element, NS_LITERAL_STRING("driverVersionComparator"),
-                                  getter_AddRefs(dataNode))) {
-    BlacklistNodeToTextValue(dataNode, dataValue);
-    aDriverInfo.mComparisonOp = BlacklistComparatorToComparisonOp(dataValue);
-  }
-
-  // <model>foo</model>
-  if (BlacklistNodeGetChildByName(element, NS_LITERAL_STRING("model"),
-                                  getter_AddRefs(dataNode))) {
-    BlacklistNodeToTextValue(dataNode, dataValue);
-    aDriverInfo.mModel = dataValue;
-  }
-  // <product>foo</product>
-  if (BlacklistNodeGetChildByName(element, NS_LITERAL_STRING("product"),
-                                  getter_AddRefs(dataNode))) {
-    BlacklistNodeToTextValue(dataNode, dataValue);
-    aDriverInfo.mProduct = dataValue;
-  }
-  // <manufacturer>foo</manufacturer>
-  if (BlacklistNodeGetChildByName(element, NS_LITERAL_STRING("manufacturer"),
-                                  getter_AddRefs(dataNode))) {
-    BlacklistNodeToTextValue(dataNode, dataValue);
-    aDriverInfo.mManufacturer = dataValue;
-  }
-  // <hardware>foo</hardware>
-  if (BlacklistNodeGetChildByName(element, NS_LITERAL_STRING("hardware"),
-                                  getter_AddRefs(dataNode))) {
-    BlacklistNodeToTextValue(dataNode, dataValue);
-    aDriverInfo.mHardware = dataValue;
-  }
-  if (BlacklistAttrToTextValue(element,
-                               NS_LITERAL_STRING("blockID"),
-                               dataValue) && !dataValue.IsEmpty()) {
-    nsCString blockIdStr = NS_LITERAL_CSTRING("FEATURE_FAILURE_DL_BLACKLIST_") +
-                           NS_ConvertUTF16toUTF8(dataValue);
-    aDriverInfo.mRuleId = blockIdStr.get();
-  } else {
-    aDriverInfo.mRuleId = NS_LITERAL_CSTRING("FEATURE_FAILURE_DL_BLACKLIST_NO_ID");
-  }
-
-  // We explicitly ignore unknown elements.
-
   return true;
 }
 
 static void
-BlacklistEntriesToDriverInfo(nsIDOMHTMLCollection* aBlacklistEntries,
+BlacklistEntriesToDriverInfo(nsTArray<nsCString>& aBlacklistEntries,
                              nsTArray<GfxDriverInfo>& aDriverInfo)
 {
-  uint32_t length;
-  if (NS_FAILED(aBlacklistEntries->GetLength(&length)))
-    return;
+  aDriverInfo.Clear();
+  aDriverInfo.SetLength(aBlacklistEntries.Length());
 
-  aDriverInfo.Clear();
-  aDriverInfo.SetLength(length);
-  for (uint32_t i = 0; i < length; ++i) {
-    nsCOMPtr<nsIDOMNode> blacklistEntry;
-    if (NS_SUCCEEDED(aBlacklistEntries->Item(i,
-                                             getter_AddRefs(blacklistEntry))) &&
-        blacklistEntry) {
-      GfxDriverInfo di;
-      if (BlacklistEntryToDriverInfo(blacklistEntry, di)) {
-        aDriverInfo[i] = di;
-        // Prevent di falling out of scope from destroying the devices.
-        di.mDeleteDevices = false;
-      }
+  for (uint32_t i = 0; i < aBlacklistEntries.Length(); ++i) {
+    nsCString blacklistEntry = aBlacklistEntries[i];
+    GfxDriverInfo di;
+    if (BlacklistEntryToDriverInfo(blacklistEntry, di)) {
+      aDriverInfo[i] = di;
+      // Prevent di falling out of scope from destroying the devices.
+      di.mDeleteDevices = false;
     }
   }
 }
 
 NS_IMETHODIMP
 GfxInfoBase::Observe(nsISupports* aSubject, const char* aTopic,
                      const char16_t* aData)
 {
   if (strcmp(aTopic, "blocklist-data-gfxItems") == 0) {
-    nsCOMPtr<nsIDOMElement> gfxItems = do_QueryInterface(aSubject);
-    if (gfxItems) {
-      nsCOMPtr<nsIDOMHTMLCollection> blacklistEntries;
-      if (NS_SUCCEEDED(gfxItems->
-            GetElementsByTagName(NS_LITERAL_STRING(BLACKLIST_ENTRY_TAG_NAME),
-                                 getter_AddRefs(blacklistEntries))) &&
-          blacklistEntries)
-      {
-        nsTArray<GfxDriverInfo> driverInfo;
-        BlacklistEntriesToDriverInfo(blacklistEntries, driverInfo);
-        EvaluateDownloadedBlacklist(driverInfo);
-      }
+    nsTArray<GfxDriverInfo> driverInfo;
+    nsTArray<nsCString> blacklistEntries;
+    nsCString utf8Data = NS_ConvertUTF16toUTF8(aData);
+    if (utf8Data.Length() > 0) {
+      ParseString(utf8Data, '\n', blacklistEntries);
     }
+    BlacklistEntriesToDriverInfo(blacklistEntries, driverInfo);
+    EvaluateDownloadedBlacklist(driverInfo);
   }
 
   return NS_OK;
 }
 
 GfxInfoBase::GfxInfoBase()
     : mMutex("GfxInfoBase")
 {
@@ -765,17 +616,17 @@ GfxInfoBase::FindBlocklistedDeviceInList
                                          OperatingSystem os)
 {
   int32_t status = nsIGfxInfo::FEATURE_STATUS_UNKNOWN;
 
   uint32_t i = 0;
   for (; i < info.Length(); i++) {
     // Do the operating system check first, no point in getting the driver
     // info if we won't need to use it.  If the OS of the system we are running
-    // on is unknown, we still let DRIVER_OS_ALL catch and disable it; 
+    // on is unknown, we still let DRIVER_OS_ALL catch and disable it;
     // if the OS of the downloadable entry is unknown, we skip the entry
     // as invalid.
     if (info[i].mOperatingSystem == DRIVER_OS_UNKNOWN ||
         (info[i].mOperatingSystem != DRIVER_OS_ALL &&
          info[i].mOperatingSystem != os))
     {
       continue;
     }