Bug 511593: Need to check for add-on updates, r=gavin
authorMark Finkle <mfinkle@mozilla.com>
Sun, 06 Sep 2009 21:08:06 -0400
changeset 65524 1fe0c59607f0e59b2a654cd97863889cf6612053
parent 65523 9caf823c6fbdf2479a25c4d74cd612f48cdd15b0
child 65525 0548b8947c0c4b334d4d1883d145a4e0a21a4d32
push id1
push userroot
push dateTue, 26 Apr 2011 22:38:44 +0000
treeherdermozilla-beta@bfdb6e623a36 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgavin
bugs511593
Bug 511593: Need to check for add-on updates, r=gavin
mobile/chrome/content/bindings/extensions.xml
mobile/chrome/content/browser.xul
mobile/chrome/content/extensions.js
mobile/locales/en-US/chrome/browser.dtd
mobile/locales/en-US/chrome/browser.properties
mobile/themes/hildon/platform.css
mobile/themes/wince/platform.css
--- a/mobile/chrome/content/bindings/extensions.xml
+++ b/mobile/chrome/content/bindings/extensions.xml
@@ -18,20 +18,21 @@
         <xul:image xbl:inherits="src=iconURL"/>
         <xul:vbox flex="1">
           <xul:hbox align="center">
             <xul:label class="title" xbl:inherits="value=name"/>
             <xul:label class="normal" xbl:inherits="value=version"/>
             <xul:spacer flex="1"/>
             <xul:label class="normal" crop="end" xbl:inherits="value=typeLabel"/>            
           </xul:hbox>
-          <xul:hbox align="center">
+          <xul:vbox>
             <xul:label class="normal hide-on-select" xbl:inherits="value=description" crop="end" flex="1"/>
             <xul:description class="normal show-on-select" xbl:inherits="xbl:text=description" flex="1"/>
-          </xul:hbox>
+            <xul:label class="normal-bold" xbl:inherits="value=updateStatus"/>            
+          </xul:vbox>
         </xul:vbox>
       </xul:hbox>
       <xul:hbox class="show-on-select buttons-box">
         <xul:button anonid="options-button" class="addon-options" label="&addonOptions.label;"
                     oncommand="document.getBindingParent(this).toggleOptions();"/>
         <xul:spacer flex="1"/>
         <xul:button anonid="enable-button" class="show-on-disable hide-on-enable hide-on-uninstall addon-enable" label="&addonEnable.label;"
                     oncommand="ExtensionsView.enable(document.getBindingParent(this));"/>
@@ -113,20 +114,20 @@
         <xul:image xbl:inherits="src=iconURL"/>
         <xul:vbox flex="1">
           <xul:hbox align="center">
             <xul:label class="title" xbl:inherits="value=name"/>
             <xul:label class="normal" xbl:inherits="value=version"/>
             <xul:spacer flex="1"/>
             <xul:hbox class="addon-type-or-rating" align="center" xbl:inherits="rating"/> 
           </xul:hbox>
-          <xul:hbox align="center">
+          <xul:vbox>
             <xul:label class="normal hide-on-select" xbl:inherits="value=description" crop="end" flex="1"/>
             <xul:description class="normal show-on-select" xbl:inherits="xbl:text=description" flex="1"/>
-          </xul:hbox>
+          </xul:vbox>
         </xul:vbox>
       </xul:hbox>
       <xul:vbox flex="1">
         <xul:hbox class="show-on-select">
           <xul:button class="addon-install hide-on-install hide-on-restart" label="&addonShowPage.label;"
                       oncommand="ExtensionsView.showPage(document.getBindingParent(this));"/>
           <xul:spacer flex="1"/>
           <xul:button class="addon-install hide-on-install hide-on-restart" label="&addonInstall2.label;"
--- a/mobile/chrome/content/browser.xul
+++ b/mobile/chrome/content/browser.xul
@@ -321,16 +321,18 @@
         <vbox id="addons-container" flex="1">
           <hbox id="addons-header" class="panel-header">
             <label value="&addonsHeader.label;"/>
           </hbox>
           <notificationbox id="addons-messages" flex="1">
             <richlistbox id="addons-list" flex="1" onselect="ExtensionsView.hideOptions()">
               <richlistitem id="addons-local" class="section-header" align="center">
                 <label value="&addonsLocal.label;" flex="1"/>
+                <spacer flex="1"/>
+                <button class="button-dark" label="&addonsUpdate.label;" oncommand="ExtensionsView.updateAll();"/>
               </richlistitem>
               <richlistitem id="addons-repo" class="section-header">
                 <label value="&addonsRepo.label;" flex="1"/>
                 <textbox id="addons-search-text" emptytext="&addonsSearch2.emptytext;" type="search" searchbutton="false"
                          oncommand="ExtensionsView.getAddonsFromRepo(this.value);"/>
               </richlistitem>
             </richlistbox>
           </notificationbox>
--- a/mobile/chrome/content/extensions.js
+++ b/mobile/chrome/content/extensions.js
@@ -55,17 +55,17 @@ var ExtensionsView = {
   _list: null,
   _localItem: null,
   _repoItem: null,
   _msg: null,
   _dloadmgr: null,
   _restartCount: 0,
   _observerIndex: -1,
 
-  _isXPInstallEnabled: function isXPInstallEnabled() {
+  _isXPInstallEnabled: function ev__isXPInstallEnabled() {
     let enabled = false;
     let locked = false;
     try {
       enabled = this._pref.getBoolPref("xpinstall.enabled");
       if (enabled)
         return true;
       locked = this._pref.prefIsLocked("xpinstall.enabled");
     }
@@ -302,23 +302,25 @@ var ExtensionsView = {
       let addon = items[i];
 
       // Some information is not directly accessible from the extmgr
       let isDisabled = this._getRDFProperty(addon.id, "isDisabled") == "true";
       let appDisabled = this._getRDFProperty(addon.id, "appDisabled");
       let desc = this._getRDFProperty(addon.id, "description");
       let optionsURL = this._getRDFProperty(addon.id, "optionsURL");
       let opType = this._getRDFProperty(addon.id, "opType");
+      let updateable = this._getRDFProperty(addon.id, "updateable");
 
       let listitem = this._createItem(addon, "local");
       listitem.setAttribute("isDisabled", isDisabled);
       listitem.setAttribute("appDisabled", appDisabled);
       listitem.setAttribute("description", desc);
       listitem.setAttribute("optionsURL", optionsURL);
       listitem.setAttribute("opType", opType);
+      listitem.setAttribute("updateable", updateable);
       this._list.insertBefore(listitem, this._repoItem);
     }
   },
 
   enable: function ev_enable(aItem) {
     let id = this._getIDFromURI(aItem.id);
     this._extmgr.enableItem(id);
 
@@ -505,16 +507,42 @@ var ExtensionsView = {
       BrowserUI.newTab(uri);
       BrowserUI.hidePanel();
     }
   },
 
   resetSearch: function ev_resetSearch() {
     document.getElementById("addons-search-text").value = "";
     this.getAddonsFromRepo("");
+  },
+  
+  updateAll: function ev_updateAll() {
+    if (!this._isXPInstallEnabled())
+      return;
+  
+    // To support custom views we check the add-ons displayed in the list
+    let items = [];
+    let start = this._localItem.nextSibling;
+    let end = this._repoItem;
+
+    while (start != end) {
+      if (start.getAttribute("updateable") != "false")
+        items.push(this._extmgr.getItemForID(start.getAttribute("addonID")));
+      start = start.nextSibling;
+    }
+  
+    if (items.length > 0) {
+      let listener = new UpdateCheckListener();
+      this._extmgr.update(items, items.length, Ci.nsIExtensionManager.UPDATE_CHECK_NEWVERSION, listener);
+    }
+    
+    if (this._list.selectedItem)
+      this._list.selectedItem.focus();
+  
+    this._pref.setBoolPref("extensions.update.notifyUser", false);
   }
 };
 
 ///////////////////////////////////////////////////////////////////////////////
 // nsIAddonSearchResultsCallback for the recommended search
 var RecommendedSearchResults = {
   cache: null,
 
@@ -626,16 +654,23 @@ XPInstallDownloadManager.prototype = {
     if (!ExtensionsView.visible)
       return;
 
     var element = ExtensionsView.getElementForAddon(aAddon.id);
     if (!element)
       return;
 
     element.setAttribute("status", (Components.isSuccessCode(aStatus) ? "success" : "fail"));
+    
+    // If we are updating an add-on, change the status
+    if (element.hasAttribute("updating")) {
+      let strings = document.getElementById("bundle_browser");
+      element.setAttribute("updateStatus", strings.getFormattedString("addonUpdate.updated", [aAddon.version]));
+      element.removeAttribute("updating");
+    }
   },
 
   onInstallsCompleted: function() {
     let strings = document.getElementById("bundle_browser");
 
     // If even one add-on succeeded, display the restart notif
     if (this._succeeded.length > 0)
       ExtensionsView.showRestart();
@@ -662,15 +697,103 @@ XPInstallDownloadManager.prototype = {
       element.setAttribute("opType", "needs-install");
     }
     var progress = Math.round((aValue / aMaxValue) * 100);
     element.setAttribute("progress", progress);
   },
 
   /////////////////////////////////////////////////////////////////////////////
   // nsISupports
-  QueryInterface: function (aIID) {
+  QueryInterface: function(aIID) {
     if (!aIID.equals(Ci.nsIAddonInstallListener) &&
         !aIID.equals(Ci.nsISupports))
       throw Components.results.NS_ERROR_NO_INTERFACE;
     return this;
   }
 };
+
+///////////////////////////////////////////////////////////////////////////////
+// Add-on update listener. Starts a download for any add-on with a viable
+// update waiting
+function UpdateCheckListener() {
+  this._addons = [];
+}
+
+UpdateCheckListener.prototype = {
+  /////////////////////////////////////////////////////////////////////////////
+  // nsIAddonUpdateCheckListener
+  onUpdateStarted: function ucl_onUpdateStarted() {
+  },
+
+  onUpdateEnded: function ucl_onUpdateEnded() {
+    if (!this._addons.length)
+      return;
+
+    // If we have some updateable add-ons, let's download them
+    let items = [];
+    for (let i = 0; i < this._addons.length; i++)
+      items.push(ExtensionsView._extmgr.getItemForID(this._addons[i]));
+
+    // Start the actual downloads
+    ExtensionsView._extmgr.addDownloads(items, items.length, null);
+  },
+
+  onAddonUpdateStarted: function ucl_onAddonUpdateStarted(aAddon) {
+    if (!document)
+      return;
+
+    let strings = document.getElementById("bundle_browser");
+    let element = document.getElementById(PREFIX_ITEM_URI + aAddon.id);
+    element.setAttribute("updateStatus", strings.getString("addonUpdate.checking"));
+  },
+
+  onAddonUpdateEnded: function ucl_onAddonUpdateEnded(aAddon, aStatus) {
+    if (!document)
+      return;
+    
+    let strings = document.getElementById("bundle_browser");
+    let element = document.getElementById(PREFIX_ITEM_URI + aAddon.id);
+    let updateable = false;
+    const nsIAUCL = Ci.nsIAddonUpdateCheckListener;
+    switch (aStatus) {
+      case nsIAUCL.STATUS_UPDATE:
+        var statusMsg = strings.getFormattedString("addonUpdate.updating", [aAddon.version]);
+        updateable = true;
+        break;
+      case nsIAUCL.STATUS_VERSIONINFO:
+        statusMsg = strings.getString("addonUpdate.compatibility");
+        break;
+      case nsIAUCL.STATUS_FAILURE:
+        statusMsg = strings.getString("addonUpdate.error");
+        break;
+      case nsIAUCL.STATUS_DISABLED:
+        statusMsg = strings.getString("addonUpdate.disabled");
+        break;
+      case nsIAUCL.STATUS_APP_MANAGED:
+      case nsIAUCL.STATUS_NO_UPDATE:
+        statusMsg = strings.getString("addonUpdate.noupdate");
+        break;
+      case nsIAUCL.STATUS_NOT_MANAGED:
+        statusMsg = strings.getString("addonUpdate.notsupported");
+        break;
+      case nsIAUCL.STATUS_READ_ONLY:
+        statusMsg = strings.getString("addonUpdate.notsupported");
+        break;
+      default:
+        statusMsg = strings.getString("addonUpdate.noupdate");
+    }
+    element.setAttribute("updateStatus", statusMsg);
+
+    // Save the add-on id if we can download an update
+    if (updateable) {
+      this._addons.push(aAddon.id);
+      element.setAttribute("updating", "true");
+    }
+  },
+
+  QueryInterface: function ucl_QueryInterface(aIID) {
+    if (!aIID.equals(Ci.nsIAddonUpdateCheckListener) &&
+        !aIID.equals(Ci.nsISupports))
+      throw Components.results.NS_ERROR_NO_INTERFACE;
+    return this;
+  }
+};
+
--- a/mobile/locales/en-US/chrome/browser.dtd
+++ b/mobile/locales/en-US/chrome/browser.dtd
@@ -38,16 +38,17 @@
 <!ENTITY findOnCmd.commandkey "f">
 <!ENTITY findAgainCmd.label  "Find Again">
 <!ENTITY findAgainCmd.accesskey "g">
 <!ENTITY findAgainCmd.commandkey "g">
 <!ENTITY findAgainCmd.commandkey2 "VK_F3">
 
 <!ENTITY addonsHeader.label        "Add-ons">
 <!ENTITY addonsLocal.label         "Your Add-ons">
+<!ENTITY addonsUpdate.label        "Update">
 <!ENTITY addonsRepo.label          "Get Add-ons">
 <!ENTITY addonsRecommended.label   "Recommended">
 <!ENTITY addonsSearch.label        "Search">
 <!ENTITY addonsSearch2.emptytext   "Search Catalog">
 <!ENTITY addonsSearch.recommended  "Recommended">
 
 <!ENTITY addonOptions.label        "Options">
 <!ENTITY addonEnable.label         "Enable">
--- a/mobile/locales/en-US/chrome/browser.properties
+++ b/mobile/locales/en-US/chrome/browser.properties
@@ -15,16 +15,25 @@ addonsSearchSuccess.button=Clear search
 
 addonsConfirmInstall.title=Installing Add-on
 addonsConfirmInstall.install=Install
 
 addonType.2=Extension
 addonType.4=Theme
 addonType.8=Locale
 
+addonUpdate.checking=Checking for updates…
+addonUpdate.updating=Updating to %S
+addonUpdate.updated=Updated to %S
+addonUpdate.compatibility=A compatibility update has been applied
+addonUpdate.noupdate=No updates were found
+addonUpdate.notsupported=Updates not supported
+addonUpdate.disabled=Updates are disabled
+addonUpdate.error=An error occurred
+
 # Download Manager
 # LOCALIZATION NOTE (Status): — is the "em dash" (long dash)
 # #1 download size for FINISHED or download state; #2 host (e.g., eTLD + 1, IP)
 downloadsStatus=#1 — #2
 downloadsUnknownSize=Unknown size
 # LOCALIZATION NOTE (KnownSize): #1 size number; #2 size unit
 downloadsKnownSize=#1 #2
 donwloadsYesterday=Yesterday
--- a/mobile/themes/hildon/platform.css
+++ b/mobile/themes/hildon/platform.css
@@ -299,16 +299,23 @@ richlistitem description.title {
 
 richlistitem label.normal,
 richlistitem description.normal {
   color: gray;
   font-size: 60% !important;
   white-space: pre-wrap;
 }
 
+richlistitem label.normal-bold,
+richlistitem description.normal-bold {
+  font-weight: bold;
+  font-size: 60% !important;
+  white-space: pre-wrap;
+}
+
 richlistitem[selected="true"] {
   color: black;
   background-color: white;
 }
 
 richlistitem.section-header,
 richlistitem[selected="true"].section-header {
   font-weight: bold;
--- a/mobile/themes/wince/platform.css
+++ b/mobile/themes/wince/platform.css
@@ -447,16 +447,23 @@ richlistitem description.title {
 
 richlistitem label.normal,
 richlistitem description.normal {
   color: gray;
   font-size: 8pt !important;
   white-space: pre-wrap;
 }
 
+richlistitem label.normal-bold,
+richlistitem description.normal-bold {
+  font-weight: bold;
+  font-size: 60% !important;
+  white-space: pre-wrap;
+}
+
 richlistitem[selected="true"] {
   color: black;
   background-color: white;
 }
 
 richlistitem.section-header,
 richlistitem[selected="true"].section-header {
   font-weight: bold;