forked from mirrors/gecko-dev
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
219 lines
8.9 KiB
JavaScript
219 lines
8.9 KiB
JavaScript
/* 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");
|
|
});
|
|
});
|