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
--- 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");