feat(role): roll and pass WPT accname tests, calculate description (#30434)

There are new "non-manual" WPT accname tests that we now mostly pass,
which required a few tweeks in calculating role and name.

Also implemented accessible description computation, which is just a
small addition on top of accessible name, and passed respective wpt
tests.

References #18332.
This commit is contained in:
Dmitry Gozman 2024-04-19 12:49:49 -07:00 committed by GitHub
parent 3f8bd5ffd1
commit 9b6627a063
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
175 changed files with 2726 additions and 37 deletions

View file

@ -29,7 +29,7 @@ import type { CSSComplexSelectorList } from '../../utils/isomorphic/cssParser';
import { generateSelector, type GenerateSelectorOptions } from './selectorGenerator'; import { generateSelector, type GenerateSelectorOptions } from './selectorGenerator';
import type * as channels from '@protocol/channels'; import type * as channels from '@protocol/channels';
import { Highlight } from './highlight'; import { Highlight } from './highlight';
import { getChecked, getAriaDisabled, getAriaRole, getElementAccessibleName } from './roleUtils'; import { getChecked, getAriaDisabled, getAriaRole, getElementAccessibleName, getElementAccessibleDescription } from './roleUtils';
import { kLayoutSelectorNames, type LayoutSelectorName, layoutSelectorScore } from './layoutSelectorUtils'; import { kLayoutSelectorNames, type LayoutSelectorName, layoutSelectorScore } from './layoutSelectorUtils';
import { asLocator } from '../../utils/isomorphic/locatorGenerators'; import { asLocator } from '../../utils/isomorphic/locatorGenerators';
import type { Language } from '../../utils/isomorphic/locatorGenerators'; import type { Language } from '../../utils/isomorphic/locatorGenerators';
@ -1285,6 +1285,10 @@ export class InjectedScript {
return getElementAccessibleName(element, !!includeHidden); return getElementAccessibleName(element, !!includeHidden);
} }
getElementAccessibleDescription(element: Element, includeHidden?: boolean): string {
return getElementAccessibleDescription(element, !!includeHidden);
}
getAriaRole(element: Element) { getAriaRole(element: Element) {
return getAriaRole(element); return getAriaRole(element);
} }

View file

@ -71,7 +71,7 @@ function isFocusable(element: Element) {
function isNativelyFocusable(element: Element) { function isNativelyFocusable(element: Element) {
const tagName = element.tagName.toUpperCase(); const tagName = element.tagName.toUpperCase();
if (['BUTTON', 'DETAILS', 'SELECT', 'SUMMARY', 'TEXTAREA'].includes(tagName)) if (['BUTTON', 'DETAILS', 'SELECT', 'TEXTAREA'].includes(tagName))
return true; return true;
if (tagName === 'A' || tagName === 'AREA') if (tagName === 'A' || tagName === 'AREA')
return element.hasAttribute('href'); return element.hasAttribute('href');
@ -81,6 +81,7 @@ function isNativelyFocusable(element: Element) {
} }
// https://w3c.github.io/html-aam/#html-element-role-mappings // https://w3c.github.io/html-aam/#html-element-role-mappings
// https://www.w3.org/TR/html-aria/#docconformance
const kImplicitRoleByTagName: { [tagName: string]: (e: Element) => string | null } = { const kImplicitRoleByTagName: { [tagName: string]: (e: Element) => string | null } = {
'A': (e: Element) => { 'A': (e: Element) => {
return e.hasAttribute('href') ? 'link' : null; return e.hasAttribute('href') ? 'link' : null;
@ -115,7 +116,7 @@ const kImplicitRoleByTagName: { [tagName: string]: (e: Element) => string | null
'HEADER': (e: Element) => closestCrossShadow(e, kAncestorPreventingLandmark) ? null : 'banner', 'HEADER': (e: Element) => closestCrossShadow(e, kAncestorPreventingLandmark) ? null : 'banner',
'HR': () => 'separator', 'HR': () => 'separator',
'HTML': () => 'document', 'HTML': () => 'document',
'IMG': (e: Element) => (e.getAttribute('alt') === '') && !hasGlobalAriaAttribute(e) && !hasTabIndex(e) ? 'presentation' : 'img', 'IMG': (e: Element) => (e.getAttribute('alt') === '') && !e.getAttribute('title') && !hasGlobalAriaAttribute(e) && !hasTabIndex(e) ? 'presentation' : 'img',
'INPUT': (e: Element) => { 'INPUT': (e: Element) => {
const type = (e as HTMLInputElement).type.toLowerCase(); const type = (e as HTMLInputElement).type.toLowerCase();
if (type === 'search') if (type === 'search')
@ -338,9 +339,15 @@ function getIdRefs(element: Element, ref: string | null): Element[] {
} }
} }
function normalizeAccessbileName(s: string): string { function trimFlatString(s: string): string {
// "Flat string" at https://w3c.github.io/accname/#terminology // "Flat string" at https://w3c.github.io/accname/#terminology
return s.replace(/\r\n/g, '\n').replace(/\u00A0/g, ' ').replace(/\s\s+/g, ' ').trim(); return s.trim();
}
function asFlatString(s: string): string {
// "Flat string" at https://w3c.github.io/accname/#terminology
// Note that non-breaking spaces are preserved.
return s.split('\u00A0').map(chunk => chunk.replace(/\r\n/g, '\n').replace(/\s\s*/g, ' ')).join('\u00A0').trim();
} }
function queryInAriaOwned(element: Element, selector: string): Element[] { function queryInAriaOwned(element: Element, selector: string): Element[] {
@ -416,9 +423,10 @@ export function getElementAccessibleName(element: Element, includeHidden: boolea
if (!elementProhibitsNaming) { if (!elementProhibitsNaming) {
// step 2. // step 2.
accessibleName = normalizeAccessbileName(getElementAccessibleNameInternal(element, { accessibleName = asFlatString(getTextAlternativeInternal(element, {
includeHidden, includeHidden,
visitedElements: new Set(), visitedElements: new Set(),
embeddedInDescribedBy: undefined,
embeddedInLabelledBy: undefined, embeddedInLabelledBy: undefined,
embeddedInLabel: undefined, embeddedInLabel: undefined,
embeddedInNativeTextAlternative: undefined, embeddedInNativeTextAlternative: undefined,
@ -431,16 +439,53 @@ export function getElementAccessibleName(element: Element, includeHidden: boolea
return accessibleName; return accessibleName;
} }
export function getElementAccessibleDescription(element: Element, includeHidden: boolean): string {
const cache = (includeHidden ? cacheAccessibleDescriptionHidden : cacheAccessibleDescription);
let accessibleDescription = cache?.get(element);
if (accessibleDescription === undefined) {
// https://w3c.github.io/accname/#mapping_additional_nd_description
// https://www.w3.org/TR/html-aam-1.0/#accdesc-computation
accessibleDescription = '';
if (element.hasAttribute('aria-describedby')) {
// precedence 1
const describedBy = getIdRefs(element, element.getAttribute('aria-describedby'));
accessibleDescription = asFlatString(describedBy.map(ref => getTextAlternativeInternal(ref, {
includeHidden,
visitedElements: new Set(),
embeddedInLabelledBy: undefined,
embeddedInLabel: undefined,
embeddedInNativeTextAlternative: undefined,
embeddedInTargetElement: 'none',
embeddedInDescribedBy: { element: ref, hidden: isElementHiddenForAria(ref) },
})).join(' '));
} else if (element.hasAttribute('aria-description')) {
// precedence 2
accessibleDescription = asFlatString(element.getAttribute('aria-description') || '');
} else {
// TODO: handle precedence 3 - html-aam-specific cases like table>caption.
// https://www.w3.org/TR/html-aam-1.0/#accdesc-computation
// precedence 4
accessibleDescription = asFlatString(element.getAttribute('title') || '');
}
cache?.set(element, accessibleDescription);
}
return accessibleDescription;
}
type AccessibleNameOptions = { type AccessibleNameOptions = {
includeHidden: boolean, includeHidden: boolean,
visitedElements: Set<Element>, visitedElements: Set<Element>,
embeddedInDescribedBy: { element: Element, hidden: boolean } | undefined,
embeddedInLabelledBy: { element: Element, hidden: boolean } | undefined, embeddedInLabelledBy: { element: Element, hidden: boolean } | undefined,
embeddedInLabel: { element: Element, hidden: boolean } | undefined, embeddedInLabel: { element: Element, hidden: boolean } | undefined,
embeddedInNativeTextAlternative: { element: Element, hidden: boolean } | undefined, embeddedInNativeTextAlternative: { element: Element, hidden: boolean } | undefined,
embeddedInTargetElement: 'none' | 'self' | 'descendant', embeddedInTargetElement: 'none' | 'self' | 'descendant',
}; };
function getElementAccessibleNameInternal(element: Element, options: AccessibleNameOptions): string { function getTextAlternativeInternal(element: Element, options: AccessibleNameOptions): string {
if (options.visitedElements.has(element)) if (options.visitedElements.has(element))
return ''; return '';
@ -454,6 +499,7 @@ function getElementAccessibleNameInternal(element: Element, options: AccessibleN
// Nor part of a native host language text alternative element (e.g. label in HTML) or attribute traversal, where the root of that traversal was hidden. // Nor part of a native host language text alternative element (e.g. label in HTML) or attribute traversal, where the root of that traversal was hidden.
if (!options.includeHidden && if (!options.includeHidden &&
!options.embeddedInLabelledBy?.hidden && !options.embeddedInLabelledBy?.hidden &&
!options.embeddedInDescribedBy?.hidden &&
!options?.embeddedInNativeTextAlternative?.hidden && !options?.embeddedInNativeTextAlternative?.hidden &&
!options?.embeddedInLabel?.hidden && !options?.embeddedInLabel?.hidden &&
isElementHiddenForAria(element)) { isElementHiddenForAria(element)) {
@ -468,9 +514,10 @@ function getElementAccessibleNameInternal(element: Element, options: AccessibleN
// at least one valid IDREF, and the current node is not already part of an ongoing // at least one valid IDREF, and the current node is not already part of an ongoing
// aria-labelledby or aria-describedby traversal, process its IDREFs in the order they occur... // aria-labelledby or aria-describedby traversal, process its IDREFs in the order they occur...
if (!options.embeddedInLabelledBy) { if (!options.embeddedInLabelledBy) {
const accessibleName = (labelledBy || []).map(ref => getElementAccessibleNameInternal(ref, { const accessibleName = (labelledBy || []).map(ref => getTextAlternativeInternal(ref, {
...options, ...options,
embeddedInLabelledBy: { element: ref, hidden: isElementHiddenForAria(ref) }, embeddedInLabelledBy: { element: ref, hidden: isElementHiddenForAria(ref) },
embeddedInDescribedBy: undefined,
embeddedInTargetElement: 'none', embeddedInTargetElement: 'none',
embeddedInLabel: undefined, embeddedInLabel: undefined,
embeddedInNativeTextAlternative: undefined, embeddedInNativeTextAlternative: undefined,
@ -516,7 +563,7 @@ function getElementAccessibleNameInternal(element: Element, options: AccessibleN
// wpt test name_heading-combobox-focusable-alternative-manual.html do this. // wpt test name_heading-combobox-focusable-alternative-manual.html do this.
return (element as HTMLInputElement).value; return (element as HTMLInputElement).value;
} }
return selectedOptions.map(option => getElementAccessibleNameInternal(option, childOptions)).join(' '); return selectedOptions.map(option => getTextAlternativeInternal(option, childOptions)).join(' ');
} }
if (['progressbar', 'scrollbar', 'slider', 'spinbutton', 'meter'].includes(role)) { if (['progressbar', 'scrollbar', 'slider', 'spinbutton', 'meter'].includes(role)) {
options.visitedElements.add(element); options.visitedElements.add(element);
@ -536,7 +583,7 @@ function getElementAccessibleNameInternal(element: Element, options: AccessibleN
// step 2d. // step 2d.
const ariaLabel = element.getAttribute('aria-label') || ''; const ariaLabel = element.getAttribute('aria-label') || '';
if (ariaLabel.trim()) { if (trimFlatString(ariaLabel)) {
options.visitedElements.add(element); options.visitedElements.add(element);
return ariaLabel; return ariaLabel;
} }
@ -552,7 +599,7 @@ function getElementAccessibleNameInternal(element: Element, options: AccessibleN
if (element.tagName === 'INPUT' && ['button', 'submit', 'reset'].includes((element as HTMLInputElement).type)) { if (element.tagName === 'INPUT' && ['button', 'submit', 'reset'].includes((element as HTMLInputElement).type)) {
options.visitedElements.add(element); options.visitedElements.add(element);
const value = (element as HTMLInputElement).value || ''; const value = (element as HTMLInputElement).value || '';
if (value.trim()) if (trimFlatString(value))
return value; return value;
if ((element as HTMLInputElement).type === 'submit') if ((element as HTMLInputElement).type === 'submit')
return 'Submit'; return 'Submit';
@ -572,10 +619,10 @@ function getElementAccessibleNameInternal(element: Element, options: AccessibleN
if (labels.length && !options.embeddedInLabelledBy) if (labels.length && !options.embeddedInLabelledBy)
return getAccessibleNameFromAssociatedLabels(labels, options); return getAccessibleNameFromAssociatedLabels(labels, options);
const alt = element.getAttribute('alt') || ''; const alt = element.getAttribute('alt') || '';
if (alt.trim()) if (trimFlatString(alt))
return alt; return alt;
const title = element.getAttribute('title') || ''; const title = element.getAttribute('title') || '';
if (title.trim()) if (trimFlatString(title))
return title; return title;
// SPEC DIFFERENCE. // SPEC DIFFERENCE.
// Spec says return localized "Submit Query", but browsers and axe-core insist on "Sumbit". // Spec says return localized "Submit Query", but browsers and axe-core insist on "Sumbit".
@ -624,7 +671,7 @@ function getElementAccessibleNameInternal(element: Element, options: AccessibleN
options.visitedElements.add(element); options.visitedElements.add(element);
for (let child = element.firstElementChild; child; child = child.nextElementSibling) { for (let child = element.firstElementChild; child; child = child.nextElementSibling) {
if (child.tagName === 'LEGEND') { if (child.tagName === 'LEGEND') {
return getElementAccessibleNameInternal(child, { return getTextAlternativeInternal(child, {
...childOptions, ...childOptions,
embeddedInNativeTextAlternative: { element: child, hidden: isElementHiddenForAria(child) }, embeddedInNativeTextAlternative: { element: child, hidden: isElementHiddenForAria(child) },
}); });
@ -639,7 +686,7 @@ function getElementAccessibleNameInternal(element: Element, options: AccessibleN
options.visitedElements.add(element); options.visitedElements.add(element);
for (let child = element.firstElementChild; child; child = child.nextElementSibling) { for (let child = element.firstElementChild; child; child = child.nextElementSibling) {
if (child.tagName === 'FIGCAPTION') { if (child.tagName === 'FIGCAPTION') {
return getElementAccessibleNameInternal(child, { return getTextAlternativeInternal(child, {
...childOptions, ...childOptions,
embeddedInNativeTextAlternative: { element: child, hidden: isElementHiddenForAria(child) }, embeddedInNativeTextAlternative: { element: child, hidden: isElementHiddenForAria(child) },
}); });
@ -656,7 +703,7 @@ function getElementAccessibleNameInternal(element: Element, options: AccessibleN
if (element.tagName === 'IMG') { if (element.tagName === 'IMG') {
options.visitedElements.add(element); options.visitedElements.add(element);
const alt = element.getAttribute('alt') || ''; const alt = element.getAttribute('alt') || '';
if (alt.trim()) if (trimFlatString(alt))
return alt; return alt;
const title = element.getAttribute('title') || ''; const title = element.getAttribute('title') || '';
return title; return title;
@ -667,7 +714,7 @@ function getElementAccessibleNameInternal(element: Element, options: AccessibleN
options.visitedElements.add(element); options.visitedElements.add(element);
for (let child = element.firstElementChild; child; child = child.nextElementSibling) { for (let child = element.firstElementChild; child; child = child.nextElementSibling) {
if (child.tagName === 'CAPTION') { if (child.tagName === 'CAPTION') {
return getElementAccessibleNameInternal(child, { return getTextAlternativeInternal(child, {
...childOptions, ...childOptions,
embeddedInNativeTextAlternative: { element: child, hidden: isElementHiddenForAria(child) }, embeddedInNativeTextAlternative: { element: child, hidden: isElementHiddenForAria(child) },
}); });
@ -687,7 +734,7 @@ function getElementAccessibleNameInternal(element: Element, options: AccessibleN
if (element.tagName === 'AREA') { if (element.tagName === 'AREA') {
options.visitedElements.add(element); options.visitedElements.add(element);
const alt = element.getAttribute('alt') || ''; const alt = element.getAttribute('alt') || '';
if (alt.trim()) if (trimFlatString(alt))
return alt; return alt;
const title = element.getAttribute('title') || ''; const title = element.getAttribute('title') || '';
return title; return title;
@ -698,7 +745,7 @@ function getElementAccessibleNameInternal(element: Element, options: AccessibleN
options.visitedElements.add(element); options.visitedElements.add(element);
for (let child = element.firstElementChild; child; child = child.nextElementSibling) { for (let child = element.firstElementChild; child; child = child.nextElementSibling) {
if (child.tagName.toUpperCase() === 'TITLE' && (child as SVGElement).ownerSVGElement) { if (child.tagName.toUpperCase() === 'TITLE' && (child as SVGElement).ownerSVGElement) {
return getElementAccessibleNameInternal(child, { return getTextAlternativeInternal(child, {
...childOptions, ...childOptions,
embeddedInLabelledBy: { element: child, hidden: isElementHiddenForAria(child) }, embeddedInLabelledBy: { element: child, hidden: isElementHiddenForAria(child) },
}); });
@ -707,17 +754,21 @@ function getElementAccessibleNameInternal(element: Element, options: AccessibleN
} }
if ((element as SVGElement).ownerSVGElement && element.tagName.toUpperCase() === 'A') { if ((element as SVGElement).ownerSVGElement && element.tagName.toUpperCase() === 'A') {
const title = element.getAttribute('xlink:title') || ''; const title = element.getAttribute('xlink:title') || '';
if (title.trim()) { if (trimFlatString(title)) {
options.visitedElements.add(element); options.visitedElements.add(element);
return title; return title;
} }
} }
} }
// See https://w3c.github.io/html-aam/#summary-element-accessible-name-computation for "summary"-specific check.
const shouldNameFromContentForSummary = element.tagName === 'SUMMARY' && !['presentation', 'none'].includes(role);
// step 2f + step 2h. // step 2f + step 2h.
if (allowsNameFromContent(role, options.embeddedInTargetElement === 'descendant') || if (allowsNameFromContent(role, options.embeddedInTargetElement === 'descendant') ||
!!options.embeddedInLabelledBy || !!options.embeddedInLabel || shouldNameFromContentForSummary ||
!!options.embeddedInNativeTextAlternative) { !!options.embeddedInLabelledBy || !!options.embeddedInDescribedBy ||
!!options.embeddedInLabel || !!options.embeddedInNativeTextAlternative) {
options.visitedElements.add(element); options.visitedElements.add(element);
const tokens: string[] = []; const tokens: string[] = [];
const visit = (node: Node, skipSlotted: boolean) => { const visit = (node: Node, skipSlotted: boolean) => {
@ -725,7 +776,7 @@ function getElementAccessibleNameInternal(element: Element, options: AccessibleN
return; return;
if (node.nodeType === 1 /* Node.ELEMENT_NODE */) { if (node.nodeType === 1 /* Node.ELEMENT_NODE */) {
const display = getElementComputedStyle(node as Element)?.display || 'inline'; const display = getElementComputedStyle(node as Element)?.display || 'inline';
let token = getElementAccessibleNameInternal(node as Element, childOptions); let token = getTextAlternativeInternal(node as Element, childOptions);
// SPEC DIFFERENCE. // SPEC DIFFERENCE.
// Spec says "append the result to the accumulated text", assuming "with space". // Spec says "append the result to the accumulated text", assuming "with space".
// However, multiple tests insist that inline elements do not add a space. // However, multiple tests insist that inline elements do not add a space.
@ -755,7 +806,11 @@ function getElementAccessibleNameInternal(element: Element, options: AccessibleN
} }
tokens.push(getPseudoContent(element, '::after')); tokens.push(getPseudoContent(element, '::after'));
const accessibleName = tokens.join(''); const accessibleName = tokens.join('');
if (accessibleName.trim()) // Spec says "Return the accumulated text if it is not the empty string". However, that is not really
// compatible with the real browser behavior and wpt tests, where an element with empty contents will fallback to the title.
// So we follow the spec everywhere except for the target element itself. This can probably be improved.
const maybeTrimmedAccessibleName = options.embeddedInTargetElement === 'self' ? trimFlatString(accessibleName) : accessibleName;
if (maybeTrimmedAccessibleName)
return accessibleName; return accessibleName;
} }
@ -763,7 +818,7 @@ function getElementAccessibleNameInternal(element: Element, options: AccessibleN
if (!['presentation', 'none'].includes(role) || element.tagName === 'IFRAME') { if (!['presentation', 'none'].includes(role) || element.tagName === 'IFRAME') {
options.visitedElements.add(element); options.visitedElements.add(element);
const title = element.getAttribute('title') || ''; const title = element.getAttribute('title') || '';
if (title.trim()) if (trimFlatString(title))
return title; return title;
} }
@ -888,17 +943,20 @@ function hasExplicitAriaDisabled(element: Element | undefined): boolean {
} }
function getAccessibleNameFromAssociatedLabels(labels: Iterable<HTMLLabelElement>, options: AccessibleNameOptions) { function getAccessibleNameFromAssociatedLabels(labels: Iterable<HTMLLabelElement>, options: AccessibleNameOptions) {
return [...labels].map(label => getElementAccessibleNameInternal(label, { return [...labels].map(label => getTextAlternativeInternal(label, {
...options, ...options,
embeddedInLabel: { element: label, hidden: isElementHiddenForAria(label) }, embeddedInLabel: { element: label, hidden: isElementHiddenForAria(label) },
embeddedInNativeTextAlternative: undefined, embeddedInNativeTextAlternative: undefined,
embeddedInLabelledBy: undefined, embeddedInLabelledBy: undefined,
embeddedInDescribedBy: undefined,
embeddedInTargetElement: 'none', embeddedInTargetElement: 'none',
})).filter(accessibleName => !!accessibleName).join(' '); })).filter(accessibleName => !!accessibleName).join(' ');
} }
let cacheAccessibleName: Map<Element, string> | undefined; let cacheAccessibleName: Map<Element, string> | undefined;
let cacheAccessibleNameHidden: Map<Element, string> | undefined; let cacheAccessibleNameHidden: Map<Element, string> | undefined;
let cacheAccessibleDescription: Map<Element, string> | undefined;
let cacheAccessibleDescriptionHidden: Map<Element, string> | undefined;
let cacheIsHidden: Map<Element, boolean> | undefined; let cacheIsHidden: Map<Element, boolean> | undefined;
let cachePseudoContentBefore: Map<Element, string> | undefined; let cachePseudoContentBefore: Map<Element, string> | undefined;
let cachePseudoContentAfter: Map<Element, string> | undefined; let cachePseudoContentAfter: Map<Element, string> | undefined;
@ -908,6 +966,8 @@ export function beginAriaCaches() {
++cachesCounter; ++cachesCounter;
cacheAccessibleName ??= new Map(); cacheAccessibleName ??= new Map();
cacheAccessibleNameHidden ??= new Map(); cacheAccessibleNameHidden ??= new Map();
cacheAccessibleDescription ??= new Map();
cacheAccessibleDescriptionHidden ??= new Map();
cacheIsHidden ??= new Map(); cacheIsHidden ??= new Map();
cachePseudoContentBefore ??= new Map(); cachePseudoContentBefore ??= new Map();
cachePseudoContentAfter ??= new Map(); cachePseudoContentAfter ??= new Map();
@ -917,6 +977,8 @@ export function endAriaCaches() {
if (!--cachesCounter) { if (!--cachesCounter) {
cacheAccessibleName = undefined; cacheAccessibleName = undefined;
cacheAccessibleNameHidden = undefined; cacheAccessibleNameHidden = undefined;
cacheAccessibleDescription = undefined;
cacheAccessibleDescriptionHidden = undefined;
cacheIsHidden = undefined; cacheIsHidden = undefined;
cachePseudoContentBefore = undefined; cachePseudoContentBefore = undefined;
cachePseudoContentAfter = undefined; cachePseudoContentAfter = undefined;

View file

@ -1,10 +1,12 @@
Web platform tests are copied from https://github.com/web-platform-tests/wpt. Web platform tests are copied from https://github.com/web-platform-tests/wpt at revision `5adfe4e8cd223729aa2942915e8a515c079ed0ef`.
Includes: Includes:
- `LICENSE.md` - `LICENSE.md`
- `accname/name*` test files - `accname/manual/name*` test files
- `accname/foo.jpg` - `accname/manual/description*` test files
- `accname/manual/foo.jpg`
- `accname/name/comp*` test files
- `accname/name/shadowdom/*` test files
- `wai-aria/scripts/manual.css` - `wai-aria/scripts/manual.css`
Modified: Instead of including `wai-aria/scripts/ATTAcomm.js` and `wai-aria/scripts/aria-utils.js`, we provide our own harness.
- `wai-aria/scripts/ATTAcomm.js` contains our own harness to avoid modifying test files

View file

@ -0,0 +1,72 @@
<!doctype html>
<html>
<head>
<title>Description 1.0 combobox-focusable</title>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
<link rel="stylesheet" href="/wai-aria/scripts/manual.css">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/wai-aria/scripts/ATTAcomm.js"></script>
<script>
setup({explicit_timeout: true, explicit_done: true });
var theTest = new ATTAcomm(
{
"steps" : [
{
"element" : "test",
"test" : {
"ATK" : [
[
"property",
"description",
"is",
""
]
],
"AXAPI" : [
[
"property",
"AXHelp",
"is",
""
]
],
"IAccessible2" : [
[
"property",
"accDescription",
"is",
""
]
],
"UIA" : [
[
"property",
"Description",
"is",
""
]
]
},
"title" : "step 1",
"type" : "test"
}
],
"title" : "Description 1.0 combobox-focusable"
}
) ;
</script>
</head>
<body>
<p>This test examines the ARIA properties for Description 1.0 combobox-focusable.</p>
<div id="test" role="combobox" tabindex="0" title="Choose your language.">
<span> English </span>
</div>
<div id="manualMode"></div>
<div id="log"></div>
<div id="ATTAmessages"></div>
</body>
</html>

View file

@ -0,0 +1,94 @@
<!doctype html>
<html>
<head>
<title>Description from content of describedby element</title>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
<link rel="stylesheet" href="/wai-aria/scripts/manual.css">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/wai-aria/scripts/ATTAcomm.js"></script>
<script>
setup({explicit_timeout: true, explicit_done: true });
var theTest = new ATTAcomm(
{
"steps" : [
{
"element" : "test",
"test" : {
"ATK" : [
[
"property",
"description",
"is",
"My name is Eli the weird. (QED) Where are my marbles?"
]
],
"AXAPI" : [
[
"property",
"AXHelp",
"is",
"My name is Eli the weird. (QED) Where are my marbles?"
]
],
"IAccessible2" : [
[
"property",
"accDescription",
"is",
"My name is Eli the weird. (QED) Where are my marbles?"
]
],
"UIA" : [
[
"property",
"Description",
"is",
"My name is Eli the weird. (QED) Where are my marbles?"
]
]
},
"title" : "step 1",
"type" : "test"
}
],
"title" : "Description from content of describedby element"
}
) ;
</script>
</head>
<body>
<p>This test examines the ARIA properties for Description from content of describedby element.</p>
<style>
.hidden { display: none; }
</style>
<input id="test" type="text" aria-label="Important stuff" aria-describedby="descId" />
<div>
<div id="descId">
<span aria-hidden="true"><i> Hello, </i></span>
<span>My</span> name is
<div><img src="file.jpg" title="Bryan" alt="" role="presentation" /></div>
<span role="presentation" aria-label="Eli">
<span aria-label="Garaventa">Zambino</span>
</span>
<span>the weird.</span>
(QED)
<span class="hidden"><i><b>and don't you forget it.</b></i></span>
<table>
<tr>
<td>Where</td>
<td style="visibility:hidden;"><div>in</div></td>
<td><div style="display:none;">the world</div></td>
<td>are my marbles?</td>
</tr>
</table>
</div>
</div>
<div id="manualMode"></div>
<div id="log"></div>
<div id="ATTAmessages"></div>
</body>
</html>

View file

@ -0,0 +1,70 @@
<!doctype html>
<html>
<head>
<title>Description link-with-label</title>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
<link rel="stylesheet" href="/wai-aria/scripts/manual.css">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/wai-aria/scripts/ATTAcomm.js"></script>
<script>
setup({explicit_timeout: true, explicit_done: true });
var theTest = new ATTAcomm(
{
"steps" : [
{
"element" : "test",
"test" : {
"ATK" : [
[
"property",
"description",
"is",
"San Francisco"
]
],
"AXAPI" : [
[
"property",
"AXHelp",
"is",
"San Francisco"
]
],
"IAccessible2" : [
[
"property",
"accDescription",
"is",
"San Francisco"
]
],
"UIA" : [
[
"property",
"Description",
"is",
"San Francisco"
]
]
},
"title" : "step 1",
"type" : "test"
}
],
"title" : "Description link-with-label"
}
) ;
</script>
</head>
<body>
<p>This test examines the ARIA properties for Description link-with-label.</p>
<a id="test" href="#" aria-label="California" title="San Francisco" >United States</a>
<div id="manualMode"></div>
<div id="log"></div>
<div id="ATTAmessages"></div>
</body>
</html>

View file

@ -0,0 +1,70 @@
<!doctype html>
<html>
<head>
<title>Description test case 557</title>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
<link rel="stylesheet" href="/wai-aria/scripts/manual.css">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/wai-aria/scripts/ATTAcomm.js"></script>
<script>
setup({explicit_timeout: true, explicit_done: true });
var theTest = new ATTAcomm(
{
"steps" : [
{
"element" : "test",
"test" : {
"ATK" : [
[
"property",
"description",
"is",
"t"
]
],
"AXAPI" : [
[
"property",
"AXHelp",
"is",
"t"
]
],
"IAccessible2" : [
[
"property",
"accDescription",
"is",
"t"
]
],
"UIA" : [
[
"property",
"Description",
"is",
"t"
]
]
},
"title" : "step 1",
"type" : "test"
}
],
"title" : "Description test case 557"
}
) ;
</script>
</head>
<body>
<p>This test examines the ARIA properties for Description test case 557.</p>
<img id="test" src="foo.jpg" aria-label="1" alt="a" title="t"/>
<div id="manualMode"></div>
<div id="log"></div>
<div id="ATTAmessages"></div>
</body>
</html>

View file

@ -0,0 +1,73 @@
<!doctype html>
<html>
<head>
<title>Description test case 664</title>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
<link rel="stylesheet" href="/wai-aria/scripts/manual.css">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/wai-aria/scripts/ATTAcomm.js"></script>
<script>
setup({explicit_timeout: true, explicit_done: true });
var theTest = new ATTAcomm(
{
"steps" : [
{
"element" : "test",
"test" : {
"ATK" : [
[
"property",
"description",
"is",
"foo"
]
],
"AXAPI" : [
[
"property",
"AXHelp",
"is",
"foo"
]
],
"IAccessible2" : [
[
"property",
"accDescription",
"is",
"foo"
]
],
"UIA" : [
[
"property",
"Description",
"is",
"foo"
]
]
},
"title" : "step 1",
"type" : "test"
}
],
"title" : "Description test case 664"
}
) ;
</script>
</head>
<body>
<p>This test examines the ARIA properties for Description test case 664.</p>
<div>
<img id="test" aria-describedby="ID1" src="test.png">
</div>
<div id="ID1">foo</div>
<div id="manualMode"></div>
<div id="log"></div>
<div id="ATTAmessages"></div>
</body>
</html>

View file

@ -0,0 +1,73 @@
<!doctype html>
<html>
<head>
<title>Description test case 665</title>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
<link rel="stylesheet" href="/wai-aria/scripts/manual.css">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/wai-aria/scripts/ATTAcomm.js"></script>
<script>
setup({explicit_timeout: true, explicit_done: true });
var theTest = new ATTAcomm(
{
"steps" : [
{
"element" : "test",
"test" : {
"ATK" : [
[
"property",
"description",
"is",
"foo"
]
],
"AXAPI" : [
[
"property",
"AXHelp",
"is",
"foo"
]
],
"IAccessible2" : [
[
"property",
"accDescription",
"is",
"foo"
]
],
"UIA" : [
[
"property",
"Description",
"is",
"foo"
]
]
},
"title" : "step 1",
"type" : "test"
}
],
"title" : "Description test case 665"
}
) ;
</script>
</head>
<body>
<p>This test examines the ARIA properties for Description test case 665.</p>
<div>
<img id="test" aria-describedby="ID1" src="test.png">
</div>
<div id="ID1" style="display:none">foo</div>
<div id="manualMode"></div>
<div id="log"></div>
<div id="ATTAmessages"></div>
</body>
</html>

View file

@ -0,0 +1,73 @@
<!doctype html>
<html>
<head>
<title>Description test case 666</title>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
<link rel="stylesheet" href="/wai-aria/scripts/manual.css">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/wai-aria/scripts/ATTAcomm.js"></script>
<script>
setup({explicit_timeout: true, explicit_done: true });
var theTest = new ATTAcomm(
{
"steps" : [
{
"element" : "test",
"test" : {
"ATK" : [
[
"property",
"description",
"is",
"foo"
]
],
"AXAPI" : [
[
"property",
"AXHelp",
"is",
"foo"
]
],
"IAccessible2" : [
[
"property",
"accDescription",
"is",
"foo"
]
],
"UIA" : [
[
"property",
"Description",
"is",
"foo"
]
]
},
"title" : "step 1",
"type" : "test"
}
],
"title" : "Description test case 666"
}
) ;
</script>
</head>
<body>
<p>This test examines the ARIA properties for Description test case 666.</p>
<div>
<img id="test" aria-describedby="ID1" src="test.png">
</div>
<div id="ID1" role="presentation">foo</div>
<div id="manualMode"></div>
<div id="log"></div>
<div id="ATTAmessages"></div>
</body>
</html>

View file

@ -0,0 +1,71 @@
<!doctype html>
<html>
<head>
<title>Description test case 772</title>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
<link rel="stylesheet" href="/wai-aria/scripts/manual.css">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/wai-aria/scripts/ATTAcomm.js"></script>
<script>
setup({explicit_timeout: true, explicit_done: true });
var theTest = new ATTAcomm(
{
"steps" : [
{
"element" : "test",
"test" : {
"ATK" : [
[
"property",
"description",
"is",
"foo"
]
],
"AXAPI" : [
[
"property",
"AXHelp",
"is",
"foo"
]
],
"IAccessible2" : [
[
"property",
"accDescription",
"is",
"foo"
]
],
"UIA" : [
[
"property",
"Description",
"is",
"foo"
]
]
},
"title" : "step 1",
"type" : "test"
}
],
"title" : "Description test case 772"
}
) ;
</script>
</head>
<body>
<p>This test examines the ARIA properties for Description test case 772.</p>
<img src="foo.jpg" id="test" alt="test" aria-describedby="t1">
<div id="t1">foo</div>
<div id="manualMode"></div>
<div id="log"></div>
<div id="ATTAmessages"></div>
</body>
</html>

View file

@ -0,0 +1,71 @@
<!doctype html>
<html>
<head>
<title>Description test case 773</title>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
<link rel="stylesheet" href="/wai-aria/scripts/manual.css">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/wai-aria/scripts/ATTAcomm.js"></script>
<script>
setup({explicit_timeout: true, explicit_done: true });
var theTest = new ATTAcomm(
{
"steps" : [
{
"element" : "test",
"test" : {
"ATK" : [
[
"property",
"description",
"is",
"foo"
]
],
"AXAPI" : [
[
"property",
"AXHelp",
"is",
"foo"
]
],
"IAccessible2" : [
[
"property",
"accDescription",
"is",
"foo"
]
],
"UIA" : [
[
"property",
"Description",
"is",
"foo"
]
]
},
"title" : "step 1",
"type" : "test"
}
],
"title" : "Description test case 773"
}
) ;
</script>
</head>
<body>
<p>This test examines the ARIA properties for Description test case 773.</p>
<img src="foo.jpg" id="test" alt="test" aria-describedby="t1">
<div id="t1" style="display:none">foo</div>
<div id="manualMode"></div>
<div id="log"></div>
<div id="ATTAmessages"></div>
</body>
</html>

View file

@ -0,0 +1,71 @@
<!doctype html>
<html>
<head>
<title>Description test case 774</title>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
<link rel="stylesheet" href="/wai-aria/scripts/manual.css">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/wai-aria/scripts/ATTAcomm.js"></script>
<script>
setup({explicit_timeout: true, explicit_done: true });
var theTest = new ATTAcomm(
{
"steps" : [
{
"element" : "test",
"test" : {
"ATK" : [
[
"property",
"description",
"is",
"foo"
]
],
"AXAPI" : [
[
"property",
"AXHelp",
"is",
"foo"
]
],
"IAccessible2" : [
[
"property",
"accDescription",
"is",
"foo"
]
],
"UIA" : [
[
"property",
"Description",
"is",
"foo"
]
]
},
"title" : "step 1",
"type" : "test"
}
],
"title" : "Description test case 774"
}
) ;
</script>
</head>
<body>
<p>This test examines the ARIA properties for Description test case 774.</p>
<img src="foo.jpg" id="test" alt="test" aria-describedby="t1">
<span id="t1" role="presentation">foo</span>
<div id="manualMode"></div>
<div id="log"></div>
<div id="ATTAmessages"></div>
</body>
</html>

View file

@ -0,0 +1,71 @@
<!doctype html>
<html>
<head>
<title>Description test case 838</title>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
<link rel="stylesheet" href="/wai-aria/scripts/manual.css">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/wai-aria/scripts/ATTAcomm.js"></script>
<script>
setup({explicit_timeout: true, explicit_done: true });
var theTest = new ATTAcomm(
{
"steps" : [
{
"element" : "test",
"test" : {
"ATK" : [
[
"property",
"description",
"is",
"foo"
]
],
"AXAPI" : [
[
"property",
"AXHelp",
"is",
"foo"
]
],
"IAccessible2" : [
[
"property",
"accDescription",
"is",
"foo"
]
],
"UIA" : [
[
"property",
"Description",
"is",
"foo"
]
]
},
"title" : "step 1",
"type" : "test"
}
],
"title" : "Description test case 838"
}
) ;
</script>
</head>
<body>
<p>This test examines the ARIA properties for Description test case 838.</p>
<img src="foo.jpg" id="test" alt="test" aria-describedby="t1">
<div id="t1" style="visibility:hidden">foo</div>
<div id="manualMode"></div>
<div id="log"></div>
<div id="ATTAmessages"></div>
</body>
</html>

View file

@ -0,0 +1,70 @@
<!doctype html>
<html>
<head>
<title>Description test case broken reference</title>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
<link rel="stylesheet" href="/wai-aria/scripts/manual.css">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/wai-aria/scripts/ATTAcomm.js"></script>
<script>
setup({explicit_timeout: true, explicit_done: true });
var theTest = new ATTAcomm(
{
"steps" : [
{
"element" : "test",
"test" : {
"ATK" : [
[
"property",
"description",
"is",
""
]
],
"AXAPI" : [
[
"property",
"AXHelp",
"is",
""
]
],
"IAccessible2" : [
[
"property",
"accDescription",
"is",
""
]
],
"UIA" : [
[
"property",
"Description",
"is",
""
]
]
},
"title" : "step 1",
"type" : "test"
}
],
"title" : "Description test case broken reference"
}
) ;
</script>
</head>
<body>
<p>This test examines the ARIA properties for Description test case broken reference.</p>
<img src="foo.jpg" id="test" alt="test" aria-describedby="t1">
<div id="manualMode"></div>
<div id="log"></div>
<div id="ATTAmessages"></div>
</body>
</html>

View file

@ -0,0 +1,71 @@
<!doctype html>
<html>
<head>
<title>Description test case one valid reference</title>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
<link rel="stylesheet" href="/wai-aria/scripts/manual.css">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/wai-aria/scripts/ATTAcomm.js"></script>
<script>
setup({explicit_timeout: true, explicit_done: true });
var theTest = new ATTAcomm(
{
"steps" : [
{
"element" : "test",
"test" : {
"ATK" : [
[
"property",
"description",
"is",
"foo"
]
],
"AXAPI" : [
[
"property",
"AXHelp",
"is",
"foo"
]
],
"IAccessible2" : [
[
"property",
"accDescription",
"is",
"foo"
]
],
"UIA" : [
[
"property",
"Description",
"is",
"foo"
]
]
},
"title" : "step 1",
"type" : "test"
}
],
"title" : "Description test case one valid reference"
}
) ;
</script>
</head>
<body>
<p>This test examines the ARIA properties for Description test case one valid reference.</p>
<img src="foo.jpg" id="test" alt="test" aria-describedby="t1 t2 t3">
<div id="t2">foo</div>
<div id="manualMode"></div>
<div id="log"></div>
<div id="ATTAmessages"></div>
</body>
</html>

View file

@ -0,0 +1,71 @@
<!doctype html>
<html>
<head>
<title>Description title-same-element</title>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type"/>
<link rel="stylesheet" href="/wai-aria/scripts/manual.css">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/wai-aria/scripts/ATTAcomm.js"></script>
<script>
setup({explicit_timeout: true, explicit_done: true });
var theTest = new ATTAcomm(
{
"steps" : [
{
"element" : "test",
"test" : {
"ATK" : [
[
"property",
"description",
"is",
"Description"
]
],
"AXAPI" : [
[
"property",
"AXHelp",
"is",
"Description"
]
],
"IAccessible2" : [
[
"property",
"accDescription",
"is",
"Description"
]
],
"UIA" : [
[
"property",
"Description",
"is",
"Description"
]
]
},
"title" : "step 1",
"type" : "test"
}
],
"title" : "Description title-same-element"
}
) ;
</script>
</head>
<body>
<p>This test examines the ARIA properties for Description title-same-element.</p>
<div><input aria-label="Name" id="test" title="Title" aria-describedby="ID1" type="text"></div>
<div id="ID1">Description</div>
<div id="manualMode"></div>
<div id="log"></div>
<div id="ATTAmessages"></div>
</body>
</html>

View file

Before

Width:  |  Height:  |  Size: 6.6 KiB

After

Width:  |  Height:  |  Size: 6.6 KiB

Some files were not shown because too many files have changed in this diff Show more