author | Yura Zenevich <yura.zenevich@gmail.com> |
Wed, 28 Aug 2019 13:09:23 +0000 | |
changeset 554202 | bcca86af8ab52be4d955431afe0822d2fbe2c0ef |
parent 554201 | e9fa44e62b03c82e363f1d710b3efeb56004fe9d |
child 554203 | 083ba02b90d27293a4b127de41f680089f63d15a |
push id | 2165 |
push user | ffxbld-merge |
push date | Mon, 14 Oct 2019 16:30:58 +0000 |
treeherder | mozilla-release@0eae18af659f [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | rcaliman |
bugs | 1564968 |
milestone | 70.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/devtools/client/accessibility/accessibility.css +++ b/devtools/client/accessibility/accessibility.css @@ -718,17 +718,17 @@ body { .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 { +.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,
--- a/devtools/client/accessibility/test/browser/browser_accessibility_sidebar_checks.js +++ b/devtools/client/accessibility/test/browser/browser_accessibility_sidebar_checks.js @@ -1,13 +1,17 @@ /* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ "use strict"; +const { + accessibility: { SCORES }, +} = require("devtools/shared/constants"); + const TEST_URI = `<html> <head> <meta charset="utf-8"/> <title>Accessibility Panel Test</title> </head> <body> <p style="color: red;">Red</p> <p style="color: blue;">Blue</p> @@ -40,17 +44,17 @@ const tests = [ }, expected: { audit: { CONTRAST: { value: 4.0, color: [255, 0, 0, 1], backgroundColor: [255, 255, 255, 1], isLargeText: false, - score: "fail", + score: SCORES.FAIL, }, }, }, }, { desc: "Check accessible representing text node in blue.", setup: async ({ doc }) => { await toggleRow(doc, 3); @@ -58,17 +62,17 @@ const tests = [ }, expected: { audit: { CONTRAST: { value: 8.59, color: [0, 0, 255, 1], backgroundColor: [255, 255, 255, 1], isLargeText: false, - score: "AAA", + score: SCORES.AAA, }, }, }, }, ]; /** * Test that checks the Accessibility panel sidebar.
--- a/devtools/client/accessibility/test/jest/components/__snapshots__/text-label-check.test.js.snap +++ b/devtools/client/accessibility/test/jest/components/__snapshots__/text-label-check.test.js.snap @@ -1,7 +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>"`; -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>"`; +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>"`;
--- a/devtools/client/accessibility/test/jest/components/audit-filter.test.js +++ b/devtools/client/accessibility/test/jest/components/audit-filter.test.js @@ -14,16 +14,20 @@ const Provider = createFactory( const ConnectedAuditFilterClass = require("devtools/client/accessibility/components/AuditFilter"); const AuditFilterClass = ConnectedAuditFilterClass.WrappedComponent; const AuditFilter = createFactory(ConnectedAuditFilterClass); const { setupStore, } = require("devtools/client/accessibility/test/jest/helpers"); const { FILTERS } = require("devtools/client/accessibility/constants"); +const { + accessibility: { SCORES }, +} = require("devtools/shared/constants"); + describe("AuditController component:", () => { it("audit filter not filtered", () => { const store = setupStore(); const wrapper = mount(Provider({ store }, AuditFilter({}, span()))); expect(wrapper.html()).toMatchSnapshot(); const filter = wrapper.find(AuditFilterClass); @@ -62,17 +66,17 @@ describe("AuditController component:", ( AuditFilter( { checks: { CONTRAST: { value: 5.11, color: [255, 0, 0, 1], backgroundColor: [255, 255, 255, 1], isLargeText: false, - score: "AA", + score: SCORES.AA, }, }, }, span() ) ) ); expect(wrapper.html()).toMatchSnapshot(); @@ -84,17 +88,17 @@ describe("AuditController component:", ( preloadedState: { audit: { filters: { [FILTERS.CONTRAST]: true } } }, }); const CONTRAST = { value: 3.1, color: [255, 0, 0, 1], backgroundColor: [255, 255, 255, 1], isLargeText: false, - score: "fail", + score: SCORES.FAIL, }; const wrapper = mount( Provider( { store }, AuditFilter( { checks: { CONTRAST }, @@ -116,17 +120,17 @@ describe("AuditController component:", ( const CONTRAST = { min: 1.19, max: 1.39, color: [128, 128, 128, 1], backgroundColorMin: [219, 106, 116, 1], backgroundColorMax: [156, 145, 211, 1], isLargeText: false, - score: "fail", + score: SCORES.FAIL, }; const wrapper = mount( Provider( { store }, AuditFilter( { checks: { CONTRAST },
--- a/devtools/client/accessibility/test/jest/components/badges.test.js +++ b/devtools/client/accessibility/test/jest/components/badges.test.js @@ -15,16 +15,20 @@ const { } = require("devtools/client/accessibility/test/jest/helpers"); const Badge = require("devtools/client/accessibility/components/Badge"); const Badges = createFactory( require("devtools/client/accessibility/components/Badges") ); const ContrastBadge = require("devtools/client/accessibility/components/ContrastBadge"); +const { + accessibility: { SCORES }, +} = require("devtools/shared/constants"); + describe("Badges component:", () => { const store = setupStore(); it("no props render", () => { const wrapper = mount(Provider({ store }, Badges())); expect(wrapper.html()).toMatchSnapshot(); expect(wrapper.isEmptyRender()).toBe(true); }); @@ -53,33 +57,33 @@ describe("Badges component:", () => { { store }, Badges({ checks: { CONTRAST: { value: 5.11, color: [255, 0, 0, 1], backgroundColor: [255, 255, 255, 1], isLargeText: false, - score: "AA", + score: SCORES.AA, }, }, }) ) ); expect(wrapper.html()).toMatchSnapshot(); expect(wrapper.isEmptyRender()).toBe(true); }); it("contrast ratio fail render", () => { const CONTRAST = { value: 3.1, color: [255, 0, 0, 1], backgroundColor: [255, 255, 255, 1], isLargeText: false, - score: "fail", + score: SCORES.FAIL, }; const wrapper = mount( Provider({ store }, Badges({ checks: { CONTRAST } })) ); expect(wrapper.html()).toMatchSnapshot(); expect(wrapper.find(Badge).length).toBe(1); expect(wrapper.find(ContrastBadge).length).toBe(1); @@ -94,17 +98,17 @@ describe("Badges component:", () => { it("contrast ratio fail range render", () => { const CONTRAST = { min: 1.19, max: 1.39, color: [128, 128, 128, 1], backgroundColorMin: [219, 106, 116, 1], backgroundColorMax: [156, 145, 211, 1], isLargeText: false, - score: "fail", + score: SCORES.FAIL, }; const wrapper = mount( Provider({ store }, Badges({ checks: { CONTRAST } })) ); expect(wrapper.html()).toMatchSnapshot(); expect(wrapper.find(Badge).length).toBe(1); expect(wrapper.find(ContrastBadge).length).toBe(1);
--- a/devtools/client/accessibility/test/jest/components/contrast-badge.test.js +++ b/devtools/client/accessibility/test/jest/components/contrast-badge.test.js @@ -14,70 +14,74 @@ const Provider = createFactory( const { setupStore, } = require("devtools/client/accessibility/test/jest/helpers"); const Badge = require("devtools/client/accessibility/components/Badge"); const ContrastBadgeClass = require("devtools/client/accessibility/components/ContrastBadge"); const ContrastBadge = createFactory(ContrastBadgeClass); +const { + accessibility: { SCORES }, +} = require("devtools/shared/constants"); + describe("ContrastBadge component:", () => { const store = setupStore(); it("error render", () => { const wrapper = shallow(ContrastBadge({ error: true })); expect(wrapper.html()).toMatchSnapshot(); expect(wrapper.isEmptyRender()).toBe(true); }); it("success render", () => { const wrapper = shallow( ContrastBadge({ value: 5.11, isLargeText: false, - score: "AA", + score: SCORES.AA, }) ); expect(wrapper.html()).toMatchSnapshot(); expect(wrapper.isEmptyRender()).toBe(true); }); it("success range render", () => { const wrapper = shallow( ContrastBadge({ min: 5.11, max: 6.25, isLargeText: false, - score: "AA", + score: SCORES.AA, }) ); expect(wrapper.html()).toMatchSnapshot(); expect(wrapper.isEmptyRender()).toBe(true); }); it("success large text render", () => { const wrapper = shallow( ContrastBadge({ value: 3.77, isLargeText: true, - score: "AA", + score: SCORES.AA, }) ); expect(wrapper.html()).toMatchSnapshot(); expect(wrapper.isEmptyRender()).toBe(true); }); it("fail render", () => { const wrapper = mount( Provider( { store }, ContrastBadge({ value: 3.77, isLargeText: false, - score: "fail", + score: SCORES.FAIL, }) ) ); expect(wrapper.html()).toMatchSnapshot(); expect(wrapper.children().length).toBe(1); const contrastBadge = wrapper.find(ContrastBadgeClass); const badge = contrastBadge.childAt(0);
--- a/devtools/client/accessibility/test/jest/components/text-label-check.test.js +++ b/devtools/client/accessibility/test/jest/components/text-label-check.test.js @@ -38,17 +38,17 @@ function testTextLabelCheck(wrapper, pro 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); + expect(icon.hasClass(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 = [
--- a/devtools/client/accessibility/test/mochitest/contrast.snapshots.js +++ b/devtools/client/accessibility/test/mochitest/contrast.snapshots.js @@ -56,17 +56,17 @@ window._snapshots = { props: { role: "presentation", className: "accessibility-color-contrast", }, children: [ { type: "span", props: { - className: "accessibility-contrast-value fail", + className: "accessibility-contrast-value FAIL", role: "presentation", style: { "--accessibility-contrast-color": "rgba(255,0,0,1)", "--accessibility-contrast-bg": "rgba(255,255,255,1)", }, }, children: ["4.00"], }, @@ -117,17 +117,17 @@ window._snapshots = { props: { role: "presentation", className: "accessibility-color-contrast", }, children: [ { type: "span", props: { - className: "accessibility-contrast-value fail", + className: "accessibility-contrast-value FAIL", role: "presentation", style: { "--accessibility-contrast-color": "rgba(128,128,128,1)", "--accessibility-contrast-bg": "rgba(219,106,116,1)", }, }, children: ["1.19"], }, @@ -137,17 +137,17 @@ window._snapshots = { role: "presentation", className: "accessibility-color-contrast-separator", }, children: null, }, { type: "span", props: { - className: "accessibility-contrast-value fail", + className: "accessibility-contrast-value FAIL", role: "presentation", style: { "--accessibility-contrast-color": "rgba(128,128,128,1)", "--accessibility-contrast-bg": "rgba(156,145,211,1)", }, }, children: ["1.39"], },
--- a/devtools/client/accessibility/test/mochitest/test_accessible_contrast.html +++ b/devtools/client/accessibility/test/mochitest/test_accessible_contrast.html @@ -26,51 +26,55 @@ Test that Color Contrast component rende /* global matchSnapshot */ window.onload = async function() { try { const React = browserRequire("devtools/client/shared/vendor/react"); const { ColorContrastCheck } = browserRequire( "devtools/client/accessibility/components/ColorContrastAccessibility"); + const { + accessibility: { SCORES }, + } = browserRequire("devtools/shared/constants"); + matchSnapshot("ColorContrastAccessibility error render.", React.createElement(ColorContrastCheck, { error: true }) ); matchSnapshot("ColorContrastAccessibility basic render.", React.createElement(ColorContrastCheck, { "value": 4.00, "color": [255, 0, 0, 1], "backgroundColor": [255, 255, 255, 1], "isLargeText": false, - "score": "fail", + "score": SCORES.FAIL, }) ); matchSnapshot("ColorContrastAccessibility range render.", React.createElement(ColorContrastCheck, { "min": 1.19, "max": 1.39, "color": [128, 128, 128, 1], "backgroundColorMin": [219, 106, 116, 1], "backgroundColorMax": [156, 145, 211, 1], "isLargeText": false, - "score": "fail", - "scoreMin": "fail", - "scoreMax": "fail", + "score": SCORES.FAIL, + "scoreMin": SCORES.FAIL, + "scoreMax": SCORES.FAIL, }) ); matchSnapshot("ColorContrastAccessibility large text render.", React.createElement(ColorContrastCheck, { "value": 4.00, "color": [255, 0, 0, 1], "backgroundColor": [255, 255, 255, 1], "isLargeText": true, - "score": "AA", + "score": SCORES.AA, }) ); } catch (e) { ok(false, "Got an error: " + DevToolsUtils.safeErrorString(e)); } finally { SimpleTest.finish(); } };
--- a/devtools/client/locales/en-US/accessibility.properties +++ b/devtools/client/locales/en-US/accessibility.properties @@ -141,21 +141,21 @@ accessibility.contrast.large.title=Text accessibility.contrast.annotation.AA=Meets WCAG AA standards for accessible text. %S # LOCALIZATION NOTE (accessibility.contrast.annotation.AAA): A title text for the # paragraph describing that the given colour contrast satisfies AAA standard from Web # Content Accessibility Guidelines. %S in the content will be replaced by a link at run # time with the accessibility.learnMore string. accessibility.contrast.annotation.AAA=Meets WCAG AAA standards for accessible text. %S -# LOCALIZATION NOTE (accessibility.contrast.annotation.fail): A title text for the +# LOCALIZATION NOTE (accessibility.contrast.annotation.FAIL): A title text for the # paragraph describing that the given colour contrast fails to meet the minimum level from # Web Content Accessibility Guidelines. %S in the content will be replaced by a link at # run time with the accessibility.learnMore string. -accessibility.contrast.annotation.fail=Does not meet WCAG standards for accessible text. %S +accessibility.contrast.annotation.FAIL=Does not meet WCAG standards for accessible text. %S # LOCALIZATION NOTE (accessibility.contrast.annotation.transparent.error): A title text for the # paragraph suggesting a fix for error in color contrast calculation for text nodes with zero alpha. accessibility.contrast.annotation.transparent.error=Pick a color that is not transparent. # LOCALIZATION NOTE (accessibility.badges): A title text for the group of badges # that are rendered for each accessible row within the accessibility tree when # one or more accessibility checks fail.
--- a/devtools/client/themes/accessibility-color-contrast.css +++ b/devtools/client/themes/accessibility-color-contrast.css @@ -1,17 +1,17 @@ /* 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/. */ /* Classes used to style the color contrast section in the Accessibility * Checks panel and color picker tooltip across the Inspector panel. * * The section consists of: - * - contrast ratio value (numeric + score badge (AA/AAA/fail)): + * - contrast ratio value (numeric + score badge (AA/AAA/FAIL)): * Only shows up if contrast ratio can be calculated. * - large text indicator badge: * Only shows up if the selected text node contains large text. */ .accessibility-color-contrast { position: relative; display: flex; cursor: default; @@ -26,17 +26,17 @@ .accessibility-color-contrast .accessibility-contrast-value:not(:empty).AA:after, .accessibility-color-contrast .accessibility-contrast-value:not(:empty).AAA:after { color: var(--theme-highlight-green); } .accessibility-color-contrast - .accessibility-contrast-value:not(:empty).fail:after { + .accessibility-contrast-value:not(:empty).FAIL:after { color: #e57180; content: "⚠️"; } .accessibility-color-contrast .accessibility-contrast-value:not(:empty).AA:after { content: "AA\2713"; unicode-bidi: isolate;
--- a/devtools/server/actors/highlighters.css +++ b/devtools/server/actors/highlighters.css @@ -738,84 +738,93 @@ fill: #6a5acd; } :-moz-native-anonymous .accessible-infobar-name, :-moz-native-anonymous .accessible-infobar-audit { color: var(--highlighter-infobar-color); } -:-moz-native-anonymous .accessible-infobar-audit .accessible-contrast-ratio:not(:empty):before { +:-moz-native-anonymous .accessible-infobar-audit .accessible-contrast-ratio:not(:empty)::before { content: ""; height: 8px; width: 8px; display: inline-flex; background-color: var(--accessibility-highlighter-contrast-ratio-color); box-shadow: 0 0 0 1px var(--grey-40), 4px 3px var(--accessibility-highlighter-contrast-ratio-bg), 4px 3px 0 1px var(--grey-40); margin-inline-start: 3px; margin-inline-end: 9px; } -:-moz-native-anonymous .accessible-infobar-audit .accessible-contrast-ratio:not(:empty):after { +:-moz-native-anonymous .accessible-infobar-audit .accessible-contrast-ratio:not(:empty)::after { margin-inline-start: 2px; } -:-moz-native-anonymous .accessible-infobar-audit .accessible-contrast-ratio:not(:empty).AA:after, -:-moz-native-anonymous .accessible-infobar-audit .accessible-contrast-ratio:not(:empty).AAA:after { +:-moz-native-anonymous .accessible-infobar-audit .accessible-contrast-ratio:not(:empty).AA::after, +:-moz-native-anonymous .accessible-infobar-audit .accessible-contrast-ratio:not(:empty).AAA::after { color: #90E274; } -:-moz-native-anonymous .accessible-infobar-audit .accessible-contrast-ratio:not(:empty).fail:after { +:-moz-native-anonymous .accessible-infobar-audit .accessible-contrast-ratio:not(:empty).FAIL::after { color: #E57180; content: "⚠️"; } -:-moz-native-anonymous .accessible-infobar-audit .accessible-contrast-ratio:not(:empty).AA:after { +:-moz-native-anonymous .accessible-infobar-audit .accessible-contrast-ratio:not(:empty).AA::after { content: "AA\2713"; } -:-moz-native-anonymous .accessible-infobar-audit .accessible-contrast-ratio:not(:empty).AAA:after { +:-moz-native-anonymous .accessible-infobar-audit .accessible-contrast-ratio:not(:empty).AAA::after { content: "AAA\2713"; } :-moz-native-anonymous .accessible-infobar-audit .accessible-contrast-ratio-label, -:-moz-native-anonymous .accessible-infobar-audit .accessible-contrast-ratio-separator:before { +:-moz-native-anonymous .accessible-infobar-audit .accessible-contrast-ratio-separator::before { margin-inline-end: 3px; } -:-moz-native-anonymous .accessible-infobar-audit .accessible-contrast-ratio-separator:before { +:-moz-native-anonymous .accessible-infobar-audit .accessible-contrast-ratio-separator::before { content: "-"; margin-inline-start: 3px; } -:-moz-native-anonymous .accessible-infobar-audit .accessible-text-label:before { +:-moz-native-anonymous .accessible-infobar-audit .accessible-audit { + display: block; + padding-block-end: 5px; +} + +:-moz-native-anonymous .accessible-infobar-audit .accessible-audit:last-child { + padding-block-end: 0; +} + +:-moz-native-anonymous .accessible-infobar-audit .accessible-audit::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 { +:-moz-native-anonymous .accessible-infobar-audit .accessible-audit.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 { +:-moz-native-anonymous .accessible-infobar-audit .accessible-audit.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 { +:-moz-native-anonymous .accessible-infobar-audit .accessible-audit.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; padding-inline-start: 6px; }
--- a/devtools/server/actors/highlighters/utils/accessibility.js +++ b/devtools/server/actors/highlighters/utils/accessibility.js @@ -24,16 +24,23 @@ DevToolsUtils.defineLazyGetter( "L10N", () => new LocalizationHelper(STRINGS_URI) ); const { accessibility: { AUDIT_TYPE, ISSUE_TYPE: { + [AUDIT_TYPE.KEYBOARD]: { + FOCUSABLE_NO_SEMANTICS, + FOCUSABLE_POSITIVE_TABINDEX, + INTERACTIVE_NO_ACTION, + INTERACTIVE_NOT_FOCUSABLE, + NO_FOCUS_VISIBLE, + }, [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, @@ -415,17 +422,21 @@ class XULWindowInfobar extends Infobar { * display various audit information such as contrast ratio score. */ class Audit { constructor(infobar) { this.infobar = infobar; // A list of audit reports to be shown on the fly when highlighting an accessible // object. - this.reports = [new ContrastRatio(this), new TextLabel(this)]; + this.reports = { + [AUDIT_TYPE.CONTRAST]: new ContrastRatio(this), + [AUDIT_TYPE.KEYBOARD]: new Keyboard(this), + [AUDIT_TYPE.TEXT_LABEL]: new TextLabel(this), + }; } get prefix() { return this.infobar.prefix; } get win() { return this.infobar.win; @@ -437,25 +448,25 @@ class Audit { parent: root, attributes: { class: "infobar-audit", id: "infobar-audit", }, prefix: this.prefix, }); - this.reports.forEach(report => report.buildMarkup(audit)); + Object.values(this.reports).forEach(report => report.buildMarkup(audit)); } update(audit = {}) { const el = this.getElement("infobar-audit"); el.setAttribute("hidden", true); let updated = false; - this.reports.forEach(report => { + Object.values(this.reports).forEach(report => { if (report.update(audit)) { updated = true; } }); if (updated) { el.removeAttribute("hidden"); } @@ -466,17 +477,17 @@ class Audit { } setTextContent(el, text) { return this.infobar.setTextContent(el, text); } destroy() { this.infobar = null; - this.reports.forEach(report => report.destroy()); + Object.values(this.reports).forEach(report => report.destroy()); this.reports = null; } } /** * A common interface between audit report components used to render accessibility audit * information for the currently highlighted accessible object. */ @@ -655,16 +666,80 @@ class ContrastRatio extends AuditReport backgroundColor: backgroundColorMax, }); return true; } } /** + * Keyboard audit report that is used to display a problem with keyboard + * accessibility as part of the inforbar. + */ +class Keyboard extends AuditReport { + /** + * A map from keyboard issues to annotation component properties. + */ + static get ISSUE_TO_INFOBAR_LABEL_MAP() { + return { + [FOCUSABLE_NO_SEMANTICS]: "accessibility.keyboard.issue.semantics", + [FOCUSABLE_POSITIVE_TABINDEX]: "accessibility.keyboard.issue.tabindex", + [INTERACTIVE_NO_ACTION]: "accessibility.keyboard.issue.action", + [INTERACTIVE_NOT_FOCUSABLE]: "accessibility.keyboard.issue.focusable", + [NO_FOCUS_VISIBLE]: "accessibility.keyboard.issue.focus.visible", + }; + } + + buildMarkup(root) { + createNode(this.win, { + nodeType: "span", + parent: root, + attributes: { + class: "audit", + id: "keyboard", + }, + prefix: this.prefix, + }); + } + + /** + * Update keyboard audit infobar markup. + * @param {Object} + * Audit report for a given highlighted accessible. + * @return {Boolean} + * True if the keyboard markup was updated correctly and infobar audit + * block should be visible. + */ + update(audit) { + const el = this.getElement("keyboard"); + el.setAttribute("hidden", true); + Object.values(SCORES).forEach(className => el.classList.remove(className)); + + if (!audit) { + return false; + } + + const keyboardAudit = audit[AUDIT_TYPE.KEYBOARD]; + if (!keyboardAudit) { + return false; + } + + const { issue, score } = keyboardAudit; + this.setTextContent( + el, + L10N.getStr(Keyboard.ISSUE_TO_INFOBAR_LABEL_MAP[issue]) + ); + el.classList.add(score); + el.removeAttribute("hidden"); + + return true; + } +} + +/** * 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() { @@ -692,17 +767,17 @@ class TextLabel extends AuditReport { }; } buildMarkup(root) { createNode(this.win, { nodeType: "span", parent: root, attributes: { - class: "text-label", + class: "audit", id: "text-label", }, prefix: this.prefix, }); } /** * Update text label audit infobar markup.
--- a/devtools/server/actors/highlighters/xul-accessible.js +++ b/devtools/server/actors/highlighters/xul-accessible.js @@ -97,92 +97,101 @@ const ACCESSIBLE_BOUNDS_SHEET = padding-bottom: 2px; } .accessible-infobar-name, .accessible-infobar-audit { color: hsl(210, 30%, 85%); } - .accessible-infobar-audit .accessible-contrast-ratio:not(:empty):before { + .accessible-infobar-audit .accessible-contrast-ratio:not(:empty)::before { content: ""; height: 8px; width: 8px; display: inline-flex; background-color: var(--accessibility-highlighter-contrast-ratio-color); box-shadow: 0 0 0 1px var(--grey-40), 4px 3px var(--accessibility-highlighter-contrast-ratio-bg), 4px 3px 0 1px var(--grey-40); margin-inline-start: 3px; margin-inline-end: 9px; } - .accessible-infobar-audit .accessible-contrast-ratio:not(:empty):after { + .accessible-infobar-audit .accessible-contrast-ratio:not(:empty)::after { margin-inline-start: 2px; } - .accessible-infobar-audit .accessible-contrast-ratio:not(:empty).AA:after, - .accessible-infobar-audit .accessible-contrast-ratio:not(:empty).AAA:after { + .accessible-infobar-audit .accessible-contrast-ratio:not(:empty).AA::after, + .accessible-infobar-audit .accessible-contrast-ratio:not(:empty).AAA::after { color: #90E274; } - .accessible-infobar-audit .accessible-contrast-ratio:not(:empty).fail:after { + .accessible-infobar-audit .accessible-contrast-ratio:not(:empty).FAIL::after { color: #E57180; content: "⚠️"; } - .accessible-infobar-audit .accessible-contrast-ratio:not(:empty).AA:after { + .accessible-infobar-audit .accessible-contrast-ratio:not(:empty).AA::after { content: "AA\u2713"; } - .accessible-infobar-audit .accessible-contrast-ratio:not(:empty).AAA:after { + .accessible-infobar-audit .accessible-contrast-ratio:not(:empty).AAA::after { content: "AAA\u2713"; } .accessible-infobar-audit .accessible-contrast-ratio-label, - .accessible-infobar-audit .accessible-contrast-ratio-separator:before { + .accessible-infobar-audit .accessible-contrast-ratio-separator::before { margin-inline-end: 3px; } - .accessible-infobar-audit .accessible-contrast-ratio-separator:before { + .accessible-infobar-audit .accessible-contrast-ratio-separator::before { content: "-"; margin-inline-start: 3px; } .accessible-infobar-name:not(:empty) { border-inline-start: 1px solid #5a6169; margin-inline-start: 6px; padding-inline-start: 6px; } - .accessible-infobar-audit .accessible-text-label:before { + .accessible-infobar-audit .accessible-audit { + display: block; + padding-block-end: 5px; + } + + .accessible-infobar-audit .accessible-audit:last-child { + padding-block-end: 0; + } + + .accessible-infobar-audit .accessible-audit::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 { + .accessible-infobar-audit .accessible-audit.FAIL::before { background-image: url(chrome://devtools/skin/images/error-small.svg); fill: var(--red-40); } - .accessible-infobar-audit .accessible-text-label.WARNING:before { + .accessible-infobar-audit .accessible-audit.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 { + .accessible-infobar-audit .accessible-audit.BEST_PRACTICES::before { background-image: url(chrome://devtools/skin/images/info-small.svg); }`); /** * The XULWindowAccessibleHighlighter is a class that has the same API as the * AccessibleHighlighter, and by extension other highlighters that implement * auto-refresh highlighter, but instead of drawing in canvas frame anonymous * content (that is not available for chrome accessible highlighting) it adds a
--- a/devtools/server/tests/browser/browser.ini +++ b/devtools/server/tests/browser/browser.ini @@ -49,16 +49,17 @@ support-files = [browser_accessibility_highlighter_infobar.js] skip-if = (os == 'win' && processor == 'aarch64') # bug 1533184 [browser_accessibility_infobar_show.js] [browser_accessibility_keyboard_audit.js] skip-if = (os == 'win' && processor == 'aarch64') || # bug 1533184 fission # Fails intermittently under Fission. +[browser_accessibility_infobar_audit_keyboard.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] skip-if = (os == 'win' && processor == 'aarch64') # bug 1533184 [browser_accessibility_node_events.js] skip-if = (os == 'win' && processor == 'aarch64') # bug 1533184 [browser_accessibility_simple.js]
copy from devtools/server/tests/browser/browser_accessibility_infobar_audit_text_label.js copy to devtools/server/tests/browser/browser_accessibility_infobar_audit_keyboard.js --- a/devtools/server/tests/browser/browser_accessibility_infobar_audit_text_label.js +++ b/devtools/server/tests/browser/browser_accessibility_infobar_audit_keyboard.js @@ -1,15 +1,15 @@ /* 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 +// Checks for the AccessibleHighlighter's infobar component and its keyboard // audit. add_task(async function() { await BrowserTestUtils.withNewTab( { gBrowser, url: MAIN_DOMAIN + "doc_accessibility_infobar.html", }, @@ -28,55 +28,50 @@ add_task(async function() { 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, + [AUDIT_TYPE.KEYBOARD]: { + INTERACTIVE_NO_ACTION, + FOCUSABLE_NO_SEMANTICS, }, }, - SCORES: { BEST_PRACTICES, FAIL, WARNING }, + SCORES: { 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) { + function checkKeyboard(infobar, audit) { const { issue, score } = audit || {}; let expected = ""; if (issue) { - const { - ISSUE_TO_INFOBAR_LABEL_MAP, - } = infobar.audit.reports[1].constructor; + const { ISSUE_TO_INFOBAR_LABEL_MAP } = infobar.audit.reports[ + AUDIT_TYPE.KEYBOARD + ].constructor; expected = L10N.getStr(ISSUE_TO_INFOBAR_LABEL_MAP[issue]); } is( - infobar.getTextContent("text-label"), + infobar.getTextContent("keyboard"), expected, - "infobar text label audit text content is correct" + "infobar keyboard audit text content is correct" ); if (score) { - ok( - infobar - .getElement("text-label") - .classList.contains(score === FAIL ? "fail" : score) - ); + ok(infobar.getElement("keyboard").classList.contains(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 @@ -110,62 +105,53 @@ add_task(async function() { y: 0, w: 250, h: 100, }; const tests = [ { desc: - "Infobar is shown with no text label audit content when no audit.", + "Infobar is shown with no keyboard audit content when no audit.", }, { desc: - "Infobar is shown with no text label audit content when audit is null.", + "Infobar is shown with no keyboard 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 }, + "Infobar is shown with no keyboard audit content when empty " + + "keyboard audit.", + audit: { [AUDIT_TYPE.KEYBOARD]: null }, }, { - desc: - "Infobar is shown with text label audit content for an error.", + desc: "Infobar is shown with keyboard 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, + [AUDIT_TYPE.KEYBOARD]: { + score: FAIL, + issue: INTERACTIVE_NO_ACTION, }, }, }, { - desc: - "Infobar is shown with text label audit content for best practices.", + desc: "Infobar is shown with keyboard audit content for a warning.", audit: { - [AUDIT_TYPE.TEXT_LABEL]: { - score: BEST_PRACTICES, - issue: DIALOG_NO_NAME, + [AUDIT_TYPE.KEYBOARD]: { + score: WARNING, + issue: FOCUSABLE_NO_SEMANTICS, }, }, }, ]; for (const test of tests) { const { desc, audit } = test; info(desc); highlighter.show(node, { ...bounds, audit }); - checkTextLabel(infobar, audit && audit[AUDIT_TYPE.TEXT_LABEL]); + checkKeyboard(infobar, audit && audit[AUDIT_TYPE.KEYBOARD]); highlighter.hide(); } }); } ); });
--- a/devtools/server/tests/browser/browser_accessibility_infobar_audit_text_label.js +++ b/devtools/server/tests/browser/browser_accessibility_infobar_audit_text_label.js @@ -50,33 +50,29 @@ add_task(async function() { * 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; + const { ISSUE_TO_INFOBAR_LABEL_MAP } = infobar.audit.reports[ + AUDIT_TYPE.TEXT_LABEL + ].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) - ); + ok(infobar.getElement("text-label").classList.contains(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
--- a/devtools/server/tests/browser/browser_accessibility_node_audit.js +++ b/devtools/server/tests/browser/browser_accessibility_node_audit.js @@ -6,29 +6,29 @@ /** * Checks functionality around audit for the AccessibleActor. This includes * tests for the return value when calling the audit method, payload of the * corresponding event as well as the AccesibleFront state being up to date. */ const { - accessibility: { AUDIT_TYPE }, + accessibility: { AUDIT_TYPE, SCORES }, } = require("devtools/shared/constants"); const EMPTY_AUDIT = Object.keys(AUDIT_TYPE).reduce((audit, key) => { audit[key] = null; return audit; }, {}); const EXPECTED_CONTRAST_DATA = { value: 21, color: [0, 0, 0, 1], backgroundColor: [255, 255, 255, 1], isLargeText: true, - score: "AAA", + score: SCORES.AAA, }; const EMPTY_CONTRAST_AUDIT = { [AUDIT_TYPE.CONTRAST]: null, }; const CONTRAST_AUDIT = { [AUDIT_TYPE.CONTRAST]: EXPECTED_CONTRAST_DATA,
--- a/devtools/server/tests/browser/browser_accessibility_walker_audit.js +++ b/devtools/server/tests/browser/browser_accessibility_walker_audit.js @@ -47,17 +47,17 @@ add_task(async function() { role: "text leaf", childCount: 0, checks: { [AUDIT_TYPE.CONTRAST]: { value: 4.0, color: [255, 0, 0, 1], backgroundColor: [255, 255, 255, 1], isLargeText: false, - score: "fail", + score: SCORES.FAIL, }, [AUDIT_TYPE.KEYBOARD]: null, [AUDIT_TYPE.TEXT_LABEL]: null, }, }, { name: "", role: "paragraph", @@ -73,17 +73,17 @@ add_task(async function() { role: "text leaf", childCount: 0, checks: { [AUDIT_TYPE.CONTRAST]: { value: 4.0, color: [255, 0, 0, 1], backgroundColor: [255, 255, 255, 1], isLargeText: false, - score: "fail", + score: SCORES.FAIL, }, [AUDIT_TYPE.KEYBOARD]: null, [AUDIT_TYPE.TEXT_LABEL]: null, }, }, ]; const total = accessibles.length; const auditProgress = [
--- a/devtools/shared/constants.js +++ b/devtools/shared/constants.js @@ -75,17 +75,17 @@ const ISSUE_TYPE = { const SCORES = { // Satisfies WCAG AA guidelines. AA: "AA", // Satisfies WCAG AAA guidelines. AAA: "AAA", // Elevates accessibility experience. BEST_PRACTICES: "BEST_PRACTICES", // Does not satisfy the baseline WCAG guidelines. - FAIL: "fail", + FAIL: "FAIL", // Partially satisfies the WCAG AA guidelines. WARNING: "WARNING", }; exports.accessibility = { AUDIT_TYPE, ISSUE_TYPE, SCORES,
--- a/devtools/shared/locales/en-US/accessibility.properties +++ b/devtools/shared/locales/en-US/accessibility.properties @@ -104,8 +104,34 @@ accessibility.text.label.issue.interacti # describes that currently selected accessible object for an <optgroup> must have a # name provided via label attribute. accessibility.text.label.issue.optgroup.label2 = Use a “label” attribute to label an “optgroup”. # 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. + +# LOCALIZATION NOTE (accessibility.keyboard.issue.semantics): A title text that +# describes that currently selected accessible object is focusable and should +# indicate that it could be interacted with. +accessibility.keyboard.issue.semantics=Focusable elements should have interactive semantics. + +# LOCALIZATION NOTE (accessibility.keyboard.issue.tabindex): A title text that +# describes that currently selected accessible object has a corresponding +# DOMNode that defines a tabindex attribute greater that 0 which can result in +# unexpected behaviour when navigating with keyboard. +accessibility.keyboard.issue.tabindex=Avoid using “tabindex” attribute greater than zero. + +# LOCALIZATION NOTE (accessibility.keyboard.issue.action): A title text that +# describes that currently selected accessible object is interactive but can not +# be activated using keyboard or accessibility API. +accessibility.keyboard.issue.action=Interactive elements must be able to be activated using a keyboard. + +# LOCALIZATION NOTE (accessibility.keyboard.issue.focusable): A title text that +# describes that currently selected accessible object is interactive but is not +# focusable with a keyboard. +accessibility.keyboard.issue.focusable=Interactive elements must be focusable. + +# LOCALIZATION NOTE (accessibility.keyboard.issue.focus.visible): A title text +# that describes that currently selected accessible object is focusable but +# might not have appropriate focus styling. +accessibility.keyboard.issue.focus.visible=Focusable element may be missing focus styling.