зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1552067 - add checks section for text label audit. r=mtigley,fluent-reviewers,flod
Differential Revision: https://phabricator.services.mozilla.com/D35497 --HG-- extra : moz-landing-system : lando
This commit is contained in:
Родитель
76b7fe4f3e
Коммит
a2bb375be2
|
@ -52,8 +52,9 @@ AccessibilityView.prototype = {
|
|||
* @param {JSON} supports a collection of flags indicating which accessibility
|
||||
* panel features are supported by the current serverside
|
||||
* version.
|
||||
* @param {Array} fluentBundles array of FluentBundles elements for localization
|
||||
*/
|
||||
async initialize(accessibility, walker, supports) {
|
||||
async initialize(accessibility, walker, supports, fluentBundles) {
|
||||
// Make sure state is reset every time accessibility panel is initialized.
|
||||
await this.store.dispatch(reset(accessibility, supports));
|
||||
const container = document.getElementById("content");
|
||||
|
@ -63,7 +64,7 @@ AccessibilityView.prototype = {
|
|||
return;
|
||||
}
|
||||
|
||||
const mainFrame = MainFrame({ accessibility, walker });
|
||||
const mainFrame = MainFrame({ accessibility, walker, fluentBundles });
|
||||
// Render top level component
|
||||
const provider = createElement(Provider, { store: this.store }, mainFrame);
|
||||
this.mainFrame = ReactDOM.render(provider, container);
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
--accessible-label-color: var(--grey-60);
|
||||
/* Similarly to webconsole, add more padding before the toolbar group. */
|
||||
--separator-inline-margin: 5px;
|
||||
--accessibility-code-background: var(--grey-20);
|
||||
}
|
||||
|
||||
:root.theme-dark {
|
||||
|
@ -42,6 +43,7 @@
|
|||
--accessible-label-background-color: var(--grey-80);
|
||||
--accessible-label-border-color: var(--grey-50);
|
||||
--accessible-label-color: var(--grey-40);
|
||||
--accessibility-code-background: var(--grey-70);
|
||||
}
|
||||
|
||||
/* General */
|
||||
|
@ -660,8 +662,31 @@ body {
|
|||
white-space: initial;
|
||||
}
|
||||
|
||||
/* Color Contrast */
|
||||
.accessibility-color-contrast-check,
|
||||
/* Checks */
|
||||
.accessibility-check code {
|
||||
background-color: var(--accessibility-code-background);
|
||||
border-radius: 2px;
|
||||
box-decoration-break: clone;
|
||||
padding: 0 4px;
|
||||
}
|
||||
|
||||
.accessibility-text-label-check .icon {
|
||||
display: inline;
|
||||
-moz-context-properties: fill;
|
||||
vertical-align: top;
|
||||
margin-block-start: 2px;
|
||||
margin-inline-end: 4px;
|
||||
}
|
||||
|
||||
.accessibility-text-label-check .icon.fail {
|
||||
fill: var(--theme-icon-error-color);
|
||||
}
|
||||
|
||||
.accessibility-text-label-check .icon.WARNING {
|
||||
fill: var(--theme-icon-warning-color);
|
||||
}
|
||||
|
||||
.accessibility-check,
|
||||
.accessibility-color-contrast {
|
||||
position: relative;
|
||||
display: flex;
|
||||
|
@ -669,7 +694,7 @@ body {
|
|||
height: inherit;
|
||||
}
|
||||
|
||||
.accessibility-color-contrast-check {
|
||||
.accessibility-check {
|
||||
flex-direction: column;
|
||||
padding: 4px var(--accessibility-horizontal-indent);
|
||||
line-height: 20px;
|
||||
|
@ -679,20 +704,21 @@ body {
|
|||
align-items: baseline;
|
||||
}
|
||||
|
||||
.accessibility-color-contrast-header {
|
||||
.accessibility-check-header {
|
||||
margin: 0;
|
||||
font-weight: bold;
|
||||
font-size: var(--accessibility-font-size);
|
||||
line-height: var(--accessibility-toolbar-height);
|
||||
}
|
||||
|
||||
.accessibility-color-contrast-annotation {
|
||||
.accessibility-check-annotation {
|
||||
display: inline;
|
||||
margin: 0;
|
||||
white-space: normal;
|
||||
color: var(--accessible-label-color);
|
||||
}
|
||||
|
||||
.accessibility-color-contrast-annotation .link {
|
||||
.accessibility-check-annotation .link {
|
||||
color: var(--accessibility-link-color);
|
||||
cursor: pointer;
|
||||
outline: 0;
|
||||
|
@ -700,16 +726,16 @@ body {
|
|||
font-style: normal;
|
||||
}
|
||||
|
||||
.accessibility-color-contrast-annotation .link:hover:not(:focus) {
|
||||
.accessibility-check-annotation .link:hover:not(:focus) {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.accessibility-color-contrast-annotation .link:focus:not(:active) {
|
||||
.accessibility-check-annotation .link:focus:not(:active) {
|
||||
box-shadow: 0 0 0 2px var(--accessibility-toolbar-focus), 0 0 0 4px var(--accessibility-toolbar-focus-alpha30);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.accessibility-color-contrast-annotation .link:active {
|
||||
.accessibility-check-annotation .link:active {
|
||||
color: var(--accessibility-link-color-active);
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ const { div } = require("devtools/client/shared/vendor/react-dom-factories");
|
|||
const List = createFactory(require("devtools/client/shared/components/List").List);
|
||||
const ColorContrastCheck =
|
||||
createFactory(require("./ColorContrastAccessibility").ColorContrastCheck);
|
||||
const TextLabelCheck = createFactory(require("./TextLabelCheck"));
|
||||
const { L10N } = require("../utils/l10n");
|
||||
|
||||
const { accessibility: { AUDIT_TYPE } } = require("devtools/shared/constants");
|
||||
|
@ -39,6 +40,10 @@ class Checks extends Component {
|
|||
return ColorContrastCheck(contrastRatio);
|
||||
}
|
||||
|
||||
[AUDIT_TYPE.TEXT_LABEL](textLabelCheck) {
|
||||
return TextLabelCheck(textLabelCheck);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { audit, labelledby } = this.props;
|
||||
if (!audit) {
|
||||
|
|
|
@ -152,7 +152,7 @@ class ContrastAnnotationClass extends Component {
|
|||
return (
|
||||
LearnMoreLink(
|
||||
{
|
||||
className: "accessibility-color-contrast-annotation",
|
||||
className: "accessibility-check-annotation",
|
||||
href: A11Y_CONTRAST_LEARN_MORE_LINK,
|
||||
learnMoreStringKey: "accessibility.learnMore",
|
||||
l10n: L10N,
|
||||
|
@ -178,10 +178,10 @@ class ColorContrastCheck extends Component {
|
|||
return (
|
||||
div({
|
||||
role: "presentation",
|
||||
className: "accessibility-color-contrast-check",
|
||||
className: "accessibility-check",
|
||||
},
|
||||
h3({
|
||||
className: "accessibility-color-contrast-header",
|
||||
className: "accessibility-check-header",
|
||||
}, L10N.getStr("accessibility.contrast.header")),
|
||||
ColorContrastAccessibility(this.props),
|
||||
!error && ContrastAnnotation(this.props)
|
||||
|
|
|
@ -12,6 +12,10 @@ const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
|
|||
const { connect } = require("devtools/client/shared/vendor/react-redux");
|
||||
const { reset } = require("../actions/ui");
|
||||
|
||||
// Localization
|
||||
const FluentReact = require("devtools/client/shared/vendor/fluent-react");
|
||||
const LocalizationProvider = createFactory(FluentReact.LocalizationProvider);
|
||||
|
||||
// Constants
|
||||
const { SIDEBAR_WIDTH, PORTRAIT_MODE_WIDTH } = require("../constants");
|
||||
|
||||
|
@ -31,6 +35,7 @@ class MainFrame extends Component {
|
|||
static get propTypes() {
|
||||
return {
|
||||
accessibility: PropTypes.object.isRequired,
|
||||
fluentBundles: PropTypes.array.isRequired,
|
||||
walker: PropTypes.object.isRequired,
|
||||
enabled: PropTypes.bool.isRequired,
|
||||
dispatch: PropTypes.func.isRequired,
|
||||
|
@ -91,7 +96,7 @@ class MainFrame extends Component {
|
|||
* Render Accessibility panel content
|
||||
*/
|
||||
render() {
|
||||
const { accessibility, walker, enabled, auditing } = this.props;
|
||||
const { accessibility, walker, fluentBundles, enabled, auditing } = this.props;
|
||||
|
||||
if (!enabled) {
|
||||
return Description({ accessibility });
|
||||
|
@ -100,7 +105,7 @@ class MainFrame extends Component {
|
|||
// Audit is currently running.
|
||||
const isAuditing = auditing.length > 0;
|
||||
|
||||
return (
|
||||
return LocalizationProvider({ messages: fluentBundles },
|
||||
div({ className: "mainFrame", role: "presentation" },
|
||||
Toolbar({ accessibility, walker }),
|
||||
isAuditing && AuditProgressOverlay(),
|
||||
|
@ -122,8 +127,10 @@ class MainFrame extends Component {
|
|||
}, AccessibilityTree({ walker })),
|
||||
endPanel: RightSidebar({ walker }),
|
||||
vert: this.useLandscapeMode,
|
||||
})),
|
||||
));
|
||||
})
|
||||
),
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,335 @@
|
|||
/* 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";
|
||||
|
||||
// React
|
||||
const { Component, createFactory } = require("devtools/client/shared/vendor/react");
|
||||
const PropTypes = require("devtools/client/shared/vendor/react-prop-types");
|
||||
const ReactDOM = require("devtools/client/shared/vendor/react-dom-factories");
|
||||
|
||||
const FluentReact = require("devtools/client/shared/vendor/fluent-react");
|
||||
const Localized = createFactory(FluentReact.Localized);
|
||||
|
||||
const { openDocLink } = require("devtools/client/shared/link");
|
||||
|
||||
const { A11Y_TEXT_LABEL_LINKS } = require("../constants");
|
||||
|
||||
const {
|
||||
accessibility: {
|
||||
AUDIT_TYPE: { TEXT_LABEL },
|
||||
ISSUE_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: { BEST_PRACTICES, FAIL, WARNING },
|
||||
},
|
||||
} = require("devtools/shared/constants");
|
||||
|
||||
/**
|
||||
* A map from text label issues to annotation component properties.
|
||||
*/
|
||||
const ISSUE_TO_ANNOTATION_MAP = {
|
||||
[AREA_NO_NAME_FROM_ALT]: {
|
||||
href: A11Y_TEXT_LABEL_LINKS.AREA_NO_NAME_FROM_ALT,
|
||||
l10nId: "accessibility-text-label-issue-area",
|
||||
args: {
|
||||
get code() {
|
||||
return ReactDOM.code({}, "alt");
|
||||
},
|
||||
// Note: there is no way right now to use custom elements in privileged
|
||||
// content. We have to use something like <div> since we can't provide
|
||||
// three args with the same name.
|
||||
get div() {
|
||||
return ReactDOM.code({}, "area");
|
||||
},
|
||||
// Note: there is no way right now to use custom elements in privileged
|
||||
// content. We have to use something like <span> since we can't provide
|
||||
// three args with the same name.
|
||||
get span() {
|
||||
return ReactDOM.code({}, "href");
|
||||
},
|
||||
},
|
||||
},
|
||||
[DIALOG_NO_NAME]: {
|
||||
href: A11Y_TEXT_LABEL_LINKS.DIALOG_NO_NAME,
|
||||
l10nId: "accessibility-text-label-issue-dialog",
|
||||
},
|
||||
[DOCUMENT_NO_TITLE]: {
|
||||
href: A11Y_TEXT_LABEL_LINKS.DOCUMENT_NO_TITLE,
|
||||
l10nId: "accessibility-text-label-issue-document-title",
|
||||
args: {
|
||||
get code() {
|
||||
return ReactDOM.code({}, "title");
|
||||
},
|
||||
},
|
||||
},
|
||||
[EMBED_NO_NAME]: {
|
||||
href: A11Y_TEXT_LABEL_LINKS.EMBED_NO_NAME,
|
||||
l10nId: "accessibility-text-label-issue-embed",
|
||||
},
|
||||
[FIGURE_NO_NAME]: {
|
||||
href: A11Y_TEXT_LABEL_LINKS.FIGURE_NO_NAME,
|
||||
l10nId: "accessibility-text-label-issue-figure",
|
||||
},
|
||||
[FORM_FIELDSET_NO_NAME]: {
|
||||
href: A11Y_TEXT_LABEL_LINKS.FORM_FIELDSET_NO_NAME,
|
||||
l10nId: "accessibility-text-label-issue-fieldset",
|
||||
args: {
|
||||
get code() {
|
||||
return ReactDOM.code({}, "fieldset");
|
||||
},
|
||||
},
|
||||
},
|
||||
[FORM_FIELDSET_NO_NAME_FROM_LEGEND]: {
|
||||
href: A11Y_TEXT_LABEL_LINKS.FORM_FIELDSET_NO_NAME_FROM_LEGEND,
|
||||
l10nId: "accessibility-text-label-issue-fieldset-legend",
|
||||
args: {
|
||||
get code() {
|
||||
return ReactDOM.code({}, "legend");
|
||||
},
|
||||
// Note: there is no way right now to use custom elements in privileged
|
||||
// content. We have to use something like <span> since we can't provide
|
||||
// two args with the same name.
|
||||
get span() {
|
||||
return ReactDOM.code({}, "fieldset");
|
||||
},
|
||||
},
|
||||
},
|
||||
[FORM_NO_NAME]: {
|
||||
href: A11Y_TEXT_LABEL_LINKS.FORM_NO_NAME,
|
||||
l10nId: "accessibility-text-label-issue-form",
|
||||
},
|
||||
[FORM_NO_VISIBLE_NAME]: {
|
||||
href: A11Y_TEXT_LABEL_LINKS.FORM_NO_VISIBLE_NAME,
|
||||
l10nId: "accessibility-text-label-issue-form-visible",
|
||||
},
|
||||
[FORM_OPTGROUP_NO_NAME]: {
|
||||
href: A11Y_TEXT_LABEL_LINKS.FORM_OPTGROUP_NO_NAME,
|
||||
l10nId: "accessibility-text-label-issue-optgroup",
|
||||
args: {
|
||||
get code() {
|
||||
return ReactDOM.code({}, "optgroup");
|
||||
},
|
||||
},
|
||||
},
|
||||
[FORM_OPTGROUP_NO_NAME_FROM_LABEL]: {
|
||||
href: A11Y_TEXT_LABEL_LINKS.FORM_OPTGROUP_NO_NAME_FROM_LABEL,
|
||||
l10nId: "accessibility-text-label-issue-optgroup-label",
|
||||
args: {
|
||||
get code() {
|
||||
return ReactDOM.code({}, "label");
|
||||
},
|
||||
// Note: there is no way right now to use custom elements in privileged
|
||||
// content. We have to use something like <span> since we can't provide
|
||||
// two args with the same name.
|
||||
get span() {
|
||||
return ReactDOM.code({}, "optgroup");
|
||||
},
|
||||
},
|
||||
},
|
||||
[FRAME_NO_NAME]: {
|
||||
href: A11Y_TEXT_LABEL_LINKS.FRAME_NO_NAME,
|
||||
l10nId: "accessibility-text-label-issue-frame",
|
||||
args: {
|
||||
get code() {
|
||||
return ReactDOM.code({}, "frame");
|
||||
},
|
||||
},
|
||||
},
|
||||
[HEADING_NO_CONTENT]: {
|
||||
href: A11Y_TEXT_LABEL_LINKS.HEADING_NO_CONTENT,
|
||||
l10nId: "accessibility-text-label-issue-heading-content",
|
||||
},
|
||||
[HEADING_NO_NAME]: {
|
||||
href: A11Y_TEXT_LABEL_LINKS.HEADING_NO_NAME,
|
||||
l10nId: "accessibility-text-label-issue-heading",
|
||||
},
|
||||
[IFRAME_NO_NAME_FROM_TITLE]: {
|
||||
href: A11Y_TEXT_LABEL_LINKS.IFRAME_NO_NAME_FROM_TITLE,
|
||||
l10nId: "accessibility-text-label-issue-iframe",
|
||||
args: {
|
||||
get code() {
|
||||
return ReactDOM.code({}, "title");
|
||||
},
|
||||
// Note: there is no way right now to use custom elements in privileged
|
||||
// content. We have to use something like <span> since we can't provide
|
||||
// two args with the same name.
|
||||
get span() {
|
||||
return ReactDOM.code({}, "iframe");
|
||||
},
|
||||
},
|
||||
},
|
||||
[IMAGE_NO_NAME]: {
|
||||
href: A11Y_TEXT_LABEL_LINKS.IMAGE_NO_NAME,
|
||||
l10nId: "accessibility-text-label-issue-image",
|
||||
},
|
||||
[INTERACTIVE_NO_NAME]: {
|
||||
href: A11Y_TEXT_LABEL_LINKS.INTERACTIVE_NO_NAME,
|
||||
l10nId: "accessibility-text-label-issue-interactive",
|
||||
},
|
||||
[MATHML_GLYPH_NO_NAME]: {
|
||||
href: A11Y_TEXT_LABEL_LINKS.MATHML_GLYPH_NO_NAME,
|
||||
l10nId: "accessibility-text-label-issue-glyph",
|
||||
args: {
|
||||
get code() {
|
||||
return ReactDOM.code({}, "alt");
|
||||
},
|
||||
// Note: there is no way right now to use custom elements in privileged
|
||||
// content. We have to use something like <span> since we can't provide
|
||||
// two args with the same name.
|
||||
get span() {
|
||||
return ReactDOM.code({}, "mglyph");
|
||||
},
|
||||
},
|
||||
},
|
||||
[TOOLBAR_NO_NAME]: {
|
||||
href: A11Y_TEXT_LABEL_LINKS.TOOLBAR_NO_NAME,
|
||||
l10nId: "accessibility-text-label-issue-toolbar",
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* A map of accessibility scores to the text descriptions of check icons.
|
||||
*/
|
||||
const SCORE_TO_ICON_MAP = {
|
||||
[BEST_PRACTICES]: {
|
||||
l10nId: "accessibility-best-practices",
|
||||
src: "chrome://devtools/skin/images/info.svg",
|
||||
|
||||
},
|
||||
[FAIL]: {
|
||||
l10nId: "accessibility-fail",
|
||||
src: "chrome://devtools/skin/images/error.svg",
|
||||
},
|
||||
[WARNING]: {
|
||||
l10nId: "accessibility-warning",
|
||||
src: "chrome://devtools/skin/images/alert.svg",
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Localized "Learn more" link that opens a new tab with relevant documentation.
|
||||
*/
|
||||
class LearnMoreClass extends Component {
|
||||
static get propTypes() {
|
||||
return {
|
||||
href: PropTypes.string,
|
||||
l10nId: PropTypes.string.isRequired,
|
||||
onClick: PropTypes.func,
|
||||
};
|
||||
}
|
||||
|
||||
static get defaultProps() {
|
||||
return {
|
||||
href: "#",
|
||||
l10nId: null,
|
||||
onClick: LearnMoreClass.openDocOnClick,
|
||||
};
|
||||
}
|
||||
|
||||
static openDocOnClick(event) {
|
||||
event.preventDefault();
|
||||
openDocLink(event.target.href);
|
||||
}
|
||||
|
||||
render() {
|
||||
const { href, l10nId, onClick } = this.props;
|
||||
const className = "link";
|
||||
|
||||
return Localized({ id: l10nId }, ReactDOM.a({ className, href, onClick }));
|
||||
}
|
||||
}
|
||||
|
||||
const LearnMore = createFactory(LearnMoreClass);
|
||||
|
||||
/**
|
||||
* Renders icon with text description for the text label accessibility check.
|
||||
*
|
||||
* @param {Object}
|
||||
* Options:
|
||||
* - score: value from SCORES from "devtools/shared/constants"
|
||||
*/
|
||||
function Icon({ score }) {
|
||||
const { l10nId, src } = SCORE_TO_ICON_MAP[score];
|
||||
|
||||
return Localized({ id: l10nId, attrs: { alt: true } },
|
||||
ReactDOM.img({ src, className: `icon ${score}` })
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders text description of the text label accessibility check.
|
||||
*
|
||||
* @param {Object}
|
||||
* Options:
|
||||
* - issue: value from ISSUE_TYPE[AUDIT_TYPE.TEXT_LABEL] from
|
||||
* "devtools/shared/constants"
|
||||
*/
|
||||
function Annotation({ issue }) {
|
||||
const { args, href, l10nId } = ISSUE_TO_ANNOTATION_MAP[issue];
|
||||
|
||||
return Localized({
|
||||
id: l10nId,
|
||||
a: LearnMore({ l10nId: "accessibility-learn-more", href }),
|
||||
...args,
|
||||
},
|
||||
ReactDOM.p({ className: "accessibility-check-annotation" })
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Component for rendering a check for text label accessibliity check failures,
|
||||
* warnings and best practices suggestions association with a given
|
||||
* accessibility object in the accessibility tree.
|
||||
*/
|
||||
class TextLabelCheck extends Component {
|
||||
static get propTypes() {
|
||||
return {
|
||||
issue: PropTypes.string.isRequired,
|
||||
score: PropTypes.string.isRequired,
|
||||
};
|
||||
}
|
||||
|
||||
render() {
|
||||
const { issue, score } = this.props;
|
||||
|
||||
return ReactDOM.div({
|
||||
role: "presentation",
|
||||
className: "accessibility-check",
|
||||
},
|
||||
Localized({
|
||||
id: "accessibility-text-label-header",
|
||||
},
|
||||
ReactDOM.h3({ className: "accessibility-check-header" })),
|
||||
ReactDOM.div({
|
||||
role: "presentation",
|
||||
className: "accessibility-text-label-check",
|
||||
},
|
||||
Icon({ score }),
|
||||
Annotation({ issue })
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = TextLabelCheck;
|
|
@ -22,5 +22,6 @@ DevToolsModules(
|
|||
'MainFrame.js',
|
||||
'RightSidebar.js',
|
||||
'TextLabelBadge.js',
|
||||
'TextLabelCheck.js',
|
||||
'Toolbar.js'
|
||||
)
|
||||
|
|
|
@ -3,7 +3,34 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
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,
|
||||
},
|
||||
},
|
||||
},
|
||||
} = require("devtools/shared/constants");
|
||||
|
||||
// Used in accessible component for properties tree rendering.
|
||||
exports.TREE_ROW_HEIGHT = 21;
|
||||
|
@ -84,3 +111,41 @@ exports.A11Y_LEARN_MORE_LINK =
|
|||
exports.A11Y_CONTRAST_LEARN_MORE_LINK =
|
||||
"https://developer.mozilla.org/docs/Web/Accessibility/Understanding_WCAG/Perceivable/" +
|
||||
"Color_contrast?utm_source=devtools&utm_medium=a11y-panel-checks-color-contrast";
|
||||
|
||||
const A11Y_TEXT_LABEL_LINK_BASE =
|
||||
"https://developer.mozilla.org/docs/Web/Accessibility/Understanding_WCAG/Text_labels_and_names" +
|
||||
"?utm_source=devtools&utm_medium=a11y-panel-checks-text-label";
|
||||
|
||||
const A11Y_TEXT_LABEL_LINK_IDS = {
|
||||
[AREA_NO_NAME_FROM_ALT]:
|
||||
"Use_alt_attribute_to_provide_a_name_for_areas_that_have_the_href_attribute",
|
||||
[DIALOG_NO_NAME]: "Dialogs_should_have_a_name",
|
||||
[DOCUMENT_NO_TITLE]: "Documents_must_have_a_title",
|
||||
[EMBED_NO_NAME]: "Embedded_content_must_have_a_name",
|
||||
[FIGURE_NO_NAME]: "Figures_with_optional_captions_should_have_a_name",
|
||||
[FORM_FIELDSET_NO_NAME]: "Form_element_groups_must_have_a_name",
|
||||
[FORM_FIELDSET_NO_NAME_FROM_LEGEND]:
|
||||
"Use_legend_element_to_provide_a_name_for_form_element_groups",
|
||||
[FORM_NO_NAME]: "Form_elements_must_have_a_name",
|
||||
[FORM_NO_VISIBLE_NAME]: "Form_elements_should_have_a_visible_text_label",
|
||||
[FORM_OPTGROUP_NO_NAME]: "Groupings_of_options_must_have_a_name",
|
||||
[FORM_OPTGROUP_NO_NAME_FROM_LABEL]:
|
||||
"Use_label_attribute_to_provide_a_name_for_groupings_of_options",
|
||||
[FRAME_NO_NAME]: "Frames_must_have_a_name",
|
||||
[HEADING_NO_NAME]: "Headings_must_have_a_name",
|
||||
[HEADING_NO_CONTENT]: "Headings_must_have_visible_text_content",
|
||||
[IFRAME_NO_NAME_FROM_TITLE]: "Use_title_attribute_to_describe_iframe_content",
|
||||
[IMAGE_NO_NAME]: "Content_with_images_must_have_a_name",
|
||||
[INTERACTIVE_NO_NAME]: "Interactive_elements_must_have_a_name",
|
||||
[MATHML_GLYPH_NO_NAME]:
|
||||
"Use_alt_attribute_to_provide_a_name_for_MathML_glyphs",
|
||||
[TOOLBAR_NO_NAME]:
|
||||
"Toolbars_must_have_a_name_when_there_is_more_than_one_toolbar",
|
||||
};
|
||||
|
||||
const A11Y_TEXT_LABEL_LINKS = {};
|
||||
for (const key in A11Y_TEXT_LABEL_LINK_IDS) {
|
||||
A11Y_TEXT_LABEL_LINKS[key] =
|
||||
`${A11Y_TEXT_LABEL_LINK_BASE}#${A11Y_TEXT_LABEL_LINK_IDS[key]}`;
|
||||
}
|
||||
exports.A11Y_TEXT_LABEL_LINKS = A11Y_TEXT_LABEL_LINKS;
|
||||
|
|
|
@ -3,6 +3,9 @@
|
|||
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
"use strict";
|
||||
|
||||
const Services = require("Services");
|
||||
const { L10nRegistry } = require("resource://gre/modules/L10nRegistry.jsm");
|
||||
|
||||
const EventEmitter = require("devtools/shared/event-emitter");
|
||||
|
||||
const Telemetry = require("devtools/client/shared/telemetry");
|
||||
|
@ -81,6 +84,8 @@ AccessibilityPanel.prototype = {
|
|||
this.picker = new Picker(this);
|
||||
}
|
||||
|
||||
this.fluentBundles = await this.createFluentBundles();
|
||||
|
||||
this.updateA11YServiceDurationTimer();
|
||||
this.front.on("init", this.updateA11YServiceDurationTimer);
|
||||
this.front.on("shutdown", this.updateA11YServiceDurationTimer);
|
||||
|
@ -94,6 +99,25 @@ AccessibilityPanel.prototype = {
|
|||
return this._opening;
|
||||
},
|
||||
|
||||
/**
|
||||
* Retrieve message contexts for the current locales, and return them as an
|
||||
* array of FluentBundles elements.
|
||||
*/
|
||||
async createFluentBundles() {
|
||||
const locales = Services.locale.appLocalesAsBCP47;
|
||||
const generator =
|
||||
L10nRegistry.generateBundles(locales, ["devtools/accessibility.ftl"]);
|
||||
|
||||
// Return value of generateBundles is a generator and should be converted to
|
||||
// a sync iterable before using it with React.
|
||||
const contexts = [];
|
||||
for await (const message of generator) {
|
||||
contexts.push(message);
|
||||
}
|
||||
|
||||
return contexts;
|
||||
},
|
||||
|
||||
onNewAccessibleFrontSelected(selected) {
|
||||
this.emit("new-accessible-front-selected", selected);
|
||||
},
|
||||
|
@ -133,7 +157,8 @@ AccessibilityPanel.prototype = {
|
|||
}
|
||||
// Alright reset the flag we are about to refresh the panel.
|
||||
this.shouldRefresh = false;
|
||||
this.postContentMessage("initialize", this.front, this.walker, this.supports);
|
||||
this.postContentMessage("initialize", this.front, this.walker, this.supports,
|
||||
this.fluentBundles);
|
||||
},
|
||||
|
||||
updateA11YServiceDurationTimer() {
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
/* 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";
|
||||
|
||||
module.exports = {
|
||||
"plugins": ["@babel/plugin-proposal-async-generator-functions"],
|
||||
};
|
|
@ -4,10 +4,10 @@ exports[`AuditController component: audit filter filtered contrast checks fail 1
|
|||
|
||||
exports[`AuditController component: audit filter filtered contrast checks fail range 1`] = `"<span></span>"`;
|
||||
|
||||
exports[`AuditController component: audit filter filtered contrast checks success 1`] = `null`;
|
||||
exports[`AuditController component: audit filter filtered contrast checks success 1`] = `""`;
|
||||
|
||||
exports[`AuditController component: audit filter filtered no checks 1`] = `null`;
|
||||
exports[`AuditController component: audit filter filtered no checks 1`] = `""`;
|
||||
|
||||
exports[`AuditController component: audit filter filtered unknown checks 1`] = `null`;
|
||||
exports[`AuditController component: audit filter filtered unknown checks 1`] = `""`;
|
||||
|
||||
exports[`AuditController component: audit filter not filtered 1`] = `"<span></span>"`;
|
||||
|
|
|
@ -10,4 +10,4 @@ exports[`AuditProgressOverlay component: render auditing progress 2`] = `"<span
|
|||
|
||||
exports[`AuditProgressOverlay component: render auditing progress 3`] = `"<span id=\\"audit-progress-container\\">accessibility.progress.progressbar<progress max=\\"100\\" value=\\"75\\" class=\\"audit-progress-progressbar\\" aria-labelledby=\\"audit-progress-container\\"></progress></span>"`;
|
||||
|
||||
exports[`AuditProgressOverlay component: render not auditing 1`] = `null`;
|
||||
exports[`AuditProgressOverlay component: render not auditing 1`] = `""`;
|
||||
|
|
|
@ -6,10 +6,10 @@ exports[`Badges component: contrast ratio fail render 1`] = `"<span class=\\"bad
|
|||
|
||||
exports[`Badges component: contrast ratio success render 1`] = `"<span class=\\"badges\\" role=\\"group\\" aria-label=\\"accessibility.badges\\"></span>"`;
|
||||
|
||||
exports[`Badges component: empty checks render 1`] = `null`;
|
||||
exports[`Badges component: empty checks render 1`] = `""`;
|
||||
|
||||
exports[`Badges component: no props render 1`] = `null`;
|
||||
exports[`Badges component: no props render 1`] = `""`;
|
||||
|
||||
exports[`Badges component: null checks render 1`] = `null`;
|
||||
exports[`Badges component: null checks render 1`] = `""`;
|
||||
|
||||
exports[`Badges component: unsupported checks render 1`] = `null`;
|
||||
exports[`Badges component: unsupported checks render 1`] = `""`;
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`TextLabelCheck component: BEST_PRACTICES render 1`] = `"<div role=\\"presentation\\" class=\\"accessibility-check\\"><h3 class=\\"accessibility-check-header\\"></h3><div role=\\"presentation\\" class=\\"accessibility-text-label-check\\"><img src=\\"chrome://devtools/skin/images/info.svg\\" class=\\"icon BEST_PRACTICES\\"><p class=\\"accessibility-check-annotation\\"></p></div></div>"`;
|
||||
|
||||
exports[`TextLabelCheck component: WARNING render 1`] = `"<div role=\\"presentation\\" class=\\"accessibility-check\\"><h3 class=\\"accessibility-check-header\\"></h3><div role=\\"presentation\\" class=\\"accessibility-text-label-check\\"><img src=\\"chrome://devtools/skin/images/alert.svg\\" class=\\"icon WARNING\\"><p class=\\"accessibility-check-annotation\\"></p></div></div>"`;
|
||||
|
||||
exports[`TextLabelCheck component: fail render 1`] = `"<div role=\\"presentation\\" class=\\"accessibility-check\\"><h3 class=\\"accessibility-check-header\\"></h3><div role=\\"presentation\\" class=\\"accessibility-text-label-check\\"><img src=\\"chrome://devtools/skin/images/error.svg\\" class=\\"icon fail\\"><p class=\\"accessibility-check-annotation\\"></p></div></div>"`;
|
|
@ -0,0 +1,69 @@
|
|||
/* 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";
|
||||
|
||||
const { mount } = require("enzyme");
|
||||
const { createFactory } = require("devtools/client/shared/vendor/react");
|
||||
const TextLabelCheckClass = require("devtools/client/accessibility/components/TextLabelCheck");
|
||||
const TextLabelCheck = createFactory(TextLabelCheckClass);
|
||||
|
||||
const FluentReact = require("devtools/client/shared/vendor/fluent-react");
|
||||
const LocalizationProvider = createFactory(FluentReact.LocalizationProvider);
|
||||
|
||||
const {
|
||||
accessibility: {
|
||||
AUDIT_TYPE: { TEXT_LABEL },
|
||||
ISSUE_TYPE: {
|
||||
[TEXT_LABEL]: {
|
||||
AREA_NO_NAME_FROM_ALT,
|
||||
DIALOG_NO_NAME,
|
||||
FORM_NO_VISIBLE_NAME,
|
||||
},
|
||||
},
|
||||
SCORES: { BEST_PRACTICES, FAIL, WARNING },
|
||||
},
|
||||
} = require("devtools/shared/constants");
|
||||
|
||||
function testTextLabelCheck(wrapper, props) {
|
||||
expect(wrapper.html()).toMatchSnapshot();
|
||||
expect(wrapper.children().length).toBe(1);
|
||||
const container = wrapper.childAt(0);
|
||||
expect(container.hasClass("accessibility-check")).toBe(true);
|
||||
expect(container.prop("role")).toBe("presentation");
|
||||
expect(wrapper.props()).toMatchObject(props);
|
||||
|
||||
const localized = wrapper.find(FluentReact.Localized);
|
||||
expect(localized.length).toBe(3);
|
||||
|
||||
const heading = localized.at(0).childAt(0);
|
||||
expect(heading.type()).toBe("h3");
|
||||
expect(heading.hasClass("accessibility-check-header")).toBe(true);
|
||||
|
||||
const icon = localized.at(1).childAt(0);
|
||||
expect(icon.type()).toBe("img");
|
||||
expect(icon.hasClass(props.score === FAIL ? "fail" : props.score)).toBe(true);
|
||||
|
||||
const annotation = localized.at(2).childAt(0);
|
||||
expect(annotation.type()).toBe("p");
|
||||
expect(annotation.hasClass("accessibility-check-annotation")).toBe(true);
|
||||
}
|
||||
|
||||
describe("TextLabelCheck component:", () => {
|
||||
const testProps = [
|
||||
{ issue: AREA_NO_NAME_FROM_ALT, score: FAIL },
|
||||
{ issue: FORM_NO_VISIBLE_NAME, score: WARNING },
|
||||
{ issue: DIALOG_NO_NAME, score: BEST_PRACTICES },
|
||||
];
|
||||
|
||||
for (const props of testProps) {
|
||||
it(`${props.score} render`, () => {
|
||||
const wrapper = mount(LocalizationProvider({ messages: []},
|
||||
TextLabelCheck(props)));
|
||||
|
||||
const textLabelCheck = wrapper.find(TextLabelCheckClass);
|
||||
testTextLabelCheck(textLabelCheck, props);
|
||||
});
|
||||
}
|
||||
});
|
|
@ -10,12 +10,13 @@
|
|||
"test-ci": "jest --json"
|
||||
},
|
||||
"dependencies": {
|
||||
"jest": "^23.0.0",
|
||||
"@babel/plugin-proposal-async-generator-functions": "^7.2.0",
|
||||
"jest": "^24.6.0",
|
||||
"react-test-renderer": "16.4.1",
|
||||
"react": "16.4.1",
|
||||
"react-dom": "16.4.1",
|
||||
"enzyme": "^3.3.0",
|
||||
"enzyme-to-json": "3.3.4",
|
||||
"enzyme-adapter-react-16": "^1.1.1"
|
||||
"enzyme": "^3.9.0",
|
||||
"enzyme-to-json": "^3.3.5",
|
||||
"enzyme-adapter-react-16": "^1.13.2"
|
||||
}
|
||||
}
|
||||
|
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -8,13 +8,13 @@ window._snapshots = {
|
|||
"type": "div",
|
||||
"props": {
|
||||
"role": "presentation",
|
||||
"className": "accessibility-color-contrast-check",
|
||||
"className": "accessibility-check",
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "h3",
|
||||
"props": {
|
||||
"className": "accessibility-color-contrast-header",
|
||||
"className": "accessibility-check-header",
|
||||
},
|
||||
"children": [
|
||||
"Color and Contrast",
|
||||
|
@ -45,13 +45,13 @@ window._snapshots = {
|
|||
"type": "div",
|
||||
"props": {
|
||||
"role": "presentation",
|
||||
"className": "accessibility-color-contrast-check",
|
||||
"className": "accessibility-check",
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "h3",
|
||||
"props": {
|
||||
"className": "accessibility-color-contrast-header",
|
||||
"className": "accessibility-check-header",
|
||||
},
|
||||
"children": [
|
||||
"Color and Contrast",
|
||||
|
@ -83,7 +83,7 @@ window._snapshots = {
|
|||
{
|
||||
"type": "p",
|
||||
"props": {
|
||||
"className": "accessibility-color-contrast-annotation",
|
||||
"className": "accessibility-check-annotation",
|
||||
},
|
||||
"children": [
|
||||
"Does not meet WCAG standards for accessible text. ",
|
||||
|
@ -110,13 +110,13 @@ window._snapshots = {
|
|||
"type": "div",
|
||||
"props": {
|
||||
"role": "presentation",
|
||||
"className": "accessibility-color-contrast-check",
|
||||
"className": "accessibility-check",
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "h3",
|
||||
"props": {
|
||||
"className": "accessibility-color-contrast-header",
|
||||
"className": "accessibility-check-header",
|
||||
},
|
||||
"children": [
|
||||
"Color and Contrast",
|
||||
|
@ -170,7 +170,7 @@ window._snapshots = {
|
|||
{
|
||||
"type": "p",
|
||||
"props": {
|
||||
"className": "accessibility-color-contrast-annotation",
|
||||
"className": "accessibility-check-annotation",
|
||||
},
|
||||
"children": [
|
||||
"Does not meet WCAG standards for accessible text. ",
|
||||
|
@ -197,13 +197,13 @@ window._snapshots = {
|
|||
"type": "div",
|
||||
"props": {
|
||||
"role": "presentation",
|
||||
"className": "accessibility-color-contrast-check",
|
||||
"className": "accessibility-check",
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"type": "h3",
|
||||
"props": {
|
||||
"className": "accessibility-color-contrast-header",
|
||||
"className": "accessibility-check-header",
|
||||
},
|
||||
"children": [
|
||||
"Color and Contrast",
|
||||
|
@ -246,7 +246,7 @@ window._snapshots = {
|
|||
{
|
||||
"type": "p",
|
||||
"props": {
|
||||
"className": "accessibility-color-contrast-annotation",
|
||||
"className": "accessibility-check-annotation",
|
||||
},
|
||||
"children": [
|
||||
"Meets WCAG AA standards for accessible text. ",
|
||||
|
|
|
@ -0,0 +1,62 @@
|
|||
# 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/.
|
||||
|
||||
### These strings are used inside the Accessibility panel.
|
||||
|
||||
accessibility-learn-more = Learn more
|
||||
|
||||
accessibility-text-label-header = Text Labels and Names
|
||||
|
||||
## Text entries that are used as text alternative for icons that depict accessibility isses.
|
||||
|
||||
accessibility-warning =
|
||||
.alt = Warning
|
||||
|
||||
accessibility-fail =
|
||||
.alt = Error
|
||||
|
||||
accessibility-best-practices =
|
||||
.alt = Best Practices
|
||||
|
||||
## Text entries for a paragraph used in the accessibility panel sidebar's checks section
|
||||
## that describe that currently selected accessible object has an accessibility issue
|
||||
## with its text label or accessible name.
|
||||
|
||||
accessibility-text-label-issue-area = Use <code>alt</code> attribute to label <div>area</div> elements that have the <span>href</span> attribute. <a>Learn more</a>
|
||||
|
||||
accessibility-text-label-issue-dialog = Dialogs should be labeled. <a>Learn more</a>
|
||||
|
||||
accessibility-text-label-issue-document-title = Documents must have a <code>title</code>. <a>Learn more</a>
|
||||
|
||||
accessibility-text-label-issue-embed = Embedded content must be labeled. <a>Learn more</a>
|
||||
|
||||
accessibility-text-label-issue-figure = Figures with optional captions should be labeled. <a>Learn more</a>
|
||||
|
||||
accessibility-text-label-issue-fieldset = <code>fieldset</code> elements must be labeled. <a>Learn more</a>
|
||||
|
||||
accessibility-text-label-issue-fieldset-legend = Use <code>legend</code> element to label <span>fieldset</span> elements. <a>Learn more</a>
|
||||
|
||||
accessibility-text-label-issue-form = Form elements must be labeled. <a>Learn more</a>
|
||||
|
||||
accessibility-text-label-issue-form-visible = Form elements should have a visible text label. <a>Learn more</a>
|
||||
|
||||
accessibility-text-label-issue-frame = <code>frame</code> elements must be labeled. <a>Learn more</a>
|
||||
|
||||
accessibility-text-label-issue-glyph = Use <code>alt</code> attribute to label <span>mglyph</span> elements. <a>Learn more</a>
|
||||
|
||||
accessibility-text-label-issue-heading = Headings must be labeled. <a>Learn more</a>
|
||||
|
||||
accessibility-text-label-issue-heading-content = Headings should have visible text content. <a>Learn more</a>
|
||||
|
||||
accessibility-text-label-issue-iframe = Use <code>title</code> attribute to describe <span>iframe</span> content. <a>Learn more</a>
|
||||
|
||||
accessibility-text-label-issue-image = Content with images must be labeled. <a>Learn more</a>
|
||||
|
||||
accessibility-text-label-issue-interactive = Interactive elements must be labeled. <a>Learn more</a>
|
||||
|
||||
accessibility-text-label-issue-optgroup = <code>optgroup</code> elements must be labeled. <a>Learn more</a>
|
||||
|
||||
accessibility-text-label-issue-optgroup-label = Use <code>label</code> attribute to label <span>optgroup</span> elements. <a>Learn more</a>
|
||||
|
||||
accessibility-text-label-issue-toolbar = Toolbars must be labeled when there is more than one toolbar. <a>Learn more</a>
|
Загрузка…
Ссылка в новой задаче