зеркало из https://github.com/mozilla/gecko-dev.git
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:
Родитель
a2bb375be2
Коммит
1012544d50
|
@ -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.
|
||||
|
|
Загрузка…
Ссылка в новой задаче