Bug 1436086: Implement keyboard navigation for the main and Bookmarks toolbars. r=Gijs

Having separate tab stops for every toolbar control results in an unmanageable number of tab stops.
Therefore, we group several buttons under a single tab stop and allow movement between them using left/right arrows.
However, text inputs use the arrow keys for their own purposes, so they need their own tab stop.
There are also groups of buttons before and after the URL bar input which should get their own tab stop.
The subsequent buttons on the toolbar are then another tab stop after that.

Tab stops for groups of buttons are set using the <toolbartabstop/> element.
This element is invisible, but gets included in the tab order.
When one of these gets focus, it redirects focus to the appropriate button.
This avoids the need to continually manage the tabindex of toolbar buttons in response to toolbarchanges.

Navigation to for the View site information button and notification anchors is now managed by this new framework.
As such, they no longer need their own position in the tab order and the CSS has been tweaked accordingly.

For now, this new functionality is behind a pref (browser.toolbars.keyboard_navigation) which is currently disabled by default.

Differential Revision: https://phabricator.services.mozilla.com/D15060

--HG--
extra : moz-landing-system : lando
This commit is contained in:
James Teh 2019-02-13 22:51:06 +00:00
Родитель e5c5dceda1
Коммит 3676e15738
9 изменённых файлов: 587 добавлений и 3 удалений

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

@ -1814,3 +1814,5 @@ pref("browser.aboutConfig.showWarning", true);
// Launcher process is disabled by default, will be selectively enabled via SHIELD
pref("browser.launcherProcess.enabled", false);
#endif // defined(XP_WIN) && defined(MOZ_LAUNCHER_PROCESS)
pref("browser.toolbars.keyboard_navigation", false);

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

@ -0,0 +1,246 @@
/* 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/. */
// This file is loaded into the browser window scope.
/* eslint-env mozilla/browser-window */
/**
* Handle keyboard navigation for toolbars.
* Having separate tab stops for every toolbar control results in an
* unmanageable number of tab stops. Therefore, we group buttons under a single
* tab stop and allow movement between them using left/right arrows.
* However, text inputs use the arrow keys for their own purposes, so they need
* their own tab stop. There are also groups of buttons before and after the
* URL bar input which should get their own tab stop. The subsequent buttons on
* the toolbar are then another tab stop after that.
* Tab stops for groups of buttons are set using the <toolbartabstop/> element.
* This element is invisible, but gets included in the tab order. When one of
* these gets focus, it redirects focus to the appropriate button. This avoids
* the need to continually manage the tabindex of toolbar buttons in response to
* toolbarchanges.
*/
ToolbarKeyboardNavigator = {
// Toolbars we want to be keyboard navigable.
kToolbars: [CustomizableUI.AREA_NAVBAR, CustomizableUI.AREA_BOOKMARKS],
_isButton(aElem) {
return aElem.tagName == "toolbarbutton" ||
aElem.getAttribute("role") == "button";
},
// Get a TreeWalker which includes only controls which should be keyboard
// navigable.
_getWalker(aRoot) {
if (aRoot._toolbarKeyNavWalker) {
return aRoot._toolbarKeyNavWalker;
}
let filter = (aNode) => {
if (aNode.tagName == "toolbartabstop") {
return NodeFilter.FILTER_ACCEPT;
}
// Special case for the "View site information" button, which isn't
// actionable in some cases but is still visible.
if (aNode.id == "identity-box" &&
document.getElementById("urlbar").getAttribute("pageproxystate") ==
"invalid") {
return NodeFilter.FILTER_REJECT;
}
// Skip invisible or disabled elements.
if (aNode.hidden || aNode.disabled) {
return NodeFilter.FILTER_REJECT;
}
// This width check excludes the overflow button when there's no overflow.
let bounds = window.windowUtils.getBoundsWithoutFlushing(aNode);
if (bounds.width == 0) {
return NodeFilter.FILTER_REJECT;
}
if (this._isButton(aNode)) {
return NodeFilter.FILTER_ACCEPT;
}
return NodeFilter.FILTER_SKIP;
};
aRoot._toolbarKeyNavWalker = document.createTreeWalker(aRoot,
NodeFilter.SHOW_ELEMENT, filter);
return aRoot._toolbarKeyNavWalker;
},
init() {
for (let id of this.kToolbars) {
let toolbar = document.getElementById(id);
// When enabled, no toolbar buttons should themselves be tabbable.
// We manage toolbar focus completely. This attribute ensures that CSS
// doesn't set -moz-user-focus: normal.
toolbar.setAttribute("keyNav", "true");
for (let stop of toolbar.getElementsByTagName("toolbartabstop")) {
// These are invisible, but because they need to be in the tab order,
// they can't get display: none or similar. They must therefore be
// explicitly hidden for accessibility.
stop.setAttribute("aria-hidden", "true");
stop.addEventListener("focus", this);
}
toolbar.addEventListener("keydown", this);
toolbar.addEventListener("keypress", this);
}
},
uninit() {
for (let id of this.kToolbars) {
let toolbar = document.getElementById(id);
for (let stop of toolbar.getElementsByTagName("toolbartabstop")) {
stop.removeEventListener("focus", this);
}
toolbar.removeEventListener("keydown", this);
toolbar.removeEventListener("keypress", this);
toolbar.removeAttribute("keyNav");
}
},
_focusButton(aButton) {
// Toolbar buttons aren't focusable because if they were, clicking them
// would focus them, which is undesirable. Therefore, we must make a
// button focusable only when we want to focus it.
aButton.setAttribute("tabindex", "-1");
aButton.focus();
// We could remove tabindex now, but even though the button keeps DOM
// focus, a11y gets confused because the button reports as not being
// focusable. This results in weirdness if the user switches windows and
// then switches back. Instead, remove tabindex when the button loses
// focus.
aButton.addEventListener("blur", this);
},
_onButtonBlur(aEvent) {
if (document.activeElement == aEvent.target) {
// This event was fired because the user switched windows. This button
// will get focus again when the user returns.
return;
}
aEvent.target.removeEventListener("blur", this);
aEvent.target.removeAttribute("tabindex");
},
_onTabStopFocus(aEvent) {
let toolbar = aEvent.target.closest("toolbar");
let walker = this._getWalker(toolbar);
let oldFocus = aEvent.relatedTarget;
if (oldFocus) {
// Save this because we might rewind focus and the subsequent focus event
// won't get a relatedTarget.
this._isFocusMovingBackward =
oldFocus.compareDocumentPosition(aEvent.target) &
Node.DOCUMENT_POSITION_PRECEDING;
if (this._isFocusMovingBackward && oldFocus && this._isButton(oldFocus)) {
// Shift+tabbing from a button will land on its toolbartabstop. Skip it.
document.commandDispatcher.rewindFocus();
return;
}
}
walker.currentNode = aEvent.target;
let button = walker.nextNode();
if (!button || !this._isButton(button)) {
// No navigable buttons for this tab stop. Skip it.
if (this._isFocusMovingBackward) {
document.commandDispatcher.rewindFocus();
} else {
document.commandDispatcher.advanceFocus();
}
return;
}
this._focusButton(button);
},
navigateButtons(aToolbar, aPrevious) {
let oldFocus = document.activeElement;
let walker = this._getWalker(aToolbar);
// Start from the current control and walk to the next/previous control.
walker.currentNode = oldFocus;
let newFocus;
if (aPrevious) {
newFocus = walker.previousNode();
} else {
newFocus = walker.nextNode();
}
if (!newFocus || newFocus.tagName == "toolbartabstop") {
// There are no more controls or we hit a tab stop placeholder.
return;
}
this._focusButton(newFocus);
},
_onKeyDown(aEvent) {
let focus = document.activeElement;
if (aEvent.altKey || aEvent.controlKey || aEvent.metaKey ||
aEvent.shiftKey || !this._isButton(focus)) {
return;
}
switch (aEvent.key) {
case "ArrowLeft":
this.navigateButtons(aEvent.currentTarget, true);
break;
case "ArrowRight":
this.navigateButtons(aEvent.currentTarget, false);
break;
default:
return;
}
aEvent.preventDefault();
},
_onKeyPress(aEvent) {
let focus = document.activeElement;
if ((aEvent.key != "Enter" && aEvent.key != " ") ||
!this._isButton(focus)) {
return;
}
if (focus.getAttribute("type") == "menu") {
focus.open = true;
} else {
// Several buttons specifically don't use command events; e.g. because
// they want to activate for middle click. Therefore, simulate a
// click event.
// If this button does handle command events, that won't trigger here.
// Command events have their own keyboard handling: keypress for enter
// and keyup for space. We rely on that behavior, since there's no way
// for us to reliably know what events a button handles.
focus.dispatchEvent(new MouseEvent("click", {
bubbles: true,
ctrlKey: aEvent.ctrlKey,
altKey: aEvent.altKey,
shiftKey: aEvent.shiftKey,
metaKey: aEvent.metaKey,
}));
}
// We deliberately don't call aEvent.preventDefault() here so that enter
// will trigger a command event handler if appropriate.
aEvent.stopPropagation();
},
handleEvent(aEvent) {
switch (aEvent.type) {
case "focus":
this._onTabStopFocus(aEvent);
break;
case "keydown":
this._onKeyDown(aEvent);
break;
case "keypress":
this._onKeyPress(aEvent);
break;
case "blur":
this._onButtonBlur(aEvent);
break;
}
},
};

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

@ -778,7 +778,7 @@ html|input.urlbar-input {
display: none;
}
#identity-box {
#nav-bar:not([keyNav=true]) #identity-box {
-moz-user-focus: normal;
}
@ -981,7 +981,7 @@ html|*#fullscreen-exit-button {
/* notification anchors should only be visible when their associated
notifications are */
.notification-anchor-icon {
#nav-bar:not([keyNav=true]) .notification-anchor-icon {
-moz-user-focus: normal;
}
@ -1427,4 +1427,8 @@ toolbarpaletteitem > toolbaritem {
}
}
toolbar[keyNav=true]:not([collapsed=true]):not([customizing=true]) toolbartabstop {
-moz-user-focus: normal;
}
%include theme-vars.inc.css

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

@ -148,6 +148,8 @@ if (AppConstants.NIGHTLY_BUILD) {
}
XPCOMUtils.defineLazyScriptGetter(this, "pktUI", "chrome://pocket/content/main.js");
XPCOMUtils.defineLazyScriptGetter(this, "ToolbarKeyboardNavigator",
"chrome://browser/content/browser-toolbarKeyNav.js");
// lazy service getters
@ -283,6 +285,16 @@ XPCOMUtils.defineLazyGetter(this, "Win7Features", () => {
return null;
});
XPCOMUtils.defineLazyPreferenceGetter(this, "gToolbarKeyNavEnabled",
"browser.toolbars.keyboard_navigation", false,
(aPref, aOldVal, aNewVal) => {
if (aNewVal) {
ToolbarKeyboardNavigator.init();
} else {
ToolbarKeyboardNavigator.uninit();
}
});
customElements.setElementCreationCallback("translation-notification", () => {
Services.scriptloader.loadSubScript(
"chrome://browser/content/translation-notification.js", window);
@ -1384,6 +1396,9 @@ var gBrowserInit = {
BrowserPageActions.init();
gAccessibilityServiceIndicator.init();
AccessibilityRefreshBlocker.init();
if (gToolbarKeyNavEnabled) {
ToolbarKeyboardNavigator.init();
}
gRemoteControl.updateVisualCue(Marionette.running);
@ -1930,6 +1945,10 @@ var gBrowserInit = {
AccessibilityRefreshBlocker.uninit();
if (gToolbarKeyNavEnabled) {
ToolbarKeyboardNavigator.uninit();
}
LanguagePrompt.uninit();
BrowserSearch.uninit();

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

@ -835,6 +835,7 @@ xmlns="http://www.w3.org/1999/xhtml"
overflowpanel="widget-overflow"
context="toolbar-context-menu">
<toolbartabstop/>
<hbox id="nav-bar-customization-target" flex="1">
<toolbarbutton id="back-button" class="toolbarbutton-1 chromeclass-toolbar-additional"
label="&backCmd.label;"
@ -888,6 +889,7 @@ xmlns="http://www.w3.org/1999/xhtml"
<toolbaritem id="urlbar-container" flex="400" persist="width"
removable="false"
class="chromeclass-location" overflows="false">
<toolbartabstop/>
<textbox id="urlbar" flex="1"
placeholder="&urlbar.placeholder2;"
defaultPlaceholder="&urlbar.placeholder2;"
@ -1007,6 +1009,7 @@ xmlns="http://www.w3.org/1999/xhtml"
<label id="extension" class="urlbar-display urlbar-display-extension" value="&urlbar.extension.label;"/>
</box>
<hbox id="page-action-buttons" context="pageActionContextMenu">
<toolbartabstop/>
<hbox id="contextual-feature-recommendation" role="button" hidden="true">
<hbox id="cfr-label-container">
<label id="cfr-label"/>
@ -1064,6 +1067,7 @@ xmlns="http://www.w3.org/1999/xhtml"
</hbox>
</hbox>
</textbox>
<toolbartabstop/>
</toolbaritem>
<toolbarspring cui-areatype="toolbar" class="chromeclass-toolbar-additional"/>
@ -1159,6 +1163,7 @@ xmlns="http://www.w3.org/1999/xhtml"
toolbarname="&personalbarCmd.label;" accesskey="&personalbarCmd.accesskey;"
collapsed="true"
customizable="true">
<toolbartabstop skipintoolbarset="true"/>
<toolbaritem id="personal-bookmarks"
title="&bookmarksToolbarItem.label;"
cui-areatype="toolbar"
@ -1328,7 +1333,9 @@ xmlns="http://www.w3.org/1999/xhtml"
align="center"
flex="100"
persist="width">
<toolbartabstop/>
<searchbar id="searchbar" flex="1"/>
<toolbartabstop/>
</toolbaritem>
</toolbarpalette>
</toolbox>

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

@ -1 +1,3 @@
[browser_toolbarButtonKeyPress.js]
[browser_toolbarKeyNav.js]
support-files = !/browser/base/content/test/permissions/permissions.html

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

@ -30,6 +30,12 @@ function waitForLocationChange() {
return promise;
}
add_task(async function setPref() {
await SpecialPowers.pushPrefEnv({
set: [["browser.toolbars.keyboard_navigation", true]],
});
});
// Test activation of the app menu button from the keyboard.
// The app menu should appear and focus should move inside it.
add_task(async function testAppMenuButtonPress() {
@ -86,6 +92,7 @@ add_task(async function testDeveloperButtonPress() {
await hidden;
CustomizableUI.reset();
});
// Test that the Developer menu doesn't open when a key other than space or
// enter is pressed .
add_task(async function testDeveloperButtonWrongKey() {
@ -159,3 +166,80 @@ add_task(async function testSendTabToDeviceButtonPress() {
PageActions.actionForID("sendToDevice").pinnedToUrlbar = false;
});
});
// Test activation of the Reload button from the keyboard.
// This is a toolbarbutton with a click handler and no command handler, but
// the toolbar keyboard navigation code should handle keyboard activation.
add_task(async function testReloadButtonPress() {
await BrowserTestUtils.withNewTab("https://example.com", async function(aBrowser) {
let button = document.getElementById("reload-button");
await TestUtils.waitForCondition(() => !button.disabled);
forceFocus(button);
let loaded = BrowserTestUtils.browserLoaded(aBrowser);
EventUtils.synthesizeKey(" ");
await loaded;
ok(true, "Page loaded after Reload button pressed");
});
});
// Test activation of the Sidebars button from the keyboard.
// This is a toolbarbutton with a command handler.
add_task(async function testSidebarsButtonPress() {
let button = document.getElementById("sidebar-button");
ok(!button.checked, "Sidebars button not checked at start of test");
let sidebarBox = document.getElementById("sidebar-box");
ok(sidebarBox.hidden, "Sidebar hidden at start of test");
forceFocus(button);
EventUtils.synthesizeKey(" ");
await TestUtils.waitForCondition(() => button.checked);
ok(true, "Sidebars button checked after press");
ok(!sidebarBox.hidden, "Sidebar visible after press");
// Make sure the sidebar is fully loaded before we hide it.
// Otherwise, the unload event might call JS which isn't loaded yet.
// We can't use BrowserTestUtils.browserLoaded because it fails on non-tab
// docs. Instead, wait for something in the JS script.
let sidebarWin = document.getElementById("sidebar").contentWindow;
await TestUtils.waitForCondition(() => sidebarWin.PlacesUIUtils);
forceFocus(button);
EventUtils.synthesizeKey(" ");
await TestUtils.waitForCondition(() => !button.checked);
ok(true, "Sidebars button not checked after press");
ok(sidebarBox.hidden, "Sidebar hidden after press");
});
// Test activation of the Bookmark this page button from the keyboard.
// This is an image with a click handler on its parent and no command handler,
// but the toolbar keyboard navigation code should handle keyboard activation.
add_task(async function testBookmarkButtonPress() {
await BrowserTestUtils.withNewTab("https://example.com", async function(aBrowser) {
let button = document.getElementById("star-button");
forceFocus(button);
let panel = document.getElementById("editBookmarkPanel");
let focused = BrowserTestUtils.waitForEvent(panel, "focus", true);
EventUtils.synthesizeKey(" ");
await focused;
ok(true, "Focus inside edit bookmark panel after Bookmark button pressed");
let hidden = BrowserTestUtils.waitForEvent(panel, "popuphidden");
EventUtils.synthesizeKey("KEY_Escape");
await hidden;
});
});
// Test activation of the Bookmarks Menu button from the keyboard.
// This is a button with type="menu".
// The Bookmarks Menu should appear.
add_task(async function testBookmarksmenuButtonPress() {
CustomizableUI.addWidgetToArea("bookmarks-menu-button",
CustomizableUI.AREA_NAVBAR);
let button = document.getElementById("bookmarks-menu-button");
forceFocus(button);
let menu = document.getElementById("BMB_bookmarksPopup");
let shown = BrowserTestUtils.waitForEvent(menu, "popupshown");
EventUtils.synthesizeKey(" ");
await shown;
ok(true, "Bookmarks Menu shown after toolbar button pressed");
let hidden = BrowserTestUtils.waitForEvent(menu, "popuphidden");
menu.hidePopup();
await hidden;
CustomizableUI.reset();
});

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

@ -0,0 +1,219 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
/**
* Test browser toolbar keyboard navigation.
* These tests assume the default browser configuration for toolbars unless
* otherwise specified.
*/
const PERMISSIONS_PAGE = "https://example.com/browser/browser/base/content/test/permissions/permissions.html";
async function expectFocusAfterKey(aKey, aFocus, aAncestorOk = false) {
let res = aKey.match(/^(Shift\+)?(?:(.)|(.+))$/);
let shift = Boolean(res[1]);
let key;
if (res[2]) {
key = res[2]; // Character.
} else {
key = "KEY_" + res[3]; // Tab, ArrowRight, etc.
}
let expected;
let friendlyExpected;
if (typeof aFocus == "string") {
expected = document.getElementById(aFocus);
friendlyExpected = aFocus;
} else {
expected = aFocus;
if (aFocus == gURLBar.inputField) {
friendlyExpected = "URL bar input";
} else if (aFocus == gBrowser.selectedBrowser) {
friendlyExpected = "Web document";
}
}
let focused = BrowserTestUtils.waitForEvent(expected, "focus", aAncestorOk);
EventUtils.synthesizeKey(key, {shiftKey: shift});
await focused;
ok(true, friendlyExpected + " focused after " + aKey + " pressed");
}
function startFromUrlBar() {
gURLBar.focus();
is(document.activeElement, gURLBar.inputField,
"URL bar focused for start of test");
}
// The Reload button is disabled for a short time even after the page finishes
// loading. Wait for it to be enabled.
async function waitUntilReloadEnabled() {
let button = document.getElementById("reload-button");
await TestUtils.waitForCondition(() => !button.disabled);
}
add_task(async function setPref() {
await SpecialPowers.pushPrefEnv({
set: [
["browser.toolbars.keyboard_navigation", true],
["accessibility.tabfocus", 7],
],
});
});
// Test tab stops with no page loaded.
add_task(async function testTabStopsNoPage() {
await BrowserTestUtils.withNewTab("about:blank", async function() {
startFromUrlBar();
await expectFocusAfterKey("Shift+Tab", "home-button");
await expectFocusAfterKey("Shift+Tab", "tabbrowser-tabs", true);
await expectFocusAfterKey("Tab", "home-button");
await expectFocusAfterKey("Tab", gURLBar.inputField);
await expectFocusAfterKey("Tab", "library-button");
await expectFocusAfterKey("Tab", gBrowser.selectedBrowser);
});
});
// Test tab stops with a page loaded.
add_task(async function testTabStopsPageLoaded() {
await BrowserTestUtils.withNewTab("https://example.com", async function() {
await waitUntilReloadEnabled();
startFromUrlBar();
await expectFocusAfterKey("Shift+Tab", "identity-box");
await expectFocusAfterKey("Shift+Tab", "reload-button");
await expectFocusAfterKey("Shift+Tab", "tabbrowser-tabs", true);
await expectFocusAfterKey("Tab", "reload-button");
await expectFocusAfterKey("Tab", "identity-box");
await expectFocusAfterKey("Tab", gURLBar.inputField);
await expectFocusAfterKey("Tab", "pageActionButton");
await expectFocusAfterKey("Tab", "library-button");
await expectFocusAfterKey("Tab", gBrowser.selectedBrowser);
});
});
// Test tab stops with a notification anchor visible.
// The notification anchor should not get its own tab stop.
add_task(async function testTabStopsWithNotification() {
await BrowserTestUtils.withNewTab(PERMISSIONS_PAGE, async function(aBrowser) {
let popupShown = BrowserTestUtils.waitForEvent(PopupNotifications.panel, "popupshown");
// Request a permission.
BrowserTestUtils.synthesizeMouseAtCenter("#geo", {}, aBrowser);
await popupShown;
startFromUrlBar();
// If the notification anchor were in the tab order, the next shift+tab
// would focus it instead of #identity-box.
await expectFocusAfterKey("Shift+Tab", "identity-box");
});
});
// Test tab stops with the Bookmarks toolbar visible.
add_task(async function testTabStopsWithBookmarksToolbar() {
await BrowserTestUtils.withNewTab("about:blank", async function() {
CustomizableUI.setToolbarVisibility("PersonalToolbar", true);
startFromUrlBar();
await expectFocusAfterKey("Tab", "library-button");
await expectFocusAfterKey("Tab", "PersonalToolbar", true);
await expectFocusAfterKey("Tab", gBrowser.selectedBrowser);
// Make sure the Bookmarks toolbar is no longer tabbable once hidden.
CustomizableUI.setToolbarVisibility("PersonalToolbar", false);
startFromUrlBar();
await expectFocusAfterKey("Tab", "library-button");
await expectFocusAfterKey("Tab", gBrowser.selectedBrowser);
});
});
// Test a focusable toolbartabstop which has no navigable buttons.
add_task(async function testTabStopNoButtons() {
await BrowserTestUtils.withNewTab("about:blank", async function() {
// The Back, Forward and Reload buttons are all currently disabled.
// The Home button is the only other button at that tab stop.
CustomizableUI.removeWidgetFromArea("home-button");
startFromUrlBar();
await expectFocusAfterKey("Shift+Tab", "tabbrowser-tabs", true);
await expectFocusAfterKey("Tab", gURLBar.inputField);
CustomizableUI.reset();
// Make sure the button is reachable now that it has been re-added.
await expectFocusAfterKey("Shift+Tab", "home-button", true);
});
});
// Test that right/left arrows move through toolbarbuttons.
// This also verifies that:
// 1. Right/left arrows do nothing when at the edges; and
// 2. The overflow menu button can't be reached by right arrow when it isn't
// visible.
add_task(async function testArrowsToolbarbuttons() {
await BrowserTestUtils.withNewTab("about:blank", async function() {
startFromUrlBar();
await expectFocusAfterKey("Tab", "library-button");
EventUtils.synthesizeKey("KEY_ArrowLeft");
is(document.activeElement.id, "library-button",
"ArrowLeft at end of button group does nothing");
await expectFocusAfterKey("ArrowRight", "sidebar-button");
// This next check also confirms that the overflow menu button is skipped,
// since it is currently invisible.
await expectFocusAfterKey("ArrowRight", "PanelUI-menu-button");
EventUtils.synthesizeKey("KEY_ArrowRight");
is(document.activeElement.id, "PanelUI-menu-button",
"ArrowRight at end of button group does nothing");
await expectFocusAfterKey("ArrowLeft", "sidebar-button");
await expectFocusAfterKey("ArrowLeft", "library-button");
});
});
// Test that right/left arrows move through buttons wihch aren't toolbarbuttons
// but have role="button".
add_task(async function testArrowsRoleButton() {
await BrowserTestUtils.withNewTab("https://example.com", async function() {
startFromUrlBar();
await expectFocusAfterKey("Tab", "pageActionButton");
await expectFocusAfterKey("ArrowRight", "pocket-button");
await expectFocusAfterKey("ArrowRight", "star-button");
await expectFocusAfterKey("ArrowLeft", "pocket-button");
await expectFocusAfterKey("ArrowLeft", "pageActionButton");
});
});
// Test that right/left arrows do not land on disabled buttons.
add_task(async function testArrowsDisabledButtons() {
await BrowserTestUtils.withNewTab("https://example.com", async function(aBrowser) {
await waitUntilReloadEnabled();
startFromUrlBar();
await expectFocusAfterKey("Shift+Tab", "identity-box");
// Back and Forward buttons are disabled.
await expectFocusAfterKey("Shift+Tab", "reload-button");
EventUtils.synthesizeKey("KEY_ArrowLeft");
is(document.activeElement.id, "reload-button",
"ArrowLeft on Reload button when prior buttons disabled does nothing");
BrowserTestUtils.loadURI(aBrowser, "https://example.com/2");
await BrowserTestUtils.browserLoaded(aBrowser);
await waitUntilReloadEnabled();
startFromUrlBar();
await expectFocusAfterKey("Shift+Tab", "identity-box");
await expectFocusAfterKey("Shift+Tab", "back-button");
// Forward button is still disabled.
await expectFocusAfterKey("ArrowRight", "reload-button");
});
});
// Test that right arrow reaches the overflow menu button when it is visible.
add_task(async function testArrowsOverflowButton() {
await BrowserTestUtils.withNewTab("about:blank", async function() {
// Move something to the overflow menu to make the button appear.
CustomizableUI.addWidgetToArea("home-button", CustomizableUI.AREA_FIXED_OVERFLOW_PANEL);
startFromUrlBar();
await expectFocusAfterKey("Tab", "library-button");
await expectFocusAfterKey("ArrowRight", "sidebar-button");
await expectFocusAfterKey("ArrowRight", "nav-bar-overflow-button");
await expectFocusAfterKey("ArrowRight", "PanelUI-menu-button");
await expectFocusAfterKey("ArrowLeft", "nav-bar-overflow-button");
// Make sure the button is not reachable once it is invisible again.
await expectFocusAfterKey("ArrowRight", "PanelUI-menu-button");
CustomizableUI.reset();
// Flush layout so its invisibility can be detected.
document.getElementById("nav-bar-overflow-button").clientWidth;
await expectFocusAfterKey("ArrowLeft", "sidebar-button");
});
});

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

@ -55,7 +55,8 @@ browser.jar:
content/browser/browser-sidebar.js (content/browser-sidebar.js)
content/browser/browser-siteIdentity.js (content/browser-siteIdentity.js)
content/browser/browser-sync.js (content/browser-sync.js)
content/browser/browser-tabsintitlebar.js (content/browser-tabsintitlebar.js)
content/browser/browser-tabsintitlebar.js (content/browser-tabsintitlebar.js)
content/browser/browser-toolbarKeyNav.js (content/browser-toolbarKeyNav.js)
content/browser/browser-thumbnails.js (content/browser-thumbnails.js)
content/browser/browser-webrender.js (content/browser-webrender.js)
content/browser/tab-content.js (content/tab-content.js)