Skip to content

Commit e78afa3

Browse files
Phase1.25
1 parent bc346a3 commit e78afa3

File tree

338 files changed

+13961
-2374
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

338 files changed

+13961
-2374
lines changed

build/tasks/metadata-function-map.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ module.exports = function (grunt) {
2222
'// This file is automatically generated using build/tasks/metadata-function-map.js\n';
2323

2424
src.forEach(globPath => {
25-
glob.sync(globPath).forEach(filePath => {
25+
glob.sync(globPath, { posix: true }).forEach(filePath => {
2626
const relativePath = path.relative(
2727
path.dirname(file.dest),
2828
filePath

build/tasks/validate.js

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ function fileExists(v, o) {
1414
var exists;
1515
try {
1616
exists = fs.existsSync(file);
17-
} catch (e) {
17+
} catch {
1818
return false;
1919
}
2020
return exists;
@@ -361,6 +361,12 @@ const standardsTags = [
361361
standardRegex: /^EN-301-549$/,
362362
criterionRegex: /^EN-9\.[1-4]\.[1-9]\.\d{1,2}$/,
363363
wcagLevelRegex: /^wcag21?aa?$/
364+
},
365+
{
366+
name: 'RGAA',
367+
standardRegex: /^RGAAv4$/,
368+
criterionRegex: /^RGAA-\d{1,2}\.\d{1,2}\.\d{1,2}$/,
369+
wcagLevelRegex: /^wcag21?aa?$/
364370
}
365371
];
366372

@@ -411,7 +417,7 @@ function findTagIssues(tags) {
411417
standardTag: standardTags[0] ?? null,
412418
criterionTags
413419
};
414-
if (bestPracticeTags.length !== 0) {
420+
if (name !== 'RGAA' && bestPracticeTags.length !== 0) {
415421
issues.push(`${name} tags cannot be used along side best-practice tag`);
416422
}
417423
if (standardTags.length === 0) {
@@ -423,7 +429,7 @@ function findTagIssues(tags) {
423429
issues.push(`Expected at least one ${name} criterion tag, got 0`);
424430
}
425431

426-
if (wcagLevelRegex) {
432+
if (wcagLevelRegex && standards.WCAG) {
427433
const wcagLevel = standards.WCAG.standardTag;
428434
if (!wcagLevel.match(wcagLevelRegex)) {
429435
issues.push(`${name} rules not allowed on ${wcagLevel}`);

doc/examples/qunit/test/test.html

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,10 @@
66
<!-- Load local QUnit. -->
77
<link
88
rel="stylesheet"
9-
href="../node_modules/qunitjs/qunit/qunit.css"
9+
href="../node_modules/qunit/qunit/qunit.css"
1010
media="screen"
1111
/>
12-
<script src="../node_modules/qunitjs/qunit/qunit.js"></script>
12+
<script src="../node_modules/qunit/qunit/qunit.js"></script>
1313
<!-- Load local lib and tests. -->
1414
<script src="../node_modules/axe-core/axe.min.js"></script>
1515
<script src="a11y.js"></script>

lib/checks/aria/aria-errormessage-evaluate.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import standards from '../../standards';
2-
import { idrefs } from '../../commons/dom';
2+
import { idrefs, isVisibleToScreenReaders } from '../../commons/dom';
33
import { tokenList } from '../../core/utils';
4-
import { isVisibleToScreenReaders } from '../../commons/dom';
4+
import { getExplicitRole } from '../../commons/aria';
55
/**
66
* Check if `aria-errormessage` references an element that also uses a technique to announce the message (aria-live, aria-describedby, etc.).
77
*
@@ -46,7 +46,7 @@ export default function ariaErrormessageEvaluate(node, options, virtualNode) {
4646

4747
try {
4848
idref = attr && idrefs(virtualNode, 'aria-errormessage')[0];
49-
} catch (e) {
49+
} catch {
5050
this.data({
5151
messageKey: 'idrefs',
5252
values: tokenList(attr)
@@ -63,7 +63,7 @@ export default function ariaErrormessageEvaluate(node, options, virtualNode) {
6363
return false;
6464
}
6565
return (
66-
idref.getAttribute('role') === 'alert' ||
66+
getExplicitRole(idref) === 'alert' ||
6767
idref.getAttribute('aria-live') === 'assertive' ||
6868
idref.getAttribute('aria-live') === 'polite' ||
6969
tokenList(virtualNode.attr('aria-describedby')).indexOf(attr) > -1

lib/checks/aria/aria-prohibited-attr-evaluate.js

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import { getRole } from '../../commons/aria';
1+
import { getRole, getRoleType } from '../../commons/aria';
22
import { sanitize, subtreeText } from '../../commons/text';
33
import standards from '../../standards';
4+
import memoize from '../../core/utils/memoize';
45

56
/**
67
* Check that an element does not use any prohibited ARIA attributes.
@@ -33,9 +34,14 @@ export default function ariaProhibitedAttrEvaluate(
3334
) {
3435
const elementsAllowedAriaLabel = options?.elementsAllowedAriaLabel || [];
3536
const { nodeName } = virtualNode.props;
36-
const role = getRole(virtualNode, { chromium: true });
37+
const role = getRole(virtualNode, {
38+
chromium: true,
39+
// this check allows fallback roles. For example, `<div role="foo img" aria-label="...">` is legal.
40+
fallback: true
41+
});
3742

3843
const prohibitedList = listProhibitedAttrs(
44+
virtualNode,
3945
role,
4046
nodeName,
4147
elementsAllowedAriaLabel
@@ -51,7 +57,7 @@ export default function ariaProhibitedAttrEvaluate(
5157
return false;
5258
}
5359

54-
let messageKey = virtualNode.hasAttr('role') ? 'hasRole' : 'noRole';
60+
let messageKey = role !== null ? 'hasRole' : 'noRole';
5561
messageKey += prohibited.length > 1 ? 'Plural' : 'Singular';
5662
this.data({ role, nodeName, messageKey, prohibited });
5763

@@ -64,13 +70,32 @@ export default function ariaProhibitedAttrEvaluate(
6470
return true;
6571
}
6672

67-
function listProhibitedAttrs(role, nodeName, elementsAllowedAriaLabel) {
73+
function listProhibitedAttrs(vNode, role, nodeName, elementsAllowedAriaLabel) {
6874
const roleSpec = standards.ariaRoles[role];
6975
if (roleSpec) {
7076
return roleSpec.prohibitedAttrs || [];
7177
}
72-
if (!!role || elementsAllowedAriaLabel.includes(nodeName)) {
78+
if (
79+
!!role ||
80+
elementsAllowedAriaLabel.includes(nodeName) ||
81+
getClosestAncestorRoleType(vNode) === 'widget'
82+
) {
7383
return [];
7484
}
7585
return ['aria-label', 'aria-labelledby'];
7686
}
87+
88+
const getClosestAncestorRoleType = memoize(
89+
function getClosestAncestorRoleTypeMemoized(vNode) {
90+
if (!vNode) {
91+
return;
92+
}
93+
94+
const role = getRole(vNode, { noPresentational: true, chromium: true });
95+
if (role) {
96+
return getRoleType(role);
97+
}
98+
99+
return getClosestAncestorRoleType(vNode.parent);
100+
}
101+
);

lib/checks/aria/aria-required-attr-evaluate.js

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,11 @@ export default function ariaRequiredAttrEvaluate(
5454
) {
5555
return true;
5656
}
57+
// Non-normative exception for things like media player seek slider.
58+
// Tested to work in various screen readers.
59+
if (role === 'slider' && virtualNode.attr('aria-valuetext')?.trim()) {
60+
return true;
61+
}
5762

5863
const elmSpec = getElementSpec(virtualNode);
5964
const missingAttrs = requiredAttrs.filter(

lib/checks/aria/aria-required-parent-evaluate.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -48,8 +48,8 @@ function getMissingContext(
4848
}
4949

5050
function getAriaOwners(element) {
51-
var owners = [],
52-
o = null;
51+
const owners = [];
52+
let o = null;
5353

5454
while (element) {
5555
if (element.getAttribute('id')) {

lib/checks/aria/aria-valid-attr-value-evaluate.js

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,23 @@ export default function ariaValidAttrValueEvaluate(node, options, virtualNode) {
3636

3737
const preChecks = {
3838
// aria-controls should only check if element exists if the element
39-
// doesn't have aria-expanded=false or aria-selected=false (tabs)
39+
// doesn't have aria-expanded=false, aria-selected=false (tabs),
40+
// or aria-haspopup (may load later)
4041
// @see https://github.com/dequelabs/axe-core/issues/1463
42+
// @see https://github.com/dequelabs/axe-core/issues/4363
4143
'aria-controls': () => {
44+
const hasPopup =
45+
['false', null].includes(virtualNode.attr('aria-haspopup')) === false;
46+
47+
if (hasPopup) {
48+
needsReview = `aria-controls="${virtualNode.attr('aria-controls')}"`;
49+
messageKey = 'controlsWithinPopup';
50+
}
51+
4252
return (
4353
virtualNode.attr('aria-expanded') !== 'false' &&
44-
virtualNode.attr('aria-selected') !== 'false'
54+
virtualNode.attr('aria-selected') !== 'false' &&
55+
hasPopup === false
4556
);
4657
},
4758
// aria-current should mark as needs review if any value is used that is
@@ -104,7 +115,7 @@ export default function ariaValidAttrValueEvaluate(node, options, virtualNode) {
104115

105116
try {
106117
validValue = validateAttrValue(virtualNode, attrName);
107-
} catch (e) {
118+
} catch {
108119
needsReview = `${attrName}="${attrValue}"`;
109120
messageKey = 'idrefs';
110121
return;

lib/checks/aria/aria-valid-attr-value.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
"noIdShadow": "ARIA attribute element ID does not exist on the page or is a descendant of a different shadow DOM tree: ${data.needsReview}",
1616
"ariaCurrent": "ARIA attribute value is invalid and will be treated as \"aria-current=true\": ${data.needsReview}",
1717
"idrefs": "Unable to determine if ARIA attribute element ID exists on the page: ${data.needsReview}",
18-
"empty": "ARIA attribute value is ignored while empty: ${data.needsReview}"
18+
"empty": "ARIA attribute value is ignored while empty: ${data.needsReview}",
19+
"controlsWithinPopup": "Unable to determine if aria-controls referenced ID exists on the page while using aria-haspopup: ${data.needsReview}"
1920
}
2021
}
2122
}

lib/checks/aria/has-widget-role-evaluate.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { getRoleType } from '../../commons/aria';
1+
import { getRoleType, getExplicitRole } from '../../commons/aria';
22

33
/**
44
* Check if an elements `role` attribute uses any widget or composite role values.
@@ -8,8 +8,8 @@ import { getRoleType } from '../../commons/aria';
88
* @memberof checks
99
* @return {Boolean} True if the element uses a `widget` or `composite` role. False otherwise.
1010
*/
11-
function hasWidgetRoleEvaluate(node) {
12-
const role = node.getAttribute('role');
11+
function hasWidgetRoleEvaluate(node, options, virtualNode) {
12+
const role = getExplicitRole(virtualNode);
1313
if (role === null) {
1414
return false;
1515
}

0 commit comments

Comments
 (0)