summaryrefslogtreecommitdiffstats
path: root/browser/base/content/test/keyboard
diff options
context:
space:
mode:
Diffstat (limited to 'browser/base/content/test/keyboard')
-rw-r--r--browser/base/content/test/keyboard/.eslintrc.js5
-rw-r--r--browser/base/content/test/keyboard/browser.ini10
-rw-r--r--browser/base/content/test/keyboard/browser_bookmarks_shortcut.js144
-rw-r--r--browser/base/content/test/keyboard/browser_popup_keyNav.js49
-rw-r--r--browser/base/content/test/keyboard/browser_toolbarButtonKeyPress.js334
-rw-r--r--browser/base/content/test/keyboard/browser_toolbarKeyNav.js431
-rw-r--r--browser/base/content/test/keyboard/focusableContent.html1
-rw-r--r--browser/base/content/test/keyboard/head.js55
8 files changed, 1029 insertions, 0 deletions
diff --git a/browser/base/content/test/keyboard/.eslintrc.js b/browser/base/content/test/keyboard/.eslintrc.js
new file mode 100644
index 0000000000..1779fd7f1c
--- /dev/null
+++ b/browser/base/content/test/keyboard/.eslintrc.js
@@ -0,0 +1,5 @@
+"use strict";
+
+module.exports = {
+ extends: ["plugin:mozilla/browser-test"],
+};
diff --git a/browser/base/content/test/keyboard/browser.ini b/browser/base/content/test/keyboard/browser.ini
new file mode 100644
index 0000000000..355c67e43d
--- /dev/null
+++ b/browser/base/content/test/keyboard/browser.ini
@@ -0,0 +1,10 @@
+[DEFAULT]
+support-files = head.js
+
+[browser_bookmarks_shortcut.js]
+[browser_popup_keyNav.js]
+support-files = focusableContent.html
+[browser_toolbarButtonKeyPress.js]
+skip-if = os == "linux" #Bug 1532501
+[browser_toolbarKeyNav.js]
+support-files = !/browser/base/content/test/permissions/permissions.html
diff --git a/browser/base/content/test/keyboard/browser_bookmarks_shortcut.js b/browser/base/content/test/keyboard/browser_bookmarks_shortcut.js
new file mode 100644
index 0000000000..3a8b050c26
--- /dev/null
+++ b/browser/base/content/test/keyboard/browser_bookmarks_shortcut.js
@@ -0,0 +1,144 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Test the behavior of keypress shortcuts for the bookmarks toolbar.
+ */
+
+// Test that the bookmarks toolbar's visibility is toggled using the bookmarks-shortcut.
+add_task(async function testBookmarksToolbarShortcut() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.toolbars.bookmarks.2h2020", true]],
+ });
+
+ let blankTab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ opening: "example.com",
+ waitForLoad: false,
+ });
+
+ info("Toggle toolbar visibility on");
+ let toolbar = document.getElementById("PersonalToolbar");
+ is(
+ toolbar.getAttribute("collapsed"),
+ "true",
+ "Toolbar bar should already be collapsed"
+ );
+
+ EventUtils.synthesizeKey("b", { shiftKey: true, accelKey: true });
+ toolbar = document.getElementById("PersonalToolbar");
+ await BrowserTestUtils.waitForAttribute("collapsed", toolbar, "false");
+ ok(true, "bookmarks toolbar is visible");
+
+ await testIsBookmarksMenuItemStateChecked("always");
+
+ info("Toggle toolbar visibility off");
+ EventUtils.synthesizeKey("b", { shiftKey: true, accelKey: true });
+ toolbar = document.getElementById("PersonalToolbar");
+ await BrowserTestUtils.waitForAttribute("collapsed", toolbar, "true");
+ ok(true, "bookmarks toolbar is not visible");
+
+ await testIsBookmarksMenuItemStateChecked("never");
+
+ await BrowserTestUtils.removeTab(blankTab);
+});
+
+// Test that the bookmarks library windows opens with the new keyboard shortcut.
+add_task(async function testNewBookmarksLibraryShortcut() {
+ let blankTab = await BrowserTestUtils.openNewForegroundTab({
+ gBrowser,
+ opening: "example.com",
+ waitForLoad: false,
+ });
+
+ info("Check that the bookmarks library windows opens.");
+ let bookmarksLibraryOpened = promiseOpenBookmarksLibrary();
+
+ await EventUtils.synthesizeKey("o", { shiftKey: true, accelKey: true });
+
+ let win = await bookmarksLibraryOpened;
+
+ ok(true, "Bookmarks library successfully opened.");
+
+ win.close();
+
+ await BrowserTestUtils.removeTab(blankTab);
+});
+
+/**
+ * Tests whether or not the bookmarks' menuitem state is checked.
+ */
+async function testIsBookmarksMenuItemStateChecked(expected) {
+ info("Test that the toolbar menuitem state is correct.");
+ let contextMenu = document.getElementById("toolbar-context-menu");
+ let target = document.getElementById("PanelUI-menu-button");
+
+ await openContextMenu(contextMenu, target);
+
+ let menuitems = ["always", "never", "newtab"].map(e =>
+ document.querySelector(`menuitem[data-visibility-enum="${e}"]`)
+ );
+
+ let checkedItem = menuitems.filter(m => m.getAttribute("checked") == "true");
+ is(checkedItem.length, 1, "should have only one menuitem checked");
+ is(
+ checkedItem[0].dataset.visibilityEnum,
+ expected,
+ `checked menuitem should be ${expected}`
+ );
+
+ for (let menuitem of menuitems) {
+ if (menuitem.dataset.visibilityEnum == expected) {
+ ok(!menuitem.hasAttribute("key"), "dont show shortcut on current state");
+ } else {
+ is(
+ menuitem.hasAttribute("key"),
+ menuitem.dataset.visibilityEnum != "newtab",
+ "shortcut is on the menuitem opposite of the current state excluding newtab"
+ );
+ }
+ }
+
+ await closeContextMenu(contextMenu);
+}
+
+/**
+ * Returns a promise for opening the bookmarks library.
+ */
+async function promiseOpenBookmarksLibrary() {
+ return BrowserTestUtils.domWindowOpened(null, async win => {
+ await BrowserTestUtils.waitForEvent(win, "load");
+ await BrowserTestUtils.waitForCondition(
+ () =>
+ win.document.documentURI ===
+ "chrome://browser/content/places/places.xhtml"
+ );
+ return true;
+ });
+}
+
+/**
+ * Helper for opening the context menu.
+ */
+async function openContextMenu(contextMenu, target) {
+ info("Opening context menu.");
+ EventUtils.synthesizeMouseAtCenter(target, {
+ type: "contextmenu",
+ });
+ await BrowserTestUtils.waitForPopupEvent(contextMenu, "shown");
+ let bookmarksToolbarMenu = document.querySelector("#toggle_PersonalToolbar");
+ let subMenu = bookmarksToolbarMenu.querySelector("menupopup");
+ EventUtils.synthesizeMouseAtCenter(bookmarksToolbarMenu, {});
+ await BrowserTestUtils.waitForPopupEvent(subMenu, "shown");
+}
+
+/**
+ * Helper for closing the context menu.
+ */
+async function closeContextMenu(contextMenu) {
+ info("Closing context menu.");
+ contextMenu.hidePopup();
+ await BrowserTestUtils.waitForPopupEvent(contextMenu, "hidden");
+}
diff --git a/browser/base/content/test/keyboard/browser_popup_keyNav.js b/browser/base/content/test/keyboard/browser_popup_keyNav.js
new file mode 100644
index 0000000000..8c3165c0cd
--- /dev/null
+++ b/browser/base/content/test/keyboard/browser_popup_keyNav.js
@@ -0,0 +1,49 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+const TEST_PATH = getRootDirectory(gTestPath).replace(
+ "chrome://mochitests/content",
+ "http://example.com"
+);
+
+/**
+ * Keyboard navigation has some edgecases in popups because
+ * there is no tabstrip or menubar. Check that tabbing forward
+ * and backward to/from the content document works:
+ */
+add_task(async function test_popup_keynav() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.toolbars.keyboard_navigation", true],
+ ["accessibility.tabfocus", 7],
+ ],
+ });
+
+ const kURL = TEST_PATH + "focusableContent.html";
+ await BrowserTestUtils.withNewTab(kURL, async browser => {
+ let windowPromise = BrowserTestUtils.waitForNewWindow({
+ url: kURL,
+ });
+ SpecialPowers.spawn(browser, [], () => {
+ content.window.open(
+ content.location.href,
+ "_blank",
+ "height=500,width=500,menubar=no,toolbar=no,status=1,resizable=1"
+ );
+ });
+ let win = await windowPromise;
+ let hamburgerButton = win.document.getElementById("PanelUI-menu-button");
+ forceFocus(hamburgerButton);
+ await expectFocusAfterKey("Tab", win.gBrowser.selectedBrowser, false, win);
+ // Focus the button inside the webpage.
+ EventUtils.synthesizeKey("KEY_Tab", {}, win);
+ // Focus the first item in the URL bar
+ let firstButton = win.document
+ .getElementById("urlbar-container")
+ .querySelector("toolbarbutton,[role=button]");
+ await expectFocusAfterKey("Tab", firstButton, false, win);
+ await BrowserTestUtils.closeWindow(win);
+ });
+});
diff --git a/browser/base/content/test/keyboard/browser_toolbarButtonKeyPress.js b/browser/base/content/test/keyboard/browser_toolbarButtonKeyPress.js
new file mode 100644
index 0000000000..95431b43dc
--- /dev/null
+++ b/browser/base/content/test/keyboard/browser_toolbarButtonKeyPress.js
@@ -0,0 +1,334 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Test the behavior of key presses on various toolbar buttons.
+ */
+
+function waitForLocationChange() {
+ let promise = new Promise(resolve => {
+ let wpl = {
+ onLocationChange(aWebProgress, aRequest, aLocation) {
+ gBrowser.removeProgressListener(wpl);
+ resolve();
+ },
+ };
+ gBrowser.addProgressListener(wpl);
+ });
+ 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() {
+ let button = document.getElementById("PanelUI-menu-button");
+ forceFocus(button);
+ let focused = BrowserTestUtils.waitForEvent(
+ window.PanelUI.mainView,
+ "focus",
+ true
+ );
+ EventUtils.synthesizeKey(" ");
+ await focused;
+ ok(true, "Focus inside app menu after toolbar button pressed");
+ let hidden = BrowserTestUtils.waitForEvent(
+ window.PanelUI.panel,
+ "popuphidden"
+ );
+ EventUtils.synthesizeKey("KEY_Escape");
+ await hidden;
+});
+
+// Test that the app menu doesn't open when a key other than space or enter is
+// pressed .
+add_task(async function testAppMenuButtonWrongKey() {
+ let button = document.getElementById("PanelUI-menu-button");
+ forceFocus(button);
+ EventUtils.synthesizeKey("KEY_Tab");
+ await TestUtils.waitForTick();
+ is(window.PanelUI.panel.state, "closed", "App menu is closed after tab");
+});
+
+// Test activation of the Library button from the keyboard.
+// The Library menu should appear and focus should move inside it.
+add_task(async function testLibraryButtonPress() {
+ let button = document.getElementById("library-button");
+ forceFocus(button);
+ EventUtils.synthesizeKey(" ");
+ let view = document.getElementById("appMenu-libraryView");
+ let focused = BrowserTestUtils.waitForEvent(view, "focus", true);
+ await focused;
+ ok(true, "Focus inside Library menu after toolbar button pressed");
+ let hidden = BrowserTestUtils.waitForEvent(document, "popuphidden", true);
+ view.closest("panel").hidePopup();
+ await hidden;
+});
+
+// Test activation of the Developer button from the keyboard.
+// This is a customizable widget of type "view".
+// The Developer menu should appear and focus should move inside it.
+add_task(async function testDeveloperButtonPress() {
+ CustomizableUI.addWidgetToArea(
+ "developer-button",
+ CustomizableUI.AREA_NAVBAR
+ );
+ let button = document.getElementById("developer-button");
+ forceFocus(button);
+ EventUtils.synthesizeKey(" ");
+ let view = document.getElementById("PanelUI-developer");
+ let focused = BrowserTestUtils.waitForEvent(view, "focus", true);
+ await focused;
+ ok(true, "Focus inside Developer menu after toolbar button pressed");
+ let hidden = BrowserTestUtils.waitForEvent(document, "popuphidden", true);
+ view.closest("panel").hidePopup();
+ 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() {
+ CustomizableUI.addWidgetToArea(
+ "developer-button",
+ CustomizableUI.AREA_NAVBAR
+ );
+ let button = document.getElementById("developer-button");
+ forceFocus(button);
+ EventUtils.synthesizeKey("KEY_Tab");
+ await TestUtils.waitForTick();
+ let panel = document.getElementById("PanelUI-developer").closest("panel");
+ ok(!panel || panel.state == "closed", "Developer menu not open after tab");
+ CustomizableUI.reset();
+});
+
+// Test activation of the Page actions button from the keyboard.
+// The Page Actions menu should appear and focus should move inside it.
+add_task(async function testPageActionsButtonPress() {
+ await BrowserTestUtils.withNewTab("https://example.com", async function() {
+ let button = document.getElementById("pageActionButton");
+ forceFocus(button);
+ EventUtils.synthesizeKey(" ");
+ let view = document.getElementById("pageActionPanelMainView");
+ let focused = BrowserTestUtils.waitForEvent(view, "focus", true);
+ await focused;
+ ok(true, "Focus inside Page Actions menu after toolbar button pressed");
+ let hidden = BrowserTestUtils.waitForEvent(document, "popuphidden", true);
+ view.closest("panel").hidePopup();
+ await hidden;
+ });
+});
+
+// Test activation of the Back and Forward buttons from the keyboard.
+add_task(async function testBackForwardButtonPress() {
+ await BrowserTestUtils.withNewTab("https://example.com/1", async function(
+ aBrowser
+ ) {
+ BrowserTestUtils.loadURI(aBrowser, "https://example.com/2");
+
+ await BrowserTestUtils.browserLoaded(aBrowser);
+ let backButton = document.getElementById("back-button");
+ forceFocus(backButton);
+ let onLocationChange = waitForLocationChange();
+ EventUtils.synthesizeKey(" ");
+ await onLocationChange;
+ ok(true, "Location changed after back button pressed");
+
+ let forwardButton = document.getElementById("forward-button");
+ forceFocus(forwardButton);
+ onLocationChange = waitForLocationChange();
+ EventUtils.synthesizeKey(" ");
+ await onLocationChange;
+ ok(true, "Location changed after forward button pressed");
+ });
+});
+
+// Test activation of the Send Tab to Device button from the keyboard.
+// This is a page action button built at runtime by PageActions.
+// The Send Tab to Device menu should appear and focus should move inside it.
+add_task(async function testSendTabToDeviceButtonPress() {
+ await BrowserTestUtils.withNewTab("https://example.com", async function() {
+ PageActions.actionForID("sendToDevice").pinnedToUrlbar = true;
+ let button = document.getElementById("pageAction-urlbar-sendToDevice");
+ forceFocus(button);
+ let mainPopupSet = document.getElementById("mainPopupSet");
+ let focused = BrowserTestUtils.waitForEvent(mainPopupSet, "focus", true);
+ EventUtils.synthesizeKey(" ");
+ await focused;
+ let view = document.getElementById(
+ "pageAction-urlbar-sendToDevice-subview"
+ );
+ ok(
+ view.contains(document.activeElement),
+ "Focus inside Page Actions menu after toolbar button pressed"
+ );
+ let hidden = BrowserTestUtils.waitForEvent(document, "popuphidden", true);
+ view.closest("panel").hidePopup();
+ await hidden;
+ 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);
+ StarUI._createPanelIfNeeded();
+ let panel = document.getElementById("editBookmarkPanel");
+ let focused = BrowserTestUtils.waitForEvent(panel, "focus", true);
+ // The button ignores activation while the bookmarked status is being
+ // updated. So, wait for it to finish updating.
+ await TestUtils.waitForCondition(
+ () => BookmarkingUI.status != BookmarkingUI.STATUS_UPDATING
+ );
+ 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();
+});
+
+// Test activation of the overflow button from the keyboard.
+// The overflow menu should appear and focus should move inside it.
+add_task(async function testOverflowButtonPress() {
+ // Move something to the overflow menu to make the button appear.
+ CustomizableUI.addWidgetToArea(
+ "developer-button",
+ CustomizableUI.AREA_FIXED_OVERFLOW_PANEL
+ );
+ let button = document.getElementById("nav-bar-overflow-button");
+ forceFocus(button);
+ let view = document.getElementById("widget-overflow-mainView");
+ let focused = BrowserTestUtils.waitForEvent(view, "focus", true);
+ EventUtils.synthesizeKey(" ");
+ await focused;
+ ok(true, "Focus inside overflow menu after toolbar button pressed");
+ let panel = document.getElementById("widget-overflow");
+ let hidden = BrowserTestUtils.waitForEvent(panel, "popuphidden");
+ panel.hidePopup();
+ await hidden;
+ CustomizableUI.reset();
+});
+
+// Test activation of the Downloads button from the keyboard.
+// The Downloads panel should appear and focus should move inside it.
+add_task(async function testDownloadsButtonPress() {
+ DownloadsButton.unhide();
+ let button = document.getElementById("downloads-button");
+ forceFocus(button);
+ let panel = document.getElementById("downloadsPanel");
+ let focused = BrowserTestUtils.waitForEvent(panel, "focus", true);
+ EventUtils.synthesizeKey(" ");
+ await focused;
+ ok(true, "Focus inside Downloads panel after toolbar button pressed");
+ let hidden = BrowserTestUtils.waitForEvent(panel, "popuphidden");
+ panel.hidePopup();
+ await hidden;
+ DownloadsButton.hide();
+});
+
+// Test activation of the Save to Pocket button from the keyboard.
+// This is a page action button which shows an iframe (wantsIframe: true).
+// The Pocket iframe should appear and focus should move inside it.
+add_task(async function testPocketButtonPress() {
+ await BrowserTestUtils.withNewTab("https://example.com", async function(
+ aBrowser
+ ) {
+ let button = document.getElementById("pocket-button");
+ forceFocus(button);
+ // The panel is created on the fly, so we can't simply wait for focus
+ // inside it.
+ let showing = BrowserTestUtils.waitForEvent(document, "popupshowing", true);
+ EventUtils.synthesizeKey(" ");
+ let event = await showing;
+ let panel = event.target;
+ is(panel.id, "pageActionActivatedActionPanel");
+ let focused = BrowserTestUtils.waitForEvent(panel, "focus", true);
+ await focused;
+ is(
+ document.activeElement.tagName,
+ "iframe",
+ "Focus inside Pocket iframe after Bookmark button pressed"
+ );
+ let hidden = BrowserTestUtils.waitForEvent(panel, "popuphidden");
+ EventUtils.synthesizeKey("KEY_Escape");
+ await hidden;
+ });
+});
diff --git a/browser/base/content/test/keyboard/browser_toolbarKeyNav.js b/browser/base/content/test/keyboard/browser_toolbarKeyNav.js
new file mode 100644
index 0000000000..580a92f740
--- /dev/null
+++ b/browser/base/content/test/keyboard/browser_toolbarKeyNav.js
@@ -0,0 +1,431 @@
+/* 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";
+
+// The DevEdition has the DevTools button in the toolbar by default. Remove it
+// to prevent branch-specific rules what button should be focused.
+function resetToolbarWithoutDevEditionButtons() {
+ CustomizableUI.reset();
+ CustomizableUI.removeWidgetFromArea("developer-button");
+}
+
+function startFromUrlBar(aWindow = window) {
+ aWindow.gURLBar.focus();
+ is(
+ aWindow.document.activeElement,
+ aWindow.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);
+}
+
+// Opens a new, blank tab, executes a task and closes the tab.
+function withNewBlankTab(taskFn) {
+ return BrowserTestUtils.withNewTab("about:blank", async function() {
+ // For a blank tab, the Reload button should be disabled. However, when we
+ // open about:blank with BrowserTestUtils.withNewTab, this is unreliable.
+ // Therefore, explicitly disable the reload command.
+ // We disable the command (rather than disabling the button directly) so the
+ // button will be updated correctly for future page loads.
+ document.getElementById("Browser:Reload").setAttribute("disabled", "true");
+ await taskFn();
+ });
+}
+
+const BOOKMARKS_COUNT = 100;
+
+add_task(async function setup() {
+ await SpecialPowers.pushPrefEnv({
+ set: [
+ ["browser.toolbars.keyboard_navigation", true],
+ ["accessibility.tabfocus", 7],
+ ],
+ });
+ resetToolbarWithoutDevEditionButtons();
+
+ await PlacesUtils.bookmarks.eraseEverything();
+ // Add bookmarks.
+ let bookmarks = new Array(BOOKMARKS_COUNT);
+ for (let i = 0; i < BOOKMARKS_COUNT; ++i) {
+ bookmarks[i] = { url: `http://test.places.${i}/` };
+ }
+ await PlacesUtils.bookmarks.insertTree({
+ guid: PlacesUtils.bookmarks.toolbarGuid,
+ children: bookmarks,
+ });
+});
+
+// Test tab stops with no page loaded.
+add_task(async function testTabStopsNoPage() {
+ await withNewBlankTab(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",
+ "tracking-protection-icon-container"
+ );
+ await expectFocusAfterKey("Shift+Tab", "reload-button");
+ await expectFocusAfterKey("Shift+Tab", "tabbrowser-tabs", true);
+ await expectFocusAfterKey("Tab", "reload-button");
+ await expectFocusAfterKey("Tab", "tracking-protection-icon-container");
+ 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 #tracking-protection-icon-container.
+ await expectFocusAfterKey(
+ "Shift+Tab",
+ "tracking-protection-icon-container"
+ );
+ });
+});
+
+// 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 withNewBlankTab(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);
+ resetToolbarWithoutDevEditionButtons();
+ // 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");
+ await expectFocusAfterKey("ArrowRight", "fxa-toolbar-menu-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", "fxa-toolbar-menu-button");
+ 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",
+ "tracking-protection-icon-container"
+ );
+ // 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",
+ "tracking-protection-icon-container"
+ );
+ 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", "fxa-toolbar-menu-button");
+ await expectFocusAfterKey("ArrowRight", "nav-bar-overflow-button");
+ // Make sure the button is not reachable once it is invisible again.
+ await expectFocusAfterKey("ArrowRight", "PanelUI-menu-button");
+ resetToolbarWithoutDevEditionButtons();
+ // Flush layout so its invisibility can be detected.
+ document.getElementById("nav-bar-overflow-button").clientWidth;
+ await expectFocusAfterKey("ArrowLeft", "fxa-toolbar-menu-button");
+ });
+});
+
+// Test that toolbar keyboard navigation doesn't interfere with PanelMultiView
+// keyboard navigation.
+// We do this by opening the Library menu and ensuring that pressing left arrow
+// does nothing.
+add_task(async function testArrowsInPanelMultiView() {
+ let button = document.getElementById("library-button");
+ forceFocus(button);
+ EventUtils.synthesizeKey(" ");
+ let view = document.getElementById("appMenu-libraryView");
+ let focused = BrowserTestUtils.waitForEvent(view, "focus", true);
+ let focusEvt = await focused;
+ ok(true, "Focus inside Library menu after toolbar button pressed");
+ EventUtils.synthesizeKey("KEY_ArrowLeft");
+ is(
+ document.activeElement,
+ focusEvt.target,
+ "ArrowLeft inside panel does nothing"
+ );
+ let hidden = BrowserTestUtils.waitForEvent(document, "popuphidden", true);
+ view.closest("panel").hidePopup();
+ await hidden;
+});
+
+// Test that right/left arrows move in the expected direction for RTL locales.
+add_task(async function testArrowsRtl() {
+ await SpecialPowers.pushPrefEnv({ set: [["intl.l10n.pseudo", "bidi"]] });
+ // window.RTL_UI doesn't update in existing windows when this pref is changed,
+ // so we need to test in a new window.
+ let win = await BrowserTestUtils.openNewBrowserWindow();
+ startFromUrlBar(win);
+ await expectFocusAfterKey("Tab", "library-button", false, win);
+ EventUtils.synthesizeKey("KEY_ArrowRight", {}, win);
+ is(
+ win.document.activeElement.id,
+ "library-button",
+ "ArrowRight at end of button group does nothing"
+ );
+ await expectFocusAfterKey("ArrowLeft", "sidebar-button", false, win);
+ await BrowserTestUtils.closeWindow(win);
+ await SpecialPowers.popPrefEnv();
+});
+
+// Test that right arrow reaches the overflow menu button on the Bookmarks
+// toolbar when it is visible.
+add_task(async function testArrowsBookmarksOverflowButton() {
+ let toolbarOpened = TestUtils.waitForCondition(() => {
+ let toolbar = gNavToolbox.querySelector("#PersonalToolbar");
+ return !toolbar.collapsed;
+ }, "waiting for toolbar to become visible");
+ CustomizableUI.setToolbarVisibility("PersonalToolbar", true);
+ await toolbarOpened;
+ let items = document.getElementById("PlacesToolbarItems").children;
+ let lastVisible;
+ for (let item of items) {
+ if (item.style.visibility == "hidden") {
+ break;
+ }
+ lastVisible = item;
+ }
+ forceFocus(lastVisible);
+ await expectFocusAfterKey("ArrowRight", "PlacesChevron");
+ CustomizableUI.setToolbarVisibility("PersonalToolbar", false);
+});
+
+registerCleanupFunction(async function() {
+ CustomizableUI.reset();
+ await PlacesUtils.bookmarks.eraseEverything();
+});
+
+// Test that when a toolbar button opens a panel, closing the panel restores
+// focus to the button which opened it.
+add_task(async function testPanelCloseRestoresFocus() {
+ await withNewBlankTab(async function() {
+ // We can't use forceFocus because that removes focusability immediately.
+ // Instead, we must let ToolbarKeyboardNavigator handle this properly.
+ startFromUrlBar();
+ await expectFocusAfterKey("Tab", "library-button");
+ let view = document.getElementById("appMenu-libraryView");
+ let shown = BrowserTestUtils.waitForEvent(view, "ViewShown");
+ EventUtils.synthesizeKey(" ");
+ await shown;
+ let hidden = BrowserTestUtils.waitForEvent(document, "popuphidden", true);
+ view.closest("panel").hidePopup();
+ await hidden;
+ is(
+ document.activeElement.id,
+ "library-button",
+ "Focus restored to Library button after panel closed"
+ );
+ });
+});
+
+// Test that the arrow key works in the group of the
+// 'tracking-protection-icon-container' and the 'identity-box'.
+add_task(async function testArrowKeyForTPIconContainerandIdentityBox() {
+ await BrowserTestUtils.withNewTab("https://example.com", async function() {
+ await waitUntilReloadEnabled();
+ startFromUrlBar();
+ await expectFocusAfterKey(
+ "Shift+Tab",
+ "tracking-protection-icon-container"
+ );
+ await expectFocusAfterKey("ArrowRight", "identity-box");
+ await expectFocusAfterKey(
+ "ArrowLeft",
+ "tracking-protection-icon-container"
+ );
+ });
+});
+
+// Test navigation by typed characters.
+add_task(async function testCharacterNavigation() {
+ await BrowserTestUtils.withNewTab("https://example.com", async function() {
+ await waitUntilReloadEnabled();
+ startFromUrlBar();
+ await expectFocusAfterKey("Tab", "pageActionButton");
+ await expectFocusAfterKey("h", "home-button");
+ // There's no button starting with "hs", so pressing s should do nothing.
+ EventUtils.synthesizeKey("s");
+ is(
+ document.activeElement.id,
+ "home-button",
+ "home-button still focused after s pressed"
+ );
+ // Escape should reset the search.
+ EventUtils.synthesizeKey("KEY_Escape");
+ // Now that the search is reset, pressing s should focus Save to Pocket.
+ await expectFocusAfterKey("s", "pocket-button");
+ // Pressing i makes the search "si", so it should focus Sidebars.
+ await expectFocusAfterKey("i", "sidebar-button");
+ // Reset the search.
+ EventUtils.synthesizeKey("KEY_Escape");
+ await expectFocusAfterKey("s", "pocket-button");
+ // Pressing s again should find the next button starting with s: Sidebars.
+ await expectFocusAfterKey("s", "sidebar-button");
+ });
+});
+
+// Test that toolbar character navigation doesn't trigger in PanelMultiView for
+// a panel anchored to the toolbar.
+// We do this by opening the Library menu and ensuring that pressing s
+// does nothing.
+// This test should be removed if PanelMultiView implements character
+// navigation.
+add_task(async function testCharacterInPanelMultiView() {
+ let button = document.getElementById("library-button");
+ forceFocus(button);
+ let view = document.getElementById("appMenu-libraryView");
+ let focused = BrowserTestUtils.waitForEvent(view, "focus", true);
+ EventUtils.synthesizeKey(" ");
+ let focusEvt = await focused;
+ ok(true, "Focus inside Library menu after toolbar button pressed");
+ EventUtils.synthesizeKey("s");
+ is(document.activeElement, focusEvt.target, "s inside panel does nothing");
+ let hidden = BrowserTestUtils.waitForEvent(document, "popuphidden", true);
+ view.closest("panel").hidePopup();
+ await hidden;
+});
+
+// Test tab stops after the search bar is added.
+add_task(async function testTabStopsAfterSearchBarAdded() {
+ await SpecialPowers.pushPrefEnv({
+ set: [["browser.search.widget.inNavBar", 1]],
+ });
+ await withNewBlankTab(async function() {
+ startFromUrlBar();
+ await expectFocusAfterKey("Tab", "searchbar", true);
+ await expectFocusAfterKey("Tab", "library-button");
+ });
+ await SpecialPowers.popPrefEnv();
+});
diff --git a/browser/base/content/test/keyboard/focusableContent.html b/browser/base/content/test/keyboard/focusableContent.html
new file mode 100644
index 0000000000..255512645c
--- /dev/null
+++ b/browser/base/content/test/keyboard/focusableContent.html
@@ -0,0 +1 @@
+<button>Just a button here to have something focusable.</button>
diff --git a/browser/base/content/test/keyboard/head.js b/browser/base/content/test/keyboard/head.js
new file mode 100644
index 0000000000..9d6f901f2c
--- /dev/null
+++ b/browser/base/content/test/keyboard/head.js
@@ -0,0 +1,55 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Force focus to an element that isn't focusable.
+ * Toolbar buttons aren't focusable because if they were, clicking them would
+ * focus them, which is undesirable. Therefore, they're only made focusable
+ * when a user is navigating with the keyboard. This function forces focus as
+ * is done during toolbar keyboard navigation.
+ */
+function forceFocus(aElem) {
+ aElem.setAttribute("tabindex", "-1");
+ aElem.focus();
+ aElem.removeAttribute("tabindex");
+}
+
+async function expectFocusAfterKey(
+ aKey,
+ aFocus,
+ aAncestorOk = false,
+ aWindow = window
+) {
+ 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 = aWindow.document.getElementById(aFocus);
+ friendlyExpected = aFocus;
+ } else {
+ expected = aFocus;
+ if (aFocus == aWindow.gURLBar.inputField) {
+ friendlyExpected = "URL bar input";
+ } else if (aFocus == aWindow.gBrowser.selectedBrowser) {
+ friendlyExpected = "Web document";
+ }
+ }
+ info("Listening on item " + (expected.id || expected.className));
+ let focused = BrowserTestUtils.waitForEvent(expected, "focus", aAncestorOk);
+ EventUtils.synthesizeKey(key, { shiftKey: shift }, aWindow);
+ let receivedEvent = await focused;
+ info(
+ "Got focus on item: " +
+ (receivedEvent.target.id || receivedEvent.target.className)
+ );
+ ok(true, friendlyExpected + " focused after " + aKey + " pressed");
+}