From 26a029d407be480d791972afb5975cf62c9360a6 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Fri, 19 Apr 2024 02:47:55 +0200 Subject: Adding upstream version 124.0.1. Signed-off-by: Daniel Baumann --- browser/base/content/test/keyboard/browser.toml | 22 + .../test/keyboard/browser_bookmarks_shortcut.js | 140 +++++ .../browser_cancel_caret_browsing_in_content.js | 91 +++ .../content/test/keyboard/browser_popup_keyNav.js | 51 ++ .../test/keyboard/browser_toolbarButtonKeyPress.js | 334 +++++++++++ .../content/test/keyboard/browser_toolbarKeyNav.js | 643 +++++++++++++++++++++ browser/base/content/test/keyboard/file_empty.html | 8 + .../content/test/keyboard/focusableContent.html | 1 + browser/base/content/test/keyboard/head.js | 61 ++ 9 files changed, 1351 insertions(+) create mode 100644 browser/base/content/test/keyboard/browser.toml create mode 100644 browser/base/content/test/keyboard/browser_bookmarks_shortcut.js create mode 100644 browser/base/content/test/keyboard/browser_cancel_caret_browsing_in_content.js create mode 100644 browser/base/content/test/keyboard/browser_popup_keyNav.js create mode 100644 browser/base/content/test/keyboard/browser_toolbarButtonKeyPress.js create mode 100644 browser/base/content/test/keyboard/browser_toolbarKeyNav.js create mode 100644 browser/base/content/test/keyboard/file_empty.html create mode 100644 browser/base/content/test/keyboard/focusableContent.html create mode 100644 browser/base/content/test/keyboard/head.js (limited to 'browser/base/content/test/keyboard') diff --git a/browser/base/content/test/keyboard/browser.toml b/browser/base/content/test/keyboard/browser.toml new file mode 100644 index 0000000000..770e1bb39f --- /dev/null +++ b/browser/base/content/test/keyboard/browser.toml @@ -0,0 +1,22 @@ +[DEFAULT] +support-files = ["head.js"] + +["browser_bookmarks_shortcut.js"] +https_first_disabled = true + +["browser_cancel_caret_browsing_in_content.js"] +support-files = ["file_empty.html"] + +["browser_popup_keyNav.js"] +https_first_disabled = true +support-files = ["focusableContent.html"] + +["browser_toolbarButtonKeyPress.js"] +skip-if = [ + "os == 'linux' && (asan || tsan || debug)", # Bug 1775712 + "os == 'mac' && debug", # Bug 1775712 + "os == 'win'", # Bug 1775712 +] + +["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..02aedfaf79 --- /dev/null +++ b/browser/base/content/test/keyboard/browser_bookmarks_shortcut.js @@ -0,0 +1,140 @@ +/* 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() { + 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 TestUtils.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"); + bookmarksToolbarMenu.openMenu(true); + 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_cancel_caret_browsing_in_content.js b/browser/base/content/test/keyboard/browser_cancel_caret_browsing_in_content.js new file mode 100644 index 0000000000..719b92eed6 --- /dev/null +++ b/browser/base/content/test/keyboard/browser_cancel_caret_browsing_in_content.js @@ -0,0 +1,91 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +add_task(async function () { + const kPrefName_CaretBrowsingOn = "accessibility.browsewithcaret"; + await SpecialPowers.pushPrefEnv({ + set: [ + ["accessibility.browsewithcaret_shortcut.enabled", true], + ["accessibility.warn_on_browsewithcaret", false], + ["test.events.async.enabled", true], + [kPrefName_CaretBrowsingOn, false], + ], + }); + + await BrowserTestUtils.withNewTab( + "https://example.com/browser/browser/base/content/test/keyboard/file_empty.html", + async function (browser) { + await SpecialPowers.spawn(browser, [], () => { + content.document.documentElement.scrollTop; // Flush layout. + }); + function promiseFirstAndReplyKeyEvents(aExpectedConsume) { + return new Promise(resolve => { + const eventType = aExpectedConsume ? "keydown" : "keypress"; + let eventCount = 0; + let listener = () => { + if (++eventCount === 2) { + window.removeEventListener(eventType, listener, { + capture: true, + mozSystemGroup: true, + }); + resolve(); + } + }; + window.addEventListener(eventType, listener, { + capture: true, + mozSystemGroup: true, + }); + registerCleanupFunction(() => { + window.removeEventListener(eventType, listener, { + capture: true, + mozSystemGroup: true, + }); + }); + }); + } + let promiseReplyF7KeyEvents = promiseFirstAndReplyKeyEvents(false); + EventUtils.synthesizeKey("KEY_F7"); + info("Waiting reply F7 keypress event..."); + await promiseReplyF7KeyEvents; + await TestUtils.waitForTick(); + is( + Services.prefs.getBoolPref(kPrefName_CaretBrowsingOn), + true, + "F7 key should enable caret browsing mode" + ); + + await SpecialPowers.pushPrefEnv({ + set: [[kPrefName_CaretBrowsingOn, false]], + }); + + await SpecialPowers.spawn(browser, [], () => { + content.document.documentElement.scrollTop; // Flush layout. + content.window.addEventListener( + "keydown", + event => event.preventDefault(), + { capture: true } + ); + }); + promiseReplyF7KeyEvents = promiseFirstAndReplyKeyEvents(true); + EventUtils.synthesizeKey("KEY_F7"); + info("Waiting for reply F7 keydown event..."); + await promiseReplyF7KeyEvents; + try { + info(`Checking reply keypress event is not fired...`); + await TestUtils.waitForCondition( + () => Services.prefs.getBoolPref(kPrefName_CaretBrowsingOn), + "", + 100, // interval + 5 // maxTries + ); + } catch (e) {} + is( + Services.prefs.getBoolPref(kPrefName_CaretBrowsingOn), + false, + "F7 key shouldn't enable caret browsing mode because F7 keydown event is consumed by web content" + ); + } + ); +}); 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..2b381dda4b --- /dev/null +++ b/browser/base/content/test/keyboard/browser_popup_keyNav.js @@ -0,0 +1,51 @@ +/* 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", + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + "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"); + await focusAndActivateElement(hamburgerButton, () => + 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..8640716bab --- /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"; + +const kDevPanelID = "PanelUI-developer-tools"; + +/** + * 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"); + let focused = BrowserTestUtils.waitForEvent( + window.PanelUI.mainView, + "focus", + true + ); + await focusAndActivateElement(button, () => 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"); + await focusAndActivateElement(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() { + CustomizableUI.addWidgetToArea("library-button", "nav-bar"); + let button = document.getElementById("library-button"); + await focusAndActivateElement(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; + CustomizableUI.removeWidgetFromArea("library-button"); +}); + +// 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"); + await focusAndActivateElement(button, () => EventUtils.synthesizeKey(" ")); + let view = document.getElementById(kDevPanelID); + 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"); + await focusAndActivateElement(button, () => + EventUtils.synthesizeKey("KEY_Tab") + ); + await TestUtils.waitForTick(); + let panel = document.getElementById(kDevPanelID).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() { + // The page actions button is not normally visible, so we must + // unhide it. + BrowserPageActions.mainButtonNode.style.visibility = "visible"; + registerCleanupFunction(() => { + BrowserPageActions.mainButtonNode.style.removeProperty("visibility"); + }); + await BrowserTestUtils.withNewTab("https://example.com", async function () { + let button = document.getElementById("pageActionButton"); + await focusAndActivateElement(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.startLoadingURIString(aBrowser, "https://example.com/2"); + + await BrowserTestUtils.browserLoaded(aBrowser); + let backButton = document.getElementById("back-button"); + let onLocationChange = waitForLocationChange(); + await focusAndActivateElement(backButton, () => + EventUtils.synthesizeKey(" ") + ); + await onLocationChange; + ok(true, "Location changed after back button pressed"); + + let forwardButton = document.getElementById("forward-button"); + onLocationChange = waitForLocationChange(); + await focusAndActivateElement(forwardButton, () => + EventUtils.synthesizeKey(" ") + ); + await onLocationChange; + ok(true, "Location changed after forward button pressed"); + } + ); +}); + +// 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/1", + async function (aBrowser) { + info("Waiting for button to be enabled."); + let button = document.getElementById("reload-button"); + await TestUtils.waitForCondition(() => !button.disabled); + let loaded = BrowserTestUtils.browserLoaded(aBrowser); + info("Focusing button"); + await focusAndActivateElement(button, () => { + info("Pressing space on the button"); + EventUtils.synthesizeKey(" "); + }); + info("Waiting for load."); + 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() { + CustomizableUI.addWidgetToArea("sidebar-button", "nav-bar"); + 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"); + await focusAndActivateElement(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); + await focusAndActivateElement(button, () => EventUtils.synthesizeKey(" ")); + await TestUtils.waitForCondition(() => !button.checked); + ok(true, "Sidebars button not checked after press"); + ok(sidebarBox.hidden, "Sidebar hidden after press"); + CustomizableUI.removeWidgetFromArea("sidebar-button"); +}); + +// 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-box"); + 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 + ); + await focusAndActivateElement(button, () => + 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"); + let menu = document.getElementById("BMB_bookmarksPopup"); + let shown = BrowserTestUtils.waitForEvent(menu, "popupshown"); + await focusAndActivateElement(button, () => 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"); + let view = document.getElementById("widget-overflow-mainView"); + let focused = BrowserTestUtils.waitForEvent(view, "focus", true); + await focusAndActivateElement(button, () => 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"); + let panel = document.getElementById("downloadsPanel"); + let focused = BrowserTestUtils.waitForEvent(panel, "focus", true); + await focusAndActivateElement(button, () => 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 customizable widget button which shows an popup panel +// with a browser element to embed the pocket UI into it. +// The Pocket panel 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("save-to-pocket-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 + ); + await focusAndActivateElement(button, () => + EventUtils.synthesizeKey(" ") + ); + let event = await showing; + let panel = event.target; + is(panel.id, "customizationui-widget-panel"); + let focused = BrowserTestUtils.waitForEvent(panel, "focus", true); + await focused; + is( + document.activeElement.tagName, + "browser", + "Focus inside Pocket panel 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..6cd2eee35c --- /dev/null +++ b/browser/base/content/test/keyboard/browser_toolbarKeyNav.js @@ -0,0 +1,643 @@ +/* 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"; +const afterUrlBarButton = "save-to-pocket-button"; + +// 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 AddHomeBesideReload() { + CustomizableUI.addWidgetToArea( + "home-button", + "nav-bar", + CustomizableUI.getPlacementOfWidget("stop-reload-button").position + 1 + ); +} + +function RemoveHomeButton() { + CustomizableUI.removeWidgetFromArea("home-button"); +} + +function AddOldMenuSideButtons() { + // Make the FxA button visible even though we're signed out. + // We'll use oldfxastatus to restore the old state. + document.documentElement.setAttribute( + "oldfxastatus", + document.documentElement.getAttribute("fxastatus") + ); + document.documentElement.setAttribute("fxastatus", "signed_in"); + // The FxA button is supposed to be last, add these buttons before it. + CustomizableUI.addWidgetToArea( + "library-button", + "nav-bar", + CustomizableUI.getWidgetIdsInArea("nav-bar").length - 3 + ); + CustomizableUI.addWidgetToArea( + "sidebar-button", + "nav-bar", + CustomizableUI.getWidgetIdsInArea("nav-bar").length - 3 + ); + CustomizableUI.addWidgetToArea( + "unified-extensions-button", + "nav-bar", + CustomizableUI.getWidgetIdsInArea("nav-bar").length - 3 + ); +} + +function RemoveOldMenuSideButtons() { + CustomizableUI.removeWidgetFromArea("library-button"); + CustomizableUI.removeWidgetFromArea("sidebar-button"); + document.documentElement.setAttribute( + "fxastatus", + document.documentElement.getAttribute("oldfxastatus") + ); + document.documentElement.removeAttribute("oldfxastatus"); +} + +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(); + }); +} + +function removeFirefoxViewButton() { + CustomizableUI.removeWidgetFromArea("firefox-view-button"); +} + +const BOOKMARKS_COUNT = 100; + +add_setup(async function () { + 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) { + // eslint-disable-next-line @microsoft/sdl/no-insecure-url + bookmarks[i] = { url: `http://test.places.${i}y/` }; + } + await PlacesUtils.bookmarks.insertTree({ + guid: PlacesUtils.bookmarks.toolbarGuid, + children: bookmarks, + }); + + // The page actions button is not normally visible, so we must + // unhide it. + BrowserPageActions.mainButtonNode.style.visibility = "visible"; + registerCleanupFunction(() => { + BrowserPageActions.mainButtonNode.style.removeProperty("visibility"); + }); +}); + +// Test tab stops with no page loaded. +add_task(async function testTabStopsNoPageWithHomeButton() { + AddHomeBesideReload(); + await withNewBlankTab(async function () { + startFromUrlBar(); + await expectFocusAfterKey("Shift+Tab", "home-button"); + await expectFocusAfterKey("Shift+Tab", "tabs-newtab-button"); + await expectFocusAfterKey("Shift+Tab", gBrowser.selectedTab); + await expectFocusAfterKey("Tab", "tabs-newtab-button"); + await expectFocusAfterKey("Tab", "home-button"); + await expectFocusAfterKey("Tab", gURLBar.inputField); + await expectFocusAfterKey("Tab", afterUrlBarButton); + await expectFocusAfterKey("Tab", gBrowser.selectedBrowser); + }); + RemoveHomeButton(); +}); + +async function doTestTabStopsPageLoaded(aPageActionsVisible) { + info(`doTestTabStopsPageLoaded(${aPageActionsVisible})`); + + BrowserPageActions.mainButtonNode.style.visibility = aPageActionsVisible + ? "visible" + : ""; + 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", "tabs-newtab-button"); + await expectFocusAfterKey("Shift+Tab", gBrowser.selectedTab); + await expectFocusAfterKey("Tab", "tabs-newtab-button"); + await expectFocusAfterKey("Tab", "reload-button"); + await expectFocusAfterKey("Tab", "tracking-protection-icon-container"); + await expectFocusAfterKey("Tab", gURLBar.inputField); + await expectFocusAfterKey( + "Tab", + aPageActionsVisible ? "pageActionButton" : "star-button-box" + ); + await expectFocusAfterKey("Tab", afterUrlBarButton); + await expectFocusAfterKey("Tab", gBrowser.selectedBrowser); + }); +} + +// Test tab stops with a page loaded. +add_task(async function testTabStopsPageLoaded() { + is( + BrowserPageActions.mainButtonNode.style.visibility, + "visible", + "explicitly shown at the beginning of test" + ); + await doTestTabStopsPageLoaded(false); + await doTestTabStopsPageLoaded(true); +}); + +// 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", afterUrlBarButton); + 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", afterUrlBarButton); + 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", "tabs-newtab-button"); + await expectFocusAfterKey("Tab", gURLBar.inputField); + resetToolbarWithoutDevEditionButtons(); + AddHomeBesideReload(); + // Make sure the button is reachable now that it has been re-added. + await expectFocusAfterKey("Shift+Tab", "home-button", true); + RemoveHomeButton(); + }); +}); + +// 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() { + AddOldMenuSideButtons(); + await BrowserTestUtils.withNewTab("about:blank", async function () { + startFromUrlBar(); + await expectFocusAfterKey("Tab", afterUrlBarButton); + EventUtils.synthesizeKey("KEY_ArrowLeft"); + is( + document.activeElement.id, + afterUrlBarButton, + "ArrowLeft at end of button group does nothing" + ); + await expectFocusAfterKey("ArrowRight", "library-button"); + await expectFocusAfterKey("ArrowRight", "sidebar-button"); + await expectFocusAfterKey("ArrowRight", "unified-extensions-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", "unified-extensions-button"); + await expectFocusAfterKey("ArrowLeft", "sidebar-button"); + await expectFocusAfterKey("ArrowLeft", "library-button"); + }); + RemoveOldMenuSideButtons(); +}); + +// Test that right/left arrows move through buttons which 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", "star-button-box"); + 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.startLoadingURIString(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() { + AddOldMenuSideButtons(); + 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", afterUrlBarButton); + await expectFocusAfterKey("ArrowRight", "library-button"); + await expectFocusAfterKey("ArrowRight", "sidebar-button"); + await expectFocusAfterKey("ArrowRight", "unified-extensions-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; + // We reset the toolbar above so the unified extensions button is now the + // "last" button. + await expectFocusAfterKey("ArrowLeft", "unified-extensions-button"); + await expectFocusAfterKey("ArrowLeft", "fxa-toolbar-menu-button"); + }); + RemoveOldMenuSideButtons(); +}); + +// 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() { + AddOldMenuSideButtons(); + let button = document.getElementById("library-button"); + await focusAndActivateElement(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; + RemoveOldMenuSideButtons(); +}); + +// Test that right/left arrows move in the expected direction for RTL locales. +add_task(async function testArrowsRtl() { + AddOldMenuSideButtons(); + 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", afterUrlBarButton, false, win); + EventUtils.synthesizeKey("KEY_ArrowRight", {}, win); + is( + win.document.activeElement.id, + afterUrlBarButton, + "ArrowRight at end of button group does nothing" + ); + await expectFocusAfterKey("ArrowLeft", "library-button", false, win); + await expectFocusAfterKey("ArrowLeft", "sidebar-button", false, win); + await BrowserTestUtils.closeWindow(win); + await SpecialPowers.popPrefEnv(); + RemoveOldMenuSideButtons(); +}); + +// Test that right arrow reaches the overflow menu button on the Bookmarks +// toolbar when it is visible. +add_task(async function testArrowsBookmarksOverflowButton() { + let toolbar = gNavToolbox.querySelector("#PersonalToolbar"); + // Third parameter is 'persist' and true is the default. + // Fourth parameter is 'animated' and we want no animation. + setToolbarVisibility(toolbar, true, true, false); + Assert.ok(!toolbar.collapsed, "toolbar should be visible"); + + await BrowserTestUtils.waitForEvent( + toolbar, + "BookmarksToolbarVisibilityUpdated" + ); + let items = document.getElementById("PlacesToolbarItems").children; + let lastVisible; + for (let item of items) { + if (item.style.visibility == "hidden") { + break; + } + lastVisible = item; + } + await focusAndActivateElement(lastVisible, () => + expectFocusAfterKey("ArrowRight", "PlacesChevron") + ); + setToolbarVisibility(toolbar, false, true, 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() { + AddOldMenuSideButtons(); + 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", afterUrlBarButton); + await expectFocusAfterKey("ArrowRight", "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" + ); + }); + RemoveOldMenuSideButtons(); +}); + +// 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 (browser) { + // Simulate geo sharing so the permission box shows + gBrowser.updateBrowserSharing(browser, { geo: true }); + await waitUntilReloadEnabled(); + startFromUrlBar(); + await expectFocusAfterKey( + "Shift+Tab", + "tracking-protection-icon-container" + ); + await expectFocusAfterKey("ArrowRight", "identity-icon-box"); + await expectFocusAfterKey("ArrowRight", "identity-permission-box"); + await expectFocusAfterKey("ArrowLeft", "identity-icon-box"); + await expectFocusAfterKey( + "ArrowLeft", + "tracking-protection-icon-container" + ); + gBrowser.updateBrowserSharing(browser, { geo: false }); + } + ); +}); + +// Test navigation by typed characters. +add_task(async function testCharacterNavigation() { + AddHomeBesideReload(); + AddOldMenuSideButtons(); + 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", "save-to-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", "save-to-pocket-button"); + // Pressing s again should find the next button starting with s: Sidebars. + await expectFocusAfterKey("s", "sidebar-button"); + }); + RemoveHomeButton(); + RemoveOldMenuSideButtons(); +}); + +// 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() { + AddOldMenuSideButtons(); + let button = document.getElementById("library-button"); + let view = document.getElementById("appMenu-libraryView"); + let focused = BrowserTestUtils.waitForEvent(view, "focus", true); + await focusAndActivateElement(button, () => 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; + RemoveOldMenuSideButtons(); +}); + +// Test tab stops after the search bar is added. +add_task(async function testTabStopsAfterSearchBarAdded() { + AddOldMenuSideButtons(); + await SpecialPowers.pushPrefEnv({ + set: [["browser.search.widget.inNavBar", 1]], + }); + await withNewBlankTab(async function () { + startFromUrlBar(); + await expectFocusAfterKey("Tab", "searchbar", true); + await expectFocusAfterKey("Tab", afterUrlBarButton); + await expectFocusAfterKey("ArrowRight", "library-button"); + await expectFocusAfterKey("ArrowLeft", afterUrlBarButton); + await expectFocusAfterKey("Shift+Tab", "searchbar", true); + await expectFocusAfterKey("Shift+Tab", gURLBar.inputField); + }); + await SpecialPowers.popPrefEnv(); + RemoveOldMenuSideButtons(); +}); + +// Test tab navigation when the Firefox View button is present +// and when the button is not present. +add_task(async function testFirefoxViewButtonNavigation() { + // Add enough tabs so that the new-tab-button appears in the toolbar + // and the tabs-newtab-button is hidden. + await BrowserTestUtils.overflowTabs(registerCleanupFunction, window); + + // Assert that Firefox View button receives focus when tab navigating + // forward from the end of web content. + // Additionally, ensure that focus is not trapped between the + // selected tab and the new-tab button. + // Finally, assert that focus is restored to web content when + // navigating backwards from the Firefox View button. + await BrowserTestUtils.withNewTab( + PERMISSIONS_PAGE, + async function (aBrowser) { + await SpecialPowers.spawn(aBrowser, [], async () => { + content.document.querySelector("#camera").focus(); + }); + + await expectFocusAfterKey("Tab", "firefox-view-button"); + let selectedTab = document.querySelector("tab[selected]"); + await expectFocusAfterKey("Tab", selectedTab); + await expectFocusAfterKey("Tab", "new-tab-button"); + await expectFocusAfterKey("Shift+Tab", selectedTab); + await expectFocusAfterKey("Shift+Tab", "firefox-view-button"); + + // Moving from toolbar back into content + EventUtils.synthesizeKey("KEY_Tab", { shiftKey: true }); + await SpecialPowers.spawn(aBrowser, [], async () => { + let activeElement = content.document.activeElement; + let expectedElement = content.document.querySelector("#camera"); + is( + activeElement, + expectedElement, + "Focus should be returned to the 'camera' button" + ); + }); + } + ); + + // Assert that the selected tab receives focus before the new-tab button + // if there is no Firefox View button. + // Additionally, assert that navigating backwards from the selected tab + // restores focus to the last element in the web content. + await BrowserTestUtils.withNewTab( + PERMISSIONS_PAGE, + async function (aBrowser) { + removeFirefoxViewButton(); + + await SpecialPowers.spawn(aBrowser, [], async () => { + content.document.querySelector("#camera").focus(); + }); + + let selectedTab = document.querySelector("tab[selected]"); + await expectFocusAfterKey("Tab", selectedTab); + await expectFocusAfterKey("Tab", "new-tab-button"); + await expectFocusAfterKey("Shift+Tab", selectedTab); + + // Moving from toolbar back into content + EventUtils.synthesizeKey("KEY_Tab", { shiftKey: true }); + await SpecialPowers.spawn(aBrowser, [], async () => { + let activeElement = content.document.activeElement; + let expectedElement = content.document.querySelector("#camera"); + is( + activeElement, + expectedElement, + "Focus should be returned to the 'camera' button" + ); + }); + } + ); + + // Clean up extra tabs + while (gBrowser.tabs.length > 1) { + BrowserTestUtils.removeTab(gBrowser.tabs[0]); + } + CustomizableUI.reset(); +}); diff --git a/browser/base/content/test/keyboard/file_empty.html b/browser/base/content/test/keyboard/file_empty.html new file mode 100644 index 0000000000..d2b0361f09 --- /dev/null +++ b/browser/base/content/test/keyboard/file_empty.html @@ -0,0 +1,8 @@ + + + + Page left intentionally blank... + + + + 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 @@ + diff --git a/browser/base/content/test/keyboard/head.js b/browser/base/content/test/keyboard/head.js new file mode 100644 index 0000000000..4f87109b32 --- /dev/null +++ b/browser/base/content/test/keyboard/head.js @@ -0,0 +1,61 @@ +/* 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. + * It then runs the `activateMethod` passed in, and restores usual focus state + * afterwards. `activateMethod` can be async. + */ +async function focusAndActivateElement(elem, activateMethod) { + elem.setAttribute("tabindex", "-1"); + elem.focus(); + try { + await activateMethod(elem); + } finally { + elem.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"); +} -- cgit v1.2.3