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 173518 c8e2cb87f6c163f7fbbc1fcd5cf9bb3565d2163b
parent 173517 716a6ec17ceca5d433e04a18685db82c1a75b129
child 173519 8c0180723f67e61122da211c0d8243d557dd8233
push idunknown
push userunknown
push dateunknown
reviewersUnfocused
bugs897735
milestone29.0a1
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.