Bug 1340112 - Move focus into popupnotification panels when selected via keyboard. r=florian
MozReview-Commit-ID: 3DSugKa3cQ3
--- a/browser/base/content/test/popupNotifications/browser_popupNotification_5.js
+++ b/browser/base/content/test/popupNotifications/browser_popupNotification_5.js
@@ -334,16 +334,18 @@ var tests = [
// Test clicking the anchor icon.
// Clicking the anchor of an already visible persistent notification should
// focus the main action button, but not cause additional showing/shown event
// callback calls.
// Clicking the anchor of a dismissed notification should show it, even when
// the currently displayed notification is a persistent one.
{ id: "Test#11",
*run() {
+ yield SpecialPowers.pushPrefEnv({"set": [["accessibility.tabfocus", 7]]});
+
function clickAnchor(notifyObj) {
let anchor = document.getElementById(notifyObj.anchorID);
EventUtils.synthesizeMouseAtCenter(anchor, {});
}
let popup = PopupNotifications.panel;
let notifyObj1 = new BasicNotification(this.id);
@@ -354,20 +356,21 @@ var tests = [
let notification1 = showNotification(notifyObj1);
yield shown;
checkPopup(popup, notifyObj1);
ok(!notifyObj1.dismissalCallbackTriggered,
"Should not have dismissed the notification");
notifyObj1.shownCallbackTriggered = false;
notifyObj1.showingCallbackTriggered = false;
- // Click the anchor. This should focus the primary button, but
- // not call event callbacks on the notification object.
+ // Click the anchor. This should focus the closebutton
+ // (because it's the first focusable element), but not
+ // call event callbacks on the notification object.
clickAnchor(notifyObj1);
- is(document.activeElement, popup.childNodes[0].button);
+ is(document.activeElement, popup.childNodes[0].closebutton);
ok(!notifyObj1.dismissalCallbackTriggered,
"Should not have dismissed the notification");
ok(!notifyObj1.shownCallbackTriggered,
"Should have triggered the shown event again");
ok(!notifyObj1.showingCallbackTriggered,
"Should have triggered the showing event again");
// Add another notification.
--- a/browser/base/content/test/popupNotifications/browser_popupNotification_keyboard.js
+++ b/browser/base/content/test/popupNotifications/browser_popupNotification_keyboard.js
@@ -48,27 +48,88 @@ 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
+ persistent: true,
});
this.notification = showNotification(this.notifyObj);
},
*onShown(popup) {
checkPopup(popup, this.notifyObj);
let anchor = document.getElementById(this.notifyObj.anchorID);
anchor.focus();
is(document.activeElement, anchor);
EventUtils.synthesizeKey(" ", {});
- is(document.activeElement, popup.childNodes[0].button);
+ is(document.activeElement, popup.childNodes[0].closebutton);
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",
+ },
+ persistent: true,
+ });
+ let opened = waitForNotificationPanel();
+ let notification1 = showNotification(notifyObj1);
+ yield opened;
+
+ let notifyObj2 = new BasicNotification(this.id);
+ notifyObj2.id += "_2";
+ notifyObj2.anchorID = "geo-notification-icon";
+ notifyObj2.addOptions({
+ persistent: true,
+ });
+ opened = waitForNotificationPanel();
+ let notification2 = showNotification(notifyObj2);
+ let popup = yield opened;
+
+ // Make sure notification 2 is visible
+ checkPopup(popup, notifyObj2);
+
+ // Activate the anchor for notification 1 and wait until it's shown.
+ let anchor = document.getElementById(notifyObj1.anchorID);
+ anchor.focus();
+ is(document.activeElement, anchor);
+ opened = waitForNotificationPanel();
+ EventUtils.synthesizeKey(" ", {});
+ popup = yield opened;
+ checkPopup(popup, notifyObj1);
+
+ is(document.activeElement, popup.childNodes[0].checkbox);
+
+ // Activate the anchor for notification 2 and wait until it's shown.
+ anchor = document.getElementById(notifyObj2.anchorID);
+ anchor.focus();
+ is(document.activeElement, anchor);
+ opened = waitForNotificationPanel();
+ EventUtils.synthesizeKey(" ", {});
+ popup = yield opened;
+ checkPopup(popup, notifyObj2);
+
+ is(document.activeElement, popup.childNodes[0].closebutton);
+
+ notification1.remove();
+ notification2.remove();
+ goNext();
+ },
+ },
];
--- a/toolkit/modules/PopupNotifications.jsm
+++ b/toolkit/modules/PopupNotifications.jsm
@@ -1219,27 +1219,27 @@ 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");
-
// Avoid reshowing notifications that are already shown and have not been dismissed.
if (this.panel.state == "closed" || anchor != this._currentAnchorElement) {
- this._reshowNotifications(anchor);
- }
+ // As soon as the panel is shown, focus the first element in the selected notification.
+ this.panel.addEventListener("popupshown",
+ () => this.window.document.commandDispatcher.advanceFocusIntoSubtree(this.panel),
+ {once: true});
- // If the user re-selects the current notification, focus it.
- if (anchor == this._currentAnchorElement && this.panel.firstChild) {
- this.panel.firstChild.button.focus();
+ this._reshowNotifications(anchor);
+ } else {
+ // Focus the first element in the selected notification.
+ this.window.document.commandDispatcher.advanceFocusIntoSubtree(this.panel);
}
},
_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);
notifications.forEach(function(n) {
@@ -1309,22 +1309,16 @@ 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 it was opened with user interaction. When the panel is closed, we have
- // to restore the attribute to its default value, so we don't autofocus it
- // if it is 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();