Bug 514327 - Detect outdated plugins and offer upgrade path. ui-r=beltzner, r=dtownsend,josh
authorBlair McBride <bmcbride@mozilla.com>
Fri, 02 Oct 2009 13:26:04 +0200
changeset 33384 a804141648882410d949e18a526f0ed6ac5f03eb
parent 33383 5c8ab5dd179254811cfb61da4e713267e7050483
child 33385 acfc95cc1e9255271291f7d235c75e1c650e795f
push idunknown
push userunknown
push dateunknown
reviewersbeltzner, dtownsend, josh
bugs514327
milestone1.9.3a1pre
Bug 514327 - Detect outdated plugins and offer upgrade path. ui-r=beltzner, r=dtownsend,josh
browser/app/profile/firefox.js
browser/base/content/browser.js
browser/components/nsBrowserGlue.js
content/base/src/nsObjectLoadingContent.cpp
content/base/src/nsObjectLoadingContent.h
modules/plugin/base/public/nsIPluginHost.idl
modules/plugin/base/src/nsPluginHost.cpp
toolkit/mozapps/extensions/content/extensions.css
toolkit/mozapps/extensions/content/extensions.js
toolkit/mozapps/extensions/content/extensions.xml
toolkit/mozapps/extensions/content/extensions.xul
toolkit/mozapps/extensions/src/nsBlocklistService.js
toolkit/mozapps/extensions/test/unit/data/bug455906_start.xml
toolkit/mozapps/extensions/test/unit/data/bug455906_warn.xml
toolkit/mozapps/extensions/test/unit/data/test_bug514327_1.xml
toolkit/mozapps/extensions/test/unit/data/test_bug514327_2.xml
toolkit/mozapps/extensions/test/unit/data/test_bug514327_3_empty.xml
toolkit/mozapps/extensions/test/unit/data/test_bug514327_3_outdated_1.xml
toolkit/mozapps/extensions/test/unit/data/test_bug514327_3_outdated_2.xml
toolkit/mozapps/extensions/test/unit/test_bug514327_1.js
toolkit/mozapps/extensions/test/unit/test_bug514327_2.js
toolkit/mozapps/extensions/test/unit/test_bug514327_3.js
toolkit/mozapps/update/content/updates.js
toolkit/mozapps/update/content/updates.xul
toolkit/themes/gnomestripe/mozapps/jar.mn
toolkit/themes/gnomestripe/mozapps/plugins/pluginOutdated-16.png
toolkit/themes/pinstripe/mozapps/jar.mn
toolkit/themes/pinstripe/mozapps/plugins/pluginOutdated-16.png
toolkit/themes/winstripe/mozapps/jar.mn
toolkit/themes/winstripe/mozapps/plugins/pluginOutdated-16-aero.png
toolkit/themes/winstripe/mozapps/plugins/pluginOutdated-16.png
xpcom/system/nsIBlocklistService.idl
--- a/browser/app/profile/firefox.js
+++ b/browser/app/profile/firefox.js
@@ -563,18 +563,22 @@ pref("accessibility.typeaheadfind.linkso
 pref("accessibility.typeaheadfind.flashBar", 1);
 
 // Disable the default plugin for firefox
 pref("plugin.default_plugin_disabled", true);
 
 // plugin finder service url
 pref("pfs.datasource.url", "https://pfs.mozilla.org/plugins/PluginFinderService.php?mimetype=%PLUGIN_MIMETYPE%&appID=%APP_ID%&appVersion=%APP_VERSION%&clientOS=%CLIENT_OS%&chromeLocale=%CHROME_LOCALE%&appRelease=%APP_RELEASE%");
 
-// by default we show an infobar message when pages require plugins the user has not installed
+// by default we show an infobar message when pages require plugins the user has not installed, or are outdated
 pref("plugins.hide_infobar_for_missing_plugin", false);
+pref("plugins.hide_infobar_for_outdated_plugin", false);
+
+pref("plugins.update.url", "https://www.mozilla.com/%LOCALE%/plugins/");
+pref("plugins.update.notifyUser", false);
 
 #ifdef XP_WIN
 pref("browser.preferences.instantApply", false);
 #else
 pref("browser.preferences.instantApply", true);
 #endif
 #ifdef XP_MACOSX
 pref("browser.preferences.animateFadeIn", true);
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -1069,16 +1069,17 @@ function HandleAppCommandEvent(evt) {
 }
 
 function prepareForStartup() {
   gBrowser.addEventListener("DOMUpdatePageReport", gPopupBlockerObserver.onUpdatePageReport, false);
   // Note: we need to listen to untrusted events, because the pluginfinder XBL
   // binding can't fire trusted ones (runs with page privileges).
   gBrowser.addEventListener("PluginNotFound", gMissingPluginInstaller.newMissingPlugin, true, true);
   gBrowser.addEventListener("PluginBlocklisted", gMissingPluginInstaller.newMissingPlugin, true, true);
+  gBrowser.addEventListener("PluginOutdated", gMissingPluginInstaller.newMissingPlugin, true, true);
   gBrowser.addEventListener("PluginDisabled", gMissingPluginInstaller.newDisabledPlugin, true, true);
   gBrowser.addEventListener("NewPluginInstalled", gMissingPluginInstaller.refreshBrowser, false);
   gBrowser.addEventListener("NewTab", BrowserOpenTab, false);
   window.addEventListener("AppCommand", HandleAppCommandEvent, true);
 
   var webNavigation;
   try {
     webNavigation = getWebNavigation();
@@ -5894,77 +5895,133 @@ missingPluginInstaller.prototype.newMiss
     return;
 
   // For broken non-object plugin tags, register a click handler so
   // that the user can click the plugin replacement to get the new
   // plugin. Object tags can, and often do, deal with that themselves,
   // so don't stomp on the page developers toes.
 
   if (aEvent.type != "PluginBlocklisted" &&
+      aEvent.type != "PluginOutdated" &&
       !(aEvent.target instanceof HTMLObjectElement)) {
     aEvent.target.addEventListener("click",
                                    gMissingPluginInstaller.installSinglePlugin,
                                    true);
   }
 
+  let hideBarPrefName = aEvent.type == "PluginOutdated" ?
+                  "plugins.hide_infobar_for_outdated_plugin" :
+                  "plugins.hide_infobar_for_missing_plugin";
   try {
-    if (gPrefService.getBoolPref("plugins.hide_infobar_for_missing_plugin"))
+    if (gPrefService.getBoolPref(hideBarPrefName))
       return;
   } catch (ex) {} // if the pref is missing, treat it as false, which shows the infobar
 
   var browser = gBrowser.getBrowserForDocument(aEvent.target.ownerDocument
                                                      .defaultView.top.document);
   if (!browser.missingPlugins)
     browser.missingPlugins = {};
 
   var pluginInfo = getPluginInfo(aEvent.target);
 
   browser.missingPlugins[pluginInfo.mimetype] = pluginInfo;
 
   var notificationBox = gBrowser.getNotificationBox(browser);
 
-  // If there is already a missing plugin notification then do nothing
-  if (notificationBox.getNotificationWithValue("missing-plugins"))
+  // Should only display one of these warnings per page.
+  // In order of priority, they are: outdated > missing > blocklisted
+
+  // If there is already an outdated plugin notification then do nothing
+  if (notificationBox.getNotificationWithValue("outdated-plugins"))
     return;
   var blockedNotification = notificationBox.getNotificationWithValue("blocked-plugins");
+  var missingNotification = notificationBox.getNotificationWithValue("missing-plugins");
   var priority = notificationBox.PRIORITY_WARNING_MEDIUM;
+  
+  function showBlocklistInfo() {
+    var formatter = Cc["@mozilla.org/toolkit/URLFormatterService;1"].
+                    getService(Ci.nsIURLFormatter);
+    var url = formatter.formatURLPref("extensions.blocklist.detailsURL");
+    gBrowser.loadOneTab(url, {inBackground: false});
+    return true;
+  }
+  
+  function showOutdatedPluginsInfo() {
+    var formatter = Cc["@mozilla.org/toolkit/URLFormatterService;1"].
+                    getService(Ci.nsIURLFormatter);
+    var url = formatter.formatURLPref("plugins.update.url");
+    gBrowser.loadOneTab(url, {inBackground: false});
+    return true;
+  }
+  
+  function showPluginsMissing() {
+    // get the urls of missing plugins
+    var missingPluginsArray = gBrowser.selectedBrowser.missingPlugins;
+    if (missingPluginsArray) {
+      window.openDialog("chrome://mozapps/content/plugins/pluginInstallerWizard.xul",
+                        "PFSWindow", "chrome,centerscreen,resizable=yes",
+                        {plugins: missingPluginsArray, browser: gBrowser.selectedBrowser});
+    }
+  }
 
   if (aEvent.type == "PluginBlocklisted") {
-    if (blockedNotification)
+    if (blockedNotification || missingNotification)
       return;
 
     let iconURL = "chrome://mozapps/skin/plugins/pluginBlocked-16.png";
     let messageString = gNavigatorBundle.getString("blockedpluginsMessage.title");
     let buttons = [{
       label: gNavigatorBundle.getString("blockedpluginsMessage.infoButton.label"),
       accessKey: gNavigatorBundle.getString("blockedpluginsMessage.infoButton.accesskey"),
       popup: null,
-      callback: blocklistInfo
+      callback: showBlocklistInfo
     }, {
       label: gNavigatorBundle.getString("blockedpluginsMessage.searchButton.label"),
       accessKey: gNavigatorBundle.getString("blockedpluginsMessage.searchButton.accesskey"),
       popup: null,
-      callback: pluginsMissing
+      callback: showOutdatedPluginsInfo
     }];
 
     notificationBox.appendNotification(messageString, "blocked-plugins",
                                        iconURL, priority, buttons);
   }
+  else if (aEvent.type == "PluginOutdated") {
+    // Cancel any notification about blocklisting/missing plugins
+    if (blockedNotification)
+      blockedNotification.close();
+    if (missingNotification)
+      missingNotification.close();
+
+    let iconURL = "chrome://mozapps/skin/plugins/pluginOutdated-16.png";
+    let messageString = gNavigatorBundle.getString("outdatedpluginsMessage.title");
+    let buttons = [{
+      label: gNavigatorBundle.getString("outdatedpluginsMessage.updateButton.label"),
+      accessKey: gNavigatorBundle.getString("outdatedpluginsMessage.updateButton.accesskey"),
+      popup: null,
+      callback: showOutdatedPluginsInfo
+    }];
+
+    notificationBox.appendNotification(messageString, "outdated-plugins",
+                                       iconURL, priority, buttons);
+  }
   else if (aEvent.type == "PluginNotFound") {
-    // Cancel any notification about blocklisting
+    if (missingNotification)
+      return;
+
+    // Cancel any notification about blocklisting plugins
     if (blockedNotification)
       blockedNotification.close();
 
     let iconURL = "chrome://mozapps/skin/plugins/pluginGeneric-16.png";
     let messageString = gNavigatorBundle.getString("missingpluginsMessage.title");
     let buttons = [{
       label: gNavigatorBundle.getString("missingpluginsMessage.button.label"),
       accessKey: gNavigatorBundle.getString("missingpluginsMessage.button.accesskey"),
       popup: null,
-      callback: pluginsMissing
+      callback: showPluginsMissing
     }];
   
     notificationBox.appendNotification(messageString, "missing-plugins",
                                        iconURL, priority, buttons);
   }
 }
 
 missingPluginInstaller.prototype.newDisabledPlugin = function(aEvent){
@@ -5989,36 +6046,16 @@ missingPluginInstaller.prototype.refresh
   if (notification) {
     // reset UI
     notificationBox.removeNotification(notification);
   }
   // reload the browser to make the new plugin show.
   browser.reload();
 }
 
-function blocklistInfo()
-{
-  var formatter = Components.classes["@mozilla.org/toolkit/URLFormatterService;1"]
-                            .getService(Components.interfaces.nsIURLFormatter);
-  var url = formatter.formatURLPref("extensions.blocklist.detailsURL");
-  gBrowser.loadOneTab(url, {inBackground: false});
-  return true;
-}
-
-function pluginsMissing()
-{
-  // get the urls of missing plugins
-  var missingPluginsArray = gBrowser.selectedBrowser.missingPlugins;
-  if (missingPluginsArray) {
-    window.openDialog("chrome://mozapps/content/plugins/pluginInstallerWizard.xul",
-                      "PFSWindow", "chrome,centerscreen,resizable=yes",
-                      {plugins: missingPluginsArray, browser: gBrowser.selectedBrowser});
-  }
-}
-
 var gMissingPluginInstaller = new missingPluginInstaller();
 
 function convertFromUnicode(charset, str)
 {
   try {
     var unicodeConverter = Components
        .classes["@mozilla.org/intl/scriptableunicodeconverter"]
        .createInstance(Components.interfaces.nsIScriptableUnicodeConverter);
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -47,16 +47,18 @@ const Cr = Components.results;
 const Cu = Components.utils;
 
 const XULNS = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 
 Cu.import("resource://gre/modules/XPCOMUtils.jsm");
 Cu.import("resource:///modules/distribution.js");
 
 const PREF_EM_NEW_ADDONS_LIST = "extensions.newAddons";
+const PREF_PLUGINS_NOTIFYUSER = "plugins.update.notifyUser";
+const PREF_PLUGINS_UPDATEURL  = "plugins.update.url";
 
 // We try to backup bookmarks at idle times, to avoid doing that at shutdown.
 // Number of idle seconds before trying to backup bookmarks.  15 minutes.
 const BOOKMARKS_BACKUP_IDLE_TIME = 15 * 60;
 // Minimum interval in milliseconds between backups.
 const BOOKMARKS_BACKUP_INTERVAL = 86400 * 1000;
 // Maximum number of backups to create.  Old ones will be purged.
 const BOOKMARKS_BACKUP_MAX_BACKUPS = 10;
@@ -329,16 +331,21 @@ BrowserGlue.prototype = {
     // Load the "more info" page for a locked places.sqlite
     // This property is set earlier in the startup process:
     // nsPlacesDBFlush loads after profile-after-change and initializes
     // the history service, which sends out places-database-locked
     // which sets this property.
     if (this._isPlacesDatabaseLocked) {
       this._showPlacesLockedNotificationBox();
     }
+
+    // If there are plugins installed that are outdated, and the user hasn't
+    // been warned about them yet, open the plugins update page.
+    if (this._prefs.getBoolPref(PREF_PLUGINS_NOTIFYUSER))
+      this._showPluginUpdatePage();
   },
 
   _onQuitRequest: function(aCancelQuit, aQuitType)
   {
     // If user has already dismissed quit request, then do nothing
     if ((aCancelQuit instanceof Ci.nsISupportsPRBool) && aCancelQuit.data)
       return;
 
@@ -521,16 +528,28 @@ BrowserGlue.prototype = {
 
     // Set pref to indicate we've shown the notification.
     var currentVersion = this._prefs.getIntPref("browser.rights.version");
     this._prefs.setBoolPref("browser.rights." + currentVersion + ".shown", true);
 
     var box = notifyBox.appendNotification(notifyRightsText, "about-rights", null, notifyBox.PRIORITY_INFO_LOW, buttons);
     box.persistence = 3; // arbitrary number, just so bar sticks around for a bit
   },
+  
+  _showPluginUpdatePage : function () {
+    this._prefs.setBoolPref(PREF_PLUGINS_NOTIFYUSER, false);
+
+    var formatter = Cc["@mozilla.org/toolkit/URLFormatterService;1"].
+                    getService(Ci.nsIURLFormatter);
+    var updateUrl = formatter.formatURLPref(PREF_PLUGINS_UPDATEURL);
+
+    var win = this.getMostRecentBrowserWindow();
+    var browser = win.gBrowser;
+    browser.selectedTab = browser.addTab(updateUrl);
+  },
 
   // returns the (cached) Sanitizer constructor
   get Sanitizer() 
   {
     if(typeof(Sanitizer) != "function") { // we should dynamically load the script
       Cc["@mozilla.org/moz/jssubscript-loader;1"].
       getService(Ci.mozIJSSubScriptLoader).
       loadSubScript("chrome://browser/content/sanitize.js", null);
--- a/content/base/src/nsObjectLoadingContent.cpp
+++ b/content/base/src/nsObjectLoadingContent.cpp
@@ -57,16 +57,17 @@
 #include "nsIScriptGlobalObject.h"
 #include "nsIScriptSecurityManager.h"
 #include "nsIStreamConverterService.h"
 #include "nsIURILoader.h"
 #include "nsIURL.h"
 #include "nsIWebNavigation.h"
 #include "nsIWebNavigationInfo.h"
 #include "nsIScriptChannel.h"
+#include "nsIBlocklistService.h"
 
 #include "nsPluginError.h"
 
 // Util headers
 #include "prlog.h"
 
 #include "nsAutoPtr.h"
 #include "nsCURILoader.h"
@@ -192,16 +193,19 @@ nsPluginErrorEvent::Run()
       type = NS_LITERAL_STRING("PluginNotFound");
       break;
     case ePluginDisabled:
       type = NS_LITERAL_STRING("PluginDisabled");
       break;
     case ePluginBlocklisted:
       type = NS_LITERAL_STRING("PluginBlocklisted");
       break;
+    case ePluginOutdated:
+      type = NS_LITERAL_STRING("PluginOutdated");
+      break;
     default:
       return NS_OK;
   }
   nsContentUtils::DispatchTrustedEvent(mContent->GetDocument(), mContent,
                                        type, PR_TRUE, PR_TRUE);
 
   return NS_OK;
 }
@@ -1729,36 +1733,54 @@ nsObjectLoadingContent::Instantiate(nsIO
   PRBool oldInstantiatingValue = mInstantiating;
   mInstantiating = PR_TRUE;
 
   nsCString typeToUse(aMIMEType);
   if (typeToUse.IsEmpty() && aURI) {
     IsPluginEnabledByExtension(aURI, typeToUse);
   }
 
+  nsCOMPtr<nsIContent> thisContent = 
+    do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
+  NS_ASSERTION(thisContent, "must be a content");
+  
   nsCOMPtr<nsIURI> baseURI;
   if (!aURI) {
     // We need some URI. If we have nothing else, use the base URI.
     // XXX(biesi): The code used to do this. Not sure why this is correct...
-    nsCOMPtr<nsIContent> thisContent = 
-      do_QueryInterface(static_cast<nsIImageLoadingContent*>(this));
-    NS_ASSERTION(thisContent, "must be a content");
-
     GetObjectBaseURI(thisContent, getter_AddRefs(baseURI));
     aURI = baseURI;
   }
 
   // We'll always have a type or a URI by the time we get here
   NS_ASSERTION(aURI || !typeToUse.IsEmpty(), "Need a URI or a type");
   LOG(("OBJLC [%p]: Calling [%p]->Instantiate(<%s>, %p)\n", this, aFrame,
        typeToUse.get(), aURI));
   nsresult rv = aFrame->Instantiate(typeToUse.get(), aURI);
 
   mInstantiating = oldInstantiatingValue;
 
+  nsCOMPtr<nsIPluginInstance> pluginInstance;
+  aFrame->GetPluginInstance(*getter_AddRefs(pluginInstance));
+  if (pluginInstance) {
+    nsCOMPtr<nsIPluginTag> pluginTag;
+    nsCOMPtr<nsIPluginHost> host(do_GetService(MOZ_PLUGIN_HOST_CONTRACTID));
+    host->GetPluginTagForInstance(pluginInstance, getter_AddRefs(pluginTag));
+
+    nsCOMPtr<nsIBlocklistService> blocklist =
+      do_GetService("@mozilla.org/extensions/blocklist;1");
+    if (blocklist) {
+      PRUint32 blockState = nsIBlocklistService::STATE_NOT_BLOCKED;
+      blocklist->GetPluginBlocklistState(pluginTag, EmptyString(),
+                                         EmptyString(), &blockState);
+      if (blockState == nsIBlocklistService::STATE_OUTDATED)
+        FirePluginError(thisContent, ePluginOutdated);
+    }
+  }
+
   return rv;
 }
 
 nsresult
 nsObjectLoadingContent::CheckClassifier(nsIChannel *aChannel)
 {
   nsresult rv;
   nsCOMPtr<nsIChannelClassifier> classifier =
--- a/content/base/src/nsObjectLoadingContent.h
+++ b/content/base/src/nsObjectLoadingContent.h
@@ -59,16 +59,17 @@ class AutoNotifier;
 class AutoFallback;
 class AutoSetInstantiatingToFalse;
 
 enum PluginSupportState {
   ePluginUnsupported,  // The plugin is not supported (not installed, say)
   ePluginDisabled,     // The plugin has been explicitly disabled by the
                        // user.
   ePluginBlocklisted,  // The plugin is blocklisted and disabled
+  ePluginOutdated,     // The plugin is considered outdated, but not disabled
   ePluginOtherState    // Something else (e.g. not a plugin at all as far
                        // as we can tell).
 };
 
 /**
  * INVARIANTS OF THIS CLASS
  * - mChannel is non-null between asyncOpen and onStopRequest (NOTE: Only needs
  *   to be valid until onStopRequest is called on mFinalListener, not
--- a/modules/plugin/base/public/nsIPluginHost.idl
+++ b/modules/plugin/base/public/nsIPluginHost.idl
@@ -59,17 +59,17 @@ interface nsIURI;
 interface nsIDOMPlugin;
 interface nsIChannel;
 interface nsIPluginStreamListener;
 
 [ptr] native PRLibraryPtr(PRLibrary);
 [ref] native nsIStreamListenerRef(nsIStreamListener *);
 [ptr] native nsPluginNativeWindowPtr(nsPluginNativeWindow);
 
-[scriptable, uuid(23E8FD98-A625-4B08-BE1A-F7CC18A5B106)]
+[scriptable, uuid(30C7C529-B05C-4950-B5B8-9AF673E46521)]
 interface nsIPluginHost : nsISupports
 {
   [noscript] void init();
 
   [noscript] void destroy();
 
   [noscript] void loadPlugins();
 
@@ -273,16 +273,23 @@ interface nsIPluginHost : nsISupports
   /**
    * Get the plugin name for the plugin instance.
    * @param aInstance the plugin instance object
    * @param aPluginName returns a pointer to a shared readonly string value,
    *        it's only valid for the lifetime of the plugin instance - you must
    *        copy the string value if you need it longer than that.
    */
   [noscript] void getPluginName(in nsIPluginInstance aInstance, [shared] out string aPluginName);
+
+  /**
+   * Get the plugin tag associated with a given plugin instance.
+   * @param aInstance the plugin instance object
+   * @return plugin tag object
+   */
+  [noscript] nsIPluginTag getPluginTagForInstance(in nsIPluginInstance aInstance);
 };
 
 %{C++
 #ifdef MOZILLA_INTERNAL_API
 /**
  * Used for creating the correct input stream for plugins
  * We can either have raw data (with or without \r\n\r\n) or a path to a file (but it must be native!)
  * When making an nsIInputStream stream for the plugins POST data, be sure to take into 
--- a/modules/plugin/base/src/nsPluginHost.cpp
+++ b/modules/plugin/base/src/nsPluginHost.cpp
@@ -4234,16 +4234,18 @@ nsresult nsPluginHost::ScanPluginsDirect
       item->mFilePath = filePath;
     }
   } // end round of up of plugin files
 
   // now sort the array by file modification time or by filename, if equal
   // put newer plugins first to weed out dups and catch upgrades, see bug 119966
   pluginFilesArray.Sort();
 
+  PRBool warnOutdated = PR_FALSE;
+
   // finally, go through the array, looking at each entry and continue processing it
   for (PRUint32 i = 0; i < pluginFilesArray.Length(); i++) {
     pluginFileinDirectory &pfd = pluginFilesArray[i];
     nsCOMPtr <nsIFile> file = do_CreateInstance("@mozilla.org/file/local;1");
     nsCOMPtr <nsILocalFile> localfile = do_QueryInterface(file);
     localfile->InitWithPath(pfd.mFilePath);
     PRInt64 fileModTime = pfd.mModTime;
 
@@ -4342,16 +4344,18 @@ nsresult nsPluginHost::ScanPluginsDirect
 
         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;
+          else if (state == nsIBlocklistService::STATE_OUTDATED && !seenBefore)
+            warnOutdated = PR_TRUE;
         }
       }
 
       if (!enabled)
         pluginTag->UnMark(NS_PLUGIN_FLAG_ENABLED);
 
       // 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
@@ -4390,16 +4394,20 @@ nsresult nsPluginHost::ScanPluginsDirect
       pluginTag->SetHost(this);
       pluginTag->mNext = mPlugins;
       mPlugins = pluginTag;
 
       if (pluginTag->IsEnabled())
         pluginTag->RegisterWithCategoryManager(mOverrideInternalTypes);
     }
   }
+  
+  if (warnOutdated)
+    mPrefService->SetBoolPref("plugins.update.notifyUser", PR_TRUE);
+
   return NS_OK;
 }
 
 nsresult nsPluginHost::ScanPluginsDirectoryList(nsISimpleEnumerator * dirEnum,
                                                 nsIComponentManager * compManager,
                                                 PRBool aCreatePluginList,
                                                 PRBool * aPluginsChanged,
                                                 PRBool checkForUnwantedPlugins)
@@ -5837,16 +5845,33 @@ nsPluginHost::InstantiateDummyJavaPlugin
 NS_IMETHODIMP
 nsPluginHost::GetPluginName(nsIPluginInstance *aPluginInstance,
                             const char** aPluginName)
 {
   *aPluginName = GetPluginName(aPluginInstance);
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsPluginHost::GetPluginTagForInstance(nsIPluginInstance *aPluginInstance,
+                                      nsIPluginTag **aPluginTag)
+{
+  NS_ENSURE_ARG_POINTER(aPluginInstance);
+  NS_ENSURE_ARG_POINTER(aPluginTag);
+  
+  nsPluginInstanceTag *plugin =
+    gActivePluginList ? gActivePluginList->find(aPluginInstance) : nsnull;
+
+  NS_ENSURE_TRUE(plugin && plugin->mPluginTag, NS_ERROR_FAILURE);
+  
+  *aPluginTag = plugin->mPluginTag;
+  NS_ADDREF(*aPluginTag);
+  return NS_OK;
+}
+
 nsresult nsPluginHost::AddUnusedLibrary(PRLibrary * aLibrary)
 {
   if (!mUnusedLibraries.Contains(aLibrary)) // don't add duplicates
     mUnusedLibraries.AppendElement(aLibrary);
 
   return NS_OK;
 }
 
--- a/toolkit/mozapps/extensions/content/extensions.css
+++ b/toolkit/mozapps/extensions/content/extensions.css
@@ -228,29 +228,31 @@ richlistitem[addonType="4"] .addonTypeTh
 }
 
 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[outdated="true"] .notifyBadge,
 richlistitem[satisfiesDependencies="false"] .notifyBadge,
 richlistitem[loading="true"] .updateBadge {
   display: -moz-box;
 }
 
 /* Selected Add-on status messages and images */
 richlistitem[compatible="true"] .incompatibleBox,
 richlistitem[providesUpdatesSecurely="true"] .insecureUpdateBox,
 richlistitem[satisfiesDependencies="true"] .needsDependenciesBox,
-richlistitem:not([blocklisted="true"]):not([blocklistedsoft="true"]) .blocklistedBox,
-richlistitem[blocklistedsoft="false"]:not([selected="true"]) .blocklistedBox,
+richlistitem:not([blocklisted="true"]):not([blocklistedsoft="true"]):not([outdated="true"]) .blocklistedBox,
+richlistitem[blocklistedsoft="false"][outdated="false"]:not([selected="true"]) .blocklistedBox,
 richlistitem[blocklisted="false"] .blocklistedLabel,
 richlistitem[blocklistedsoft="false"] .softBlocklistedLabel,
+richlistitem[outdated="false"] .outdatedLabel,
 richlistitem[opType="needs-uninstall"] .blocklistedBox,
 richlistitem[opType="needs-uninstall"] .incompatibleBox,
 richlistitem[opType="needs-uninstall"] .needsDependenciesBox,
 richlistitem[opType="needs-uninstall"] .notifyBadge,
 richlistitem[selected="true"]:not([opType]) .descriptionCrop,
 .addonRating:not([rating]) {
   display: none;
 }
--- a/toolkit/mozapps/extensions/content/extensions.js
+++ b/toolkit/mozapps/extensions/content/extensions.js
@@ -42,27 +42,30 @@
 // Globals
 const nsIExtensionManager    = Components.interfaces.nsIExtensionManager;
 const nsIUpdateItem          = Components.interfaces.nsIUpdateItem;
 const nsIFilePicker          = Components.interfaces.nsIFilePicker;
 const nsIIOService           = Components.interfaces.nsIIOService;
 const nsIFileProtocolHandler = Components.interfaces.nsIFileProtocolHandler;
 const nsIURL                 = Components.interfaces.nsIURL;
 const nsIAppStartup          = Components.interfaces.nsIAppStartup;
+const nsIBlocklistService    = Components.interfaces.nsIBlocklistService;
+const nsIPrefBranch2         = Components.interfaces.nsIPrefBranch2;
 
 var gView             = null;
 var gExtensionManager = null;
 var gExtensionsView   = null;
 var gExtensionStrings = null;
 var gDownloadManager  = null;
 var gObserverIndex    = -1;
 var gInSafeMode       = false;
 var gCheckCompat      = true;
 var gCheckUpdateSecurity = true;
 var gUpdatesOnly      = false;
+var gPluginUpdateUrl  = null;
 var gAppID            = "";
 var gPref             = null;
 var gPriorityCount    = 0;
 var gInstalling       = false;
 var gPendingActions   = false;
 var gPlugins          = null;
 var gPluginsDS        = null;
 var gSearchDS         = null;
@@ -99,16 +102,17 @@ const PREF_EXTENSIONS_DSS_SWITCHPENDING 
 const PREF_EXTENSIONS_HIDE_INSTALL_BTN      = "extensions.hideInstallButton";
 const PREF_DSS_SKIN_TO_SELECT               = "extensions.lastSelectedSkin";
 const PREF_LWTHEME_TO_SELECT                = "extensions.lwThemeToSelect";
 const PREF_GENERAL_SKINS_SELECTEDSKIN       = "general.skins.selectedSkin";
 const PREF_UPDATE_NOTIFYUSER                = "extensions.update.notifyUser";
 const PREF_GETADDONS_SHOWPANE               = "extensions.getAddons.showPane";
 const PREF_GETADDONS_REPOSITORY             = "extensions.getAddons.repository";
 const PREF_GETADDONS_MAXRESULTS             = "extensions.getAddons.maxResults";
+const PREF_PLUGINS_UPDATEURL                = "plugins.update.url";
 
 const URI_GENERIC_ICON_XPINSTALL      = "chrome://mozapps/skin/xpinstall/xpinstallItemGeneric.png";
 const URI_GENERIC_ICON_THEME          = "chrome://mozapps/skin/extensions/themeGeneric.png";
 
 #ifdef MOZ_WIDGET_GTK2
 const URI_NOTIFICATION_ICON_INFO      = "moz-icon://stock/gtk-dialog-info?size=menu";
 const URI_NOTIFICATION_ICON_WARNING   = "moz-icon://stock/gtk-dialog-warning?size=menu";
 #else
@@ -316,16 +320,17 @@ function showView(aView) {
   // 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"],
+                        ["outdated", "?outdated"],
                         ["compatible", "?compatible"],
                         ["description", "?description"],
                         ["downloadURL", "?downloadURL"],
                         ["isDisabled", "?isDisabled"],
                         ["homepageURL", "?homepageURL"],
                         ["iconURL", "?iconURL"],
                         ["internalName", "?internalName"],
                         ["locked", "?locked"],
@@ -390,17 +395,18 @@ function showView(aView) {
       types = [ [ ["type", nsIUpdateItem.TYPE_THEME, "Integer"] ] ];
       break;
     case "locales":
       types = [ [ ["type", nsIUpdateItem.TYPE_LOCALE, "Integer"] ] ];
       break;
     case "plugins":
       prefURL = PREF_EXTENSIONS_GETMOREPLUGINSURL;
       types = [ [ ["plugin", "true", null] ] ];
-      showCheckUpdatesAll = false;
+      if (!gPluginUpdateUrl)
+        showCheckUpdatesAll = false;
       break;
     case "updates":
       document.getElementById("updates-view").hidden = false;
       showInstallFile = false;
       showCheckUpdatesAll = false;
       showInstallUpdatesAll = true;
       if (gUpdatesOnly)
         showSkip = true;
@@ -474,23 +480,29 @@ function showView(aView) {
       showGetMore = getMoreURL == "about:blank" ? false : true;
     }
     catch (e) { }
   }
   getMore.hidden = !showGetMore;
 
   var isThemes = aView == "themes";
 
-  if (aView == "themes" || aView == "extensions") {
-    var el = document.getElementById("installFileButton");
-    el.setAttribute("tooltiptext", el.getAttribute(isThemes ? "tooltiptextthemes" :
-                                                              "tooltiptextaddons"));
-    el = document.getElementById("checkUpdatesAllButton");
-    el.setAttribute("tooltiptext", el.getAttribute(isThemes ? "tooltiptextthemes" :
-                                                              "tooltiptextaddons"));
+  if (aView == "themes" || aView == "extensions" || aView == "plugins") {
+    var tooltipAttr = "";
+    if (aView == "extensions")
+      tooltipAttr = "tooltiptextaddons";
+    else
+      tooltipAttr = "tooltiptext" + aView;
+
+    var el = document.getElementById("checkUpdatesAllButton");
+    el.setAttribute("tooltiptext", el.getAttribute(tooltipAttr));
+    if (aView != "plugins") {
+      el = document.getElementById("installFileButton");
+      el.setAttribute("tooltiptext", el.getAttribute(tooltipAttr));
+    }
   }
 
   document.getElementById("installFileButton").hidden = !showInstallFile;
   document.getElementById("checkUpdatesAllButton").hidden = !showCheckUpdatesAll;
   document.getElementById("installUpdatesAllButton").hidden = !showInstallUpdatesAll;
   document.getElementById("skipDialogButton").hidden = !showSkip;
   document.getElementById("themePreviewArea").hidden = !isThemes;
   document.getElementById("themeSplitter").hidden = !isThemes;
@@ -983,16 +995,18 @@ function initPluginsDS()
 {
   gPluginsDS = Components.classes["@mozilla.org/rdf/datasource;1?name=in-memory-datasource"]
                          .createInstance(Components.interfaces.nsIRDFDataSource);
   rebuildPluginsDS();
 }
 
 function rebuildPluginsDS()
 {
+  var blocklist = Components.classes["@mozilla.org/extensions/blocklist;1"]
+                            .getService(nsIBlocklistService);
   var phs = Components.classes["@mozilla.org/plugin/host;1"]
                       .getService(Components.interfaces.nsIPluginHost);
   var plugins = phs.getPluginTags({ });
   var rdfCU = Components.classes["@mozilla.org/rdf/container-utils;1"]
                         .getService(Components.interfaces.nsIRDFContainerUtils);
   var rootctr = rdfCU.MakeSeq(gPluginsDS, gRDF.GetResource(RDFURI_ITEM_ROOT));
   gPlugins = { };
   
@@ -1012,28 +1026,27 @@ function rebuildPluginsDS()
     if (!(desc in gPlugins[name])) {
       var homepageURL = null;
       // Some plugins (e.g. QuickTime) add an anchor to their description to
       // provide a link to the plugin's homepage in about:plugins. This can be
       // used to provide access to a plugins homepage in the add-ons mgr.
       if (/<A\s+HREF=[^>]*>/i.test(plugin.description))
         homepageURL = /<A\s+HREF=["']?([^>"'\s]*)/i.exec(plugin.description)[1];
 
-      gPlugins[name][desc] = { filename    : plugin.filename,
-                               version     : plugin.version,
-                               homepageURL : homepageURL,
-                               disabled    : plugin.disabled,
-                               blocklisted : plugin.blocklisted,
-                               plugins     : [] };
+      gPlugins[name][desc] = { filename       : plugin.filename,
+                               version        : plugin.version,
+                               homepageURL    : homepageURL,
+                               blocklistState : blocklist.getPluginBlocklistState(plugin),
+                               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),
@@ -1059,22 +1072,26 @@ 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;
+      var softblocked = plugin.blocklistState == nsIBlocklistService.STATE_SOFTBLOCKED;
       gPluginsDS.Assert(pluginNode,
                         gRDF.GetResource(PREFIX_NS_EM + "blocklistedsoft"),
                         gRDF.GetLiteral(softblocked ? "true" : "false"),
                         true);
+      var outdated = plugin.blocklistState == nsIBlocklistService.STATE_OUTDATED;
+      gPluginsDS.Assert(pluginNode,
+                        gRDF.GetResource(PREFIX_NS_EM + "outdated"),
+                        gRDF.GetLiteral((outdated && gPluginUpdateUrl) ? "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);
@@ -1100,17 +1117,17 @@ function togglePluginDisabled(aName, aDe
 }
 
 ///////////////////////////////////////////////////////////////////////////////
 // Startup, Shutdown
 function Startup()
 {
   gExtensionStrings = document.getElementById("extensionsStrings");
   gPref = Components.classes["@mozilla.org/preferences-service;1"]
-                    .getService(Components.interfaces.nsIPrefBranch2);
+                    .getService(nsIPrefBranch2);
   var defaultPref = gPref.QueryInterface(Components.interfaces.nsIPrefService)
                          .getDefaultBranch(null);
   try {
     gThemeToSelect = gCurrentTheme = gPref.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN);
     gDefaultTheme = defaultPref.getCharPref(PREF_GENERAL_SKINS_SELECTEDSKIN);
     if (gPref.getBoolPref(PREF_EXTENSIONS_DSS_SWITCHPENDING))
       gThemeToSelect = gPref.getCharPref(PREF_DSS_SKIN_TO_SELECT);
   }
@@ -1139,16 +1156,20 @@ function Startup()
   try {
     gCheckCompat = gPref.getBoolPref(PREF_EM_CHECK_COMPATIBILITY);
   } catch(e) { }
 
   try {
     gCheckUpdateSecurity = gPref.getBoolPref(PREF_EM_CHECK_UPDATE_SECURITY);
   } catch(e) { }
 
+  try {
+    gPluginUpdateUrl = gPref.getCharPref(PREF_PLUGINS_UPDATEURL);
+  } catch(e) { }
+
   gPref.addObserver(PREF_DSS_SKIN_TO_SELECT, gPrefObserver, false);
   gPref.addObserver(PREF_GENERAL_SKINS_SELECTEDSKIN, gPrefObserver, false);
 
   try {
     gShowGetAddonsPane = gPref.getBoolPref(PREF_GETADDONS_SHOWPANE);
   } catch(e) { }
 
   // Extension Command Updating is handled by a command controller.
@@ -2378,16 +2399,20 @@ function updateGlobalCommands() {
   if (gExtensionsView.hasAttribute("update-operation")) {
     disableInstallFile = true;
     disableRestartButton();
   }
   else if (gView == "updates") {
     disableInstallUpdate = false;
     disableRestartButton();
   }
+  else if (gView == "plugins") {
+    if (gPluginUpdateUrl)
+      disableUpdateCheck = false;
+  }
   else {
     var children = gExtensionsView.children;
     for (var i = 0; i < children.length; ++i) {
       if (children[i].getAttribute("updateable") == "true") {
         disableUpdateCheck = false;
         break;
       }
     }
@@ -2420,16 +2445,21 @@ function hideUpdateInfo()
   document.getElementById("themeSplitter").hidden = true;
   document.getElementById("showUpdateInfoButton").hidden = false;
   document.getElementById("hideUpdateInfoButton").hidden = true;
 }
 
 function checkUpdatesAll() {
   if (isOffline("offlineUpdateMsg2"))
     return;
+  
+  if (gView == "plugins") {
+    openURL(gPluginUpdateUrl);
+    return;
+  }
 
   if (!isXPInstallEnabled())
     return;
 
   // To support custom views we check the add-ons displayed in the list
   var items = [];
   var children = gExtensionsView.children;
   for (var i = 0; i < children.length; ++i) {
--- a/toolkit/mozapps/extensions/content/extensions.xml
+++ b/toolkit/mozapps/extensions/content/extensions.xml
@@ -89,16 +89,17 @@
     </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="isOutdated" onget="return this.getAttribute('outdated') == '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;
@@ -131,34 +132,38 @@
         <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 class="outdatedLabel" value="&outdated.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) {
+          if (this.isBlocklisted || this.isSoftBlocklisted || this.isOutdated) {
             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");
+              if (this.isOutdated)
+                var url = formatter.formatURLPref("plugins.update.url");
+              else
+                var url = formatter.formatURLPref("extensions.blocklist.detailsURL");
               blocklistMoreInfo.setAttribute("moreInfoURL", url);
             } catch(e) {
               blocklistMoreInfo.hidden = true;
             }
           }
         ]]>
       </constructor>
 
@@ -236,16 +241,17 @@
               <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 class="blocklistedLabel" value="&blocklisted.label;" crop="end"/>
               <xul:label class="softBlocklistedLabel" value="&softBlocklisted.label;" crop="end"/>
+              <xul:label class="outdatedLabel" value="&outdated.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;"
@@ -280,24 +286,27 @@
           </xul:hbox>
         </xul:vbox>
       </xul:hbox>
     </content>
 
     <implementation implements="nsIAccessibleProvider, nsIDOMXULSelectControlItemElement">
       <constructor>
         <![CDATA[
-          if (this.isBlocklisted || this.isSoftBlocklisted) {
+          if (this.isBlocklisted || this.isSoftBlocklisted || this.isOutdated) {
             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");
+              if (this.isOutdated)
+                var url = formatter.formatURLPref("plugins.update.url");
+              else
+                var url = formatter.formatURLPref("extensions.blocklist.detailsURL");
               blocklistMoreInfo.setAttribute("moreInfoURL", url);
             } catch(e) {
               blocklistMoreInfo.hidden = true;
             }
           }
 
           if (!this.isCompatible) {
             var label = document.getAnonymousElementByAttribute(this, "anonid", "incompatibleLabel");
--- a/toolkit/mozapps/extensions/content/extensions.xul
+++ b/toolkit/mozapps/extensions/content/extensions.xul
@@ -256,16 +256,17 @@
               accesskey="&cmd.installLocalFile.accesskey;"
               tooltiptextaddons="&cmd.installFileAddon.tooltip;"
               tooltiptextthemes="&cmd.installFileTheme.tooltip;"
               command="cmd_installFile"/>
       <button id="checkUpdatesAllButton" label="&cmd.checkUpdatesAll.label;"
               accesskey="&cmd.checkUpdatesAll.accesskey;"
               tooltiptextaddons="&cmd.checkUpdatesAllAddon.tooltip;"
               tooltiptextthemes="&cmd.checkUpdatesAllTheme.tooltip;"
+              tooltiptextplugins="&cmd.checkUpdatesAllPlugin.tooltip;"
               command="cmd_checkUpdatesAll"/>
       <spacer flex="1"/>
       <button id="skipDialogButton" label="&cmd.skip.label;"
               accesskey="&cmd.skip.accesskey;"
               tooltiptext="&cmd.skip.tooltip;"
               command="cmd_close"/>
       <button id="continueDialogButton" label="&cmd.continue.label;"
               accesskey="&cmd.continue.accesskey;"
--- a/toolkit/mozapps/extensions/src/nsBlocklistService.js
+++ b/toolkit/mozapps/extensions/src/nsBlocklistService.js
@@ -49,29 +49,31 @@ 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_PLUGINS_NOTIFYUSER         = "plugins.update.notifyUser";
 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 SEVERITY_OUTDATED               = 0;
 
 const MODE_RDONLY   = 0x01;
 const MODE_WRONLY   = 0x02;
 const MODE_CREATE   = 0x08;
 const MODE_APPEND   = 0x10;
 const MODE_TRUNCATE = 0x20;
 
 const PERMS_FILE      = 0644;
@@ -815,20 +817,23 @@ Blocklist.prototype = {
         }
       }
 
       if (matchFailed)
         continue;
 
       for (var i = 0; i < blockEntry.versions.length; i++) {
         if (blockEntry.versions[i].includesItem(plugin.version, appVersion,
-                                                toolkitVersion))
-          return blockEntry.versions[i].severity >= gBlocklistLevel ?
-                                                    Ci.nsIBlocklistService.STATE_BLOCKED :
-                                                    Ci.nsIBlocklistService.STATE_SOFTBLOCKED;
+                                                toolkitVersion)) {
+          if (blockEntry.versions[i].severity >= gBlocklistLevel)
+            return Ci.nsIBlocklistService.STATE_BLOCKED;
+          if (blockEntry.versions[i].severity == SEVERITY_OUTDATED)
+            return Ci.nsIBlocklistService.STATE_OUTDATED;
+          return Ci.nsIBlocklistService.STATE_SOFTBLOCKED;
+        }
       }
     }
 
     return Ci.nsIBlocklistService.STATE_NOT_BLOCKED;
   },
 
   _blocklistUpdated: function(oldAddonEntries, oldPluginEntries) {
     var addonList = [];
@@ -870,24 +875,29 @@ Blocklist.prototype = {
       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]
-        });
+        if (state == Ci.nsIBlocklistService.STATE_OUTDATED) {
+          gPref.setBoolPref(PREF_PLUGINS_NOTIFYUSER, true);
+        }
+        else {
+          addonList.push({
+            name: plugins[i].name,
+            version: plugins[i].version,
+            icon: "chrome://mozapps/skin/plugins/pluginGeneric.png",
+            disable: false,
+            blocked: state == Ci.nsIBlocklistService.STATE_BLOCKED,
+            item: plugins[i]
+          });
+        }
       }
       plugins[i].blocklisted = state == Ci.nsIBlocklistService.STATE_BLOCKED;
     }
 
     if (addonList.length == 0)
       return;
 
     var args = {
--- a/toolkit/mozapps/extensions/test/unit/data/bug455906_start.xml
+++ b/toolkit/mozapps/extensions/test/unit/data/bug455906_start.xml
@@ -1,14 +1,14 @@
 <?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"/>
+      <versionRange severity="-1"/>
     </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"/>
--- a/toolkit/mozapps/extensions/test/unit/data/bug455906_warn.xml
+++ b/toolkit/mozapps/extensions/test/unit/data/bug455906_warn.xml
@@ -1,33 +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"/>
+      <versionRange severity="-1"/>
     </emItem>
     <emItem id="test_bug455906_2@tests.mozilla.org">
-      <versionRange severity="0"/>
+      <versionRange severity="-1"/>
     </emItem>
     <emItem id="test_bug455906_3@tests.mozilla.org">
-      <versionRange severity="0"/>
+      <versionRange severity="-1"/>
     </emItem>
     <emItem id="test_bug455906_4@tests.mozilla.org">
-      <versionRange severity="0"/>
+      <versionRange severity="-1"/>
     </emItem>
     <emItem id="test_bug455906_5@tests.mozilla.org">
-      <versionRange severity="0"/>
+      <versionRange severity="-1"/>
     </emItem>
     <emItem id="test_bug455906_6@tests.mozilla.org">
-      <versionRange severity="0"/>
+      <versionRange severity="-1"/>
     </emItem>
     <emItem id="test_bug455906_7@tests.mozilla.org">
-      <versionRange severity="0"/>
+      <versionRange severity="-1"/>
     </emItem>
   </emItems>
   <pluginItems>
     <pluginItem>
       <match name="name" exp="^test_bug455906"/>
-      <versionRange severity="0"/>
+      <versionRange severity="-1"/>
     </pluginItem>
   </pluginItems>
 </blocklist>
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/unit/data/test_bug514327_1.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist">
+  <pluginItems>
+    <pluginItem>
+      <match name="name" exp="^test_bug514327_1"/>
+    </pluginItem>
+    <pluginItem>
+      <match name="name" exp="^test_bug514327_2"/>
+      <versionRange severity="0"/>
+    </pluginItem>
+    <pluginItem>
+      <match name="name" exp="^test_bug514327_3"/>
+      <versionRange severity="0"/>
+    </pluginItem>
+  </pluginItems>
+</blocklist>
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/unit/data/test_bug514327_2.xml
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist">
+  <pluginItems>
+    <pluginItem>
+      <match name="name" exp="Test Plug-in"/>
+      <versionRange severity="0"/>
+    </pluginItem>
+  </pluginItems>
+</blocklist>
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/unit/data/test_bug514327_3_empty.xml
@@ -0,0 +1,4 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist">
+</blocklist>
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/unit/data/test_bug514327_3_outdated_1.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist">
+  <pluginItems>
+    <pluginItem>
+      <match name="name" exp="test_bug514327_1"/>
+    </pluginItem>
+    <pluginItem>
+      <match name="name" exp="test_bug514327_outdated"/>
+      <versionRange severity="0"/>
+    </pluginItem>
+  </pluginItems>
+</blocklist>
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/unit/data/test_bug514327_3_outdated_2.xml
@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<blocklist xmlns="http://www.mozilla.org/2006/addons-blocklist">
+  <pluginItems>
+    <pluginItem>
+      <match name="name" exp="test_bug514327_2"/>
+    </pluginItem>
+    <pluginItem>
+      <match name="name" exp="test_bug514327_outdated"/>
+      <versionRange severity="0"/>
+    </pluginItem>
+  </pluginItems>
+</blocklist>
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/unit/test_bug514327_1.js
@@ -0,0 +1,93 @@
+/* ***** 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
+ * Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Blair McBride <bmcbride@mozilla.com> (Original Author)
+ *
+ * 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 Cc = Components.classes;
+const Ci = Components.interfaces;
+
+const nsIBLS = Ci.nsIBlocklistService;
+
+var PLUGINS = [{
+  // blocklisted - default severity
+  name: "test_bug514327_1",
+  version: "5",
+  disabled: false,
+  blocklisted: false
+},
+{
+  // outdated - severity of "0"
+  name: "test_bug514327_2",
+  version: "5",
+  disabled: false,
+  blocklisted: false
+},
+{
+  // outdated - severity of "0"
+  name: "test_bug514327_3",
+  version: "5",
+  disabled: false,
+  blocklisted: false
+},
+{
+  // not blocklisted, not outdated
+  name: "test_bug514327_4",
+  version: "5",
+  disabled: false,
+  blocklisted: false,
+  outdated: false
+}];
+
+
+function run_test() {
+  createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9");
+
+  var source = do_get_file("data/test_bug514327_1.xml");
+  source.copyTo(gProfD, "blocklist.xml");
+
+  var blocklist = Cc["@mozilla.org/extensions/blocklist;1"].getService(nsIBLS);
+
+  // blocked (sanity check)
+  do_check_true(blocklist.getPluginBlocklistState(PLUGINS[0], "1", "1.9") == nsIBLS.STATE_BLOCKED);
+
+  // outdated
+  do_check_true(blocklist.getPluginBlocklistState(PLUGINS[1], "1", "1.9") == nsIBLS.STATE_OUTDATED);
+
+  // outdated
+  do_check_true(blocklist.getPluginBlocklistState(PLUGINS[2], "1", "1.9") == nsIBLS.STATE_OUTDATED);
+
+  // not blocked
+  do_check_true(blocklist.getPluginBlocklistState(PLUGINS[3], "1", "1.9") == nsIBLS.STATE_NOT_BLOCKED);
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/unit/test_bug514327_2.js
@@ -0,0 +1,72 @@
+/* ***** 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
+ * Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Blair McBride <bmcbride@mozilla.com> (Original Author)
+ *
+ * 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 Cc = Components.classes;
+const Ci = Components.interfaces;
+
+const nsIBLS = Ci.nsIBlocklistService;
+
+// Finds the test nsIPluginTag
+function get_test_plugintag() {
+  var host = Cc["@mozilla.org/plugin/host;1"].getService(Ci.nsIPluginHost);
+  var tags = host.getPluginTags({});
+  for (var i = 0; i < tags.length; i++) {
+    if (tags[i].name == "Test Plug-in")
+      return tags[i];
+  }
+  return null;
+}
+
+function run_test() {
+  createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9");
+
+  var source = do_get_file("data/test_bug514327_2.xml");
+  source.copyTo(gProfD, "blocklist.xml");
+
+  var blocklist = Cc["@mozilla.org/extensions/blocklist;1"].getService(nsIBLS);
+  var prefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch);
+
+  var plugin = get_test_plugintag();
+  if (!plugin)
+    do_throw("Plugin tag not found");
+
+  // should be marked as outdated by the blocklist
+  do_check_true(blocklist.getPluginBlocklistState(plugin, "1", "1.9") == nsIBLS.STATE_OUTDATED);
+
+  // should indicate that a warning should be shown
+  do_check_true(prefs.getBoolPref("plugins.update.notifyUser"));
+}
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/test/unit/test_bug514327_3.js
@@ -0,0 +1,194 @@
+/* ***** 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
+ * Mozilla Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Blair McBride <bmcbride@mozilla.com> (Original Author)
+ *
+ * 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 ***** */
+
+do_load_httpd_js();
+
+
+const nsIBLS = Ci.nsIBlocklistService;
+const URI_EXTENSION_BLOCKLIST_DIALOG = "chrome://mozapps/content/extensions/blocklist.xul";
+
+var gBlocklist = null;
+var gPrefs = null;
+var gTestserver = null;
+
+var gNextTestPart = null;
+
+
+var PLUGINS = [{
+  // Tests a plugin whose state goes from not-blocked, to outdated
+  name: "test_bug514327_outdated",
+  version: "5",
+  disabled: false,
+  blocklisted: false
+}, {
+  // Used to trigger the blocklist dialog, which indicates the blocklist has updated
+  name: "test_bug514327_1",
+  version: "5",
+  disabled: false,
+  blocklisted: false
+}, {
+  // Used to trigger the blocklist dialog, which indicates the blocklist has updated
+  name: "test_bug514327_2",
+  version: "5",
+  disabled: false,
+  blocklisted: false
+} ];
+
+
+// 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);
+    // Should only include one item
+    do_check_eq(arguments.wrappedJSObject.list.length, 1);
+    // And that item should be the blocked plugin, not the outdated one
+    var item = arguments.wrappedJSObject.list[0];
+    do_check_true(item.item instanceof Ci.nsIPluginTag);
+    do_check_neq(item.name, "test_bug514327_outdated");
+
+    // Call the next test after the blocklist has finished up
+    do_timeout(0, "gNextTestPart()");
+  },
+
+  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(Ci.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 do_update_blocklist(aDatafile, aNextPart) {
+  gNextTestPart = aNextPart;
+  
+  gPrefs.setCharPref("extensions.blocklist.url", "http://localhost:4444/data/" + aDatafile);
+  gBlocklist.QueryInterface(Ci.nsITimerCallback).notify(null);
+}
+
+function run_test() {
+  createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "1.9");
+  
+  gTestserver = new nsHttpServer();
+  gTestserver.registerDirectory("/data/", do_get_file("data"));
+  gTestserver.start(4444);
+
+
+  // initialize the blocklist with no entries
+  var source = do_get_file("data/test_bug514327_3_empty.xml");
+  source.copyTo(gProfD, "blocklist.xml");
+  
+  gPrefs = Cc["@mozilla.org/preferences-service;1"].getService(Ci.nsIPrefBranch2);
+  gBlocklist = Cc["@mozilla.org/extensions/blocklist;1"].getService(nsIBLS);
+  
+  // should NOT be marked as outdated by the blocklist
+  do_check_true(gBlocklist.getPluginBlocklistState(PLUGINS[0], "1", "1.9") == nsIBLS.STATE_NOT_BLOCKED);
+  
+  do_test_pending();
+
+  // update blocklist with data that marks the plugin as outdated
+  do_update_blocklist("test_bug514327_3_outdated_1.xml", test_part_1);
+}
+
+function test_part_1() {
+  // plugin should now be marked as outdated
+  do_check_true(gBlocklist.getPluginBlocklistState(PLUGINS[0], "1", "1.9") == nsIBLS.STATE_OUTDATED);
+  // and the notifyUser pref should be set to true
+  do_check_true(gPrefs.getBoolPref("plugins.update.notifyUser"));
+  
+  // preternd the user has been notified, reset the pref
+  gPrefs.setBoolPref("plugins.update.notifyUser", false);
+  
+  // update blocklist with data that marks the plugin as outdated
+  do_update_blocklist("test_bug514327_3_outdated_2.xml", test_part_2);
+}
+
+function test_part_2() {
+  // plugin should still be marked as outdated
+  do_check_true(gBlocklist.getPluginBlocklistState(PLUGINS[0], "1", "1.9") == nsIBLS.STATE_OUTDATED);
+  // and the notifyUser pref should NOT be set to true, as the plugin was already outdated
+  do_check_false(gPrefs.getBoolPref("plugins.update.notifyUser"));
+
+  finish();
+}
+
+function finish() {
+  gTestserver.stop(do_test_finished);
+}
--- a/toolkit/mozapps/update/content/updates.js
+++ b/toolkit/mozapps/update/content/updates.js
@@ -48,16 +48,17 @@ const CoR = Components.results;
 
 const XMLNS_XUL               = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
 
 const PREF_UPDATE_MANUAL_URL        = "app.update.url.manual";
 const PREF_APP_UPDATE_LOG_BRANCH    = "app.update.log.";
 const PREF_UPDATE_TEST_LOOP         = "app.update.test.loop";
 const PREF_UPDATE_NEVER_BRANCH      = "app.update.never.";
 const PREF_AUTO_UPDATE_ENABLED      = "app.update.enabled";
+const PREF_PLUGINS_UPDATEURL        = "plugins.update.url";
 
 const UPDATE_TEST_LOOP_INTERVAL     = 2000;
 
 const URI_UPDATES_PROPERTIES  = "chrome://mozapps/locale/update/updates.properties";
 
 const STATE_DOWNLOADING       = "downloading";
 const STATE_PENDING           = "pending";
 const STATE_APPLYING          = "applying";
@@ -534,16 +535,72 @@ var gCheckingPage = {
           !aIID.equals(CoI.nsISupports))
         throw CoR.NS_ERROR_NO_INTERFACE;
       return this;
     }
   }
 };
 
 /**
+ * The "You have outdated plugins" page
+ */
+var gPluginsPage = {
+  /**
+   * URL of the plugin updates page
+   */
+  _url: null,
+  
+  /**
+   * Initialize
+   */
+  onPageShow: function() {
+    if (gPref.getPrefType(PREF_PLUGINS_UPDATEURL) == gPref.PREF_INVALID) {
+      gUpdates.wiz.goTo("noupdatesfound");
+      return;
+    }
+    
+    var formatter = CoC["@mozilla.org/toolkit/URLFormatterService;1"].
+                       getService(CoI.nsIURLFormatter);
+    this._url = formatter.formatURLPref(PREF_PLUGINS_UPDATEURL);
+    var link = document.getElementById("pluginupdateslink");
+    link.setAttribute("href", this._url);
+
+
+    var phs = CoC["@mozilla.org/plugin/host;1"].
+                 getService(CoI.nsIPluginHost);
+    var plugins = phs.getPluginTags({});
+    var blocklist = CoC["@mozilla.org/extensions/blocklist;1"].
+                      getService(CoI.nsIBlocklistService);
+
+    var hasOutdated = false;
+    for (let i = 0; i < plugins.length; i++) {
+      let pluginState = blocklist.getPluginBlocklistState(plugins[i]);
+      if (pluginState == CoI.nsIBlocklistService.STATE_OUTDATED) {
+        hasOutdated = true;
+        break;
+      }
+    }
+    if (!hasOutdated) {
+      gUpdates.wiz.goTo("noupdatesfound");
+      return;
+    }
+
+    gUpdates.setButtons(null, null, "okButton", true);
+    gUpdates.wiz.getButton("finish").focus();
+  },
+  
+  /**
+   * Finish button clicked.
+   */
+  onWizardFinish: function() {
+    openURL(this._url);
+  }
+};
+
+/**
  * The "No Updates Are Available" page
  */
 var gNoUpdatesPage = {
   /**
    * Initialize
    */
   onPageShow: function() {
     LOG("gNoUpdatesPage", "onPageShow - could not select an appropriate " +
--- a/toolkit/mozapps/update/content/updates.xul
+++ b/toolkit/mozapps/update/content/updates.xul
@@ -67,31 +67,40 @@
   
   <stringbundleset id="updateSet">
     <stringbundle id="brandStrings" src="chrome://branding/locale/brand.properties"/>
     <stringbundle id="updateStrings" src="chrome://mozapps/locale/update/updates.properties"/>
   </stringbundleset>
   
   <wizardpage id="dummy" pageid="dummy" firstpage="true"/>
 
-  <wizardpage id="checking" pageid="checking" next="noupdatesfound"
+  <wizardpage id="checking" pageid="checking" next="pluginupdatesfound"
               label="&checking.title;" object="gCheckingPage"
               onpageshow="gCheckingPage.onPageShow();">
     <label>&updateCheck.label;</label>
     <separator class="thin"/>
     <progressmeter id="checkingProgress" mode="undetermined" hidden="true"/>
   </wizardpage>
-  
+
+  <wizardpage id="pluginupdatesfound" pageid="pluginupdatesfound"
+              label="&pluginupdatesfound.title;"
+              object="gPluginsPage" onpageshow="gPluginsPage.onPageShow();">
+    <label>&pluginupdatesfound.label;</label>
+    <separator class="thin"/>
+    <label id="pluginupdateslink" class="text-link"
+           onclick="openUpdateURL(event);">&pluginupdateslink.label;</label>
+  </wizardpage>
+
   <wizardpage id="noupdatesfound" pageid="noupdatesfound"
               label="&noupdatesfound.title;" object="gNoUpdatesPage"
               onpageshow="gNoUpdatesPage.onPageShow();">
     <label id="noUpdatesAutoEnabled" hidden="true">&noupdatesautoenabled.intro;</label>
     <label id="noUpdatesAutoDisabled" hidden="true">&noupdatesautodisabled.intro;</label>
   </wizardpage>
-  
+
   <wizardpage id="incompatibleCheck" pageid="incompatibleCheck"
               next="updatesfound" label="&incompatibleCheck.title;"
               object="gIncompatibleCheckPage"
               onpageshow="gIncompatibleCheckPage.onPageShow();">
     <label>&incompatibleCheck.label;</label>
     <separator class="thin"/>
     <progressmeter id="incompatibleCheckProgress" mode="undetermined" hidden="true"/>
   </wizardpage>
--- a/toolkit/themes/gnomestripe/mozapps/jar.mn
+++ b/toolkit/themes/gnomestripe/mozapps/jar.mn
@@ -10,15 +10,16 @@ toolkit.jar:
 + skin/classic/mozapps/extensions/themeGeneric.png         (extensions/themeGeneric.png)
 + skin/classic/mozapps/extensions/viewButtons.png          (extensions/viewButtons.png)
 + skin/classic/mozapps/passwordmgr/key.png                 (passwordmgr/key.png)
 + 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/plugins/pluginOutdated-16.png       (plugins/pluginOutdated-16.png)
 + skin/classic/mozapps/profile/profileicon.png             (profile/profileicon.png)
 + skin/classic/mozapps/viewsource/viewsource.css           (viewsource/viewsource.css)
 + skin/classic/mozapps/xpinstall/xpinstallItemGeneric.png  (xpinstall/xpinstallItemGeneric.png)
 #ifdef MOZ_PLACES
 + skin/classic/mozapps/places/defaultFavicon.png           (places/defaultFavicon.png)
 + skin/classic/mozapps/places/tagContainerIcon.png         (places/tagContainerIcon.png)
 #endif
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..d4dd69dd3d2e6e2a68e1e0bfa43db30883279c26
GIT binary patch
literal 830
zc$@(~1Ht@>P)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00004b3#c}2nYxW
zd<bNS00009a7bBm000fw000fw0YWI7cmMzZ8FWQhbW?9;ba!ELWdL_~cP?peYja~^
zaAhuUa%Y?FJQ@H102OpaSaeirbZlh+Mrm?oW^Z^PP;7N)X>N0$gZ>c!000+sMObt}
zb#!QNasW(WaBm<(VQgV-VQyq{Woh4$8Y%z)02p*dSad^jWnpw_Z*Cw|X>DZyGB7YW
zEio}IFf_X303`qb0#ivuK~y-6jgUV`lyMlxKhN`fU+)e?l2q=lKekjN<diwME3`Pd
zI3`3>OW<USlcQ4*h=oH45j6#h(C8AEmKDa8b-r0rshoGdo%i-W4cA?Nbm$Ae<qMw=
zzvqjYVP?FAQ-x%5HGnt}DFz}p%#8QR<joD|O27JN0i^4UnIRwGG9tIjA}2HB<D|N~
z0r)sNik8ci(rM04O#x6^SfDsFgNVGx?8+IlC%hKf+8rCSH$6R8NTn<Q3p~$ORjo8L
zV^4kGg63umzzV9iaq*&UU%zfIMe=5bhD}Y!6Foib=ks*jxkKCB9LMk8B_RUq>p0J&
zZF-uvxjEWrXQ`~Lkm~EBY}N_jg@DB|Un><3R#&M!e@?i(%#Io9IOf~I!C(FMj~|=>
z_Nm1S0rh>N_I6%8d`L2i2zPhUOa}G5BYS|bE+|D2)ga)~$OwhCH7cbNtzBJo4h`XD
zv-F=n&AUgB_+Bj1dhQ(At}d#D0_D1(41~+egpVK73TR6U?f37K=<Nld2{?b@0$+xQ
ziTCy>E-jH79BiDR`+$hlo+`&CCK_-(iQZmXCniwmQ0GAP_Xhibs3OiGKx$xsPuVR0
zrX!bYz~bCtfjU=fL=M6Az9CTky+H)%AA-u_BALz2LwtDe9-<1)p{m674FJSB)b~;6
zC{IpO&E@`rtGOKI$;sLVzF!m7f1q)^^En9GGJ}I)1|o#h(^R{=(R7-4Ym4aBD-!+v
zjZ;44^Z2R(fEUBn2lMl{B9Zjb#~+Sowi(6oH2^WQ|K*O%PvBGzI*9?o(*OVf07*qo
IM6N<$f?yGC`~Uy|
--- a/toolkit/themes/pinstripe/mozapps/jar.mn
+++ b/toolkit/themes/pinstripe/mozapps/jar.mn
@@ -20,16 +20,17 @@ toolkit.jar:
   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/plugins/pluginOutdated-16.png              (plugins/pluginOutdated-16.png)
   skin/classic/mozapps/profile/profileicon.png                    (profile/profileicon.png)
   skin/classic/mozapps/profile/profileicon-selected.png           (profile/profileicon-selected.png)
   skin/classic/mozapps/profile/profileSelection.css               (profile/profileSelection.css)
   skin/classic/mozapps/update/buttons.png                         (update/buttons.png)
   skin/classic/mozapps/update/updates.css                         (update/updates.css)
   skin/classic/mozapps/update/update.png                          (update/update.png)
   skin/classic/mozapps/viewsource/viewsource.css                  (viewsource/viewsource.css)
   skin/classic/mozapps/xpinstall/xpinstallItemGeneric.png         (xpinstall/xpinstallItemGeneric.png)
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..1700ed11f285d6bcdd4d18997438624d25ba3718
GIT binary patch
literal 823
zc$@(@1IYY|P)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV0000PbVXQnQ*UN;
zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!*-1n}RCwB?Q(Z`tVHAEozy0Vo=Qhzz
zaa!U~?4poBL?HsbXi_0wNL~m@Aa&i1;H^P7-DyNOyRfvbUF>G%#S{`rVPa}aXUwK<
zZMxsj@3-@g1%-Ik2j_c#p7%NLbIyAXr)e7g=i@Hf*A@N#PnG$6A6p1!bEs-?@HS25
z3kS<%MQ(NVA(u#8RaKSC%d&oZYO3$tW<!>-lF7cf&-Y9(ijpQt;9ah?V|e%lL260C
ztAPR6U5_VTO@s=bM?8_hk6;iPhXW}Ju{xPVZ+|~@=`_?r0fNbd^=#HV6bJ;E0>gc`
zduMBY9#L7wPa-&VIy{)3hPt~8C7%b^+KR}+0=D<}aX`3*&fW8R7l`X*3Tz+U?sQdc
zEo34J1X>jZdZV$Fpd!s#7)DV9PkWl#j4Kuk44DjE%gYX?K%bYSh|%F_v$VIPa&HeN
z0%JQnh!W|TZmgrQ?u?9}ZfFR~*KOq9zr(4bL|6`)167oythucXMyC^SyRknv2YpQq
zZd$D{+3m2lw4i=+5**zuH#9&Y2j`;5gsBzuQ__mECLA3h;`8BTWd-)GE~Mgdd?OWR
zy2I4dgWSdjq7xH1TUvrdd5N8!tuZchAS<$**0NbNQKnDV*HMhcz*FoTJsDw84F)7;
zXHnDJ3qxZgBtd|vs%ge8DTo$}o3T&`GRL8Hd>q!!PRK+CIgu#`dJ3<3W(KygG5qrT
zA*(75L~)aGc~*@fO-tTTRRbT1T(dMa8B~&32!~5m=4ft)#9n}E;*gx227~!8g7Esu
z@$nmiGhDfxEo2QI!1D-r?BTdUL%BrV-Q6f29+qnOyCf|>Po-XNi6VP}I9cvbNwZMQ
zWQ}CkZ*kn{Tb>UGbUN*GM~AlPa)k#>rcs8K&zQ@c3(dq-o5<{B^-py=uhHegbS(CP
z)+b~vafQE|io2vk_2sat%X`_fT*@r|>HpsX3;=y}Lr6=eHM0N!002ovPDHLkV1kiH
Bb{zl!
--- a/toolkit/themes/winstripe/mozapps/jar.mn
+++ b/toolkit/themes/winstripe/mozapps/jar.mn
@@ -26,16 +26,17 @@ toolkit.jar:
         skin/classic/mozapps/places/tagContainerIcon.png           (places/tagContainerIcon.png)
 #endif
         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/plugins/pluginOutdated-16.png         (plugins/pluginOutdated-16.png)
         skin/classic/mozapps/plugins/pluginInstallerWizard.css     (plugins/pluginInstallerWizard.css)
         skin/classic/mozapps/profile/profileicon.png               (profile/profileicon.png)
         skin/classic/mozapps/profile/profileSelection.css          (profile/profileSelection.css)
         skin/classic/mozapps/update/downloadButtons.png            (update/downloadButtons.png)
         skin/classic/mozapps/update/update.png                     (update/update.png)
         skin/classic/mozapps/update/updates.css                    (update/updates.css)
         skin/classic/mozapps/viewsource/viewsource.css             (viewsource/viewsource.css)
 *       skin/classic/mozapps/xpinstall/xpinstallConfirm.css        (xpinstall/xpinstallConfirm.css)
@@ -66,16 +67,17 @@ toolkit.jar:
         skin/classic/aero/mozapps/places/tagContainerIcon.png              (places/tagContainerIcon-aero.png)
 #endif
         skin/classic/aero/mozapps/plugins/missingPlugin.css                (plugins/missingPlugin.css)
         skin/classic/aero/mozapps/plugins/pluginGeneric.png                (plugins/pluginGeneric-aero.png)
         skin/classic/aero/mozapps/plugins/pluginDisabled.png               (plugins/pluginDisabled-aero.png)
         skin/classic/aero/mozapps/plugins/pluginBlocked.png                (plugins/pluginBlocked-aero.png)
         skin/classic/aero/mozapps/plugins/pluginGeneric-16.png             (plugins/pluginGeneric-16-aero.png)
         skin/classic/aero/mozapps/plugins/pluginBlocked-16.png             (plugins/pluginBlocked-16-aero.png)
+        skin/classic/aero/mozapps/plugins/pluginOutdated-16.png            (plugins/pluginOutdated-16-aero.png)
         skin/classic/aero/mozapps/plugins/pluginInstallerWizard.css        (plugins/pluginInstallerWizard.css)
         skin/classic/aero/mozapps/profile/profileicon.png                  (profile/profileicon-aero.png)
         skin/classic/aero/mozapps/profile/profileSelection.css             (profile/profileSelection.css)
         skin/classic/aero/mozapps/update/downloadButtons.png               (update/downloadButtons-aero.png)
         skin/classic/aero/mozapps/update/update.png                        (update/update-aero.png)
         skin/classic/aero/mozapps/update/updates.css                       (update/updates.css)
         skin/classic/aero/mozapps/viewsource/viewsource.css                (viewsource/viewsource.css)
 *       skin/classic/aero/mozapps/xpinstall/xpinstallConfirm.css           (xpinstall/xpinstallConfirm.css)
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..4a66446e6256df3d7a836c2633a824ec0f8253b9
GIT binary patch
literal 638
zc$@)#0)hRBP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV0000PbVXQnQ*UN;
zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!AW1|)RCwB?Q%g%*K@|SxzH-}MW2q>O
z(MXImLco$mCG-afT?qLTZt7#NOIO_%gjUp@;6Awa2M8`x5lXe%iW>SLB%!&l={e)Q
zx3`tjMGwrKbI+XbJKvm{5mHM0j|nE|pPwgz_e)E!{dW7Mk<GrD+Spk6TZ}DJDEuNg
zSy-q`&wJHBJuU0mEP{3$&g`s8>>M5<j%A8PSLpgmd1q&n35=Lcx9vJiv#d=|L;61K
z@o@!Udr+w$==WibjzZIQ3!3(V!kfI75D42Yb&ihkg+Px?6F07lF(P66ba8<Z%feUB
z!w@OS)@r#JrzHv;r<9=zy)eYPUJu+_E0<G)eU9-O=NN;7r5KkexK}LZXyDo0+?`I(
zRI3O&9fU*@k_xZY8oEPpwY8;Y83go^2&h^p1VZvdiu24$eD-N=GQ9=vCIc0iw=*z+
z!s;q&%_g27A7gTD4VvR9@Sm0_2qG|BesK|F%gf1sfqakqu)eNTq%WT$s0vYeo6n<5
z$Hz2Iy^hB4Fg!~1qvR)zhH4_)eNDs6#6;yp%BCXtNHGSts6kBd;p`0GQi>4m`$t4(
z(tJsnL;;0$26v+hH&pHE!NG3?*L!;iA_7)MKs_O#T85jF`<arNYGi(1{kQh_ak;yj
z5V*Iqk^TtYS4@Cz7uVa{YNT(RM#mcWfIN}BMj^!Ghd|lCmG!UpEvR7d9iSe{tDgc4
Y063Ecv)0^v!~g&Q07*qoM6N<$f~!j=5C8xG
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..e5a5bc54c2402dca2724a7000227dbafff9a1516
GIT binary patch
literal 699
zc$@*d0!00ZP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV0000PbVXQnQ*UN;
zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!T}ebiRCwB?Q$1)CVHkeCyGt&AYMa!O
zM1x|qQ6p%p)`~bexkx+dpo7xI4y7On8H%7t6cH>=MR0I%ans!`LsfLqK(s|mp_NFj
zqPffc`@U!HG!=31<-Yrp_wRY$=lf20o`?T2g<orpdgy50sm?fB>y(^GKIxyTO#aQs
z)?3;O!3?;;N_Bb^mU%;OyeTQ0(y+gNM(Xe}Kk@tX8UkOuR5lTbPUb4tX1Re9bdxu1
z9Cfu;DLFI(&$bX1JK(vlA4}{%1Q3v_48U{jsEEZ+l6y8Bflb8YMf?3czPx;fcxE$<
z)g}_f0Y9cKyoH+Shrap=JwwAt3=ZdlIw=&RCtZx>3vdbP!^$!=(l)xK;4l8oK8|sY
zluQ-7MD!U*QZBY<AGYyYr}VBP$GiS}MmzIIgSE$xAY_^5x8YePJk#QN;Ti@&yJs5E
z92W+$XwiOR>`eCvBn(Jh2t|R?+lQ@F6%5wuIQU`#h09lfWD3EJiLAc|z7Z;$YnsTN
zpFsA)L>S6nyabC-Utg`jrjya!Uzg8kaCCRMEX5K&eL;=-#!h2yXBu<cQ>f*mc$$qM
ze|9`9RwRJ~R1-2-q@xC(xzUDgm>r>XAJwFSb_bD*4IY>FWv&StP*`@&HeAbQh0%O?
zA0tb8m-6+Qo3MC9!f}YV>_43b4(&|e(KR1=>FzC<gq}Y)&Ul!Aa1XC;U#Af$my(U&
z^b1~s)t6Kie0jarH!N1C@s1WZ(g{`~+A1Miw34KOfG7OZ<Bv%QkyU|`^OM%UzHdQE
h4zXxZ?Z+Pi3;=ZS6<8#Gc}f5P002ovPDHLkV1kheJ{kZ3
--- a/xpcom/system/nsIBlocklistService.idl
+++ b/xpcom/system/nsIBlocklistService.idl
@@ -46,16 +46,19 @@ interface nsIBlocklistService : nsISuppo
 {
   // 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;
+  // Indicates that the item is considered outdated, and there is a known
+  // update available.
+  const unsigned long STATE_OUTDATED    = 3;
 
   /**
    * Determine if an item is blocklisted
    * @param   id
    *          The ID of the item.
    * @param   version
    *          The item's version.
    * @param   appVersion