Bug 406026: Point users to a specific page for each blocklisted item. r=dtownsend
authorHernán Rodriguez Colmeiro <colmeiro@gmail.com>, Mehdi Mulani <mars.martian+bugmail@gmail.com>
Wed, 25 May 2011 14:31:56 -0700
changeset 70173 e07897c7b558579aaf4feacbf180230eb042ad4f
parent 70172 5e1ee7f192ad25f1b55085efbb6e11f32205cc48
child 70174 f584467626dfce28ed4531f7cbcbf727f6c06713
push idunknown
push userunknown
push dateunknown
reviewersdtownsend
bugs406026
milestone7.0a1
Bug 406026: Point users to a specific page for each blocklisted item. r=dtownsend
browser/app/profile/firefox.js
toolkit/mozapps/extensions/PluginProvider.jsm
toolkit/mozapps/extensions/XPIProvider.jsm
toolkit/mozapps/extensions/content/blocklist.js
toolkit/mozapps/extensions/content/extensions.js
toolkit/mozapps/extensions/content/extensions.xml
toolkit/mozapps/extensions/nsBlocklistService.js
toolkit/mozapps/extensions/test/browser/browser_bug523784.js
toolkit/mozapps/extensions/test/browser/browser_details.js
toolkit/mozapps/extensions/test/browser/browser_list.js
toolkit/mozapps/extensions/test/xpcshell/data/bug455906_block.xml
toolkit/mozapps/extensions/test/xpcshell/test_blocklistchange.js
toolkit/mozapps/extensions/test/xpcshell/test_bug455906.js
toolkit/mozapps/extensions/test/xpcshell/test_bug514327_2.js
xpcom/system/nsIBlocklistService.idl
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -65,16 +65,17 @@ pref("extensions.webservice.discoverURL"
 // Blocklist preferences
 pref("extensions.blocklist.enabled", true);
 pref("extensions.blocklist.interval", 86400);
 // Controls what level the blocklist switches from warning about items to forcibly
 // blocking them.
 pref("extensions.blocklist.level", 2);
 pref("extensions.blocklist.url", "https://addons.mozilla.org/blocklist/3/%APP_ID%/%APP_VERSION%/%PRODUCT%/%BUILD_ID%/%BUILD_TARGET%/%LOCALE%/%CHANNEL%/%OS_VERSION%/%DISTRIBUTION%/%DISTRIBUTION_VERSION%/%PING_COUNT%/%TOTAL_PING_COUNT%/%DAYS_SINCE_LAST_PING%/");
 pref("extensions.blocklist.detailsURL", "https://www.mozilla.com/%LOCALE%/blocklist/");
+pref("extensions.blocklist.itemURL", "https://addons.mozilla.org/%LOCALE%/%APP%/blocked/%blockID%");
 
 pref("extensions.update.autoUpdateDefault", true);
 
 // Dictionary download preference
 pref("browser.dictionaries.download.url", "https://addons.mozilla.org/%LOCALE%/firefox/dictionaries/");
 
 // The minimum delay in seconds for the timer to fire.
 // default=2 minutes
--- a/toolkit/mozapps/extensions/PluginProvider.jsm
+++ b/toolkit/mozapps/extensions/PluginProvider.jsm
@@ -231,16 +231,22 @@ function PluginWrapper(aId, aName, aDesc
   });
 
   this.__defineGetter__("blocklistState", function() {
     let bs = Cc["@mozilla.org/extensions/blocklist;1"].
              getService(Ci.nsIBlocklistService);
     return bs.getPluginBlocklistState(aTags[0]);
   });
 
+  this.__defineGetter__("blocklistURL", function() {
+    let bs = Cc["@mozilla.org/extensions/blocklist;1"].
+             getService(Ci.nsIBlocklistService);
+    return bs.getPluginBlocklistURL(aTags[0]);
+  });
+
   this.__defineGetter__("size", function() {
     function getDirectorySize(aFile) {
       let size = 0;
       let entries = aFile.directoryEntries.QueryInterface(Ci.nsIDirectoryEnumerator);
       let entry;
       while (entry = entries.nextFile) {
         if (entry.isSymlink() || !entry.isDirectory())
           size += entry.fileSize;
--- a/toolkit/mozapps/extensions/XPIProvider.jsm
+++ b/toolkit/mozapps/extensions/XPIProvider.jsm
@@ -6622,16 +6622,22 @@ AddonInternal.prototype = {
   },
 
   get blocklistState() {
     let bs = Cc["@mozilla.org/extensions/blocklist;1"].
              getService(Ci.nsIBlocklistService);
     return bs.getAddonBlocklistState(this.id, this.version);
   },
 
+  get blocklistURL() {
+    let bs = Cc["@mozilla.org/extensions/blocklist;1"].
+             getService(Ci.nsIBlocklistService);
+    return bs.getAddonBlocklistURL(this.id, this.version);
+  },
+
   applyCompatibilityUpdate: function(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;
           aTargetApp.maxVersion = aUpdateTarget.maxVersion;
         }
@@ -6776,17 +6782,17 @@ function AddonWrapper(aAddon) {
         (objValue === undefined || objValue === null)) {
       return [repositoryAddon[aProp], true];
     }
 
     return [objValue, false];
   }
 
   ["id", "version", "type", "isCompatible", "isPlatformCompatible",
-   "providesUpdatesSecurely", "blocklistState", "appDisabled",
+   "providesUpdatesSecurely", "blocklistState", "blocklistURL", "appDisabled",
    "softDisabled", "skinnable", "size"].forEach(function(aProp) {
      this.__defineGetter__(aProp, function() aAddon[aProp]);
   }, this);
 
   ["fullDescription", "developerComments", "eula", "supportURL",
    "contributionURL", "contributionAmount", "averageRating", "reviewCount",
    "reviewURL", "totalDownloads", "weeklyDownloads", "dailyUsers",
    "repositoryStatus"].forEach(function(aProp) {
--- a/toolkit/mozapps/extensions/content/blocklist.js
+++ b/toolkit/mozapps/extensions/content/blocklist.js
@@ -74,21 +74,24 @@ function init() {
 
   if (hasHardBlocks && hasSoftBlocks)
     document.getElementById("bothMessage").hidden = false;
   else if (hasHardBlocks)
     document.getElementById("hardBlockMessage").hidden = false;
   else
     document.getElementById("softBlockMessage").hidden = false;
 
-  var formatter = Components.classes["@mozilla.org/toolkit/URLFormatterService;1"]
-                            .getService(Components.interfaces.nsIURLFormatter);
-  var url = formatter.formatURLPref("extensions.blocklist.detailsURL");
   var link = document.getElementById("moreInfo");
-  link.setAttribute("href", url);
+  if (list.length == 1 && list[0].url) {
+    link.setAttribute("href", list[0].url);
+  }
+  else {
+    var url = Services.urlFormatter.formatURLPref("extensions.blocklist.detailsURL");
+    link.setAttribute("href", url);
+  }
 }
 
 function finish(shouldRestartNow) {
   gArgs.restart = shouldRestartNow;
   var list = gArgs.list;
   var items = document.getElementById("addonList").childNodes;
   for (let i = 0; i < list.length; i++) {
     if (!list[i].blocked)
--- a/toolkit/mozapps/extensions/content/extensions.js
+++ b/toolkit/mozapps/extensions/content/extensions.js
@@ -2746,34 +2746,34 @@ var gDetailView = {
       if (this._addon.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED) {
         this.node.setAttribute("notification", "error");
         document.getElementById("detail-error").textContent = gStrings.ext.formatStringFromName(
           "details.notification.blocked",
           [this._addon.name], 1
         );
         var errorLink = document.getElementById("detail-error-link");
         errorLink.value = gStrings.ext.GetStringFromName("details.notification.blocked.link");
-        errorLink.href = Services.urlFormatter.formatURLPref("extensions.blocklist.detailsURL");
+        errorLink.href = this._addon.blocklistURL;
         errorLink.hidden = false;
       } else if (!this._addon.isCompatible) {
         this.node.setAttribute("notification", "warning");
         document.getElementById("detail-warning").textContent = gStrings.ext.formatStringFromName(
           "details.notification.incompatible",
           [this._addon.name, gStrings.brandShortName, gStrings.appVersion], 3
         );
         document.getElementById("detail-warning-link").hidden = true;
       } else if (this._addon.blocklistState == Ci.nsIBlocklistService.STATE_SOFTBLOCKED) {
         this.node.setAttribute("notification", "warning");
         document.getElementById("detail-warning").textContent = gStrings.ext.formatStringFromName(
           "details.notification.softblocked",
           [this._addon.name], 1
         );
         var warningLink = document.getElementById("detail-warning-link");
         warningLink.value = gStrings.ext.GetStringFromName("details.notification.softblocked.link");
-        warningLink.href = Services.urlFormatter.formatURLPref("extensions.blocklist.detailsURL");
+        warningLink.href = this._addon.blocklistURL;
         warningLink.hidden = false;
       } else if (this._addon.blocklistState == Ci.nsIBlocklistService.STATE_OUTDATED) {
         this.node.setAttribute("notification", "warning");
         document.getElementById("detail-warning").textContent = gStrings.ext.formatStringFromName(
           "details.notification.outdated",
           [this._addon.name], 1
         );
         var warningLink = document.getElementById("detail-warning-link");
--- a/toolkit/mozapps/extensions/content/extensions.xml
+++ b/toolkit/mozapps/extensions/content/extensions.xml
@@ -1212,34 +1212,34 @@
               this._warningLink.hidden = true;
             } else if (!isUpgrade && this.mAddon.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED) {
               this.setAttribute("notification", "error");
               this._error.textContent = gStrings.ext.formatStringFromName(
                 "notification.blocked",
                 [this.mAddon.name], 1
               );
               this._errorLink.value = gStrings.ext.GetStringFromName("notification.blocked.link");
-              this._errorLink.href = Services.urlFormatter.formatURLPref("extensions.blocklist.detailsURL");
+              this._errorLink.href = this.mAddon.blocklistURL;
               this._errorLink.hidden = false;
             } else if (!isUpgrade && !this.mAddon.isCompatible) {
               this.setAttribute("notification", "warning");
               this._warning.textContent = gStrings.ext.formatStringFromName(
                 "notification.incompatible",
                 [this.mAddon.name, gStrings.brandShortName, gStrings.appVersion], 3
               );
               this._warningLink.hidden = true;
               this._warningBtn.hidden = true;
             } else if (!isUpgrade && this.mAddon.blocklistState == Ci.nsIBlocklistService.STATE_SOFTBLOCKED) {
               this.setAttribute("notification", "warning");
               this._warning.textContent = gStrings.ext.formatStringFromName(
                 "notification.softblocked",
                 [this.mAddon.name], 1
               );
               this._warningLink.value = gStrings.ext.GetStringFromName("notification.softblocked.link");
-              this._warningLink.href = Services.urlFormatter.formatURLPref("extensions.blocklist.detailsURL");
+              this._warningLink.href = this.mAddon.blocklistURL;
               this._warningLink.hidden = false;
               this._warningBtn.hidden = true;
             } else if (!isUpgrade && this.mAddon.blocklistState == Ci.nsIBlocklistService.STATE_OUTDATED) {
               this.setAttribute("notification", "warning");
               this._warning.textContent = gStrings.ext.formatStringFromName(
                 "notification.outdated",
                 [this.mAddon.name], 1
               );
--- a/toolkit/mozapps/extensions/nsBlocklistService.js
+++ b/toolkit/mozapps/extensions/nsBlocklistService.js
@@ -50,16 +50,17 @@ Components.utils.import("resource://gre/
 Components.utils.import("resource://gre/modules/Services.jsm");
 
 const TOOLKIT_ID                      = "toolkit@mozilla.org"
 const KEY_PROFILEDIR                  = "ProfD";
 const KEY_APPDIR                      = "XCurProcD";
 const FILE_BLOCKLIST                  = "blocklist.xml";
 const PREF_BLOCKLIST_LASTUPDATETIME   = "app.update.lastUpdateTime.blocklist-background-update-timer";
 const PREF_BLOCKLIST_URL              = "extensions.blocklist.url";
+const PREF_BLOCKLIST_ITEM_URL         = "extensions.blocklist.itemURL";
 const PREF_BLOCKLIST_ENABLED          = "extensions.blocklist.enabled";
 const PREF_BLOCKLIST_INTERVAL         = "extensions.blocklist.interval";
 const PREF_BLOCKLIST_LEVEL            = "extensions.blocklist.level";
 const PREF_BLOCKLIST_PINGCOUNTTOTAL   = "extensions.blocklist.pingCountTotal";
 const PREF_BLOCKLIST_PINGCOUNTVERSION = "extensions.blocklist.pingCountVersion";
 const PREF_PLUGINS_NOTIFYUSER         = "plugins.update.notifyUser";
 const PREF_GENERAL_USERAGENT_LOCALE   = "general.useragent.locale";
 const PREF_PARTNER_BRANCH             = "app.partner.";
@@ -412,16 +413,38 @@ Blocklist.prototype = {
     for (var i = 0; i < blItem.length; ++i) {
       if (blItem[i].includesItem(version, appVersion, toolkitVersion))
         return blItem[i].severity >= gBlocklistLevel ? Ci.nsIBlocklistService.STATE_BLOCKED :
                                                        Ci.nsIBlocklistService.STATE_SOFTBLOCKED;
     }
     return Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
   },
 
+  /* See nsIBlocklistService */
+  getAddonBlocklistURL: function(id, version, appVersion, toolkitVersion) {
+    if (!gBlocklistEnabled)
+      return "";
+
+    if (!this._addonEntries)
+      this._loadBlocklist();
+
+    let blItem = this._addonEntries[id];
+    if (!blItem || !blItem.blockID)
+      return null;
+
+    return this._createBlocklistURL(blItem.blockID);
+  },
+
+  _createBlocklistURL: function(id) {
+    let url = Services.urlFormatter.formatURLPref(PREF_BLOCKLIST_ITEM_URL);
+    url = url.replace(/%blockID%/g, id);
+
+    return url;
+  },
+
   notify: function(aTimer) {
     if (!gBlocklistEnabled)
       return;
 
     try {
       var dsURI = gPref.getCharPref(PREF_BLOCKLIST_URL);
     }
     catch (e) {
@@ -605,17 +628,17 @@ Blocklist.prototype = {
     LOG("Blocklist::_loadBlocklist: no XML File found");
   },
 
   /**
 #    The blocklist XML file looks something like this:
 #
 #    <blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist">
 #      <emItems>
-#        <emItem id="item_1@domain">
+#        <emItem id="item_1@domain" blockID="i1">
 #          <versionRange minVersion="1.0" maxVersion="2.0.*">
 #            <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
 #              <versionRange minVersion="1.5" maxVersion="1.5.*"/>
 #              <versionRange minVersion="1.7" maxVersion="1.7.*"/>
 #            </targetApplication>
 #            <targetApplication id="toolkit@mozilla.org">
 #              <versionRange minVersion="1.9" maxVersion="1.9.*"/>
 #            </targetApplication>
@@ -624,36 +647,36 @@ Blocklist.prototype = {
 #            <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
 #              <versionRange minVersion="1.5" maxVersion="1.5.*"/>
 #            </targetApplication>
 #            <targetApplication id="toolkit@mozilla.org">
 #              <versionRange minVersion="1.9" maxVersion="1.9.*"/>
 #            </targetApplication>
 #          </versionRange>
 #        </emItem>
-#        <emItem id="item_2@domain">
+#        <emItem id="item_2@domain" blockID="i2">
 #          <versionRange minVersion="3.1" maxVersion="4.*"/>
 #        </emItem>
 #        <emItem id="item_3@domain">
 #          <versionRange>
 #            <targetApplication id="{ec8030f7-c20a-464f-9b0e-13a3a9e97384}">
 #              <versionRange minVersion="1.5" maxVersion="1.5.*"/>
 #            </targetApplication>
 #          </versionRange>
 #        </emItem>
-#        <emItem id="item_4@domain">
+#        <emItem id="item_4@domain" blockID="i3">
 #          <versionRange>
 #            <targetApplication>
 #              <versionRange minVersion="1.5" maxVersion="1.5.*"/>
 #            </targetApplication>
 #          </versionRange>
 #        <emItem id="item_5@domain"/>
 #      </emItems>
 #      <pluginItems>
-#        <pluginItem>
+#        <pluginItem blockID="i4">
 #          <!-- All match tags must match a plugin to blocklist a plugin -->
 #          <match name="name" exp="some plugin"/>
 #          <match name="description" exp="1[.]2[.]3"/>
 #        </pluginItem>
 #      </pluginItems>
 #    </blocklist>
    */
 
@@ -738,26 +761,29 @@ Blocklist.prototype = {
         continue;
 
       result[id].push(new BlocklistItemData(versionRangeElement));
     }
     // if only the extension ID is specified block all versions of the
     // extension for the current application.
     if (result[id].length == 0)
       result[id].push(new BlocklistItemData(null));
+
+    result[id].blockID = blocklistElement.getAttribute("blockID");
   },
 
   _handlePluginItemNode: function(blocklistElement, result) {
     if (!matchesOSABI(blocklistElement))
       return;
 
     var matchNodes = blocklistElement.childNodes;
     var blockEntry = {
       matches: {},
-      versions: []
+      versions: [],
+      blockID: null,
     };
     var hasMatch = false;
     for (var x = 0; x < matchNodes.length; ++x) {
       var matchElement = matchNodes.item(x);
       if (!(matchElement instanceof Ci.nsIDOMElement))
         continue;
       if (matchElement.localName == "match") {
         var name = matchElement.getAttribute("name");
@@ -773,16 +799,19 @@ Blocklist.prototype = {
         blockEntry.versions.push(new BlocklistItemData(matchElement));
     }
     // Plugin entries require *something* to match to an actual plugin
     if (!hasMatch)
       return;
     // Add a default versionRange if there wasn't one specified
     if (blockEntry.versions.length == 0)
       blockEntry.versions.push(new BlocklistItemData(null));
+
+    blockEntry.blockID = blocklistElement.getAttribute("blockID");
+
     result.push(blockEntry);
   },
 
   /* See nsIBlocklistService */
   getPluginBlocklistState: function(plugin, appVersion, toolkitVersion) {
     if (!this._pluginEntries)
       this._loadBlocklist();
     return this._getPluginBlocklistState(plugin, this._pluginEntries,
@@ -839,16 +868,44 @@ Blocklist.prototype = {
           return Ci.nsIBlocklistService.STATE_SOFTBLOCKED;
         }
       }
     }
 
     return Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
   },
 
+  /* See nsIBlocklistService */
+  getPluginBlocklistURL: function(plugin) {
+    if (!gBlocklistEnabled)
+      return "";
+
+    if (!this._pluginEntries)
+      this._loadBlocklist();
+
+    for each (let blockEntry in this._pluginEntries) {
+      let matchFailed = false;
+      for (let name in blockEntry.matches) {
+        if (!(name in plugin) ||
+            typeof(plugin[name]) != "string" ||
+            !blockEntry.matches[name].test(plugin[name])) {
+          matchFailed = true;
+          break;
+        }
+      }
+
+      if (!matchFailed) {
+        if(!blockEntry.blockID)
+          return null;
+        else
+          return this._createBlocklistURL(blockEntry.blockID);
+      }
+    }
+  },
+
   _blocklistUpdated: function(oldAddonEntries, oldPluginEntries) {
     var addonList = [];
 
     var self = this;
     AddonManager.getAddonsByTypes(["extension", "theme", "locale"], function(addons) {
 
       for (let i = 0; i < addons.length; i++) {
         let oldState = Ci.nsIBlocklistService.STATE_NOTBLOCKED;
@@ -886,17 +943,18 @@ Blocklist.prototype = {
           continue;
 
         addonList.push({
           name: addons[i].name,
           version: addons[i].version,
           icon: addons[i].iconURL,
           disable: false,
           blocked: state == Ci.nsIBlocklistService.STATE_BLOCKED,
-          item: addons[i]
+          item: addons[i],
+          url: self.getAddonBlocklistURL(addons[i].id),
         });
       }
 
       AddonManagerPrivate.updateAddonAppDisabledStates();
 
       var phs = Cc["@mozilla.org/plugin/host;1"].
                 getService(Ci.nsIPluginHost);
       var plugins = phs.getPluginTags();
@@ -922,17 +980,18 @@ Blocklist.prototype = {
           }
           else {
             addonList.push({
               name: plugins[i].name,
               version: plugins[i].version,
               icon: "chrome://mozapps/skin/plugins/pluginGeneric.png",
               disable: false,
               blocked: state == Ci.nsIBlocklistService.STATE_BLOCKED,
-              item: plugins[i]
+              item: plugins[i],
+              url: self.getPluginBlocklistURL(plugins[i]),
             });
           }
         }
         plugins[i].blocklisted = state == Ci.nsIBlocklistService.STATE_BLOCKED;
       }
 
       if (addonList.length == 0) {
         Services.obs.notifyObservers(self, "blocklist-updated", "");
@@ -953,35 +1012,49 @@ Blocklist.prototype = {
 
       var args = {
         restart: false,
         list: addonList
       };
       // This lets the dialog get the raw js object
       args.wrappedJSObject = args;
 
-      var ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].
-               getService(Ci.nsIWindowWatcher);
-      ww.openWindow(null, URI_BLOCKLIST_DIALOG, "",
-                    "chrome,centerscreen,dialog,modal,titlebar", args);
+      /*
+        Some tests run without UI, so the async code listens to a message
+        that can be sent programatically
+      */
+      let applyBlocklistChanges = function() {
+        for (let i = 0; i < addonList.length; i++) {
+          if (!addonList[i].disable)
+            continue;
 
-      for (let i = 0; i < addonList.length; i++) {
-        if (!addonList[i].disable)
-          continue;
+          if (addonList[i].item instanceof Ci.nsIPluginTag)
+            addonList[i].item.disabled = true;
+          else
+            addonList[i].item.softDisabled = true;
+        }
 
-        if (addonList[i].item instanceof Ci.nsIPluginTag)
-          addonList[i].item.disabled = true;
-        else
-          addonList[i].item.softDisabled = true;
+        if (args.restart)
+          restartApp();
+
+        Services.obs.notifyObservers(self, "blocklist-updated", "");
+        Services.obs.removeObserver(arguments.callee, "addon-blocklist-closed");
       }
 
-      if (args.restart)
-        restartApp();
+      Services.obs.addObserver(applyBlocklistChanges, "addon-blocklist-closed", false)
+
+      let blocklistWindow = Services.ww.openWindow(null, URI_BLOCKLIST_DIALOG, "",
+                              "chrome,centerscreen,dialog,titlebar", args);
 
-      Services.obs.notifyObservers(self, "blocklist-updated", "");
+      blocklistWindow.addEventListener("unload", function(event) {
+        if(event.target.location == URI_BLOCKLIST_DIALOG) {
+          applyBlocklistChanges();
+          blocklistWindow.removeEventListener("unload", arguments.callee);
+        }
+      },false)
     });
   },
 
   classID: Components.ID("{66354bc9-7ed1-4692-ae1d-8da97d6b205e}"),
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
                                          Ci.nsIBlocklistService,
                                          Ci.nsITimerCallback]),
 };
--- a/toolkit/mozapps/extensions/test/browser/browser_bug523784.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_bug523784.js
@@ -14,16 +14,17 @@ Components.utils.import("resource://gre/
 let args = {
   restart: false,
   list: [{
     name: "Bug 523784 softblocked addon",
     version: "1",
     icon: "chrome://mozapps/skin/plugins/pluginGeneric.png",
     disable: false,
     blocked: false,
+    url: 'http://example.com/bug523784_1',
   }],
 };
 
 function test() {
   waitForExplicitFinish();
 
   let windowObserver = function(aSubject, aTopic, aData) {
     if (aTopic != "domwindowopened")
@@ -44,31 +45,81 @@ function test() {
   Services.ww.openWindow(null, URI_BLOCKLIST_DIALOG, "",
                          "chrome,centerscreen,dialog,titlebar", args);
 }
 
 function bug523784_test1(win) {
   let bundle = Services.strings.
               createBundle("chrome://mozapps/locale/update/updates.properties");
   let cancelButton = win.document.documentElement.getButton("cancel");
+  let moreInfoLink = win.document.getElementById("moreInfo");
 
   is(cancelButton.getAttribute("label"),
      bundle.GetStringFromName("restartLaterButton"),
      "Text should be changed on Cancel button");
   is(cancelButton.getAttribute("accesskey"),
      bundle.GetStringFromName("restartLaterButton.accesskey"),
      "Accesskey should also be changed on Cancel button");
-
+  is(moreInfoLink.getAttribute("href"),
+     'http://example.com/bug523784_1',
+     "More Info link should link to a detailed blocklist page.");
   let windowObserver = function(aSubject, aTopic, aData) {
     if (aTopic != "domwindowclosed")
       return;
 
     Services.ww.unregisterNotification(windowObserver);
 
     ok(args.list[0].disable, "Should be blocking add-on");
     ok(!args.restart, "Should not restart browser immediately");
 
-    executeSoon(finish);
+    executeSoon(bug523784_test2);
   };
   Services.ww.registerNotification(windowObserver);
 
   cancelButton.doCommand();
 }
+
+function bug523784_test2(win) {
+  let windowObserver = function(aSubject, aTopic, aData) {
+    if (aTopic != "domwindowopened")
+      return;
+
+    Services.ww.unregisterNotification(windowObserver);
+    let win = aSubject.QueryInterface(Ci.nsIDOMWindow);
+    win.addEventListener("load", function() {
+      win.removeEventListener("load", arguments.callee, false);
+
+    executeSoon(function(){
+      let moreInfoLink = win.document.getElementById("moreInfo");
+      let cancelButton = win.document.documentElement.getButton("cancel");
+      is(moreInfoLink.getAttribute("href"),
+         Services.urlFormatter.formatURLPref("extensions.blocklist.detailsURL"),
+         "More Info link should link to the general blocklist page.");
+      cancelButton.doCommand();
+      executeSoon(finish);
+    })
+    }, false);
+  };
+  Services.ww.registerNotification(windowObserver);
+
+  // Add 2 more addons to the blocked list to check that the more info link
+  // points to the general blocked list page.
+  args.list.push({
+    name: "Bug 523784 softblocked addon 2",
+    version: "2",
+    icon: "chrome://mozapps/skin/plugins/pluginGeneric.png",
+    disable: false,
+    blocked: false,
+    url: 'http://example.com/bug523784_2'
+  });
+  args.list.push({
+    name: "Bug 523784 softblocked addon 3",
+    version: "4",
+    icon: "chrome://mozapps/skin/plugins/pluginGeneric.png",
+    disable: false,
+    blocked: false,
+    url: 'http://example.com/bug523784_3'
+  });
+
+  args.wrappedJSObject = args;
+  Services.ww.openWindow(null, URI_BLOCKLIST_DIALOG, "",
+                         "chrome,centerscreen,dialog,titlebar", args);
+}
\ No newline at end of file
--- a/toolkit/mozapps/extensions/test/browser/browser_details.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_details.js
@@ -100,37 +100,42 @@ function test() {
                  AddonManager.PERM_CAN_DISABLE |
                  AddonManager.PERM_CAN_UPGRADE,
     screenshots: [{
       url: "http://example.com/screenshot",
       thumbnailURL: "http://example.com/thumbnail"
     }],
   }, {
     id: "addon4@tests.mozilla.org",
+    blocklistURL: "http://example.com/addon4@tests.mozilla.org",
     name: "Test add-on 4",
     _userDisabled: true,
     isActive: false,
     blocklistState: Ci.nsIBlocklistService.STATE_SOFTBLOCKED
   }, {
     id: "addon5@tests.mozilla.org",
+    blocklistURL: "http://example.com/addon5@tests.mozilla.org",
     name: "Test add-on 5",
     isActive: false,
     blocklistState: Ci.nsIBlocklistService.STATE_BLOCKED,
     appDisabled: true
   }, {
     id: "addon6@tests.mozilla.org",
+    blocklistURL: "http://example.com/addon6@tests.mozilla.org",
     name: "Test add-on 6",
     operationsRequiringRestart: AddonManager.OP_NEEDS_RESTART_NONE
   }, {
     id: "addon7@tests.mozilla.org",
+    blocklistURL: "http://example.com/addon7@tests.mozilla.org",
     name: "Test add-on 7",
     _userDisabled: true,
     isActive: false
   }, {
     id: "addon8@tests.mozilla.org",
+    blocklistURL: "http://example.com/addon8@tests.mozilla.org",
     name: "Test add-on 8",
     blocklistState: Ci.nsIBlocklistService.STATE_OUTDATED
   }]);
 
   open_manager(null, function(aWindow) {
     gManagerWindow = aWindow;
     gCategoryUtilities = new CategoryUtilities(gManagerWindow);
 
@@ -377,17 +382,17 @@ add_test(function() {
     is_element_visible(get("detail-enable-btn"), "Enable button should be visible");
     is_element_hidden(get("detail-disable-btn"), "Disable button should be hidden");
     is_element_visible(get("detail-uninstall-btn"), "Remove button should be visible");
 
     is_element_visible(get("detail-warning"), "Warning message should be visible");
     is(get("detail-warning").textContent, "Test add-on 4 is known to cause security or stability issues.", "Warning message should be correct");
     is_element_visible(get("detail-warning-link"), "Warning link should be visible");
     is(get("detail-warning-link").value, "More Information", "Warning link text should be correct");
-    is(get("detail-warning-link").href, gBlocklistURL, "Warning link should be correct");
+    is(get("detail-warning-link").href, "http://example.com/addon4@tests.mozilla.org", "Warning link should be correct");
     is_element_hidden(get("detail-error"), "Error message should be hidden");
     is_element_hidden(get("detail-error-link"), "Error link should be hidden");
     is_element_hidden(get("detail-pending"), "Pending message should be hidden");
 
     // Enable it
     EventUtils.synthesizeMouseAtCenter(get("detail-enable-btn"), {}, gManagerWindow);
     is_element_hidden(get("detail-prefs-btn"), "Preferences button should be hidden");
     is_element_hidden(get("detail-enable-btn"), "Enable button should be hidden");
@@ -421,17 +426,17 @@ add_test(function() {
       is_element_visible(get("detail-enable-btn"), "Enable button should be visible");
       is_element_hidden(get("detail-disable-btn"), "Disable button should be hidden");
       is_element_visible(get("detail-uninstall-btn"), "Remove button should be visible");
 
       is_element_visible(get("detail-warning"), "Warning message should be visible");
       is(get("detail-warning").textContent, "Test add-on 4 is known to cause security or stability issues.", "Warning message should be correct");
       is_element_visible(get("detail-warning-link"), "Warning link should be visible");
       is(get("detail-warning-link").value, "More Information", "Warning link text should be correct");
-      is(get("detail-warning-link").href, gBlocklistURL, "Warning link should be correct");
+      is(get("detail-warning-link").href, "http://example.com/addon4@tests.mozilla.org", "Warning link should be correct");
       is_element_hidden(get("detail-error"), "Error message should be hidden");
       is_element_hidden(get("detail-error-link"), "Error link should be hidden");
       is_element_hidden(get("detail-pending"), "Pending message should be hidden");
 
       run_next_test();
     });
   });
 });
@@ -447,17 +452,17 @@ add_test(function() {
     is_element_visible(get("detail-uninstall-btn"), "Remove button should be visible");
 
     is_element_hidden(get("detail-warning"), "Warning message should be hidden");
     is_element_hidden(get("detail-warning-link"), "Warning link should be hidden");
     is_element_visible(get("detail-error"), "Error message should be visible");
     is(get("detail-error").textContent, "Test add-on 5 has been disabled due to security or stability issues.", "Error message should be correct");
     is_element_visible(get("detail-error-link"), "Error link should be visible");
     is(get("detail-error-link").value, "More Information", "Error link text should be correct");
-    is(get("detail-error-link").href, gBlocklistURL, "Error link should be correct");
+    is(get("detail-error-link").href, "http://example.com/addon5@tests.mozilla.org", "Error link should be correct");
     is_element_hidden(get("detail-pending"), "Pending message should be hidden");
 
     run_next_test();
   });
 });
 
 // Opens and tests the details view for add-on 6
 add_test(function() {
--- a/toolkit/mozapps/extensions/test/browser/browser_list.js
+++ b/toolkit/mozapps/extensions/test/browser/browser_list.js
@@ -57,32 +57,36 @@ function test() {
     isActive: false,
     isCompatible: false,
     appDisabled: true,
     permissions: AddonManager.PERM_CAN_ENABLE |
                  AddonManager.PERM_CAN_DISABLE |
                  AddonManager.PERM_CAN_UPGRADE
   }, {
     id: "addon4@tests.mozilla.org",
+    blocklistURL: "http://example.com/addon4@tests.mozilla.org",
     name: "Test add-on 4",
     _userDisabled: true,
     isActive: false,
     blocklistState: Ci.nsIBlocklistService.STATE_SOFTBLOCKED
   }, {
     id: "addon5@tests.mozilla.org",
+    blocklistURL: "http://example.com/addon5@tests.mozilla.org",
     name: "Test add-on 5",
     isActive: false,
     blocklistState: Ci.nsIBlocklistService.STATE_BLOCKED,
     appDisabled: true
   }, {
     id: "addon6@tests.mozilla.org",
+    blocklistURL: "http://example.com/addon6@tests.mozilla.org",
     name: "Test add-on 6",
     operationsRequiringRestart: AddonManager.OP_NEEDS_RESTART_NONE
   }, {
     id: "addon7@tests.mozilla.org",
+    blocklistURL: "http://example.com/addon7@tests.mozilla.org",
     name: "Test add-on 7",
     blocklistState: Ci.nsIBlocklistService.STATE_OUTDATED,
   }]);
 
   open_manager(null, function(aWindow) {
     gManagerWindow = aWindow;
     gCategoryUtilities = new CategoryUtilities(gManagerWindow);
     run_next_test();
@@ -225,17 +229,17 @@ add_test(function() {
     is_element_visible(get_node(addon, "enable-btn"), "Enable button should be visible");
     is_element_hidden(get_node(addon, "disable-btn"), "Disable button should be hidden");
     is_element_visible(get_node(addon, "remove-btn"), "Remove button should be visible");
 
     is_element_visible(get_node(addon, "warning"), "Warning message should be visible");
     is(get_node(addon, "warning").textContent, "Test add-on 4 is known to cause security or stability issues.", "Warning message should be correct");
     is_element_visible(get_node(addon, "warning-link"), "Warning link should be visible");
     is(get_node(addon, "warning-link").value, "More Information", "Warning link text should be correct");
-    is(get_node(addon, "warning-link").href, gBlocklistURL, "Warning link should be correct");
+    is(get_node(addon, "warning-link").href, "http://example.com/addon4@tests.mozilla.org", "Warning link should be correct");
     is_element_hidden(get_node(addon, "error"), "Error message should be hidden");
     is_element_hidden(get_node(addon, "error-link"), "Error link should be hidden");
     is_element_hidden(get_node(addon, "pending"), "Pending message should be hidden");
 
     info("Enabling");
     EventUtils.synthesizeMouseAtCenter(get_node(addon, "enable-btn"), {}, gManagerWindow);
     is_element_hidden(get_node(addon, "preferences-btn"), "Preferences button should be hidden");
     is_element_hidden(get_node(addon, "enable-btn"), "Enable button should be hidden");
@@ -260,17 +264,17 @@ add_test(function() {
     is_element_visible(get_node(addon, "remove-btn"), "Remove button should be visible");
 
     is_element_hidden(get_node(addon, "warning"), "Warning message should be hidden");
     is_element_hidden(get_node(addon, "warning-link"), "Warning link should be hidden");
     is_element_visible(get_node(addon, "error"), "Error message should be visible");
     is(get_node(addon, "error").textContent, "Test add-on 5 has been disabled due to security or stability issues.", "Error message should be correct");
     is_element_visible(get_node(addon, "error-link"), "Error link should be visible");
     is(get_node(addon, "error-link").value, "More Information", "Error link text should be correct");
-    is(get_node(addon, "error-link").href, gBlocklistURL, "Error link should be correct");
+    is(get_node(addon, "error-link").href, "http://example.com/addon5@tests.mozilla.org", "Error link should be correct");
     is_element_hidden(get_node(addon, "pending"), "Pending message should be hidden");
 
     info("Addon 6");
     addon = items["Test add-on 6"];
     addon.parentNode.ensureElementIsVisible(addon);
     is(get_node(addon, "name").value, "Test add-on 6", "Name should be correct");
     is_element_hidden(get_class_node(addon, "disabled-postfix"), "Disabled postfix should be hidden");
 
@@ -456,17 +460,17 @@ add_test(function() {
       is_element_visible(get_node(addon, "enable-btn"), "Enable button should be visible");
       is_element_hidden(get_node(addon, "disable-btn"), "Disable button should be hidden");
       is_element_visible(get_node(addon, "remove-btn"), "Remove button should be visible");
 
       is_element_visible(get_node(addon, "warning"), "Warning message should be visible");
       is(get_node(addon, "warning").textContent, "Test add-on 4 is known to cause security or stability issues.", "Warning message should be correct");
       is_element_visible(get_node(addon, "warning-link"), "Warning link should be visible");
       is(get_node(addon, "warning-link").value, "More Information", "Warning link text should be correct");
-      is(get_node(addon, "warning-link").href, gBlocklistURL, "Warning link should be correct");
+      is(get_node(addon, "warning-link").href, "http://example.com/addon4@tests.mozilla.org", "Warning link should be correct");
       is_element_hidden(get_node(addon, "error"), "Error message should be hidden");
       is_element_hidden(get_node(addon, "error-link"), "Error link should be hidden");
       is_element_hidden(get_node(addon, "pending"), "Pending message should be hidden");
 
       info("Addon 6");
       addon = items["Test add-on 6"];
       addon.parentNode.ensureElementIsVisible(addon);
       is(get_node(addon, "name").value, "Test add-on 6", "Name should be correct");
--- a/toolkit/mozapps/extensions/test/xpcshell/data/bug455906_block.xml
+++ b/toolkit/mozapps/extensions/test/xpcshell/data/bug455906_block.xml
@@ -1,18 +1,18 @@
 <?xml version="1.0" encoding="UTF-8"?>
 
 <blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist">
   <emItems>
-    <emItem id="test_bug455906_1@tests.mozilla.org"/>
-    <emItem id="test_bug455906_2@tests.mozilla.org"/>
-    <emItem id="test_bug455906_3@tests.mozilla.org"/>
-    <emItem id="test_bug455906_4@tests.mozilla.org"/>
-    <emItem id="test_bug455906_5@tests.mozilla.org"/>
-    <emItem id="test_bug455906_6@tests.mozilla.org"/>
-    <emItem id="test_bug455906_7@tests.mozilla.org"/>
+    <emItem id="test_bug455906_1@tests.mozilla.org" blockID="test_bug455906_1@tests.mozilla.org"/>
+    <emItem id="test_bug455906_2@tests.mozilla.org" blockID="test_bug455906_2@tests.mozilla.org"/>
+    <emItem id="test_bug455906_3@tests.mozilla.org" blockID="test_bug455906_3@tests.mozilla.org"/>
+    <emItem id="test_bug455906_4@tests.mozilla.org" blockID="test_bug455906_4@tests.mozilla.org"/>
+    <emItem id="test_bug455906_5@tests.mozilla.org" blockID="test_bug455906_5@tests.mozilla.org"/>
+    <emItem id="test_bug455906_6@tests.mozilla.org" blockID="test_bug455906_6@tests.mozilla.org"/>
+    <emItem id="test_bug455906_7@tests.mozilla.org" blockID="test_bug455906_7@tests.mozilla.org"/>
   </emItems>
   <pluginItems>
-    <pluginItem>
+    <pluginItem blockID="test_bug455906_plugin">
       <match name="name" exp="^test_bug455906"/>
     </pluginItem>
   </pluginItems>
 </blocklist>
--- a/toolkit/mozapps/extensions/test/xpcshell/test_blocklistchange.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_blocklistchange.js
@@ -283,16 +283,20 @@ var WindowWatcher = {
     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;
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug455906.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug455906.js
@@ -32,16 +32,20 @@
  * and other provisions required by the GPL or the LGPL. If you do not delete
  * the provisions above, a recipient may use your version of this file under
  * the terms of any one of the MPL, the GPL or the LGPL
  *
  * ***** END LICENSE BLOCK *****
  */
 const URI_EXTENSION_BLOCKLIST_DIALOG = "chrome://mozapps/content/extensions/blocklist.xul";
 
+// Workaround for Bug 658720 - URL formatter can leak during xpcshell tests
+const PREF_BLOCKLIST_ITEM_URL = "extensions.blocklist.itemURL";
+Services.prefs.setCharPref(PREF_BLOCKLIST_ITEM_URL, "http://localhost:4444/blocklist/%blockID%");
+
 do_load_httpd_js();
 
 var ADDONS = [{
   // Tests how the blocklist affects a disabled add-on
   id: "test_bug455906_1@tests.mozilla.org",
   name: "Bug 455906 Addon Test 1",
   version: "5",
   appVersion: "3"
@@ -168,16 +172,19 @@ var WindowWatcher = {
     // Should be called to list the newly blocklisted items
     do_check_eq(url, URI_EXTENSION_BLOCKLIST_DIALOG);
 
     if (gNotificationCheck) {
       var args = arguments.wrappedJSObject;
       gNotificationCheck(args);
     }
 
+    //run the code after the blocklist is closed
+    Services.obs.notifyObservers(null, "addon-blocklist-closed", null);
+
     // Call the next test after the blocklist has finished up
     do_timeout(0, gTestCheck);
   },
 
   QueryInterface: function(iid) {
     if (iid.equals(Ci.nsIWindowWatcher)
      || iid.equals(Ci.nsISupports))
       return this;
@@ -241,16 +248,22 @@ function load_blocklist(file) {
 function check_addon_state(addon) {
   return addon.userDisabled + "," + addon.softDisabled + "," + addon.appDisabled;
 }
 
 function check_plugin_state(plugin) {
   return plugin.disabled + "," + plugin.blocklisted;
 }
 
+function create_blocklistURL(blockID){
+  let url = Services.urlFormatter.formatURLPref(PREF_BLOCKLIST_ITEM_URL);
+  url = url.replace(/%blockID%/g, blockID);
+  return url;
+}
+
 // Performs the initial setup
 function run_test() {
   // Setup for test
   dump("Setting up tests\n");
   // Rather than keeping lots of identical add-ons in version control, just
   // write them into the profile.
   for (var i = 0; i < ADDONS.length; i++)
     create_addon(ADDONS[i]);
@@ -453,31 +466,48 @@ function check_notification_pt3(args) {
     }
   }
 }
 
 function check_test_pt3() {
   restartManager();
   dump("Checking results pt 3\n");
 
+  let blocklist = Cc["@mozilla.org/extensions/blocklist;1"].
+                  getService(Ci.nsIBlocklistService);
+
   AddonManager.getAddonsByIDs([a.id for each (a in ADDONS)], function(addons) {
     // All should have gained the blocklist state, user disabled as previously
     do_check_eq(check_addon_state(addons[0]), "true,false,true");
     do_check_eq(check_addon_state(addons[1]), "false,false,true");
     do_check_eq(check_addon_state(addons[2]), "false,false,true");
     do_check_eq(check_addon_state(addons[4]), "false,false,true");
     do_check_eq(check_plugin_state(PLUGINS[0]), "true,true");
     do_check_eq(check_plugin_state(PLUGINS[1]), "false,true");
     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));
+
+    // 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'));
+
     // Shouldn't be changed
     do_check_eq(check_addon_state(addons[5]), "false,false,true");
     do_check_eq(check_addon_state(addons[6]), "false,false,true");
     do_check_eq(check_plugin_state(PLUGINS[5]), "false,true");
 
     // Back to starting state
     gNotificationCheck = null;
     gTestCheck = run_test_pt4;
--- a/toolkit/mozapps/extensions/test/xpcshell/test_bug514327_2.js
+++ b/toolkit/mozapps/extensions/test/xpcshell/test_bug514327_2.js
@@ -63,14 +63,18 @@ function run_test() {
 
   var blocklist = Cc["@mozilla.org/extensions/blocklist;1"].getService(nsIBLS);
   var prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
 
   var plugin = get_test_plugintag();
   if (!plugin)
     do_throw("Plugin tag not found");
 
-  // should be marked as outdated by the blocklist
-  do_check_true(blocklist.getPluginBlocklistState(plugin, "1", "1.9") == nsIBLS.STATE_OUTDATED);
+  //run the code after the blocklist is closed
+  Services.obs.notifyObservers(null, "addon-blocklist-closed", null);
+  do_execute_soon(function() {
+    // should be marked as outdated by the blocklist
+    do_check_true(blocklist.getPluginBlocklistState(plugin, "1", "1.9") == nsIBLS.STATE_OUTDATED);
 
-  // should indicate that a warning should be shown
-  do_check_true(prefs.getBoolPref("plugins.update.notifyUser"));
+    // should indicate that a warning should be shown
+    do_check_true(prefs.getBoolPref("plugins.update.notifyUser"));
+  });
 }
--- a/xpcom/system/nsIBlocklistService.idl
+++ b/xpcom/system/nsIBlocklistService.idl
@@ -37,17 +37,17 @@
  * ***** END LICENSE BLOCK ***** */
 
 
 #include "nsISupports.idl"
 
 interface nsIPluginTag;
 interface nsIVariant;
 
-[scriptable, uuid(8439f9c0-da03-4260-8b21-dc635eed28fb)]
+[scriptable, uuid(31845f85-718a-4581-a672-a45c0327cb21)]
 interface nsIBlocklistService : nsISupports
 {
   // Indicates that the item does not appear in the blocklist.
   const unsigned long STATE_NOT_BLOCKED = 0;
   // Indicates that the item is in the blocklist but the problem is not severe
   // enough to warant forcibly blocking.
   const unsigned long STATE_SOFTBLOCKED = 1;
   // Indicates that the item should be blocked and never used.
@@ -109,16 +109,34 @@ interface nsIBlocklistService : nsISuppo
    *          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 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.
+   * @returns The URL of the description page.
+   */
+  AString getAddonBlocklistURL(in AString id, in AString version,
+                              [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.
+   */
+  AString getPluginBlocklistURL(in nsIPluginTag plugin);
 };
 
 /**
  * nsIBlocklistPrompt is used, if available, by the default implementation of 
  * nsIBlocklistService to display a confirmation UI to the user before blocking
  * extensions/plugins.
  */
 [scriptable, uuid(36f97f40-b0c9-11df-94e2-0800200c9a66)]