diff --git a/toolkit/mozapps/extensions/content/aboutaddons.js b/toolkit/mozapps/extensions/content/aboutaddons.js index 1d346d89562a..80eee8d41bb4 100644 --- a/toolkit/mozapps/extensions/content/aboutaddons.js +++ b/toolkit/mozapps/extensions/content/aboutaddons.js @@ -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(); diff --git a/toolkit/mozapps/extensions/content/extensions.js b/toolkit/mozapps/extensions/content/extensions.js index eb2902fae313..d16e20119172 100644 --- a/toolkit/mozapps/extensions/content/extensions.js +++ b/toolkit/mozapps/extensions/content/extensions.js @@ -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); diff --git a/toolkit/mozapps/extensions/test/browser/browser.ini b/toolkit/mozapps/extensions/test/browser/browser.ini index f98cea322a53..6aa92a5cde68 100644 --- a/toolkit/mozapps/extensions/test/browser/browser.ini +++ b/toolkit/mozapps/extensions/test/browser/browser.ini @@ -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] diff --git a/toolkit/mozapps/extensions/test/browser/browser_panel_list_accessibility.js b/toolkit/mozapps/extensions/test/browser/browser_panel_list_accessibility.js new file mode 100644 index 000000000000..b3729442cf10 --- /dev/null +++ b/toolkit/mozapps/extensions/test/browser/browser_panel_list_accessibility.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); +});