Bug 1593649 - Part 1: Fix a11y issues with about:addons header/recommendations r=robwu,rpl,fluent-reviewers,flod

Add a proper title and popup attributes to page-options button.
Make recommended card's add-on names headings.
Give the HTML pane a title so it reads better in screen readers.
Always include a label for the search box.
Clarify the label on the extension enable checkbox.

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

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Mark Striemer 2019-11-29 19:36:57 +00:00
parent e6655d9d96
commit 415e677b84
11 changed files with 193 additions and 47 deletions

View File

@ -48,14 +48,15 @@ function getAddonElement(managerWindow, addonId) {
);
}
function assertDisabledSideloadedAddonElement(managerWindow, addonElement) {
const doc = addonElement.ownerDocument;
function assertSideloadedAddonElementState(addonElement, checked) {
const enableBtn = addonElement.querySelector('[action="toggle-disabled"]');
is(
doc.l10n.getAttributes(enableBtn).id,
"enable-addon-button-label",
"The button has the enable label"
enableBtn.checked,
checked,
`The enable button is ${!checked ? " not " : ""} checked`
);
is(enableBtn.localName, "input", "The enable button is an input");
is(enableBtn.type, "checkbox", "It's a checkbox");
}
function clickEnableExtension(managerWindow, addonElement) {
@ -220,7 +221,7 @@ add_task(async function test_sideloading() {
// XUL or HTML about:addons addon entry element.
const addonElement = await getAddonElement(win, ID2);
assertDisabledSideloadedAddonElement(win, addonElement);
assertSideloadedAddonElementState(addonElement, false);
info("Test enabling sideloaded addon 2 from about:addons enable button");
@ -267,6 +268,7 @@ add_task(async function test_sideloading() {
addon2 = await AddonManager.getAddonByID(ID2);
is(addon2.userDisabled, false, "Addon 2 should be enabled");
assertSideloadedAddonElementState(addonElement, true);
// Test post install notification on addon 2.
await testPostInstallIncognitoCheckbox(addon2);

View File

@ -0,0 +1,28 @@
# coding=utf8
# Any copyright is dedicated to the Public Domain.
# http://creativecommons.org/publicdomain/zero/1.0/
from __future__ import absolute_import
import fluent.syntax.ast as FTL
from fluent.migrate.helpers import transforms_from
from fluent.migrate import COPY_PATTERN
TARGET_FILE = "toolkit/toolkit/about/aboutAddons.ftl"
SOURCE_FILE = TARGET_FILE
def migrate(ctx):
"""Bug 1593649 - about:addons tools button, part {index}"""
ctx.add_transforms(
TARGET_FILE,
SOURCE_FILE,
transforms_from(
"""
addon-page-options-button =
.title = {COPY_PATTERN(from_path, "tools-menu.tooltiptext")}
addons-page-title = {COPY_PATTERN(from_path, "addons-window.title")}
""",
from_path=SOURCE_FILE),
)

View File

@ -4,6 +4,7 @@
addons-window =
.title = Add-ons Manager
addons-page-title = Add-ons Manager
search-header =
.placeholder = Search addons.mozilla.org
@ -38,9 +39,6 @@ preferences =
*[other] { -brand-short-name } Preferences
}
tools-menu =
.tooltiptext = Tools for all add-ons
show-unsigned-extensions-button =
.label = Some extensions could not be verified
@ -367,9 +365,9 @@ remove-addon-button = Remove
remove-addon-disabled-button = Cant Be Removed <a data-l10n-name="link">Why?</a>
disable-addon-button = Disable
enable-addon-button = Enable
disable-addon-button-label =
.aria-label = Disable
enable-addon-button-label =
# This is used for the toggle on the extension card, it's a checkbox and this
# is always its label.
extension-enable-addon-button-label =
.aria-label = Enable
preferences-addon-button =
{ PLATFORM() ->
@ -482,5 +480,9 @@ shortcuts-heading = Manage Extension Shortcuts
theme-heading-search-label = Find more themes
extension-heading-search-label = Find more extensions
default-heading-search-label = Find more add-ons
addons-heading-search-input =
.placeholder = Search addons.mozilla.org
addon-page-options-button =
.title = Tools for all add-ons

View File

@ -292,10 +292,6 @@ addon-card:not([expanded]) .addon-description {
/* Discopane extensions to the add-on card */
recommended-addon-card .addon-name {
display: flex;
}
recommended-addon-card .addon-description:not(:empty) {
margin-top: 0.5em;
}
@ -306,6 +302,13 @@ recommended-addon-card .addon-description:not(:empty) {
flex-direction: column;
}
.disco-addon-name {
font-size: inherit;
font-weight: normal;
line-height: normal;
margin: 0;
}
.disco-addon-author {
font-size: 12px;
font-weight: normal;

View File

@ -5,6 +5,8 @@
<!DOCTYPE html>
<html>
<head>
<title data-l10n-id="addons-page-title"></title>
<!-- Bug 1571346 Remove 'unsafe-inline' from style-src within about:addons -->
<!-- @CSP the 'onclick' handler for the searchButtonIcon within the file search-textbox.js, using: sha512-kSDNX67wegjpcf8CSj/L6h46a0QUKm2CyijGxC5PhSWVvPU9gdd28QVBBFq9t8N5UGKUFdDcZsjYbGSlYG0y3g== -->
<meta http-equiv="Content-Security-Policy" content="default-src chrome:; script-src chrome: 'sha512-kSDNX67wegjpcf8CSj/L6h46a0QUKm2CyijGxC5PhSWVvPU9gdd28QVBBFq9t8N5UGKUFdDcZsjYbGSlYG0y3g=='; style-src chrome: 'unsafe-inline'; img-src chrome: file: jar: https: http:; connect-src chrome: data: https: http:; object-src 'none'">
@ -54,7 +56,7 @@
<div class="spacer"></div>
<addon-updates-message id="updates-message" hidden></addon-updates-message>
<div class="page-options-menu">
<button class="more-options-button" action="page-options"></button>
<button class="more-options-button" action="page-options" aria-haspopup="menu" aria-expanded="false" data-l10n-id="addon-page-options-button"></button>
</div>
</div>
</div>
@ -121,7 +123,7 @@
</a>
<div class="spacer"></div>
<button class="theme-enable-button" action="toggle-disabled" hidden></button>
<input type="checkbox" class="toggle-button extension-enable-button" action="toggle-disabled" hidden>
<input type="checkbox" class="toggle-button extension-enable-button" action="toggle-disabled" data-l10n-id="extension-enable-addon-button-label" hidden>
<button
class="more-options-button"
action="more-options"
@ -142,7 +144,7 @@
<template name="addon-name-container-in-disco-card">
<div class="disco-card-head">
<span class="disco-addon-name"></span>
<h3 class="disco-addon-name"></h3>
<span class="disco-addon-author"><a data-l10n-name="author" target="_blank"></a></span>
</div>
<button class="disco-cta-button primary" action="install-addon"></button>
@ -342,7 +344,7 @@
<div class="card shortcut">
<div class="card-heading">
<img class="card-heading-icon addon-icon">
<span class="addon-name"></span>
<h2 class="addon-name"></h2>
</div>
</div>
</template>

View File

@ -1283,6 +1283,9 @@ class AddonPageHeader extends HTMLElement {
this.heading = this.querySelector(".header-name");
this.searchLabel = this.querySelector(".search-label");
this.backButton = this.querySelector(".back-button");
this.pageOptionsMenuButton = this.querySelector(
'[action="page-options"]'
);
// The addon-page-options element is outside of this element since this is
// position: sticky and that would break the positioning of the menu.
this.pageOptionsMenu = document.getElementById(
@ -1290,10 +1293,16 @@ class AddonPageHeader extends HTMLElement {
);
}
this.addEventListener("click", this);
// Use capture since the event is actually triggered on the internal
// panel-list and it doesn't bubble.
this.pageOptionsMenu.addEventListener("shown", this, true);
this.pageOptionsMenu.addEventListener("hidden", this, true);
}
disconnectedCallback() {
this.removeEventListener("click", this);
this.pageOptionsMenu.removeEventListener("shown", this, true);
this.pageOptionsMenu.removeEventListener("hidden", this, true);
}
setViewInfo({ type, param }) {
@ -1309,20 +1318,16 @@ class AddonPageHeader extends HTMLElement {
document.l10n.setAttributes(this.heading, `${viewType}-heading`);
}
if (
viewType === "extension" ||
viewType === "theme" ||
viewType === "shortcuts"
) {
let labelType = viewType === "shortcuts" ? "extension" : viewType;
document.l10n.setAttributes(
this.searchLabel,
`${labelType}-heading-search-label`
);
} else {
this.searchLabel.removeAttribute("data-l10n-id");
this.searchLabel.textContent = "";
}
let customSearchLabelTypes = {
shortcuts: "extension",
extension: "extension",
theme: "theme",
};
let searchLabelType = customSearchLabelTypes[viewType] || "default";
document.l10n.setAttributes(
this.searchLabel,
`${searchLabelType}-heading-search-label`
);
}
handleEvent(e) {
@ -1336,6 +1341,11 @@ class AddonPageHeader extends HTMLElement {
this.pageOptionsMenu.toggle(e);
break;
}
} else if (e.type == "shown" || e.type == "hidden") {
this.pageOptionsMenuButton.setAttribute(
"aria-expanded",
this.pageOptionsMenu.open
);
}
}
}
@ -1412,6 +1422,10 @@ class AddonPageOptions extends HTMLElement {
return this.panel.toggle(...args);
}
get open() {
return this.panel.open;
}
render() {
this.appendChild(importTemplate("addon-page-options"));
this.panel = this.querySelector("panel-list");
@ -2730,11 +2744,10 @@ class AddonCard extends HTMLElement {
}
name.title = `${addon.name} ${addon.version}`;
let toggleDisabledAction = addon.userDisabled ? "enable" : "disable";
let canToggleDisabled = hasPermission(addon, toggleDisabledAction);
let toggleDisabledButton = card.querySelector('[action="toggle-disabled"]');
if (toggleDisabledButton) {
toggleDisabledButton.hidden = !canToggleDisabled;
let toggleDisabledAction = addon.userDisabled ? "enable" : "disable";
toggleDisabledButton.hidden = !hasPermission(addon, toggleDisabledAction);
if (addon.type === "theme") {
document.l10n.setAttributes(
toggleDisabledButton,
@ -2742,10 +2755,6 @@ class AddonCard extends HTMLElement {
);
} else if (addon.type === "extension") {
toggleDisabledButton.checked = !addon.userDisabled;
document.l10n.setAttributes(
toggleDisabledButton,
`${toggleDisabledAction}-addon-button-label`
);
}
}

View File

@ -94,6 +94,8 @@ skip-if = verify
[browser_interaction_telemetry.js]
[browser_manage_shortcuts.js]
[browser_manage_shortcuts_hidden.js]
[browser_menu_button_accessibility.js]
[browser_page_accessibility.js]
[browser_page_options_install_addon.js]
[browser_page_options_updates.js]
[browser_panel_item_accesskey.js]

View File

@ -19,9 +19,10 @@ function assertDisabledSideloadedExtensionElement(managerWindow, addonElement) {
);
is(
doc.l10n.getAttributes(toggleDisabled).id,
"enable-addon-button-label",
"extension-enable-addon-button-label",
"Addon toggle-disabled action has the enable label"
);
ok(!toggleDisabled.checked, "toggle-disable isn't checked");
}
function assertEnabledSideloadedExtensionElement(managerWindow, addonElement) {
@ -31,9 +32,10 @@ function assertEnabledSideloadedExtensionElement(managerWindow, addonElement) {
);
is(
doc.l10n.getAttributes(toggleDisabled).id,
"enable-addon-button-label",
"extension-enable-addon-button-label",
"Addon toggle-disabled action has the enable label"
);
ok(!toggleDisabled.checked, "toggle-disable isn't checked");
}
function clickEnableExtension(managerWindow, addonElement) {

View File

@ -110,8 +110,8 @@ add_task(async function testExtensionList() {
ok(disableToggle.checked, "The disable toggle is checked");
is(
doc.l10n.getAttributes(disableToggle).id,
"disable-addon-button-label",
"The toggle has the disable label"
"extension-enable-addon-button-label",
"The toggle has the enable label"
);
ok(disableToggle.getAttribute("aria-label"), "There's an aria-label");
ok(!disableToggle.hidden, "The toggle is visible");
@ -129,8 +129,8 @@ add_task(async function testExtensionList() {
ok(!disableToggle.checked, "The disable toggle is unchecked");
is(
doc.l10n.getAttributes(disableToggle).id,
"enable-addon-button-label",
"The button has the enable label"
"extension-enable-addon-button-label",
"The button has the same enable label"
);
ok(disableToggle.getAttribute("aria-label"), "There's an aria-label");

View File

@ -0,0 +1,81 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
async function testOpenMenu(btn, method) {
let shown = BrowserTestUtils.waitForEvent(btn.ownerGlobal, "shown", true);
await method();
await shown;
is(btn.getAttribute("aria-expanded"), "true", "expanded when open");
}
async function testCloseMenu(btn, method) {
let hidden = BrowserTestUtils.waitForEvent(btn.ownerGlobal, "hidden", true);
await method();
await hidden;
is(btn.getAttribute("aria-expanded"), "false", "not expanded when closed");
}
async function testButton(btn) {
let win = btn.ownerGlobal;
is(btn.getAttribute("aria-haspopup"), "menu", "it has a menu");
is(btn.getAttribute("aria-expanded"), "false", "not expanded");
info("Test open/close with mouse");
await testOpenMenu(btn, () => {
EventUtils.synthesizeMouseAtCenter(btn, {}, win);
});
await testCloseMenu(btn, () => {
let spacer = win.document.querySelector(".main-heading .spacer");
EventUtils.synthesizeMouseAtCenter(spacer, {}, win);
});
info("Test open/close with keyboard");
await testOpenMenu(btn, async () => {
btn.focus();
EventUtils.synthesizeKey(" ", {}, win);
});
await testCloseMenu(btn, () => {
EventUtils.synthesizeKey("Escape", {}, win);
});
}
add_task(async function testPageOptionsMenuButton() {
let win = await loadInitialView("extension");
await testButton(
win.document.querySelector(".page-options-menu .more-options-button")
);
await closeView(win);
});
add_task(async function testCardMoreOptionsButton() {
let id = "more-options-button@mochi.test";
let extension = ExtensionTestUtils.loadExtension({
manifest: {
applications: { gecko: { id } },
},
useAddonManager: "temporary",
});
await extension.startup();
let win = await loadInitialView("extension");
let card = getAddonCard(win, id);
info("Check list page");
await testButton(card.querySelector(".more-options-button"));
let viewLoaded = waitForViewLoad(win);
EventUtils.synthesizeMouseAtCenter(card, {}, win);
await viewLoaded;
info("Check detail page");
card = getAddonCard(win, id);
await testButton(card.querySelector(".more-options-button"));
await closeView(win);
await extension.unload();
});

View File

@ -0,0 +1,15 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
add_task(async function testPageTitle() {
let win = await loadInitialView("extension");
let title = win.document.querySelector("title");
is(
win.document.l10n.getAttributes(title).id,
"addons-page-title",
"The page title is set"
);
await closeView(win);
});