Bug 552965 and bug 553455: Display notifications for add-on download failures and prompt the user to restart after installation. r=gavin

This commit is contained in:
Dave Townsend 2010-06-30 14:13:17 -07:00
Родитель 4df955ab2a
Коммит 2f164a09a3
9 изменённых файлов: 488 добавлений и 60 удалений

Просмотреть файл

@ -323,6 +323,7 @@ window[chromehidden~="toolbar"] toolbar:not(.toolbar-primary):not(.chromeclass-m
display: none;
}
#notification-popup-box[anchorid="geo-notification-icon"] > #geo-notification-icon {
#notification-popup-box[anchorid="geo-notification-icon"] > #geo-notification-icon,
#notification-popup-box[anchorid="addons-notification-icon"] > #addons-notification-icon {
display: -moz-box;
}

Просмотреть файл

@ -130,6 +130,15 @@ __defineGetter__("gPrefService", function() {
return this.gPrefService = Services.prefs;
});
__defineGetter__("AddonManager", function() {
Cu.import("resource://gre/modules/AddonManager.jsm");
return this.AddonManager;
});
__defineSetter__("AddonManager", function (val) {
delete this.AddonManager;
return this.AddonManager = val;
});
__defineGetter__("PluralForm", function() {
Cu.import("resource://gre/modules/PluralForm.jsm");
return this.PluralForm;
@ -623,26 +632,35 @@ const gXPInstallObserver = {
observe: function (aSubject, aTopic, aData)
{
var brandBundle = document.getElementById("bundle_brand");
switch (aTopic) {
case "addon-install-blocked":
var installInfo = aSubject.QueryInterface(Components.interfaces.amIWebInstallInfo);
var win = installInfo.originatingWindow;
var shell = win.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
.getInterface(Components.interfaces.nsIWebNavigation)
.QueryInterface(Components.interfaces.nsIDocShell);
var browser = this._getBrowser(shell);
if (browser) {
var host = installInfo.originatingURI.host;
if (!browser)
return;
const anchorID = "addons-notification-icon";
var messageString, action;
var brandShortName = brandBundle.getString("brandShortName");
var notificationName, messageString, buttons;
var host = installInfo.originatingURI ? installInfo.originatingURI.host : browser.currentURI.host;
var notificationID = aTopic;
switch (aTopic) {
case "addon-install-blocked":
var enabled = true;
try {
enabled = gPrefService.getBoolPref("xpinstall.enabled");
}
catch (e) {
}
if (!enabled) {
notificationName = "xpinstall-disabled"
notificationID = "xpinstall-disabled"
if (PopupNotifications.getNotification(notificationID, browser))
return;
if (gPrefService.prefIsLocked("xpinstall.enabled")) {
messageString = gNavigatorBundle.getString("xpinstallDisabledMessageLocked");
buttons = [];
@ -651,41 +669,105 @@ const gXPInstallObserver = {
messageString = gNavigatorBundle.getFormattedString("xpinstallDisabledMessage",
[brandShortName, host]);
buttons = [{
action = {
label: gNavigatorBundle.getString("xpinstallDisabledButton"),
accessKey: gNavigatorBundle.getString("xpinstallDisabledButton.accesskey"),
popup: null,
callback: function editPrefs() {
gPrefService.setBoolPref("xpinstall.enabled", true);
return false;
}
}];
};
}
}
else {
notificationName = "xpinstall"
if (PopupNotifications.getNotification(notificationID, browser))
return;
messageString = gNavigatorBundle.getFormattedString("xpinstallPromptWarning",
[brandShortName, host]);
buttons = [{
action = {
label: gNavigatorBundle.getString("xpinstallPromptAllowButton"),
accessKey: gNavigatorBundle.getString("xpinstallPromptAllowButton.accesskey"),
popup: null,
callback: function() {
installInfo.install();
return false;
}
}];
};
}
var notificationBox = gBrowser.getNotificationBox(browser);
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);
PopupNotifications.show(browser, notificationID, messageString, anchorID,
action);
break;
case "addon-install-failed":
// TODO This isn't terribly ideal for the multiple failure case
installInfo.installs.forEach(function(aInstall) {
var error = "addonError";
if (aInstall.error != 0)
error += aInstall.error;
else if (aInstall.addon.blocklistState == Ci.nsIBlocklistService.STATE_BLOCKED)
error += "Blocklisted";
else
error += "Incompatible";
messageString = gNavigatorBundle.getString(error);
messageString = messageString.replace("#1", aInstall.name);
messageString = messageString.replace("#2", host);
messageString = messageString.replace("#3", brandShortName);
messageString = messageString.replace("#4", Services.appinfo.version);
PopupNotifications.show(browser, notificationID, messageString, anchorID,
action);
});
break;
case "addon-install-complete":
var notification = PopupNotifications.getNotification(notificationID, browser);
if (notification)
PopupNotifications.remove(notification);
var needsRestart = installInfo.installs.some(function(i) {
return (i.addon.pendingOperations & AddonManager.PENDING_INSTALL) != 0;
});
if (needsRestart) {
messageString = gNavigatorBundle.getString("addonsInstalledNeedsRestart");
action = {
label: gNavigatorBundle.getString("addonInstallRestartButton"),
accessKey: gNavigatorBundle.getString("addonInstallRestartButton.accesskey"),
callback: function() {
Application.restart();
}
};
}
else {
messageString = gNavigatorBundle.getString("addonsInstalled");
action = {
label: gNavigatorBundle.getString("addonInstallManage"),
accessKey: gNavigatorBundle.getString("addonInstallManage.accesskey"),
callback: function() {
// Calculate the add-on type that is most popular in the list of
// installs
var types = {};
var bestType = null;
installInfo.installs.forEach(function(aInstall) {
if (aInstall.type in types)
types[aInstall.type]++;
else
types[aInstall.type] = 1;
if (!bestType || types[aInstall.type] > types[bestType])
bestType = aInstall.type;
});
BrowserOpenAddonsMgr("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);
PopupNotifications.show(browser, notificationID, messageString, anchorID,
action);
break;
}
}
@ -1218,6 +1300,8 @@ function prepareForStartup() {
function delayedStartup(isLoadingBlank, mustLoadSidebar) {
Services.obs.addObserver(gSessionHistoryObserver, "browser:purge-session-history", 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);
BrowserOffline.init();
OfflineApps.init();
@ -1438,6 +1522,8 @@ function BrowserShutdown()
Services.obs.removeObserver(gSessionHistoryObserver, "browser:purge-session-history");
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");
try {

Просмотреть файл

@ -608,6 +608,7 @@
onblur="setTimeout(function() document.getElementById('identity-box').style.MozUserFocus = '', 0);">
<box id="notification-popup-box" hidden="true" align="center">
<image id="geo-notification-icon" class="notification-anchor-icon"/>
<image id="addons-notification-icon" class="notification-anchor-icon"/>
</box>
<!-- Use onclick instead of normal popup= syntax since the popup
code fires onmousedown, and hence eats our favicon drag events.

Просмотреть файл

@ -129,6 +129,7 @@ _BROWSER_FILES = \
browser_bug521216.js \
browser_bug537474.js \
browser_bug550565.js \
browser_bug553455.js \
browser_bug555224.js \
browser_bug555767.js \
browser_bug556061.js \

Просмотреть файл

@ -0,0 +1,270 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/
*/
const TESTROOT = "http://example.com/browser/toolkit/mozapps/extensions/test/xpinstall/";
const XPINSTALL_URL = "chrome://mozapps/content/xpinstall/xpinstallConfirm.xul";
function wait_for_notification(aCallback) {
PopupNotifications.panel.addEventListener("popupshown", function() {
PopupNotifications.panel.removeEventListener("popupshown", arguments.callee, false);
aCallback(PopupNotifications.panel);
}, false);
}
function wait_for_install_dialog(aCallback) {
Services.wm.addListener({
onOpenWindow: function(aXULWindow) {
Services.wm.removeListener(this);
var domwindow = aXULWindow.QueryInterface(Ci.nsIInterfaceRequestor)
.getInterface(Ci.nsIDOMWindowInternal);
domwindow.addEventListener("load", function() {
domwindow.removeEventListener("load", arguments.callee, false);
is(domwindow.document.location.href, XPINSTALL_URL, "Should have seen the right window open");
// Allow other window load listeners to execute before passing to callback
executeSoon(function() {
// Override the countdown timer on the accept button
var button = domwindow.document.documentElement.getButton("accept");
button.disabled = false;
aCallback(domwindow);
});
}, false);
},
onCloseWindow: function(aXULWindow) {
},
onWindowTitleChange: function(aXULWindow, aNewTitle) {
}
});
}
var TESTS = [
function test_blocked_install() {
var triggers = encodeURIComponent(JSON.stringify({
"XPI": "unsigned.xpi"
}));
gBrowser.selectedTab = gBrowser.addTab();
gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
// Wait for the blocked notification
wait_for_notification(function(aPanel) {
let notification = aPanel.childNodes[0];
is(notification.id, "addon-install-blocked", "Should have seen the install blocked");
is(notification.button.label, "Allow", "Should have seen the right button");
// Click on Allow
EventUtils.synthesizeMouse(notification.button, 20, 10, {});
// Wait for the install confirmation dialog
wait_for_install_dialog(function(aWindow) {
aWindow.document.documentElement.acceptDialog();
// Wait for the complete notification
wait_for_notification(function(aPanel) {
let notification = aPanel.childNodes[0];
is(notification.id, "addon-install-complete", "Should have seen the install complete");
is(notification.button.label, "Restart Now", "Should have seen the right button");
AddonManager.getAllInstalls(function(aInstalls) {
is(aInstalls.length, 1, "Should be one pending install");
aInstalls[0].cancel();
gBrowser.removeTab(gBrowser.selectedTab);
runNextTest();
});
});
});
});
},
function test_whitelisted_install() {
var pm = Services.perms;
pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
var triggers = encodeURIComponent(JSON.stringify({
"XPI": "unsigned.xpi"
}));
gBrowser.selectedTab = gBrowser.addTab();
gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
// Wait for the install confirmation dialog
wait_for_install_dialog(function(aWindow) {
aWindow.document.documentElement.acceptDialog();
// Wait for the complete notification
wait_for_notification(function(aPanel) {
let notification = aPanel.childNodes[0];
is(notification.id, "addon-install-complete", "Should have seen the install complete");
is(notification.button.label, "Restart Now", "Should have seen the right button");
AddonManager.getAllInstalls(function(aInstalls) {
is(aInstalls.length, 1, "Should be one pending install");
aInstalls[0].cancel();
gBrowser.removeTab(gBrowser.selectedTab);
Services.perms.remove("example.com", "install");
runNextTest();
});
});
});
},
function test_failed_download() {
var pm = Services.perms;
pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
var triggers = encodeURIComponent(JSON.stringify({
"XPI": "missing.xpi"
}));
gBrowser.selectedTab = gBrowser.addTab();
gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
// Wait for the failed notification
wait_for_notification(function(aPanel) {
let notification = aPanel.childNodes[0];
is(notification.id, "addon-install-failed", "Should have seen the install fail");
gBrowser.removeTab(gBrowser.selectedTab);
Services.perms.remove("example.com", "install");
runNextTest();
});
},
function test_corrupt_file() {
var pm = Services.perms;
pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
var triggers = encodeURIComponent(JSON.stringify({
"XPI": "corrupt.xpi"
}));
gBrowser.selectedTab = gBrowser.addTab();
gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
// Wait for the failed notification
wait_for_notification(function(aPanel) {
let notification = aPanel.childNodes[0];
is(notification.id, "addon-install-failed", "Should have seen the install fail");
gBrowser.removeTab(gBrowser.selectedTab);
Services.perms.remove("example.com", "install");
runNextTest();
});
},
function test_incompatible() {
var pm = Services.perms;
pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
var triggers = encodeURIComponent(JSON.stringify({
"XPI": "incompatible.xpi"
}));
gBrowser.selectedTab = gBrowser.addTab();
gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
// Wait for the failed notification
wait_for_notification(function(aPanel) {
let notification = aPanel.childNodes[0];
is(notification.id, "addon-install-failed", "Should have seen the install fail");
gBrowser.removeTab(gBrowser.selectedTab);
Services.perms.remove("example.com", "install");
runNextTest();
});
},
function test_restartless() {
var pm = Services.perms;
pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
var triggers = encodeURIComponent(JSON.stringify({
"XPI": "restartless.xpi"
}));
gBrowser.selectedTab = gBrowser.addTab();
gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
// Wait for the install confirmation dialog
wait_for_install_dialog(function(aWindow) {
aWindow.document.documentElement.acceptDialog();
// Wait for the complete notification
wait_for_notification(function(aPanel) {
let notification = aPanel.childNodes[0];
is(notification.id, "addon-install-complete", "Should have seen the install complete");
is(notification.button.label, "Open Add-ons Manager", "Should have seen the right button");
AddonManager.getAllInstalls(function(aInstalls) {
is(aInstalls.length, 0, "Should be no pending installs");
AddonManager.getAddonByID("restartless-xpi@tests.mozilla.org", function(aAddon) {
aAddon.uninstall();
gBrowser.removeTab(gBrowser.selectedTab);
Services.perms.remove("example.com", "install");
runNextTest();
});
});
});
});
},
function test_multiple() {
var pm = Services.perms;
pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
var triggers = encodeURIComponent(JSON.stringify({
"Unsigned XPI": "unsigned.xpi",
"Restartless XPI": "restartless.xpi"
}));
gBrowser.selectedTab = gBrowser.addTab();
gBrowser.loadURI(TESTROOT + "installtrigger.html?" + triggers);
// Wait for the install confirmation dialog
wait_for_install_dialog(function(aWindow) {
aWindow.document.documentElement.acceptDialog();
// Wait for the complete notification
wait_for_notification(function(aPanel) {
let notification = aPanel.childNodes[0];
is(notification.id, "addon-install-complete", "Should have seen the install complete");
is(notification.button.label, "Restart Now", "Should have seen the right button");
AddonManager.getAllInstalls(function(aInstalls) {
is(aInstalls.length, 1, "Should be one pending install");
aInstalls[0].cancel();
AddonManager.getAddonByID("restartless-xpi@tests.mozilla.org", function(aAddon) {
aAddon.uninstall();
gBrowser.removeTab(gBrowser.selectedTab);
Services.perms.remove("example.com", "install");
runNextTest();
});
});
});
});
}
];
function runNextTest() {
AddonManager.getAllInstalls(function(aInstalls) {
is(aInstalls.length, 0, "Should be no active installs");
if (TESTS.length == 0) {
finish();
return;
}
TESTS.shift()();
});
}
function test() {
waitForExplicitFinish();
runNextTest();
}

Просмотреть файл

@ -35,6 +35,27 @@ xpinstallDisabledMessage=Software installation is currently disabled. Click Enab
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, addonErrorIncompatible, addonErrorBlocklisted):
# #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 Firefox 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.
# LOCALIZATION NOTE (lwthemeInstallRequest.message): %S will be replaced with
# the host name of the site.
lwthemeInstallRequest.message=This site (%S) attempted to install a theme.

Просмотреть файл

@ -988,17 +988,33 @@ toolbar[iconsize="small"] #fullscreen-button {
list-style-image: url(chrome://browser/skin/Geo.png);
}
.popup-notification-icon[popupid="xpinstall-disabled"],
.popup-notification-icon[popupid="addon-install-blocked"],
.popup-notification-icon[popupid="addon-install-failed"],
.popup-notification-icon[popupid="addon-install-complete"] {
list-style-image: url(chrome://mozapps/skin/extensions/extensionGeneric.png);
width: 32px;
height: 32px;
}
/* Notification icon box */
#notification-popup-box {
margin: 0 3px;
}
#geo-notification-icon {
list-style-image: url(chrome://browser/skin/Geo.png);
.notification-anchor-icon {
width: 16px;
height: 16px;
}
#geo-notification-icon {
list-style-image: url(chrome://browser/skin/Geo.png);
}
#addons-notification-icon {
list-style-image: url(chrome://mozapps/skin/extensions/extensionGeneric-16.png);
}
/* Feed icon */
#feed-button,
#feed-button > .button-box,

Просмотреть файл

@ -1822,13 +1822,20 @@ toolbarbutton.chevron > .toolbarbutton-menu-dropmarker {
margin: 0 3px;
}
#geo-notification-icon {
list-style-image: url(chrome://browser/skin/Geo.png);
.notification-anchor-icon {
width: 16px;
height: 16px;
margin: 0 2px;
}
#geo-notification-icon {
list-style-image: url(chrome://browser/skin/Geo.png);
}
#addons-notification-icon {
list-style-image: url(chrome://mozapps/skin/extensions/extensionGeneric-16.png);
}
.popup-notification-description {
color: #fff;
}
@ -1843,6 +1850,15 @@ toolbarbutton.chevron > .toolbarbutton-menu-dropmarker {
list-style-image: url(chrome://browser/skin/Geo.png);
}
.popup-notification-icon[popupid="xpinstall-disabled"],
.popup-notification-icon[popupid="addon-install-blocked"],
.popup-notification-icon[popupid="addon-install-failed"],
.popup-notification-icon[popupid="addon-install-complete"] {
list-style-image: url(chrome://mozapps/skin/extensions/extensionGeneric.png);
width: 32px;
height: 32px;
}
#identity-popup-container,
#identity-popup-notification-container {
margin: 4px 3px 2px -30px;

Просмотреть файл

@ -1606,17 +1606,33 @@ toolbarbutton.bookmark-item[dragover="true"][open="true"] {
list-style-image: url(chrome://browser/skin/Geo.png);
}
.popup-notification-icon[popupid="xpinstall-disabled"],
.popup-notification-icon[popupid="addon-install-blocked"],
.popup-notification-icon[popupid="addon-install-failed"],
.popup-notification-icon[popupid="addon-install-complete"] {
list-style-image: url(chrome://mozapps/skin/extensions/extensionGeneric.png);
width: 32px;
height: 32px;
}
/* Notification icon box */
#notification-popup-box {
margin: 0 3px;
}
#geo-notification-icon {
list-style-image: url(chrome://browser/skin/Geo.png);
.notification-anchor-icon {
width: 16px;
height: 16px;
}
#geo-notification-icon {
list-style-image: url(chrome://browser/skin/Geo.png);
}
#addons-notification-icon {
list-style-image: url(chrome://mozapps/skin/extensions/extensionGeneric-16.png);
}
#identity-popup-container {
min-width: 280px;
padding: 9px;