Bug 297903: Extension updates should link to further information. r=robstrong
authordtownsend@oxymoronical.com
Wed, 05 Sep 2007 18:20:11 -0700
changeset 5737 0fb492b91c5aa469ce154c438edb634d29bfab27
parent 5736 f1a66a54c7c317496e3e17d2baf42ab877621888
child 5738 ccee73bb247390a35aa29eca894069b5afcb0d40
push idunknown
push userunknown
push dateunknown
reviewersrobstrong
bugs297903
milestone1.9a8pre
Bug 297903: Extension updates should link to further information. r=robstrong
toolkit/locales/en-US/chrome/mozapps/extensions/extensions.dtd
toolkit/mozapps/extensions/content/extensions.js
toolkit/mozapps/extensions/content/extensions.xml
toolkit/mozapps/extensions/content/extensions.xul
toolkit/mozapps/extensions/content/updateinfo.xsl
toolkit/mozapps/extensions/jar.mn
toolkit/mozapps/extensions/src/nsExtensionManager.js.in
toolkit/themes/pinstripe/mozapps/extensions/extensions.css
toolkit/themes/winstripe/mozapps/extensions/extensions.css
--- a/toolkit/locales/en-US/chrome/mozapps/extensions/extensions.dtd
+++ b/toolkit/locales/en-US/chrome/mozapps/extensions/extensions.dtd
@@ -71,16 +71,22 @@
 <!ENTITY cmd.cancelInstall.accesskey      "C">
 <!ENTITY cmd.cancelInstall.tooltip        "Cancel the install of this Add-on">
 <!ENTITY cmd.cancelUpgrade.label          "Cancel Upgrade">
 <!ENTITY cmd.cancelUpgrade.accesskey      "C">
 <!ENTITY cmd.cancelUpgrade.tooltip        "Cancel the upgrade of this Add-on">
 <!ENTITY cmd.installUpdate.label          "Install Update">
 <!ENTITY cmd.installUpdate.accesskey      "I">
 <!ENTITY cmd.installUpdate.tooltip        "Install an update for this Add-on">
+<!ENTITY cmd.showUpdateInfo.label         "Show Information">
+<!ENTITY cmd.showUpdateInfo.accesskey     "S">
+<!ENTITY cmd.showUpdateInfo.tooltip       "Show more information about these updates">
+<!ENTITY cmd.hideUpdateInfo.label         "Hide Information">
+<!ENTITY cmd.hideUpdateInfo.accesskey     "H">
+<!ENTITY cmd.hideUpdateInfo.tooltip       "Hide information about these updates">
 <!-- The selected add-on's cancel action button label -->
 <!ENTITY cancel.label                     "Cancel">
 <!ENTITY cancel.accesskey                 "C">
 <!ENTITY cancelInstall.label              "Cancel">
 <!ENTITY cancelInstall.accesskey          "C">
 <!ENTITY cancelUpgrade.label              "Cancel">
 <!ENTITY cancelUpgrade.accesskey          "C">
 
@@ -109,16 +115,19 @@
 <!ENTITY toBeUpdated.label                "This add-on will be updated when &brandShortName; is restarted.">
 
 <!ENTITY getExtensions.label              "Get Extensions">
 <!ENTITY getThemes.label                  "Get Themes">
 <!ENTITY getPlugins.label                 "Get Plugins">
 <!ENTITY previewNoThemeSelected.label     "No Theme Selected">
 <!ENTITY previewNoPreviewImage.label      "This Theme does not have a Preview Image">
 <!ENTITY moreInfo.label                   "More Information">
+<!ENTITY infoNoAddonSelected.label        "No Update Selected">
+<!ENTITY infoNoUpdateInfo.label           "This update does not have any additional information">
+<!ENTITY infoUpdateInfoError.label        "There was an error loading the information about this update">
 
 <!ENTITY updateSuccess.label              "Update completed successfully.">
 <!ENTITY installSuccess.label             "Install completed successfully.">
 <!ENTITY installSuccessRestart.label      "Restart to complete the installation.">
 <!ENTITY updateSuccessRestart.label       "Restart to complete the update.">
 <!ENTITY installWaiting.label             "Waiting...">
 <!ENTITY installIncompatibleUpdate.label  "Checking compatibility...">
 <!ENTITY installFinishing.label           "Installing...">
--- a/toolkit/mozapps/extensions/content/extensions.js
+++ b/toolkit/mozapps/extensions/content/extensions.js
@@ -17,17 +17,18 @@
 # The Initial Developer of the Original Code is Ben Goodger.
 # Portions created by the Initial Developer are Copyright (C) 2004
 # the Initial Developer. All Rights Reserved.
 #
 # Contributor(s):
 #   Ben Goodger <ben@mozilla.org>
 #   Robert Strong <robert.bugzilla@gmail.com>
 #   Dão Gottwald <dao@design-noir.de>
-#
+#   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
@@ -73,20 +74,21 @@ const PREF_EXTENSIONS_GETMOREEXTENSIONSU
 const PREF_EXTENSIONS_GETMOREPLUGINSURL     = "extensions.getMorePluginsURL";
 const PREF_EXTENSIONS_DSS_ENABLED           = "extensions.dss.enabled";
 const PREF_EXTENSIONS_DSS_SWITCHPENDING     = "extensions.dss.switchPending";
 const PREF_EXTENSIONS_HIDE_INSTALL_BTN      = "extensions.hideInstallButton";
 const PREF_EM_LAST_SELECTED_SKIN            = "extensions.lastSelectedSkin";
 const PREF_GENERAL_SKINS_SELECTEDSKIN       = "general.skins.selectedSkin";
 const PREF_UPDATE_NOTIFYUSER                = "extensions.update.notifyUser";
 
-const RDFURI_ITEM_ROOT  = "urn:mozilla:item:root";
-const PREFIX_ITEM_URI   = "urn:mozilla:item:";
-const PREFIX_NS_EM      = "http://www.mozilla.org/2004/em-rdf#";
-const kXULNSURI         = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+const RDFURI_ITEM_ROOT    = "urn:mozilla:item:root";
+const PREFIX_ITEM_URI     = "urn:mozilla:item:";
+const PREFIX_NS_EM        = "http://www.mozilla.org/2004/em-rdf#";
+const kXULNSURI           = "http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul";
+const XMLURI_PARSE_ERROR  = "http://www.mozilla.org/newlayout/xml/parsererror.xml"
 
 const OP_NONE                         = "";
 const OP_NEEDS_INSTALL                = "needs-install";
 const OP_NEEDS_UPGRADE                = "needs-upgrade";
 const OP_NEEDS_UNINSTALL              = "needs-uninstall";
 const OP_NEEDS_ENABLE                 = "needs-enable";
 const OP_NEEDS_DISABLE                = "needs-disable";
 
@@ -310,16 +312,17 @@ function showView(aView) {
       showInstallUpdatesAll = true;
       if (gUpdatesOnly) {
         showSkip = true;
         showRestartApp = false;
       }
       bindingList = [ ["aboutURL", "?aboutURL"],
                       ["availableUpdateURL", "?availableUpdateURL"],
                       ["availableUpdateVersion", "?availableUpdateVersion"],
+                      ["availableUpdateInfo", "?availableUpdateInfo"],
                       ["blocklisted", "?blocklisted"],
                       ["homepageURL", "?homepageURL"],
                       ["iconURL", "?iconURL"],
                       ["internalName", "?internalName"],
                       ["locked", "?locked"],
                       ["name", "?name"],
                       ["opType", "?opType"],
                       ["previewImage", "?previewImage"],
@@ -401,16 +404,18 @@ function showView(aView) {
   document.getElementById("installFileButton").hidden = !showInstallFile;
   document.getElementById("checkUpdatesAllButton").hidden = !showCheckUpdatesAll;
   document.getElementById("installUpdatesAllButton").hidden = !showInstallUpdatesAll;
   document.getElementById("restartAppButton").hidden = !showRestartApp;
   document.getElementById("skipDialogButton").hidden = !showSkip;
   document.getElementById("continueDialogButton").hidden = !showContinue;
   document.getElementById("themePreviewArea").hidden = !isThemes;
   document.getElementById("themeSplitter").hidden = !isThemes;
+  document.getElementById("showUpdateInfoButton").hidden = aView != "updates";
+  document.getElementById("hideUpdateInfoButton").hidden = true;
 
   AddonsViewBuilder.updateView(types, "richlistitem", bindingList, null);
 
   if (aView == "updates" || aView == "installs")
     gExtensionsView.selectedItem = gExtensionsView.children[0];
 
   if (showSkip) {
     var button = document.getElementById("installUpdatesAllButton");
@@ -1106,34 +1111,156 @@ function onViewDoubleClick(aEvent)
 function onAddonSelect(aEvent)
 {
   var viewButton = document.getElementById("viewGroup").selectedItem;
   if (viewButton.hasAttribute("persist") && gExtensionsView.selectedItem)
     viewButton.setAttribute("last-selected", gExtensionsView.selectedItem.id);
 
   if (!document.getElementById("themePreviewArea").hidden) {
     var previewImageDeck = document.getElementById("previewImageDeck");
-    var previewImage = document.getElementById("previewImage");
-    if (!gExtensionsView.selectedItem) {
-      previewImageDeck.setAttribute("selectedIndex", "0");
-      if (previewImage.hasAttribute("src"))
-        previewImage.removeAttribute("src");
-    }
-    else {
-      var url = gExtensionsView.selectedItem.getAttribute("previewImage");
-      if (url) {
-        previewImageDeck.setAttribute("selectedIndex", "2");
-        previewImage.setAttribute("src", url);
-      }
-      else {
-        previewImageDeck.setAttribute("selectedIndex", "1");
+    if (gView == "themes") {
+      var previewImage = document.getElementById("previewImage");
+      if (!gExtensionsView.selectedItem) {
+        previewImageDeck.selectedIndex = 0;
         if (previewImage.hasAttribute("src"))
           previewImage.removeAttribute("src");
       }
+      else {
+        var url = gExtensionsView.selectedItem.getAttribute("previewImage");
+        if (url) {
+          previewImageDeck.selectedIndex = 2;
+          previewImage.setAttribute("src", url);
+        }
+        else {
+          previewImageDeck.selectedIndex = 1;
+          if (previewImage.hasAttribute("src"))
+            previewImage.removeAttribute("src");
+        }
+      }
     }
+    else if (gView == "updates") {
+      UpdateInfoLoader.cancelLoad();
+      if (!gExtensionsView.selectedItem)
+        previewImageDeck.selectedIndex = 3;
+      else if (!gExtensionsView.selectedItem.hasAttribute("availableUpdateInfo"))
+        previewImageDeck.selectedIndex = 4;
+      else
+        UpdateInfoLoader.loadInfo(gExtensionsView.selectedItem.getAttribute("availableUpdateInfo"));
+    }
+  }
+}
+
+/**
+ * Manages the retrieval of update information and the xsl stylesheet
+ * used to format the inforation into chrome.
+ */
+var UpdateInfoLoader = {
+  _stylesheet: null,
+  _styleRequest: null,
+  _infoDocument: null,
+  _infoRequest: null,
+  
+  // Called once both stylesheet and info requests have completed
+  displayInfo: function()
+  {
+    var processor = Components.classes["@mozilla.org/document-transformer;1?type=xslt"]
+                              .createInstance(Components.interfaces.nsIXSLTProcessor);
+    processor.flags |= Components.interfaces.nsIXSLTProcessorPrivate.DISABLE_ALL_LOADS;
+    
+    processor.importStylesheet(this._stylesheet);
+    var fragment = processor.transformToFragment(this._infoDocument, document);
+    document.getElementById("infoDisplay").appendChild(fragment);
+    document.getElementById("previewImageDeck").selectedIndex = 7;
+  },
+  
+  onStylesheetLoaded: function(event)
+  {
+    var request = event.target;
+    this._styleRequest = null;
+    this._stylesheet = request.responseXML;
+
+    if (!this._stylesheet ||
+        this._stylesheet.documentElement.namespaceURI == XMLURI_PARSE_ERROR ||
+        (request.status != 200 && request.status != 0)) {
+      // The stylesheet load failing is a bad sign
+      document.getElementById("previewImageDeck").selectedIndex = 6;
+      return;
+    }
+
+    if (this._infoDocument)
+      this.displayInfo();
+  },
+  
+  onInfoLoaded: function(event)
+  {
+    var request = event.target;
+    this._infoRequest = null;
+    this._infoDocument = request.responseXML;
+    
+    if (!this._infoDocument ||
+        this._infoDocument.documentElement.namespaceURI == XMLURI_PARSE_ERROR ||
+        (request.status != 200 && request.status != 0)) {
+      // Should attempt to parse request.responseText with the html parser
+      document.getElementById("previewImageDeck").selectedIndex = 6;
+      return;
+    }
+
+    if (this._stylesheet)
+      this.displayInfo();
+  },
+  
+  onError: function(event)
+  {
+    if (event.request == this._infoRequest)
+      this._infoRequest = null;
+    else // Means the stylesheet load has failed which is pretty bad news
+      this.cancelRequest();
+
+    document.getElementById("previewImageDeck").selectedIndex = 6;
+  },
+  
+  loadInfo: function(url)
+  {
+    this.cancelLoad();
+    this._infoDocument = null;
+    document.getElementById("previewImageDeck").selectedIndex = 5;
+    var display = document.getElementById("infoDisplay");
+    while (display.lastChild)
+      display.removeChild(display.lastChild);
+
+    this._infoRequest = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]
+                                  .createInstance(Components.interfaces.nsIXMLHttpRequest);
+    this._infoRequest.open("GET", url, true);
+    
+    var self = this;
+    this._infoRequest.onerror = function(event) { self.onError(event); };
+    this._infoRequest.onload = function(event) { self.onInfoLoaded(event); };
+    this._infoRequest.send(null);
+
+    // We may have the stylesheet cached from a previous load, or may still be
+    // loading it.
+    if (this._stylesheet || this._styleRequest)
+      return;
+
+    this._styleRequest = Components.classes["@mozilla.org/xmlextras/xmlhttprequest;1"]
+                                   .createInstance(Components.interfaces.nsIXMLHttpRequest);
+    this._styleRequest.open("GET", "chrome://mozapps/content/extensions/updateinfo.xsl", true);
+    this._styleRequest.overrideMimeType("text/xml");
+    
+    this._styleRequest.onerror = function(event) { self.onError(event); };
+    this._styleRequest.onload = function(event) { self.onStylesheetLoaded(event); };
+    this._styleRequest.send(null);
+  },
+  
+  cancelLoad: function()
+  {
+    // Leave the stylesheet loader running, there's a good chance we'll need it
+    if (this._infoRequest)
+      this._infoRequest.abort();
+    this._infoRequest = null;
   }
 }
 
 ///////////////////////////////////////////////////////////////////////////////
 // View Context Menus
 var gAddonContextMenus = ["menuitem_useTheme", "menuitem_options", "menuitem_homepage",
                           "menuitem_about",  "menuseparator_1", "menuitem_uninstall",
                           "menuitem_cancelUninstall", "menuitem_cancelInstall",
@@ -1526,16 +1653,34 @@ function updateGlobalCommands() {
     }
   }
   setElementDisabledByID("cmd_checkUpdatesAll", disableUpdateCheck);
   setElementDisabledByID("cmd_installUpdatesAll", disableInstallUpdate);
   setElementDisabledByID("cmd_restartApp", disableAppRestart);
   setElementDisabledByID("cmd_installFile", disableInstallFile);
 }
 
+function showUpdateInfo()
+{
+  document.getElementById("themePreviewArea").hidden = false;
+  document.getElementById("themeSplitter").hidden = false;
+  document.getElementById("showUpdateInfoButton").hidden = true;
+  document.getElementById("hideUpdateInfoButton").hidden = false;
+  onAddonSelect();
+}
+
+function hideUpdateInfo()
+{
+  UpdateInfoLoader.cancelLoad();
+  document.getElementById("themePreviewArea").hidden = true;
+  document.getElementById("themeSplitter").hidden = true;
+  document.getElementById("showUpdateInfoButton").hidden = false;
+  document.getElementById("hideUpdateInfoButton").hidden = true;
+}
+
 function checkUpdatesAll() {
   if (isOffline("offlineUpdateMsg"))
     return;
 
   if (!isXPInstallEnabled())
     return;
 
   // To support custom views we check the add-ons displayed in the list
--- a/toolkit/mozapps/extensions/content/extensions.xml
+++ b/toolkit/mozapps/extensions/content/extensions.xml
@@ -476,22 +476,22 @@
         <xul:vbox pack="start" align="start">
           <xul:image class="addonIcon" xbl:inherits="src=iconURL"/>
         </xul:vbox>
         <xul:vbox flex="1">
           <xul:hbox class="addon-name-version" xbl:inherits="name, version"/>
           <xul:hbox>
             <xul:description xbl:inherits="value=updateAvailableMsg" flex="1" crop="end"/>
           </xul:hbox>
+          <xul:hbox pack="end" align="end">
+            <xul:checkbox anonid="includeUpdate" class="includeUpdate" checked="true"
+            label="&includeUpdate.label;" tooltiptext="&includeUpdate.tooltip;"
+            includeUpdateAccesskey="&includeUpdate.accesskey;" accesskey="&includeUpdate.accesskey;"/>
+          </xul:hbox>
         </xul:vbox>
-        <xul:hbox pack="end" align="end">
-          <xul:checkbox anonid="includeUpdate" class="includeUpdate" checked="true"
-          label="&includeUpdate.label;" tooltiptext="&includeUpdate.tooltip;"
-          includeUpdateAccesskey="&includeUpdate.accesskey;" accesskey="&includeUpdate.accesskey;"/>
-        </xul:hbox>
       </xul:hbox>
     </content>
 
     <implementation>
       <constructor>
         var updatedVersion = this.getAttribute('availableUpdateVersion');
         var msg = this.parentNode._addonStrings.getFormattedString("updateAvailableMsg", [updatedVersion]);
         this.setAttribute("updateAvailableMsg", msg);
--- a/toolkit/mozapps/extensions/content/extensions.xul
+++ b/toolkit/mozapps/extensions/content/extensions.xul
@@ -105,16 +105,18 @@
   
   <commandset id="globalCommands">
     <command id="cmd_installFile" oncommand="installWithFilePicker();"/>
     <command id="cmd_checkUpdatesAll" oncommand="checkUpdatesAll();"/>
     <command id="cmd_installUpdatesAll" oncommand="installUpdatesAll();"/>
     <command id="cmd_restartApp" oncommand="restartApp();"/>
     <command id="cmd_continue" oncommand="closeEM();" disabled="true"/>
     <command id="cmd_close" oncommand="closeEM();"/>
+    <command id="cmd_showUpdateInfo" oncommand="showUpdateInfo();"/>
+    <command id="cmd_hideUpdateInfo" oncommand="hideUpdateInfo();"/>
   </commandset>
 
   <vbox id="addonContextMenuPalette" hidden="true">
     <menuitem id="menuitem_useTheme" default="true" command="cmd_useTheme"
               label="&cmd.useTheme.label;" accesskey="&cmd.useTheme.accesskey;"/>
     <menuitem id="menuitem_options" default="true" command="cmd_options"
 #ifdef XP_WIN
               label="&cmd.options.label;" accesskey="&cmd.options.accesskey;"/>
@@ -188,31 +190,53 @@
           <vbox id="noPreviewImage" pack="center" align="center">
             <label class="previewText">&previewNoPreviewImage.label;</label>
           </vbox>
           <vbox id="previewImageContainer" align="center" pack="center">
             <description>
               <image id="previewImage"/>
             </description>
           </vbox>
+          <vbox id="infoNoAddonSelected" align="center" pack="center">
+            <label class="previewText">&infoNoAddonSelected.label;</label>
+          </vbox>
+          <vbox id="infoNoUpdateInfo" align="center" pack="center">
+            <label class="previewText">&infoNoUpdateInfo.label;</label>
+          </vbox>
+          <vbox id="infoLoadingInfo" align="center" pack="center">
+            <image class="addonThrobber"/>
+          </vbox>
+          <vbox id="infoUpdateInfoError" align="center" pack="center">
+            <label class="previewText">&infoUpdateInfoError.label;</label>
+          </vbox>
+          <vbox id="infoDisplay">
+          </vbox>
         </deck>
       </vbox>
     </hbox>
   </notificationbox>
   <vbox>
     <hbox id="commandBarBottom" align="center">
       <button id="installFileButton" label="&cmd.installLocalFile.label;"
               accesskey="&cmd.installFile.accesskey;"
               tooltiptextaddons="&cmd.installFileAddon.tooltip;"
               tooltiptextthemes="&cmd.installFileTheme.tooltip;"
               command="cmd_installFile"/>
       <button id="installUpdatesAllButton" label="&cmd.installUpdatesAll.label;"
               accesskey="&cmd.installUpdatesAll.accesskey;"
               tooltiptext="&cmd.installUpdatesAll.tooltip;"
               command="cmd_installUpdatesAll"/>
+      <button id="showUpdateInfoButton" label="&cmd.showUpdateInfo.label;"
+              accesskey="&cmd.showUpdateInfo.accesskey;"
+              tooltiptext="&cmd.showUpdateInfo.tooltip;"
+              command="cmd_showUpdateInfo"/>
+      <button id="hideUpdateInfoButton" label="&cmd.hideUpdateInfo.label;"
+              accesskey="&cmd.hideUpdateInfo.accesskey;"
+              tooltiptext="&cmd.hideUpdateInfo.tooltip;"
+              command="cmd_hideUpdateInfo"/>
       <button id="checkUpdatesAllButton" label="&cmd.checkUpdatesAll.label;"
               accesskey="&cmd.checkUpdatesAll.accesskey;"
               tooltiptextaddons="&cmd.checkUpdatesAllAddon.tooltip;"
               tooltiptextthemes="&cmd.checkUpdatesAllTheme.tooltip;"
               command="cmd_checkUpdatesAll"/>
       <spacer flex="1"/>
       <label id="getMore" class="text-link"
              onclick="openURL(this.getAttribute('getMoreURL'));"
new file mode 100644
--- /dev/null
+++ b/toolkit/mozapps/extensions/content/updateinfo.xsl
@@ -0,0 +1,38 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<xsl:stylesheet version="1.0" xmlns:xhtml="http://www.w3.org/1999/xhtml"
+                              xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
+
+  <xsl:template match="text()">
+  </xsl:template>
+  
+  <xsl:template match="text()" mode="list">
+  </xsl:template>
+  
+  <xsl:template match="text()" mode="text">
+    <xsl:value-of select="."/>
+  </xsl:template>
+  
+  <xsl:template match="xhtml:script" mode="text">
+  </xsl:template>
+  
+  <xsl:template match="xhtml:b|xhtml:i|xhtml:em|xhtml:strong" mode="text">
+    <xsl:copy><xsl:apply-templates mode="text"/></xsl:copy>
+  </xsl:template>
+  
+  <xsl:template match="xhtml:h1|xhtml:h2|xhtml:h3|xhtml:p">
+    <xsl:copy><xsl:apply-templates mode="text"/></xsl:copy>
+  </xsl:template>
+
+  <xsl:template match="xhtml:li" mode="list">
+    <xsl:copy><xsl:apply-templates mode="text"/></xsl:copy>
+  </xsl:template>
+
+  <xsl:template match="xhtml:ul|xhtml:ol">
+    <xsl:copy><xsl:apply-templates mode="list"/></xsl:copy>
+  </xsl:template>
+  
+  <xsl:template match="/">
+    <xhtml:body><xsl:apply-templates/></xhtml:body>
+  </xsl:template>
+  
+</xsl:stylesheet>
--- a/toolkit/mozapps/extensions/jar.mn
+++ b/toolkit/mozapps/extensions/jar.mn
@@ -1,13 +1,14 @@
 toolkit.jar:
 % content mozapps %content/mozapps/ xpcnativewrappers=yes
 * content/mozapps/extensions/extensions.xul                     (content/extensions.xul)
 * 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/update.xul                         (content/update.xul)
 * content/mozapps/extensions/update.js                          (content/update.js)
 
--- a/toolkit/mozapps/extensions/src/nsExtensionManager.js.in
+++ b/toolkit/mozapps/extensions/src/nsExtensionManager.js.in
@@ -4475,17 +4475,18 @@ ExtensionManager.prototype = {
 
     var props = { name            : name,
                   version         : EM_L(getManifestProperty(installManifest, "version")),
                   newVersion      : EM_L(getManifestProperty(installManifest, "version")),
                   installLocation : EM_L(installLocation.name),
                   type            : EM_I(type),
                   availableUpdateURL    : null,
                   availableUpdateHash   : null,
-                  availableUpdateVersion: null };
+                  availableUpdateVersion: null,
+                  availableUpdateInfo   : null };
     for (var p in props)
       ds.setItemProperty(id, EM_R(p), props[p]);
     ds.updateProperty(id, "availableUpdateURL");
 
     this._setOp(id, OP_NEEDS_INSTALL);
 
     // Insert it into the child list NOW rather than later because:
     // - extensions installed using the command line need to be a member
@@ -4515,17 +4516,18 @@ ExtensionManager.prototype = {
     // They will be reset as appropriate by the upgrade/install process.
     var ds = this.datasource;
     ds.updateVisibleList(id, installLocation.name, false);
     var props = { installLocation : EM_L(installLocation.name),
                   type            : EM_I(type),
                   newVersion      : EM_L(getManifestProperty(installManifest, "version")),
                   availableUpdateURL      : null,
                   availableUpdateHash     : null,
-                  availableUpdateVersion  : null };
+                  availableUpdateVersion  : null,
+                  availableUpdateInfo     : null };
     for (var p in props)
       ds.setItemProperty(id, EM_R(p), props[p]);
     ds.updateProperty(id, "availableUpdateURL");
 
     this._setOp(id, OP_NEEDS_UPGRADE);
     this._notifyAction(id, EM_ITEM_UPGRADED);
   },
 
@@ -5387,16 +5389,17 @@ ExtensionManager.prototype = {
       hashes.push(currItem.xpiHash ? currItem.xpiHash : null);
       // if this is an update remove the update metadata to prevent it from
       // being updated during an install.
       if (fromChrome) {
         var id = currItem.id
         ds.setItemProperty(id, EM_R("availableUpdateURL"), null);
         ds.setItemProperty(id, EM_R("availableUpdateHash"), null);
         ds.setItemProperty(id, EM_R("availableUpdateVersion"), null);
+        ds.setItemProperty(id, EM_R("availableUpdateInfo"), null);
         ds.updateProperty(id, "availableUpdateURL");
         ds.updateProperty(id, "updateable");
       }
       var id = fromChrome ? PREFIX_ITEM_URI + currItem.id : currItem.xpiURL;
       ds.updateDownloadState(id, "waiting");
     }
     this._transactions.push(txn);
 
@@ -6469,16 +6472,25 @@ RDFItemUpdater.prototype = {
                                  updateHash,
                                  "", /* Icon URL */
                                  "", /* RDF Update URL */
                                  "", /* Update Key */
                                  aLocalItem.type,
                                  appID);
 
       if (this._updater._isValidUpdate(aLocalItem, updatedItem)) {
+        if (aUpdateCheckType == nsIExtensionManager.UPDATE_CHECK_NEWVERSION) {
+          var infourl = this._getPropertyFromResource(aDataSource, targetApp,
+                                                      "updateInfoURL");
+          if (infourl)
+            infourl = EM_L(infourl);
+          this._updater._emDS.setItemProperty(aLocalItem.id,
+                                              EM_R("availableUpdateInfo"),
+                                              infourl);
+        }
         if (appID == this._updater._appID) {
           // App takes precedence over toolkit.  If we found the app, bail out.
           return updatedItem;
         }
         newestUpdateItem = updatedItem;
       }
     }
     return newestUpdateItem;
--- a/toolkit/themes/pinstripe/mozapps/extensions/extensions.css
+++ b/toolkit/themes/pinstripe/mozapps/extensions/extensions.css
@@ -279,8 +279,50 @@ radio#installs-view:hover, radio#install
 /* Update view checkbox */
 .includeUpdate {
   -moz-user-focus: none;
 }
 
 richlistitem[selected="true"] .includeUpdate {
   -moz-user-focus: normal;
 }
+
+#infoDisplay {
+  overflow-y: auto;
+}
+
+#infoDisplay body {
+  padding: 5px;
+}
+
+#infoDisplay h1,
+#infoDisplay h2,
+#infoDisplay h3 {
+  text-align: left;
+  font-weight: bold;
+  margin: 0 0 0.7em 0;
+}
+
+#infoDisplay h1 {
+  font-size: 150%;
+}
+
+#infoDisplay h2 {
+  font-size: 125%;
+}
+
+#infoDisplay h3 {
+  font-size: 100%;
+}
+
+#infoDisplay ol,
+#infoDisplay ul {
+  margin: 0 0 0.7em 0;
+}
+
+#infoDisplay li {
+  text-align: left;
+}
+
+#infoDisplay p {
+  text-align: justify;
+  margin: 0 0 0.7em 0;
+}
--- a/toolkit/themes/winstripe/mozapps/extensions/extensions.css
+++ b/toolkit/themes/winstripe/mozapps/extensions/extensions.css
@@ -43,17 +43,17 @@ margin-top: -12px;
   -moz-image-region: rect(0px, 84px, 21px, 63px);
 }
 
 #installFileButton[disabled="true"],
 #installUpdatesAllButton[disabled="true"] {
   -moz-image-region: rect(21px, 84px, 42px, 63px);
 }
 
-#checkUpdatesAllButton {
+#checkUpdatesAllButton, #showUpdateInfoButton, #hideUpdateInfoButton {
   -moz-image-region: rect(0px, 63px, 21px, 42px);
 }
 #checkUpdatesAllButton[disabled="true"] {
   -moz-image-region: rect(21px, 63px, 42px, 42px);
 }
 
 #restartAppButton {
   -moz-image-region: rect(0px, 42px, 21px, 21px);
@@ -346,8 +346,50 @@ radio#installs-view:hover, radio#install
 /* Update view checkbox */
 .includeUpdate {
   -moz-user-focus: none;
 }
 
 richlistitem[selected="true"] .includeUpdate {
   -moz-user-focus: normal;
 }
+
+#infoDisplay {
+  overflow-y: auto;
+}
+
+#infoDisplay body {
+  padding: 5px;
+}
+
+#infoDisplay h1,
+#infoDisplay h2,
+#infoDisplay h3 {
+  text-align: left;
+  font-weight: bold;
+  margin: 0 0 0.7em 0;
+}
+
+#infoDisplay h1 {
+  font-size: 150%;
+}
+
+#infoDisplay h2 {
+  font-size: 125%;
+}
+
+#infoDisplay h3 {
+  font-size: 100%;
+}
+
+#infoDisplay ol,
+#infoDisplay ul {
+  margin: 0 0 0.7em 0;
+}
+
+#infoDisplay li {
+  text-align: left;
+}
+
+#infoDisplay p {
+  text-align: justify;
+  margin: 0 0 0.7em 0;
+}