--- a/browser/base/content/browser-addons.js
+++ b/browser/base/content/browser-addons.js
@@ -83,20 +83,20 @@ const gXPInstallObserver = {
// the next confirmation
if (installInfo.installs.every(i => i.state != AddonManager.STATE_DOWNLOADED)) {
showNextConfirmation();
return;
}
const anchorID = "addons-notification-icon";
- // Make notifications persist a minimum of 30 seconds
+ // Make notifications persistent
var options = {
displayURI: installInfo.originatingURI,
- timeout: Date.now() + 30000,
+ persistent: true,
};
let cancelInstallation = () => {
if (installInfo) {
for (let install of installInfo.installs) {
// The notification may have been closed because the add-ons got
// cancelled elsewhere, only try to cancel those that are still
// pending install.
@@ -225,19 +225,20 @@ const gXPInstallObserver = {
if (!browser || gBrowser.browsers.indexOf(browser) == -1)
return;
const anchorID = "addons-notification-icon";
var messageString, action;
var brandShortName = brandBundle.getString("brandShortName");
var notificationID = aTopic;
- // Make notifications persist a minimum of 30 seconds
+ // Make notifications persistent
var options = {
displayURI: installInfo.originatingURI,
+ persistent: true,
timeout: Date.now() + 30000,
};
switch (aTopic) {
case "addon-install-disabled": {
notificationID = "xpinstall-disabled";
if (gPrefService.prefIsLocked("xpinstall.enabled")) {
@@ -562,17 +563,17 @@ var LightWeightThemeWebInstaller = {
label: gNavigatorBundle.getString("lwthemeNeedsRestart.button"),
accessKey: gNavigatorBundle.getString("lwthemeNeedsRestart.accesskey"),
callback: function() {
BrowserUtils.restartApplication();
}
};
let options = {
- timeout: Date.now() + 30000
+ persistent: true
};
PopupNotifications.show(gBrowser.selectedBrowser, "addon-theme-change",
messageString, "addons-notification-icon",
action, null, options);
},
onEnabled: function(aAddon) {
--- a/browser/base/content/browser-plugins.js
+++ b/browser/base/content/browser-plugins.js
@@ -273,16 +273,17 @@ var gPluginHandler = {
notification.reshow();
browser.messageManager.sendAsyncMessage("BrowserPlugins:NotificationShown");
}
return;
}
let options = {
dismissed: !showNow,
+ persistent: showNow,
eventCallback: this._clickToPlayNotificationEventCallback,
primaryPlugin: primaryPluginPermission,
pluginData: pluginData,
principal: principal,
};
PopupNotifications.show(browser, "click-to-play-plugins",
"", "plugins-notification-icon",
null, null, options);
--- a/browser/base/content/browser.js
+++ b/browser/base/content/browser.js
@@ -5966,17 +5966,17 @@ var OfflineApps = {
let warnQuotaKB = Services.prefs.getIntPref("offline-apps.quota.warn");
// This message shows the quota in MB, and so we divide the quota (in kb) by 1024.
let message = gNavigatorBundle.getFormattedString("offlineApps.usage",
[ uri.host,
warnQuotaKB / 1024 ]);
let anchorID = "indexedDB-notification-icon";
PopupNotifications.show(browser, "offline-app-usage", message,
- anchorID, mainAction);
+ anchorID, mainAction, null, { persistent: true });
// Now that we've warned once, prevent the warning from showing up
// again.
Services.perms.add(uri, "offline-app",
Ci.nsIOfflineCacheUpdateService.ALLOW_NO_WARN);
},
// XXX: duplicated in preferences/advanced.js
@@ -6046,16 +6046,17 @@ var OfflineApps = {
OfflineApps.disallowSite(ciUri);
}
}
}];
let message = gNavigatorBundle.getFormattedString("offlineApps.available",
[host]);
let anchorID = "indexedDB-notification-icon";
let options = {
+ persistent: true,
controlledItems : [[Cu.getWeakReference(browser), docId, uri]]
};
notification = PopupNotifications.show(browser, notificationID, message,
anchorID, mainAction,
secondaryActions, options);
}
},
@@ -6137,93 +6138,41 @@ var IndexedDBPromptHelper = {
var message;
var responseTopic;
if (topic == this._permissionsPrompt) {
message = gNavigatorBundle.getFormattedString("offlineApps.available",
[ host ]);
responseTopic = this._permissionsResponse;
}
- const hiddenTimeoutDuration = 30000; // 30 seconds
- const firstTimeoutDuration = 300000; // 5 minutes
-
- var timeoutId;
-
var observer = requestor.getInterface(Ci.nsIObserver);
var mainAction = {
label: gNavigatorBundle.getString("offlineApps.allow"),
accessKey: gNavigatorBundle.getString("offlineApps.allowAccessKey"),
callback: function() {
- clearTimeout(timeoutId);
observer.observe(null, responseTopic,
Ci.nsIPermissionManager.ALLOW_ACTION);
}
};
var secondaryActions = [
{
label: gNavigatorBundle.getString("offlineApps.never"),
accessKey: gNavigatorBundle.getString("offlineApps.neverAccessKey"),
callback: function() {
- clearTimeout(timeoutId);
observer.observe(null, responseTopic,
Ci.nsIPermissionManager.DENY_ACTION);
}
}
];
- // This will be set to the result of PopupNotifications.show().
- var notification;
-
- function timeoutNotification() {
- // Remove the notification.
- if (notification) {
- notification.remove();
- }
-
- // Clear all of our timeout stuff. We may be called directly, not just
- // when the timeout actually elapses.
- clearTimeout(timeoutId);
-
- // And tell the page that the popup timed out.
- observer.observe(null, responseTopic,
- Ci.nsIPermissionManager.UNKNOWN_ACTION);
- }
-
- var options = {
- eventCallback: function(state) {
- // Don't do anything if the timeout has not been set yet.
- if (!timeoutId) {
- return;
- }
-
- // If the popup is being dismissed start the short timeout.
- if (state == "dismissed") {
- clearTimeout(timeoutId);
- timeoutId = setTimeout(timeoutNotification, hiddenTimeoutDuration);
- return;
- }
-
- // If the popup is being re-shown then clear the timeout allowing
- // unlimited waiting.
- if (state == "shown") {
- clearTimeout(timeoutId);
- }
- }
- };
-
- notification = PopupNotifications.show(browser, topic, message,
- this._notificationIcon, mainAction,
- secondaryActions, options);
-
- // Set the timeoutId after the popup has been created, and use the long
- // timeout value. If the user doesn't notice the popup after this amount of
- // time then it is most likely not visible and we want to alert the page.
- timeoutId = setTimeout(timeoutNotification, firstTimeoutDuration);
+ PopupNotifications.show(browser, topic, message,
+ this._notificationIcon, mainAction,
+ secondaryActions, { persistent: true });
}
};
function CanCloseWindow()
{
// Avoid redundant calls to canClose from showing multiple
// PermitUnload dialogs.
if (Services.startup.shuttingDown || window.skipNextCanClose) {
--- a/browser/base/content/test/general/browser_bug553455.js
+++ b/browser/base/content/test/general/browser_bug553455.js
@@ -111,23 +111,23 @@ function waitForNotification(aId, aExpec
PopupNotifications.panel.removeEventListener("PanelUpdated", eventListener);
resolve();
});
});
yield observerPromise;
yield panelEventPromise;
- info("Saw a notification");
+ info("Saw a " + aId + " notification");
ok(PopupNotifications.isPanelOpen, "Panel should be open");
is(PopupNotifications.panel.childNodes.length, aExpectedCount, "Should be the right number of notifications");
if (PopupNotifications.panel.childNodes.length) {
let nodes = Array.from(PopupNotifications.panel.childNodes);
let notification = nodes.find(n => n.id == aId + "-notification");
- ok(notification, `Should have seen the right notification`);
+ ok(notification, "Should have seen the " + aId + " notification");
}
return PopupNotifications.panel;
});
}
function waitForNotificationClose() {
return new Promise(resolve => {
@@ -507,17 +507,19 @@ function test_multiple() {
Services.perms.remove(makeURI("http://example.com/"), "install");
yield removeTab();
});
},
function test_sequential() {
return Task.spawn(function* () {
// This test is only relevant if using the new doorhanger UI
- if (!Preferences.get("xpinstall.customConfirmationUI", false)) {
+ // TODO: this subtest is disabled until multiple notification prompts are
+ // reworked in bug 1188152
+ if (true || !Preferences.get("xpinstall.customConfirmationUI", false)) {
return;
}
let pm = Services.perms;
pm.add(makeURI("http://example.com/"), "install", pm.ALLOW_ACTION);
let progressPromise = waitForProgressNotification();
let dialogPromise = waitForInstallDialog();
let triggers = encodeURIComponent(JSON.stringify({
--- a/browser/base/content/test/popupNotifications/browser.ini
+++ b/browser/base/content/test/popupNotifications/browser.ini
@@ -7,12 +7,14 @@ skip-if = (os == "linux" && (debug || as
[browser_popupNotification.js]
skip-if = (os == "linux" && (debug || asan))
[browser_popupNotification_2.js]
skip-if = (os == "linux" && (debug || asan))
[browser_popupNotification_3.js]
skip-if = (os == "linux" && (debug || asan))
[browser_popupNotification_4.js]
skip-if = (os == "linux" && (debug || asan))
+[browser_popupNotification_5.js]
+skip-if = (os == "linux" && (debug || asan))
[browser_popupNotification_checkbox.js]
skip-if = (os == "linux" && (debug || asan))
[browser_reshow_in_background.js]
skip-if = (os == "linux" && (debug || asan))
--- a/browser/base/content/test/popupNotifications/browser_popupNotification_4.js
+++ b/browser/base/content/test/popupNotifications/browser_popupNotification_4.js
@@ -222,73 +222,10 @@ var tests = [
};
let notification = showNotification(notifyObj);
ok(notifyObj.showingCallbackTriggered, "the showing callback was triggered");
ok(!notifyObj.shownCallbackTriggered, "the shown callback wasn't triggered");
notification.remove();
goNext();
}
- },
- // panel updates should fire the showing and shown callbacks again.
- { id: "Test#11",
- run: function() {
- this.notifyObj = new BasicNotification(this.id);
- this.notification = showNotification(this.notifyObj);
- },
- onShown: function(popup) {
- checkPopup(popup, this.notifyObj);
-
- this.notifyObj.showingCallbackTriggered = false;
- this.notifyObj.shownCallbackTriggered = false;
-
- // Force an update of the panel. This is typically called
- // automatically when receiving 'activate' or 'TabSelect' events,
- // but from a setTimeout, which is inconvenient for the test.
- PopupNotifications._update();
-
- checkPopup(popup, this.notifyObj);
-
- this.notification.remove();
- },
- onHidden: function() { }
- },
- // A first dismissed notification shouldn't stop _update from showing a second notification
- { id: "Test#12",
- run: function() {
- this.notifyObj1 = new BasicNotification(this.id);
- this.notifyObj1.id += "_1";
- this.notifyObj1.anchorID = "default-notification-icon";
- this.notifyObj1.options.dismissed = true;
- this.notification1 = showNotification(this.notifyObj1);
-
- this.notifyObj2 = new BasicNotification(this.id);
- this.notifyObj2.id += "_2";
- this.notifyObj2.anchorID = "geo-notification-icon";
- this.notifyObj2.options.dismissed = true;
- this.notification2 = showNotification(this.notifyObj2);
-
- this.notification2.dismissed = false;
- PopupNotifications._update();
- },
- onShown: function(popup) {
- checkPopup(popup, this.notifyObj2);
- this.notification1.remove();
- this.notification2.remove();
- },
- onHidden: function(popup) { }
- },
- // The anchor icon should be shown for notifications in background windows.
- { id: "Test#13",
- run: function() {
- let notifyObj = new BasicNotification(this.id);
- notifyObj.options.dismissed = true;
- let win = gBrowser.replaceTabWithWindow(gBrowser.addTab("about:blank"));
- whenDelayedStartupFinished(win, function() {
- showNotification(notifyObj);
- let anchor = document.getElementById("default-notification-icon");
- is(anchor.getAttribute("showing"), "true", "the anchor is shown");
- win.close();
- goNext();
- });
- }
}
];
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/popupNotifications/browser_popupNotification_5.js
@@ -0,0 +1,205 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+function test() {
+ waitForExplicitFinish();
+
+ ok(PopupNotifications, "PopupNotifications object exists");
+ ok(PopupNotifications.panel, "PopupNotifications panel exists");
+
+ setup();
+ goNext();
+}
+
+var gNotification;
+
+var tests = [
+ // panel updates should fire the showing and shown callbacks again.
+ { id: "Test#1",
+ run: function() {
+ this.notifyObj = new BasicNotification(this.id);
+ this.notification = showNotification(this.notifyObj);
+ },
+ onShown: function(popup) {
+ checkPopup(popup, this.notifyObj);
+
+ this.notifyObj.showingCallbackTriggered = false;
+ this.notifyObj.shownCallbackTriggered = false;
+
+ // Force an update of the panel. This is typically called
+ // automatically when receiving 'activate' or 'TabSelect' events,
+ // but from a setTimeout, which is inconvenient for the test.
+ PopupNotifications._update();
+
+ checkPopup(popup, this.notifyObj);
+
+ this.notification.remove();
+ },
+ onHidden: function() { }
+ },
+ // A first dismissed notification shouldn't stop _update from showing a second notification
+ { id: "Test#2",
+ run: function() {
+ this.notifyObj1 = new BasicNotification(this.id);
+ this.notifyObj1.id += "_1";
+ this.notifyObj1.anchorID = "default-notification-icon";
+ this.notifyObj1.options.dismissed = true;
+ this.notification1 = showNotification(this.notifyObj1);
+
+ this.notifyObj2 = new BasicNotification(this.id);
+ this.notifyObj2.id += "_2";
+ this.notifyObj2.anchorID = "geo-notification-icon";
+ this.notifyObj2.options.dismissed = true;
+ this.notification2 = showNotification(this.notifyObj2);
+
+ this.notification2.dismissed = false;
+ PopupNotifications._update();
+ },
+ onShown: function(popup) {
+ checkPopup(popup, this.notifyObj2);
+ this.notification1.remove();
+ this.notification2.remove();
+ },
+ onHidden: function(popup) { }
+ },
+ // The anchor icon should be shown for notifications in background windows.
+ { id: "Test#3",
+ run: function() {
+ let notifyObj = new BasicNotification(this.id);
+ notifyObj.options.dismissed = true;
+ let win = gBrowser.replaceTabWithWindow(gBrowser.addTab("about:blank"));
+ whenDelayedStartupFinished(win, function() {
+ showNotification(notifyObj);
+ let anchor = document.getElementById("default-notification-icon");
+ is(anchor.getAttribute("showing"), "true", "the anchor is shown");
+ win.close();
+ goNext();
+ });
+ }
+ },
+ // Test that persistent doesn't allow the notification to persist after
+ // navigation.
+ { id: "Test#4",
+ run: function* () {
+ this.oldSelectedTab = gBrowser.selectedTab;
+ gBrowser.selectedTab = gBrowser.addTab("about:blank");
+ yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.com/");
+ this.notifyObj = new BasicNotification(this.id);
+ this.notifyObj.addOptions({
+ persistent: true
+ });
+ this.notification = showNotification(this.notifyObj);
+ },
+ onShown: function* (popup) {
+ this.complete = false;
+
+ yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.org/");
+ yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.com/");
+
+ // This code should not be executed.
+ ok(false, "Should have removed the notification after navigation");
+ // Properly dismiss and cleanup in case the unthinkable happens.
+ this.complete = true;
+ triggerSecondaryCommand(popup, 1);
+ },
+ onHidden: function(popup) {
+ ok(!this.complete, "Should have hidden the notification after navigation");
+ this.notification.remove();
+ gBrowser.removeTab(gBrowser.selectedTab);
+ gBrowser.selectedTab = this.oldSelectedTab;
+ }
+ },
+ // Test that persistent allows the notification to persist until explicitly
+ // dismissed.
+ { id: "Test#5",
+ run: function* () {
+ this.oldSelectedTab = gBrowser.selectedTab;
+ gBrowser.selectedTab = gBrowser.addTab("about:blank");
+ yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.com/");
+ this.notifyObj = new BasicNotification(this.id);
+ this.notifyObj.addOptions({
+ persistent: true
+ });
+ this.notification = showNotification(this.notifyObj);
+ },
+ onShown: function* (popup) {
+ this.complete = false;
+
+ // Notification should persist after attempt to dismiss by clicking on the
+ // content area.
+ let browser = gBrowser.selectedBrowser;
+ yield BrowserTestUtils.synthesizeMouseAtCenter("body", {}, browser)
+
+ // Notification should be hidden after dismissal via Not Now.
+ this.complete = true;
+ triggerSecondaryCommand(popup, 1);
+ },
+ onHidden: function(popup) {
+ ok(this.complete, "Should have hidden the notification after clicking Not Now");
+ this.notification.remove();
+ gBrowser.removeTab(gBrowser.selectedTab);
+ gBrowser.selectedTab = this.oldSelectedTab;
+ }
+ },
+ // Test that persistent panels are still open after switching to another tab
+ // and back.
+ { id: "Test#6a",
+ run: function* () {
+ this.notifyObj = new BasicNotification(this.id);
+ this.notifyObj.options.persistent = true;
+ gNotification = showNotification(this.notifyObj);
+ },
+ onShown: function* (popup) {
+ this.oldSelectedTab = gBrowser.selectedTab;
+ gBrowser.selectedTab = gBrowser.addTab("about:blank");
+ info("Waiting for the new tab to load.");
+ yield promiseTabLoadEvent(gBrowser.selectedTab, "http://example.com/");
+ },
+ onHidden: function(popup) {
+ ok(true, "Should have hidden the notification after tab switch");
+ gBrowser.removeTab(gBrowser.selectedTab);
+ gBrowser.selectedTab = this.oldSelectedTab;
+ }
+ },
+ // Second part of the previous test that compensates for the limitation in
+ // runNextTest that expects a single onShown/onHidden invocation per test.
+ { id: "Test#6b",
+ run: function* () {
+ let id = PopupNotifications.panel.firstChild.getAttribute("popupid");
+ ok(id.endsWith("Test#6a"), "Should have found the notification from Test6a");
+ ok(PopupNotifications.isPanelOpen, "Should have shown the popup again after getting back to the tab");
+ gNotification.remove();
+ gNotification = null;
+ goNext();
+ }
+ },
+ // Test that persistent panels are still open after switching to another
+ // window and back.
+ { id: "Test#7",
+ run: function* () {
+ this.oldSelectedTab = gBrowser.selectedTab;
+ gBrowser.selectedTab = gBrowser.addTab("about:blank");
+ let notifyObj = new BasicNotification(this.id);
+ notifyObj.options.persistent = true;
+ this.notification = showNotification(notifyObj);
+ let win = gBrowser.replaceTabWithWindow(gBrowser.addTab("about:blank"));
+ whenDelayedStartupFinished(win, () => {
+ ok(notifyObj.shownCallbackTriggered, "Should have triggered the shown callback");
+ let anchor = win.document.getElementById("default-notification-icon");
+ win.PopupNotifications._reshowNotifications(anchor);
+ ok(win.PopupNotifications.panel.childNodes.length == 0,
+ "no notification displayed in new window");
+ ok(PopupNotifications.isPanelOpen, "Should be still showing the popup in the first window");
+ win.close();
+ let id = PopupNotifications.panel.firstChild.getAttribute("popupid");
+ ok(id.endsWith("Test#7"), "Should have found the notification from Test7");
+ ok(PopupNotifications.isPanelOpen, "Should have shown the popup again after getting back to the window");
+ this.notification.remove();
+ gBrowser.removeTab(gBrowser.selectedTab);
+ gBrowser.selectedTab = this.oldSelectedTab;
+ goNext();
+ });
+ }
+ }
+];
--- a/browser/base/content/test/social/browser_social_activation.js
+++ b/browser/base/content/test/social/browser_social_activation.js
@@ -110,33 +110,33 @@ function clickAddonRemoveButton(tab, aCa
});
}
function activateOneProvider(manifest, finishActivation, aCallback) {
info("activating provider " + manifest.name);
let panel = document.getElementById("servicesInstall-notification");
BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown").then(() => {
ok(!panel.hidden, "servicesInstall-notification panel opened");
+ BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popuphidden").then(() => {
+ ok(panel.hidden, "servicesInstall-notification panel hidden");
+ if (!finishActivation) {
+ ok(panel.hidden, "activation panel is not showing");
+ executeSoon(aCallback);
+ } else {
+ waitForProviderLoad(manifest.origin).then(() => {
+ checkSocialUI();
+ executeSoon(aCallback);
+ });
+ }
+ });
if (finishActivation)
panel.button.click();
else
panel.closebutton.click();
});
- BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popuphidden").then(() => {
- ok(panel.hidden, "servicesInstall-notification panel hidden");
- if (!finishActivation) {
- ok(panel.hidden, "activation panel is not showing");
- executeSoon(aCallback);
- } else {
- waitForProviderLoad(manifest.origin).then(() => {
- checkSocialUI();
- executeSoon(aCallback);
- });
- }
- });
// the test will continue as the popup events fire...
activateProvider(manifest.origin, function() {
info("waiting on activation panel to open/close...");
});
}
var gTestDomains = ["https://example.com", "https://test1.example.com", "https://test2.example.com"];
--- a/browser/components/nsBrowserGlue.js
+++ b/browser/components/nsBrowserGlue.js
@@ -2849,16 +2849,17 @@ var E10SAccessibilityCheck = {
let secondaryActions = [{
label: win.gNavigatorBundle.getString("e10s.accessibilityNotice.enableAndRestart.label"),
accessKey: win.gNavigatorBundle.getString("e10s.accessibilityNotice.enableAndRestart.accesskey"),
callback: restartCallback,
}];
let options = {
popupIconURL: "chrome://browser/skin/e10s-64@2x.png",
learnMoreURL: Services.urlFormatter.formatURLPref("app.support.e10sAccessibilityUrl"),
+ persistent: true,
persistWhileVisible: true,
hideNotNow: true,
};
notification =
win.PopupNotifications.show(browser, "a11y_enabled_with_e10s",
promptMessage, null, mainAction,
secondaryActions, options);
--- a/browser/modules/PermissionUI.jsm
+++ b/browser/modules/PermissionUI.jsm
@@ -332,16 +332,17 @@ this.PermissionPromptPrototype = {
popupNotificationActions[0] : null;
let secondaryActions = popupNotificationActions.splice(1);
let options = this.popupOptions;
if (!options.hasOwnProperty('displayURI') || options.displayURI) {
options.displayURI = this.principal.URI;
}
+ options.persistent = true;
this.onBeforeShow();
chromeWin.PopupNotifications.show(this.browser,
this.notificationID,
this.message,
this.anchorID,
mainAction,
secondaryActions,
--- a/browser/modules/SocialService.jsm
+++ b/browser/modules/SocialService.jsm
@@ -562,16 +562,17 @@ this.SocialService = {
accessKey: browserBundle.GetStringFromName("service.install.ok.accesskey"),
callback: function() {
aAddonInstaller.install();
},
};
let options = {
learnMoreURL: Services.urlFormatter.formatURLPref("app.support.baseURL") + "social-api",
+ persistent: true,
};
let anchor = "servicesInstall-notification-icon";
let notificationid = "servicesInstall";
data.window.PopupNotifications.show(data.window.gBrowser.selectedBrowser,
notificationid, message, anchor,
action, [], options);
},
--- a/browser/modules/webrtcUI.jsm
+++ b/browser/modules/webrtcUI.jsm
@@ -351,16 +351,17 @@ function prompt(aBrowser, aRequest) {
accessKey: stringBundle.getString("getUserMedia.always.accesskey"),
callback: function(aState) {
mainAction.callback(aState, true);
}
});
}
let options = {
+ persistent: true,
eventCallback: function(aTopic, aNewBrowser) {
if (aTopic == "swapping")
return true;
let doc = this.browser.ownerDocument;
// Clean-up video streams of screensharing previews.
if ((aTopic == "dismissed" || aTopic == "removed") &&
--- a/toolkit/components/passwordmgr/nsLoginManagerPrompter.js
+++ b/toolkit/components/passwordmgr/nsLoginManagerPrompter.js
@@ -952,19 +952,19 @@ LoginManagerPrompter.prototype = {
this._getPopupNote().show(
browser,
"password",
promptMsg,
"password-notification-icon",
mainAction,
secondaryActions,
{
- timeout: Date.now() + 10000,
displayURI: Services.io.newURI(login.hostname, null, null),
persistWhileVisible: true,
+ persistent: true,
passwordNotificationType: type,
eventCallback: function(topic) {
switch (topic) {
case "showing":
currentNotification = this;
chromeDoc.getElementById("password-notification-password")
.removeAttribute("focused");
chromeDoc.getElementById("password-notification-username")
--- a/toolkit/modules/PopupNotifications.jsm
+++ b/toolkit/modules/PopupNotifications.jsm
@@ -304,16 +304,21 @@ PopupNotifications.prototype = {
* notification. The following properties are currently supported:
* persistence: An integer. The notification will not automatically
* dismiss for this many page loads.
* timeout: A time in milliseconds. The notification will not
* automatically dismiss before this time.
* persistWhileVisible:
* A boolean. If true, a visible notification will always
* persist across location changes.
+ * persistent: A boolean. If true, the notification will always
+ * persist even across tab and app changes (but not across
+ * location changes), until the user accepts or rejects
+ * the request. The notification will never be implicitly
+ * dismissed.
* dismissed: Whether the notification should be added as a dismissed
* notification. Dismissed notifications can be activated
* by clicking on their anchorElement.
* eventCallback:
* Callback to be invoked when the notification changes
* state. The callback's first argument is a string
* identifying the state change:
* "dismissed": notification has been dismissed by the
@@ -470,17 +475,17 @@ PopupNotifications.prototype = {
let notifications = this._getNotificationsForBrowser(aBrowser);
notifications = notifications.filter(function(notification) {
// The persistWhileVisible option allows an open notification to persist
// across location changes
if (notification.options.persistWhileVisible &&
this.isPanelOpen) {
if ("persistence" in notification.options &&
- notification.options.persistence)
+ notification.options.persistence)
notification.options.persistence--;
return true;
}
// The persistence option allows a notification to persist across multiple
// page loads
if ("persistence" in notification.options &&
notification.options.persistence) {
@@ -584,16 +589,24 @@ PopupNotifications.prototype = {
/**
* Dismisses the notification without removing it.
*/
_dismiss: function PopupNotifications_dismiss(telemetryReason) {
if (telemetryReason) {
this.nextDismissReason = telemetryReason;
}
+ // An explicitly dismissed persistent notification effectively becomes
+ // non-persistent.
+ if (this.panel.firstChild &&
+ (telemetryReason == TELEMETRY_STAT_DISMISSAL_CLOSE_BUTTON ||
+ telemetryReason == TELEMETRY_STAT_DISMISSAL_NOT_NOW)) {
+ this.panel.firstChild.notification.options.persistent = false;
+ }
+
let browser = this.panel.firstChild &&
this.panel.firstChild.notification.browser;
this.panel.hidePopup();
if (browser)
browser.focus();
},
/**
@@ -826,31 +839,37 @@ PopupNotifications.prototype = {
{"detail": notificationIds});
this.panel.dispatchEvent(event);
return;
}
// If the panel is already open but we're changing anchors, we need to hide
// it first. Otherwise it can appear in the wrong spot. (_hidePanel is
// safe to call even if the panel is already hidden.)
- let promise = this._hidePanel().then(() => {
+ this._hidePanel().then(() => {
// If the anchor element is hidden or null, use the tab as the anchor. We
// only ever show notifications for the current browser, so we can just use
// the current tab.
let selectedTab = this.tabbrowser.selectedTab;
if (anchorElement) {
let bo = anchorElement.boxObject;
if (bo.height == 0 && bo.width == 0)
anchorElement = selectedTab; // hidden
} else {
anchorElement = selectedTab; // null
}
this._currentAnchorElement = anchorElement;
+ if (notificationsToShow.some(n => n.options.persistent)) {
+ this.panel.setAttribute("noautohide", "true");
+ } else {
+ this.panel.removeAttribute("noautohide");
+ }
+
// On OS X and Linux we need a different panel arrow color for
// click-to-play plugins, so copy the popupid and use css.
this.panel.setAttribute("popupid", this.panel.firstChild.getAttribute("popupid"));
notificationsToShow.forEach(function(n) {
// Record that the notification was actually displayed on screen.
// Notifications that were opened a second time or that were originally
// shown with "options.dismissed" will be recorded in a separate bucket.
n._recordTelemetryStat(TELEMETRY_STAT_OFFERED);
@@ -926,19 +945,20 @@ PopupNotifications.prototype = {
for (let anchor of anchors) {
if (anchor.parentNode == this.iconBox)
continue;
useIconBox = false;
break;
}
}
- // Filter out notifications that have been dismissed.
+ // Filter out notifications that have been dismissed, unless they are
+ // persistent.
let notificationsToShow = notifications.filter(function(n) {
- return !n.dismissed && !n.options.neverShow;
+ return (!n.dismissed || n.options.persistent) && !n.options.neverShow;
});
if (useIconBox) {
// Hide icons of the previous tab.
this._hideIcons();
}
if (haveNotifications) {
@@ -1106,17 +1126,17 @@ PopupNotifications.prototype = {
// If the panel is not closed, and the anchor is different, immediately mark all
// active notifications for the previous anchor as dismissed
if (this.panel.state != "closed" && anchor != this._currentAnchorElement) {
this._dismissOrRemoveCurrentNotifications();
}
// Ensure we move focus into the panel because it's opened through user interaction:
- this.panel.removeAttribute("noautofocus", "true");
+ this.panel.removeAttribute("noautofocus");
this._reshowNotifications(anchor);
},
_reshowNotifications: function PopupNotifications_reshowNotifications(anchor, browser) {
// Mark notifications anchored to this anchor as un-dismissed
browser = browser || this.tabbrowser.selectedBrowser;
let notifications = this._getNotificationsForBrowser(browser);