Bug 1789875 - Add list style keyboard navigation for synced tabs list. r=sclements,ayeddi

Differential Revision: https://phabricator.services.mozilla.com/D158152
This commit is contained in:
Tim Giles
2022-10-31 13:54:32 +00:00
parent 3a7d010064
commit ab61c86b6a
2 changed files with 213 additions and 0 deletions

View File

@@ -86,6 +86,102 @@ class TabPickupList extends HTMLElement {
}
);
}
if (event.type == "keydown") {
switch (event.key) {
case "ArrowRight": {
event.preventDefault();
this.moveFocusToSecondElement();
break;
}
case "ArrowLeft": {
event.preventDefault();
this.moveFocusToFirstElement();
break;
}
case "ArrowDown": {
event.preventDefault();
this.moveFocusToNextElement();
break;
}
case "ArrowUp": {
event.preventDefault();
this.moveFocusToPreviousElement();
break;
}
case "Tab": {
this.resetFocus(event);
}
}
}
}
/**
* Handles removing and setting tabindex on elements
* while moving focus to the next element
*
* @param {HTMLElement} currentElement currently focused element
* @param {HTMLElement} nextElement element that should receive focus next
* @memberof TabPickupList
* @private
*/
#manageTabIndexAndFocus(currentElement, nextElement) {
currentElement.setAttribute("tabindex", "-1");
nextElement.removeAttribute("tabindex");
nextElement.focus();
}
moveFocusToFirstElement() {
let selectableElements = Array.from(this.tabsList.querySelectorAll("a"));
let firstElement = selectableElements[0];
let selectedElement = this.tabsList.querySelector("a:not([tabindex]");
this.#manageTabIndexAndFocus(selectedElement, firstElement);
}
moveFocusToSecondElement() {
let selectableElements = Array.from(this.tabsList.querySelectorAll("a"));
let secondElement = selectableElements[1];
if (secondElement) {
let selectedElement = this.tabsList.querySelector("a:not([tabindex]");
this.#manageTabIndexAndFocus(selectedElement, secondElement);
}
}
moveFocusToNextElement() {
let selectableElements = Array.from(this.tabsList.querySelectorAll("a"));
let selectedElement = this.tabsList.querySelector("a:not([tabindex]");
let nextElement =
selectableElements.findIndex(elem => elem == selectedElement) + 1;
if (nextElement < selectableElements.length) {
this.#manageTabIndexAndFocus(
selectedElement,
selectableElements[nextElement]
);
}
}
moveFocusToPreviousElement() {
let selectableElements = Array.from(this.tabsList.querySelectorAll("a"));
let selectedElement = this.tabsList.querySelector("a:not([tabindex]");
let previousElement =
selectableElements.findIndex(elem => elem == selectedElement) - 1;
if (previousElement >= 0) {
this.#manageTabIndexAndFocus(
selectedElement,
selectableElements[previousElement]
);
}
}
resetFocus(e) {
let selectableElements = Array.from(this.tabsList.querySelectorAll("a"));
let selectedElement = this.tabsList.querySelector("a:not([tabindex]");
selectedElement.setAttribute("tabindex", "-1");
selectableElements[0].removeAttribute("tabindex");
if (e.shiftKey) {
e.preventDefault();
document
.getElementById("tab-pickup-container")
.querySelector("summary")
.focus();
}
}
cleanup() {
@@ -181,6 +277,10 @@ class TabPickupList extends HTMLElement {
a.classList.add("synced-tab-a");
a.href = targetURI;
a.target = "_blank";
if (index != 0) {
a.setAttribute("tabindex", "-1");
}
a.addEventListener("keydown", this);
document.l10n.setAttributes(a, "firefoxview-tabs-list-tab-button", {
targetURI,
});

View File

@@ -491,4 +491,117 @@ add_task(async function test_tabs_sync_on_user_page_reload() {
sandbox.restore();
cleanup();
});
add_task(async function test_keyboard_navigation() {
// Setting this pref allows the test to run as expected on MacOS
await SpecialPowers.pushPrefEnv({ set: [["accessibility.tabfocus", 7]] });
TabsSetupFlowManager.resetInternalState();
await BrowserTestUtils.withNewTab(
{
gBrowser,
url: "about:firefoxview",
},
async browser => {
const { document } = browser.contentWindow;
const sandbox = setupRecentDeviceListMocks();
const syncedTabsMock = sandbox.stub(SyncedTabs, "getRecentTabs");
let mockTabs1 = getMockTabData(syncedTabsData1);
syncedTabsMock.returns(mockTabs1);
await setupListState(browser);
const tab = (shiftKey = false) => {
info(`${shiftKey ? "Shift + Tab" : "Tab"}`);
EventUtils.synthesizeKey("KEY_Tab", { shiftKey });
};
const arrowDown = () => {
info("Arrow Down");
EventUtils.synthesizeKey("KEY_ArrowDown");
};
const arrowUp = () => {
info("Arrow Up");
EventUtils.synthesizeKey("KEY_ArrowUp");
};
const arrowLeft = () => {
info("Arrow Left");
EventUtils.synthesizeKey("KEY_ArrowLeft");
};
const arrowRight = () => {
info("Arrow Right");
EventUtils.synthesizeKey("KEY_ArrowRight");
};
let syncedTabsLinks = document
.querySelector("ol.synced-tabs-list")
.querySelectorAll("a");
let summary = document
.getElementById("tab-pickup-container")
.querySelector("summary");
summary.focus();
tab();
is(
syncedTabsLinks[0],
document.activeElement,
"First synced tab should be focused"
);
arrowDown();
is(
syncedTabsLinks[1],
document.activeElement,
"Second synced tab should be focused"
);
arrowDown();
is(
syncedTabsLinks[2],
document.activeElement,
"Third synced tab should be focused"
);
arrowDown();
is(
syncedTabsLinks[2],
document.activeElement,
"Third synced tab should still be focused"
);
arrowUp();
is(
syncedTabsLinks[1],
document.activeElement,
"Second synced tab should be focused"
);
arrowLeft();
is(
syncedTabsLinks[0],
document.activeElement,
"First synced tab should be focused"
);
arrowRight();
is(
syncedTabsLinks[1],
document.activeElement,
"Second synced tab should be focused"
);
arrowDown();
is(
syncedTabsLinks[2],
document.activeElement,
"Third synced tab should be focused"
);
arrowLeft();
is(
syncedTabsLinks[0],
document.activeElement,
"First synced tab should be focused"
);
tab(true);
is(
summary,
document.activeElement,
"Summary element should be focused when shift tabbing away from list"
);
sandbox.restore();
cleanup();
}
);
});
});