зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1567600 - Part 2: Better accessibility for about:addons panel-list r=Gijs
Differential Revision: https://phabricator.services.mozilla.com/D40506 --HG-- extra : moz-landing-system : lando
This commit is contained in:
Родитель
97c482692c
Коммит
c19ef4049e
|
@ -220,11 +220,11 @@
|
|||
|
||||
<template name="panel-list">
|
||||
<link rel="stylesheet" href="chrome://mozapps/content/extensions/panel-list.css">
|
||||
<div class="arrow top"></div>
|
||||
<div class="list">
|
||||
<div class="arrow top" role="presentation"></div>
|
||||
<div class="list" role="presentation">
|
||||
<slot></slot>
|
||||
</div>
|
||||
<div class="arrow bottom"></div>
|
||||
<div class="arrow bottom" role="presentation"></div>
|
||||
</template>
|
||||
|
||||
<template name="panel-item">
|
||||
|
|
|
@ -551,6 +551,7 @@ class PanelList extends HTMLElement {
|
|||
super();
|
||||
this.attachShadow({ mode: "open" });
|
||||
this.shadowRoot.appendChild(importTemplate("panel-list"));
|
||||
this.setAttribute("role", "menu");
|
||||
}
|
||||
|
||||
attributeChangedCallback(name, oldVal, newVal) {
|
||||
|
@ -646,14 +647,12 @@ class PanelList extends HTMLElement {
|
|||
this.setAttribute("valign", valign);
|
||||
this.parentNode.style.overflow = "";
|
||||
this.removeAttribute("showing");
|
||||
|
||||
// Send the shown event after the next paint.
|
||||
requestAnimationFrame(() => this.sendEvent("shown"));
|
||||
}
|
||||
|
||||
addHideListeners() {
|
||||
// Hide when a panel-item is clicked in the list.
|
||||
this.addEventListener("click", this);
|
||||
document.addEventListener("keydown", this);
|
||||
// Hide when a click is initiated outside the panel.
|
||||
document.addEventListener("mousedown", this);
|
||||
// Hide if focus changes and the panel isn't in focus.
|
||||
|
@ -668,6 +667,7 @@ class PanelList extends HTMLElement {
|
|||
|
||||
removeHideListeners() {
|
||||
this.removeEventListener("click", this);
|
||||
document.removeEventListener("keydown", this);
|
||||
document.removeEventListener("mousedown", this);
|
||||
document.removeEventListener("focusin", this);
|
||||
window.removeEventListener("resize", this);
|
||||
|
@ -696,6 +696,53 @@ class PanelList extends HTMLElement {
|
|||
e.stopPropagation();
|
||||
}
|
||||
break;
|
||||
case "keydown":
|
||||
if (e.key === "ArrowDown" || e.key === "ArrowUp" || e.key === "Tab") {
|
||||
// Ignore tabbing with a modifer other than shift.
|
||||
if (e.key === "Tab" && (e.altKey || e.ctrlKey || e.metaKey)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't scroll the page or let the regular tab order take effect.
|
||||
e.preventDefault();
|
||||
|
||||
// Keep moving to the next/previous element sibling until we find a
|
||||
// panel-item that isn't hidden.
|
||||
let moveForward =
|
||||
e.key === "ArrowDown" || (e.key === "Tab" && !e.shiftKey);
|
||||
|
||||
// If the menu is opened with the mouse, the active element might be
|
||||
// somewhere else in the document. In that case we should ignore it
|
||||
// to avoid walking unrelated DOM nodes.
|
||||
this.walker.currentNode = this.contains(document.activeElement)
|
||||
? document.activeElement
|
||||
: this;
|
||||
let nextItem = moveForward
|
||||
? this.walker.nextNode()
|
||||
: this.walker.previousNode();
|
||||
|
||||
// If the next item wasn't found, try looping to the top/bottom.
|
||||
if (!nextItem) {
|
||||
this.walker.currentNode = this;
|
||||
if (moveForward) {
|
||||
nextItem = this.walker.firstChild();
|
||||
} else {
|
||||
nextItem = this.walker.lastChild();
|
||||
}
|
||||
}
|
||||
|
||||
if (nextItem) {
|
||||
nextItem.focus();
|
||||
}
|
||||
break;
|
||||
} else if (e.key === "Escape") {
|
||||
let { triggeringEvent } = this;
|
||||
this.hide();
|
||||
if (triggeringEvent && triggeringEvent.target) {
|
||||
triggeringEvent.target.focus();
|
||||
}
|
||||
}
|
||||
break;
|
||||
case "mousedown":
|
||||
case "focusin":
|
||||
// There will be a focusin after the mousedown that opens the panel
|
||||
|
@ -720,9 +767,44 @@ class PanelList extends HTMLElement {
|
|||
}
|
||||
}
|
||||
|
||||
onShow() {
|
||||
this.setAlign();
|
||||
get walker() {
|
||||
if (!this._walker) {
|
||||
this._walker = document.createTreeWalker(this, NodeFilter.SHOW_ELEMENT, {
|
||||
acceptNode: node => {
|
||||
if (node.disabled || node.hidden || node.localName !== "panel-item") {
|
||||
return NodeFilter.FILTER_REJECT;
|
||||
}
|
||||
|
||||
return NodeFilter.FILTER_ACCEPT;
|
||||
},
|
||||
});
|
||||
}
|
||||
return this._walker;
|
||||
}
|
||||
|
||||
async onShow() {
|
||||
let { triggeringEvent } = this;
|
||||
|
||||
this.addHideListeners();
|
||||
await this.setAlign();
|
||||
|
||||
// Wait until the next paint for the alignment to be set and panel to be
|
||||
// visible.
|
||||
requestAnimationFrame(() => {
|
||||
// Focus the first visible panel-item if we were opened with the keyboard.
|
||||
if (
|
||||
triggeringEvent &&
|
||||
triggeringEvent.mozInputSource === MouseEvent.MOZ_SOURCE_KEYBOARD
|
||||
) {
|
||||
this.walker.currentNode = this;
|
||||
let firstItem = this.walker.nextNode();
|
||||
if (firstItem) {
|
||||
firstItem.focus();
|
||||
}
|
||||
}
|
||||
|
||||
this.sendEvent("shown");
|
||||
});
|
||||
}
|
||||
|
||||
onHide() {
|
||||
|
@ -742,6 +824,7 @@ class PanelItem extends HTMLElement {
|
|||
this.attachShadow({ mode: "open" });
|
||||
this.shadowRoot.appendChild(importTemplate("panel-item"));
|
||||
this.button = this.shadowRoot.querySelector("button");
|
||||
this.button.setAttribute("role", "menuitem");
|
||||
}
|
||||
|
||||
get disabled() {
|
||||
|
@ -759,6 +842,10 @@ class PanelItem extends HTMLElement {
|
|||
set checked(val) {
|
||||
this.toggleAttribute("checked", val);
|
||||
}
|
||||
|
||||
focus() {
|
||||
this.button.focus();
|
||||
}
|
||||
}
|
||||
customElements.define("panel-item", PanelItem);
|
||||
|
||||
|
|
|
@ -385,34 +385,33 @@ add_task(async function testKeyboardSupport() {
|
|||
// Test opening and closing the menu.
|
||||
let moreOptionsMenu = card.querySelector("panel-list");
|
||||
let expandButton = moreOptionsMenu.querySelector('[action="expand"]');
|
||||
let toggleDisableButton = card.querySelector('[action="toggle-disabled"]');
|
||||
is(moreOptionsMenu.open, false, "The menu is closed");
|
||||
space();
|
||||
is(moreOptionsMenu.open, true, "The menu is open");
|
||||
space();
|
||||
is(moreOptionsMenu.open, false, "The menu is closed");
|
||||
|
||||
// Test tabbing out of the menu.
|
||||
space();
|
||||
is(moreOptionsMenu.open, true, "The menu is open");
|
||||
tab({ shiftKey: true });
|
||||
is(moreOptionsMenu.open, false, "Tabbing away from the menu closes it");
|
||||
tab();
|
||||
isFocused(moreOptionsButton, "The button is focused again");
|
||||
let shown = BrowserTestUtils.waitForEvent(moreOptionsMenu, "shown");
|
||||
space();
|
||||
await shown;
|
||||
is(moreOptionsMenu.open, true, "The menu is open");
|
||||
for (let it of moreOptionsMenu.querySelectorAll("panel-item:not([hidden])")) {
|
||||
tab();
|
||||
isFocused(it, `After tab, focus item "${it.getAttribute("action")}"`);
|
||||
}
|
||||
isFocused(expandButton, "The last item is focused");
|
||||
tab();
|
||||
is(moreOptionsMenu.open, false, "Tabbing out of the menu closes it");
|
||||
isFocused(toggleDisableButton, "The disable button is now focused");
|
||||
EventUtils.synthesizeKey("Escape", {});
|
||||
is(moreOptionsMenu.open, false, "The menu is closed");
|
||||
isFocused(moreOptionsButton, "The more options button is focused");
|
||||
|
||||
// Focus the button again, focus may have moved out of the browser.
|
||||
moreOptionsButton.focus();
|
||||
isFocused(moreOptionsButton, "The button is focused again");
|
||||
// Test tabbing out of the menu.
|
||||
space();
|
||||
shown = BrowserTestUtils.waitForEvent(moreOptionsMenu, "shown");
|
||||
is(moreOptionsMenu.open, true, "The menu is open");
|
||||
await shown;
|
||||
tab({ shiftKey: true });
|
||||
is(moreOptionsMenu.open, true, "The menu stays open");
|
||||
isFocused(expandButton, "The focus has looped to the bottom");
|
||||
tab();
|
||||
is(moreOptionsMenu.open, true, "The menu stays open");
|
||||
isFocused(toggleDisableButton, "The focus has looped to the top");
|
||||
|
||||
let hidden = BrowserTestUtils.waitForEvent(moreOptionsMenu, "hidden");
|
||||
EventUtils.synthesizeKey("Escape", {});
|
||||
await hidden;
|
||||
isFocused(moreOptionsButton, "Escape closed the menu");
|
||||
|
||||
// Open the menu to test contents.
|
||||
shown = BrowserTestUtils.waitForEvent(moreOptionsMenu, "shown");
|
||||
|
@ -422,8 +421,6 @@ add_task(async function testKeyboardSupport() {
|
|||
await shown;
|
||||
|
||||
// Disable the add-on.
|
||||
let toggleDisableButton = card.querySelector('[action="toggle-disabled"]');
|
||||
tab();
|
||||
isFocused(toggleDisableButton, "The disable button is focused");
|
||||
is(card.parentNode, enabledSection, "The card is in the enabled section");
|
||||
let disabled = BrowserTestUtils.waitForEvent(list, "move");
|
||||
|
@ -444,7 +441,6 @@ add_task(async function testKeyboardSupport() {
|
|||
|
||||
// Remove the add-on.
|
||||
tab();
|
||||
tab();
|
||||
let removeButton = card.querySelector('[action="remove"]');
|
||||
isFocused(removeButton, "The remove button is focused");
|
||||
let removed = BrowserTestUtils.waitForEvent(list, "remove");
|
||||
|
|
Загрузка…
Ссылка в новой задаче