Bug 897735 - Support regular expression filters for name and creator in extension blocks. r=Unfocused
authorSachin Hosmani <sachinhosmani2@gmail.com>
Mon, 06 Jan 2014 14:16:32 +0530
changeset 163307 c8e2cb87f6c163f7fbbc1fcd5cf9bb3565d2163b
parent 163306 716a6ec17ceca5d433e04a18685db82c1a75b129
child 163308 8c0180723f67e61122da211c0d8243d557dd8233
push id25992
push userkwierso@gmail.com
push dateTue, 14 Jan 2014 23:05:14 +0000
treeherdermozilla-central@6e825843f9d9 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersUnfocused
bugs897735
milestone29.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 897735 - Support regular expression filters for name and creator in extension blocks. r=Unfocused CLOSED TREE
CLOBBER
browser/base/content/aboutDialog.js
browser/base/content/test/social/browser_blocklist.js
browser/metro/base/content/flyoutpanels/AboutFlyoutPanel.js
toolkit/components/social/SocialService.jsm
toolkit/mozapps/extensions/AddonUpdateChecker.jsm
toolkit/mozapps/extensions/XPIProvider.jsm
toolkit/mozapps/extensions/nsBlocklistService.js
toolkit/mozapps/extensions/test/xpcshell/data/test_blocklist_metadata_filters_1.xml
toolkit/mozapps/extensions/test/xpcshell/data/test_bug393285.xml
toolkit/mozapps/extensions/test/xpcshell/test_blocklist_metadata_filters.js
toolkit/mozapps/extensions/test/xpcshell/test_bug335238.js
toolkit/mozapps/extensions/test/xpcshell/test_bug393285.js
toolkit/mozapps/extensions/test/xpcshell/test_bug406118.js
toolkit/mozapps/extensions/test/xpcshell/test_bug455906.js
toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini
toolkit/mozapps/update/content/updates.js
toolkit/mozapps/update/nsUpdateService.js
xpcom/system/nsIBlocklistService.idl
--- a/CLOBBER
+++ b/CLOBBER
@@ -17,9 +17,9 @@
 #
 # Modifying this file will now automatically clobber the buildbot machines \o/
 #
 
 # Are you updating CLOBBER because you think it's needed for your WebIDL
 # changes to stick? As of bug 928195, this shouldn't be necessary! Please
 # don't change CLOBBER for WebIDL changes any more.
 
-Bug 950298 requires clobber because it changes where nsinstall is picked and it was previously installed there with wrong permissions. Also, the directory where js is built changed.
+Bug 897735 requires a clobber due to mass mochitest bustage otherwise
--- a/browser/base/content/aboutDialog.js
+++ b/browser/base/content/aboutDialog.js
@@ -458,17 +458,17 @@ appUpdater.prototype =
       }
     }
   },
 
   /**
    * See XPIProvider.jsm
    */
   onUpdateAvailable: function(aAddon, aInstall) {
-    if (!Services.blocklist.isAddonBlocklisted(aAddon.id, aInstall.version,
+    if (!Services.blocklist.isAddonBlocklisted(aAddon,
                                                this.update.appVersion,
                                                this.update.platformVersion)) {
       // Compatibility or new version updates mean the same thing here.
       this.onCompatibilityUpdateAvailable(aAddon);
     }
   },
 
   /**
--- a/browser/base/content/test/social/browser_blocklist.js
+++ b/browser/base/content/test/social/browser_blocklist.js
@@ -31,20 +31,20 @@ function test() {
     resetBlocklist(finish); //restore to original pref
   });
 }
 
 var tests = {
   testSimpleBlocklist: function(next) {
     // this really just tests adding and clearing our blocklist for later tests
     setAndUpdateBlocklist(blocklistURL, function() {
-      ok(Services.blocklist.isAddonBlocklisted("test1.example.com@services.mozilla.org", "0", "0", "0"), "blocking 'blocked'");
-      ok(!Services.blocklist.isAddonBlocklisted("example.com@services.mozilla.org", "0", "0", "0"), "not blocking 'good'");
+      ok(Services.blocklist.isAddonBlocklisted(SocialService.createWrapper(manifest_bad)), "blocking 'blocked'");
+      ok(!Services.blocklist.isAddonBlocklisted(SocialService.createWrapper(manifest)), "not blocking 'good'");
       resetBlocklist(function() {
-        ok(!Services.blocklist.isAddonBlocklisted("test1.example.com@services.mozilla.org", "0", "0", "0"), "blocklist cleared");
+        ok(!Services.blocklist.isAddonBlocklisted(SocialService.createWrapper(manifest_bad)), "blocklist cleared");
         next();
       });
     });
   },
   testAddingNonBlockedProvider: function(next) {
     function finish(isgood) {
       ok(isgood, "adding non-blocked provider ok");
       Services.prefs.clearUserPref("social.manifest.good");
--- a/browser/metro/base/content/flyoutpanels/AboutFlyoutPanel.js
+++ b/browser/metro/base/content/flyoutpanels/AboutFlyoutPanel.js
@@ -449,17 +449,17 @@ appUpdater.prototype =
       }
     }
   },
 
   /**
    * See XPIProvider.jsm
    */
   onUpdateAvailable: function(aAddon, aInstall) {
-    if (!Services.blocklist.isAddonBlocklisted(aAddon.id, aInstall.version,
+    if (!Services.blocklist.isAddonBlocklisted(aAddon,
                                                this.update.appVersion,
                                                this.update.platformVersion)) {
       // Compatibility or new version updates mean the same thing here.
       this.onCompatibilityUpdateAvailable(aAddon);
     }
   },
 
   /**
--- a/toolkit/components/social/SocialService.jsm
+++ b/toolkit/components/social/SocialService.jsm
@@ -580,50 +580,52 @@ this.SocialService = {
 
     let anchor = "servicesInstall-notification-icon";
     let notificationid = "servicesInstall";
     chromeWin.PopupNotifications.show(browser, notificationid, message, anchor,
                                       action, [], {});
   },
 
   installProvider: function(aDOMDocument, data, installCallback) {
+    let manifest;
     let installOrigin = aDOMDocument.nodePrincipal.origin;
 
+    if (data) {
+      let installType = getOriginActivationType(installOrigin);
+      // if we get data, we MUST have a valid manifest generated from the data
+      manifest = this._manifestFromData(installType, data, aDOMDocument.nodePrincipal);
+      if (!manifest)
+        throw new Error("SocialService.installProvider: service configuration is invalid from " + aDOMDocument.location.href);
+
+      let addon = new AddonWrapper(manifest);
+      if (addon && addon.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED)
+        throw new Error("installProvider: provider with origin [" +
+                        installOrigin + "] is blocklisted");
+    }
+
     let id = getAddonIDFromOrigin(installOrigin);
-    let version = data && data.version ? data.version : "0";
-    if (Services.blocklist.getAddonBlocklistState(id, version) == Ci.nsIBlocklistService.STATE_BLOCKED)
-      throw new Error("installProvider: provider with origin [" +
-                      installOrigin + "] is blocklisted");
-
     AddonManager.getAddonByID(id, function(aAddon) {
       if (aAddon && aAddon.userDisabled) {
         aAddon.cancelUninstall();
         aAddon.userDisabled = false;
       }
       schedule(function () {
-        this._installProvider(aDOMDocument, data, aManifest => {
+        this._installProvider(aDOMDocument, manifest, aManifest => {
           this._notifyProviderListeners("provider-installed", aManifest.origin);
           installCallback(aManifest);
         });
       }.bind(this));
     }.bind(this));
   },
 
-  _installProvider: function(aDOMDocument, data, installCallback) {
+  _installProvider: function(aDOMDocument, manifest, installCallback) {
     let sourceURI = aDOMDocument.location.href;
     let installOrigin = aDOMDocument.nodePrincipal.origin;
 
     let installType = getOriginActivationType(installOrigin);
-    let manifest;
-    if (data) {
-      // if we get data, we MUST have a valid manifest generated from the data
-      manifest = this._manifestFromData(installType, data, aDOMDocument.nodePrincipal);
-      if (!manifest)
-        throw new Error("SocialService.installProvider: service configuration is invalid from " + sourceURI);
-    }
     let installer;
     switch(installType) {
       case "foreign":
         if (!Services.prefs.getBoolPref("social.remote-install.enabled"))
           throw new Error("Remote install of services is disabled");
         if (!manifest)
           throw new Error("Cannot install provider without manifest data");
 
@@ -657,16 +659,20 @@ this.SocialService = {
         this._showInstallNotification(aDOMDocument, installer);
         break;
       default:
         throw new Error("SocialService.installProvider: Invalid install type "+installType+"\n");
         break;
     }
   },
 
+  createWrapper: function(manifest) {
+    return new AddonWrapper(manifest);
+  },
+
   /**
    * updateProvider is used from the worker to self-update.  Since we do not
    * have knowledge of the currently selected provider here, we will notify
    * the front end to deal with any reload.
    */
   updateProvider: function(aUpdateOrigin, aManifest) {
     let originUri = Services.io.newURI(aUpdateOrigin, null, null);
     let principal = Services.scriptSecurityManager.getNoAppCodebasePrincipal(originUri);
@@ -711,18 +717,18 @@ this.SocialService = {
  * @param {bool} boolean indicating whether this provider is "built in"
  */
 function SocialProvider(input) {
   if (!input.name)
     throw new Error("SocialProvider must be passed a name");
   if (!input.origin)
     throw new Error("SocialProvider must be passed an origin");
 
-  let id = getAddonIDFromOrigin(input.origin);
-  if (Services.blocklist.getAddonBlocklistState(id, input.version || "0") == Ci.nsIBlocklistService.STATE_BLOCKED)
+  let addon = new AddonWrapper(input);
+  if (addon.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED)
     throw new Error("SocialProvider: provider with origin [" +
                     input.origin + "] is blocklisted");
 
   this.name = input.name;
   this.iconURL = input.iconURL;
   this.icon32URL = input.icon32URL;
   this.icon64URL = input.icon64URL;
   this.workerURL = input.workerURL;
@@ -1006,18 +1012,18 @@ var SocialAddonProvider = {
 
   shutdown: function() {},
 
   updateAddonAppDisabledStates: function() {
     // we wont bother with "enabling" services that are released from blocklist
     for (let manifest of SocialServiceInternal.manifests) {
       try {
         if (ActiveProviders.has(manifest.origin)) {
-          let id = getAddonIDFromOrigin(manifest.origin);
-          if (Services.blocklist.getAddonBlocklistState(id, manifest.version || "0") != Ci.nsIBlocklistService.STATE_NOT_BLOCKED) {
+          let addon = new AddonWrapper(manifest);
+          if (addon.blocklistState != Ci.nsIBlocklistService.STATE_NOT_BLOCKED) {
             SocialService.removeProvider(manifest.origin);
           }
         }
       } catch(e) {
         Cu.reportError(e);
       }
     }
   },
@@ -1095,21 +1101,21 @@ AddonWrapper.prototype = {
     return true;
   },
 
   get providesUpdatesSecurely() {
     return true;
   },
 
   get blocklistState() {
-    return Services.blocklist.getAddonBlocklistState(this.id, this.version || "0");
+    return Services.blocklist.getAddonBlocklistState(this);
   },
 
   get blocklistURL() {
-    return Services.blocklist.getAddonBlocklistURL(this.id, this.version || "0");
+    return Services.blocklist.getAddonBlocklistURL(this);
   },
 
   get screenshots() {
     return [];
   },
 
   get pendingOperations() {
     return this._pending || AddonManager.PENDING_NONE;
--- a/toolkit/mozapps/extensions/AddonUpdateChecker.jsm
+++ b/toolkit/mozapps/extensions/AddonUpdateChecker.jsm
@@ -716,18 +716,17 @@ this.AddonUpdateChecker = {
 
     let blocklist = Cc["@mozilla.org/extensions/blocklist;1"].
                     getService(Ci.nsIBlocklistService);
 
     let newest = null;
     for (let update of aUpdates) {
       if (!update.updateURL)
         continue;
-      let state = blocklist.getAddonBlocklistState(update.id, update.version,
-                                                   aAppVersion, aPlatformVersion);
+      let state = blocklist.getAddonBlocklistState(update, aAppVersion, aPlatformVersion);
       if (state != Ci.nsIBlocklistService.STATE_NOT_BLOCKED)
         continue;
       if ((newest == null || (Services.vc.compare(newest.version, update.version) < 0)) &&
           matchesVersions(update, aAppVersion, aPlatformVersion,
                           aIgnoreMaxVersion, aIgnoreStrictCompat,
                           aCompatOverrides)) {
         newest = update;
       }
--- a/toolkit/mozapps/extensions/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/XPIProvider.jsm
@@ -545,22 +545,20 @@ function applyBlocklistChanges(aOldAddon
                                aOldPlatformVersion) {
   // Copy the properties by default
   aNewAddon.userDisabled = aOldAddon.userDisabled;
   aNewAddon.softDisabled = aOldAddon.softDisabled;
 
   let bs = Cc["@mozilla.org/extensions/blocklist;1"].
            getService(Ci.nsIBlocklistService);
 
-  let oldBlocklistState = bs.getAddonBlocklistState(aOldAddon.id,
-                                                    aOldAddon.version,
+  let oldBlocklistState = bs.getAddonBlocklistState(createWrapper(aOldAddon),
                                                     aOldAppVersion,
                                                     aOldPlatformVersion);
-  let newBlocklistState = bs.getAddonBlocklistState(aNewAddon.id,
-                                                    aNewAddon.version);
+  let newBlocklistState = bs.getAddonBlocklistState(createWrapper(aNewAddon));
 
   // If the blocklist state hasn't changed then the properties don't need to
   // change
   if (newBlocklistState == oldBlocklistState)
     return;
 
   if (newBlocklistState == Ci.nsIBlocklistService.STATE_SOFTBLOCKED) {
     if (aNewAddon.type != "theme") {
@@ -6207,29 +6205,29 @@ AddonInternal.prototype = {
 
   get blocklistState() {
     let staticItem = findMatchingStaticBlocklistItem(this);
     if (staticItem)
       return staticItem.level;
 
     let bs = Cc["@mozilla.org/extensions/blocklist;1"].
              getService(Ci.nsIBlocklistService);
-    return bs.getAddonBlocklistState(this.id, this.version);
+    return bs.getAddonBlocklistState(createWrapper(this));
   },
 
   get blocklistURL() {
     let staticItem = findMatchingStaticBlocklistItem(this);
     if (staticItem) {
       let url = Services.urlFormatter.formatURLPref("extensions.blocklist.itemURL");
       return url.replace(/%blockID%/g, staticItem.blockID);
     }
 
     let bs = Cc["@mozilla.org/extensions/blocklist;1"].
              getService(Ci.nsIBlocklistService);
-    return bs.getAddonBlocklistURL(this.id, this.version);
+    return bs.getAddonBlocklistURL(createWrapper(this));
   },
 
   applyCompatibilityUpdate: function AddonInternal_applyCompatibilityUpdate(aUpdate, aSyncCompatibility) {
     this.targetApplications.forEach(function(aTargetApp) {
       aUpdate.targetApplications.forEach(function(aUpdateTarget) {
         if (aTargetApp.id == aUpdateTarget.id && (aSyncCompatibility ||
             Services.vc.compare(aTargetApp.maxVersion, aUpdateTarget.maxVersion) < 0)) {
           aTargetApp.minVersion = aUpdateTarget.minVersion;
@@ -6337,17 +6335,17 @@ function AddonWrapper(aAddon) {
     }
 
     return [objValue, false];
   }
 
   ["id", "syncGUID", "version", "type", "isCompatible", "isPlatformCompatible",
    "providesUpdatesSecurely", "blocklistState", "blocklistURL", "appDisabled",
    "softDisabled", "skinnable", "size", "foreignInstall", "hasBinaryComponents",
-   "strictCompatibility", "compatibilityOverrides"].forEach(function(aProp) {
+   "strictCompatibility", "compatibilityOverrides", "updateURL"].forEach(function(aProp) {
      this.__defineGetter__(aProp, function AddonWrapper_propertyGetter() aAddon[aProp]);
   }, this);
 
   ["fullDescription", "developerComments", "eula", "supportURL",
    "contributionURL", "contributionAmount", "averageRating", "reviewCount",
    "reviewURL", "totalDownloads", "weeklyDownloads", "dailyUsers",
    "repositoryStatus"].forEach(function(aProp) {
     this.__defineGetter__(aProp, function AddonWrapper_repoPropertyGetter() {
--- a/toolkit/mozapps/extensions/nsBlocklistService.js
+++ b/toolkit/mozapps/extensions/nsBlocklistService.js
@@ -44,16 +44,18 @@ const URI_BLOCKLIST_DIALOG            = 
 const DEFAULT_SEVERITY                = 3;
 const DEFAULT_LEVEL                   = 2;
 const MAX_BLOCK_LEVEL                 = 3;
 const SEVERITY_OUTDATED               = 0;
 const VULNERABILITYSTATUS_NONE             = 0;
 const VULNERABILITYSTATUS_UPDATE_AVAILABLE = 1;
 const VULNERABILITYSTATUS_NO_UPDATE        = 2;
 
+const EXTENSION_BLOCK_FILTERS = ["id", "name", "creator", "homepageURL", "updateURL"];
+
 var gLoggingEnabled = null;
 var gBlocklistEnabled = true;
 var gBlocklistLevel = DEFAULT_LEVEL;
 
 XPCOMUtils.defineLazyServiceGetter(this, "gConsole",
                                    "@mozilla.org/consoleservice;1",
                                    "nsIConsoleService");
 
@@ -312,26 +314,26 @@ Blocklist.prototype = {
           this._blocklistUpdated(null, null);
           break;
       }
       break;
     }
   },
 
   /* See nsIBlocklistService */
-  isAddonBlocklisted: function Blocklist_isAddonBlocklisted(id, version, appVersion, toolkitVersion) {
-    return this.getAddonBlocklistState(id, version, appVersion, toolkitVersion) ==
+  isAddonBlocklisted: function Blocklist_isAddonBlocklisted(addon, appVersion, toolkitVersion) {
+    return this.getAddonBlocklistState(addon, appVersion, toolkitVersion) ==
                    Ci.nsIBlocklistService.STATE_BLOCKED;
   },
 
   /* See nsIBlocklistService */
-  getAddonBlocklistState: function Blocklist_getAddonBlocklistState(id, version, appVersion, toolkitVersion) {
+  getAddonBlocklistState: function Blocklist_getAddonBlocklistState(addon, appVersion, toolkitVersion) {
     if (!this._addonEntries)
       this._loadBlocklist();
-    return this._getAddonBlocklistState(id, version, this._addonEntries,
+    return this._getAddonBlocklistState(addon, this._addonEntries,
                                         appVersion, toolkitVersion);
   },
 
   /**
    * Private version of getAddonBlocklistState that allows the caller to pass in
    * the add-on blocklist entries to compare against.
    *
    * @param   id
@@ -344,71 +346,98 @@ Blocklist.prototype = {
    *          The application version to compare to, will use the current
    *          version if null.
    * @param   toolkitVersion
    *          The toolkit version to compare to, will use the current version if
    *          null.
    * @returns The blocklist state for the item, one of the STATE constants as
    *          defined in nsIBlocklistService.
    */
-  _getAddonBlocklistState: function Blocklist_getAddonBlocklistStateCall(id,
-                           version, addonEntries, appVersion, toolkitVersion) {
+  _getAddonBlocklistState: function Blocklist_getAddonBlocklistStateCall(addon,
+                           addonEntries, appVersion, toolkitVersion) {
     if (!gBlocklistEnabled)
       return Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
 
     if (!appVersion)
       appVersion = gApp.version;
     if (!toolkitVersion)
       toolkitVersion = gApp.platformVersion;
 
-    var blItem = this._findMatchingAddonEntry(addonEntries, id);
+    var blItem = this._findMatchingAddonEntry(addonEntries, addon);
     if (!blItem)
       return Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
 
     for (let currentblItem of blItem.versions) {
-      if (currentblItem.includesItem(version, appVersion, toolkitVersion))
+      if (currentblItem.includesItem(addon.version, appVersion, toolkitVersion))
         return currentblItem.severity >= gBlocklistLevel ? Ci.nsIBlocklistService.STATE_BLOCKED :
                                                        Ci.nsIBlocklistService.STATE_SOFTBLOCKED;
     }
     return Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
   },
 
   /**
    * Returns the set of prefs of the add-on stored in the blocklist file
    * (probably to revert them on disabling).
-   * @param id
-   *        ID of the add-on.
+   * @param addon
+   *        The add-on whose to-be-reset prefs are to be found.
    */
-  _getAddonPrefs: function Blocklist_getAddonPrefs(id) {
-    let entry = this._findMatchingAddonEntry(this._addonEntries, id);
+  _getAddonPrefs: function Blocklist_getAddonPrefs(addon) {
+    let entry = this._findMatchingAddonEntry(this._addonEntries, addon);
     return entry.prefs.slice(0);
   },
 
   _findMatchingAddonEntry: function Blocklist_findMatchingAddonEntry(aAddonEntries,
-                                                                     aId) {
+                                                                     aAddon) {
+    if (!aAddon)
+      return null;
+    // Returns true if the params object passes the constraints set by entry.
+    // (For every non-null property in entry, the same key must exist in
+    // params and value must be the same)
+    function checkEntry(entry, params) {
+      for (let [key, value] of entry) {
+        if (value === null || value === undefined)
+          continue;
+        if (params[key]) {
+          if (value instanceof RegExp) {
+            if (!value.test(params[key])) {
+              return false;
+            }
+          } else if (value !== params[key]) {
+            return false;
+          }
+        } else {
+          return false;
+        }
+      }
+      return true;
+    }
+
+    let params = {};
+    for (let filter of EXTENSION_BLOCK_FILTERS) {
+      params[filter] = aAddon[filter];
+    }
+    if (params.creator)
+      params.creator = params.creator.name;
     for (let entry of aAddonEntries) {
-      if (entry.id instanceof RegExp) {
-        if (entry.id.test(aId))
-          return entry;
-      } else if (entry.id == aId) {
-        return entry;
-      }
-    }
-    return null;
+      if (checkEntry(entry.attributes, params)) {
+         return entry;
+       }
+     }
+     return null;
   },
 
   /* See nsIBlocklistService */
-  getAddonBlocklistURL: function Blocklist_getAddonBlocklistURL(id, version, appVersion, toolkitVersion) {
+  getAddonBlocklistURL: function Blocklist_getAddonBlocklistURL(addon, appVersion, toolkitVersion) {
     if (!gBlocklistEnabled)
       return "";
 
     if (!this._addonEntries)
       this._loadBlocklist();
 
-    let blItem = this._findMatchingAddonEntry(this._addonEntries, id);
+    let blItem = this._findMatchingAddonEntry(this._addonEntries, addon);
     if (!blItem || !blItem.blockID)
       return null;
 
     return this._createBlocklistURL(blItem.blockID);
   },
 
   _createBlocklistURL: function Blocklist_createBlocklistURL(id) {
     let url = Services.urlFormatter.formatURLPref(PREF_BLOCKLIST_ITEM_URL);
@@ -729,28 +758,36 @@ Blocklist.prototype = {
     return result;
   },
 
   _handleEmItemNode: function Blocklist_handleEmItemNode(blocklistElement, result) {
     if (!matchesOSABI(blocklistElement))
       return;
 
     let blockEntry = {
-      id: null,
       versions: [],
       prefs: [],
-      blockID: null
+      blockID: null,
+      attributes: new Map()
+      // Atleast one of EXTENSION_BLOCK_FILTERS must get added to attributes
     };
 
+    // Any filter starting with '/' is interpreted as a regex. So if an attribute
+    // starts with a '/' it must be checked via a regex.
+    function regExpCheck(attr) {
+      return attr.startsWith("/") ? parseRegExp(attr) : attr;
+    }
+
+    for (let filter of EXTENSION_BLOCK_FILTERS) {
+      let attr = blocklistElement.getAttribute(filter);
+      if (attr)
+        blockEntry.attributes.set(filter, regExpCheck(attr));
+    }
+
     var childNodes = blocklistElement.childNodes;
-    var id = blocklistElement.getAttribute("id");
-    // Add-on IDs cannot contain '/', so an ID starting with '/' must be a regex
-    if (id.startsWith("/"))
-      id = parseRegExp(id);
-    blockEntry.id = id;
 
     for (let x = 0; x < childNodes.length; x++) {
       var childElement = childNodes.item(x);
       if (!(childElement instanceof Ci.nsIDOMElement))
         continue;
       if (childElement.localName === "prefs") {
         let prefElements = childElement.childNodes;
         for (let i = 0; i < prefElements.length; i++) {
@@ -923,30 +960,29 @@ Blocklist.prototype = {
     }
     var self = this;
     const types = ["extension", "theme", "locale", "dictionary", "service"];
     AddonManager.getAddonsByTypes(types, function blocklistUpdated_getAddonsByTypes(addons) {
 
       for (let addon of addons) {
         let oldState = Ci.nsIBlocklistService.STATE_NOTBLOCKED;
         if (oldAddonEntries)
-          oldState = self._getAddonBlocklistState(addon.id, addon.version,
-                                                  oldAddonEntries);
-        let state = self.getAddonBlocklistState(addon.id, addon.version);
+          oldState = self._getAddonBlocklistState(addon, oldAddonEntries);
+        let state = self.getAddonBlocklistState(addon);
 
         LOG("Blocklist state for " + addon.id + " changed from " +
             oldState + " to " + state);
 
         // We don't want to re-warn about add-ons
         if (state == oldState)
           continue;
 
         if (state === Ci.nsIBlocklistService.STATE_BLOCKED) {
           // It's a hard block. We must reset certain preferences.
-          let prefs = self._getAddonPrefs(addon.id);
+          let prefs = self._getAddonPrefs(addon);
           resetPrefs(prefs);
          }
 
         // Ensure that softDisabled is false if the add-on is not soft blocked
         if (state != Ci.nsIBlocklistService.STATE_SOFTBLOCKED)
           addon.softDisabled = false;
 
         // Don't warn about add-ons becoming unblocked.
@@ -968,17 +1004,17 @@ Blocklist.prototype = {
 
         addonList.push({
           name: addon.name,
           version: addon.version,
           icon: addon.iconURL,
           disable: false,
           blocked: state == Ci.nsIBlocklistService.STATE_BLOCKED,
           item: addon,
-          url: self.getAddonBlocklistURL(addon.id),
+          url: self.getAddonBlocklistURL(addon),
         });
       }
 
       AddonManagerPrivate.updateAddonAppDisabledStates();
 
       var phs = Cc["@mozilla.org/plugin/host;1"].
                 getService(Ci.nsIPluginHost);
       var plugins = phs.getPluginTags();
@@ -1051,17 +1087,17 @@ Blocklist.prototype = {
             continue;
 
           if (addon.item instanceof Ci.nsIPluginTag)
             addon.item.enabledState = Ci.nsIPluginTag.STATE_DISABLED;
           else {
             // This add-on is softblocked.
             addon.item.softDisabled = true;
             // We must revert certain prefs.
-            let prefs = self._getAddonPrefs(addon.item.id);
+            let prefs = self._getAddonPrefs(addon.item);
             resetPrefs(prefs);
           }
         }
 
         if (args.restart)
           restartApp();
 
         Services.obs.notifyObservers(self, "blocklist-updated", "");
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_blocklist_metadata_filters_1.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist">
+  <emItems>
+    <emItem name="/^Mozilla Corp\.$/">
+      <versionRange severity="1">
+        <targetApplication id="xpcshell@tests.mozilla.org">
+          <versionRange minVersion="1" maxVersion="2.*"/>
+        </targetApplication>
+      </versionRange>
+    </emItem>
+    <emItem id="/block2/" name="/^Moz/" creator="Dangerous"
+      homepageURL="/\.dangerous\.com/" updateURL="/\.dangerous\.com/">
+      <versionRange severity="3">
+        <targetApplication id="xpcshell@tests.mozilla.org">
+          <versionRange minVersion="1" maxVersion="2.*"/>
+        </targetApplication>
+      </versionRange>
+    </emItem>
+  </emItems>
+</blocklist>
--- a/toolkit/mozapps/extensions/test/xpcshell/data/test_bug393285.xml
+++ b/toolkit/mozapps/extensions/test/xpcshell/data/test_bug393285.xml
@@ -1,14 +1,17 @@
 <?xml version="1.0" encoding="UTF-8"?>
 
 <blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist">
   <emItems>
     <emItem id="test_bug393285_2@tests.mozilla.org"/>
-    <emItem id="test_bug393285_3@tests.mozilla.org">
+    <emItem id="test_bug393285_3a@tests.mozilla.org">
+      <versionRange minVersion="1.0" maxVersion="1.0"/>
+    </emItem>
+    <emItem id="test_bug393285_3b@tests.mozilla.org">
       <versionRange minVersion="1.0" maxVersion="1.0"/>
     </emItem>
     <emItem id="test_bug393285_4@tests.mozilla.org">
       <versionRange minVersion="1.0" maxVersion="1.0">
         <targetApplication id="xpcshell@tests.mozilla.org">
           <versionRange minVersion="1.0" maxVersion="1.0"/>
         </targetApplication>
       </versionRange>
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_blocklist_metadata_filters.js
@@ -0,0 +1,159 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+// Tests blocking of extensions by ID, name, creator, homepageURL, updateURL
+// and RegExps for each. See bug 897735.
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+const URI_EXTENSION_BLOCKLIST_DIALOG = "chrome://mozapps/content/extensions/blocklist.xul";
+
+Cu.import("resource://testing-common/httpd.js");
+var testserver = new HttpServer();
+testserver.start(-1);
+gPort = testserver.identity.primaryPort;
+
+// register static files with server and interpolate port numbers in them
+mapFile("/data/test_blocklist_metadata_filters_1.xml", testserver);
+
+const profileDir = gProfD.clone();
+profileDir.append("extensions");
+
+// Don't need the full interface, attempts to call other methods will just
+// throw which is just fine
+var WindowWatcher = {
+  openWindow: function(parent, url, name, features, arguments) {
+    // Should be called to list the newly blocklisted items
+    do_check_eq(url, URI_EXTENSION_BLOCKLIST_DIALOG);
+
+    // Simulate auto-disabling any softblocks
+    var list = arguments.wrappedJSObject.list;
+    list.forEach(function(aItem) {
+      if (!aItem.blocked)
+        aItem.disable = true;
+    });
+
+    //run the code after the blocklist is closed
+    Services.obs.notifyObservers(null, "addon-blocklist-closed", null);
+
+  },
+
+  QueryInterface: function(iid) {
+    if (iid.equals(Ci.nsIWindowWatcher)
+     || iid.equals(Ci.nsISupports))
+      return this;
+
+    throw Cr.NS_ERROR_NO_INTERFACE;
+  }
+};
+
+var WindowWatcherFactory = {
+  createInstance: function createInstance(outer, iid) {
+    if (outer != null)
+      throw Cr.NS_ERROR_NO_AGGREGATION;
+    return WindowWatcher.QueryInterface(iid);
+  }
+};
+
+var registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
+registrar.registerFactory(Components.ID("{1dfeb90a-2193-45d5-9cb8-864928b2af55}"),
+                          "Fake Window Watcher",
+                          "@mozilla.org/embedcomp/window-watcher;1",
+                          WindowWatcherFactory);
+
+
+function load_blocklist(aFile, aCallback) {
+  Services.obs.addObserver(function() {
+    Services.obs.removeObserver(arguments.callee, "blocklist-updated");
+
+    do_execute_soon(aCallback);
+  }, "blocklist-updated", false);
+
+  Services.prefs.setCharPref("extensions.blocklist.url", "http://localhost:" +
+                             gPort + "/data/" + aFile);
+  var blocklist = Cc["@mozilla.org/extensions/blocklist;1"].
+                  getService(Ci.nsITimerCallback);
+  blocklist.notify(null);
+}
+
+
+function end_test() {
+  testserver.stop(do_test_finished);
+}
+
+function run_test() {
+  do_test_pending();
+
+  createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1");
+
+  // Should get blocked by name
+  writeInstallRDFForExtension({
+    id: "block1@tests.mozilla.org",
+    version: "1.0",
+    name: "Mozilla Corp.",
+    targetApplications: [{
+      id: "xpcshell@tests.mozilla.org",
+      minVersion: "1",
+      maxVersion: "3"
+    }]
+  }, profileDir);
+
+  // Should get blocked by all the attributes.
+  writeInstallRDFForExtension({
+    id: "block2@tests.mozilla.org",
+    version: "1.0",
+    name: "Moz-addon",
+    creator: "Dangerous",
+    homepageURL: "www.extension.dangerous.com",
+    updateURL: "www.extension.dangerous.com/update.rdf",
+    targetApplications: [{
+      id: "xpcshell@tests.mozilla.org",
+      minVersion: "1",
+      maxVersion: "3"
+    }]
+  }, profileDir);
+
+  // Fails to get blocked because of a different ID even though other
+  // attributes match against a blocklist entry.
+  writeInstallRDFForExtension({
+    id: "block3@tests.mozilla.org",
+    version: "1.0",
+    name: "Moz-addon",
+    creator: "Dangerous",
+    homepageURL: "www.extensions.dangerous.com",
+    updateURL: "www.extension.dangerous.com/update.rdf",
+    targetApplications: [{
+      id: "xpcshell@tests.mozilla.org",
+      minVersion: "1",
+      maxVersion: "3"
+    }]
+  }, profileDir);
+
+  startupManager();
+
+  AddonManager.getAddonsByIDs(["block1@tests.mozilla.org",
+                               "block2@tests.mozilla.org",
+                               "block3@tests.mozilla.org"], function([a1, a2, a3]) {
+    do_check_eq(a1.blocklistState, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
+    do_check_eq(a2.blocklistState, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
+    do_check_eq(a3.blocklistState, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
+
+    run_test_1();
+  });
+}
+
+function run_test_1() {
+  load_blocklist("test_blocklist_metadata_filters_1.xml", function() {
+    restartManager();
+
+    AddonManager.getAddonsByIDs(["block1@tests.mozilla.org",
+                                 "block2@tests.mozilla.org",
+                                 "block3@tests.mozilla.org"], function([a1, a2, a3]) {
+      do_check_eq(a1.blocklistState, Ci.nsIBlocklistService.STATE_SOFTBLOCKED);
+      do_check_eq(a2.blocklistState, Ci.nsIBlocklistService.STATE_BLOCKED);
+      do_check_eq(a3.blocklistState, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
+      end_test();
+    });
+  });
+}
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug335238.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug335238.js
@@ -74,30 +74,30 @@ var ADDONS = [
   {id: "bug335238_3@tests.mozilla.org",
    addon: "test_bug335238_3"},
   {id: "bug335238_4@tests.mozilla.org",
    addon: "test_bug335238_4"}
 ];
 
 // This is a replacement for the blocklist service
 var BlocklistService = {
-  getAddonBlocklistState: function(aId, aVersion, aAppVersion, aToolkitVersion) {
-    if (aId == "bug335238_3@tests.mozilla.org")
+  getAddonBlocklistState: function(aAddon, aAppVersion, aToolkitVersion) {
+    if (aAddon.id == "bug335238_3@tests.mozilla.org")
       return Ci.nsIBlocklistService.STATE_SOFTBLOCKED;
-    if (aId == "bug335238_4@tests.mozilla.org")
+    if (aAddon.id == "bug335238_4@tests.mozilla.org")
       return Ci.nsIBlocklistService.STATE_BLOCKED;
     return Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
   },
 
   getPluginBlocklistState: function(aPlugin, aVersion, aAppVersion, aToolkitVersion) {
     return Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
   },
 
-  isAddonBlocklisted: function(aId, aVersion, aAppVersion, aToolkitVersion) {
-    return this.getAddonBlocklistState(aId, aVersion, aAppVersion, aToolkitVersion) ==
+  isAddonBlocklisted: function(aAddon, aAppVersion, aToolkitVersion) {
+    return this.getAddonBlocklistState(aAddon, aAppVersion, aToolkitVersion) ==
            Ci.nsIBlocklistService.STATE_BLOCKED;
   },
 
   QueryInterface: function(iid) {
     if (iid.equals(Ci.nsIBlocklistService)
      || iid.equals(Ci.nsISupports))
       return this;
 
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug393285.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug393285.js
@@ -1,55 +1,327 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
  */
 
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+const URI_EXTENSION_BLOCKLIST_DIALOG = "chrome://mozapps/content/extensions/blocklist.xul";
+
+Cu.import("resource://testing-common/httpd.js");
+var testserver = new HttpServer();
+testserver.start(-1);
+gPort = testserver.identity.primaryPort;
+
+// register static files with server and interpolate port numbers in them
+mapFile("/data/test_bug393285.xml", testserver);
+
+const profileDir = gProfD.clone();
+profileDir.append("extensions");
+
+let addonIDs = ["test_bug393285_1@tests.mozilla.org",
+                "test_bug393285_2@tests.mozilla.org",
+                "test_bug393285_3a@tests.mozilla.org",
+                "test_bug393285_3b@tests.mozilla.org",
+                "test_bug393285_4@tests.mozilla.org",
+                "test_bug393285_5@tests.mozilla.org",
+                "test_bug393285_6@tests.mozilla.org",
+                "test_bug393285_7@tests.mozilla.org",
+                "test_bug393285_8@tests.mozilla.org",
+                "test_bug393285_9@tests.mozilla.org",
+                "test_bug393285_10@tests.mozilla.org",
+                "test_bug393285_11@tests.mozilla.org",
+                "test_bug393285_12@tests.mozilla.org",
+                "test_bug393285_13@tests.mozilla.org",
+                "test_bug393285_14@tests.mozilla.org"];
+
+// A window watcher to deal with the blocklist UI dialog.
+var WindowWatcher = {
+  openWindow: function(parent, url, name, features, arguments) {
+    // Should be called to list the newly blocklisted items
+    do_check_eq(url, URI_EXTENSION_BLOCKLIST_DIALOG);
+
+    // Simulate auto-disabling any softblocks
+    var list = arguments.wrappedJSObject.list;
+    list.forEach(function(aItem) {
+      if (!aItem.blocked)
+        aItem.disable = true;
+    });
+
+    //run the code after the blocklist is closed
+    Services.obs.notifyObservers(null, "addon-blocklist-closed", null);
+
+  },
+
+  QueryInterface: function(iid) {
+    if (iid.equals(Ci.nsIWindowWatcher)
+     || iid.equals(Ci.nsISupports))
+      return this;
+
+    throw Cr.NS_ERROR_NO_INTERFACE;
+  }
+};
+
+var WindowWatcherFactory = {
+  createInstance: function createInstance(outer, iid) {
+    if (outer != null)
+      throw Cr.NS_ERROR_NO_AGGREGATION;
+    return WindowWatcher.QueryInterface(iid);
+  }
+};
+
+var registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
+registrar.registerFactory(Components.ID("{1dfeb90a-2193-45d5-9cb8-864928b2af55}"),
+                          "Fake Window Watcher",
+                          "@mozilla.org/embedcomp/window-watcher;1",
+                          WindowWatcherFactory);
+
+
+function load_blocklist(aFile, aCallback) {
+  Services.obs.addObserver(function() {
+    Services.obs.removeObserver(arguments.callee, "blocklist-updated");
+
+    do_execute_soon(aCallback);
+  }, "blocklist-updated", false);
+
+  Services.prefs.setCharPref("extensions.blocklist.url", "http://localhost:" +
+                             gPort + "/data/" + aFile);
+  var blocklist = Cc["@mozilla.org/extensions/blocklist;1"].
+                  getService(Ci.nsITimerCallback);
+  blocklist.notify(null);
+}
+
+
+function end_test() {
+  testserver.stop(do_test_finished);
+}
+
 function run_test() {
+  do_test_pending();
+
   createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9");
 
-  // We cannot force the blocklist to update so just copy our test list to the profile
-  var blocklistFile = gProfD.clone();
-  blocklistFile.append("blocklist.xml");
-  if (blocklistFile.exists())
-    blocklistFile.remove(false);
-  var source = do_get_file("data/test_bug393285.xml");
-  source.copyTo(gProfD, "blocklist.xml");
+  writeInstallRDFForExtension({
+    id: "test_bug393285_1@tests.mozilla.org",
+    name: "extension 1",
+    version: "1.0",
+    targetApplications: [{
+      id: "xpcshell@tests.mozilla.org",
+      minVersion: "1",
+      maxVersion: "3"
+    }]
+  }, profileDir);
+
+
+  writeInstallRDFForExtension({
+    id: "test_bug393285_2@tests.mozilla.org",
+    name: "extension 2",
+    version: "1.0",
+    targetApplications: [{
+      id: "xpcshell@tests.mozilla.org",
+      minVersion: "1",
+      maxVersion: "3"
+    }]
+  }, profileDir);
+
+  writeInstallRDFForExtension({
+    id: "test_bug393285_3a@tests.mozilla.org",
+    name: "extension 3a",
+    version: "1.0",
+    targetApplications: [{
+      id: "xpcshell@tests.mozilla.org",
+      minVersion: "1",
+      maxVersion: "3"
+    }]
+  }, profileDir);
+
+  writeInstallRDFForExtension({
+    id: "test_bug393285_3b@tests.mozilla.org",
+    name: "extension 3b",
+    version: "2.0",
+    targetApplications: [{
+      id: "xpcshell@tests.mozilla.org",
+      minVersion: "1",
+      maxVersion: "3"
+    }]
+  }, profileDir);
 
-  var blocklist = Components.classes["@mozilla.org/extensions/blocklist;1"]
-                            .getService(Components.interfaces.nsIBlocklistService);
-  
-  // No info in blocklist, shouldn't be blocked
-  do_check_false(blocklist.isAddonBlocklisted("test_bug393285_1@tests.mozilla.org", "1", "1", "1.9"));
+  writeInstallRDFForExtension({
+    id: "test_bug393285_4@tests.mozilla.org",
+    name: "extension 4",
+    version: "1.0",
+    targetApplications: [{
+      id: "xpcshell@tests.mozilla.org",
+      minVersion: "1",
+      maxVersion: "3"
+    }]
+  }, profileDir);
+
+  writeInstallRDFForExtension({
+    id: "test_bug393285_5@tests.mozilla.org",
+    name: "extension 5",
+    version: "1.0",
+    targetApplications: [{
+      id: "xpcshell@tests.mozilla.org",
+      minVersion: "1",
+      maxVersion: "3"
+    }]
+  }, profileDir);
+
+  writeInstallRDFForExtension({
+    id: "test_bug393285_6@tests.mozilla.org",
+    name: "extension 6",
+    version: "1.0",
+    targetApplications: [{
+      id: "xpcshell@tests.mozilla.org",
+      minVersion: "1",
+      maxVersion: "3"
+    }]
+  }, profileDir);
 
-  // Should always be blocked
-  do_check_true(blocklist.isAddonBlocklisted("test_bug393285_2@tests.mozilla.org", "1", "1", "1.9"));
+  writeInstallRDFForExtension({
+    id: "test_bug393285_7@tests.mozilla.org",
+    name: "extension 7",
+    version: "1.0",
+    targetApplications: [{
+      id: "xpcshell@tests.mozilla.org",
+      minVersion: "1",
+      maxVersion: "3"
+    }]
+  }, profileDir);
 
-  // Only version 1 should be blocked
-  do_check_true(blocklist.isAddonBlocklisted("test_bug393285_3@tests.mozilla.org", "1", "1", "1.9"));
-  do_check_false(blocklist.isAddonBlocklisted("test_bug393285_3@tests.mozilla.org", "2", "1", "1.9"));
+  writeInstallRDFForExtension({
+    id: "test_bug393285_8@tests.mozilla.org",
+    name: "extension 8",
+    version: "1.0",
+    targetApplications: [{
+      id: "xpcshell@tests.mozilla.org",
+      minVersion: "1",
+      maxVersion: "3"
+    }]
+  }, profileDir);
+
+  writeInstallRDFForExtension({
+    id: "test_bug393285_9@tests.mozilla.org",
+    name: "extension 9",
+    version: "1.0",
+    targetApplications: [{
+      id: "xpcshell@tests.mozilla.org",
+      minVersion: "1",
+      maxVersion: "3"
+    }]
+  }, profileDir);
 
-  // Should be blocked for app version 1
-  do_check_true(blocklist.isAddonBlocklisted("test_bug393285_4@tests.mozilla.org", "1", "1", "1.9"));
-  do_check_false(blocklist.isAddonBlocklisted("test_bug393285_4@tests.mozilla.org", "1", "2", "1.9"));
+  writeInstallRDFForExtension({
+    id: "test_bug393285_10@tests.mozilla.org",
+    name: "extension 10",
+    version: "1.0",
+    targetApplications: [{
+      id: "xpcshell@tests.mozilla.org",
+      minVersion: "1",
+      maxVersion: "3"
+    }]
+  }, profileDir);
+
+  writeInstallRDFForExtension({
+    id: "test_bug393285_11@tests.mozilla.org",
+    name: "extension 11",
+    version: "1.0",
+    targetApplications: [{
+      id: "xpcshell@tests.mozilla.org",
+      minVersion: "1",
+      maxVersion: "3"
+    }]
+  }, profileDir);
 
-  // Not blocklisted because we are a different OS
-  do_check_false(blocklist.isAddonBlocklisted("test_bug393285_5@tests.mozilla.org", "1", "2", "1.9"));
+  writeInstallRDFForExtension({
+    id: "test_bug393285_12@tests.mozilla.org",
+    name: "extension 12",
+    version: "1.0",
+    targetApplications: [{
+      id: "xpcshell@tests.mozilla.org",
+      minVersion: "1",
+      maxVersion: "3"
+    }]
+  }, profileDir);
 
-  // Blocklisted based on OS
-  do_check_true(blocklist.isAddonBlocklisted("test_bug393285_6@tests.mozilla.org", "1", "2", "1.9"));
-  do_check_true(blocklist.isAddonBlocklisted("test_bug393285_7@tests.mozilla.org", "1", "2", "1.9"));
+  writeInstallRDFForExtension({
+    id: "test_bug393285_13@tests.mozilla.org",
+    name: "extension 13",
+    version: "1.0",
+    targetApplications: [{
+      id: "xpcshell@tests.mozilla.org",
+      minVersion: "1",
+      maxVersion: "3"
+    }]
+  }, profileDir);
+
+  writeInstallRDFForExtension({
+    id: "test_bug393285_14@tests.mozilla.org",
+    name: "extension 14",
+    version: "1.0",
+    targetApplications: [{
+      id: "xpcshell@tests.mozilla.org",
+      minVersion: "1",
+      maxVersion: "3"
+    }]
+  }, profileDir);
+
+  startupManager();
 
-  // Not blocklisted because we are a different ABI
-  do_check_false(blocklist.isAddonBlocklisted("test_bug393285_8@tests.mozilla.org", "1", "2", "1.9"));
+  AddonManager.getAddonsByIDs(addonIDs, function(addons) {
+    for (addon of addons) {
+      do_check_eq(addon.blocklistState, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
+    }
+    run_test_1();
+  });
+}
+
+function run_test_1() {
+  load_blocklist("test_bug393285.xml", function() {
+    restartManager();
 
-  // Blocklisted based on ABI
-  do_check_true(blocklist.isAddonBlocklisted("test_bug393285_9@tests.mozilla.org", "1", "2", "1.9"));
-  do_check_true(blocklist.isAddonBlocklisted("test_bug393285_10@tests.mozilla.org", "1", "2", "1.9"));
+    var blocklist = Cc["@mozilla.org/extensions/blocklist;1"]
+                    .getService(Ci.nsIBlocklistService);
+
+    AddonManager.getAddonsByIDs(addonIDs,
+                               function([a1, a2, a3, a4, a5, a6, a7, a8, a9, a10,
+                                         a11, a12, a13, a14, a15]) {
+      // No info in blocklist, shouldn't be blocked
+      do_check_false(blocklist.isAddonBlocklisted(a1, "1", "1.9"));
+
+      // Should always be blocked
+      do_check_true(blocklist.isAddonBlocklisted(a2, "1", "1.9"));
+
+      // Only version 1 should be blocked
+      do_check_true(blocklist.isAddonBlocklisted(a3, "1", "1.9"));
+      do_check_false(blocklist.isAddonBlocklisted(a4, "1", "1.9"));
 
-  // Doesnt match both os and abi so not blocked
-  do_check_false(blocklist.isAddonBlocklisted("test_bug393285_11@tests.mozilla.org", "1", "2", "1.9"));
-  do_check_false(blocklist.isAddonBlocklisted("test_bug393285_12@tests.mozilla.org", "1", "2", "1.9"));
-  do_check_false(blocklist.isAddonBlocklisted("test_bug393285_13@tests.mozilla.org", "1", "2", "1.9"));
+      // Should be blocked for app version 1
+      do_check_true(blocklist.isAddonBlocklisted(a5, "1", "1.9"));
+      do_check_false(blocklist.isAddonBlocklisted(a5, "2", "1.9"));
+
+      // Not blocklisted because we are a different OS
+      do_check_false(blocklist.isAddonBlocklisted(a6, "2", "1.9"));
+
+      // Blocklisted based on OS
+      do_check_true(blocklist.isAddonBlocklisted(a7, "2", "1.9"));
+      do_check_true(blocklist.isAddonBlocklisted(a8, "2", "1.9"));
+
+      // Not blocklisted because we are a different ABI
+      do_check_false(blocklist.isAddonBlocklisted(a9, "2", "1.9"));
 
-  // Matches both os and abi so blocked
-  do_check_true(blocklist.isAddonBlocklisted("test_bug393285_14@tests.mozilla.org", "1", "2", "1.9"));
+      // Blocklisted based on ABI
+      do_check_true(blocklist.isAddonBlocklisted(a10, "2", "1.9"));
+      do_check_true(blocklist.isAddonBlocklisted(a11, "2", "1.9"));
+
+      // Doesnt match both os and abi so not blocked
+      do_check_false(blocklist.isAddonBlocklisted(a12, "2", "1.9"));
+      do_check_false(blocklist.isAddonBlocklisted(a13, "2", "1.9"));
+      do_check_false(blocklist.isAddonBlocklisted(a14, "2", "1.9"));
+
+      // Matches both os and abi so blocked
+      do_check_true(blocklist.isAddonBlocklisted(a15, "2", "1.9"));
+      end_test();
+    });
+  });
 }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug406118.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug406118.js
@@ -1,26 +1,167 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/.
  */
 
+let addonIDs = ["test_bug393285_1@tests.mozilla.org",
+                "test_bug393285_2@tests.mozilla.org",
+                "test_bug393285_3a@tests.mozilla.org",
+                "test_bug393285_4@tests.mozilla.org"];
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+const URI_EXTENSION_BLOCKLIST_DIALOG = "chrome://mozapps/content/extensions/blocklist.xul";
+
+Cu.import("resource://testing-common/httpd.js");
+var testserver = new HttpServer();
+testserver.start(-1);
+gPort = testserver.identity.primaryPort;
+
+// register static files with server and interpolate port numbers in them
+mapFile("/data/test_bug393285.xml", testserver);
+
+const profileDir = gProfD.clone();
+profileDir.append("extensions");
+
+// A window watcher to deal with the blocklist UI dialog.
+var WindowWatcher = {
+  openWindow: function(parent, url, name, features, arguments) {
+    // Should be called to list the newly blocklisted items
+    do_check_eq(url, URI_EXTENSION_BLOCKLIST_DIALOG);
+
+    // Simulate auto-disabling any softblocks
+    var list = arguments.wrappedJSObject.list;
+    list.forEach(function(aItem) {
+      if (!aItem.blocked)
+        aItem.disable = true;
+    });
+
+    //run the code after the blocklist is closed
+    Services.obs.notifyObservers(null, "addon-blocklist-closed", null);
+
+  },
+
+  QueryInterface: function(iid) {
+    if (iid.equals(Ci.nsIWindowWatcher)
+     || iid.equals(Ci.nsISupports))
+      return this;
+
+    throw Cr.NS_ERROR_NO_INTERFACE;
+  }
+};
+
+var WindowWatcherFactory = {
+  createInstance: function createInstance(outer, iid) {
+    if (outer != null)
+      throw Cr.NS_ERROR_NO_AGGREGATION;
+    return WindowWatcher.QueryInterface(iid);
+  }
+};
+
+var registrar = Components.manager.QueryInterface(Ci.nsIComponentRegistrar);
+registrar.registerFactory(Components.ID("{1dfeb90a-2193-45d5-9cb8-864928b2af55}"),
+                          "Fake Window Watcher",
+                          "@mozilla.org/embedcomp/window-watcher;1",
+                          WindowWatcherFactory);
+
+
+function load_blocklist(aFile, aCallback) {
+  Services.obs.addObserver(function() {
+    Services.obs.removeObserver(arguments.callee, "blocklist-updated");
+
+    do_execute_soon(aCallback);
+  }, "blocklist-updated", false);
+
+  Services.prefs.setCharPref("extensions.blocklist.url", "http://localhost:" +
+                             gPort + "/data/" + aFile);
+  var blocklist = Cc["@mozilla.org/extensions/blocklist;1"].
+                  getService(Ci.nsITimerCallback);
+  blocklist.notify(null);
+}
+
+
+function end_test() {
+  testserver.stop(do_test_finished);
+}
+
 function run_test() {
+  do_test_pending();
+
   createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9");
 
-  // We cannot force the blocklist to update so just copy our test list to the profile
-  var blocklistFile = gProfD.clone();
-  blocklistFile.append("blocklist.xml");
-  if (blocklistFile.exists())
-    blocklistFile.remove(false);
-  var source = do_get_file("data/test_bug393285.xml");
-  source.copyTo(gProfD, "blocklist.xml");
+  writeInstallRDFForExtension({
+    id: "test_bug393285_1@tests.mozilla.org",
+    name: "extension 1",
+    version: "1.0",
+    targetApplications: [{
+      id: "xpcshell@tests.mozilla.org",
+      minVersion: "1",
+      maxVersion: "3"
+    }]
+  }, profileDir);
+
+
+  writeInstallRDFForExtension({
+    id: "test_bug393285_2@tests.mozilla.org",
+    name: "extension 2",
+    version: "1.0",
+    targetApplications: [{
+      id: "xpcshell@tests.mozilla.org",
+      minVersion: "1",
+      maxVersion: "3"
+    }]
+  }, profileDir);
+
+  writeInstallRDFForExtension({
+    id: "test_bug393285_3a@tests.mozilla.org",
+    name: "extension 3a",
+    version: "1.0",
+    targetApplications: [{
+      id: "xpcshell@tests.mozilla.org",
+      minVersion: "1",
+      maxVersion: "3"
+    }]
+  }, profileDir);
 
-  var blocklist = Components.classes["@mozilla.org/extensions/blocklist;1"]
-                            .getService(Components.interfaces.nsIBlocklistService);
-  
-  // All these should be blocklisted for the current app.
-  do_check_false(blocklist.isAddonBlocklisted("test_bug393285_1@tests.mozilla.org", "1", null, null));
-  do_check_true(blocklist.isAddonBlocklisted("test_bug393285_2@tests.mozilla.org", "1", null, null));
-  do_check_true(blocklist.isAddonBlocklisted("test_bug393285_3@tests.mozilla.org", "1", null, null));
-  do_check_true(blocklist.isAddonBlocklisted("test_bug393285_4@tests.mozilla.org", "1", null, null));
+  writeInstallRDFForExtension({
+    id: "test_bug393285_4@tests.mozilla.org",
+    name: "extension 4",
+    version: "1.0",
+    targetApplications: [{
+      id: "xpcshell@tests.mozilla.org",
+      minVersion: "1",
+      maxVersion: "3"
+    }]
+  }, profileDir);
+
+  startupManager();
+
+  AddonManager.getAddonsByIDs(addonIDs, function(addons) {
+    for (addon of addons) {
+      do_check_eq(addon.blocklistState, Ci.nsIBlocklistService.STATE_NOT_BLOCKED);
+    }
+    run_test_1();
+  });
+}
 
+function run_test_1() {
+  load_blocklist("test_bug393285.xml", function() {
+    restartManager();
+
+    var blocklist = Cc["@mozilla.org/extensions/blocklist;1"]
+                    .getService(Ci.nsIBlocklistService);
+
+    AddonManager.getAddonsByIDs(addonIDs,
+                               function([a1, a2, a3, a4]) {
+      // No info in blocklist, shouldn't be blocked
+      do_check_false(blocklist.isAddonBlocklisted(a1, null, null));
+
+      // All these should be blocklisted for the current app.
+      do_check_true(blocklist.isAddonBlocklisted(a2, null, null));
+      do_check_true(blocklist.isAddonBlocklisted(a3, null, null));
+      do_check_true(blocklist.isAddonBlocklisted(a4, null, null));
+
+      end_test();
+    });
+  });
 }
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug455906.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug455906.js
@@ -456,21 +456,21 @@ function check_test_pt3() {
     do_check_eq(check_plugin_state(PLUGINS[2]), "false,true");
     do_check_eq(check_plugin_state(PLUGINS[3]), "true,true");
     do_check_eq(check_plugin_state(PLUGINS[4]), "false,true");
 
     // Should have gained the blocklist state but no longer be soft disabled
     do_check_eq(check_addon_state(addons[3]), "false,false,true");
 
     // Check blockIDs are correct
-    do_check_eq(blocklist.getAddonBlocklistURL(addons[0].id,''),create_blocklistURL(addons[0].id));
-    do_check_eq(blocklist.getAddonBlocklistURL(addons[1].id,''),create_blocklistURL(addons[1].id));
-    do_check_eq(blocklist.getAddonBlocklistURL(addons[2].id,''),create_blocklistURL(addons[2].id));
-    do_check_eq(blocklist.getAddonBlocklistURL(addons[3].id,''),create_blocklistURL(addons[3].id));
-    do_check_eq(blocklist.getAddonBlocklistURL(addons[4].id,''),create_blocklistURL(addons[4].id));
+    do_check_eq(blocklist.getAddonBlocklistURL(addons[0]),create_blocklistURL(addons[0].id));
+    do_check_eq(blocklist.getAddonBlocklistURL(addons[1]),create_blocklistURL(addons[1].id));
+    do_check_eq(blocklist.getAddonBlocklistURL(addons[2]),create_blocklistURL(addons[2].id));
+    do_check_eq(blocklist.getAddonBlocklistURL(addons[3]),create_blocklistURL(addons[3].id));
+    do_check_eq(blocklist.getAddonBlocklistURL(addons[4]),create_blocklistURL(addons[4].id));
 
     // All plugins have the same blockID on the test
     do_check_eq(blocklist.getPluginBlocklistURL(PLUGINS[0]), create_blocklistURL('test_bug455906_plugin'));
     do_check_eq(blocklist.getPluginBlocklistURL(PLUGINS[1]), create_blocklistURL('test_bug455906_plugin'));
     do_check_eq(blocklist.getPluginBlocklistURL(PLUGINS[2]), create_blocklistURL('test_bug455906_plugin'));
     do_check_eq(blocklist.getPluginBlocklistURL(PLUGINS[3]), create_blocklistURL('test_bug455906_plugin'));
     do_check_eq(blocklist.getPluginBlocklistURL(PLUGINS[4]), create_blocklistURL('test_bug455906_plugin'));
 
--- a/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini
+++ b/toolkit/mozapps/extensions/test/xpcshell/xpcshell-shared.ini
@@ -12,16 +12,17 @@ skip-if = os == "android"
 [test_DeferredSave.js]
 [test_LightweightThemeManager.js]
 [test_XPIcancel.js]
 [test_backgroundupdate.js]
 [test_bad_json.js]
 [test_badschema.js]
 [test_blocklistchange.js]
 [test_blocklist_prefs.js]
+[test_blocklist_metadata_filters.js]
 # Bug 676992: test consistently hangs on Android
 skip-if = os == "android"
 [test_blocklist_regexp.js]
 skip-if = os == "android"
 [test_bootstrap.js]
 # Bug 676992: test consistently hangs on Android
 skip-if = os == "android"
 [test_bug299716.js]
--- a/toolkit/mozapps/update/content/updates.js
+++ b/toolkit/mozapps/update/content/updates.js
@@ -864,17 +864,17 @@ var gIncompatibleCheckPage = {
   },
 
   onUpdateAvailable: function(addon, install) {
     // If the new version of this add-on is blocklisted for the new application
     // then it isn't a valid update and the user should still be warned that
     // the add-on will become incompatible.
     let bs = CoC["@mozilla.org/extensions/blocklist;1"].
              getService(CoI.nsIBlocklistService);
-    if (bs.isAddonBlocklisted(addon.id, install.version,
+    if (bs.isAddonBlocklisted(addon,
                               gUpdates.update.appVersion,
                               gUpdates.update.platformVersion))
       return;
 
     // Compatibility or new version updates mean the same thing here.
     this.onCompatibilityUpdateAvailable(addon);
   },
 
--- a/toolkit/mozapps/update/nsUpdateService.js
+++ b/toolkit/mozapps/update/nsUpdateService.js
@@ -2952,17 +2952,17 @@ UpdateService.prototype = {
     if (getPref("getIntPref", PREF_APP_UPDATE_INCOMPATIBLE_MODE, 0) == 1)
       return;
 
     // If the new version of this add-on is blocklisted for the new application
     // then it isn't a valid update and the user should still be warned that
     // the add-on will become incompatible.
     let bs = Cc["@mozilla.org/extensions/blocklist;1"].
              getService(Ci.nsIBlocklistService);
-    if (bs.isAddonBlocklisted(addon.id, install.version,
+    if (bs.isAddonBlocklisted(addon,
                               gUpdates.update.appVersion,
                               gUpdates.update.platformVersion))
       return;
 
     // Compatibility or new version updates mean the same thing here.
     this.onCompatibilityUpdateAvailable(addon);
   },
 
--- a/xpcom/system/nsIBlocklistService.idl
+++ b/xpcom/system/nsIBlocklistService.idl
@@ -24,52 +24,48 @@ interface nsIBlocklistService : nsISuppo
   const unsigned long STATE_OUTDATED    = 3;
   // Indicates that the item is vulnerable and there is an update.
   const unsigned long STATE_VULNERABLE_UPDATE_AVAILABLE = 4;
   // Indicates that the item is vulnerable and there is no update.
   const unsigned long STATE_VULNERABLE_NO_UPDATE = 5;
 
   /**
    * Determine if an item is blocklisted
-   * @param   id
-   *          The ID of the item.
-   * @param   version
-   *          The item's version.
+   * @param   addon
+   *          The addon item to be checked.
    * @param   appVersion
    *          The version of the application we are checking in the blocklist.
    *          If this parameter is null, the version of the running application
    *          is used.
    * @param   toolkitVersion
    *          The version of the toolkit we are checking in the blocklist.
    *          If this parameter is null, the version of the running toolkit
    *          is used.
    * @returns true if the item is compatible with this version of the
    *          application or this version of the toolkit, false, otherwise.
    */
-  boolean isAddonBlocklisted(in AString id, in AString version,
+  boolean isAddonBlocklisted(in jsval addon,
                              [optional] in AString appVersion,
                              [optional] in AString toolkitVersion);
 
   /**
    * Determine the blocklist state of an add-on
    * @param   id
-   *          The ID of the item.
-   * @param   version
-   *          The item's version.
+   *          The addon item to be checked.
    * @param   appVersion
    *          The version of the application we are checking in the blocklist.
    *          If this parameter is null, the version of the running application
    *          is used.
    * @param   toolkitVersion
    *          The version of the toolkit we are checking in the blocklist.
    *          If this parameter is null, the version of the running toolkit
    *          is used.
    * @returns The STATE constant.
    */
-  unsigned long getAddonBlocklistState(in AString id, in AString version,
+  unsigned long getAddonBlocklistState(in jsval addon,
                                        [optional] in AString appVersion,
                                        [optional] in AString toolkitVersion);
 
   /**
    * Determine the blocklist state of a plugin
    * @param   plugin
    *          The plugin to get the state for
    * @param   appVersion
@@ -83,21 +79,21 @@ interface nsIBlocklistService : nsISuppo
    * @returns The STATE constant.
    */
   unsigned long getPluginBlocklistState(in nsIPluginTag plugin,
                                         [optional] in AString appVersion,
                                         [optional] in AString toolkitVersion);
 
   /**
    * Determine the blocklist web page of an add-on.
-   * @param   id
-   *          The ID of the blocked add-on.
+   * @param   addon
+   *          The addon item whose url is required.
    * @returns The URL of the description page.
    */
-  AString getAddonBlocklistURL(in AString id, in AString version,
+  AString getAddonBlocklistURL(in jsval addon,
                               [optional] in AString appVersion,
                               [optional] in AString toolkitVersion);
 
   /**
    * Determine the blocklist web page of a plugin.
    * @param   plugin
    *          The blocked plugin that we are determining the web page for.
    * @returns The URL of the description page.