Bug 1593649 - Part 2: Always set focus as if using the keyboard r=rpl

Differential Revision: https://phabricator.services.mozilla.com/D55194

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Mark Striemer 2019-11-29 19:37:04 +00:00
Родитель 415e677b84
Коммит d2062ef28c
4 изменённых файлов: 83 добавлений и 38 удалений

Просмотреть файл

@ -677,14 +677,8 @@ class PanelList extends HTMLElement {
let openingEvent = this.triggeringEvent;
this.triggeringEvent = triggeringEvent;
this.open = false;
// Since the previously focused element (which is inside the panel) is now
// hidden, move the focus back to the element that opened the panel if it
// was opened with the keyboard.
if (
openingEvent &&
openingEvent.target &&
openingEvent.mozInputSource === MouseEvent.MOZ_SOURCE_KEYBOARD
) {
// Refocus the button that opened the menu if we have one.
if (openingEvent && openingEvent.target) {
openingEvent.target.focus();
}
}
@ -945,8 +939,6 @@ class PanelList extends HTMLElement {
}
async onShow() {
let { triggeringEvent } = this;
this.sendEvent("showing");
this.addHideListeners();
await this.setAlign();
@ -954,14 +946,9 @@ class PanelList extends HTMLElement {
// 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.focusWalker.currentNode = this;
this.focusWalker.nextNode();
}
// Focus the first focusable panel-item.
this.focusWalker.currentNode = this;
this.focusWalker.nextNode();
this.sendEvent("shown");
});
@ -1371,7 +1358,7 @@ class AddonUpdatesMessage extends HTMLElement {
this.button = document.createElement("button");
this.button.addEventListener("click", e => {
if (e.button === 0) {
loadViewFn("updates/available", e);
loadViewFn("updates/available");
}
});
this.button.hidden = true;
@ -1456,7 +1443,7 @@ class AddonPageOptions extends HTMLElement {
await this.checkForUpdates();
break;
case "view-recent-updates":
loadViewFn("updates/recent", e);
loadViewFn("updates/recent");
break;
case "install-from-file":
if (XPINSTALL_ENABLED) {
@ -1480,7 +1467,7 @@ class AddonPageOptions extends HTMLElement {
await this.resetAutomaticUpdates();
break;
case "manage-shortcuts":
loadViewFn("shortcuts/shortcuts", e);
loadViewFn("shortcuts/shortcuts");
break;
}
}
@ -2517,7 +2504,7 @@ class AddonCard extends HTMLElement {
openOptionsInTab(addon.optionsURL);
} else if (getOptionsType(addon) == "inline") {
this.recordActionEvent("preferences", "inline");
loadViewFn(`detail/${this.addon.id}/preferences`, e);
loadViewFn(`detail/${this.addon.id}/preferences`);
}
break;
case "remove":
@ -2544,7 +2531,7 @@ class AddonCard extends HTMLElement {
}
break;
case "expand":
loadViewFn(`detail/${this.addon.id}`, e);
loadViewFn(`detail/${this.addon.id}`);
break;
case "more-options":
// Open panel on click from the keyboard.
@ -2570,7 +2557,7 @@ class AddonCard extends HTMLElement {
!this.expanded &&
(e.target === this.addonNameEl || !e.target.closest("a"))
) {
loadViewFn(`detail/${this.addon.id}`, e);
loadViewFn(`detail/${this.addon.id}`);
} else if (
e.target.localName == "a" &&
e.target.getAttribute("data-telemetry-name")
@ -2898,7 +2885,7 @@ class AddonCard extends HTMLElement {
this.appendChild(this.card);
if (this.expanded && this.keyboardNavigation) {
if (this.expanded) {
requestAnimationFrame(() => this.optionsButton.focus());
}
@ -3076,7 +3063,7 @@ class RecommendedAddonCard extends HTMLElement {
action: "manage",
addon: this.discoAddon,
});
loadViewFn(`detail/${this.addonId}`, event);
loadViewFn(`detail/${this.addonId}`);
break;
default:
if (event.target.matches(".disco-addon-author a[href]")) {
@ -3964,12 +3951,11 @@ class ListView {
}
class DetailView {
constructor({ isKeyboardNavigation, param, root }) {
constructor({ param, root }) {
let [id, selectedTab] = param.split("/");
this.id = id;
this.selectedTab = selectedTab;
this.root = root;
this.isKeyboardNavigation = isKeyboardNavigation;
}
async render() {
@ -3990,7 +3976,6 @@ class DetailView {
card.setAddon(addon);
card.expand();
card.keyboardNavigation = this.isKeyboardNavigation;
await card.render();
if (
this.selectedTab === "preferences" &&
@ -4132,7 +4117,7 @@ function initialize(opts) {
* resolve once the view has been updated to conform with other about:addons
* views.
*/
async function show(type, param, { isKeyboardNavigation, historyEntryId }) {
async function show(type, param, { historyEntryId }) {
let container = document.createElement("div");
container.setAttribute("current-view", type);
addonPageHeader.setViewInfo({ type, param });
@ -4140,7 +4125,6 @@ async function show(type, param, { isKeyboardNavigation, historyEntryId }) {
await new ListView({ param, root: container }).render();
} else if (type == "detail") {
await new DetailView({
isKeyboardNavigation,
param,
root: container,
}).render();

Просмотреть файл

@ -608,7 +608,7 @@ var gViewController = {
);
},
loadView(aViewId, sourceEvent) {
loadView(aViewId) {
var isRefresh = false;
if (aViewId == this.currentViewId) {
if (this.isLoading) {
@ -623,14 +623,10 @@ var gViewController = {
isRefresh = true;
}
let isKeyboardNavigation =
sourceEvent &&
sourceEvent.mozInputSource === MouseEvent.MOZ_SOURCE_KEYBOARD;
var state = {
view: aViewId,
previousView: this.currentViewId,
historyEntryId: ++this.nextHistoryEntryId,
isKeyboardNavigation,
};
if (!isRefresh) {
gHistory.pushState(state);
@ -1538,9 +1534,9 @@ const addonTypes = new Set([
"locale",
]);
const htmlViewOpts = {
loadViewFn(view, sourceEvent) {
loadViewFn(view) {
let viewId = `addons://${view}`;
gViewController.loadView(viewId, sourceEvent);
gViewController.loadView(viewId);
},
replaceWithDefaultViewFn() {
gViewController.replaceView(gViewDefault);

Просмотреть файл

@ -99,6 +99,7 @@ skip-if = verify
[browser_page_options_install_addon.js]
[browser_page_options_updates.js]
[browser_panel_item_accesskey.js]
[browser_panel_list_accessibility.js]
[browser_pluginprefs.js]
[browser_reinstall.js]
[browser_search_bar_focus.js]

Просмотреть файл

@ -0,0 +1,64 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
function setupPanel(win) {
let doc = win.document;
let panelList = doc.createElement("panel-list");
let items = ["one", "two", "three"];
let panelItems = items.map(item => {
let panelItem = doc.createElement("panel-item");
panelItem.textContent = item;
panelList.append(panelItem);
return panelItem;
});
let anchorButton = doc.createElement("button");
anchorButton.addEventListener("click", e => panelList.toggle(e));
doc.body.append(anchorButton, panelList);
return { anchorButton, panelList, panelItems };
}
add_task(async function testItemFocusOnOpen() {
let win = await loadInitialView("extension");
let doc = win.document;
let { anchorButton, panelList, panelItems } = setupPanel(win);
ok(doc.activeElement, "There is an active element");
ok(!doc.activeElement.closest("panel-list"), "Focus isn't in the list");
let shown = BrowserTestUtils.waitForEvent(panelList, "shown");
EventUtils.synthesizeMouseAtCenter(anchorButton, {}, win);
await shown;
is(doc.activeElement, panelItems[0], "The first item is focused");
let hidden = BrowserTestUtils.waitForEvent(panelList, "hidden");
EventUtils.synthesizeKey("Escape", {}, win);
await hidden;
is(doc.activeElement, anchorButton, "The anchor is focused again on close");
await closeView(win);
});
add_task(async function testAriaAttributes() {
let win = await loadInitialView("extension");
let { panelList, panelItems } = setupPanel(win);
is(panelList.getAttribute("role"), "menu", "The panel is a menu");
is(panelItems.length, 3, "There are 3 items");
Assert.deepEqual(
panelItems.map(panelItem => panelItem.button.getAttribute("role")),
new Array(panelItems.length).fill("menuitem"),
"All of the items have a menuitem button"
);
await closeView(win);
});