Bug 1320719 - The Esc key should deny permission requests for persistent prompts. r=florian
MozReview-Commit-ID: 3qj0pub48q9
--- a/browser/base/content/test/popupNotifications/browser.ini
+++ b/browser/base/content/test/popupNotifications/browser.ini
@@ -11,12 +11,14 @@ skip-if = (os == "linux" && (debug || as
[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_popupNotification_keyboard.js]
+skip-if = (os == "linux" && (debug || asan))
[browser_popupNotification_no_anchors.js]
skip-if = (os == "linux" && (debug || asan))
[browser_reshow_in_background.js]
skip-if = (os == "linux" && (debug || asan))
new file mode 100644
--- /dev/null
+++ b/browser/base/content/test/popupNotifications/browser_popupNotification_keyboard.js
@@ -0,0 +1,53 @@
+/* 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();
+}
+
+var tests = [
+ // Test that for persistent notifications,
+ // the secondary action is triggered by pressing the escape key.
+ { id: "Test#1",
+ run() {
+ this.notifyObj = new BasicNotification(this.id);
+ this.notifyObj.options.persistent = true;
+ showNotification(this.notifyObj);
+ },
+ onShown(popup) {
+ checkPopup(popup, this.notifyObj);
+ EventUtils.synthesizeKey("VK_ESCAPE", {});
+ },
+ onHidden(popup) {
+ ok(!this.notifyObj.mainActionClicked, "mainAction was not clicked");
+ ok(this.notifyObj.secondaryActionClicked, "secondaryAction was clicked");
+ ok(!this.notifyObj.dismissalCallbackTriggered, "dismissal callback wasn't triggered");
+ ok(this.notifyObj.removedCallbackTriggered, "removed callback triggered");
+ }
+ },
+ // Test that for non-persistent notifications, the escape key dismisses the notification.
+ { id: "Test#2",
+ *run() {
+ yield waitForWindowReadyForPopupNotifications(window);
+ this.notifyObj = new BasicNotification(this.id);
+ this.notification = showNotification(this.notifyObj);
+ },
+ onShown(popup) {
+ checkPopup(popup, this.notifyObj);
+ EventUtils.synthesizeKey("VK_ESCAPE", {});
+ },
+ onHidden(popup) {
+ ok(!this.notifyObj.mainActionClicked, "mainAction was not clicked");
+ ok(!this.notifyObj.secondaryActionClicked, "secondaryAction was not clicked");
+ ok(this.notifyObj.dismissalCallbackTriggered, "dismissal callback triggered");
+ ok(!this.notifyObj.removedCallbackTriggered, "removed callback was not triggered");
+ this.notification.remove();
+ }
+ },
+];
--- a/toolkit/modules/PopupNotifications.jsm
+++ b/toolkit/modules/PopupNotifications.jsm
@@ -214,16 +214,43 @@ this.PopupNotifications = function Popup
this.window = tabbrowser.ownerDocument.defaultView;
this.panel = panel;
this.tabbrowser = tabbrowser;
this.iconBox = iconBox;
this.buttonDelay = Services.prefs.getIntPref(PREF_SECURITY_DELAY);
this.panel.addEventListener("popuphidden", this, true);
+ // This listener will be attached to the chrome window whenever a notification
+ // is showing, to allow the user to dismiss notifications using the escape key.
+ this._handleWindowKeyPress = aEvent => {
+ if (aEvent.keyCode != aEvent.DOM_VK_ESCAPE) {
+ return;
+ }
+
+ // Esc key cancels the topmost notification, if there is one.
+ let notification = this.panel.firstChild;
+ if (!notification) {
+ return;
+ }
+
+ let doc = this.window.document;
+ let activeElement = doc.activeElement;
+
+ // If the chrome window has a focused element, let it handle the ESC key instead.
+ if (!activeElement ||
+ activeElement == doc.body ||
+ activeElement == this.tabbrowser.selectedBrowser ||
+ // Ignore focused elements inside the notification.
+ getNotificationFromElement(activeElement) == notification ||
+ notification.contains(activeElement)) {
+ this._onButtonEvent(aEvent, "secondarybuttoncommand", notification);
+ }
+ };
+
this.window.addEventListener("activate", this, true);
if (this.tabbrowser.tabContainer)
this.tabbrowser.tabContainer.addEventListener("TabSelect", this, true);
}
PopupNotifications.prototype = {
window: null,
@@ -997,16 +1024,20 @@ PopupNotifications.prototype = {
}
}
if (notificationsToShow.length > 0) {
let anchorElement = anchors.values().next().value;
if (anchorElement) {
this._showPanel(notificationsToShow, anchorElement);
}
+
+ // Setup a capturing event listener on the whole window to catch the
+ // escape key while persistent notifications are visible.
+ this.window.addEventListener("keypress", this._handleWindowKeyPress, true);
} else {
// Notify observers that we're not showing the popup (useful for testing)
this._notify("updateNotShowing");
// Close the panel if there are no notifications to show.
// When called from PopupNotifications.show() we should never close the
// panel, however. It may just be adding a dismissed notification, in
// which case we want to continue showing any existing notifications.
@@ -1018,16 +1049,19 @@ PopupNotifications.prototype = {
if (!haveNotifications) {
if (useIconBox) {
this.iconBox.hidden = true;
} else if (anchors.size) {
for (let anchorElement of anchors)
anchorElement.removeAttribute(ICON_ATTRIBUTE_SHOWING);
}
}
+
+ // Stop listening to keyboard events for notifications.
+ this.window.removeEventListener("keypress", this._handleWindowKeyPress, true);
}
},
_updateAnchorIcons: function PopupNotifications_updateAnchorIcons(notifications,
anchorElements) {
for (let anchorElement of anchorElements) {
anchorElement.setAttribute(ICON_ATTRIBUTE_SHOWING, "true");
// Use the anchorID as a class along with the default icon class as a
@@ -1276,18 +1310,20 @@ PopupNotifications.prototype = {
this._remove(notificationObj);
} else {
notificationObj.dismissed = true;
this._fireCallback(notificationObj, NOTIFICATION_EVENT_DISMISSED);
}
}, this);
},
- _onButtonEvent(event, type) {
- let notificationEl = getNotificationFromElement(event.originalTarget);
+ _onButtonEvent(event, type, notificationEl = null) {
+ if (!notificationEl) {
+ notificationEl = getNotificationFromElement(event.originalTarget);
+ }
if (!notificationEl)
throw "PopupNotifications._onButtonEvent: couldn't find notification element";
if (!notificationEl.notification)
throw "PopupNotifications._onButtonEvent: couldn't find notification";
let notification = notificationEl.notification;