Bug 1824935 part 3: Add Accessible::ComputedARIARole. r=eeejay

This also exposes this via XPCOM.
This will be used by WebDriver and Dev Tools.

Differential Revision: https://phabricator.services.mozilla.com/D175583
This commit is contained in:
James Teh 2023-04-25 07:06:35 +00:00
parent 05cd652e5e
commit 888f2eec3d
9 changed files with 189 additions and 0 deletions

View File

@ -529,6 +529,47 @@ nsStaticAtom* Accessible::LandmarkRole() const {
: nullptr;
}
nsStaticAtom* Accessible::ComputedARIARole() const {
const nsRoleMapEntry* roleMap = ARIARoleMap();
if (roleMap && roleMap->roleAtom != nsGkAtoms::_empty &&
// region has its own Gecko role and it needs to be handled specially.
roleMap->roleAtom != nsGkAtoms::region &&
(roleMap->roleRule == kUseNativeRole || roleMap->IsOfType(eLandmark) ||
roleMap->roleAtom == nsGkAtoms::alertdialog ||
roleMap->roleAtom == nsGkAtoms::feed ||
roleMap->roleAtom == nsGkAtoms::rowgroup ||
roleMap->roleAtom == nsGkAtoms::searchbox)) {
// Explicit ARIA role (e.g. specified via the role attribute) which does not
// map to a unique Gecko role.
return roleMap->roleAtom;
}
role geckoRole = Role();
if (geckoRole == roles::LANDMARK) {
// Landmark role from native markup; e.g. <main>, <nav>.
return LandmarkRole();
}
if (geckoRole == roles::GROUPING) {
// Gecko doesn't differentiate between group and rowgroup. It uses
// roles::GROUPING for both.
nsAtom* tag = TagName();
if (tag == nsGkAtoms::tbody || tag == nsGkAtoms::tfoot ||
tag == nsGkAtoms::thead) {
return nsGkAtoms::rowgroup;
}
}
// Role from native markup or layout.
#define ROLE(_geckoRole, stringRole, ariaRole, atkRole, macRole, macSubrole, \
msaaRole, ia2Role, androidClass, nameRule) \
case roles::_geckoRole: \
return ariaRole;
switch (geckoRole) {
#include "RoleMap.h"
}
#undef ROLE
MOZ_ASSERT_UNREACHABLE("Unknown role");
return nullptr;
}
void Accessible::ApplyImplicitState(uint64_t& aState) const {
// nsAccessibilityService (and thus FocusManager) can be shut down before
// RemoteAccessibles.

View File

@ -352,6 +352,13 @@ class Accessible {
*/
virtual void Language(nsAString& aLocale) = 0;
/**
* Get the role of this Accessible as an ARIA role token. This might have been
* set explicitly (e.g. role="button") or it might be implicit in native
* markup (e.g. <button> returns "button").
*/
nsStaticAtom* ComputedARIARole() const;
// Methods that interact with content.
virtual void TakeFocus() const = 0;

View File

@ -345,6 +345,13 @@ interface nsIAccessible : nsISupports
*/
void announce(in AString announcement, in unsigned short priority);
/**
* Get the role of this Accessible as an ARIA role token. This might have been
* set explicitly (e.g. role="button") or it might be implicit in native
* markup (e.g. <button> returns "button").
*/
readonly attribute AString computedARIARole;
[notxpcom, nostdcall] InternalLocalAccessible toInternalAccessible();
[notxpcom, nostdcall] InternalAccessible toInternalGeneric();

View File

@ -40,6 +40,7 @@ BROWSER_CHROME_MANIFESTS += [
"tests/browser/general/browser.ini",
"tests/browser/hittest/browser.ini",
"tests/browser/mac/browser.ini",
"tests/browser/role/browser.ini",
"tests/browser/scroll/browser.ini",
"tests/browser/selectable/browser.ini",
"tests/browser/states/browser.ini",

View File

@ -0,0 +1,11 @@
[DEFAULT]
subsuite = a11y
support-files =
head.js
!/accessible/tests/browser/shared-head.js
!/accessible/tests/mochitest/*.js
!/accessible/tests/browser/*.mjs
prefs =
javascript.options.asyncstack_capture_debuggee_only=false
[browser_computedARIARole.js]

View File

@ -0,0 +1,88 @@
/* 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";
addAccessibleTask(
`
<div id="ariaButton" role="button">ARIA button</div>
<div id="ariaLog" role="log">ARIA log</div>
<div id="ariaMain" role="main">ARIA main</div>
<div id="ariaRegion" role="region" aria-label="ARIA region">ARIA region</div>
<nav id="ariaUnnamedRegion" role="region">ARIA unnamed region</nav>
<div id="ariaDirectory" role="directory">ARIA directory</div>
<div id="ariaAlertdialog" role="alertdialog">ARIA alertdialog</div>
<div id="ariaFeed" role="feed">ARIA feed</div>
<div id="ariaRowgroup" role="rowgroup">ARIA rowgroup</div>
<div id="ariaSearchbox" role="searchbox">ARIA searchbox</div>
<div id="ariaUnknown" role="unknown">unknown ARIA role</div>
<button id="htmlButton">HTML button</button>
<button id="toggleButton" aria-pressed="true">toggle button</button>
<main id="htmlMain">HTML main</main>
<header id="htmlHeader">HTML header</header>
<section id="htmlSection">
<header id="htmlSectionHeader">HTML header inside section</header>
</section>
<section id="htmlRegion" aria-label="HTML region">HTML region</section>
<fieldset id="htmlFieldset">HTML fieldset</fieldset>
<table>
<tbody id="htmlTbody" tabindex="-1"><tr><th>HTML tbody</th></tr></tbody>
</table>
<table role="grid">
<tr>
<td id="htmlGridcell">HTML implicit gridcell</td>
</tr>
</table>
<div id="htmlDiv">HTML div</div>
<span id="htmlSpan" aria-label="HTML span">HTML span</span>
<iframe id="iframe"></iframe>
`,
async function(browser, docAcc) {
function testComputedARIARole(id, role) {
const acc = findAccessibleChildByID(docAcc, id);
is(acc.computedARIARole, role, `computedARIARole for ${id} is correct`);
}
testComputedARIARole("ariaButton", "button");
testComputedARIARole("ariaLog", "log");
// Landmarks map to a single Gecko role.
testComputedARIARole("ariaMain", "main");
testComputedARIARole("ariaRegion", "region");
// Unnamed ARIA regions should ignore the ARIA role.
testComputedARIARole("ariaUnnamedRegion", "navigation");
// The directory ARIA role is an alias of list.
testComputedARIARole("ariaDirectory", "list");
// alertdialog, feed, rowgroup and searchbox map to a Gecko role, but it
// isn't unique.
testComputedARIARole("ariaAlertdialog", "alertdialog");
testComputedARIARole("ariaFeed", "feed");
testComputedARIARole("ariaRowgroup", "rowgroup");
testComputedARIARole("ariaSearchbox", "searchbox");
testComputedARIARole("ariaUnknown", "generic");
testComputedARIARole("htmlButton", "button");
// There is only a single ARIA role for buttons, but Gecko uses different
// roles depending on states.
testComputedARIARole("toggleButton", "button");
testComputedARIARole("htmlMain", "main");
testComputedARIARole("htmlHeader", "banner");
// <section> only maps to the region ARIA role if it has a label.
testComputedARIARole("htmlSection", "generic");
// <header> only maps to the banner role if it is not a child of a
// sectioning element.
testComputedARIARole("htmlSectionHeader", "generic");
testComputedARIARole("htmlRegion", "region");
// Gecko doesn't have a rowgroup role. Ensure we differentiate for
// computedARIARole.
testComputedARIARole("htmlFieldset", "group");
testComputedARIARole("htmlTbody", "rowgroup");
// <td> inside <table role="grid"> implicitly maps to ARIA gridcell.
testComputedARIARole("htmlGridcell", "gridcell");
// Test generics.
testComputedARIARole("htmlDiv", "generic");
testComputedARIARole("htmlSpan", "generic");
// Some roles can't be mapped to ARIA role tokens.
testComputedARIARole("iframe", "");
},
{ chrome: true, topLevel: isCacheEnabled }
);

View File

@ -0,0 +1,18 @@
/* 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";
// Load the shared-head file first.
Services.scriptloader.loadSubScript(
"chrome://mochitests/content/browser/accessible/tests/browser/shared-head.js",
this
);
// Loading and common.js from accessible/tests/mochitest/ for all tests, as
// well as promisified-events.js.
loadScripts(
{ name: "common.js", dir: MOCHITESTS_DIR },
{ name: "promisified-events.js", dir: MOCHITESTS_DIR }
);

View File

@ -762,3 +762,17 @@ xpcAccessible::Announce(const nsAString& aAnnouncement, uint16_t aPriority) {
return NS_OK;
}
NS_IMETHODIMP
xpcAccessible::GetComputedARIARole(nsAString& aRole) {
if (!IntlGeneric()) {
return NS_ERROR_FAILURE;
}
nsStaticAtom* ariaRole = IntlGeneric()->ComputedARIARole();
if (ariaRole) {
ariaRole->ToString(aRole);
}
return NS_OK;
}

View File

@ -93,6 +93,8 @@ class xpcAccessible : public nsIAccessible {
NS_IMETHOD Announce(const nsAString& aAnnouncement, uint16_t aPriority) final;
NS_IMETHOD GetComputedARIARole(nsAString& aRole) final;
protected:
xpcAccessible() {}
virtual ~xpcAccessible() {}