Bug 455906: Support severities for blocklist entries. r=robstrong, r=jst
authorDave Townsend <dtownsend@oxymoronical.com>
Sun, 02 Nov 2008 12:13:48 +0000
changeset 21189 8291b82dd32306adbe2471988f2270ff68274c71
parent 21188 30a69fb5df8e1bce073e17cbff5637a93e1a1c02
child 21190 82db59e4d40b15c3dc1205154f38aa43ebddae4a
push id3389
push userdtownsend@mozilla.com
push dateSun, 02 Nov 2008 12:14:10 +0000
treeherdermozilla-central@8291b82dd323 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersrobstrong, jst
bugs455906
milestone1.9.1b2pre
Bug 455906: Support severities for blocklist entries. r=robstrong, r=jst
browser/app/profile/firefox.js
modules/plugin/base/src/nsPluginHostImpl.cpp
toolkit/locales/en-US/chrome/mozapps/extensions/blocklist.dtd
toolkit/locales/en-US/chrome/mozapps/extensions/extensions.dtd
toolkit/locales/en-US/chrome/mozapps/extensions/extensions.properties
toolkit/locales/jar.mn
toolkit/mozapps/extensions/content/blocklist.css
toolkit/mozapps/extensions/content/blocklist.js
toolkit/mozapps/extensions/content/blocklist.xul
toolkit/mozapps/extensions/content/extensions.js
toolkit/mozapps/extensions/content/extensions.xml
toolkit/mozapps/extensions/content/list.js
toolkit/mozapps/extensions/content/list.xul
toolkit/mozapps/extensions/jar.mn
toolkit/mozapps/extensions/public/Makefile.in
toolkit/mozapps/extensions/public/nsIBlocklistService.idl
toolkit/mozapps/extensions/public/nsIExtensionManager.idl
toolkit/mozapps/extensions/src/nsBlocklistService.js
toolkit/mozapps/extensions/src/nsExtensionManager.js.in
toolkit/mozapps/extensions/test/unit/data/bug455906_block.xml
toolkit/mozapps/extensions/test/unit/data/bug455906_empty.xml
toolkit/mozapps/extensions/test/unit/data/bug455906_start.xml
toolkit/mozapps/extensions/test/unit/data/bug455906_warn.xml
toolkit/mozapps/extensions/test/unit/test_bug449027.js
toolkit/mozapps/extensions/test/unit/test_bug455906.js
toolkit/themes/gnomestripe/mozapps/extensions/extensions.css
toolkit/themes/pinstripe/mozapps/extensions/blocklist.css
toolkit/themes/pinstripe/mozapps/extensions/extensions.css
toolkit/themes/pinstripe/mozapps/jar.mn
toolkit/themes/winstripe/mozapps/extensions/blocklist.css
toolkit/themes/winstripe/mozapps/extensions/extensions.css
toolkit/themes/winstripe/mozapps/jar.mn
xpcom/system/Makefile.in
xpcom/system/nsIBlocklistService.idl
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -75,16 +75,19 @@ pref("extensions.getAddons.maxResults", 
 pref("extensions.getAddons.recommended.browseURL", "https://%LOCALE%.add-ons.mozilla.com/%LOCALE%/%APP%/recommended");
 pref("extensions.getAddons.recommended.url", "https://services.addons.mozilla.org/%LOCALE%/%APP%/api/%API_VERSION%/list/featured/all/10/%OS%/%VERSION%");
 pref("extensions.getAddons.search.browseURL", "https://%LOCALE%.add-ons.mozilla.com/%LOCALE%/%APP%/search?q=%TERMS%");
 pref("extensions.getAddons.search.url", "https://services.addons.mozilla.org/%LOCALE%/%APP%/api/%API_VERSION%/search/%TERMS%/all/10/%OS%/%VERSION%");
 
 // 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/2/%APP_ID%/%APP_VERSION%/%PRODUCT%/%BUILD_ID%/%BUILD_TARGET%/%LOCALE%/%CHANNEL%/%OS_VERSION%/%DISTRIBUTION%/%DISTRIBUTION_VERSION%/");
 pref("extensions.blocklist.detailsURL", "http://%LOCALE%.www.mozilla.com/%LOCALE%/blocklist/");
 
 // Dictionary download preference
 pref("browser.dictionaries.download.url", "https://%LOCALE%.add-ons.mozilla.com/%LOCALE%/firefox/%VERSION%/dictionaries/");
 
 // App-specific update preferences
 
--- a/modules/plugin/base/src/nsPluginHostImpl.cpp
+++ b/modules/plugin/base/src/nsPluginHostImpl.cpp
@@ -87,16 +87,17 @@
 #include "nsHashtable.h"
 #include "nsIProxyInfo.h"
 #include "nsObsoleteModuleLoading.h"
 #include "nsIComponentRegistrar.h"
 #include "nsPluginLogging.h"
 #include "nsIPrefBranch2.h"
 #include "nsIScriptChannel.h"
 #include "nsPrintfCString.h"
+#include "nsIBlocklistService.h"
 
 // Friggin' X11 has to "#define None". Lame!
 #ifdef None
 #undef None
 #endif
 
 #ifdef CursorShape
 #undef CursorShape /*X.h defines it as 0,
@@ -5124,23 +5125,24 @@ nsresult nsPluginHostImpl::ScanPluginsDi
     localfile->InitWithPath(pfd->mFilename);
     PRInt64 fileModTime = pfd->mModTime;
 
     // Look for it in our cache
     nsRefPtr<nsPluginTag> pluginTag;
     RemoveCachedPluginsInfo(NS_ConvertUTF16toUTF8(pfd->mFilename).get(),
                             getter_AddRefs(pluginTag));
 
-    PRUint32 oldFlags = NS_PLUGIN_FLAG_ENABLED;
+    PRBool enabled = PR_TRUE;
+    PRBool seenBefore = PR_FALSE;
     if (pluginTag) {
+      seenBefore = PR_TRUE;
       // If plugin changed, delete cachedPluginTag and don't use cache
       if (LL_NE(fileModTime, pluginTag->mLastModifiedTime)) {
         // Plugins has changed. Don't use cached plugin info.
-        oldFlags = pluginTag->Flags() &
-                   (NS_PLUGIN_FLAG_ENABLED | NS_PLUGIN_FLAG_BLOCKLISTED);
+        enabled = (pluginTag->Flags() & NS_PLUGIN_FLAG_ENABLED) != 0;
         pluginTag = nsnull;
 
         // plugin file changed, flag this fact
         *aPluginsChanged = PR_TRUE;
       }
       else {
         // if it is unwanted plugin we are checking for, get it back to the cache info list
         // if this is a duplicate plugin, too place it back in the cache info list marking unwantedness
@@ -5206,23 +5208,36 @@ nsresult nsPluginHostImpl::ScanPluginsDi
       pluginTag = new nsPluginTag(&info);
       pluginFile.FreePluginInfo(info);
 
       if(pluginTag == nsnull)
         return NS_ERROR_OUT_OF_MEMORY;
 
       pluginTag->mLibrary = pluginLibrary;
       pluginTag->mLastModifiedTime = fileModTime;
-      if (!(oldFlags & NS_PLUGIN_FLAG_ENABLED) ||
-          (pluginTag->mIsJavaPlugin && !mJavaEnabled))
+
+      nsCOMPtr<nsIBlocklistService> blocklist = do_GetService("@mozilla.org/extensions/blocklist;1");
+      if (blocklist) {
+        PRUint32 state;
+        rv = blocklist->GetPluginBlocklistState(pluginTag, EmptyString(),
+                                                EmptyString(), &state);
+
+        if (NS_SUCCEEDED(rv)) {
+          // If the blocklist says so then block the plugin. If the blocklist says
+          // it is risky and we have never seen this plugin before then disable it
+          if (state == nsIBlocklistService::STATE_BLOCKED)
+            pluginTag->Mark(NS_PLUGIN_FLAG_BLOCKLISTED);
+          else if (state == nsIBlocklistService::STATE_SOFTBLOCKED && !seenBefore)
+            enabled = PR_FALSE;
+        }
+      }
+
+      if (!enabled || (pluginTag->mIsJavaPlugin && !mJavaEnabled))
         pluginTag->UnMark(NS_PLUGIN_FLAG_ENABLED);
 
-      if (oldFlags & NS_PLUGIN_FLAG_BLOCKLISTED)
-        pluginTag->Mark(NS_PLUGIN_FLAG_BLOCKLISTED);
-
       // if this is unwanted plugin we are checkin for, or this is a duplicate plugin,
       // add it to our cache info list so we can cache the unwantedness of this plugin
       // when we sync cached plugins to registry
       NS_ASSERTION(!pluginTag->HasFlag(NS_PLUGIN_FLAG_UNWANTED),
                    "Brand-new tags should not be unwanted");
       if((checkForUnwantedPlugins && isUnwantedPlugin(pluginTag)) ||
          IsDuplicatePlugin(pluginTag)) {
         pluginTag->Mark(NS_PLUGIN_FLAG_UNWANTED);
new file mode 100644
--- /dev/null
+++ b/toolkit/locales/en-US/chrome/mozapps/extensions/blocklist.dtd
@@ -0,0 +1,10 @@
+<!ENTITY blocklist.title             "Add-ons may be causing problems">
+<!ENTITY blocklist.style             "width: 45em; height: 30em">
+<!ENTITY blocklist.summary           "&brandShortName; has determined that the following add-ons are known to cause stability or security problems:">
+<!ENTITY blocklist.softblocked       "For your protection, it is highly recommended that you restart with these add-ons disabled.">
+<!ENTITY blocklist.hardblocked       "These add-ons have a high risk of causing stability or security problems and have been blocked, but a restart is required to disable them completely.">
+<!ENTITY blocklist.softandhard       "The add-ons that have a high risk of causing stability or security problems have been blocked. The others are lower risk, but it is highly recommended that you restart with them disabled.">
+<!ENTITY blocklist.moreinfo          "More information">
+
+<!ENTITY blocklist.accept.label      "Restart &brandShortName;">
+<!ENTITY blocklist.accept.accesskey  "R">
--- a/toolkit/locales/en-US/chrome/mozapps/extensions/extensions.dtd
+++ b/toolkit/locales/en-US/chrome/mozapps/extensions/extensions.dtd
@@ -107,16 +107,17 @@
 <!ENTITY includeUpdate.label              "Include this update">
 <!ENTITY includeUpdate.accesskey          "n">
 <!ENTITY includeUpdate.tooltip            "Include this Add-on when installing the updates">
 
 <!-- Status Messsages -->
 <!ENTITY insecureUpdate.label             "Does not provide secure updates.">
 <!ENTITY needsDependencies.label          "Requires additional items.">
 <!ENTITY blocklisted.label                "Disabled for your protection.">
+<!ENTITY softBlocklisted.label            "Known to cause security or stability issues.">
 <!ENTITY toBeDisabled.label               "This add-on will be disabled when &brandShortName; is restarted.">
 <!ENTITY toBeEnabled.label                "This add-on will be enabled when &brandShortName; is restarted.">
 <!ENTITY toBeInstalled.label              "This add-on will be installed when &brandShortName; is restarted.">
 <!ENTITY toBeUninstalled.label            "This add-on will be uninstalled when &brandShortName; is restarted.">
 <!ENTITY toBeUpdated.label                "This add-on will be updated when &brandShortName; is restarted.">
 
 <!ENTITY getExtensions.label              "Get Extensions">
 <!ENTITY getThemes.label                  "Get Themes">
@@ -163,8 +164,11 @@
 <!ENTITY installFailure.label             "Install failed.">
 
 <!ENTITY progressStatus.label             "Checking For Updates">
 
 <!ENTITY eula.title                       "End-User License Agreement">
 <!ENTITY eula.width                       "560px">
 <!ENTITY eula.height                      "400px">
 <!ENTITY eula.accept                      "Accept and Install…">
+
+<!ENTITY blocklist.blocked.label          "Blocked">
+<!ENTITY blocklist.checkbox.label         "Disable">
--- a/toolkit/locales/en-US/chrome/mozapps/extensions/extensions.properties
+++ b/toolkit/locales/en-US/chrome/mozapps/extensions/extensions.properties
@@ -54,21 +54,22 @@ incompatibleThemeName=this Theme
 incompatibleExtension=Disabled - not compatible with %S %S
 incompatibleAddonMsg=Not compatible with %S %S
 insecureUpdateMessage="%S" will not be installed because it does not provide secure updates
 
 invalidGUIDMessage="%S" could not be installed because of an error in its Install Manifest ("%S" is not a valid GUID). Please contact the author of this item about the problem.
 invalidVersionMessage="%S" could not be installed because of an error in its Install Manifest ("%S" is not a valid Version String). Please contact the author of this item about the problem.
 incompatiblePlatformMessage="%S" could not be installed because it is not compatible with your %S build type (%S). Please contact the author of this item about the problem.
 
-blocklistedInstallTitle=This extension is not secure
-blocklistedInstallMsg=The extension %S is known to be dangerous, and can't be installed.
-blocklistNotifyTitle2=Add-ons may be causing problems
-blocklistNotifyMsg2=%S has determined that the following add-ons may be unstable or insecure.
-blocklistRestartMsg2=You should restart %S so that these add-ons can be disabled.
+blocklistedInstallTitle2=This add-on is dangerous to use
+blocklistedInstallMsg2=The add-on %S has a high risk of causing stability or security problems and can't be installed.
+softBlockedInstallTitle=This add-on may be dangerous to use
+softBlockedInstallMsg=The add-on %S may cause stability or security problems. It is highly recommended that you do not install it.
+softBlockedInstallAcceptLabel=Install Anyway
+softBlockedInstallAcceptKey=I
 
 missingFileTitle=Missing File
 missingFileMessage=%S could not load this item because the file %S was missing.
 
 malformedMessage=%S could not install this item because "%S" (provided by the item) is not well-formed or does not exist. Please contact the author about this problem.
 malformedTitle=Malformed File
 
 malformedRegistrationTitle=Chrome Registration Failed
--- a/toolkit/locales/jar.mn
+++ b/toolkit/locales/jar.mn
@@ -72,16 +72,17 @@
 % locale mozapps @AB_CD@ %locale/@AB_CD@/mozapps/
 * locale/@AB_CD@/mozapps/downloads/unknownContentType.properties  (%chrome/mozapps/downloads/unknownContentType.properties)
 * locale/@AB_CD@/mozapps/downloads/unknownContentType.dtd         (%chrome/mozapps/downloads/unknownContentType.dtd)
 * locale/@AB_CD@/mozapps/downloads/settingsChange.dtd             (%chrome/mozapps/downloads/settingsChange.dtd)
   locale/@AB_CD@/mozapps/downloads/downloads.dtd                  (%chrome/mozapps/downloads/downloads.dtd)
   locale/@AB_CD@/mozapps/downloads/downloads.properties           (%chrome/mozapps/downloads/downloads.properties)
   locale/@AB_CD@/mozapps/extensions/extensions.dtd                (%chrome/mozapps/extensions/extensions.dtd)
   locale/@AB_CD@/mozapps/extensions/extensions.properties         (%chrome/mozapps/extensions/extensions.properties)
+  locale/@AB_CD@/mozapps/extensions/blocklist.dtd                 (%chrome/mozapps/extensions/blocklist.dtd)
   locale/@AB_CD@/mozapps/extensions/about.dtd                     (%chrome/mozapps/extensions/about.dtd)
   locale/@AB_CD@/mozapps/extensions/errors.dtd                    (%chrome/mozapps/extensions/errors.dtd)
   locale/@AB_CD@/mozapps/extensions/update.dtd                    (%chrome/mozapps/extensions/update.dtd)
   locale/@AB_CD@/mozapps/extensions/update.properties             (%chrome/mozapps/extensions/update.properties)
   locale/@AB_CD@/mozapps/handling/handling.dtd                    (%chrome/mozapps/handling/handling.dtd)
   locale/@AB_CD@/mozapps/handling/handling.properties             (%chrome/mozapps/handling/handling.properties)
   locale/@AB_CD@/mozapps/plugins/plugins.dtd                      (%chrome/mozapps/plugins/plugins.dtd)
   locale/@AB_CD@/mozapps/plugins/plugins.properties               (%chrome/mozapps/plugins/plugins.properties)
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/content/blocklist.css
@@ -0,0 +1,11 @@
+hbox.addon-name-version {
+  -moz-binding: url("chrome://mozapps/content/extensions/extensions.xml#addon-name-version");
+}
+
+.hardBlockedAddon {
+  -moz-binding: url("chrome://mozapps/content/extensions/extensions.xml#hardblockedaddon");
+}
+
+.softBlockedAddon {
+  -moz-binding: url("chrome://mozapps/content/extensions/extensions.xml#softblockedaddon");
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/content/blocklist.js
@@ -0,0 +1,81 @@
+# ***** BEGIN LICENSE BLOCK *****
+# Version: MPL 1.1/GPL 2.0/LGPL 2.1
+#
+# The contents of this file are subject to the Mozilla Public License Version
+# 1.1 (the "License"); you may not use this file except in compliance with
+# the License. You may obtain a copy of the License at
+# http://www.mozilla.org/MPL/
+#
+# Software distributed under the License is distributed on an "AS IS" basis,
+# WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+# for the specific language governing rights and limitations under the
+# License.
+#
+# The Original Code is the Extension Blocklist UI.
+#
+# The Initial Developer of the Original Code is
+# Mozilla Corporation
+# Portions created by the Initial Developer are Copyright (C) 2008
+# the Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+#   Dave Townsend <dtownsend@oxymoronical.com>
+#
+# Alternatively, the contents of this file may be used under the terms of
+# either the GNU General Public License Version 2 or later (the "GPL"), or
+# the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+# in which case the provisions of the GPL or the LGPL are applicable instead
+# of those above. If you wish to allow use of your version of this file only
+# under the terms of either the GPL or the LGPL, and not to allow others to
+# use your version of this file under the terms of the MPL, indicate your
+# decision by deleting the provisions above and replace them with the notice
+# 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 *****
+
+var gArgs;
+
+function init() {
+  var hasHardBlocks = false;
+  var hasSoftBlocks = false;
+  gArgs = window.arguments[0].wrappedJSObject;
+
+  var richlist = document.getElementById("addonList");
+  var list = gArgs.list;
+  list.sort(function(a, b) { return String.localeCompare(a.name, b.name); });
+  for (let i = 0; i < list.length; i++) {
+    let item = document.createElement("richlistitem");
+    item.setAttribute("name", list[i].name);
+    item.setAttribute("version", list[i].version);
+    item.setAttribute("icon", list[i].icon);
+    if (list[i].blocked) {
+      item.setAttribute("class", "hardBlockedAddon");
+      hasHardBlocks = true;
+    }
+    else {
+      item.setAttribute("class", "softBlockedAddon");
+      hasSoftBlocks = true;
+    }
+    richlist.appendChild(item);
+  }
+
+  if (hasHardBlocks && hasSoftBlocks)
+    document.getElementById("bothMessage").hidden = false;
+  else if (hasHardBlocks)
+    document.getElementById("hardBlockMessage").hidden = false;
+  else
+    document.getElementById("softBlockMessage").hidden = false;
+}
+
+function accept() {
+  gArgs.restart = true;
+  var list = gArgs.list;
+  var items = document.getElementById("addonList").childNodes;
+  for (let i = 0; i < list.length; i++) {
+    if (!list[i].blocked)
+      list[i].disable = items[i].checked;
+  }
+  return true;
+}
copy from toolkit/mozapps/extensions/content/list.xul
copy to toolkit/mozapps/extensions/content/blocklist.xul
--- a/toolkit/mozapps/extensions/content/list.xul
+++ b/toolkit/mozapps/extensions/content/blocklist.xul
@@ -18,66 +18,63 @@
 #
 # The Initial Developer of the Original Code is Google Inc.
 # Portions created by the Initial Developer are Copyright (C) 2005
 # the Initial Developer. All Rights Reserved.
 #
 # Contributor(s):
 #   Ben Goodger <ben@mozilla.org>
 #   Robert Strong <robert.bugzilla@gmail.com>
+#   Dave Townsend <dtownsend@oxymoronical.com>
 #
 # Alternatively, the contents of this file may be used under the terms of
 # either the GNU General Public License Version 2 or later (the "GPL"), or
 # the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 # in which case the provisions of the GPL or the LGPL are applicable instead
 # of those above. If you wish to allow use of your version of this file only
 # under the terms of either the GPL or the LGPL, and not to allow others to
 # use your version of this file under the terms of the MPL, indicate your
 # decision by deleting the provisions above and replace them with the notice
 # 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 *****
 
 <?xml-stylesheet href="chrome://global/skin/"?>
+<?xml-stylesheet href="chrome://mozapps/skin/extensions/blocklist.css"?>
+<?xml-stylesheet href="chrome://mozapps/content/extensions/blocklist.css"?>
 
-<dialog id="addonList" windowtype="Addons:List"
+<!DOCTYPE dialog [
+<!ENTITY % brandDTD SYSTEM "chrome://branding/locale/brand.dtd">
+%brandDTD;
+<!ENTITY % extensionsDTD SYSTEM "chrome://mozapps/locale/extensions/blocklist.dtd">
+%extensionsDTD;
+]>
+
+<dialog windowtype="Addons:Blocklist" title="&blocklist.title;" align="stretch"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
-        onunload="shutdown();"
-        buttons="accept,cancel" onload="init();">
+        onload="init();" ondialogaccept="return accept()"
+        buttons="accept,cancel" style="&blocklist.style;"
+        buttonlabelaccept="&blocklist.accept.label;"
+        buttonaccesskeyaccept="&blocklist.accept.accesskey;">
 
   <script type="application/javascript" src="chrome://global/content/globalOverlay.js"/>
-  <script type="application/javascript" 
-          src="chrome://mozapps/content/extensions/list.js"/>
-          
-  <stringbundle id="extensionsBundle" 
-                src="chrome://mozapps/locale/extensions/extensions.properties"/>
-  <stringbundle id="brandBundle"
-                src="chrome://branding/locale/brand.properties"/>
+  <script type="application/javascript" src="chrome://mozapps/content/extensions/blocklist.js"/>
 
-  <hbox align="start">
-    <vbox>
-      <image id="infoIcon"/>
+  <hbox align="stretch" flex="1">
+    <vbox pack="start">
+      <image class="error-icon"/>
     </vbox>
-    <vbox class="spaced" style="min-width: 20em;">
-      <label id="message1" class="spaced" hidden="true"/>
+    <vbox flex="1">
+      <label>&blocklist.summary;</label>
+      <separator class="thin"/>
+      <richlistbox id="addonList" flex="1"/>
       <separator class="thin"/>
-      <tree id="addonsTree" rows="6" hidecolumnpicker="true" hidden="true" class="spaced">
-        <treecols style="max-width: 25em;">
-          <treecol flex="1" id="nameColumn" hideheader="true"/>
-        </treecols>
-        <treechildren id="addonsChildren"/>
-      </tree>
-      <label id="message2" class="spaced" hidden="true"/>
-      <label class="bold spaced" id="message3" hidden="true"/>
-      <hbox id="moreInfoBox" hidden="true">
-        <label id="moreInfo" class="text-link spaced"/>
-        <spacer flex="1"/>
+      <description id="bothMessage" hidden="true" class="bold">&blocklist.softandhard;</description>
+      <description id="hardBlockMessage" hidden="true" class="bold">&blocklist.hardblocked;</description>
+      <description id="softBlockMessage" hidden="true" class="bold">&blocklist.softblocked;</description>
+      <hbox pack="start">
+        <label class="text-link" value="&blocklist.moreinfo;"/>
       </hbox>
     </vbox>
   </hbox>
-  <hbox id="buttonCenteredBox">
-    <spacer flex="1"/>
-    <button id="centeredButton"/>
-    <spacer flex="1"/>
-  </hbox>
 </dialog>
--- a/toolkit/mozapps/extensions/content/extensions.js
+++ b/toolkit/mozapps/extensions/content/extensions.js
@@ -293,16 +293,17 @@ function showView(aView) {
   // Using disabled to represent add-on state in regards to the EM causes evil
   // focus behavior when used as an element attribute when the element isn't
   // really disabled.
   var bindingList = [ [ ["aboutURL", "?aboutURL"],
                         ["addonID", "?addonID"],
                         ["availableUpdateURL", "?availableUpdateURL"],
                         ["availableUpdateVersion", "?availableUpdateVersion"],
                         ["blocklisted", "?blocklisted"],
+                        ["blocklistedsoft", "?blocklistedsoft"],
                         ["compatible", "?compatible"],
                         ["description", "?description"],
                         ["downloadURL", "?downloadURL"],
                         ["isDisabled", "?isDisabled"],
                         ["hidden", "?hidden"],
                         ["homepageURL", "?homepageURL"],
                         ["iconURL", "?iconURL"],
                         ["internalName", "?internalName"],
@@ -379,16 +380,17 @@ function showView(aView) {
       showInstallUpdatesAll = true;
       if (gUpdatesOnly)
         showSkip = true;
       bindingList = [ [ ["aboutURL", "?aboutURL"],
                         ["availableUpdateURL", "?availableUpdateURL"],
                         ["availableUpdateVersion", "?availableUpdateVersion"],
                         ["availableUpdateInfo", "?availableUpdateInfo"],
                         ["blocklisted", "?blocklisted"],
+                        ["blocklistedsoft", "?blocklistedsoft"],
                         ["homepageURL", "?homepageURL"],
                         ["iconURL", "?iconURL"],
                         ["internalName", "?internalName"],
                         ["locked", "?locked"],
                         ["name", "?name"],
                         ["opType", "?opType"],
                         ["previewImage", "?previewImage"],
                         ["satisfiesDependencies", "?satisfiesDependencies"],
@@ -405,16 +407,17 @@ function showView(aView) {
       showInstallFile = false;
       showCheckUpdatesAll = false;
       showInstallUpdatesAll = false;
       bindingList = [ [ ["aboutURL", "?aboutURL"],
                         ["addonID", "?addonID"],
                         ["availableUpdateURL", "?availableUpdateURL"],
                         ["availableUpdateVersion", "?availableUpdateVersion"],
                         ["blocklisted", "?blocklisted"],
+                        ["blocklistedsoft", "?blocklistedsoft"],
                         ["compatible", "?compatible"],
                         ["description", "?description"],
                         ["downloadURL", "?downloadURL"],
                         ["incompatibleUpdate", "?incompatibleUpdate"],
                         ["isDisabled", "?isDisabled"],
                         ["hidden", "?hidden"],
                         ["homepageURL", "?homepageURL"],
                         ["iconURL", "?iconURL"],
@@ -913,16 +916,18 @@ function rebuildPluginsDS()
                                homepageURL : homepageURL,
                                disabled    : plugin.disabled,
                                blocklisted : plugin.blocklisted,
                                plugins     : [] };
     }
     gPlugins[name][desc].plugins.push(plugin);
   }
 
+  var blocklist = Components.classes["@mozilla.org/extensions/blocklist;1"]
+                            .getService(Components.interfaces.nsIBlocklistService);
   for (var pluginName in gPlugins) {
     for (var pluginDesc in gPlugins[pluginName]) {
       plugin = gPlugins[pluginName][pluginDesc];
       var pluginNode = gRDF.GetResource(PREFIX_ITEM_URI + plugin.filename);
       rootctr.AppendElement(pluginNode);
       gPluginsDS.Assert(pluginNode,
                         gRDF.GetResource(PREFIX_NS_EM + "name"),
                         gRDF.GetLiteral(pluginName),
@@ -948,16 +953,22 @@ function rebuildPluginsDS()
                         gRDF.GetResource(PREFIX_NS_EM + "isDisabled"),
                         gRDF.GetLiteral((plugin.disabled ||
                                          plugin.blocklisted) ? "true" : "false"),
                         true);
       gPluginsDS.Assert(pluginNode,
                         gRDF.GetResource(PREFIX_NS_EM + "blocklisted"),
                         gRDF.GetLiteral(plugin.blocklisted ? "true" : "false"),
                         true);
+      var softblocked = blocklist.getPluginBlocklistState(plugin) == 
+                        Components.interfaces.nsIBlocklistService.STATE_SOFTBLOCKED;
+      gPluginsDS.Assert(pluginNode,
+                        gRDF.GetResource(PREFIX_NS_EM + "blocklistedsoft"),
+                        gRDF.GetLiteral(softblocked ? "true" : "false"),
+                        true);
       gPluginsDS.Assert(pluginNode,
                         gRDF.GetResource(PREFIX_NS_EM + "compatible"),
                         gRDF.GetLiteral("true"),
                         true);
       gPluginsDS.Assert(pluginNode,
                         gRDF.GetResource(PREFIX_NS_EM + "plugin"),
                         gRDF.GetLiteral("true"),
                         true);
--- a/toolkit/mozapps/extensions/content/extensions.xml
+++ b/toolkit/mozapps/extensions/content/extensions.xml
@@ -90,16 +90,17 @@
       <stylesheet src="chrome://mozapps/skin/extensions/extensions.css"/>
     </resources>
 
     <implementation>
       <field name="eventPrefix">"extension-"</field>
       <property name="type" onget="return parseInt(this.getAttribute('type'));"/>
       <property name="isCompatible" onget="return this.getAttribute('compatible') == 'true';"/>
       <property name="isBlocklisted" onget="return this.getAttribute('blocklisted') == 'true';"/>
+      <property name="isSoftBlocklisted" onget="return this.getAttribute('blocklistedsoft') == 'true';"/>
       <property name="isDisabled" onget="return this.getAttribute('isDisabled') == 'true';"/>
       <property name="providesUpdatesSecurely" onget="return this.getAttribute('providesUpdatesSecurely') == 'true';"/>
       <property name="satisfiesDependencies" onget="return this.getAttribute('satisfiesDependencies') == 'true';"/>
       <property name="opType">
         <getter>
           <![CDATA[
             var opType = this.getAttribute('opType');
             return opType == 'none' ? null : opType;
@@ -128,20 +129,46 @@
 
   <binding id="addon" extends="chrome://mozapps/content/extensions/extensions.xml#addon-base">
     <content>
       <xul:hbox flex="1">
         <xul:vbox class="addon-icon" xbl:inherits="iconURL"/>
         <xul:vbox flex="1" class="addonTextBox">
           <xul:hbox class="addon-name-version" xbl:inherits="name, version"/>
           <xul:hbox anonid="addonDescription" class="addon-description" xbl:inherits="description, opType"/>
+          <xul:vbox anonid="addonSelectedStatusMsgs" class="selectedStatusMsgs">
+            <xul:hbox flex="1" class="blocklistedBox attention" align="center">
+              <xul:label class="blocklistedLabel" value="&blocklisted.label;" crop="end"/>
+              <xul:label class="softBlocklistedLabel" value="&softBlocklisted.label;" crop="end"/>
+              <xul:label anonid="blocklistMoreInfo" class="text-link" value="&moreInfo.label;"
+                         onclick="if (event.button == 0) { openURL(this.getAttribute('moreInfoURL')); }" />
+            </xul:hbox>
+          </xul:vbox>
         </xul:vbox>
       </xul:hbox>
     </content>
     <implementation implements="nsIAccessibleProvider, nsIDOMXULSelectControlItemElement">
+      <constructor>
+        <![CDATA[
+          if (this.isBlocklisted || this.isSoftBlocklisted) {
+            try {
+              var blocklistMoreInfo = document.getAnonymousElementByAttribute(this, "anonid", "blocklistMoreInfo");
+              var prefs = Components.classes["@mozilla.org/preferences-service;1"]
+                                    .getService(Components.interfaces.nsIPrefBranch);
+              var formatter = Components.classes["@mozilla.org/toolkit/URLFormatterService;1"]
+                                        .getService(Components.interfaces.nsIURLFormatter);
+              var url = formatter.formatURLPref("extensions.blocklist.detailsURL");
+              blocklistMoreInfo.setAttribute("moreInfoURL", url);
+            } catch(e) {
+              blocklistMoreInfo.hidden = true;
+            }
+          }
+        ]]>
+      </constructor>
+
       <field name="_addonDescription">
         document.getAnonymousElementByAttribute(this, "anonid", "addonDescription");
       </field>
 
       <property name="label" readonly="true">
         <getter>
           <![CDATA[
             var labelPieces = [];
@@ -209,17 +236,18 @@
             </xul:hbox>
             <xul:hbox flex="1" class="insecureUpdateBox attention">
               <xul:label value="&insecureUpdate.label;" crop="end"/>
             </xul:hbox>
             <xul:hbox flex="1" class="needsDependenciesBox attention">
               <xul:label value="&needsDependencies.label;" crop="end"/>
             </xul:hbox>
             <xul:hbox flex="1" class="blocklistedBox attention" align="center">
-              <xul:label value="&blocklisted.label;" crop="end"/>
+              <xul:label class="blocklistedLabel" value="&blocklisted.label;" crop="end"/>
+              <xul:label class="softBlocklistedLabel" value="&softBlocklisted.label;" crop="end"/>
               <xul:label anonid="blocklistMoreInfo" class="text-link" value="&moreInfo.label;"
                          onclick="if (event.button == 0) { openURL(this.getAttribute('moreInfoURL')); }" />
             </xul:hbox>
           </xul:vbox>
           <xul:hbox anonid="selectedButtons" flex="1" class="selectedButtons">
             <xul:button class="uninstallHide optionsButton"
 #ifdef XP_WIN
               label="&cmd.options.label;" accesskey="&cmd.options.accesskey;"
@@ -254,17 +282,17 @@
           </xul:hbox>
         </xul:vbox>
       </xul:hbox>
     </content>
 
     <implementation implements="nsIAccessibleProvider, nsIDOMXULSelectControlItemElement">
       <constructor>
         <![CDATA[
-          if (this.isBlocklisted) {
+          if (this.isBlocklisted || this.isSoftBlocklisted) {
             try {
               var blocklistMoreInfo = document.getAnonymousElementByAttribute(this, "anonid", "blocklistMoreInfo");
               var prefs = Components.classes["@mozilla.org/preferences-service;1"]
                                     .getService(Components.interfaces.nsIPrefBranch);
               var formatter = Components.classes["@mozilla.org/toolkit/URLFormatterService;1"]
                                         .getService(Components.interfaces.nsIURLFormatter);
               var url = formatter.formatURLPref("extensions.blocklist.detailsURL");
               blocklistMoreInfo.setAttribute("moreInfoURL", url);
@@ -919,9 +947,45 @@
           <![CDATA[
             return Components.interfaces.nsIAccessibleProvider.XULListitem;
           ]]>
         </getter>
       </property>
     </implementation>
   </binding>
 
+  <binding id="hardblockedaddon">
+    <content align="start">
+      <xul:image xbl:inherits="src=icon"/>
+      <xul:vbox flex="1">
+        <xul:hbox class="addon-name-version" xbl:inherits="name, version"/>
+        <xul:hbox>
+          <xul:spacer flex="1"/>
+          <xul:label class="blockedLabel" value="&blocklist.blocked.label;"/>
+        </xul:hbox>
+      </xul:vbox>
+    </content>
+  </binding>
+
+  <binding id="softblockedaddon">
+    <content align="start">
+      <xul:image xbl:inherits="src=icon"/>
+      <xul:vbox flex="1">
+        <xul:hbox class="addon-name-version" xbl:inherits="name, version"/>
+        <xul:hbox>
+          <xul:spacer flex="1"/>
+          <xul:checkbox class="disableCheckbox" checked="true" label="&blocklist.checkbox.label;"/>
+        </xul:hbox>
+      </xul:vbox>
+    </content>
+    <implementation>
+      <field name="_checkbox">
+        document.getAnonymousElementByAttribute(this, "class", "disableCheckbox")
+      </field>
+      <property name="checked" readonly="true">
+        <getter>
+          return this._checkbox.checked;
+        </getter>
+      </property>
+    </implementation>
+  </binding>
+
 </bindings>
--- a/toolkit/mozapps/extensions/content/list.js
+++ b/toolkit/mozapps/extensions/content/list.js
@@ -76,79 +76,61 @@ const kDialog = "dialog";
  */
 
 var gButtons = { };
 
 function init() {
   var de = document.documentElement;
   var items = [];
   if (window.arguments[0] instanceof Components.interfaces.nsIDialogParamBlock) {
+    // This is a warning about a blocklisted item the user is trying to install
     var args = window.arguments[0];
-    var fromInstall = args.GetInt(0) == 1 ? true : false;
-    var numberOfItems = args.GetInt(1);
-    for (var i = 0; i < numberOfItems; ++i)
-      items.push(args.GetString(i));
+    var softblocked = args.GetInt(0) == 1 ? true : false;
 
     var extensionsBundle = document.getElementById("extensionsBundle");
     try {
       var formatter = Components.classes["@mozilla.org/toolkit/URLFormatterService;1"]
                                 .getService(Components.interfaces.nsIURLFormatter);
       var url = formatter.formatURLPref("extensions.blocklist.detailsURL");
     }
     catch (e) { }
 
-    if (fromInstall) { // Blocklist blocked install
-      var params = {
-        message1: extensionsBundle.getFormattedString("blocklistedInstallMsg",
-                                                      [items[0]]),
-        moreInfoURL: url,
-        title: extensionsBundle.getString("blocklistedInstallTitle")
-      };
-      items = [];
-      var button = document.getElementById("centeredButton");
-      button.setAttribute("dlgtype", "accept");
+    var params = {
+      moreInfoURL: url,
+    };
+
+    if (softblocked) {
+      params.title = extensionsBundle.getString("softBlockedInstallTitle");
+      params.message1 = extensionsBundle.getFormattedString("softBlockedInstallMsg",
+                                                           [args.GetString(0)]);
+      var accept = de.getButton("accept");
+      accept.label = extensionsBundle.getString("softBlockedInstallAcceptLabel");
+      accept.accessKey = extensionsBundle.getString("softBlockedInstallAcceptKey");
+      de.getButton("cancel").focus();
+      document.addEventListener("dialogaccept", allowInstall, false);
+    }
+    else {
+      params.title = extensionsBundle.getString("blocklistedInstallTitle2");
+      params.message1 = extensionsBundle.getFormattedString("blocklistedInstallMsg2",
+                                                           [args.GetString(0)]);
       de.buttons = "accept";
       de.getButton("accept").focus();
     }
-    else { // Blocklist background notification
-      // only hide when not used due to focus issues
-      document.getElementById("buttonCenteredBox").hidden = true;
-      var brandBundle = document.getElementById("brandBundle");
-      var brandShortName = brandBundle.getString("brandShortName");
-      params = {
-        message1: extensionsBundle.getFormattedString("blocklistNotifyMsg2",
-                                                      [brandShortName]),
-        message2: extensionsBundle.getFormattedString("blocklistRestartMsg2",
-                                                      [brandShortName]),
-        moreInfoURL: url,
-        title: extensionsBundle.getString("blocklistNotifyTitle2")
-      };
-      de.buttons = "extra1,cancel";
-      button = de.getButton("cancel");
-      button.label = extensionsBundle.getString("laterButton");
-      de.setAttribute("ondialogextra1", "restartApp();");
-      button.focus();
-      button = de.getButton("extra1");
-      button.label = extensionsBundle.getFormattedString("restartButton",
-                                                         [brandShortName]);
-    }
   }
   else {
-    // only hide when not used due to focus issues
-    document.getElementById("buttonCenteredBox").hidden = true;
     items = window.arguments[0];
     params = window.arguments[1];
   }
 
   var addons = document.getElementById("addonsChildren");
   if (items.length > 0)
     document.getElementById("addonsTree").hidden = false;
 
   // Fill the addons list
-  for (i = 0; i < items.length; ++i) {
+  for (var i = 0; i < items.length; ++i) {
     var treeitem = document.createElementNS(kXULNS, "treeitem");
     var treerow  = document.createElementNS(kXULNS, "treerow");
     var treecell = document.createElementNS(kXULNS, "treecell");
     treecell.setAttribute("label", items[i]);
     treerow.appendChild(treecell);
     treeitem.appendChild(treerow);
     addons.appendChild(treeitem);
   }
@@ -180,45 +162,33 @@ function init() {
   // Set up the buttons
   if ("buttons" in params) {
     gButtons = params.buttons;
     var buttonString = "";
     for (var buttonType in gButtons)
       buttonString += "," + buttonType;
     de.buttons = buttonString.substr(1);
     for (buttonType in gButtons) {
-      button = de.getButton(buttonType);
+      var button = de.getButton(buttonType);
       button.label = gButtons[buttonType].label;
       if (gButtons[buttonType].focused)
         button.focus();
       document.addEventListener(kDialog + buttonType, handleButtonCommand, true);
     }
   }
 }
 
 function shutdown() {
-  for (buttonType in gButtons)
+  for (var buttonType in gButtons)
     document.removeEventListener(kDialog + buttonType, handleButtonCommand, true);
 }
 
-function restartApp() {
-  const nsIAppStartup = Components.interfaces.nsIAppStartup;
-  // Notify all windows that an application quit has been requested.
-  var os = Components.classes["@mozilla.org/observer-service;1"]
-                     .getService(Components.interfaces.nsIObserverService);
-  var cancelQuit = Components.classes["@mozilla.org/supports-PRBool;1"]
-                             .createInstance(Components.interfaces.nsISupportsPRBool);
-  os.notifyObservers(cancelQuit, "quit-application-requested", null);
-
-  // Something aborted the quit process. 
-  if (cancelQuit.data)
-    return;
-
-  Components.classes["@mozilla.org/toolkit/app-startup;1"].getService(nsIAppStartup)
-            .quit(nsIAppStartup.eRestart | nsIAppStartup.eAttemptQuit);
+function allowInstall() {
+  var args = window.arguments[0];
+  args.SetInt(1, 1);
 }
 
 /**
  * Watch for the user hitting one of the buttons to dismiss the dialog
  * and report the result back to the caller through the |result| property on
  * the arguments object.
  */
 function handleButtonCommand(event) {
--- a/toolkit/mozapps/extensions/content/list.xul
+++ b/toolkit/mozapps/extensions/content/list.xul
@@ -53,31 +53,26 @@
                 src="chrome://mozapps/locale/extensions/extensions.properties"/>
   <stringbundle id="brandBundle"
                 src="chrome://branding/locale/brand.properties"/>
 
   <hbox align="start">
     <vbox>
       <image id="infoIcon"/>
     </vbox>
-    <vbox class="spaced" style="min-width: 20em;">
+    <vbox class="spaced" style="min-width: 20em; max-width: 40em">
       <label id="message1" class="spaced" hidden="true"/>
       <separator class="thin"/>
       <tree id="addonsTree" rows="6" hidecolumnpicker="true" hidden="true" class="spaced">
         <treecols style="max-width: 25em;">
           <treecol flex="1" id="nameColumn" hideheader="true"/>
         </treecols>
         <treechildren id="addonsChildren"/>
       </tree>
       <label id="message2" class="spaced" hidden="true"/>
       <label class="bold spaced" id="message3" hidden="true"/>
       <hbox id="moreInfoBox" hidden="true">
         <label id="moreInfo" class="text-link spaced"/>
         <spacer flex="1"/>
       </hbox>
     </vbox>
   </hbox>
-  <hbox id="buttonCenteredBox">
-    <spacer flex="1"/>
-    <button id="centeredButton"/>
-    <spacer flex="1"/>
-  </hbox>
 </dialog>
--- a/toolkit/mozapps/extensions/jar.mn
+++ b/toolkit/mozapps/extensions/jar.mn
@@ -4,13 +4,16 @@ toolkit.jar:
 * content/mozapps/extensions/extensions.js                      (content/extensions.js)
 * content/mozapps/extensions/extensions.xml                     (content/extensions.xml)
   content/mozapps/extensions/updateinfo.xsl                     (content/updateinfo.xsl)
   content/mozapps/extensions/extensions.css                     (content/extensions.css)
 * content/mozapps/extensions/about.xul                          (content/about.xul)
 * content/mozapps/extensions/about.js                           (content/about.js)
 * content/mozapps/extensions/list.xul                           (content/list.xul)
 * content/mozapps/extensions/list.js                            (content/list.js)
+* content/mozapps/extensions/blocklist.xul                      (content/blocklist.xul)
+* content/mozapps/extensions/blocklist.js                       (content/blocklist.js)
+* content/mozapps/extensions/blocklist.css                      (content/blocklist.css)
 * content/mozapps/extensions/errors.xul                         (content/errors.xul)
 * content/mozapps/extensions/update.xul                         (content/update.xul)
 * content/mozapps/extensions/update.js                          (content/update.js)
 * content/mozapps/extensions/eula.xul                           (content/eula.xul)
 * content/mozapps/extensions/eula.js                            (content/eula.js)
--- a/toolkit/mozapps/extensions/public/Makefile.in
+++ b/toolkit/mozapps/extensions/public/Makefile.in
@@ -41,14 +41,13 @@ VPATH   = @srcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
 MODULE        = extensions
 XPIDL_MODULE  = extensions
 
 XPIDLSRCS = \
   nsIExtensionManager.idl \
-  nsIBlocklistService.idl \
   nsIAddonRepository.idl \
   $(NULL)
 
 include $(topsrcdir)/config/rules.mk
 
--- a/toolkit/mozapps/extensions/public/nsIExtensionManager.idl
+++ b/toolkit/mozapps/extensions/public/nsIExtensionManager.idl
@@ -194,17 +194,17 @@ interface nsIInstallLocation : nsISuppor
 
 /**
  * Interface representing a system for the installation and management of
  * Extensions, Themes etc.
  *
  * XXXben - Some of this stuff should go into a management-ey interface,
  *          some into an app-startup-ey interface.
  */
-[scriptable, uuid(23080b61-dab9-48b7-bd68-b5ea23cae14b)]
+[scriptable, uuid(7fbb049a-00e6-4ce2-82fc-854c52788df9)]
 interface nsIExtensionManager : nsISupports
 {
   /**
    * Constants representing types of update checks.
    */
   const unsigned long UPDATE_CHECK_NEWVERSION    = 0;
   const unsigned long UPDATE_CHECK_COMPATIBILITY = 1;
   const unsigned long UPDATE_SYNC_COMPATIBILITY  = 2;
@@ -465,25 +465,30 @@ interface nsIExtensionManager : nsISuppo
    *          specified by the id parameter.
    */
   void getDependentItemListForID(in AString id,
                                  in boolean includeDisabled,
                                  out unsigned long itemCount,
                                  [retval, array, size_is(itemCount)] out nsIUpdateItem items);
 
   /**
-   * Checks for changes to the blocklist using the local blocklist file,
-   * application disables / enables items that have been added / removed from
-   * the blocklist, and if there are additions to the blocklist this will
-   * inform the user by displaying a list of the items added.
+   * Checks for changes to the blocklist using the local blocklist file.
+   * This will immediately application disable items that have been hard blocked
+   * and application enable items that are no longer hard blocked. It will also
+   * return items that are either soft or hard blocked and aren't already
+   * disabled or disabled pending a restart.
    *
-   * XXXrstrong - this method is not terribly useful and was added so we can
-   * trigger this check from the additional timer used by blocklisting.
+   * This is likely to change or go away in the future and should not be used
+   * by anyone outside of the blocklist service.
+   *
+   * @returns An array of nsIUpdateItems that are blocklisted or the user should
+   *          be warned about but are currently enabled.
    */
-  void checkForBlocklistChanges();
+  void updateAndGetNewBlocklistedItems(out unsigned long itemCount,
+                                       [retval, array, size_is(itemCount)] out nsIUpdateItem items);
 
   /**
    * Sorts addons of the specified type by the specified property in the
    * Extensions Datasource container starting from the top of their container.
    * If the addons are already sorted then no action is performed.
    * @param   type
    *          The nsIUpdateItem type of the items to sort.
    * @param   propertyName
--- a/toolkit/mozapps/extensions/src/nsBlocklistService.js
+++ b/toolkit/mozapps/extensions/src/nsBlocklistService.js
@@ -48,25 +48,30 @@ Components.utils.import("resource://gre/
 
 const TOOLKIT_ID                      = "toolkit@mozilla.org"
 const KEY_PROFILEDIR                  = "ProfD";
 const KEY_APPDIR                      = "XCurProcD";
 const FILE_BLOCKLIST                  = "blocklist.xml";
 const PREF_BLOCKLIST_URL              = "extensions.blocklist.url";
 const PREF_BLOCKLIST_ENABLED          = "extensions.blocklist.enabled";
 const PREF_BLOCKLIST_INTERVAL         = "extensions.blocklist.interval";
+const PREF_BLOCKLIST_LEVEL            = "extensions.blocklist.level";
 const PREF_GENERAL_USERAGENT_LOCALE   = "general.useragent.locale";
 const PREF_PARTNER_BRANCH             = "app.partner.";
 const PREF_APP_DISTRIBUTION           = "distribution.id";
 const PREF_APP_DISTRIBUTION_VERSION   = "distribution.version";
 const PREF_APP_UPDATE_CHANNEL         = "app.update.channel";
 const PREF_EM_LOGGING_ENABLED         = "extensions.logging.enabled";
 const XMLURI_BLOCKLIST                = "http://www.mozilla.org/2006/addons-blocklist";
 const XMLURI_PARSE_ERROR              = "http://www.mozilla.org/newlayout/xml/parsererror.xml"
 const UNKNOWN_XPCOM_ABI               = "unknownABI";
+const URI_BLOCKLIST_DIALOG            = "chrome://mozapps/content/extensions/blocklist.xul"
+const DEFAULT_SEVERITY                = 3;
+const DEFAULT_LEVEL                   = 2;
+const MAX_BLOCK_LEVEL                 = 3;
 
 const MODE_RDONLY   = 0x01;
 const MODE_WRONLY   = 0x02;
 const MODE_CREATE   = 0x08;
 const MODE_APPEND   = 0x10;
 const MODE_TRUNCATE = 0x20;
 
 const PERMS_FILE      = 0644;
@@ -75,16 +80,18 @@ const PERMS_DIRECTORY = 0755;
 var gApp = null;
 var gPref = null;
 var gOS = null;
 var gConsole = null;
 var gVersionChecker = null;
 var gLoggingEnabled = null;
 var gABI = null;
 var gOSVersion = null;
+var gBlocklistEnabled = true;
+var gBlocklistLevel = DEFAULT_LEVEL;
 
 // shared code for suppressing bad cert dialogs
 #include ../../shared/src/badCertHandler.js
 
 /**
  * Logs a string to the error console.
  * @param   string
  *          The string to write to the error console..
@@ -182,16 +189,34 @@ function closeSafeFileOutputStream(strea
  * @returns The nsIURI constructed.
  */
 function newURI(spec) {
   var ioServ = Cc["@mozilla.org/network/io-service;1"].
                getService(Ci.nsIIOService);
   return ioServ.newURI(spec, null, null);
 }
 
+// Restarts the application checking in with observers first
+function restartApp() {
+  // Notify all windows that an application quit has been requested.
+  var os = Cc["@mozilla.org/observer-service;1"].
+           getService(Ci.nsIObserverService);
+  var cancelQuit = Cc["@mozilla.org/supports-PRBool;1"].
+                   createInstance(Ci.nsISupportsPRBool);
+  os.notifyObservers(cancelQuit, "quit-application-requested", null);
+
+  // Something aborted the quit process. 
+  if (cancelQuit.data)
+    return;
+
+  var as = Cc["@mozilla.org/toolkit/app-startup;1"].
+           getService(Ci.nsIAppStartup);
+  as.quit(Ci.nsIAppStartup.eRestart | Ci.nsIAppStartup.eAttemptQuit);
+}
+
 /**
  * Checks whether this blocklist element is valid for the current OS and ABI.
  * If the element has an "os" attribute then the current OS must appear in
  * its comma separated list for the element to be valid. Similarly for the
  * xpcomabi attribute.
  */
 function matchesOSABI(blocklistElement) {
   if (blocklistElement.hasAttribute("os")) {
@@ -212,38 +237,34 @@ function matchesOSABI(blocklistElement) 
 /**
  * Gets the current value of the locale.  It's possible for this preference to
  * be localized, so we have to do a little extra work here.  Similar code
  * exists in nsHttpHandler.cpp when building the UA string.
  */
 function getLocale() {
   try {
       // Get the default branch
-      var prefs = Components.classes["@mozilla.org/preferences-service;1"]
-          .getService(Components.interfaces.nsIPrefService);
-      var defaultPrefs = prefs.getDefaultBranch(null);
+      var defaultPrefs = gPref.getDefaultBranch(null);
       return defaultPrefs.getCharPref(PREF_GENERAL_USERAGENT_LOCALE);
   } catch (e) {}
 
   return gPref.getCharPref(PREF_GENERAL_USERAGENT_LOCALE);
 }
 
 /**
  * Read the update channel from defaults only.  We do this to ensure that
  * the channel is tightly coupled with the application and does not apply
  * to other installations of the application that may use the same profile.
  */
 function getUpdateChannel() {
   var channel = "default";
   var prefName;
   var prefValue;
 
-  var defaults =
-      gPref.QueryInterface(Components.interfaces.nsIPrefService).
-      getDefaultBranch(null);
+  var defaults = gPref.getDefaultBranch(null);
   try {
     channel = defaults.getCharPref(PREF_APP_UPDATE_CHANNEL);
   } catch (e) {
     // use default when pref not found
   }
 
   try {
     var partners = gPref.getChildList(PREF_PARTNER_BRANCH, { });
@@ -263,19 +284,17 @@ function getUpdateChannel() {
 
   return channel;
 }
 
 /* Get the distribution pref values, from defaults only */
 function getDistributionPrefValue(aPrefName) {
   var prefValue = "default";
 
-  var defaults =
-      gPref.QueryInterface(Components.interfaces.nsIPrefService).
-      getDefaultBranch(null);
+  var defaults = gPref.getDefaultBranch(null);
   try {
     prefValue = defaults.getCharPref(aPrefName);
   } catch (e) {
     // use default when pref not found
   }
 
   return prefValue;
 }
@@ -286,17 +305,18 @@ function getDistributionPrefValue(aPrefN
  * items managed by the Extension Manager with an item's appDisabled property.
  * It also blocklists plugins with data from blocklist.xml.
  */
 
 function Blocklist() {
   gApp = Cc["@mozilla.org/xre/app-info;1"].getService(Ci.nsIXULAppInfo);
   gApp.QueryInterface(Ci.nsIXULRuntime);
   gPref = Cc["@mozilla.org/preferences-service;1"].
-          getService(Ci.nsIPrefBranch2);
+          getService(Ci.nsIPrefService).
+          QueryInterface(Ci.nsIPrefBranch2);
   gVersionChecker = Cc["@mozilla.org/xpcom/version-comparator;1"].
                     getService(Ci.nsIVersionComparator);
   gConsole = Cc["@mozilla.org/consoleservice;1"].
              getService(Ci.nsIConsoleService);
 
   gOS = Cc["@mozilla.org/observer-service;1"].
         getService(Ci.nsIObserverService);
   gOS.addObserver(this, "xpcom-shutdown", false);
@@ -358,67 +378,116 @@ Blocklist.prototype = {
    *                                 (default = *)
    */
   _addonEntries: null,
   _pluginEntries: null,
 
   observe: function (aSubject, aTopic, aData) {
     switch (aTopic) {
     case "app-startup":
-      gOS.addObserver(this, "plugins-list-updated", false);
       gOS.addObserver(this, "profile-after-change", false);
       gOS.addObserver(this, "quit-application", false);
       break;
     case "profile-after-change":
       gLoggingEnabled = getPref("getBoolPref", PREF_EM_LOGGING_ENABLED, false);
+      gBlocklistEnabled = getPref("getBoolPref", PREF_BLOCKLIST_ENABLED, true);
+      gBlocklistLevel = Math.min(getPref("getIntPref", PREF_BLOCKLIST_LEVEL, DEFAULT_LEVEL),
+                                 MAX_BLOCK_LEVEL);
+      gPref.addObserver("extensions.blocklist.", this, false);
       var tm = Cc["@mozilla.org/updates/timer-manager;1"].
                getService(Ci.nsIUpdateTimerManager);
       var interval = getPref("getIntPref", PREF_BLOCKLIST_INTERVAL, 86400);
       tm.registerTimer("blocklist-background-update-timer", this, interval);
       break;
-    case "plugins-list-updated":
-      this._checkPluginsList();
-      break;
     case "quit-application":
-      gOS.removeObserver(this, "plugins-list-updated");
       gOS.removeObserver(this, "profile-after-change");
       gOS.removeObserver(this, "quit-application");
+      gPref.removeObserver("extensions.blocklist.", this);
       break;
     case "xpcom-shutdown":
       gOS.removeObserver(this, "xpcom-shutdown");
       gOS = null;
       gPref = null;
       gConsole = null;
       gVersionChecker = null;
       gApp = null;
       break;
+    case "nsPref:changed":
+      switch (aData) {
+        case PREF_BLOCKLIST_ENABLED:
+          gBlocklistEnabled = getPref("getBoolPref", PREF_BLOCKLIST_ENABLED, true);
+          this._loadBlocklist();
+          this._blocklistUpdated(null, null);
+          break;
+        case PREF_BLOCKLIST_LEVEL:
+          gBlocklistLevel = Math.min(getPref("getIntPref", PREF_BLOCKLIST_LEVEL, DEFAULT_LEVEL),
+                                     MAX_BLOCK_LEVEL);
+          this._blocklistUpdated(null, null);
+          break;
+      }
+      break;
     }
   },
 
+  /* See nsIBlocklistService */
   isAddonBlocklisted: function(id, version, appVersion, toolkitVersion) {
+    return this.getAddonBlocklistState(id, version, appVersion, toolkitVersion) ==
+                   Ci.nsIBlocklistService.STATE_BLOCKED;
+  },
+
+  /* See nsIBlocklistService */
+  getAddonBlocklistState: function(id, version, appVersion, toolkitVersion) {
     if (!this._addonEntries)
       this._loadBlocklist();
+    return this._getAddonBlocklistState(id, version, 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
+   *          The ID of the item to get the blocklist state for.
+   * @param   version
+   *          The version of the item to get the blocklist state for.
+   * @param   addonEntries
+   *          The add-on blocklist entries to compare against.
+   * @param   appVersion
+   *          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(id, version, addonEntries, appVersion, toolkitVersion) {
+    if (!gBlocklistEnabled)
+      return Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
+
     if (!appVersion)
       appVersion = gApp.version;
     if (!toolkitVersion)
       toolkitVersion = gApp.platformVersion;
 
-    var blItem = this._addonEntries[id];
+    var blItem = addonEntries[id];
     if (!blItem)
-      return false;
+      return Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
 
     for (var i = 0; i < blItem.length; ++i) {
       if (blItem[i].includesItem(version, appVersion, toolkitVersion))
-        return true;
+        return blItem[i].severity >= gBlocklistLevel ? Ci.nsIBlocklistService.STATE_BLOCKED :
+                                                       Ci.nsIBlocklistService.STATE_SOFTBLOCKED;
     }
-    return false;
+    return Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
   },
 
   notify: function(aTimer) {
-    if (getPref("getBoolPref", PREF_BLOCKLIST_ENABLED, true) == false)
+    if (!gBlocklistEnabled)
       return;
 
     try {
       var dsURI = gPref.getCharPref(PREF_BLOCKLIST_URL);
     }
     catch (e) {
       LOG("Blocklist::notify: The " + PREF_BLOCKLIST_URL + " preference" +
           " is missing!");
@@ -458,16 +527,21 @@ Blocklist.prototype = {
     request.overrideMimeType("text/xml");
     request.setRequestHeader("Cache-Control", "no-cache");
     request.QueryInterface(Components.interfaces.nsIJSXMLHttpRequest);
 
     var self = this;
     request.onerror = function(event) { self.onXMLError(event); };
     request.onload  = function(event) { self.onXMLLoad(event);  };
     request.send(null);
+
+    // When the blocklist loads we need to compare it to the current copy so
+    // make sure we have loaded it.
+    if (!this._addonEntries)
+      this._loadBlocklist();
   },
 
   onXMLLoad: function(aEvent) {
     var request = aEvent.target;
     try {
       checkCert(request.channel);
     }
     catch (e) {
@@ -481,21 +555,24 @@ Blocklist.prototype = {
       return;
     }
     var blocklistFile = getFile(KEY_PROFILEDIR, [FILE_BLOCKLIST]);
     if (blocklistFile.exists())
       blocklistFile.remove(false);
     var fos = openSafeFileOutputStream(blocklistFile);
     fos.write(request.responseText, request.responseText.length);
     closeSafeFileOutputStream(fos);
+
+    var oldAddonEntries = this._addonEntries;
+    var oldPluginEntries = this._pluginEntries;
+    this._addonEntries = { };
+    this._pluginEntries = { };
     this._loadBlocklistFromFile(getFile(KEY_PROFILEDIR, [FILE_BLOCKLIST]));
-    var em = Cc["@mozilla.org/extensions/manager;1"].
-             getService(Ci.nsIExtensionManager);
-    em.checkForBlocklistChanges();
-    this._checkPluginsList();
+
+    this._blocklistUpdated(oldAddonEntries, oldPluginEntries);
   },
 
   onXMLError: function(aEvent) {
     try {
       var request = aEvent.target;
       // the following may throw (e.g. a local file or timeout)
       var status = request.status;
     }
@@ -579,17 +656,17 @@ Blocklist.prototype = {
 #          <match name="name" exp="some plugin"/>
 #          <match name="description" exp="1[.]2[.]3"/>
 #        </pluginItem>
 #      </pluginItems>
 #    </blocklist>
    */
 
   _loadBlocklistFromFile: function(file) {
-    if (getPref("getBoolPref", PREF_BLOCKLIST_ENABLED, true) == false) {
+    if (!gBlocklistEnabled) {
       LOG("Blocklist::_loadBlocklistFromFile: blocklist is disabled");
       return;
     }
 
     if (!file.exists()) {
       LOG("Blocklist::_loadBlocklistFromFile: XML File does not exist");
       return;
     }
@@ -685,58 +762,168 @@ Blocklist.prototype = {
       if (matchElement.localName == "match") {
         var name = matchElement.getAttribute("name");
         var exp = matchElement.getAttribute("exp");
         blockEntry.matches[name] = new RegExp(exp, "m");
       }
       if (matchElement.localName == "versionRange")
         blockEntry.versions.push(new BlocklistItemData(matchElement));
     }
+    // Add a default versionRange if there wasn't one specified
+    if (blockEntry.versions.length == 0)
+      blockEntry.versions.push(new BlocklistItemData(null));
     result.push(blockEntry);
   },
 
-  _isPluginBlocklisted: function(plugin, appVersion, toolkitVersion) {
-    for each (var blockEntry in this._pluginEntries) {
+  /* See nsIBlocklistService */
+  getPluginBlocklistState: function(plugin, appVersion, toolkitVersion) {
+    if (!this._pluginEntries)
+      this._loadBlocklist();
+    return this._getPluginBlocklistState(plugin, this._pluginEntries,
+                                         appVersion, toolkitVersion);
+  },
+
+  /**
+   * Private version of getPluginBlocklistState that allows the caller to pass in
+   * the plugin blocklist entries.
+   *
+   * @param   plugin
+   *          The nsIPluginTag to get the blocklist state for.
+   * @param   pluginEntries
+   *          The plugin blocklist entries to compare against.
+   * @param   appVersion
+   *          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.
+   */
+  _getPluginBlocklistState: function(plugin, pluginEntries, appVersion, toolkitVersion) {
+    if (!gBlocklistEnabled)
+      return Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
+
+    if (!appVersion)
+      appVersion = gApp.version;
+    if (!toolkitVersion)
+      toolkitVersion = gApp.platformVersion;
+
+    for each (var blockEntry in pluginEntries) {
       var matchFailed = false;
       for (var name in blockEntry.matches) {
         if (!(name in plugin) ||
             typeof(plugin[name]) != "string" ||
             !blockEntry.matches[name].test(plugin[name])) {
           matchFailed = true;
           break;
         }
       }
 
       if (matchFailed)
         continue;
 
-      // No version ranges means match any versions
-      if (blockEntry.versions.length == 0)
-        return true;
-
       for (var i = 0; i < blockEntry.versions.length; i++) {
         if (blockEntry.versions[i].includesItem(plugin.version, appVersion,
                                                 toolkitVersion))
-          return true;
+          return blockEntry.versions[i].severity >= gBlocklistLevel ?
+                                                    Ci.nsIBlocklistService.STATE_BLOCKED :
+                                                    Ci.nsIBlocklistService.STATE_SOFTBLOCKED;
       }
     }
 
-    return false;
+    return Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
   },
 
-  _checkPluginsList: function() {
-    if (!this._addonEntries)
-      this._loadBlocklist();
+  _blocklistUpdated: function(oldAddonEntries, oldPluginEntries) {
+    var addonList = [];
+
+    var em = Cc["@mozilla.org/extensions/manager;1"].
+             getService(Ci.nsIExtensionManager);
+    var addons = em.updateAndGetNewBlocklistedItems({});
+
+    for (let i = 0; i < addons.length; i++) {
+      let oldState = -1;
+      if (oldAddonEntries)
+        oldState = this._getAddonBlocklistState(addons[i].id, addons[i].version,
+                                                oldAddonEntries);
+      let state = this.getAddonBlocklistState(addons[i].id, addons[i].version);
+      // We don't want to re-warn about items
+      if (state == oldState)
+        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]
+      });
+    }
+
     var phs = Cc["@mozilla.org/plugin/host;1"].
               getService(Ci.nsIPluginHost);
     var plugins = phs.getPluginTags({});
-    for (var i = 0; i < plugins.length; i++)
-      plugins[i].blocklisted = this._isPluginBlocklisted(plugins[i],
-                                                         gApp.version,
-                                                         gApp.platformVersion);
+
+    for (let i = 0; i < plugins.length; i++) {
+      let oldState = -1;
+      if (oldPluginEntries)
+        oldState = this._getPluginBlocklistState(plugins[i], oldPluginEntries);
+      let state = this.getPluginBlocklistState(plugins[i]);
+      // We don't want to re-warn about items
+      if (state == oldState)
+        continue;
+
+      if (plugins[i].blocklisted) {
+        if (state == Ci.nsIBlocklistService.STATE_SOFTBLOCKED)
+          plugins[i].disabled = true;
+      }
+      else if (!plugins[i].disabled && state != Ci.nsIBlocklistService.STATE_NOT_BLOCKED) {
+        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]
+        });
+      }
+      plugins[i].blocklisted = state == Ci.nsIBlocklistService.STATE_BLOCKED;
+    }
+
+    if (addonList.length == 0)
+      return;
+
+    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);
+
+    for (let i = 0; i < addonList.length; i++) {
+      if (!addonList[i].disable)
+        continue;
+
+      if (addonList[i].item instanceof Ci.nsIUpdateItem)
+        em.disableItem(addonList[i].item.id);
+      else if (addonList[i].item instanceof Ci.nsIPluginTag)
+        addonList[i].item.disabled = true;
+      else
+        LOG("Unknown add-on type: " + addonList[i].item);
+    }
+
+    if (args.restart)
+      restartApp();
   },
 
   classDescription: "Blocklist Service",
   contractID: "@mozilla.org/extensions/blocklist;1",
   classID: Components.ID("{66354bc9-7ed1-4692-ae1d-8da97d6b205e}"),
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIObserver,
                                          Ci.nsIBlocklistService,
                                          Ci.nsITimerCallback]),
@@ -748,16 +935,20 @@ Blocklist.prototype = {
 
 /**
  * Helper for constructing a blocklist.
  */
 function BlocklistItemData(versionRangeElement) {
   var versionRange = this.getBlocklistVersionRange(versionRangeElement);
   this.minVersion = versionRange.minVersion;
   this.maxVersion = versionRange.maxVersion;
+  if (versionRangeElement && versionRangeElement.hasAttribute("severity"))
+    this.severity = versionRangeElement.getAttribute("severity");
+  else
+    this.severity = DEFAULT_SEVERITY;
   this.targetApps = { };
   var found = false;
 
   if (versionRangeElement) {
     for (var i = 0; i < versionRangeElement.childNodes.length; ++i) {
       var targetAppElement = versionRangeElement.childNodes.item(i);
       if (!(targetAppElement instanceof Ci.nsIDOMElement) ||
           targetAppElement.localName != "targetApplication")
--- a/toolkit/mozapps/extensions/src/nsExtensionManager.js.in
+++ b/toolkit/mozapps/extensions/src/nsExtensionManager.js.in
@@ -147,16 +147,17 @@ const INSTALLERROR_INVALID_VERSION      
 const INSTALLERROR_INVALID_GUID          = -2;
 const INSTALLERROR_INCOMPATIBLE_VERSION  = -3;
 const INSTALLERROR_PHONED_HOME           = -4;
 const INSTALLERROR_INCOMPATIBLE_PLATFORM = -5;
 const INSTALLERROR_BLOCKLISTED           = -6;
 const INSTALLERROR_INSECURE_UPDATE       = -7;
 const INSTALLERROR_INVALID_MANIFEST      = -8;
 const INSTALLERROR_RESTRICTED            = -9;
+const INSTALLERROR_SOFTBLOCKED           = -10;
 
 const MODE_RDONLY   = 0x01;
 const MODE_WRONLY   = 0x02;
 const MODE_CREATE   = 0x08;
 const MODE_APPEND   = 0x10;
 const MODE_TRUNCATE = 0x20;
 
 const PERMS_FILE      = 0644;
@@ -764,43 +765,40 @@ function showMessage(titleKey, titlePara
   else
     message = extensionStrings.GetStringFromName(messageKey);
   var ps = Cc["@mozilla.org/embedcomp/prompt-service;1"].
            getService(Ci.nsIPromptService);
   ps.alert(null, title, message);
 }
 
 /**
- * Shows a dialog for blocklisted items.
- * @param   items
- *          An array of nsIUpdateItems.
- * @param   fromInstall
- *          Whether this is called from an install or from the blocklist
- *          background check.
+ * Shows a dialog for a blocklisted item. For soft blocked items this will
+ * return true if the item should still be installed
+ * @param   item
+ *          The nsIUpdateItem that is blocklisted
+ * @param   softblocked
+ *          True if this item is only soft blocked and may still be installed.
  */
-function showBlocklistMessage(items, fromInstall) {
-  var win = null;
+function showBlocklistMessage(item, softblocked) {
   var params = Cc["@mozilla.org/embedcomp/dialogparam;1"].
                createInstance(Ci.nsIDialogParamBlock);
-  params.SetInt(0, (fromInstall ? 1 : 0));
-  params.SetInt(1, items.length);
-  params.SetNumberStrings(items.length * 2);
-  for (var i = 0; i < items.length; ++i)
-    params.SetString(i, items[i].name + " " + items[i].version);
-
-  // if this was initiated from an install try to find the appropriate manager
-  if (fromInstall) {
-    var wm = Cc["@mozilla.org/appshell/window-mediator;1"].
-             getService(Ci.nsIWindowMediator);
-    win = wm.getMostRecentWindow("Extension:Manager");
-  }
+  params.SetInt(0, softblocked ? 1 : 0);
+  params.SetInt(1, 0);
+  params.SetNumberStrings(1);
+  params.SetString(0, item.name + " " + item.version);
+
+  var wm = Cc["@mozilla.org/appshell/window-mediator;1"].
+           getService(Ci.nsIWindowMediator);
+  var win = wm.getMostRecentWindow("Extension:Manager");
   var ww = Cc["@mozilla.org/embedcomp/window-watcher;1"].
            getService(Ci.nsIWindowWatcher);
   ww.openWindow(win, URI_EXTENSION_LIST_DIALOG, "",
                 "chrome,centerscreen,modal,dialog,titlebar", params);
+
+  return params.GetInt(1) == 0 ? false : true;
 }
 
 /**
  * Gets a zip reader for the file specified.
  * @param   zipFile
  *          A ZIP archive to open with a nsIZipReader.
  * @return  A nsIZipReader for the file specified.
  */
@@ -2974,16 +2972,21 @@ ExtensionManager.prototype = {
         callback(installManifest, installData.id, location, installData.type);
         this._appDisableItem(id);
       }
       else if (installData.error == INSTALLERROR_BLOCKLISTED) {
         LOG("... success, item installed but is blocklisted");
         callback(installManifest, installData.id, location, installData.type);
         this._appDisableItem(id);
       }
+      else if (installData.error == INSTALLERROR_SOFTBLOCKED) {
+        LOG("... success, item installed but is soft blocked, item will be disabled");
+        callback(installManifest, installData.id, location, installData.type);
+        this.disableItem(id);
+      }
       else {
         /**
          * Turns an error code into a message for logging
          * @param   error
          *          an Install Error code
          * @returns A string message to be logged.
          */
         function translateErrorMessage(error) {
@@ -4251,19 +4254,21 @@ ExtensionManager.prototype = {
       installData.error = INSTALLERROR_INCOMPATIBLE_VERSION;
       return installData;
     }
     
     // Check if the item is blocklisted.
     if (!gBlocklist)
       gBlocklist = Cc["@mozilla.org/extensions/blocklist;1"].
                    getService(Ci.nsIBlocklistService);
-    if (gBlocklist.isAddonBlocklisted(installData.id, installData.version,
-                                      null, null))
+    var state = gBlocklist.getAddonBlocklistState(installData.id, installData.version);
+    if (state == Ci.nsIBlocklistService.STATE_BLOCKED)
       installData.error = INSTALLERROR_BLOCKLISTED;
+    else if (state == Ci.nsIBlocklistService.STATE_SOFTBLOCKED)
+      installData.error = INSTALLERROR_SOFTBLOCKED;
 
     return installData;
   },
 
   /**
    * Installs an item from a XPI/JAR file.
    * This is the main entry point into the Install system from outside code
    * (e.g. XPInstall).
@@ -4477,56 +4482,56 @@ ExtensionManager.prototype = {
        * @param   installManifest
        *          The Install Manifest datasource for the item.
        * @param   xpiFile
        *          The staged source XPI file that contains the item. Cleaned
        *          up by this process.
        * @param   installRDF
        *          The install.rdf file that was extracted from the xpi.
        */
-      checkForUpdates: function EM_checkForUpdates(item, installManifest, xpiFile) {
+      checkForUpdates: function IncObs_checkForUpdates(item, installManifest, xpiFile) {
         this._xpi             = xpiFile;
         this._installManifest = installManifest;
 
         for (var i = 0; i < em._installListeners.length; ++i)
           em._installListeners[i].onCompatibilityCheckStarted(item);
         em._compatibilityCheckCount++;
         em.update([item], 1, Ci.nsIExtensionManager.UPDATE_CHECK_COMPATIBILITY, this);
       },
 
       /**
        * See nsIExtensionManager.idl
        */
-      onUpdateStarted: function EM_onUpdateStarted() {
+      onUpdateStarted: function IncObs_onUpdateStarted() {
         LOG("Phone Home Listener: Update Started");
       },
 
       /**
        * See nsIExtensionManager.idl
        */
-      onUpdateEnded: function EM_onUpdateEnded() {
+      onUpdateEnded: function IncObs_onUpdateEnded() {
         LOG("Phone Home Listener: Update Ended");
       },
 
       /**
        * See nsIExtensionManager.idl
        */
-      onAddonUpdateStarted: function EM_onAddonUpdateStarted(addon) {
+      onAddonUpdateStarted: function IncObs_onAddonUpdateStarted(addon) {
         if (!addon)
           throw Cr.NS_ERROR_INVALID_ARG;
 
         LOG("Phone Home Listener: Update For " + addon.id + " started");
         em.datasource.addIncompatibleUpdateItem(addon.name, this._xpi.path,
                                                 addon.type, addon.version);
       },
 
       /**
        * See nsIExtensionManager.idl
        */
-      onAddonUpdateEnded: function EM_onAddonUpdateEnded(addon, status) {
+      onAddonUpdateEnded: function IncObs_onAddonUpdateEnded(addon, status) {
         if (!addon)
           throw Cr.NS_ERROR_INVALID_ARG;
 
         LOG("Phone Home Listener: Update For " + addon.id + " ended, status = " + status);
         em.datasource.removeDownload(this._xpi.path);
         LOG("Version Check Phone Home Completed");
 
         for (var i = 0; i < em._installListeners.length; ++i)
@@ -4661,16 +4666,20 @@ ExtensionManager.prototype = {
         //        app that can handle this item, if so just stage and don't show
         //        this error!
         showIncompatibleError(installData);
         LOG("Add-on " + installData.id + " is incompatible with " +
             BundleManager.appName + " " + gApp.version + ", Toolkit " +
             gApp.platformVersion + ". Remote compatibility check was not performed.");
       }
       break;
+    case INSTALLERROR_SOFTBLOCKED:
+      if (!showBlocklistMessage(installData, true))
+        break;
+      // Fall through to continue the install
     case INSTALLERROR_SUCCESS:
       // Installation of multiple extensions / themes contained within a single xpi.
       if (installData.type == Ci.nsIUpdateItem.TYPE_MULTI_XPI) {
         installMultiXPI(aXPIFile, installData);
         break;
       }
 
       // Stage the extension's XPI so it can be extracted at the next restart.
@@ -4754,17 +4763,17 @@ ExtensionManager.prototype = {
       showMessage("incompatibleTitle",
                   [bundle.GetStringFromName("type-" + installData.type)],
                   "incompatiblePlatformMessage",
                   [installData.name, BundleManager.appName, osABI]);
       break;
     case INSTALLERROR_BLOCKLISTED:
       LOG("Blocklisted Item: Item: \"" + installData.id + "\" version " +
           installData.version + " was not installed.");
-      showBlocklistMessage([installData], true);
+      showBlocklistMessage(installData, false);
       break;
     case INSTALLERROR_INSECURE_UPDATE:
       LOG("No secure updates: Item: \"" + installData.id + "\" version " + 
           installData.version + " was not installed.");
       var bundle = BundleManager.getBundle(URI_EXTENSIONS_PROPERTIES);
       showMessage("incompatibleTitle", 
                   [bundle.GetStringFromName("type-" + installData.type)], 
                   "insecureUpdateMessage", [installData.name]);
@@ -5484,42 +5493,61 @@ ExtensionManager.prototype = {
       items = this.getItemList(Ci.nsIUpdateItem.TYPE_ANY, { });
 
     var updater = new ExtensionItemUpdater(this);
     updater.checkForUpdates(items, items.length, updateCheckType, listener,
                             appVersion, platformVersion);
   },
 
   /**
-   * Checks for changes to the blocklist using the local blocklist file,
-   * application disables / enables items that have been added / removed from
-   * the blocklist, and if there are additions to the blocklist this will
-   * inform the user by displaying a list of the items added.
-   *
-   * XXXrstrong - this method is not terribly useful and was added so we can
-   * trigger this check from the additional timer used by blocklisting.
-   */
-  checkForBlocklistChanges: function EM_checkForBlocklistChanges() {
+   * See nsIExtensionManager.idl
+   */
+  updateAndGetNewBlocklistedItems: function EM_updateAndGetNewBlocklistedItems(itemCount) {
+    if (!gBlocklist)
+      gBlocklist = Cc["@mozilla.org/extensions/blocklist;1"].
+                   getService(Ci.nsIBlocklistService);
+
+    var list = [];
     var ds = this.datasource;
     var items = this.getItemList(Ci.nsIUpdateItem.TYPE_ANY, { });
     for (var i = 0; i < items.length; ++i) {
       var id = items[i].id;
-      ds.updateProperty(id, "blocklisted");
-      if (this._isUsableItem(id))
+
+      // Get whether the add-on is currently disabled or set to be disabled.
+      var appDisabled = (ds.getItemProperty(id, "appDisabled") == "true" ||
+                         ds.getItemProperty(id, "appDisabled") == OP_NEEDS_DISABLE);
+      var userDisabled = (ds.getItemProperty(id, "userDisabled") == "true" ||
+                          ds.getItemProperty(id, "userDisabled") == OP_NEEDS_DISABLE);
+      var usable = this._isUsableItem(id);
+      var state = gBlocklist.getAddonBlocklistState(items[i].id, items[i].version);
+
+      // We only return items that are now blocked or to be warned about and aren't
+      // already disabled for some reason.
+      if (!appDisabled && !userDisabled && state != Ci.nsIBlocklistService.STATE_NOT_BLOCKED)
+        list.push(items[i]);
+
+      // Update the appDisabled status based on the new blocked state
+      if (usable)
         this._appEnableItem(id);
-    }
-
-    items = ds.getBlocklistedItemList(null, null, Ci.nsIUpdateItem.TYPE_ANY,
-                                      false);
-    for (i = 0; i < items.length; ++i)
-      this._appDisableItem(items[i].id);
-
-    // show the blocklist notification window if there are new blocklist items.
-    if (items.length > 0)
-      showBlocklistMessage(items, false);
+      else
+        this._appDisableItem(id);
+
+      // If the item was appDisabled and is now usable then it is something
+      // that is no longer hard blocked. If it is still to be warned about then
+      // just user disable it.
+      if (appDisabled && usable && !userDisabled &&
+          state == Ci.nsIBlocklistService.STATE_SOFTBLOCKED)
+        this.disableItem(id);
+
+      ds.updateProperty(id, "blocklisted");
+      ds.updateProperty(id, "blocklistedsoft");
+    }
+
+    itemCount.value = list.length;
+    return list;
   },
 
   /**
    * @returns An enumeration of all registered Install Locations.
    */
   get installLocations () {
     return InstallLocations.enumeration;
   },
@@ -5558,24 +5586,17 @@ ExtensionManager.prototype = {
    *          specified by the id parameter.
    */
   getDependentItemListForID: function EM_getDependentItemListForID(id,
                                                                    includeDisabled,
                                                                    countRef) {
     return this.datasource.getDependentItemListForID(id, includeDisabled, countRef);
   },
 
-  /**
-   * Retrieves a list of nsIUpdateItems of items matching the specified type.
-   * @param   type
-   *          The type of item to return.
-   * @param   countRef
-   *          The XPCJS reference to the number of items returned.
-   * @returns An array of nsIUpdateItems matching the id/type filter.
-   */
+  /* See nsIExtensionManager.idl */
   getItemList: function EM_getItemList(type, countRef) {
     return this.datasource.getItemList(type, countRef);
   },
 
   /* See nsIExtensionManager.idl */
   getIncompatibleItemList: function EM_getIncompatibleItemList(id, appVersion,
                                                                platformVersion,
                                                                type,
@@ -6306,18 +6327,20 @@ ExtensionItemUpdater.prototype = {
 
     // Check if the update will only run on an older version of the application.
     if (!max || gVersionChecker.compare(appExtensionsVersion, max) > 0)
       return false;
 
     if (!gBlocklist)
       gBlocklist = Cc["@mozilla.org/extensions/blocklist;1"].
                    getService(Ci.nsIBlocklistService);
+    // Denies updates that are hard blocked, soft blocked items will be warned
+    // about during the install.
     if (gBlocklist.isAddonBlocklisted(aLocalItem.id, aRemoteItem.version,
-                                      null, null))
+                                      this._appVersion, this._platformVersion))
       return false;
 
     return true;
   },
 
   checkForDone: function ExtensionItemUpdater_checkForDone(item, status) {
     if (this._listener) {
       try {
@@ -7238,59 +7261,16 @@ ExtensionsDataSource.prototype = {
       if (type != -1 && (type & desiredType) &&
           !this.isCompatible(this, item, appVersion, platformVersion))
         items.push(this.getItemForID(id));
     }
     return items;
   },
 
   /**
-   * Retrieves a list of items that will be blocklisted by the application for
-   * a specific application or toolkit version.
-   * @param   appVersion
-   *          The Version of the application to check the blocklist against.
-   * @param   platformVersion
-   *          The Version of the toolkit to check the blocklist against.
-   * @param   desiredType
-   *          The nsIUpdateItem type of items to look for
-   * @param   includeAppDisabled
-   *          Whether or not items that are or are already set to be disabled
-   *          by the app on next restart should be included in the set returned
-   * @returns An array of nsIUpdateItems that are blocklisted with the application
-   *          or toolkit version supplied.
-   */
-  getBlocklistedItemList: function EMDS_getBlocklistedItemList(appVersion,
-                                                               platformVersion,
-                                                               desiredType,
-                                                               includeAppDisabled) {
-    if (!gBlocklist)
-      gBlocklist = Cc["@mozilla.org/extensions/blocklist;1"].
-                   getService(Ci.nsIBlocklistService);
-    var items = [];
-    var ctr = getContainer(this._inner, this._itemRoot);
-    var elements = ctr.GetElements();
-    while (elements.hasMoreElements()) {
-      var item = elements.getNext().QueryInterface(Ci.nsIRDFResource);
-      var id = stripPrefix(item.Value, PREFIX_ITEM_URI);
-      var type = this.getItemProperty(id, "type");
-
-      if (!includeAppDisabled &&
-          (this.getItemProperty(id, "appDisabled") == "true" ||
-          this.getItemProperty(id, "appDisabled") == OP_NEEDS_DISABLE))
-        continue;
-
-      var version = this.getItemProperty(id, "version");
-      if (type != -1 && (type & desiredType) &&
-          gBlocklist.isAddonBlocklisted(id, version, appVersion, platformVersion))
-        items.push(this.getItemForID(id));
-    }
-    return items;
-  },
-
-  /**
    * Gets a list of items of a specific type
    * @param   desiredType
    *          The nsIUpdateItem type of items to return
    * @param   countRef
    *          The XPCJS reference to the size of the returned array
    * @returns An array of nsIUpdateItems, populated only with an item for |id|
    *          if |id| is non-null, otherwise all items matching the specified
    *          type.
@@ -8450,17 +8430,33 @@ ExtensionsDataSource.prototype = {
    * Get the em:blocklisted property (whether or not this item is blocklisted)
    */
   _rdfGet_blocklisted: function EMDS__rdfGet_blocklisted(item, property) {
     var id = stripPrefix(item.Value, PREFIX_ITEM_URI);
     var version = this.getItemProperty(id, "version");
     if (!gBlocklist)
       gBlocklist = Cc["@mozilla.org/extensions/blocklist;1"].
                    getService(Ci.nsIBlocklistService);
-    if (gBlocklist.isAddonBlocklisted(id, version, null, null))
+    if (gBlocklist.getAddonBlocklistState(id, version) == Ci.nsIBlocklistService.STATE_BLOCKED)
+      return EM_L("true");
+
+    return EM_L("false");
+  },
+
+  /**
+   * Get the em:blocklistedsoft property (whether or not this item is listed in the blocklist
+   * at a low severity)
+   */
+  _rdfGet_blocklistedsoft: function EMDS__rdfGet_blocklistedsoft(item, property) {
+    var id = stripPrefix(item.Value, PREFIX_ITEM_URI);
+    var version = this.getItemProperty(id, "version");
+    if (!gBlocklist)
+      gBlocklist = Cc["@mozilla.org/extensions/blocklist;1"].
+                   getService(Ci.nsIBlocklistService);
+    if (gBlocklist.getAddonBlocklistState(id, version) == Ci.nsIBlocklistService.STATE_SOFTBLOCKED)
       return EM_L("true");
 
     return EM_L("false");
   },
 
   /**
    * Get the em:state property (represents the current phase of an install).
    */
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/unit/data/bug455906_block.xml
@@ -0,0 +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"/>
+  </emItems>
+  <pluginItems>
+    <pluginItem>
+      <match name="name" exp="^test_bug455906"/>
+    </pluginItem>
+  </pluginItems>
+</blocklist>
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/unit/data/bug455906_empty.xml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist">
+  <emItems>
+    <emItem id="dummy_bug455906_2@tests.mozilla.org"/>
+  </emItems>
+</blocklist>
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/unit/data/bug455906_start.xml
@@ -0,0 +1,30 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist">
+  <emItems>
+    <emItem id="test_bug455906_4@tests.mozilla.org">
+      <versionRange severity="0"/>
+    </emItem>
+    <emItem id="test_bug455906_5@tests.mozilla.org">
+      <versionRange severity="1"/>
+    </emItem>
+    <emItem id="test_bug455906_6@tests.mozilla.org">
+      <versionRange severity="2"/>
+    </emItem>
+    <emItem id="dummy_bug455906_1@tests.mozilla.org"/>
+  </emItems>
+  <pluginItems>
+    <pluginItem>
+      <match name="name" exp="^test_bug455906_4$"/>
+      <versionRange severity="0"/>
+    </pluginItem>
+    <pluginItem>
+      <match name="name" exp="^test_bug455906_5$"/>
+      <versionRange severity="1"/>
+    </pluginItem>
+    <pluginItem>
+      <match name="name" exp="^test_bug455906_6$"/>
+      <versionRange severity="2"/>
+    </pluginItem>
+  </pluginItems>
+</blocklist>
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/unit/data/bug455906_warn.xml
@@ -0,0 +1,33 @@
+<?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">
+      <versionRange severity="0"/>
+    </emItem>
+    <emItem id="test_bug455906_2@tests.mozilla.org">
+      <versionRange severity="0"/>
+    </emItem>
+    <emItem id="test_bug455906_3@tests.mozilla.org">
+      <versionRange severity="0"/>
+    </emItem>
+    <emItem id="test_bug455906_4@tests.mozilla.org">
+      <versionRange severity="0"/>
+    </emItem>
+    <emItem id="test_bug455906_5@tests.mozilla.org">
+      <versionRange severity="0"/>
+    </emItem>
+    <emItem id="test_bug455906_6@tests.mozilla.org">
+      <versionRange severity="0"/>
+    </emItem>
+    <emItem id="test_bug455906_7@tests.mozilla.org">
+      <versionRange severity="0"/>
+    </emItem>
+  </emItems>
+  <pluginItems>
+    <pluginItem>
+      <match name="name" exp="^test_bug455906"/>
+      <versionRange severity="0"/>
+    </pluginItem>
+  </pluginItems>
+</blocklist>
--- a/toolkit/mozapps/extensions/test/unit/test_bug449027.js
+++ b/toolkit/mozapps/extensions/test/unit/test_bug449027.js
@@ -30,17 +30,17 @@
  * use your version of this file under the terms of the MPL, indicate your
  * decision by deleting the provisions above and replace them with the notice
  * 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_LIST_DIALOG = "chrome://mozapps/content/extensions/list.xul";
+const URI_EXTENSION_BLOCKLIST_DIALOG = "chrome://mozapps/content/extensions/blocklist.xul";
 
 do_import_script("netwerk/test/httpserver/httpd.js");
 
 var ADDONS = [{
   id: "test_bug449027_1@tests.mozilla.org",
   name: "Bug 449027 Addon Test 1",
   version: "5",
   start: false,
@@ -421,24 +421,25 @@ var PluginHostFactory = {
   }
 };
 
 // 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_LIST_DIALOG);
+    do_check_eq(url, URI_EXTENSION_BLOCKLIST_DIALOG);
     do_check_neq(gCallback, null);
-    do_check_true(arguments instanceof Components.interfaces.nsIDialogParamBlock);
+
+    var args = arguments.wrappedJSObject;
 
     gNewBlocks = [];
-    var count = arguments.GetInt(1);
-    for (var i = 0; i < count; i++)
-      gNewBlocks.push(arguments.GetString(i));
+    var list = args.list;
+    for (var i = 0; i < list.length; i++)
+      gNewBlocks.push(list[i].name + " " + list[i].version);
 
     // Call the callback after the blocklist has finished up
     do_timeout(0, "gCallback()");
   },
 
   QueryInterface: function(iid) {
     if (iid.equals(Components.interfaces.nsIWindowWatcher)
      || iid.equals(Components.interfaces.nsISupports))
@@ -513,16 +514,23 @@ function check_state(test, lastTest) {
     var expected = 0;
     for (i = 0; i < ADDONS.length; i++) {
       if (ADDONS[i][test] && !ADDONS[i][lastTest]) {
         if (gNewBlocks.indexOf(ADDONS[i].name + " " + ADDONS[i].version) < 0)
           do_throw("Addon " + (i + 1) + " should have been listed in the blocklist notification for test " + test);
         expected++;
       }
     }
+    for (i = 0; i < PLUGINS.length; i++) {
+      if (PLUGINS[i][test] && !PLUGINS[i][lastTest]) {
+        if (gNewBlocks.indexOf(PLUGINS[i].name + " " + PLUGINS[i].version) < 0)
+          do_throw("Plugin " + (i + 1) + " should have been listed in the blocklist notification for test " + test);
+        expected++;
+      }
+    }
 
     do_check_eq(expected, gNewBlocks.length);
   }
 }
 
 function load_blocklist(file) {
   gPrefs.setCharPref("extensions.blocklist.url", "http://localhost:4444/data/" + file);
   var blocklist = Components.classes["@mozilla.org/extensions/blocklist;1"]
@@ -535,16 +543,17 @@ function run_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]);
 
   createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "3", "8");
   startupEM();
+
   gTestserver = new nsHttpServer();
   gTestserver.registerDirectory("/data/", do_get_file("toolkit/mozapps/extensions/test/unit/data"));
   gTestserver.start(4444);
 
   do_test_pending();
   check_test_pt1();
 }
 
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/unit/test_bug455906.js
@@ -0,0 +1,531 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * Dave Townsend <dtownsend@oxymoronical.com>.
+ *
+ * Portions created by the Initial Developer are Copyright (C) 2008
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * 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";
+
+do_import_script("netwerk/test/httpserver/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"
+}, {
+  // Tests how the blocklist affects an enabled add-on
+  id: "test_bug455906_2@tests.mozilla.org",
+  name: "Bug 455906 Addon Test 2",
+  version: "5",
+  appVersion: "3"
+}, {
+  // Tests how the blocklist affects an enabled add-on, to be disabled by the notification
+  id: "test_bug455906_3@tests.mozilla.org",
+  name: "Bug 455906 Addon Test 3",
+  version: "5",
+  appVersion: "3"
+}, {
+  // Tests how the blocklist affects a disabled add-on that was already warned about
+  id: "test_bug455906_4@tests.mozilla.org",
+  name: "Bug 455906 Addon Test 4",
+  version: "5",
+  appVersion: "3"
+}, {
+  // Tests how the blocklist affects an enabled add-on that was already warned about
+  id: "test_bug455906_5@tests.mozilla.org",
+  name: "Bug 455906 Addon Test 5",
+  version: "5",
+  appVersion: "3"
+}, {
+  // Tests how the blocklist affects an already blocked add-on
+  id: "test_bug455906_6@tests.mozilla.org",
+  name: "Bug 455906 Addon Test 6",
+  version: "5",
+  appVersion: "3"
+}, {
+  // Tests how the blocklist affects an incompatible add-on
+  id: "test_bug455906_7@tests.mozilla.org",
+  name: "Bug 455906 Addon Test 7",
+  version: "5",
+  appVersion: "2"
+}, {
+  // Spare add-on used to ensure we get a notification when switching lists
+  id: "dummy_bug455906_1@tests.mozilla.org",
+  name: "Dummy Addon 1",
+  version: "5",
+  appVersion: "3"
+}, {
+  // Spare add-on used to ensure we get a notification when switching lists
+  id: "dummy_bug455906_2@tests.mozilla.org",
+  name: "Dummy Addon 2",
+  version: "5",
+  appVersion: "3"
+}];
+
+var PLUGINS = [{
+  // Tests how the blocklist affects a disabled plugin
+  name: "test_bug455906_1",
+  version: "5",
+  disabled: true,
+  blocklisted: false
+}, {
+  // Tests how the blocklist affects an enabled plugin
+  name: "test_bug455906_2",
+  version: "5",
+  disabled: false,
+  blocklisted: false
+}, {
+  // Tests how the blocklist affects an enabled plugin, to be disabled by the notification
+  name: "test_bug455906_3",
+  version: "5",
+  disabled: false,
+  blocklisted: false
+}, {
+  // Tests how the blocklist affects a disabled plugin that was already warned about
+  name: "test_bug455906_4",
+  version: "5",
+  disabled: true,
+  blocklisted: false
+}, {
+  // Tests how the blocklist affects an enabled plugin that was already warned about
+  name: "test_bug455906_5",
+  version: "5",
+  disabled: false,
+  blocklisted: false
+}, {
+  // Tests how the blocklist affects an already blocked plugin
+  name: "test_bug455906_6",
+  version: "5",
+  disabled: false,
+  blocklisted: true
+}];
+
+var gNotificationCheck = null;
+var gTestCheck = null;
+var gTestserver = null;
+
+// A fake plugin host for the blocklist service to use
+var PluginHost = {
+  getPluginTags: function(countRef) {
+    countRef.value = PLUGINS.length;
+    return PLUGINS;
+  },
+
+  QueryInterface: function(iid) {
+    if (iid.equals(Ci.nsIPluginHost)
+     || iid.equals(Ci.nsISupports))
+      return this;
+  
+    throw Components.results.NS_ERROR_NO_INTERFACE;
+  }
+}
+
+var PluginHostFactory = {
+  createInstance: function (outer, iid) {
+    if (outer != null)
+      throw Components.results.NS_ERROR_NO_AGGREGATION;
+    return PluginHost.QueryInterface(iid);
+  }
+};
+
+// 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);
+
+    if (gNotificationCheck) {
+      var args = arguments.wrappedJSObject;
+      gNotificationCheck(args);
+    }
+
+    // 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;
+
+    throw Cr.NS_ERROR_NO_INTERFACE;
+  }
+}
+
+var WindowWatcherFactory = {
+  createInstance: function createInstance(outer, iid) {
+    if (outer != null)
+      throw Components.results.NS_ERROR_NO_AGGREGATION;
+    return WindowWatcher.QueryInterface(iid);
+  }
+};
+var registrar = Components.manager.QueryInterface(Components.interfaces.nsIComponentRegistrar);
+registrar.registerFactory(Components.ID("{721c3e73-969e-474b-a6dc-059fd288c428}"),
+                          "Fake Plugin Host",
+                          "@mozilla.org/plugin/host;1", PluginHostFactory);
+registrar.registerFactory(Components.ID("{1dfeb90a-2193-45d5-9cb8-864928b2af55}"),
+                          "Fake Window Watcher",
+                          "@mozilla.org/embedcomp/window-watcher;1", WindowWatcherFactory);
+
+function create_addon(addon) {
+  var installrdf = "<?xml version=\"1.0\"?>\n" +
+                   "\n" +
+                   "<RDF xmlns=\"http://www.w3.org/1999/02/22-rdf-syntax-ns#\"\n" +
+                   "     xmlns:em=\"http://www.mozilla.org/2004/em-rdf#\">\n" +
+                   "  <Description about=\"urn:mozilla:install-manifest\">\n" +
+                   "    <em:id>" + addon.id + "</em:id>\n" +
+                   "    <em:version>" + addon.version + "</em:version>\n" +
+                   "    <em:targetApplication>\n" +
+                   "      <Description>\n" +
+                   "        <em:id>xpcshell@tests.mozilla.org</em:id>\n" +
+                   "        <em:minVersion>" + addon.appVersion + "</em:minVersion>\n" +
+                   "        <em:maxVersion>" + addon.appVersion + "</em:maxVersion>\n" +
+                   "      </Description>\n" +
+                   "    </em:targetApplication>\n" +
+                   "    <em:name>" + addon.name + "</em:name>\n" +
+                   "  </Description>\n" +
+                   "</RDF>\n";
+  var target = gProfD.clone();
+  target.append("extensions");
+  target.append(addon.id);
+  target.append("install.rdf");
+  target.create(target.NORMAL_FILE_TYPE, 0644);
+  var stream = Cc["@mozilla.org/network/file-output-stream;1"].
+               createInstance(Ci.nsIFileOutputStream);
+  stream.init(target, 0x04 | 0x08 | 0x20, 0664, 0); // write, create, truncate
+  stream.write(installrdf, installrdf.length);
+  stream.close();
+}
+
+function load_blocklist(file) {
+  gPrefs.setCharPref("extensions.blocklist.url", "http://localhost:4444/data/" + file);
+  var blocklist = Cc["@mozilla.org/extensions/blocklist;1"].
+                  getService(Ci.nsITimerCallback);
+  blocklist.notify(null);
+}
+
+function isUserDisabled(id) {
+  return getManifestProperty(id, "userDisabled") == "true";
+}
+
+function isAppDisabled(id) {
+  return getManifestProperty(id, "appDisabled") == "true";
+}
+
+function check_addon_state(id) {
+  return isUserDisabled(id) + "," + isAppDisabled(id);
+}
+
+function check_plugin_state(plugin) {
+  return plugin.disabled + "," + plugin.blocklisted;
+}
+
+// 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]);
+
+  // Copy the initial blocklist into the profile to check add-ons start in the
+  // right state.
+  var blocklist = do_get_file("toolkit/mozapps/extensions/test/unit/data/bug455906_start.xml")
+  blocklist.copyTo(gProfD, "blocklist.xml");
+
+  createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "3", "8");
+  startupEM();
+
+  gTestserver = new nsHttpServer();
+  gTestserver.registerDirectory("/data/", do_get_file("toolkit/mozapps/extensions/test/unit/data"));
+  gTestserver.start(4444);
+
+  do_test_pending();
+  check_test_pt1();
+}
+
+// Before every main test this is the state the add-ons are meant to be in
+function check_initial_state() {
+  do_check_eq(check_addon_state(ADDONS[0].id), "true,false");
+  do_check_eq(check_addon_state(ADDONS[1].id), "false,false");
+  do_check_eq(check_addon_state(ADDONS[2].id), "false,false");
+  do_check_eq(check_addon_state(ADDONS[3].id), "true,false");
+  do_check_eq(check_addon_state(ADDONS[4].id), "false,false");
+  do_check_eq(check_addon_state(ADDONS[5].id), "false,true");
+  do_check_eq(check_addon_state(ADDONS[6].id), "false,true");
+
+  do_check_eq(check_plugin_state(PLUGINS[0]), "true,false");
+  do_check_eq(check_plugin_state(PLUGINS[1]), "false,false");
+  do_check_eq(check_plugin_state(PLUGINS[2]), "false,false");
+  do_check_eq(check_plugin_state(PLUGINS[3]), "true,false");
+  do_check_eq(check_plugin_state(PLUGINS[4]), "false,false");
+  do_check_eq(check_plugin_state(PLUGINS[5]), "false,true");
+}
+
+// Tests the add-ons were installed and the initial blocklist applied as expected
+function check_test_pt1() {
+  dump("Checking pt 1\n");
+  for (var i = 0; i < ADDONS.length; i++) {
+    if (!gEM.getItemForID(ADDONS[i].id))
+      do_throw("Addon " + (i + 1) + " did not get installed correctly");
+  }
+
+  do_check_eq(check_addon_state(ADDONS[0].id), "false,false");
+  do_check_eq(check_addon_state(ADDONS[1].id), "false,false");
+  do_check_eq(check_addon_state(ADDONS[2].id), "false,false");
+
+  // Warn add-ons should be user disabled automatically
+  do_check_eq(check_addon_state(ADDONS[3].id), "true,false");
+  do_check_eq(check_addon_state(ADDONS[4].id), "true,false");
+
+  // Blocked and incompatible should be app disabled only
+  do_check_eq(check_addon_state(ADDONS[5].id), "false,true");
+  do_check_eq(check_addon_state(ADDONS[6].id), "false,true");
+
+  // We've overridden the plugin host so we cannot tell what that would have
+  // initialised the plugins as
+
+  // Put the add-ons into the base state
+  gEM.disableItem(ADDONS[0].id);
+  gEM.enableItem(ADDONS[4].id);
+  restartEM();
+  check_initial_state();
+
+  gNotificationCheck = check_notification_pt2;
+  gTestCheck = check_test_pt2;
+  load_blocklist("bug455906_warn.xml");
+}
+
+function check_notification_pt2(args) {
+  dump("Checking notification pt 2\n");
+  do_check_eq(args.list.length, 4);
+
+  for (let i = 0; i < args.list.length; i++) {
+    let addon = args.list[i];
+    if (addon.item instanceof Ci.nsIUpdateItem) {
+      switch (addon.item.id) {
+        case "test_bug455906_2@tests.mozilla.org":
+          do_check_false(addon.blocked);
+          break;
+        case "test_bug455906_3@tests.mozilla.org":
+          do_check_false(addon.blocked);
+          addon.disable = true;
+          break;
+        default:
+          do_throw("Unknown addon: " + addon.item.id);
+      }
+    }
+    else if (addon.item instanceof Ci.nsIPluginTag) {
+      switch (addon.item.name) {
+        case "test_bug455906_2":
+          do_check_false(addon.blocked);
+          break;
+        case "test_bug455906_3":
+          do_check_false(addon.blocked);
+          addon.disable = true;
+          break;
+        default:
+          do_throw("Unknown addon: " + addon.item.name);
+      }
+    }
+    else {
+      do_throw("Unknown add-on type " + addon.item);
+    }
+  }
+}
+
+function check_test_pt2() {
+  restartEM();
+  dump("Checking results pt 2\n");
+
+  // Should have disabled this add-on as requested
+  do_check_eq(check_addon_state(ADDONS[2].id), "true,false");
+  do_check_eq(check_plugin_state(PLUGINS[2]), "true,false");
+
+  // The blocked add-on should have changed to user disabled
+  do_check_eq(check_addon_state(ADDONS[5].id), "true,false");
+  do_check_eq(check_plugin_state(PLUGINS[5]), "true,false");
+
+  // These should have been unchanged
+  do_check_eq(check_addon_state(ADDONS[0].id), "true,false");
+  do_check_eq(check_addon_state(ADDONS[1].id), "false,false");
+  do_check_eq(check_addon_state(ADDONS[3].id), "true,false");
+  do_check_eq(check_addon_state(ADDONS[4].id), "false,false");
+  do_check_eq(check_addon_state(ADDONS[6].id), "false,true");
+  do_check_eq(check_plugin_state(PLUGINS[0]), "true,false");
+  do_check_eq(check_plugin_state(PLUGINS[1]), "false,false");
+  do_check_eq(check_plugin_state(PLUGINS[3]), "true,false");
+  do_check_eq(check_plugin_state(PLUGINS[4]), "false,false");
+
+  // Back to starting state
+  gEM.enableItem(ADDONS[2].id);
+  gEM.enableItem(ADDONS[5].id);
+  PLUGINS[2].disabled = false;
+  PLUGINS[5].disabled = false;
+  restartEM();
+  gNotificationCheck = null;
+  gTestCheck = run_test_pt3;
+  load_blocklist("bug455906_start.xml");
+}
+
+function run_test_pt3() {
+  restartEM();
+  check_initial_state();
+
+  gNotificationCheck = check_notification_pt3;
+  gTestCheck = check_test_pt3;
+  load_blocklist("bug455906_block.xml");
+}
+
+function check_notification_pt3(args) {
+  dump("Checking notification pt 3\n");
+  do_check_eq(args.list.length, 6);
+
+  for (let i = 0; i < args.list.length; i++) {
+    let addon = args.list[i];
+    if (addon.item instanceof Ci.nsIUpdateItem) {
+      switch (addon.item.id) {
+        case "test_bug455906_2@tests.mozilla.org":
+          do_check_true(addon.blocked);
+          break;
+        case "test_bug455906_3@tests.mozilla.org":
+          do_check_true(addon.blocked);
+          break;
+        case "test_bug455906_5@tests.mozilla.org":
+          do_check_true(addon.blocked);
+          break;
+        default:
+          do_throw("Unknown addon: " + addon.item.id);
+      }
+    }
+    else if (addon.item instanceof Ci.nsIPluginTag) {
+      switch (addon.item.name) {
+        case "test_bug455906_2":
+          do_check_true(addon.blocked);
+          break;
+        case "test_bug455906_3":
+          do_check_true(addon.blocked);
+          break;
+        case "test_bug455906_5":
+          do_check_true(addon.blocked);
+          break;
+        default:
+          do_throw("Unknown addon: " + addon.item.name);
+      }
+    }
+    else {
+      do_throw("Unknown add-on type " + addon.item);
+    }
+  }
+}
+
+function check_test_pt3() {
+  restartEM();
+  dump("Checking results pt 3\n");
+
+  // All should have gained the blocklist state, user disabled as previously
+  do_check_eq(check_addon_state(ADDONS[0].id), "true,true");
+  do_check_eq(check_addon_state(ADDONS[1].id), "false,true");
+  do_check_eq(check_addon_state(ADDONS[2].id), "false,true");
+  do_check_eq(check_addon_state(ADDONS[3].id), "true,true");
+  do_check_eq(check_addon_state(ADDONS[4].id), "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");
+
+  // Shouldn't be changed
+  do_check_eq(check_addon_state(ADDONS[5].id), "false,true");
+  do_check_eq(check_addon_state(ADDONS[6].id), "false,true");
+  do_check_eq(check_plugin_state(PLUGINS[5]), "false,true");
+
+  // Back to starting state
+  gNotificationCheck = null;
+  gTestCheck = run_test_pt4;
+  load_blocklist("bug455906_start.xml");
+}
+
+function run_test_pt4() {
+  gEM.enableItem(ADDONS[4].id);
+  PLUGINS[4].disabled = false;
+  restartEM();
+  check_initial_state();
+
+  gNotificationCheck = check_notification_pt4;
+  gTestCheck = check_test_pt4;
+  load_blocklist("bug455906_empty.xml");
+}
+
+function check_notification_pt4(args) {
+  dump("Checking notification pt 4\n");
+
+  // Should be just the dummy add-on to force this notification
+  do_check_eq(args.list.length, 1);
+  do_check_true(args.list[0].item instanceof Ci.nsIUpdateItem);
+  do_check_eq(args.list[0].item.id, "dummy_bug455906_2@tests.mozilla.org");
+}
+
+function check_test_pt4() {
+  restartEM();
+  dump("Checking results pt 4\n");
+
+  // This should have become unblocked
+  do_check_eq(check_addon_state(ADDONS[5].id), "false,false");
+  do_check_eq(check_plugin_state(PLUGINS[5]), "false,false");
+
+  // No change for anything else
+  do_check_eq(check_addon_state(ADDONS[0].id), "true,false");
+  do_check_eq(check_addon_state(ADDONS[1].id), "false,false");
+  do_check_eq(check_addon_state(ADDONS[2].id), "false,false");
+  do_check_eq(check_addon_state(ADDONS[3].id), "true,false");
+  do_check_eq(check_addon_state(ADDONS[4].id), "false,false");
+  do_check_eq(check_addon_state(ADDONS[6].id), "false,true");
+  do_check_eq(check_plugin_state(PLUGINS[0]), "true,false");
+  do_check_eq(check_plugin_state(PLUGINS[1]), "false,false");
+  do_check_eq(check_plugin_state(PLUGINS[2]), "false,false");
+  do_check_eq(check_plugin_state(PLUGINS[3]), "true,false");
+  do_check_eq(check_plugin_state(PLUGINS[4]), "false,false");
+
+  finish();
+}
+
+function finish() {
+  gTestserver.stop();
+  do_test_finished();
+}
--- a/toolkit/themes/gnomestripe/mozapps/extensions/extensions.css
+++ b/toolkit/themes/gnomestripe/mozapps/extensions/extensions.css
@@ -161,16 +161,17 @@ richlistitem[selected="true"]:not([opTyp
   display: none;
 }
 
 richlistitem[availableUpdateURL][updateable="true"] .updateBadge,
 richlistitem[availableUpdateURL][updateable="true"] .updateAvailableBox,
 richlistitem[compatible="false"] .notifyBadge,
 richlistitem[providesUpdatesSecurely="false"] .notifyBadge,
 richlistitem[blocklisted="true"] .notifyBadge,
+richlistitem[blocklistedsoft="true"] .notifyBadge,
 richlistitem[satisfiesDependencies="false"] .notifyBadge {
   display: -moz-box;
 }
 
 /* Selected Add-on buttons
    See content/extensions.css to hide / display buttons */
 .selectedButtons {
   margin-top: 4px;
@@ -193,17 +194,20 @@ richlistitem[satisfiesDependencies="fals
 .uninstallButton, .cancelUninstallButton {
   -moz-margin-start: 5px;
 }
 
 /* Selected Add-on status messages and images */
 richlistitem[compatible="true"] .incompatibleBox,
 richlistitem[providesUpdatesSecurely="true"] .insecureUpdateBox,
 richlistitem[satisfiesDependencies="true"] .needsDependenciesBox,
-richlistitem[blocklisted="false"] .blocklistedBox,
+richlistitem:not([blocklisted="true"]):not([blocklistedsoft="true"]) .blocklistedBox,
+richlistitem[blocklistedsoft="false"]:not([selected="true"]) .blocklistedBox,
+richlistitem[blocklisted="false"] .blocklistedLabel,
+richlistitem[blocklistedsoft="false"] .softBlocklistedLabel,
 richlistitem[opType="needs-uninstall"] .blocklistedBox,
 richlistitem[opType="needs-uninstall"] .incompatibleBox,
 richlistitem[opType="needs-uninstall"] .needsDependenciesBox,
 richlistitem[opType="needs-uninstall"] .blocklistedBox {
   display: none;
 }
 
 richlistitem[loading="true"] .updateBadge {
new file mode 100644
--- /dev/null
+++ b/toolkit/themes/pinstripe/mozapps/extensions/blocklist.css
@@ -0,0 +1,16 @@
+richlistitem {
+  padding-top: 6px;
+  padding-bottom: 6px;
+  -moz-padding-start: 7px;
+  -moz-padding-end: 7px;
+  border-bottom: 1px solid #C0C0C0;
+}
+
+.addon-name-version {
+  font-size: 110%;
+}
+
+.blockedLabel {
+  font-weight: bold;
+  font-style: italic;
+}
--- a/toolkit/themes/pinstripe/mozapps/extensions/extensions.css
+++ b/toolkit/themes/pinstripe/mozapps/extensions/extensions.css
@@ -159,16 +159,17 @@ richlistitem[selected="true"]:not([opTyp
   display: none;
 }
 
 richlistitem[availableUpdateURL][updateable="true"] .updateBadge,
 richlistitem[availableUpdateURL][updateable="true"] .updateAvailableBox,
 richlistitem[compatible="false"] .notifyBadge,
 richlistitem[providesUpdatesSecurely="false"] .notifyBadge,
 richlistitem[blocklisted="true"] .notifyBadge,
+richlistitem[blocklistedsoft="true"] .notifyBadge,
 richlistitem[satisfiesDependencies="false"] .notifyBadge {
   display: -moz-box;
 }
 
 /* Selected Add-on buttons
    See content/extensions.css to hide / display buttons */
 .selectedButtons {
   margin-top: 4px;
@@ -192,26 +193,27 @@ richlistitem[satisfiesDependencies="fals
 .uninstallButton, .cancelUninstallButton {
   -moz-margin-start: 5px;
 }
 
 /* Selected Add-on status messages and images */
 richlistitem[compatible="true"] .incompatibleBox,
 richlistitem[providesUpdatesSecurely="true"] .insecureUpdateBox,
 richlistitem[satisfiesDependencies="true"] .needsDependenciesBox,
-richlistitem[blocklisted="false"] .blocklistedBox,
+richlistitem:not([blocklisted="true"]):not([blocklistedsoft="true"]) .blocklistedBox,
+richlistitem[blocklistedsoft="false"]:not([selected="true"]) .blocklistedBox,
+richlistitem[blocklisted="false"] .blocklistedLabel,
+richlistitem[blocklistedsoft="false"] .softBlocklistedLabel,
 richlistitem[opType="needs-uninstall"] .blocklistedBox,
 richlistitem[opType="needs-uninstall"] .incompatibleBox,
 richlistitem[opType="needs-uninstall"] .needsDependenciesBox,
 richlistitem[opType="needs-uninstall"] .blocklistedBox {
   display: none;
 }
 
-
-
 richlistitem[loading="true"] .updateBadge {
   display: -moz-box;
   width: 16px;
   height: 16px;
   margin-bottom: -3px;
   -moz-margin-end: -2px;
   list-style-image: url("chrome://global/skin/icons/loading_16.png");
   -moz-image-region: auto;
--- a/toolkit/themes/pinstripe/mozapps/jar.mn
+++ b/toolkit/themes/pinstripe/mozapps/jar.mn
@@ -13,16 +13,17 @@ classic.jar:
   skin/classic/mozapps/extensions/viewButtons.png                 (extensions/viewButtons.png)
   skin/classic/mozapps/extensions/ratings.png                     (extensions/ratings.png)
   skin/classic/mozapps/extensions/extensionIcons.png              (extensions/extensionIcons.png)
   skin/classic/mozapps/extensions/about.css                       (extensions/about.css)
   skin/classic/mozapps/extensions/extensions.css                  (extensions/extensions.css)
   skin/classic/mozapps/extensions/extensions.xml                  (extensions/extensions.xml)
   skin/classic/mozapps/extensions/update.css                      (extensions/update.css)
   skin/classic/mozapps/extensions/eula.css                        (extensions/eula.css)
+  skin/classic/mozapps/extensions/blocklist.css                   (extensions/blocklist.css)
   skin/classic/mozapps/passwordmgr/key.png                        (passwordmgr/key.png)
   skin/classic/mozapps/plugins/missingPlugin.css                  (plugins/missingPlugin.css)
   skin/classic/mozapps/plugins/pluginGeneric.png                  (plugins/pluginGeneric.png)
   skin/classic/mozapps/plugins/pluginDisabled.png                 (plugins/pluginDisabled.png)
   skin/classic/mozapps/plugins/pluginBlocked.png                  (plugins/pluginBlocked.png)
   skin/classic/mozapps/plugins/pluginGeneric-16.png               (plugins/pluginGeneric-16.png)
   skin/classic/mozapps/plugins/pluginBlocked-16.png               (plugins/pluginBlocked-16.png)
   skin/classic/mozapps/profile/profileicon.png                    (profile/profileicon.png)
new file mode 100644
--- /dev/null
+++ b/toolkit/themes/winstripe/mozapps/extensions/blocklist.css
@@ -0,0 +1,16 @@
+richlistitem {
+  padding-top: 6px;
+  padding-bottom: 6px;
+  -moz-padding-start: 7px;
+  -moz-padding-end: 7px;
+  border-bottom: 1px solid #C0C0C0;
+}
+
+.addonName {
+  font-weight: bold;
+}
+
+.blockedLabel {
+  font-weight: bold;
+  font-style: italic;
+}
--- a/toolkit/themes/winstripe/mozapps/extensions/extensions.css
+++ b/toolkit/themes/winstripe/mozapps/extensions/extensions.css
@@ -152,16 +152,17 @@ richlistitem[selected="true"]:not([opTyp
   display: none;
 }
 
 richlistitem[availableUpdateURL][updateable="true"] .updateBadge,
 richlistitem[availableUpdateURL][updateable="true"] .updateAvailableBox,
 richlistitem[compatible="false"] .notifyBadge,
 richlistitem[providesUpdatesSecurely="false"] .notifyBadge,
 richlistitem[blocklisted="true"] .notifyBadge,
+richlistitem[blocklistedsoft="true"] .notifyBadge,
 richlistitem[satisfiesDependencies="false"] .notifyBadge {
   display: -moz-box;
 }
 
 /* Selected Add-on buttons
    See content/extensions.css to hide / display buttons */
 .selectedButtons {
   margin-top: 4px;
@@ -184,17 +185,20 @@ richlistitem[satisfiesDependencies="fals
 .uninstallButton, .cancelUninstallButton {
   -moz-margin-start: 5px;
 }
 
 /* Selected Add-on status messages and images */
 richlistitem[compatible="true"] .incompatibleBox,
 richlistitem[providesUpdatesSecurely="true"] .insecureUpdateBox,
 richlistitem[satisfiesDependencies="true"] .needsDependenciesBox,
-richlistitem[blocklisted="false"] .blocklistedBox,
+richlistitem:not([blocklisted="true"]):not([blocklistedsoft="true"]) .blocklistedBox,
+richlistitem[blocklistedsoft="false"]:not([selected="true"]) .blocklistedBox,
+richlistitem[blocklisted="false"] .blocklistedLabel,
+richlistitem[blocklistedsoft="false"] .softBlocklistedLabel,
 richlistitem[opType="needs-uninstall"] .blocklistedBox,
 richlistitem[opType="needs-uninstall"] .incompatibleBox,
 richlistitem[opType="needs-uninstall"] .needsDependenciesBox,
 richlistitem[opType="needs-uninstall"] .blocklistedBox {
   display: none;
 }
 
 richlistitem[loading="true"] .updateBadge {
--- a/toolkit/themes/winstripe/mozapps/jar.mn
+++ b/toolkit/themes/winstripe/mozapps/jar.mn
@@ -3,16 +3,17 @@ classic.jar:
 % skin mozapps classic/1.0 %skin/classic/mozapps/ os!=WINNT
 # NOTE: If you add a new file here, you'll need to add it to the aero
 # section at the bottom of this file
         skin/classic/mozapps/downloads/downloadButtons.png         (downloads/downloadButtons.png)
         skin/classic/mozapps/downloads/downloadIcon.png            (downloads/downloadIcon.png)
         skin/classic/mozapps/downloads/downloads.css               (downloads/downloads.css)
         skin/classic/mozapps/downloads/unknownContentType.css      (downloads/unknownContentType.css)
         skin/classic/mozapps/extensions/about.css                  (extensions/about.css)
+        skin/classic/mozapps/extensions/blocklist.css              (extensions/blocklist.css)
         skin/classic/mozapps/extensions/extensions.css             (extensions/extensions.css)
         skin/classic/mozapps/extensions/itemDisabledFader.png      (extensions/itemDisabledFader.png)
         skin/classic/mozapps/extensions/itemEnabledFader.png       (extensions/itemEnabledFader.png)
         skin/classic/mozapps/extensions/notifyBadges.png           (extensions/notifyBadges.png)
         skin/classic/mozapps/extensions/update.css                 (extensions/update.css)
         skin/classic/mozapps/extensions/themeGeneric.png           (extensions/themeGeneric.png)
         skin/classic/mozapps/extensions/viewButtons.png            (extensions/viewButtons.png)
         skin/classic/mozapps/extensions/ratings.png                (extensions/ratings.png)
--- a/xpcom/system/Makefile.in
+++ b/xpcom/system/Makefile.in
@@ -44,15 +44,16 @@ include $(DEPTH)/config/autoconf.mk
 
 MODULE          = xpcom
 XPIDL_MODULE    = xpcom_system
 
 XPIDLSRCS = \
         nsIXULAppInfo.idl \
         nsIGConfService.idl \
         nsIGnomeVFSService.idl \
+        nsIBlocklistService.idl \
         $(NULL)
 
 ifdef MOZ_CRASHREPORTER
 XPIDLSRCS += nsICrashReporter.idl
 endif
 
 include $(topsrcdir)/config/rules.mk
rename from toolkit/mozapps/extensions/public/nsIBlocklistService.idl
rename to xpcom/system/nsIBlocklistService.idl
--- a/toolkit/mozapps/extensions/public/nsIBlocklistService.idl
+++ b/xpcom/system/nsIBlocklistService.idl
@@ -34,31 +34,80 @@
  * 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 ***** */
 
 
 #include "nsISupports.idl"
 
-[scriptable, uuid(0c3fe697-d50d-4f42-b747-0c5855cfc60e)]
+interface nsIPluginTag;
+
+[scriptable, uuid(8439f9c0-da03-4260-8b21-dc635eed28fb)]
 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.
+  const unsigned long STATE_BLOCKED     = 2;
+
   /**
    * Determine if an item is blocklisted
    * @param   id
-   *          The GUID of the item.
+   *          The ID of the item.
    * @param   version
    *          The item's version.
    * @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,
-                             in AString appVersion, in AString toolkitVersion);
+                             [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.
+   * @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,
+                                       [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
+   *          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 getPluginBlocklistState(in nsIPluginTag plugin,
+                                        [optional] in AString appVersion,
+                                        [optional] in AString toolkitVersion);
 };