Bug 1518808 - add accessibility highlighter infobar for text label audit. When accessible object does not pass text label accessibility audit, the infobar will display a short message describing the particular labeling issue. r=nchevobbe,flod

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

--HG--
extra : moz-landing-system : lando
This commit is contained in:
Yura Zenevich 2019-07-04 15:16:57 +00:00
Родитель a2bb375be2
Коммит 1012544d50
6 изменённых файлов: 397 добавлений и 6 удалений

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

@ -39,6 +39,8 @@
--highlighter-marker-color: #000;
--grey-40: #b1b1b3;
--red-40: #ff3b6b;
--yellow-60: #d7b600;
}
/**
@ -737,6 +739,34 @@
margin-inline-start: 3px;
}
:-moz-native-anonymous .accessible-infobar-audit .accessible-text-label:before {
display: inline-block;
width: 12px;
height: 12px;
content: "";
margin-inline-end: 4px;
vertical-align: -2px;
background-image: none;
background-position: center;
background-repeat: no-repeat;
-moz-context-properties: fill;
fill: currentColor;
}
:-moz-native-anonymous .accessible-infobar-audit .accessible-text-label.fail:before {
background-image: url(chrome://devtools/skin/images/error-small.svg);
fill: var(--red-40);
}
:-moz-native-anonymous .accessible-infobar-audit .accessible-text-label.WARNING:before {
background-image: url(chrome://devtools/skin/images/alert-small.svg);
fill: var(--yellow-60);
}
:-moz-native-anonymous .accessible-infobar-audit .accessible-text-label.BEST_PRACTICES:before {
background-image: url(chrome://devtools/skin/images/info-small.svg);
}
:-moz-native-anonymous .accessible-infobar-name:not(:empty) {
border-inline-start: 1px solid #5a6169;
margin-inline-start: 6px;

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

@ -9,13 +9,39 @@ const { getCurrentZoom, getViewportDimensions } = require("devtools/shared/layou
const { moveInfobar, createNode } = require("./markup");
const { truncateString } = require("devtools/shared/inspector/utils");
const { accessibility: { SCORES } } = require("devtools/shared/constants");
const STRINGS_URI = "devtools/shared/locales/accessibility.properties";
loader.lazyRequireGetter(this, "LocalizationHelper", "devtools/shared/l10n", true);
DevToolsUtils.defineLazyGetter(this, "L10N", () => new LocalizationHelper(STRINGS_URI));
const { accessibility: { AUDIT_TYPE } } = require("devtools/shared/constants");
const {
accessibility: {
AUDIT_TYPE,
ISSUE_TYPE: {
[AUDIT_TYPE.TEXT_LABEL]: {
AREA_NO_NAME_FROM_ALT,
DIALOG_NO_NAME,
DOCUMENT_NO_TITLE,
EMBED_NO_NAME,
FIGURE_NO_NAME,
FORM_FIELDSET_NO_NAME,
FORM_FIELDSET_NO_NAME_FROM_LEGEND,
FORM_NO_NAME,
FORM_NO_VISIBLE_NAME,
FORM_OPTGROUP_NO_NAME,
FORM_OPTGROUP_NO_NAME_FROM_LABEL,
FRAME_NO_NAME,
HEADING_NO_CONTENT,
HEADING_NO_NAME,
IFRAME_NO_NAME_FROM_TITLE,
IMAGE_NO_NAME,
INTERACTIVE_NO_NAME,
MATHML_GLYPH_NO_NAME,
TOOLBAR_NO_NAME,
},
},
SCORES,
},
} = require("devtools/shared/constants");
// Max string length for truncating accessible name values.
const MAX_STRING_LENGTH = 50;
@ -383,6 +409,7 @@ class Audit {
// object.
this.reports = [
new ContrastRatio(this),
new TextLabel(this),
];
}
@ -539,13 +566,13 @@ class ContrastRatio extends AuditReport {
/**
* Update contrast ratio score infobar markup.
* @param {Number}
* Contrast ratio for an accessible object being highlighted.
* @param {Object}
* Audit report for a given highlighted accessible.
* @return {Boolean}
* True if the contrast ratio markup was updated correctly and infobar audit
* block should be visible.
*/
update({ [AUDIT_TYPE.CONTRAST]: contrastRatio }) {
update(audit) {
const els = {};
for (const key of ["label", "min", "max", "error", "separator"]) {
const el = els[key] = this.getElement(`contrast-ratio-${key}`);
@ -559,6 +586,11 @@ class ContrastRatio extends AuditReport {
el.removeAttribute("style");
}
if (!audit) {
return false;
}
const contrastRatio = audit[AUDIT_TYPE.CONTRAST];
if (!contrastRatio) {
return false;
}
@ -592,6 +624,82 @@ class ContrastRatio extends AuditReport {
}
}
/**
* Text label audit report that is used to display a problem with text alternatives
* as part of the inforbar.
*/
class TextLabel extends AuditReport {
/**
* A map from text label issues to annotation component properties.
*/
static get ISSUE_TO_INFOBAR_LABEL_MAP() {
return {
[AREA_NO_NAME_FROM_ALT]: "accessibility.text.label.issue.area",
[DIALOG_NO_NAME]: "accessibility.text.label.issue.dialog",
[DOCUMENT_NO_TITLE]: "accessibility.text.label.issue.document.title",
[EMBED_NO_NAME]: "accessibility.text.label.issue.embed",
[FIGURE_NO_NAME]: "accessibility.text.label.issue.figure",
[FORM_FIELDSET_NO_NAME]: "accessibility.text.label.issue.fieldset",
[FORM_FIELDSET_NO_NAME_FROM_LEGEND]:
"accessibility.text.label.issue.fieldset.legend",
[FORM_NO_NAME]: "accessibility.text.label.issue.form",
[FORM_NO_VISIBLE_NAME]: "accessibility.text.label.issue.form.visible",
[FORM_OPTGROUP_NO_NAME]: "accessibility.text.label.issue.optgroup",
[FORM_OPTGROUP_NO_NAME_FROM_LABEL]: "accessibility.text.label.issue.optgroup.label",
[FRAME_NO_NAME]: "accessibility.text.label.issue.frame",
[HEADING_NO_CONTENT]: "accessibility.text.label.issue.heading.content",
[HEADING_NO_NAME]: "accessibility.text.label.issue.heading",
[IFRAME_NO_NAME_FROM_TITLE]: "accessibility.text.label.issue.iframe",
[IMAGE_NO_NAME]: "accessibility.text.label.issue.image",
[INTERACTIVE_NO_NAME]: "accessibility.text.label.issue.interactive",
[MATHML_GLYPH_NO_NAME]: "accessibility.text.label.issue.glyph",
[TOOLBAR_NO_NAME]: "accessibility.text.label.issue.toolbar",
};
}
buildMarkup(root) {
createNode(this.win, {
nodeType: "span",
parent: root,
attributes: {
"class": "text-label",
"id": "text-label",
},
prefix: this.prefix,
});
}
/**
* Update text label audit infobar markup.
* @param {Object}
* Audit report for a given highlighted accessible.
* @return {Boolean}
* True if the text label markup was updated correctly and infobar
* audit block should be visible.
*/
update(audit) {
const el = this.getElement("text-label");
el.setAttribute("hidden", true);
Object.values(SCORES).forEach(className => el.classList.remove(className));
if (!audit) {
return false;
}
const textLabelAudit = audit[AUDIT_TYPE.TEXT_LABEL];
if (!textLabelAudit) {
return false;
}
const { issue, score } = textLabelAudit;
this.setTextContent(el, L10N.getStr(TextLabel.ISSUE_TO_INFOBAR_LABEL_MAP[issue]));
el.classList.add(score);
el.removeAttribute("hidden");
return true;
}
}
/**
* A helper function that calculate accessible object bounds and positioning to
* be used for highlighting.

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

@ -20,6 +20,8 @@ const ACCESSIBLE_BOUNDS_SHEET = "data:text/css;charset=utf-8," + encodeURICompon
--highlighter-bubble-arrow-size: 8px;
--grey-40: #b1b1b3;
--red-40: #ff3b6b;
--yellow-60: #d7b600;
}
.accessible-bounds {
@ -147,6 +149,34 @@ const ACCESSIBLE_BOUNDS_SHEET = "data:text/css;charset=utf-8," + encodeURICompon
border-inline-start: 1px solid #5a6169;
margin-inline-start: 6px;
padding-inline-start: 6px;
}
.accessible-infobar-audit .accessible-text-label:before {
display: inline-block;
width: 12px;
height: 12px;
content: "";
margin-inline-end: 4px;
vertical-align: -2px;
background-image: none;
background-position: center;
background-repeat: no-repeat;
-moz-context-properties: fill;
fill: currentColor;
}
.accessible-infobar-audit .accessible-text-label.fail:before {
background-image: url(chrome://devtools/skin/images/error-small.svg);
fill: var(--red-40);
}
.accessible-infobar-audit .accessible-text-label.WARNING:before {
background-image: url(chrome://devtools/skin/images/alert-small.svg);
fill: var(--yellow-60);
}
.accessible-infobar-audit .accessible-text-label.BEST_PRACTICES:before {
background-image: url(chrome://devtools/skin/images/info-small.svg);
}`);
/**

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

@ -43,6 +43,7 @@ support-files =
[browser_accessibility_highlighter_infobar.js]
skip-if = (os == 'win' && processor == 'aarch64') # bug 1533184
[browser_accessibility_infobar_show.js]
[browser_accessibility_infobar_audit_text_label.js]
[browser_accessibility_node.js]
skip-if = (os == 'win' && processor == 'aarch64') # bug 1533184
[browser_accessibility_node_audit.js]

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

@ -0,0 +1,127 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
"use strict";
// Checks for the AccessibleHighlighter's infobar component and its text label
// audit.
add_task(async function() {
await BrowserTestUtils.withNewTab({
gBrowser,
url: MAIN_DOMAIN + "doc_accessibility_infobar.html",
}, async function(browser) {
await ContentTask.spawn(browser, null, async function() {
const { require } = ChromeUtils.import("resource://devtools/shared/Loader.jsm");
const { HighlighterEnvironment } = require("devtools/server/actors/highlighters");
const { AccessibleHighlighter } = require("devtools/server/actors/highlighters/accessible");
const { LocalizationHelper } = require("devtools/shared/l10n");
const L10N = new LocalizationHelper(
"devtools/shared/locales/accessibility.properties");
const {
accessibility: {
AUDIT_TYPE,
ISSUE_TYPE: {
[AUDIT_TYPE.TEXT_LABEL]: {
DIALOG_NO_NAME,
FORM_NO_VISIBLE_NAME,
TOOLBAR_NO_NAME,
},
},
SCORES: { BEST_PRACTICES, FAIL, WARNING },
},
} = require("devtools/shared/constants");
/**
* Checks for updated content for an infobar.
*
* @param {Object} infobar
* Accessible highlighter's infobar component.
* @param {Object} audit
* Audit information that is passed on highlighter show.
*/
function checkTextLabel(infobar, audit) {
const { issue, score } = audit || {};
let expected = "";
if (issue) {
const { ISSUE_TO_INFOBAR_LABEL_MAP } = infobar.audit.reports[1].constructor;
expected = L10N.getStr(ISSUE_TO_INFOBAR_LABEL_MAP[issue]);
}
is(infobar.getTextContent("text-label"), expected,
"infobar text label audit text content is correct");
if (score) {
ok(infobar.getElement("text-label").classList.contains(
score === FAIL ? "fail" : score));
}
}
// Start testing. First, create highlighter environment and initialize.
const env = new HighlighterEnvironment();
env.initFromWindow(content.window);
// Wait for loading highlighter environment content to complete before creating the
// highlighter.
await new Promise(resolve => {
const doc = env.document;
function onContentLoaded() {
if (doc.readyState === "interactive" || doc.readyState === "complete") {
resolve();
} else {
doc.addEventListener("DOMContentLoaded", onContentLoaded, { once: true });
}
}
onContentLoaded();
});
// Now, we can test the Infobar's audit content.
const node = content.document.createElement("div");
content.document.body.append(node);
const highlighter = new AccessibleHighlighter(env);
const infobar = highlighter.accessibleInfobar;
const bounds = {
x: 0,
y: 0,
w: 250,
h: 100,
};
const tests = [{
desc: "Infobar is shown with no text label audit content when no audit.",
}, {
desc: "Infobar is shown with no text label audit content when audit is null.",
audit: null,
}, {
desc: "Infobar is shown with no text label audit content when empty " +
"text label audit.",
audit: { [AUDIT_TYPE.TEXT_LABEL]: null },
}, {
desc: "Infobar is shown with text label audit content for an error.",
audit: { [AUDIT_TYPE.TEXT_LABEL]: { score: FAIL, issue: TOOLBAR_NO_NAME } },
}, {
desc: "Infobar is shown with text label audit content for a warning.",
audit: { [AUDIT_TYPE.TEXT_LABEL]: {
score: WARNING, issue: FORM_NO_VISIBLE_NAME,
}},
}, {
desc: "Infobar is shown with text label audit content for best practices.",
audit: { [AUDIT_TYPE.TEXT_LABEL]: {
score: BEST_PRACTICES, issue: DIALOG_NO_NAME,
}},
}];
for (const test of tests) {
const { desc, audit } = test;
info(desc);
highlighter.show(node, { ...bounds, audit });
checkTextLabel(infobar, audit && audit[AUDIT_TYPE.TEXT_LABEL]);
highlighter.hide();
}
});
});
});

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

@ -19,3 +19,98 @@ accessibility.contrast.ratio.label=Contrast:
# contrast ratio description that also specifies that the color contrast criteria used is
# if for large text.
accessibility.contrast.ratio.label.large=Contrast (large text):
# LOCALIZATION NOTE (accessibility.text.label.issue.area): A title text that
# describes that currently selected accessible object for an <area> element must have
# its name provided via the alt attribute.
accessibility.text.label.issue.area = Use “alt” attribute to label “area” elements that have the “href” attribute.
# LOCALIZATION NOTE (accessibility.text.label.issue.dialog): A title text that
# describes that currently selected accessible object for a dialog should have a name
# provided.
accessibility.text.label.issue.dialog = Dialogs should be labeled.
# LOCALIZATION NOTE (accessibility.text.label.issue.document.title): A title text that
# describes that currently selected accessible object for a document must have a name
# provided via title.
accessibility.text.label.issue.document.title = Documents must have a title.
# LOCALIZATION NOTE (accessibility.text.label.issue.embed): A title text that
# describes that currently selected accessible object for an <embed> must have a name
# provided.
accessibility.text.label.issue.embed = Embedded content must be labeled.
# LOCALIZATION NOTE (accessibility.text.label.issue.figure): A title text that
# describes that currently selected accessible object for a figure should have a name
# provided.
accessibility.text.label.issue.figure = Figures with optional captions should be labeled.
# LOCALIZATION NOTE (accessibility.text.label.issue.fieldset): A title text that
# describes that currently selected accessible object for a <fieldset> must have a name
# provided.
accessibility.text.label.issue.fieldset = “fieldset” elements must be labeled.
# LOCALIZATION NOTE (accessibility.text.label.issue.fieldset.legend): A title text that
# describes that currently selected accessible object for a <fieldset> must have a name
# provided via <legend> element.
accessibility.text.label.issue.fieldset.legend = Use “legend” element to label “fieldset” elements.
# LOCALIZATION NOTE (accessibility.text.label.issue.form): A title text that
# describes that currently selected accessible object for a form element must have a name
# provided.
accessibility.text.label.issue.form = Form elements must be labeled.
# LOCALIZATION NOTE (accessibility.text.label.issue.form.visible): A title text that
# describes that currently selected accessible object for a form element should have a name
# provided via a visible label/element.
accessibility.text.label.issue.form.visible = Form elements should have a visible text label.
# LOCALIZATION NOTE (accessibility.text.label.issue.frame): A title text that
# describes that currently selected accessible object for a <frame> must have a name
# provided.
accessibility.text.label.issue.frame = “frame” elements must be labeled.
# LOCALIZATION NOTE (accessibility.text.label.issue.glyph): A title text that
# describes that currently selected accessible object for a <mglyph> must have a name
# provided via alt attribute.
accessibility.text.label.issue.glyph = Use “alt” attribute to label “mglyph” elements.
# LOCALIZATION NOTE (accessibility.text.label.issue.heading): A title text that
# describes that currently selected accessible object for a heading must have a name
# provided.
accessibility.text.label.issue.heading = Headings must be labeled.
# LOCALIZATION NOTE (accessibility.text.label.issue.heading.content): A title text that
# describes that currently selected accessible object for a heading must have visible
# content.
accessibility.text.label.issue.heading.content = Headings should have visible text content.
# LOCALIZATION NOTE (accessibility.text.label.issue.iframe): A title text that
# describes that currently selected accessible object for an <iframe> have a name
# provided via title attribute.
accessibility.text.label.issue.iframe = Use “title” attribute to describe “iframe” content.
# LOCALIZATION NOTE (accessibility.text.label.issue.image): A title text that
# describes that currently selected accessible object for graphical content must have a
# name provided.
accessibility.text.label.issue.image = Content with images must be labeled.
# LOCALIZATION NOTE (accessibility.text.label.issue.interactive): A title text that
# describes that currently selected accessible object for interactive element must have a
# name provided.
accessibility.text.label.issue.interactive = Interactive elements must be labeled.
# LOCALIZATION NOTE (accessibility.text.label.issue.optgroup): A title text that
# describes that currently selected accessible object for an <optgroup> must have a
# name provided.
accessibility.text.label.issue.optgroup = “optgroup” elements must be labeled.
# LOCALIZATION NOTE (accessibility.text.label.issue.optgroup.label): A title text that
# describes that currently selected accessible object for an <optgroup> must have a
# name provided via label attribute.
accessibility.text.label.issue.optgroup.label = Use “label” attribute to label “optgroup” elements.
# LOCALIZATION NOTE (accessibility.text.label.issue.toolbar): A title text that
# describes that currently selected accessible object for a toolbar must have a
# name provided when there is more than one toolbar in the document.
accessibility.text.label.issue.toolbar = Toolbars must be labeled when there is more than one toolbar.