From 4c0b4af80b2f7d2b14bb5947e4184b40cb13db0d Mon Sep 17 00:00:00 2001 From: Yura Zenevich Date: Wed, 10 Apr 2019 18:36:02 +0000 Subject: [PATCH] Bug 1540904 - added full page API for accessibility walker actor. r=pbro Differential Revision: https://phabricator.services.mozilla.com/D26458 --HG-- extra : moz-landing-system : lando --- .../server/actors/accessibility/walker.js | 45 +++++ devtools/server/tests/browser/browser.ini | 2 + .../browser_accessibility_walker_audit.js | 163 ++++++++++++++++++ .../browser/doc_accessibility_audit.html | 10 ++ devtools/server/tests/browser/head.js | 2 +- devtools/shared/specs/accessibility.js | 6 + 6 files changed, 227 insertions(+), 1 deletion(-) create mode 100644 devtools/server/tests/browser/browser_accessibility_walker_audit.js create mode 100644 devtools/server/tests/browser/doc_accessibility_audit.html diff --git a/devtools/server/actors/accessibility/walker.js b/devtools/server/actors/accessibility/walker.js index e25fec695bba..1bc8682bb938 100644 --- a/devtools/server/actors/accessibility/walker.js +++ b/devtools/server/actors/accessibility/walker.js @@ -122,6 +122,27 @@ function isStale(accessible) { return !!(extraState.value & Ci.nsIAccessibleStates.EXT_STATE_STALE); } +/** + * Get accessibility audit starting with the passed accessible object as a root. + * + * @param {Object} acc + * AccessibileActor to be used as the root for the audit. + * @param {Map} report + * An accumulator map to be used to store audit information. + */ +function getAudit(acc, report) { + if (acc.isDefunct) { + return; + } + + // Audit returns a promise, save the actual value in the report. + report.set(acc, acc.audit().then(result => report.set(acc, result))); + + for (const child of acc.children()) { + getAudit(child, report); + } +} + /** * The AccessibleWalkerActor stores a cache of AccessibleActors that represent * accessible objects in a given document. @@ -367,6 +388,30 @@ const AccessibleWalkerActor = ActorClassWithSpec(accessibleWalkerSpec, { { accessible: parent, children: parent.children() })); }, + /** + * Run accessibility audit and return relevant ancestries for AccessibleActors + * that have non-empty audit checks. + * + * @return {Promise} + * A promise that resolves when the audit is complete and all relevant + * ancestries are calculated. + */ + async audit() { + const doc = await this.getDocument(); + const report = new Map(); + getAudit(doc, report); + await Promise.all(report.values()); + + const ancestries = []; + for (const [acc, audit] of report.entries()) { + if (audit && Object.values(audit).filter(check => check != null).length > 0) { + ancestries.push(this.getAncestry(acc)); + } + } + + return Promise.all(ancestries); + }, + onHighlighterEvent: function(data) { this.emit("highlighter-event", data); }, diff --git a/devtools/server/tests/browser/browser.ini b/devtools/server/tests/browser/browser.ini index b92d69600c12..bfe2b5380123 100644 --- a/devtools/server/tests/browser/browser.ini +++ b/devtools/server/tests/browser/browser.ini @@ -5,6 +5,7 @@ support-files = head.js animation.html animation-data.html + doc_accessibility_audit.html doc_accessibility_infobar.html doc_accessibility.html doc_allocations.html @@ -47,6 +48,7 @@ skip-if = (os == 'win' && processor == 'aarch64') # bug 1533184 skip-if = (os == 'win' && processor == 'aarch64') # bug 1533184 [browser_accessibility_simple.js] skip-if = (os == 'win' && processor == 'aarch64') # bug 1533184 +[browser_accessibility_walker_audit.js] [browser_accessibility_walker.js] skip-if = (os == 'win' && processor == 'aarch64') # bug 1533487 [browser_actor_error.js] diff --git a/devtools/server/tests/browser/browser_accessibility_walker_audit.js b/devtools/server/tests/browser/browser_accessibility_walker_audit.js new file mode 100644 index 000000000000..0131522c09bd --- /dev/null +++ b/devtools/server/tests/browser/browser_accessibility_walker_audit.js @@ -0,0 +1,163 @@ +/* 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 AccessibleWalkerActor audit. +add_task(async function() { + const {target, accessibility} = + await initAccessibilityFrontForUrl(MAIN_DOMAIN + "doc_accessibility_audit.html"); + + const accessibles = [{ + name: "", + role: "document", + value: "", + description: "", + keyboardShortcut: "", + childCount: 2, + domNodeType: 9, + indexInParent: 0, + states: [ + "focused", "readonly", "focusable", "active", "opaque", "enabled", "sensitive", + ], + actions: [], + attributes: { + display: "block", + "explicit-name": "true", + "margin-bottom": "8px", + "margin-left": "8px", + "margin-right": "8px", + "margin-top": "8px", + tag: "body", + "text-align": "start", + "text-indent": "0px", + }, + checks: { + "CONTRAST": null, + }, + }, { + name: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do " + + "eiusmod tempor incididunt ut labore et dolore magna aliqua.", + role: "heading", + value: "", + description: "", + keyboardShortcut: "", + childCount: 1, + domNodeType: 1, + indexInParent: 0, + states: [ "selectable text", "opaque", "enabled", "sensitive" ], + actions: [], + attributes: { + display: "block", + formatting: "block", + id: "h1", + level: "1", + "margin-bottom": "21.4333px", + "margin-left": "0px", + "margin-right": "0px", + "margin-top": "21.4333px", + tag: "h1", + "text-align": "start", + "text-indent": "0px", + }, + checks: { + "CONTRAST": null, + }, + }, { + name: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do " + + "eiusmod tempor incididunt ut labore et dolore magna aliqua.", + role: "text leaf", + value: "", + description: "", + keyboardShortcut: "", + childCount: 0, + domNodeType: 3, + indexInParent: 0, + states: [ "opaque", "enabled", "sensitive" ], + actions: [], + attributes: { "explicit-name": "true" }, + checks: { + "CONTRAST": { + "value": 21, + "color": [0, 0, 0, 1], + "backgroundColor": [255, 255, 255, 1], + "isLargeText": true, + }, + }, + }, { + name: "", + role: "paragraph", + value: "", + description: "", + keyboardShortcut: "", + childCount: 1, + domNodeType: 1, + indexInParent: 1, + states: [ "selectable text", "opaque", "enabled", "sensitive" ], + actions: [ "Press" ], + attributes: { + display: "block", + formatting: "block", + id: "p", + "margin-bottom": "16px", + "margin-left": "0px", + "margin-right": "0px", + "margin-top": "16px", + tag: "p", + "text-align": "start", + "text-indent": "0px", + }, + checks: { + "CONTRAST": null, + }, + }, { + name: "Accessible Paragraph", + role: "text leaf", + value: "", + description: "", + keyboardShortcut: "", + childCount: 0, + domNodeType: 3, + indexInParent: 0, + states: [ "opaque", "enabled", "sensitive" ], + actions: [], + attributes: { "explicit-name": "true" }, + checks: { + "CONTRAST": { + "value": 21, + "color": [0, 0, 0, 1], + "backgroundColor": [255, 255, 255, 1], + "isLargeText": false, + }, + }, + }]; + + function findAccessible(name, role) { + return accessibles.find(accessible => + accessible.name === name && accessible.role === role); + } + + const a11yWalker = await accessibility.getWalker(); + ok(a11yWalker, "The AccessibleWalkerFront was returned"); + await accessibility.enable(); + + info("Checking AccessibleWalker audit functionality"); + const ancestries = await a11yWalker.audit(); + + for (const ancestry of ancestries) { + for (const { accessible, children } of ancestry) { + checkA11yFront(accessible, + findAccessible(accessibles.name, accessibles.role)); + for (const child of children) { + checkA11yFront(child, + findAccessible(child.name, child.role)); + } + } + } + + await accessibility.disable(); + await waitForA11yShutdown(); + await target.destroy(); + gBrowser.removeCurrentTab(); +}); diff --git a/devtools/server/tests/browser/doc_accessibility_audit.html b/devtools/server/tests/browser/doc_accessibility_audit.html new file mode 100644 index 000000000000..9cda24ceb119 --- /dev/null +++ b/devtools/server/tests/browser/doc_accessibility_audit.html @@ -0,0 +1,10 @@ + + + + + + +

Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.

+

Accessible Paragraph

+ + diff --git a/devtools/server/tests/browser/head.js b/devtools/server/tests/browser/head.js index 5870e32ec597..798958e5623c 100644 --- a/devtools/server/tests/browser/head.js +++ b/devtools/server/tests/browser/head.js @@ -272,7 +272,7 @@ function checkA11yFront(front, expected, expectedFront) { } for (const key in expected) { - if (["actions", "states", "attributes"].includes(key)) { + if (["actions", "states", "attributes", "checks"].includes(key)) { SimpleTest.isDeeply(front[key], expected[key], `Accessible Front has correct ${key}`); } else { diff --git a/devtools/shared/specs/accessibility.js b/devtools/shared/specs/accessibility.js index 8b5c957cae74..db469259f8f5 100644 --- a/devtools/shared/specs/accessibility.js +++ b/devtools/shared/specs/accessibility.js @@ -162,6 +162,12 @@ const accessibleWalkerSpec = generateActorSpec({ ancestry: RetVal("array:accessibleWithChildren"), }, }, + audit: { + request: {}, + response: { + audit: RetVal("array:array:accessibleWithChildren"), + }, + }, highlightAccessible: { request: { accessible: Arg(0, "accessible"),