Bug 613385: No notifications are sent out for webpage attempts to install add-ons when xpinstall.enabled=false. r=gavin, r=robstrong, a=blocks-betaN
authorDave Townsend <dtownsend@oxymoronical.com>
Wed, 24 Nov 2010 12:37:28 -0800
changeset 58189 9ff362a5b8db9dcfe81efa677e8325cb6ec9e1f7
parent 58188 ee357cb142cb47545456dc889c4c56465ba34011
child 58190 436ae4ff202beca009d72ea41aa913036b2ebca9
push idunknown
push userunknown
push dateunknown
reviewersgavin, robstrong, blocks-betaN
bugs613385
milestone2.0b8pre
Bug 613385: No notifications are sent out for webpage attempts to install add-ons when xpinstall.enabled=false. r=gavin, r=robstrong, a=blocks-betaN
browser/base/content/browser.js
browser/base/content/test/browser_bug553455.js
toolkit/mozapps/extensions/AddonManager.jsm
toolkit/mozapps/extensions/addonManager.js
toolkit/mozapps/extensions/amIWebInstallListener.idl
toolkit/mozapps/extensions/amWebInstallListener.js
toolkit/mozapps/extensions/test/xpinstall/browser_enabled3.js
toolkit/mozapps/extensions/test/xpinstall/head.js
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -652,59 +652,53 @@ const gXPInstallObserver = {
 
     var notificationID = aTopic;
     // Make notifications persist a minimum of 30 seconds
     var options = {
       timeout: Date.now() + 30000
     };
 
     switch (aTopic) {
-    case "addon-install-blocked":
-      var enabled = true;
-      try {
-        enabled = gPrefService.getBoolPref("xpinstall.enabled");
-      }
-      catch (e) {
-      }
-
-      if (!enabled) {
-        notificationID = "xpinstall-disabled"
-
-        if (gPrefService.prefIsLocked("xpinstall.enabled")) {
-          messageString = gNavigatorBundle.getString("xpinstallDisabledMessageLocked");
-          buttons = [];
-        }
-        else {
-          messageString = gNavigatorBundle.getString("xpinstallDisabledMessage");
-
-          action = {
-            label: gNavigatorBundle.getString("xpinstallDisabledButton"),
-            accessKey: gNavigatorBundle.getString("xpinstallDisabledButton.accesskey"),
-            callback: function editPrefs() {
-              gPrefService.setBoolPref("xpinstall.enabled", true);
-            }
-          };
-        }
+    case "addon-install-disabled":
+      notificationID = "xpinstall-disabled"
+
+      if (gPrefService.prefIsLocked("xpinstall.enabled")) {
+        messageString = gNavigatorBundle.getString("xpinstallDisabledMessageLocked");
+        buttons = [];
       }
       else {
-        messageString = gNavigatorBundle.getFormattedString("xpinstallPromptWarning",
-                          [brandShortName, installInfo.originatingURI.host]);
+        messageString = gNavigatorBundle.getString("xpinstallDisabledMessage");
 
         action = {
-          label: gNavigatorBundle.getString("xpinstallPromptAllowButton"),
-          accessKey: gNavigatorBundle.getString("xpinstallPromptAllowButton.accesskey"),
-          callback: function() {
-            installInfo.install();
+          label: gNavigatorBundle.getString("xpinstallDisabledButton"),
+          accessKey: gNavigatorBundle.getString("xpinstallDisabledButton.accesskey"),
+          callback: function editPrefs() {
+            gPrefService.setBoolPref("xpinstall.enabled", true);
           }
         };
       }
 
       PopupNotifications.show(browser, notificationID, messageString, anchorID,
                               action, null, options);
       break;
+    case "addon-install-blocked":
+      messageString = gNavigatorBundle.getFormattedString("xpinstallPromptWarning",
+                        [brandShortName, installInfo.originatingURI.host]);
+
+      action = {
+        label: gNavigatorBundle.getString("xpinstallPromptAllowButton"),
+        accessKey: gNavigatorBundle.getString("xpinstallPromptAllowButton.accesskey"),
+        callback: function() {
+          installInfo.install();
+        }
+      };
+
+      PopupNotifications.show(browser, notificationID, messageString, anchorID,
+                              action, null, options);
+      break;
     case "addon-install-failed":
       // TODO This isn't terribly ideal for the multiple failure case
       installInfo.installs.forEach(function(aInstall) {
         var host = (installInfo.originatingURI instanceof Ci.nsIStandardURL) &&
                    installInfo.originatingURI.host;
         if (!host)
           host = (aInstall.sourceURI instanceof Ci.nsIStandardURL) &&
                  aInstall.sourceURI.host;
@@ -1364,16 +1358,17 @@ function prepareForStartup() {
                             OfflineApps, false);
 
   // setup simple gestures support
   gGestureSupport.init(true);
 }
 
 function delayedStartup(isLoadingBlank, mustLoadSidebar) {
   Services.obs.addObserver(gSessionHistoryObserver, "browser:purge-session-history", false);
+  Services.obs.addObserver(gXPInstallObserver, "addon-install-disabled", false);
   Services.obs.addObserver(gXPInstallObserver, "addon-install-blocked", false);
   Services.obs.addObserver(gXPInstallObserver, "addon-install-failed", false);
   Services.obs.addObserver(gXPInstallObserver, "addon-install-complete", false);
   Services.obs.addObserver(gFormSubmitObserver, "invalidformsubmit", false);
 
   BrowserOffline.init();
   OfflineApps.init();
   IndexedDBPromptHelper.init();
@@ -1613,16 +1608,17 @@ function BrowserShutdown()
   try {
     FullZoom.destroy();
   }
   catch(ex) {
     Components.utils.reportError(ex);
   }
 
   Services.obs.removeObserver(gSessionHistoryObserver, "browser:purge-session-history");
+  Services.obs.removeObserver(gXPInstallObserver, "addon-install-disabled");
   Services.obs.removeObserver(gXPInstallObserver, "addon-install-blocked");
   Services.obs.removeObserver(gXPInstallObserver, "addon-install-failed");
   Services.obs.removeObserver(gXPInstallObserver, "addon-install-complete");
   Services.obs.removeObserver(gPluginHandler.pluginCrashed, "plugin-crashed");
   Services.obs.removeObserver(gFormSubmitObserver, "invalidformsubmit");
 
   try {
     gBrowser.removeProgressListener(window.XULBrowserWindow);
--- a/browser/base/content/test/browser_bug553455.js
+++ b/browser/base/content/test/browser_bug553455.js
@@ -54,16 +54,55 @@ function wait_for_install_dialog(aCallba
     },
 
     onWindowTitleChange: function(aXULWindow, aNewTitle) {
     }
   });
 }
 
 var TESTS = [
+function test_disabled_install() {
+  Services.prefs.setBoolPref("xpinstall.enabled", false);
+
+  // Wait for the disabled notification
+  wait_for_notification(function(aPanel) {
+    let notification = aPanel.childNodes[0];
+    is(notification.id, "xpinstall-disabled-notification", "Should have seen installs disabled");
+    is(notification.button.label, "Enable", "Should have seen the right button");
+    is(notification.getAttribute("label"),
+       "Software installation is currently disabled. Click Enable and try again.");
+
+    // Click on Enable
+    EventUtils.synthesizeMouseAtCenter(notification.button, {});
+
+    try {
+      Services.prefs.getBoolPref("xpinstall.disabled");
+      ok(false, "xpinstall.disabled should not be set");
+    }
+    catch (e) {
+      ok(true, "xpinstall.disabled should not be set");
+    }
+
+    gBrowser.removeTab(gBrowser.selectedTab);
+
+    AddonManager.getAllInstalls(function(aInstalls) {
+      is(aInstalls.length, 1, "Should have been one install created");
+      aInstalls[0].cancel();
+
+      runNextTest();
+    });
+  });
+
+  var triggers = encodeURIComponent(JSON.stringify({
+    "XPI": "unsigned.xpi"
+  }));
+  gBrowser.selectedTab = gBrowser.addTab();
+  gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
+},
+
 function test_blocked_install() {
   // Wait for the blocked notification
   wait_for_notification(function(aPanel) {
     let notification = aPanel.childNodes[0];
     is(notification.id, "addon-install-blocked-notification", "Should have seen the install blocked");
     is(notification.button.label, "Allow", "Should have seen the right button");
     is(notification.getAttribute("label"),
        gApp + " prevented this site (example.com) from asking you to install " +
--- a/toolkit/mozapps/extensions/AddonManager.jsm
+++ b/toolkit/mozapps/extensions/AddonManager.jsm
@@ -641,17 +641,21 @@ var AddonManagerInternal = {
       });
       return;
     }
 
     try {
       let weblistener = Cc["@mozilla.org/addons/web-install-listener;1"].
                         getService(Ci.amIWebInstallListener);
 
-      if (!this.isInstallAllowed(aMimetype, aURI)) {
+      if (!this.isInstallEnabled(aMimetype, aURI)) {
+        weblistener.onWebInstallDisabled(aSource, aURI, aInstalls,
+                                         aInstalls.length);
+      }
+      else if (!this.isInstallAllowed(aMimetype, aURI)) {
         if (weblistener.onWebInstallBlocked(aSource, aURI, aInstalls,
                                             aInstalls.length)) {
           aInstalls.forEach(function(aInstall) {
             aInstall.install();
           });
         }
       }
       else if (weblistener.onWebInstallRequested(aSource, aURI, aInstalls,
--- a/toolkit/mozapps/extensions/addonManager.js
+++ b/toolkit/mozapps/extensions/addonManager.js
@@ -110,19 +110,16 @@ amManager.prototype = {
   installAddonsFromWebpage: function AMC_installAddonsFromWebpage(aMimetype,
                                                                   aWindow,
                                                                   aReferer, aUris,
                                                                   aHashes, aNames,
                                                                   aIcons, aCallback) {
     if (aUris.length == 0)
       return false;
 
-    if (!AddonManager.isInstallEnabled(aMimetype))
-      return false;
-
     let retval = true;
     if (!AddonManager.isInstallAllowed(aMimetype, aReferer)) {
       aCallback = null;
       retval = false;
     }
 
     let loadGroup = null;
 
--- a/toolkit/mozapps/extensions/amIWebInstallListener.idl
+++ b/toolkit/mozapps/extensions/amIWebInstallListener.idl
@@ -60,20 +60,36 @@ interface amIWebInstallInfo : nsISupport
 };
 
 /**
  * The registered amIWebInstallListener is used to notify about new installs
  * triggered by websites. The default implementation displays a confirmation
  * dialog when add-ons are ready to install and uses the observer service to
  * notify when installations are blocked.
  */
-[scriptable, uuid(ea806f3a-1b27-4d3d-9aee-88dec4c29fda)]
+[scriptable, uuid(a5503979-89c8-441e-9e4a-321df379c172)]
 interface amIWebInstallListener : nsISupports
 {
   /**
+   * Called when installation by websites is currently disabled.
+   *
+   * @param  aWindow
+   *         The window that triggered the installs
+   * @param  aUri
+   *         The URI of the site that triggered the installs
+   * @param  aInstalls
+   *         The AddonInstalls that were blocked
+   * @param  aCount
+   *         The number of AddonInstalls
+   */
+  void onWebInstallDisabled(in nsIDOMWindowInternal aWindow, in nsIURI aUri,
+                            [array, size_is(aCount)] in nsIVariant aInstalls,
+                            [optional] in PRUint32 aCount);
+
+  /**
    * Called when the website is not allowed to directly prompt the user to
    * install add-ons.
    *
    * @param  aWindow
    *         The window that triggered the installs
    * @param  aUri
    *         The URI of the site that triggered the installs
    * @param  aInstalls
--- a/toolkit/mozapps/extensions/amWebInstallListener.js
+++ b/toolkit/mozapps/extensions/amWebInstallListener.js
@@ -278,16 +278,30 @@ Installer.prototype = {
 
 function extWebInstallListener() {
 }
 
 extWebInstallListener.prototype = {
   /**
    * @see amIWebInstallListener.idl
    */
+  onWebInstallDisabled: function(aWindow, aUri, aInstalls) {
+    let info = {
+      originatingWindow: aWindow,
+      originatingURI: aUri,
+      installs: aInstalls,
+
+      QueryInterface: XPCOMUtils.generateQI([Ci.amIWebInstallInfo])
+    };
+    Services.obs.notifyObservers(info, "addon-install-disabled", null);
+  },
+
+  /**
+   * @see amIWebInstallListener.idl
+   */
   onWebInstallBlocked: function(aWindow, aUri, aInstalls) {
     let info = {
       originatingWindow: aWindow,
       originatingURI: aUri,
       installs: aInstalls,
 
       install: function() {
         new Installer(this.originatingWindow, this.originatingURI, this.installs);
--- a/toolkit/mozapps/extensions/test/xpinstall/browser_enabled3.js
+++ b/toolkit/mozapps/extensions/test/xpinstall/browser_enabled3.js
@@ -1,11 +1,12 @@
 // ----------------------------------------------------------------------------
 // Test whether an InstallTrigger.install call fails when xpinstall is disabled
 function test() {
+  Harness.installDisabledCallback = install_disabled;
   Harness.installBlockedCallback = allow_blocked;
   Harness.installConfirmCallback = confirm_install;
   Harness.setup();
 
   Services.prefs.setBoolPref("xpinstall.enabled", false);
 
   var triggers = encodeURIComponent(JSON.stringify({
     "Unsigned XPI": TESTROOT + "unsigned.xpi"
@@ -14,16 +15,20 @@ function test() {
   gBrowser.selectedBrowser.addEventListener("load", function() {
     gBrowser.selectedBrowser.removeEventListener("load", arguments.callee, true);
     // Allow the in-page load handler to run first
     executeSoon(page_loaded);
   }, true);
   gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
 }
 
+function install_disabled(installInfo) {
+  ok(true, "Saw installation disabled");
+}
+
 function allow_blocked(installInfo) {
   ok(false, "Should never see the blocked install notification");
   return false;
 }
 
 function confirm_install(window) {
   ok(false, "Should never see an install confirmation dialog");
   return false;
--- a/toolkit/mozapps/extensions/test/xpinstall/head.js
+++ b/toolkit/mozapps/extensions/test/xpinstall/head.js
@@ -31,18 +31,22 @@ Components.utils.import("resource://gre/
 
 /**
  * This is a test harness designed to handle responding to UI during the process
  * of installing an XPI. A test can set callbacks to hear about specific parts
  * of the sequence.
  * Before use setup must be called and finish must be called afterwards.
  */
 var Harness = {
-  // If set then the install is expected to be blocked by the whitelist. The
-  // callback should return true to continue with the install anyway.
+  // If set then the callback is called when an install is attempted and
+  // software installation is disabled.
+  installDisabledCallback: null,
+  // If set then the callback will be called when an install is blocked by the
+  // whitelist. The callback should return true to continue with the install
+  // anyway.
   installBlockedCallback: null,
   // If set will be called in the event of authentication being needed to get
   // the xpi. Should return a 2 element array of username and password, or
   // null to not authenticate.
   authenticationCallback: null,
   // If set this will be called to allow checking the contents of the xpinstall
   // confirmation dialog. The callback should return true to continue the install.
   installConfirmCallback: null,
@@ -77,28 +81,30 @@ var Harness = {
   // Setup and tear down functions
   setup: function() {
     if (!this.waitingForFinish) {
       waitForExplicitFinish();
       this.waitingForFinish = true;
 
       Services.prefs.setBoolPref(PREF_LOGGING_ENABLED, true);
       Services.obs.addObserver(this, "addon-install-started", false);
+      Services.obs.addObserver(this, "addon-install-disabled", false);
       Services.obs.addObserver(this, "addon-install-blocked", false);
       Services.obs.addObserver(this, "addon-install-failed", false);
       Services.obs.addObserver(this, "addon-install-complete", false);
 
       AddonManager.addInstallListener(this);
 
       Services.wm.addListener(this);
 
       var self = this;
       registerCleanupFunction(function() {
         Services.prefs.clearUserPref(PREF_LOGGING_ENABLED);
         Services.obs.removeObserver(self, "addon-install-started");
+        Services.obs.removeObserver(self, "addon-install-disabled");
         Services.obs.removeObserver(self, "addon-install-blocked");
         Services.obs.removeObserver(self, "addon-install-failed");
         Services.obs.removeObserver(self, "addon-install-complete");
 
         AddonManager.removeInstallListener(self);
 
         Services.wm.removeListener(self);
 
@@ -204,16 +210,26 @@ var Harness = {
                 ok(false, "prompt type " + promptType + " not handled in test.");
                 break;
       }
     }
   },
 
   // Install blocked handling
 
+  installDisabled: function(installInfo) {
+    ok(!!this.installDisabledCallback, "Installation shouldn't have been disabled");
+    if (this.installDisabledCallback)
+      this.installDisabledCallback(installInfo);
+    installInfo.installs.forEach(function(install) {
+      install.cancel();
+    });
+    this.endTest();
+  },
+
   installBlocked: function(installInfo) {
     ok(!!this.installBlockedCallback, "Shouldn't have been blocked by the whitelist");
     if (this.installBlockedCallback && this.installBlockedCallback(installInfo)) {
       this.installBlockedCallback = null;
       installInfo.install();
     }
     else {
       installInfo.installs.forEach(function(install) {
@@ -305,16 +321,19 @@ var Harness = {
 
   observe: function(subject, topic, data) {
     var installInfo = subject.QueryInterface(Components.interfaces.amIWebInstallInfo);
     switch (topic) {
     case "addon-install-started":
       is(this.runningInstalls.length, installInfo.installs.length,
          "Should have seen the expected number of installs started");
       break;
+    case "addon-install-disabled":
+      this.installDisabled(installInfo);
+      break;
     case "addon-install-blocked":
       this.installBlocked(installInfo);
       break;
     case "addon-install-failed":
       installInfo.installs.forEach(function(aInstall) {
         isnot(this.runningInstalls.indexOf(aInstall), -1,
               "Should only see failures for started installs");