Skip to content

Commit fff9b9d

Browse files
authored
chore(release): v4.9.1 (#4446)
### [4.9.1](v4.9.0...v4.9.1) (2024-05-06) ### Bug Fixes - Prevent errors when loading axe in a page with prototype.js - **aria-allowed-attr:** allow meter role allowed aria-\* attributes on meter element ([#4435](#4435)) ([7ac6392](7ac6392)) - **aria-allowed-role:** add gridcell, separator, slider and treeitem to allowed roles of button element ([#4398](#4398)) ([4788bf8](4788bf8)) - **aria-roles:** correct abstract roles (types) for aria-roles([#4421](#4421)) - **aria-valid-attr-value:** aria-controls & aria-haspopup incomplete ([#4418](#4418)) - fix building axe-core translation files with region locales ([#4396](#4396)) ([5c318f3](5c318f3)), closes [#4388](#4388) - **invalidrole:** allow upper and mixed case role names ([#4358](#4358)) ([105016c](105016c)), closes [#2695](#2695) - **isVisibleOnScreen:** account for position: absolute elements inside overflow container ([#4405](#4405)) ([2940f6e](2940f6e)), closes [#4016](#4016) - **label-content-name-mismatch:** better dismiss and wysiwyg symbolic text characters ([#4402](#4402)) - **region:** Decorative images ignored by region rule ([#4412](#4412)) - **target-size:** ignore descendant elements in shadow dom ([#4410](#4410)) ([6091367](6091367)) - **target-size:** pass for element that has nearby elements that are obscured ([#4422](#4422)) ([3a90bb7](3a90bb7)), closes [#4387](#4387) This PR was opened by a robot 🤖 🎉 (And updated by @WilcoFiers )
2 parents d847924 + 0f2b8ac commit fff9b9d

File tree

74 files changed

+10032
-1244
lines changed

Some content is hidden

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

74 files changed

+10032
-1244
lines changed

.eslintrc.js

+26-2
Original file line numberDiff line numberDiff line change
@@ -59,13 +59,37 @@ module.exports = {
5959
selector: 'MemberExpression[property.name=tagName]',
6060
message: "Don't use node.tagName, use node.nodeName instead."
6161
},
62+
// node.attributes can be clobbered so is unsafe to use
63+
// @see https://github.com/dequelabs/axe-core/pull/1432
6264
{
63-
// node.attributes can be clobbered so is unsafe to use
64-
// @see https://github.com/dequelabs/axe-core/pull/1432
65+
// node.attributes
6566
selector:
6667
'MemberExpression[object.name=node][property.name=attributes]',
6768
message:
6869
"Don't use node.attributes, use node.hasAttributes() or axe.utils.getNodeAttributes(node) instead."
70+
},
71+
{
72+
// vNode.actualNode.attributes
73+
selector:
74+
'MemberExpression[object.property.name=actualNode][property.name=attributes]',
75+
message:
76+
"Don't use node.attributes, use node.hasAttributes() or axe.utils.getNodeAttributes(node) instead."
77+
},
78+
// node.contains doesn't work with shadow dom
79+
// @see https://github.com/dequelabs/axe-core/issues/4194
80+
{
81+
// node.contains()
82+
selector:
83+
'CallExpression[callee.object.name=node][callee.property.name=contains]',
84+
message:
85+
"Don't use node.contains(node2) as it doesn't work across shadow DOM. Use axe.utils.contains(node, node2) instead."
86+
},
87+
{
88+
// vNode.actualNode.contains()
89+
selector:
90+
'CallExpression[callee.object.property.name=actualNode][callee.property.name=contains]',
91+
message:
92+
"Don't use node.contains(node2) as it doesn't work across shadow DOM. Use axe.utils.contains(node, node2) instead."
6993
}
7094
]
7195
},

.vscode/launch.json

+18
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"version": "0.2.0",
3+
"configurations": [
4+
{
5+
"type": "chrome",
6+
"request": "attach",
7+
"name": "Attach to Karma test:debug",
8+
"address": "localhost",
9+
"port": 9765, // keep in sync with debugPort in karma.conf.js
10+
"webRoot": "${workspaceFolder}",
11+
"sourceMaps": true,
12+
"sourceMapPathOverrides": {
13+
"*": "${webRoot}/*",
14+
"base/*": "${webRoot}/*"
15+
}
16+
}
17+
]
18+
}

CHANGELOG.md

+17
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,23 @@
22

33
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
44

5+
### [4.9.1](https://github.com/dequelabs/axe-core/compare/v4.9.0...v4.9.1) (2024-05-06)
6+
7+
### Bug Fixes
8+
9+
- Prevent errors when loading axe in a page with prototype.js
10+
- **aria-allowed-attr:** allow meter role allowed aria-\* attributes on meter element ([#4435](https://github.com/dequelabs/axe-core/issues/4435)) ([7ac6392](https://github.com/dequelabs/axe-core/commit/7ac63921e7fab21f3359dcfc8affa7585bc9c25b))
11+
- **aria-allowed-role:** add gridcell, separator, slider and treeitem to allowed roles of button element ([#4398](https://github.com/dequelabs/axe-core/issues/4398)) ([4788bf8](https://github.com/dequelabs/axe-core/commit/4788bf8d6fd963d7b017dad950b122ffcea8d151))
12+
- **aria-roles:** correct abstract roles (types) for aria-roles([#4421](https://github.com/dequelabs/axe-core/pull/4421))
13+
- **aria-valid-attr-value:** aria-controls & aria-haspopup incomplete ([#4418](https://github.com/dequelabs/axe-core/pull/4418))
14+
- fix building axe-core translation files with region locales ([#4396](https://github.com/dequelabs/axe-core/issues/4396)) ([5c318f3](https://github.com/dequelabs/axe-core/commit/5c318f3537056be5779cb53374bc6f4785947c91)), closes [#4388](https://github.com/dequelabs/axe-core/issues/4388)
15+
- **invalidrole:** allow upper and mixed case role names ([#4358](https://github.com/dequelabs/axe-core/issues/4358)) ([105016c](https://github.com/dequelabs/axe-core/commit/105016cfe9d82876cfed2ff5c656a7842c5b3761)), closes [#2695](https://github.com/dequelabs/axe-core/issues/2695)
16+
- **isVisibleOnScreen:** account for position: absolute elements inside overflow container ([#4405](https://github.com/dequelabs/axe-core/issues/4405)) ([2940f6e](https://github.com/dequelabs/axe-core/commit/2940f6ee36ba52d8cf089be2a3c8e7c516c81dd6)), closes [#4016](https://github.com/dequelabs/axe-core/issues/4016)
17+
- **label-content-name-mismatch:** better dismiss and wysiwyg symbolic text characters ([#4402](https://github.com/dequelabs/axe-core/issues/4402))
18+
- **region:** Decorative images ignored by region rule ([#4412](https://github.com/dequelabs/axe-core/pull/4412))
19+
- **target-size:** ignore descendant elements in shadow dom ([#4410](https://github.com/dequelabs/axe-core/issues/4410)) ([6091367](https://github.com/dequelabs/axe-core/commit/6091367a20f70e536fc7e8d77eae4fa7232bc7c0))
20+
- **target-size:** pass for element that has nearby elements that are obscured ([#4422](https://github.com/dequelabs/axe-core/issues/4422)) ([3a90bb7](https://github.com/dequelabs/axe-core/commit/3a90bb70c8db087b2f03cc30a4aee756995c311c)), closes [#4387](https://github.com/dequelabs/axe-core/issues/4387)
21+
522
## [4.9.0](https://github.com/dequelabs/axe-core/compare/v4.8.4...v4.9.0) (2024-03-25)
623

724
### Features

CONTRIBUTING.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ If you need to debug the unit tests in a browser, you can run:
162162
npm run test:debug
163163
```
164164

165-
This will start the Karma server and open up the Chrome browser. Click the `Debug` button to start debugging the tests. You can also navigate to the listed URL in your browser of choice to debug tests using that browser.
165+
This will start the Karma server and open up the Chrome browser. Click the `Debug` button to start debugging the tests. You can either use that browser's debugger or attach an external debugger on port 9765; [a VS Code launch profile](./.vscode/launch.json) is provided. You can also navigate to the listed URL in your browser of choice to debug tests using that browser.
166166

167167
Because the amount of tests is so large, it's recommended to debug only a specific set of unit tests rather than the whole test suite. You can use the `testDirs` argument when using the debug command and pass a specific test directory. The test directory names are the same as those used for `test:unit:*`:
168168

README.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -103,9 +103,9 @@ or equivalently:
103103

104104
This will create a new build for axe, called `axe.<lang>.js` and `axe.<lang>.min.js`. If you want to build all localized versions, simply pass in `--all-lang` instead. If you want to build multiple localized versions (but not all of them), you can pass in a comma-separated list of languages to the `--lang` flag, like `--lang=nl,ja`.
105105

106-
To create a new translation for axe, start by running `grunt translate --lang=<langcode>`. This will create a json file fin the `./locales` directory, with the default English text in it for you to translate. Alternatively, you could copy `./locales/_template.json`. We welcome any localization for axe-core. For details on how to contribute, see the Contributing section below. For details on the message syntax, see [Check Message Template](/doc/check-message-template.md).
106+
To create a new translation for axe, start by running `grunt translate --lang=<langcode>`. This will create a json file in the `./locales` directory, with the default English text in it for you to translate. Alternatively, you could copy `./locales/_template.json`. We welcome any localization for axe-core. For details on how to contribute, see the Contributing section below. For details on the message syntax, see [Check Message Template](/doc/check-message-template.md).
107107

108-
To update existing translation file, re-run `grunt translate --lang=<langcode>`. This will add new messages used in English and remove messages which were not used in English.
108+
To update an existing translation file, re-run `grunt translate --lang=<langcode>`. This will add new messages used in English and remove messages which were not used in English.
109109

110110
Additionally, locale can be applied at runtime by passing a `locale` object to `axe.configure()`. The locale object must be of the same shape as existing locales in the `./locales` directory. For example:
111111

bower.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "axe-core",
3-
"version": "4.9.0",
3+
"version": "4.9.1",
44
"deprecated": true,
55
"contributors": [
66
{

build/rule-generator/questions.js

+12-3
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,10 @@ const validateGetRuleName = async input => {
3333
throw new Error(`RULE name conflicts with an existing rule's filename.`);
3434
}
3535
// 3) ensure no rule id overlaps
36-
const ruleSpecs = await glob(`${directories.rules}/**/*.json`);
36+
const ruleSpecs = await glob(`${directories.rules}/**/*.json`, {
37+
posix: true,
38+
absolute: true
39+
});
3740
const axeRulesIds = ruleSpecs.reduce((out, specPath) => {
3841
const spec = require(specPath);
3942
out.push(spec.id);
@@ -62,7 +65,10 @@ const validateGetCheckName = async input => {
6265
);
6366
}
6467
// 2) ensure no check filename overlaps
65-
const checkSpecs = await glob(`${directories.checks}/**/*.json`);
68+
const checkSpecs = await glob(`${directories.checks}/**/*.json`, {
69+
posix: true,
70+
absolute: true
71+
});
6672
// cannot use `fs.existsSync` here, as we do not know which category of checks to look under
6773
const axeChecksFileNames = checkSpecs.map(
6874
f => f.replace('.json', '').split('/').reverse()[0]
@@ -71,7 +77,10 @@ const validateGetCheckName = async input => {
7177
throw new Error('CHECK name conflicts with an existing filename.');
7278
}
7379
// 3) ensure no check id overlaps
74-
const ruleSpecs = await glob(`${directories.rules}/**/*.json`);
80+
const ruleSpecs = await glob(`${directories.rules}/**/*.json`, {
81+
posix: true,
82+
absolute: true
83+
});
7584
const axe = require(directories.axePath);
7685
const axeChecksIds = ruleSpecs.reduce((out, specPath) => {
7786
const spec = require(specPath);

build/tasks/configure.js

+5-3
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,11 @@ module.exports = function (grunt) {
1818
});
1919

2020
this.files.forEach(function (file) {
21-
const match = file.dest.auto.match(/\.([a-z]{2,3})\.js/);
22-
if (match) {
23-
options.locale = match[1];
21+
// locale will always be the 2nd to last part of the
22+
// filename and in the format of "<name>.<locale>.js"
23+
const parts = file.dest.auto.split('.');
24+
if (parts.length > 2) {
25+
options.locale = parts[parts.length - 2];
2426
}
2527

2628
buildRules(grunt, options, null, function (result) {

build/tasks/metadata-function-map.js

+1-1
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

doc/developer-guide.md

+2-2
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ Axe 3.0 supports open Shadow DOM: see our virtual DOM APIs and test utilities fo
3131

3232
### Environment Pre-requisites
3333

34-
1. You must have NodeJS version 12 or higher installed.
34+
1. You must have NodeJS version 18 or higher installed.
3535
1. Install npm development dependencies. In the root folder of your axe-core repository, run `npm install`
3636

3737
### Building axe.js
@@ -71,7 +71,7 @@ There are also a set of tests that are not considered unit tests that you can ru
7171

7272
Additionally, you can [watch for changes](#watching-for-changes) to files and automatically run the relevant tests.
7373

74-
If you need to debug a test in a non-headless browser, you can run `npm run test:debug` which will run the Karma tests in non-headless Chrome. You can also navigate to the newly opened page using any supported browser.
74+
If you need to debug a test in a non-headless browser, you can run `npm run test:debug` which will run the Karma tests in non-headless Chrome. You can either use that browser's debugger or attach an external debugger on port 9765; [a VS Code launch profile](../.vscode/launch.json) is provided. You can also navigate to the newly opened page using any supported browser.
7575

7676
You can scope which set of tests to debug by passing the `testDirs` argument. Supported values are:
7777

doc/standards-object.md

+4-2
Original file line numberDiff line numberDiff line change
@@ -67,9 +67,11 @@ The [`ariaRoles`](../lib/standards/aria-roles.js) object defines valid ARIA role
6767

6868
- `type` - string(required). [The role type](https://www.w3.org/TR/wai-aria-1.1/#roles_categorization). Valid types are:
6969
- `abstract`
70-
- `widget`
71-
- `structure`
70+
- `composite`
7271
- `landmark`
72+
- `structure`
73+
- `widget`
74+
- `window`
7375
- `requiredContext` - array(optional). List of required parent roles.
7476
- `requiredOwned` - array(optional). List of required owned roles.
7577
- `requiredAttrs` - array(optional). List of required attributes.

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

+13-2
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

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

+2-1
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/invalidrole-evaluate.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ import { tokenList } from '../../core/utils';
2828
function invalidroleEvaluate(node, options, virtualNode) {
2929
const allRoles = tokenList(virtualNode.attr('role'));
3030
const allInvalid = allRoles.every(
31-
role => !isValidRole(role, { allowAbstract: true })
31+
role => !isValidRole(role.toLowerCase(), { allowAbstract: true })
3232
);
3333

3434
/**

lib/checks/aria/valid-scrollable-semantics-evaluate.js

+6-1
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,21 @@ const VALID_TAG_NAMES_FOR_SCROLLABLE_REGIONS = {
1616
* appropriate for scrollable elements found in the focus order.
1717
*/
1818
const VALID_ROLES_FOR_SCROLLABLE_REGIONS = {
19+
alert: true,
20+
alertdialog: true,
1921
application: true,
2022
article: true,
2123
banner: false,
2224
complementary: true,
2325
contentinfo: true,
26+
dialog: true,
2427
form: true,
28+
log: true,
2529
main: true,
2630
navigation: true,
2731
region: true,
28-
search: false
32+
search: false,
33+
status: true
2934
};
3035

3136
/**

lib/checks/label/label-content-name-mismatch-evaluate.js

+6-9
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,7 @@ function labelContentNameMismatchEvaluate(node, options, virtualNode) {
4141
const pixelThreshold = options?.pixelThreshold;
4242
const occurrenceThreshold =
4343
options?.occurrenceThreshold ?? options?.occuranceThreshold;
44-
4544
const accText = accessibleText(node).toLowerCase();
46-
if (isHumanInterpretable(accText) < 1) {
47-
return undefined;
48-
}
49-
5045
const visibleText = sanitize(
5146
subtreeText(virtualNode, {
5247
subtreeDescendant: true,
@@ -55,13 +50,15 @@ function labelContentNameMismatchEvaluate(node, options, virtualNode) {
5550
occurrenceThreshold
5651
})
5752
).toLowerCase();
53+
5854
if (!visibleText) {
5955
return true;
6056
}
61-
if (isHumanInterpretable(visibleText) < 1) {
62-
if (isStringContained(visibleText, accText)) {
63-
return true;
64-
}
57+
58+
if (
59+
isHumanInterpretable(accText) < 1 ||
60+
isHumanInterpretable(visibleText) < 1
61+
) {
6562
return undefined;
6663
}
6764

lib/checks/mobile/target-offset-evaluate.js

+21-2
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,27 @@ export default function targetOffsetEvaluate(node, options, vNode) {
2020
continue;
2121
}
2222
// the offset code works off radius but we want our messaging to reflect diameter
23-
const offset =
24-
roundToSingleDecimal(getOffset(vNode, vNeighbor, minOffset / 2)) * 2;
23+
let offset = null;
24+
try {
25+
offset = getOffset(vNode, vNeighbor, minOffset / 2);
26+
} catch (err) {
27+
if (err.message.startsWith('splitRects')) {
28+
this.data({
29+
messageKey: 'tooManyRects',
30+
closestOffset: 0,
31+
minOffset
32+
});
33+
return undefined;
34+
}
35+
36+
throw err;
37+
}
38+
39+
if (offset === null) {
40+
continue;
41+
}
42+
43+
offset = roundToSingleDecimal(offset) * 2;
2544
if (offset + roundingMargin >= minOffset) {
2645
continue;
2746
}

lib/checks/mobile/target-offset.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
"fail": "Target has insufficient space to its closest neighbors. Safe clickable space has a diameter of ${data.closestOffset}px instead of at least ${data.minOffset}px.",
1515
"incomplete": {
1616
"default": "Element with negative tabindex has insufficient space to its closest neighbors. Safe clickable space has a diameter of ${data.closestOffset}px instead of at least ${data.minOffset}px. Is this a target?",
17-
"nonTabbableNeighbor": "Target has insufficient space to its closest neighbors. Safe clickable space has a diameter of ${data.closestOffset}px instead of at least ${data.minOffset}px. Is the neighbor a target?"
17+
"nonTabbableNeighbor": "Target has insufficient space to its closest neighbors. Safe clickable space has a diameter of ${data.closestOffset}px instead of at least ${data.minOffset}px. Is the neighbor a target?",
18+
"tooManyRects": "Could not get the target size because there are too many overlapping elements"
1819
}
1920
}
2021
}

lib/checks/mobile/target-size-evaluate.js

+6-5
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
rectHasMinimumSize,
66
hasVisualOverlap
77
} from '../../commons/math';
8+
import { contains } from '../../core/utils';
89

910
/**
1011
* Determine if an element has a minimum size, taking into account
@@ -131,8 +132,10 @@ function getLargestUnobscuredArea(vNode, obscuredNodes) {
131132
const obscuringRects = obscuredNodes.map(
132133
({ boundingClientRect: rect }) => rect
133134
);
134-
const unobscuredRects = splitRects(nodeRect, obscuringRects);
135-
if (unobscuredRects.length === 0) {
135+
let unobscuredRects;
136+
try {
137+
unobscuredRects = splitRects(nodeRect, obscuringRects);
138+
} catch (err) {
136139
return null;
137140
}
138141

@@ -185,9 +188,7 @@ function toDecimalSize(rect) {
185188
}
186189

187190
function isDescendantNotInTabOrder(vAncestor, vNode) {
188-
return (
189-
vAncestor.actualNode.contains(vNode.actualNode) && !isInTabOrder(vNode)
190-
);
191+
return contains(vAncestor, vNode) && !isInTabOrder(vNode);
191192
}
192193

193194
function mapActualNodes(vNodes) {

0 commit comments

Comments
 (0)