Bug 1334496 - Part 1 - Add an autofocus option to PopupNotifications.show. r=Paolo
MozReview-Commit-ID: DrJOjUWJJOD
--- a/browser/base/content/test/popupNotifications/browser_popupNotification_keyboard.js
+++ b/browser/base/content/test/popupNotifications/browser_popupNotification_keyboard.js
@@ -3,17 +3,18 @@
* 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();
+ // Force tabfocus for all elements on OSX.
+ SpecialPowers.pushPrefEnv({"set": [["accessibility.tabfocus", 7]]}).then(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);
@@ -48,17 +49,16 @@ var tests = [
ok(this.notifyObj.dismissalCallbackTriggered, "dismissal callback triggered");
ok(!this.notifyObj.removedCallbackTriggered, "removed callback was not triggered");
this.notification.remove();
}
},
// Test that the space key on an anchor element focuses an active notification
{ id: "Test#3",
*run() {
- yield SpecialPowers.pushPrefEnv({"set": [["accessibility.tabfocus", 7]]});
this.notifyObj = new BasicNotification(this.id);
this.notifyObj.anchorID = "geo-notification-icon";
this.notifyObj.addOptions({
persistent: true,
});
this.notification = showNotification(this.notifyObj);
},
*onShown(popup) {
@@ -71,18 +71,16 @@ var tests = [
this.notification.remove();
},
onHidden(popup) { }
},
// Test that you can switch between active notifications with the space key
// and that the notification is focused on selection.
{ id: "Test#4",
*run() {
- yield SpecialPowers.pushPrefEnv({"set": [["accessibility.tabfocus", 7]]});
-
let notifyObj1 = new BasicNotification(this.id);
notifyObj1.id += "_1";
notifyObj1.anchorID = "default-notification-icon";
notifyObj1.addOptions({
hideClose: true,
checkbox: {
label: "Test that elements inside the panel can be focused",
},
@@ -127,9 +125,51 @@ var tests = [
is(document.activeElement, popup.childNodes[0].closebutton);
notification1.remove();
notification2.remove();
goNext();
},
},
+ // Test that passing the autofocus option will focus an opened notification.
+ { id: "Test#5",
+ *run() {
+ this.notifyObj = new BasicNotification(this.id);
+ this.notifyObj.anchorID = "geo-notification-icon";
+ this.notifyObj.addOptions({
+ autofocus: true,
+ });
+ this.notification = showNotification(this.notifyObj);
+ },
+ *onShown(popup) {
+ checkPopup(popup, this.notifyObj);
+
+ // Initial focus on open is null because a panel itself
+ // can not be focused, next tab focus will be inside the panel.
+ is(Services.focus.focusedElement, null);
+
+ EventUtils.synthesizeKey("VK_TAB", {});
+ is(Services.focus.focusedElement, popup.childNodes[0].closebutton);
+ dismissNotification(popup);
+ },
+ *onHidden() {
+ // Focus the urlbar to check that it stays focused.
+ gURLBar.focus();
+
+ // Show another notification and make sure it's not autofocused.
+ let notifyObj = new BasicNotification(this.id);
+ notifyObj.id += "_2";
+ notifyObj.anchorID = "default-notification-icon";
+
+ let opened = waitForNotificationPanel();
+ let notification = showNotification(notifyObj);
+ let popup = yield opened;
+ checkPopup(popup, notifyObj);
+
+ // Check that the urlbar is still focused.
+ is(Services.focus.focusedElement, gURLBar.inputField);
+
+ this.notification.remove();
+ notification.remove();
+ }
+ },
];
--- a/toolkit/modules/PopupNotifications.jsm
+++ b/toolkit/modules/PopupNotifications.jsm
@@ -241,25 +241,25 @@ this.PopupNotifications = function Popup
// 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;
+ let focusedElement = Services.focus.focusedElement;
// If the chrome window has a focused element, let it handle the ESC key instead.
- if (!activeElement ||
- activeElement == doc.body ||
- activeElement == this.tabbrowser.selectedBrowser ||
+ if (!focusedElement ||
+ focusedElement == doc.body ||
+ focusedElement == this.tabbrowser.selectedBrowser ||
// Ignore focused elements inside the notification.
- getNotificationFromElement(activeElement) == notification ||
- notification.contains(activeElement)) {
+ getNotificationFromElement(focusedElement) == notification ||
+ notification.contains(focusedElement)) {
this._onButtonEvent(aEvent, "secondarybuttoncommand", notification);
}
};
this.window.addEventListener("activate", this, true);
if (this.tabbrowser.tabContainer)
this.tabbrowser.tabContainer.addEventListener("TabSelect", this, true);
}
@@ -349,16 +349,18 @@ PopupNotifications.prototype = {
* 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.
+ * autofocus: Whether the notification should be autofocused on
+ * showing, stealing focus from any other focused element.
* 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
* user (e.g. by clicking away or switching
* tabs)
* "removed": notification has been removed (due to
@@ -460,16 +462,24 @@ PopupNotifications.prototype = {
notifications.push(notification);
let isActiveBrowser = this._isActiveBrowser(browser);
let fm = Cc["@mozilla.org/focus-manager;1"].getService(Ci.nsIFocusManager);
let isActiveWindow = fm.activeWindow == this.window;
if (isActiveBrowser) {
if (isActiveWindow) {
+
+ // Autofocus if the notification requests focus.
+ if (options && !options.dismissed && options.autofocus) {
+ this.panel.removeAttribute("noautofocus");
+ } else {
+ this.panel.setAttribute("noautofocus", "true");
+ }
+
// show panel now
this._update(notifications, new Set([notification.anchorElement]), true);
} else {
// indicate attention and update the icon if necessary
if (!notification.dismissed) {
this.window.getAttention();
}
this._updateAnchorIcons(notifications, this._getAnchorsForNotifications(
@@ -1313,16 +1323,22 @@ PopupNotifications.prototype = {
return undefined;
},
_onPopupHidden: function PopupNotifications_onPopupHidden(event) {
if (event.target != this.panel) {
return;
}
+ // We may have removed the "noautofocus" attribute before showing the panel
+ // if the notification specified it wants to autofocus on first show.
+ // When the panel is closed, we have to restore the attribute to its default
+ // value, so we don't autofocus it if it's subsequently opened from a different code path.
+ this.panel.setAttribute("noautofocus", "true");
+
// Handle the case where the panel was closed programmatically.
if (this._ignoreDismissal) {
this._ignoreDismissal.resolve();
this._ignoreDismissal = null;
return;
}
this._dismissOrRemoveCurrentNotifications();