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:
@@ -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,
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user