Part of bug 571759 - Implement notifications for the revised add-on manager. r=sid0 ui-review=clarkbw
authorMark Banner <bugzilla@standard8.plus.com>
Fri, 14 Jan 2011 20:20:22 +0000
changeset 6957 ebb269ce8610a07f183f7f9cc30449fe486a695a
parent 6956 8aceaf24369903ac240048fa32e112d6ee3f4d6f
child 6958 95805deaae99505aad06c9191f1d42c4c02fb5f7
push idunknown
push userunknown
push dateunknown
reviewerssid0
bugs571759
Part of bug 571759 - Implement notifications for the revised add-on manager. r=sid0 ui-review=clarkbw
mail/base/content/specialTabs.js
mail/locales/en-US/chrome/messenger/messenger.properties
mail/test/mozmill/content-tabs/html/corrupt.xpi
mail/test/mozmill/content-tabs/html/installxpi.html
mail/test/mozmill/content-tabs/html/installxpi.xpi
mail/test/mozmill/content-tabs/test-install-xpi.js
mail/test/mozmill/shared-modules/test-content-tab-helpers.js
mail/themes/gnomestripe/jar.mn
mail/themes/gnomestripe/mail/icons/update.png
mail/themes/pinstripe/jar.mn
mail/themes/pinstripe/mail/icons/update.png
mail/themes/qute/jar.mn
mail/themes/qute/mail/icons/update.png
--- a/mail/base/content/specialTabs.js
+++ b/mail/base/content/specialTabs.js
@@ -32,16 +32,17 @@
  * 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 ***** */
 
 Components.utils.import("resource://gre/modules/XPCOMUtils.jsm");
 Components.utils.import("resource://gre/modules/Services.jsm");
+Components.utils.import("resource://gre/modules/AddonManager.jsm");
 
 function tabProgressListener(aTab, aStartsBlank) {
   this.mTab = aTab;
   this.mBrowser = aTab.browser;
   this.mBlank = aStartsBlank;
 }
 
 tabProgressListener.prototype =
@@ -174,19 +175,17 @@ var specialTabs = {
     // enable global history
     try {
       browser.docShell.QueryInterface(Components.interfaces.nsIDocShellHistory)
              .useGlobalHistory = true;
     } catch(ex) {
       Components.utils.reportError("Places database may be locked: " + ex);
     }
 
-    Components.classes["@mozilla.org/observer-service;1"]
-              .getService(Components.interfaces.nsIObserverService)
-              .addObserver(specialTabs, "mail-startup-done", false);
+    Services.obs.addObserver(specialTabs, "mail-startup-done", false);
 
     let tabmail = document.getElementById('tabmail');
 
     var prefs = Components.classes["@mozilla.org/preferences-service;1"]
                           .getService(Components.interfaces.nsIPrefBranch);
 
     tabmail.registerTabType(this.contentTabType);
     tabmail.registerTabType(this.chromeTabType);
@@ -848,96 +847,186 @@ var specialTabs = {
                                     aTab.closeListener, true);
     }
   },
 
   observe: function (aSubject, aTopic, aData) {
     if (aTopic != "mail-startup-done")
       return;
 
-    let obsService =
-      Components.classes["@mozilla.org/observer-service;1"]
-                .getService(Components.interfaces.nsIObserverService);
-
-    obsService.removeObserver(specialTabs, "mail-startup-done");
-    obsService.addObserver(this.xpInstallObserver, "xpinstall-install-blocked", false);
+    Services.obs.removeObserver(specialTabs, "mail-startup-done");
+    Services.obs.addObserver(this.xpInstallObserver, "addon-install-disabled",
+                             false);
+    Services.obs.addObserver(this.xpInstallObserver, "addon-install-blocked",
+                             false);
+    Services.obs.addObserver(this.xpInstallObserver, "addon-install-failed",
+                             false);
+    Services.obs.addObserver(this.xpInstallObserver, "addon-install-complete",
+                             false);
   },
 
   onunload: function () {
     window.removeEventListener("unload", specialTabs.onunload, false);
 
-    Components.classes["@mozilla.org/observer-service;1"]
-      .getService(Components.interfaces.nsIObserverService)
-      .removeObserver(specialTabs.xpInstallObserver, "xpinstall-install-blocked");
+    Services.obs.removeObserver(specialTabs.xpInstallObserver,
+                                "addon-install-disabled");
+    Services.obs.removeObserver(specialTabs.xpInstallObserver,
+                                "addon-install-blocked");
+    Services.obs.removeObserver(specialTabs.xpInstallObserver,
+                                "addon-install-failed");
+    Services.obs.removeObserver(specialTabs.xpInstallObserver,
+                                "addon-install-complete");
   },
 
   xpInstallObserver: {
-    get _prefService() {
-      delete this._prefService;
-      return this._prefService =
-        Components.classes["@mozilla.org/preferences-service;1"]
-                  .getService(Components.interfaces.nsIPrefBranch2);
-    },
-
     observe: function (aSubject, aTopic, aData) {
+      const Ci = Components.interfaces;
       let brandBundle = document.getElementById("bundle_brand");
       let messengerBundle = document.getElementById("bundle_messenger");
+
+      let installInfo = aSubject.QueryInterface(Ci.amIWebInstallInfo);
+      let win = installInfo.originatingWindow;
+      let notificationBox = getNotificationBox(win.top);
+      let notificationID = aTopic;
+      let brandShortName = brandBundle.getString("brandShortName");
+      let notificationName, messageString, buttons;
+      const iconURL = "chrome://messenger/skin/icons/update.png";
+
       switch (aTopic) {
-      case "xpinstall-install-blocked":
-        let installInfo =
-          aSubject.QueryInterface(Components.interfaces.nsIXPIInstallInfo);
-        let win = installInfo.originatingWindow;
-        let notificationBox = getNotificationBox(win.top);
-        if (notificationBox) {
-          let host = installInfo.originatingURI.host;
-          let brandShortName = brandBundle.getString("brandShortName");
-          let notificationName, messageString, buttons;
-          if (!this._prefService.getBoolPref("xpinstall.enabled")) {
-            notificationName = "xpinstall-disabled";
-            if (this._prefService.prefIsLocked("xpinstall.enabled")) {
-              messageString = messengerBundle.getString("xpinstallDisabledMessageLocked");
-              buttons = [];
+      case "addon-install-disabled":
+        notificationID = "xpinstall-disabled";
+
+        if (Services.prefs.prefIsLocked("xpinstall.enabled")) {
+          messageString = messengerBundle.getString("xpinstallDisabledMessageLocked");
+          buttons = [];
+        }
+        else {
+          messageString = messengerBundle.getString("xpinstallDisabledMessage");
+
+          buttons = [{
+            label: messengerBundle.getString("xpinstallDisabledButton"),
+            accessKey: messengerBundle.getString("xpinstallDisabledButton.accesskey"),
+            popup: null,
+            callback: function editPrefs() {
+              Services.prefs.setBoolPref("xpinstall.enabled", true);
+              return false;
             }
-            else {
-              messageString = messengerBundle.getString("xpinstallDisabledMessage");
+          }];
+        }
+        if (!notificationBox.getNotificationWithValue(notificationID)) {
+          notificationBox.appendNotification(messageString, notificationID,
+                                             iconURL,
+                                             notificationBox.PRIORITY_CRITICAL_HIGH,
+                                             buttons);
+        }
+        break;
+      case "addon-install-blocked":
+        messageString =
+          messengerBundle.getFormattedString("xpinstallPromptWarning",
+                                             [brandShortName, installInfo.originatingURI.host]);
 
-              buttons = [{
-                label: messengerBundle.getString("xpinstallDisabledButton"),
-                accessKey: messengerBundle.getString("xpinstallDisabledButton.accesskey"),
-                popup: null,
-                callback: function editPrefs() {
-                  specialTabs.xpInstallObserver
-                             ._prefService.setBoolPref("xpinstall.enabled", true);
-                  return false;
-                }
-              }];
-            }
+        buttons = [{
+          label: messengerBundle.getString("xpinstallPromptAllowButton"),
+          accessKey: messengerBundle.getString("xpinstallPromptAllowButton.accesskey"),
+          popup: null,
+          callback: function() {
+            installInfo.install();
           }
-          else {
-            notificationName = "xpinstall";
-            messageString = messengerBundle.getFormattedString("xpinstallPromptWarning",
-                                                               [brandShortName, host]);
+        }];
 
-            buttons = [{
-              label: messengerBundle.getString("xpinstallPromptAllowButton"),
-              accessKey: messengerBundle.getString("xpinstallPromptAllowButton.accesskey"),
-              popup: null,
-              callback: function() {
-                var mgr = Components.classes["@mozilla.org/xpinstall/install-manager;1"]
-                  .createInstance(Components.interfaces.nsIXPInstallManager);
-                mgr.initManagerWithInstallInfo(installInfo);
-                return false;
-              }
-            }];
+        if (!notificationBox.getNotificationWithValue(notificationName)) {
+            notificationBox.appendNotification(messageString, notificationName,
+                                               iconURL,
+                                               notificationBox.PRIORITY_MEDIUM_HIGH,
+                                               buttons);
           }
+        break;
+      case "addon-install-failed":
+        // XXX TODO This isn't terribly ideal for the multiple failure case
+        for ([, install] in Iterator(installInfo.installs)) {
+          let host = (installInfo.originatingURI instanceof Ci.nsIStandardURL) &&
+                      installInfo.originatingURI.host;
+          if (!host)
+            host = (install.sourceURI instanceof Ci.nsIStandardURL) &&
+                    install.sourceURI.host;
 
-          if (!notificationBox.getNotificationWithValue(notificationName)) {
-            const priority = notificationBox.PRIORITY_WARNING_MEDIUM;
-            const iconURL = "chrome://mozapps/skin/update/update.png";
-            notificationBox.appendNotification(messageString, notificationName,
-                                               iconURL, priority, buttons);
+          let error = (host || install.error == 0) ?
+                       "addonError" : "addonLocalError";
+          if (install.error != 0)
+            error += install.error;
+          else if (install.addon.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED)
+            error += "Blocklisted";
+          else
+            error += "Incompatible";
+
+          messageString = messengerBundle.getString(error);
+          messageString = messageString.replace("#1", install.name);
+          if (host)
+            messageString = messageString.replace("#2", host);
+          messageString = messageString.replace("#3", brandShortName);
+          messageString = messageString.replace("#4", Services.appinfo.version);
+
+          if (!notificationBox.getNotificationWithValue(notificationID)) {
+            notificationBox.appendNotification(messageString,
+                                               notificationID,
+                                               iconURL,
+                                               notificationBox.PRIORITY_CRITICAL_HIGH,
+                                               []);
           }
         }
         break;
+      case "addon-install-complete":
+        let needsRestart = installInfo.installs.some(function(i) {
+            return i.addon.pendingOperations != AddonManager.PENDING_NONE;
+        });
+
+        if (needsRestart) {
+          messageString = messengerBundle.getString("addonsInstalledNeedsRestart");
+          buttons = [{
+            label: messengerBundle.getString("addonInstallRestartButton"),
+            accessKey: messengerBundle.getString("addonInstallRestartButton.accesskey"),
+            popup: null,
+            callback: function() {
+              Application.restart();
+            }
+          }];
+        }
+        else {
+          messageString = messengerBundle.getString("addonsInstalled");
+          buttons = [{
+            label: messengerBundle.getString("addonInstallManage"),
+            accessKey: messengerBundle.getString("addonInstallManage.accesskey"),
+            popup: null,
+            callback: function() {
+              // Calculate the add-on type that is most popular in the list of
+              // installs.
+              let types = {};
+              let bestType = null;
+              for ([, install] in Iterator(installInfo)) {
+                if (install.type in types)
+                  types[install.type]++;
+                else
+                  types[install.type] = 1;
+
+                if (!bestType || types[install.type] > types[bestType])
+                  bestType = install.type;
+
+                openAddonsMgr("addons://list/" + bestType);
+              }
+            }
+          }];
+        }
+
+        messageString = PluralForm.get(installInfo.installs.length, messageString);
+        messageString = messageString.replace("#1", installInfo.installs[0].name);
+        messageString = messageString.replace("#2", installInfo.installs.length);
+        messageString = messageString.replace("#3", brandShortName);
+
+        notificationBox.appendNotification(messageString,
+                                           notificationID,
+                                           iconURL,
+                                           notificationBox.PRIORITY_INFO_MEDIUM,
+                                           buttons);
+        break;
       }
     }
   }
 };
--- a/mail/locales/en-US/chrome/messenger/messenger.properties
+++ b/mail/locales/en-US/chrome/messenger/messenger.properties
@@ -549,16 +549,44 @@ xpinstallPromptAllowButton=Allow
 # See http://www.mozilla.org/access/keyboard/accesskey for details
 xpinstallPromptAllowButton.accesskey=A
 
 xpinstallDisabledMessageLocked=Software installation has been disabled by your system administrator.
 xpinstallDisabledMessage=Software installation is currently disabled. Click Enable and try again.
 xpinstallDisabledButton=Enable
 xpinstallDisabledButton.accesskey=n
 
+# LOCALIZATION NOTE (addonsInstalled, addonsInstalledNeedsRestart):
+# Semi-colon list of plural forms. See:
+# http://developer.mozilla.org/en/docs/Localization_and_Plurals
+# #1 first add-on's name, #2 number of add-ons, #3 application name
+addonsInstalled=#1 has been installed successfully.;#2 add-ons have been installed successfully.
+addonsInstalledNeedsRestart=#1 will be installed after you restart #3.;#2 add-ons will be installed after you restart #3.
+addonInstallRestartButton=Restart Now
+addonInstallRestartButton.accesskey=R
+addonInstallManage=Open Add-ons Manager
+addonInstallManage.accesskey=O
+
+# LOCALIZATION NOTE (addonError-1, addonError-2, addonError-3, addonError-4):
+# #1 is the add-on name, #2 is the host name, #3 is the application name
+# #4 is the application version
+addonError-1=The add-on could not be downloaded because of a connection failure on #2.
+addonError-2=The add-on from #2 could not be installed because it does not match the add-on #3 expected.
+addonError-3=The add-on downloaded from #2 could not be installed because it appears to be corrupt.
+addonError-4=#1 could not be installed because #3 cannot modify the needed file.
+
+# LOCALIZATION NOTE (addonLocalError-1, addonLocalError-2, addonLocalError-3, addonLocalError-4, addonErrorIncompatible, addonErrorBlocklisted):
+# #1 is the add-on name, #3 is the application name, #4 is the application version
+addonLocalError-1=This add-on could not be installed because of a filesystem error.
+addonLocalError-2=This add-on could not be installed because it does not match the add-on #3 expected.
+addonLocalError-3=This add-on could not be installed because it appears to be corrupt.
+addonLocalError-4=#1 could not be installed because #3 cannot modify the needed file.
+addonErrorIncompatible=#1 could not be installed because it is not compatible with #3 #4.
+addonErrorBlocklisted=#1 could not be installed because it has a high risk of causing stability or security problems.
+
 applyToCollapsedMsgsTitle=Confirm Delete of Messages in Collapsed Thread(s)
 applyToCollapsedMsgs=Warning - this will delete messages in collapsed thread(s)
 applyToCollapsedAlwaysAskCheckbox=Always ask me before deleting messages in collapsed threads
 applyNowButton=Apply
 
 mailServerLoginFailedTitle=Login Failed
 # LOCALIZATION NOTE (mailServerLoginFailedTitle): Insert "%S" in your
 # translation where you wish to display the hostname of the server to which
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..259ff35a621b7de8d2e571442e291fc02f25ff64
GIT binary patch
literal 680
zc$^FHW@h1H00HL}el`q9fS*B>AvvQcKQ~n`H!&|WEw#8LG=!6Z`D{vsHwcGTurPdM
z6k!MenjivH&%s~?G@<UE^5rH*1_noF1_o}R37L7tC5bsXdPON|VBH6RIv*hEE)9<L
zZ#EFAeI6e2EiEW2;e|qv>*g764rWX{`s3EcO{SNcmQG&zasO$n6A9<`eVx~T@AB<q
zoA;hL$Q~@J|Kv)UY|eS1(_Pa)vtLlyH0xIM+S42hcdeUytDI}AW1d7CNA-!%3l>SP
zv0JKEt+9GyZsTF2Lmi!sVIuK1=Q|BQ7|1EB@x7XOSEgLxXmQxmuIalnStgWjyc}G;
zJmtzOB|UB5PcmFemB+O1zU=wN<+LH?z4bZ?R_?PdtO`DU7Q%wdJ+n9J@66*#Dp|u9
zF^OZEPM^((Uwar<_hn9C+`e}!i}kw1lIrXOmt1mZ?9)CyXSs{C%$G3xZvrn(|Gi~r
zJeRkTKhogigBgz)+F1M_SbWgryKb`h<=OM|9csl7KJ~sm|9OJ*+o^iqC-xnVee>Y;
z;uj$@&m6>?CC;{LOZ2@vxx(Y$`4>S)JM=<6d0dg+p&n$ETC@9CgHLs?Oxy9N$9Oi$
zuCkQB5|h7^W%XOXpk>!$0w;)andJML^q-w^`LjhjXY!Rl6Oa624e(}U5@*Jh26*A@
zC5<2wBSo-6QbcG40|Ue)TxkJf5+m3oCm@NEGFTxg17Zp*8%QM+5KaKmOduWrMZo;G
--- a/mail/test/mozmill/content-tabs/html/installxpi.html
+++ b/mail/test/mozmill/content-tabs/html/installxpi.html
@@ -1,10 +1,12 @@
 <html>
   <head>
     <title>Test xpi installation</title> 
   </head>
   <body bgcolor="#FFFFFF">
     <h1>Test xpi installation</h1>
 
+    <a id="corruptlink" href="corrupt.xpi">Install this corrupt xpi</a>
     <a id="installlink" href="installxpi.xpi">Install this xpi</a>
+
   </body>
 </html>
index 259ff35a621b7de8d2e571442e291fc02f25ff64..f13e4e5f77e8de0530dc961c7f975923655a0ce6
GIT binary patch
literal 686
zc$^FHW@h1H00HL}el`q9fS*B>AvvQcKQ~n`H!&|WEw#8LG=!6Z`D{vs_kIp--_Qyc
zhHs1_3;{qBM1bl!7$mc`>;hLtXLK+!F!(YvFmMA+$jmD)NzBR7D@sWN>s|!Zxd=&j
z>Eu}dW&@G7_di9a-kmw6%%DWz*cFM%u@YxDN#yU|n$x=_LDR_eU-|C7RD<;Q7Vm%0
znb$wbXS))+yie$!3Bu(s63b^<`5Eo6XK<T%^~l|f*kc@~zK=~C!&jI}xdpat=&zC2
znsxA1aMq^QO;S7fZpd;U^Z61HSo6_F-K??yBZuL}#q8^L=CP}OUlXMnmle#RaC*@-
z;q75Yc{^vE_L^+d-sCZFqnEtJ={N_DXK(gC>k2fu>=VLp=*tRY!QxrlG`Fc<S3Bst
zd=blASz9NegO_g3V_bjUc!R-|(x|1+bJb@g<!8vY=P!SL$z|=#eY&UTtnb)n^<vID
zj$dBOJKrnt6qfZepE$IU-(95LK&gVqJ<*|X%M!`h*uQ@=SIl-T_<8Bt^XeN>)(by#
z+?4yG`0c|}IV-l`wF@@)Mm#&i@hiJ#lgP>ck&nF$7jaLu6Z*>bu49VN%{}cE=Y6bG
z-%LCH^p@VH*j0PwO=9wQuyp(CY6g3+(>TG!dgisjv&tJ!zHFI0nNK+7-}Ea#Sp&Qo
znZ%iKr379$dr2dR#7GyckaQ7R!N34930I1Mn8XM+$q7iJqz_g|`hb|i$_7%&1cVbn
IW-)<y0Ho0N2LJ#7
new file mode 100644
--- /dev/null
+++ b/mail/test/mozmill/content-tabs/test-install-xpi.js
@@ -0,0 +1,188 @@
+/* ***** 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 Messaging.
+ * Portions created by the Initial Developer are Copyright (C) 2009
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Mark Banner <mark@standard8.plus.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+var MODULE_NAME = 'test-install-xpi';
+
+var RELATIVE_ROOT = '../shared-modules';
+var MODULE_REQUIRES = ['window-helpers', 'folder-display-helpers',
+                       'content-tab-helpers'];
+
+var controller = {};
+Components.utils.import('resource://mozmill/modules/controller.js', controller);
+var elib = {};
+Cu.import('resource://mozmill/modules/elementslib.js', elib);
+var mozmill = {};
+Components.utils.import('resource://mozmill/modules/mozmill.js', mozmill);
+Components.utils.import("resource://gre/modules/Services.jsm");
+
+// RELATIVE_ROOT messes with the collector, so we have to bring the path back
+// so we get the right path for the resources.
+var url = collector.addHttpResource('../content-tabs/html', 'content-tabs');
+var siteRegExp = new RegExp("^" + url);
+
+var gNewTab;
+var gNotificationBox;
+
+const ALERT_TIMEOUT = 10000;
+
+let AlertWatcher = {
+  planForAlert: function(aController) {
+    this.alerted = false;
+    aController.window.document.addEventListener("AlertActive",
+                                                 this.alertActive, false);
+  },
+  waitForAlert: function(aController) {
+    if (!this.alerted) {
+      aController.waitForEval("subject.alerted", ALERT_TIMEOUT, 100, this);
+    }
+    aController.window.document.removeEventListener("AlertActive",
+                                                    this.alertActive, false);
+  },
+  alerted: false,
+  alertActive: function() {
+    AlertWatcher.alerted = true;
+  }
+};
+
+var setupModule = function (module) {
+  let wh = collector.getModule('window-helpers');
+  wh.installInto(module);
+  let fdh = collector.getModule('folder-display-helpers');
+  fdh.installInto(module);
+  let cth = collector.getModule('content-tab-helpers');
+  cth.installInto(module);
+};
+
+function close_xpinstall_dialog(xpidlg) {
+ xpidlg.window.document.documentElement.cancelDialog();
+}
+
+function accept_xpinstall_dialog(xpidlg) {
+  // The install dialog has a count down that we must wait for before
+  // proceeding.
+  mc.sleep(5500);
+  xpidlg.window.document.documentElement.getButton('accept').doCommand();
+}
+
+function click_notification_box_action_in_current_tab() {
+  let actionButton = gNotificationBox.currentNotification.getElementsByTagName("button")[0];
+  mc.click(new elib.Elem(actionButton));
+}
+
+function close_notification_box() {
+  gNotificationBox.currentNotification.close();
+}
+
+function click_install_link_and_wait_for_alert(link) {
+  // Clicking the link will bring up a notification box...
+  AlertWatcher.planForAlert(mc);
+  mc.click(new elib.Elem(mc.tabmail.getBrowserForSelectedTab().contentDocument
+                           .getElementById(link)));
+  AlertWatcher.waitForAlert(mc);
+
+  // Just give other events time to clear
+  mc.sleep(55);
+}
+
+function test_setup() {
+  gNewTab =
+    open_content_tab_with_url(url + "installxpi.html",
+                              "specialTabs.siteClickHandler(event, siteRegExp);");
+
+  // make the animation only take one frame
+  gNotificationBox =
+    mc.tabmail.selectedTab.panel.getElementsByTagName("notificationbox")[0];
+  gNotificationBox.slideSteps = 1;
+}
+
+function test_install_corrupt_xpi() {
+  // This install with give us a corrupt xpi warning.
+  click_install_link_and_wait_for_alert("corruptlink");
+
+  // Clicking the install button will close the current notification and open
+  // the corrupt notification.
+  AlertWatcher.planForAlert(mc);
+  click_notification_box_action_in_current_tab();
+  AlertWatcher.waitForAlert(mc);
+
+  // Now check this matches, avoiding l10n issues for now.
+  if (gNotificationBox.currentNotification.priority !=
+      gNotificationBox.PRIORITY_CRITICAL_HIGH)
+    throw new Error("Unexpected priority used for notification. Wrong Notification? Priority used was: " + gNotificationBox.currentNotification.priority);
+
+  // We're done with this test, close the box.
+  close_notification_box();
+}
+
+function test_install_xpi_offer() {
+  click_install_link_and_wait_for_alert("installlink");
+
+  // which we want to click on!
+  plan_for_modal_dialog("Addons:Install", close_xpinstall_dialog);
+  click_notification_box_action_in_current_tab();
+  wait_for_modal_dialog("Addons:Install");
+
+  // After closing the dialog we need to give just a little extra time
+  // before we do things.
+  mc.sleep(100);
+}
+
+function test_xpinstall_disabled() {
+  Services.prefs.setBoolPref("xpinstall.enabled", false);
+
+  // Try installation again - this time we'll get an install has been disabled
+  // message.
+  click_install_link_and_wait_for_alert("installlink");
+
+  // tell it to enable installation!
+  click_notification_box_action_in_current_tab();
+}
+
+function test_xpinstall_actually_install() {
+  click_install_link_and_wait_for_alert("installlink");
+
+  // which we want to click on!
+  plan_for_modal_dialog("Addons:Install", accept_xpinstall_dialog);
+  // and this time we get an alert as well.
+  AlertWatcher.planForAlert(mc);
+  click_notification_box_action_in_current_tab();
+  wait_for_modal_dialog("Addons:Install");
+
+  AlertWatcher.waitForAlert(mc);
+  close_notification_box();
+  close_tab(gNewTab);
+}
--- a/mail/test/mozmill/shared-modules/test-content-tab-helpers.js
+++ b/mail/test/mozmill/shared-modules/test-content-tab-helpers.js
@@ -92,25 +92,28 @@ function installInto(module) {
  *
  * @param aURL The URL to load (string).
  * @param [aBackground] Whether the tab is opened in the background. Defaults to
  *                      false.
  * @param [aController] The controller to open the tab in. Defaults to |mc|.
  *
  * @returns The newly-opened tab.
  */
-function open_content_tab_with_url(aURL, aBackground, aController) {
+function open_content_tab_with_url(aURL, aClickHandler, aBackground, aController) {
+  if (aClickHandler === undefined)
+    aClickHandler = null;
   if (aBackground === undefined)
     aBackground = false;
   if (aController === undefined)
     aController = mc;
 
   let preCount = mc.tabmail.tabContainer.childNodes.length;
   let newTab = mc.tabmail.openTab("contentTab", {contentPage: aURL,
-                                                 background: aBackground});
+                                                 background: aBackground,
+                                                 clickHandler: aClickHandler});
   if (!controller.waitForEval("subject.childNodes.length == " + (preCount + 1),
                               FAST_TIMEOUT, FAST_INTERVAL,
                               aController.tabmail.tabContainer))
     mark_failure(["Timeout waiting for the content tab to open with URL:", aURL]);
 
   // We append new tabs at the end, so check the last one.
   let expectedNewTab = aController.tabmail.tabInfo[preCount];
   folderDisplayHelper.assert_selected_tab(expectedNewTab);
@@ -118,17 +121,17 @@ function open_content_tab_with_url(aURL,
   return expectedNewTab;
 }
 
 /**
  * Opens a content tab with a click on the given element. The tab is expected to
  * be opened in the foreground. The element is expected to be associated with
  * the given controller.
  *
- * @param aElem The element to click. 
+ * @param aElem The element to click.
  * @param [aController] The controller the element is associated with. Defaults
  *                      to |mc|.
  * @returns The newly-opened tab.
  */
 function open_content_tab_with_click(aElem, aController) {
   if (aController === undefined)
     aController = mc;
 
--- a/mail/themes/gnomestripe/jar.mn
+++ b/mail/themes/gnomestripe/jar.mn
@@ -112,16 +112,17 @@ classic.jar:
   skin/classic/messenger/smime/icons/sbSignNotOk.png          (mail/smime/sbSignNotOk.png)
   skin/classic/messenger/smime/icons/sbSignOk.png             (mail/smime/sbSignOk.png)
   skin/classic/messenger/smime/icons/sbSignUnknown.png        (mail/smime/sbSignUnknown.png)
   skin/classic/messenger/icons/timeline.png                   (mail/icons/timeline.png)
   skin/classic/messenger/icons/timeline-inverted.png          (mail/icons/timeline-inverted.png)
   skin/classic/messenger/icons/empty-search-results.png       (mail/icons/empty-search-results.png)
   skin/classic/messenger/icons/new-mail-alert.png             (mail/icons/new-mail-alert.png)
   skin/classic/messenger/icons/secure.png                     (mail/icons/secure.png)
+  skin/classic/messenger/icons/update.png                     (mail/icons/update.png)
   skin/classic/messenger/icons/insecure.png                   (mail/icons/insecure.png)
   skin/classic/messenger/icons/identity.png                   (mail/icons/identity.png)
   skin/classic/messenger/icons/tick.png                       (mail/icons/tick.png)
   skin/classic/messenger/icons/error.png                      (mail/icons/error.png)
   skin/classic/messenger/icons/cancel.png                     (mail/icons/cancel.png)
   skin/classic/messenger/icons/mail-toolbar.png               (mail/icons/mail-toolbar.png)
   skin/classic/messenger/icons/mail-toolbar-small.png         (mail/icons/mail-toolbar-small.png)
   skin/classic/messenger/icons/folder-pane.png                (mail/icons/folder-pane.png)
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..920e7cc108b804bcd409982024d754f29a3d96c4
GIT binary patch
literal 774
zc$@(S1Nr=kP)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV00007bV*G`2iO7@
z4m1Rd(sd;O000JJOGiWi000000Qp0^e*gdg32;bRa{vGh*8l(w*8xH(n|J^K0+UHZ
zK~y-)jgo6flVKFc{qWAq5*sN<c_~X0t>{A;=(aTT7LsLG4IHRB&Ag><-f&Y0GYyi=
zJ|w7=AIu?^nQr9L=6vVc0?RK^Fbsu)C^0p|)PLvQyg^I+@MDLw^Zed(p656KaQGjg
zh0GqIoY4r2m}#MmnIcq_^V(SBu$drM;bCGwKF9d?F?{$sg1fz0OCmXs-Lim+C%Q))
zaYrjbRbM<>Sp~#$$H^@VsNMYB=J0uCZyYLK#Da?{m$|8c3L<FMary;ELXFQ{gDw;I
zUQ4a<YJR0A;vWJ1VX5v(LbFkZCPO|N_1BQm5rC`h{-_^Jvx+#%I^#Hr#a;(!GUnkz
zoAWx4ZJdAPjB~9{NN90F-K#6sPPhZUqGH}ijKY1A;w&!t;7vM`RQz>1_(&u!xAT$S
z<%=>+80v>4Hi1aasJhGz%b&iP@x)NKaQ61jxq4$FlDl0YSt0ou@zfO=J>IBP$1R_!
z-t|)~b6lYP_`%IQb)QW`_3$a&9F9b-F&=qZf26A2kg8dM;*>r&ROmwJBE4ql%P9n(
zaN-2hb^42tYCKV{4@QkS1_eX@kPZltF=&H})3pL94IzkD3TElxv9)KU`PJEN!Ar{i
zC`cae|8=_7W3FcOB(AeNkYjL%%(NGkZ;wJ@I)W1R0J2m;f5J;$=BfSPwC3a7CHjbq
zc{&nJgcdRR+{N5_e;m0c50t*!heGw?#h5C=3`b6P(2|@DM1d{<1==9nCE9z&s?lJp
zKsj~@k&10x%TYx1iE@|O8+t#az1)uM{@wNk;u4-k+UbWfb0Esi2f3o<HF*)Um_6W)
z%zjT?Y7szGY-f>ZwlEsWvbwVL%%3-G>6f)U9VTJeUpi(=dp2qr^Z)<=07*qoM6N<$
Eg7;--BLDyZ
--- a/mail/themes/pinstripe/jar.mn
+++ b/mail/themes/pinstripe/jar.mn
@@ -168,16 +168,17 @@ classic.jar:
   skin/classic/messenger/icons/message-news-attach.png           (mail/icons/message-news-attach.png)
   skin/classic/messenger/icons/message-news-new-attach.png       (mail/icons/message-news-new-attach.png)
   skin/classic/messenger/icons/message-news-new.png              (mail/icons/message-news-new.png)
   skin/classic/messenger/icons/message-news.png                  (mail/icons/message-news.png)
   skin/classic/messenger/icons/new-mail-alert.png                (mail/icons/new-mail-alert.png)
   skin/classic/messenger/icons/readcol.png                       (mail/icons/readcol.png)
   skin/classic/messenger/icons/readmail.png                      (mail/icons/readmail.png)
   skin/classic/messenger/icons/secure.png                        (mail/icons/secure.png)
+  skin/classic/messenger/icons/update.png                        (mail/icons/update.png)
   skin/classic/messenger/icons/insecure.png                      (mail/icons/insecure.png)
   skin/classic/messenger/icons/identity.png                      (mail/icons/identity.png)
   skin/classic/messenger/icons/error.png                         (mail/icons/error.png)
   skin/classic/messenger/icons/saved-search-folder.png           (mail/icons/saved-search-folder.png)
   skin/classic/messenger/icons/server-local.png                  (mail/icons/server-local.png)
   skin/classic/messenger/icons/server-mail.png                   (mail/icons/server-mail.png)
   skin/classic/messenger/icons/server-news-lock.png              (mail/icons/server-news-lock.png)
   skin/classic/messenger/icons/server-news.png                   (mail/icons/server-news.png)
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0539bddf321a111e61ea16976a00d33885dad840
GIT binary patch
literal 753
zc$@+80uKF&P)<h;3K|Lk000e1NJLTq000mG000mO1^@s6AM^iV0000PbVXQnQ*UN;
zcVTj606}DLVr3vnZDD6+Qe|Oed2z{QJOBU!lSxEDRCwB?lTS!gK^(__GxOd)_m9fz
zk}I}Z85D{H5_?bx^H32K1qq#^B7!bqR3s4;K`cTVodTmvx1dAOW$RFT(P}YDqf82v
zw3N(kHQ&DH+nG20W|Yc|E)L9N=DqoSe&6r!H;+guVKsv&03k$gy88|{*F<Wcmat&J
zpbRLbz<mzVmoS_g+|jxHZsyNm6>BV`0a;sWmYeZV98AO1;G*E*cn(lvG~<!Xe-)^4
zqwo0nEftNaieQu|b6j{ZFqM6Ls-<_=-@!UoJ<i>4Z#5ZfT?COBmwfO3`nGl47myu)
zE%Q#H8$Z~EqL=A8a4)mM!n9pz-5M{uv?;b3cMJ?>z>GbuEJ6L6jl`G!Sqc);?_a6o
znIm`hTp)z@tF;Eqe4ht5W<h#*Ks3Z_Sgf=R@v0^OzM9g<8UW;NJAA0O?NLR@+c{eh
zx&#`q;BoKeXzs1;ih~~}bJ_M}L#f3q>;ls=)m#uR%-YVGy6OPoSlD@&|GS9IUCG?;
z#(2~#`oI`N6%stop@{7ysK+gGgmIY6b9iv_88KE=7Bv^JZ9-`ms!)p-xbI^k1PFn9
zd?^=`YCzMKZI`Q~M(5bH=!izBIq!NXAAstdKHde;!Tlrdr}{)75h+oU<!^w8b~aQQ
zR?s3hD<cdXlsqye<|H8kFQG`3LuIUtl8D6~e*D76*H^I=LW{ouYADIkFg6RX&&X#!
z{^P6DFxWj*(5_O+TOo@c!}Yi9#cUV3-ZF~$Czb&M2GMO(!WwJM#2bLyxIEExHMy^@
z$~amcA`pleXbi$HPWPpCP{$G=Xu|U+LrxENLmN{i3I*wy;+hmD-%apqLP%<vGOGYM
jt);wkWVOHZPXPu1pzkSU1pe&c00000NkvXXu0mjfiw#yr
--- a/mail/themes/qute/jar.mn
+++ b/mail/themes/qute/jar.mn
@@ -179,16 +179,17 @@ classic.jar:
   skin/classic/messenger/icons/phishing.png                   (mail/icons/phishing.png)
   skin/classic/messenger/icons/junk.png                       (mail/icons/junk.png)
   skin/classic/messenger/icons/check.gif                      (mail/icons/check.gif)
   skin/classic/messenger/icons/notchecked.gif                 (mail/icons/notchecked.gif)
   skin/classic/messenger/icons/online.png                     (mail/icons/online.png)
   skin/classic/messenger/icons/offline.png                    (mail/icons/offline.png)
   skin/classic/messenger/icons/row.png                        (mail/icons/row.png)
   skin/classic/messenger/icons/secure.png                     (mail/icons/secure.png)
+  skin/classic/messenger/icons/update.png                     (mail/icons/update.png)
   skin/classic/messenger/icons/insecure.png                   (mail/icons/insecure.png)
   skin/classic/messenger/icons/identity.png                   (mail/icons/identity.png)
   skin/classic/messenger/icons/tick.png                       (mail/icons/tick.png)
   skin/classic/messenger/icons/error.png                      (mail/icons/error.png)
   skin/classic/messenger/icons/cancel.png                     (mail/icons/cancel.png)
   skin/classic/messenger/icons/alltabs-box-overflow-end-bkgnd.png           (mail/icons/alltabs-box-overflow-end-bkgnd.png)
   skin/classic/messenger/icons/alltabs-box-overflow-end-bkgnd-animate.png   (mail/icons/alltabs-box-overflow-end-bkgnd-animate.png)
   skin/classic/messenger/icons/alltabs-box-overflow-end-bkgnd-hover.png     (mail/icons/alltabs-box-overflow-end-bkgnd-hover.png)
new file mode 100644
index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..e2737058f849ffc81b05eb8f2e1963b8f6ce547c
GIT binary patch
literal 368
zc%17D@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbK}SAb85>;M1%IYJqDVi|!XR|JE2
zHluVQqihMI$25b8`HljqjMA0Nay4vn^~_3bEXo}$8jbuKlUQ`8a_P-sHR_TuUdm>&
zg3W3*uiXk!ht*<^eOivw)ST9d`)pG7n`R!q-5_?oL)=1-ge6|dd+bwI`sN?>FWnVg
zb}+K$KvM1T_|9WR-DeACUT#`^txrFIkqPK3u96_XU<QYR{SY8~q#yz)+U@D$7$Ol~
z+8fE&Y{26pTgJr6lA#fBEoi~*zw587xUCAf`g7y6$~TNZ)YljHFQ~raE|7Qd`j5vZ
z`mZmDtZYuG*+1|5?XCH%B@S-+?R`?<%!~7q%NP7U#QJ?ED@)1!ZAo?q_e8Tde+)`+
zT6yt%=ZV&z3qxj9dwcBMz9jVI)d!zM6bq8vI0a5AN!NX0v^v54bJxXx{y=9jc)I$z
JtaD0e0s!gVj@|$O