From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- .../aboutlogins/tests/browser/browser_tabKeyNav.js | 276 +++++++++++++++++++++ 1 file changed, 276 insertions(+) create mode 100644 browser/components/aboutlogins/tests/browser/browser_tabKeyNav.js (limited to 'browser/components/aboutlogins/tests/browser/browser_tabKeyNav.js') diff --git a/browser/components/aboutlogins/tests/browser/browser_tabKeyNav.js b/browser/components/aboutlogins/tests/browser/browser_tabKeyNav.js new file mode 100644 index 0000000000..0305107d23 --- /dev/null +++ b/browser/components/aboutlogins/tests/browser/browser_tabKeyNav.js @@ -0,0 +1,276 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +EXPECTED_BREACH = { + AddedDate: "2018-12-20T23:56:26Z", + BreachDate: "2018-12-16", + Domain: "breached.example.com", + Name: "Breached", + PwnCount: 1643100, + DataClasses: ["Email addresses", "Usernames", "Passwords", "IP addresses"], + _status: "synced", + id: "047940fe-d2fd-4314-b636-b4a952ee0043", + last_modified: "1541615610052", + schema: "1541615609018", +}; + +add_setup(async function () { + TEST_LOGIN1 = await addLogin(TEST_LOGIN1); + await BrowserTestUtils.openNewForegroundTab({ + gBrowser, + url: "about:logins", + }); + registerCleanupFunction(() => { + BrowserTestUtils.removeTab(gBrowser.selectedTab); + Services.logins.removeAllUserFacingLogins(); + }); +}); + +add_task(async function test_tab_key_nav() { + const browser = gBrowser.selectedBrowser; + await SpecialPowers.spawn(browser, [], async () => { + // Helper function for getting the resulting DOM element given a list of selectors possibly inside shadow DOM + const selectWithShadowRootIfNeeded = (document, selectorsArray) => + selectorsArray.reduce( + (selectionSoFar, currentSelector) => + selectionSoFar.shadowRoot + ? selectionSoFar.shadowRoot.querySelector(currentSelector) + : selectionSoFar.querySelector(currentSelector), + document + ); + + const EventUtils = ContentTaskUtils.getEventUtils(content); + // list [selector, shadow root selector] for each element + // in the order we expect them to be navigated. + const expectedElementsInOrder = [ + ["login-list", "login-filter", "input"], + ["login-list", "button.create-login-button"], + ["login-list", "select#login-sort"], + ["login-list", "ol"], + ["login-item", "button.edit-button"], + ["login-item", "button.delete-button"], + ["login-item", "a.origin-input"], + ["login-item", "button.copy-username-button"], + ["login-item", "input.reveal-password-checkbox"], + ["login-item", "button.copy-password-button"], + ]; + + const firstElement = selectWithShadowRootIfNeeded( + content.document, + expectedElementsInOrder.at(0) + ); + + const lastElement = selectWithShadowRootIfNeeded( + content.document, + expectedElementsInOrder.at(-1) + ); + + async function tab() { + EventUtils.synthesizeKey("KEY_Tab", {}, content); + await new Promise(resolve => content.requestAnimationFrame(resolve)); + // The following line can help with focus trap debugging: + // await new Promise(resolve => content.window.setTimeout(resolve, 500)); + } + async function shiftTab() { + EventUtils.synthesizeKey("KEY_Tab", { shiftKey: true }, content); + await new Promise(resolve => content.requestAnimationFrame(resolve)); + // await new Promise(resolve => content.window.setTimeout(resolve, 500)); + } + + // Getting focused shadow DOM element itself instead of shadowRoot, + // using recursion for any component-nesting level, as in: + // document.activeElement.shadowRoot.activeElement.shadowRoot.activeElement + function getFocusedElement() { + let element = content.document.activeElement; + const getShadowRootFocus = e => { + if (e.shadowRoot) { + return getShadowRootFocus(e.shadowRoot.activeElement); + } + return e; + }; + return getShadowRootFocus(element); + } + + // Ensure the test starts in a valid state + firstElement.focus(); + // Assert that we tab navigate correctly + for (let expectedSelector of expectedElementsInOrder) { + const expectedElement = selectWithShadowRootIfNeeded( + content.document, + expectedSelector + ); + + // By default, MacOS will skip over certain text controls, such as links. + if ( + content.window.navigator.platform.toLowerCase().includes("mac") && + expectedElement.tagName === "A" + ) { + continue; + } + + const actualElement = getFocusedElement(); + + Assert.equal( + actualElement, + expectedElement, + "Actual focused element should equal the expected focused element" + ); + await tab(); + } + + lastElement.focus(); + + // Assert that we shift + tab navigate correctly starting from the last ordered element + for (let expectedSelector of expectedElementsInOrder.reverse()) { + const expectedElement = selectWithShadowRootIfNeeded( + content.document, + expectedSelector + ); + // By default, MacOS will skip over certain text controls, such as links. + if ( + content.window.navigator.platform.toLowerCase().includes("mac") && + expectedElement.tagName === "A" + ) { + continue; + } + + const actualElement = getFocusedElement(); + Assert.equal( + actualElement, + expectedElement, + "Actual focused element should equal the expected focused element" + ); + await shiftTab(); + } + await tab(); // tab back to the first element + }); +}); + +add_task(async function test_tab_to_create_button() { + const browser = gBrowser.selectedBrowser; + await SpecialPowers.spawn(browser, [], async () => { + const EventUtils = ContentTaskUtils.getEventUtils(content); + + function waitForAnimationFrame() { + return new Promise(resolve => content.requestAnimationFrame(resolve)); + } + + async function tab() { + EventUtils.synthesizeKey("KEY_Tab", {}, content); + await waitForAnimationFrame(); + } + + const loginList = content.document.querySelector("login-list"); + const loginFilter = loginList.shadowRoot.querySelector("login-filter"); + const loginSort = loginList.shadowRoot.getElementById("login-sort"); + const loginListbox = loginList.shadowRoot.querySelector("ol"); + const createButton = loginList.shadowRoot.querySelector( + ".create-login-button" + ); + + const getFocusedElement = () => loginList.shadowRoot.activeElement; + Assert.equal(getFocusedElement(), loginFilter, "login-filter is focused"); + + await tab(); + Assert.equal(getFocusedElement(), createButton, "create button is focused"); + + await tab(); + Assert.equal(getFocusedElement(), loginSort, "login sort is focused"); + + await tab(); + Assert.equal(getFocusedElement(), loginListbox, "listbox is focused next"); + + await tab(); + Assert.equal(getFocusedElement(), null, "login-list isn't focused again"); + }); +}); + +add_task(async function test_tab_to_edit_button() { + TEST_LOGIN3 = await addLogin(TEST_LOGIN3); + let browser = gBrowser.selectedBrowser; + await SpecialPowers.spawn( + browser, + [[TEST_LOGIN1.guid, TEST_LOGIN3.guid]], + async ([testLoginNormalGuid, testLoginBreachedGuid]) => { + const EventUtils = ContentTaskUtils.getEventUtils(content); + + function waitForAnimationFrame() { + return new Promise(resolve => content.requestAnimationFrame(resolve)); + } + + async function tab() { + EventUtils.synthesizeKey("KEY_Tab", {}, content); + await waitForAnimationFrame(); + } + + const loginList = content.document.querySelector("login-list"); + const loginItem = content.document.querySelector("login-item"); + const loginFilter = loginList.shadowRoot.querySelector("login-filter"); + const createButton = loginList.shadowRoot.querySelector( + ".create-login-button" + ); + const loginSort = loginList.shadowRoot.getElementById("login-sort"); + const loginListbox = loginList.shadowRoot.querySelector("ol"); + const editButton = loginItem.shadowRoot.querySelector(".edit-button"); + const breachAlert = loginItem.shadowRoot.querySelector(".breach-alert"); + const getFocusedElement = () => { + if (content.document.activeElement == loginList) { + return loginList.shadowRoot.activeElement; + } + if (content.document.activeElement == loginItem) { + return loginItem.shadowRoot.activeElement; + } + if (content.document.activeElement == loginFilter) { + return loginFilter.shadowRoot.activeElement; + } + Assert.ok( + false, + "not expecting a different element to get focused in this test: " + + content.document.activeElement.outerHTML + ); + return undefined; + }; + + for (let guidToSelect of [testLoginNormalGuid, testLoginBreachedGuid]) { + let loginListItem = loginList.shadowRoot.querySelector( + `.login-list-item[data-guid="${guidToSelect}"]` + ); + loginListItem.click(); + await ContentTaskUtils.waitForCondition(() => { + let waivedLoginItem = Cu.waiveXrays(loginItem); + return ( + waivedLoginItem._login && + waivedLoginItem._login.guid == guidToSelect + ); + }, "waiting for login-item to show the selected login"); + + Assert.equal( + breachAlert.hidden, + guidToSelect == testLoginNormalGuid, + ".breach-alert should be hidden if the login is not breached. current login breached? " + + (guidToSelect == testLoginBreachedGuid) + ); + + createButton.focus(); + Assert.equal( + getFocusedElement(), + createButton, + "create button is focused" + ); + + await tab(); + Assert.equal(getFocusedElement(), loginSort, "login sort is focused"); + + await tab(); + Assert.equal( + getFocusedElement(), + loginListbox, + "listbox is focused next" + ); + + await tab(); + Assert.equal(getFocusedElement(), editButton, "edit button is focused"); + } + } + ); +}); -- cgit v1.2.3