@surea11y/core 1.3.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +46 -2
- package/README.md +109 -35
- package/bin/surea11y-core.js +20 -0
- package/docs/API_STABILITY.md +26 -0
- package/docs/CI_INTEGRATIONS.md +7 -7
- package/docs/ENGINE_OPTIONS.md +1 -1
- package/docs/I18N.md +12 -9
- package/docs/INTEGRATION.md +1 -1
- package/docs/LIMITATIONS.md +1 -1
- package/docs/REPORT.md +1 -1
- package/package.json +50 -16
- package/src/baseline.js +0 -0
- package/src/checks/automatic/area-alt-present.js +4 -6
- package/src/checks/automatic/aria-allowed-attr.js +15 -51
- package/src/checks/automatic/aria-allowed-role.js +2 -0
- package/src/checks/automatic/aria-braille-equivalent.js +2 -0
- package/src/checks/automatic/aria-conditional-attr.js +8 -7
- package/src/checks/automatic/aria-deprecated-role.js +4 -3
- package/src/checks/automatic/aria-hidden-body.js +6 -4
- package/src/checks/automatic/aria-hidden-focus.js +12 -13
- package/src/checks/automatic/aria-prohibited-attr.js +98 -105
- package/src/checks/automatic/aria-prohibited-children.js +56 -87
- package/src/checks/automatic/aria-required-attr.js +6 -7
- package/src/checks/automatic/aria-required-children.js +7 -10
- package/src/checks/automatic/aria-required-parent.js +20 -25
- package/src/checks/automatic/aria-role-name-present.js +2 -0
- package/src/checks/automatic/aria-roles-valid.js +2 -0
- package/src/checks/automatic/aria-valid-attr-value.js +18 -15
- package/src/checks/automatic/aria-valid-attr.js +2 -0
- package/src/checks/automatic/autocomplete-valid.js +2 -0
- package/src/checks/automatic/avoid-inline-spacing.js +3 -2
- package/src/checks/automatic/binary-control-name-present.js +2 -0
- package/src/checks/automatic/button-name-present.js +7 -7
- package/src/checks/automatic/bypass-blocks-present.js +9 -7
- package/src/checks/automatic/canvas-text-alternative-present.js +2 -0
- package/src/checks/automatic/combobox-name-present.js +2 -0
- package/src/checks/automatic/contrast-computable.js +2 -0
- package/src/checks/automatic/contrast-enhanced.js +2 -0
- package/src/checks/automatic/contrast-minimum.js +2 -0
- package/src/checks/automatic/css-orientation-lock.js +21 -27
- package/src/checks/automatic/definition-list-children-valid.js +6 -6
- package/src/checks/automatic/deprecated-elements-not-used.js +4 -2
- package/src/checks/automatic/dialog-name-present.js +10 -10
- package/src/checks/automatic/dlitem-parent-valid.js +2 -0
- package/src/checks/automatic/duplicate-id-aria.js +4 -3
- package/src/checks/automatic/embed-text-alternative-present.js +2 -0
- package/src/checks/automatic/form-control-programmatic-label-present.js +2 -0
- package/src/checks/automatic/form-control-single-label.js +6 -7
- package/src/checks/automatic/html-xml-lang-mismatch.js +2 -0
- package/src/checks/automatic/iframe-focusable-content.js +246 -18
- package/src/checks/automatic/iframe-name-present.js +2 -0
- package/src/checks/automatic/iframe-title-unique.js +3 -1
- package/src/checks/automatic/img-alt-present.js +7 -9
- package/src/checks/automatic/input-image-alt-present.js +4 -6
- package/src/checks/automatic/label-in-name.js +15 -19
- package/src/checks/automatic/language-page-present.js +2 -0
- package/src/checks/automatic/link-in-text-block.js +2 -0
- package/src/checks/automatic/link-name-present.js +2 -0
- package/src/checks/automatic/list-children-valid.js +14 -24
- package/src/checks/automatic/listbox-name-present.js +2 -0
- package/src/checks/automatic/listitem-parent-valid.js +30 -7
- package/src/checks/automatic/menuitem-name-present.js +2 -0
- package/src/checks/automatic/meta-refresh-no-exceptions.js +4 -4
- package/src/checks/automatic/meta-refresh-timing-absent.js +2 -0
- package/src/checks/automatic/meta-viewport-zoom-enabled.js +2 -0
- package/src/checks/automatic/meter-name-present.js +4 -3
- package/src/checks/automatic/nested-interactive-controls-absent.js +27 -5
- package/src/checks/automatic/object-text-alternative-present.js +2 -0
- package/src/checks/automatic/option-name-present.js +2 -0
- package/src/checks/automatic/page-title-present.js +2 -0
- package/src/checks/automatic/progressbar-name-present.js +8 -10
- package/src/checks/automatic/role-img-alt-present.js +4 -4
- package/src/checks/automatic/searchbox-name-present.js +2 -0
- package/src/checks/automatic/server-side-image-map-absent.js +4 -3
- package/src/checks/automatic/slider-name-present.js +2 -0
- package/src/checks/automatic/spinbutton-name-present.js +2 -0
- package/src/checks/automatic/summary-name-present.js +2 -0
- package/src/checks/automatic/svg-image-text-alternative-present.js +2 -0
- package/src/checks/automatic/svg-text-alternative-present.js +17 -5
- package/src/checks/automatic/tab-name-present.js +2 -0
- package/src/checks/automatic/table-headers-attr-valid.js +3 -2
- package/src/checks/automatic/table-th-has-data-cells.js +2 -0
- package/src/checks/automatic/target-size-minimum.js +5 -0
- package/src/checks/automatic/td-has-header.js +24 -1
- package/src/checks/automatic/textbox-name-present.js +2 -0
- package/src/checks/automatic/tooltip-name-present.js +2 -0
- package/src/checks/automatic/treeitem-name-present.js +2 -0
- package/src/checks/automatic/valid-lang.js +2 -0
- package/src/checks/automatic/video-poster-text-alternative-present.js +2 -0
- package/src/checks/manual/accesskeys-manual.js +3 -1
- package/src/checks/manual/area-alt-decorative-manual.js +2 -0
- package/src/checks/manual/area-alt-quality-manual.js +2 -0
- package/src/checks/manual/aria-checked-state-mismatch-manual.js +14 -23
- package/src/checks/manual/aria-text-manual.js +6 -5
- package/src/checks/manual/canvas-text-alternative-quality-manual.js +2 -0
- package/src/checks/manual/css-hidden-focus.js +184 -9
- package/src/checks/manual/embed-text-alternative-quality-manual.js +18 -13
- package/src/checks/manual/empty-heading-manual.js +17 -17
- package/src/checks/manual/empty-table-header-manual.js +52 -25
- package/src/checks/manual/focus-order-semantics-manual.js +16 -4
- package/src/checks/manual/form-control-programmatic-label-quality-manual.js +2 -0
- package/src/checks/manual/heading-order-manual.js +28 -1
- package/src/checks/manual/identical-links-same-purpose-manual.js +2 -0
- package/src/checks/manual/image-redundant-alt-manual.js +21 -1
- package/src/checks/manual/img-alt-decorative-manual.js +2 -0
- package/src/checks/manual/img-alt-quality-manual.js +2 -0
- package/src/checks/manual/input-image-alt-decorative-manual.js +2 -0
- package/src/checks/manual/input-image-alt-quality-manual.js +2 -0
- package/src/checks/manual/label-title-only-manual.js +29 -22
- package/src/checks/manual/landmark-banner-is-top-level-manual.js +54 -47
- package/src/checks/manual/landmark-contentinfo-is-top-level-manual.js +42 -26
- package/src/checks/manual/landmark-main-is-top-level-manual.js +41 -24
- package/src/checks/manual/landmark-no-duplicate-banner-manual.js +16 -23
- package/src/checks/manual/landmark-no-duplicate-contentinfo-manual.js +14 -21
- package/src/checks/manual/landmark-no-duplicate-main-manual.js +10 -13
- package/src/checks/manual/landmark-one-main-manual.js +12 -23
- package/src/checks/manual/landmark-unique-manual.js +37 -52
- package/src/checks/manual/link-name-quality-manual.js +2 -0
- package/src/checks/manual/media-transcript-present-manual.js +2 -0
- package/src/checks/manual/meta-viewport-large-manual.js +3 -1
- package/src/checks/manual/mouse-only-event-handlers-manual.js +2 -0
- package/src/checks/manual/no-autoplay-audio-manual.js +2 -0
- package/src/checks/manual/object-text-alternative-quality-manual.js +2 -0
- package/src/checks/manual/p-as-heading-manual.js +2 -0
- package/src/checks/manual/page-has-heading-one-manual.js +12 -11
- package/src/checks/manual/page-title-patterns-manual.js +2 -0
- package/src/checks/manual/presentation-role-conflict-manual.js +51 -37
- package/src/checks/manual/region-manual.js +27 -36
- package/src/checks/manual/scope-attr-valid-manual.js +3 -1
- package/src/checks/manual/scrollable-region-focusable-manual.js +2 -0
- package/src/checks/manual/skip-link-manual.js +7 -6
- package/src/checks/manual/svg-text-alternative-quality-manual.js +2 -0
- package/src/checks/manual/tabindex-manual.js +3 -1
- package/src/checks/manual/table-duplicate-name-manual.js +5 -4
- package/src/checks/manual/table-fake-caption-manual.js +24 -3
- package/src/checks/manual/video-caption-manual.js +2 -0
- package/src/checks/manual-review.js +2 -0
- package/src/core.js +6772 -2158
- package/src/index.js +2 -0
- package/src/report.js +51 -9
- package/src/sarif.js +18 -3
- package/surea11y.browser.js +2731 -999
- package/bin/core.js +0 -473
- package/docs/CLI.md +0 -128
- package/src/catalogs/composites.wcag.js +0 -454
- package/src/checks/rules-and-tags.full.csv +0 -19
- package/src/checks/rules-and-tags.full.json +0 -259
- package/src/core/aria-helpers.js +0 -1211
- package/src/core/contrast-helpers.js +0 -1302
- package/src/core/dom-helpers.js +0 -4493
- package/src/core/dom-runner.js +0 -787
- package/src/core/frame-messaging.js +0 -261
- package/src/core/frame-scan.js +0 -190
- package/src/core/rollup-composites.js +0 -127
- package/src/core/rule-meta.js +0 -176
- package/src/coverage/wcag-facets.js +0 -1079
- package/src/coverage/wcag-version-map.js +0 -84
- package/src/i18n/en.js +0 -1228
- package/src/i18n/fr.js +0 -1185
- package/src/policy/contracts.js +0 -18
- package/src/policy/resolvePolicy.js +0 -59
- package/src/policy/schemas/engine-options.schema.json +0 -103
- package/src/policy/schemas/policy-contract.schema.json +0 -40
package/package.json
CHANGED
|
@@ -1,20 +1,50 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@surea11y/core",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "1.4.0",
|
|
4
|
+
"description": "Deterministic WCAG 2.2 accessibility engine that tells you what it can't tell you. Zero dependencies.",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"accessibility",
|
|
7
|
+
"a11y",
|
|
8
|
+
"wcag",
|
|
9
|
+
"wcag21",
|
|
10
|
+
"wcag22",
|
|
11
|
+
"accessibility-testing",
|
|
12
|
+
"a11y-testing",
|
|
13
|
+
"audit",
|
|
14
|
+
"aria",
|
|
15
|
+
"contrast",
|
|
16
|
+
"sarif",
|
|
17
|
+
"zero-dependencies",
|
|
18
|
+
"jsdom",
|
|
19
|
+
"playwright",
|
|
20
|
+
"puppeteer",
|
|
21
|
+
"selenium",
|
|
22
|
+
"cypress",
|
|
23
|
+
"webdriverio",
|
|
24
|
+
"jest",
|
|
25
|
+
"vitest"
|
|
26
|
+
],
|
|
5
27
|
"main": "src/index.js",
|
|
6
28
|
"bin": {
|
|
7
|
-
"surea11y": "bin/core.js"
|
|
29
|
+
"surea11y-core": "bin/surea11y-core.js"
|
|
30
|
+
},
|
|
31
|
+
"exports": {
|
|
32
|
+
".": "./src/index.js",
|
|
33
|
+
"./baseline": "./src/baseline.js",
|
|
34
|
+
"./report": "./src/report.js",
|
|
35
|
+
"./sarif": "./src/sarif.js",
|
|
36
|
+
"./browser": "./surea11y.browser.js",
|
|
37
|
+
"./package.json": "./package.json"
|
|
8
38
|
},
|
|
9
39
|
"author": "Jorge Rumoroso",
|
|
10
40
|
"license": "MPL-2.0",
|
|
11
41
|
"repository": {
|
|
12
42
|
"type": "git",
|
|
13
|
-
"url": "git+https://github.com/
|
|
43
|
+
"url": "git+https://github.com/SureA11y/core.git"
|
|
14
44
|
},
|
|
15
|
-
"homepage": "https://github.com/
|
|
45
|
+
"homepage": "https://github.com/SureA11y/core#readme",
|
|
16
46
|
"bugs": {
|
|
17
|
-
"url": "https://github.com/
|
|
47
|
+
"url": "https://github.com/SureA11y/core/issues"
|
|
18
48
|
},
|
|
19
49
|
"engines": {
|
|
20
50
|
"node": "^20.19.0 || ^22.13.0 || >=24.0.0"
|
|
@@ -23,8 +53,13 @@
|
|
|
23
53
|
"access": "public"
|
|
24
54
|
},
|
|
25
55
|
"files": [
|
|
26
|
-
"src",
|
|
27
|
-
"
|
|
56
|
+
"src/index.js",
|
|
57
|
+
"src/core.js",
|
|
58
|
+
"src/baseline.js",
|
|
59
|
+
"src/report.js",
|
|
60
|
+
"src/sarif.js",
|
|
61
|
+
"src/checks/**/*.js",
|
|
62
|
+
"bin/surea11y-core.js",
|
|
28
63
|
"surea11y.browser.js",
|
|
29
64
|
"docs/**/*.md",
|
|
30
65
|
"README.md",
|
|
@@ -33,8 +68,7 @@
|
|
|
33
68
|
"!docs/RULE_TEMPLATE.md",
|
|
34
69
|
"!docs/RULE_TEST_TEMPLATE.md",
|
|
35
70
|
"!docs/RULE_TEST_AUTHORING.md",
|
|
36
|
-
"!docs/TEST_OUTCOME_STABILITY.md"
|
|
37
|
-
"!src/explain"
|
|
71
|
+
"!docs/TEST_OUTCOME_STABILITY.md"
|
|
38
72
|
],
|
|
39
73
|
"scripts": {
|
|
40
74
|
"lint": "eslint .",
|
|
@@ -43,8 +77,8 @@
|
|
|
43
77
|
"format:check": "prettier --check \"src/**/*.js\" \"bin/**/*.js\" \"scripts/**/*.js\" \"tests/**/*.js\" \"eslint.config.js\"",
|
|
44
78
|
"build": "node scripts/build-core.js && node scripts/build-browser.js",
|
|
45
79
|
"pretest": "playwright install chromium",
|
|
46
|
-
"test": "npm run build && node scripts/run-tests.js",
|
|
47
|
-
"test:coverage": "npm run build && node scripts/run-tests.js --experimental-test-coverage --test-coverage-include=src/**/*.js
|
|
80
|
+
"test": "npm run format:check && npm run build && node scripts/run-tests.js",
|
|
81
|
+
"test:coverage": "npm run build && node scripts/run-tests.js --experimental-test-coverage --test-coverage-include=src/**/*.js",
|
|
48
82
|
"test:contrast-helpers": "node tests/contrast-helpers.test.js",
|
|
49
83
|
"helpers-perf-bench": "node --expose-gc scripts/dom-helpers-perf-bench.js",
|
|
50
84
|
"engine-perf-bench": "node --expose-gc scripts/engine-perf-bench.js --profileRules=true --iters=25 --top=15",
|
|
@@ -56,16 +90,16 @@
|
|
|
56
90
|
"fixtures:index": "node scripts/generate-fixture-index.js",
|
|
57
91
|
"docs:rule-catalog": "npm run build && node scripts/generate-rule-catalog.js",
|
|
58
92
|
"validate:automatic-rules": "npm run build && node scripts/validate-all-rules.js src/checks/automatic",
|
|
59
|
-
"validate:manual-rules": "npm run build && node scripts/validate-all-rules.js src/checks/manual"
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
"jsdom": "^29.1.1"
|
|
93
|
+
"validate:manual-rules": "npm run build && node scripts/validate-all-rules.js src/checks/manual",
|
|
94
|
+
"i18n:new": "node scripts/i18n-scaffold.js",
|
|
95
|
+
"i18n:report": "node scripts/i18n-report.js"
|
|
63
96
|
},
|
|
64
97
|
"devDependencies": {
|
|
65
98
|
"@eslint/js": "^10.0.1",
|
|
66
99
|
"eslint": "^10.8.0",
|
|
67
100
|
"eslint-config-prettier": "^10.1.8",
|
|
68
101
|
"globals": "^17.8.0",
|
|
102
|
+
"jsdom": "^29.1.1",
|
|
69
103
|
"playwright": "^1.62.1",
|
|
70
104
|
"prettier": "^3.9.6"
|
|
71
105
|
}
|
package/src/baseline.js
CHANGED
|
Binary file
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/* SPDX-License-Identifier: MPL-2.0 */
|
|
2
|
+
|
|
1
3
|
'use strict';
|
|
2
4
|
|
|
3
5
|
/**
|
|
@@ -199,12 +201,8 @@ function runInPage(ctx) {
|
|
|
199
201
|
}
|
|
200
202
|
|
|
201
203
|
// A non-empty title attribute is HTML-AAM's own next fallback naming
|
|
202
|
-
// source once alt is entirely absent
|
|
203
|
-
//
|
|
204
|
-
// non-empty-alt/aria-label/aria-labelledby). See img-alt-present's
|
|
205
|
-
// sibling fix (2026-07-23, AliExpress's title-only logo <img>) for
|
|
206
|
-
// the real page this was found via -- same gap, same fix, different
|
|
207
|
-
// element.
|
|
204
|
+
// source once alt is entirely absent. Same gap img-alt-present handles
|
|
205
|
+
// for <img title="..."> with no alt.
|
|
208
206
|
const titleRaw = (() => {
|
|
209
207
|
try {
|
|
210
208
|
return el.getAttribute('title');
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/* SPDX-License-Identifier: MPL-2.0 */
|
|
2
|
+
|
|
1
3
|
'use strict';
|
|
2
4
|
|
|
3
5
|
/**
|
|
@@ -19,57 +21,19 @@
|
|
|
19
21
|
* set already covers implicit-role elements without asserting anything
|
|
20
22
|
* role-specific; scope kept deliberately narrow to avoid false positives
|
|
21
23
|
* on implicit-role ARIA-in-HTML edge cases not modeled here.
|
|
22
|
-
* - SUPPORTED_ATTRS_BY_ROLE
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* - `
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
* by `gridcell`/`row` — sort/col-row-index/span are standard WAI-ARIA
|
|
36
|
-
* grid-model properties.
|
|
37
|
-
* - `dialog`/`alertdialog`: `aria-modal` — the spec-defining property
|
|
38
|
-
* that distinguishes a modal from a non-modal dialog; unambiguous.
|
|
39
|
-
* - `listitem`: level/posinset/setsize — standard flat-representation-
|
|
40
|
-
* of-hierarchy properties, same family as the already-covered
|
|
41
|
-
* `treeitem`.
|
|
42
|
-
* - `menu`/`menubar`/`toolbar`: activedescendant/orientation — standard
|
|
43
|
-
* composite-widget properties, same family as the already-covered
|
|
44
|
-
* `tablist`.
|
|
45
|
-
* - SUPPORTED_ATTRS_BY_ROLE full-reconciliation pass 2026-07-28: the
|
|
46
|
-
* 2026-07-21 pass deliberately deferred ~61 roles where a widely-used
|
|
47
|
-
* reference engine allows `aria-expanded`, treating it as too thin/
|
|
48
|
-
* near-universal to import blindly. Checked against `aria-query`
|
|
49
|
-
* (tracks the published WAI-ARIA 1.2 Recommendation, 6 June 2023 — the
|
|
50
|
-
* latest actually-published version, as opposed to the in-progress 1.3
|
|
51
|
-
* Editor's Draft) instead of that reference engine directly, because its own
|
|
52
|
-
* source comments (`// Spec difference: Aria-expanded removed in 1.2`)
|
|
53
|
-
* show that most of its `aria-expanded` allowances are deliberate
|
|
54
|
-
* ARIA-1.1 legacy/AT-compat carryovers it keeps on purpose, not
|
|
55
|
-
* current-spec facts — importing them wholesale would have re-added
|
|
56
|
-
* exactly the kind of unverified allowance the 07-21 pass was avoiding.
|
|
57
|
-
* `aria-query`'s per-role tables (with superclass inheritance resolved,
|
|
58
|
-
* e.g. `aria-activedescendant` via the abstract `composite` role) gave a
|
|
59
|
-
* spec-grounded diff instead. Net changes from that diff:
|
|
60
|
-
* - `aria-expanded` added to: checkbox, columnheader, gridcell, listbox,
|
|
61
|
-
* menuitemcheckbox, menuitemradio, row, rowheader, switch, tab —
|
|
62
|
-
* confirmed present in current spec for these roles specifically
|
|
63
|
-
* (unlike e.g. `listitem`, `dialog`, `alertdialog`, `heading`, which
|
|
64
|
-
* the spec does NOT list it for — those stay excluded).
|
|
65
|
-
* - `aria-activedescendant` added to: combobox, grid, listbox,
|
|
66
|
-
* radiogroup, row, spinbutton, tablist, treegrid (inherited from the
|
|
67
|
-
* abstract `composite` role for any composite/managed-focus widget).
|
|
68
|
-
* - `aria-readonly`/`aria-required` added to: switch, menuitemcheckbox,
|
|
69
|
-
* menuitemradio. `aria-posinset`/`aria-setsize` added to: tab, radio,
|
|
70
|
-
* menuitemcheckbox, menuitemradio. `aria-level` added to: tablist.
|
|
71
|
-
* - `tree`'s `aria-readonly` removed: not in aria-query's resolved
|
|
72
|
-
* props for `tree` (was an unverified carryover, not spec-backed).
|
|
24
|
+
* - SUPPORTED_ATTRS_BY_ROLE holds unambiguous, well-established ARIA facts:
|
|
25
|
+
* subclass relationships like `searchbox`==`textbox`; same-family widget
|
|
26
|
+
* properties like `columnheader`/`rowheader` sort/col-row-index/span
|
|
27
|
+
* matching `gridcell`/`row`; spec-defining properties like `dialog`/
|
|
28
|
+
* `alertdialog` `aria-modal`. `aria-expanded` entries are checked against
|
|
29
|
+
* `aria-query` (which tracks the published WAI-ARIA 1.2 Recommendation,
|
|
30
|
+
* with superclass inheritance resolved, e.g. `aria-activedescendant` via
|
|
31
|
+
* the abstract `composite` role) rather than imported wholesale, since
|
|
32
|
+
* much of the wider ARIA-1.1 `aria-expanded` allowance list is legacy/
|
|
33
|
+
* AT-compat carryover, not current-spec fact.
|
|
34
|
+
* `meter`'s valuenow/valuemax/valuemin/valuetext is also separately
|
|
35
|
+
* *required* (see `aria-required-attr`), but the two tables aren't
|
|
36
|
+
* unioned automatically, so it's listed in both places.
|
|
73
37
|
* - Not rule-gated on isAccTreeEligible: this remains a static-markup
|
|
74
38
|
* property, while engine-level hidden-subtree filtering still applies
|
|
75
39
|
* unless engineOptions.includeHiddenElements is true.
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/* SPDX-License-Identifier: MPL-2.0 */
|
|
2
|
+
|
|
1
3
|
'use strict';
|
|
2
4
|
|
|
3
5
|
/**
|
|
@@ -16,13 +18,12 @@
|
|
|
16
18
|
* `"false"` silently drops the error message from the accessibility
|
|
17
19
|
* tree — authors almost always intend it to be exposed.
|
|
18
20
|
* @implementation-notes
|
|
19
|
-
* - This is deliberately narrow: a
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
* for the same "narrower than that reference engine, but zero false positives"
|
|
21
|
+
* - This is deliberately narrow: the broader space is a table of many
|
|
22
|
+
* attribute/condition pairs. This rule implements only the one pairing
|
|
23
|
+
* (`aria-errormessage` / `aria-invalid`) that is unambiguous and
|
|
24
|
+
* explicitly stated in the ARIA spec, to keep `fail` high-confidence —
|
|
25
|
+
* matches this repo's established pattern (see `aria-required-attr`/
|
|
26
|
+
* `aria-prohibited-attr` for the same "narrow but zero false positives"
|
|
26
27
|
* trade-off).
|
|
27
28
|
* - Does not check whether the `aria-errormessage` ID reference itself
|
|
28
29
|
* resolves to an existing element — that is `aria-valid-attr-value`'s
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/* SPDX-License-Identifier: MPL-2.0 */
|
|
2
|
+
|
|
1
3
|
'use strict';
|
|
2
4
|
|
|
3
5
|
/**
|
|
@@ -12,9 +14,8 @@
|
|
|
12
14
|
* declare — either because WAI-ARIA has deprecated it (e.g. "directory",
|
|
13
15
|
* superseded by role="list") or because it's reserved for user-agent-
|
|
14
16
|
* internal use only, not a spec deprecation but the same "valid token,
|
|
15
|
-
* prohibited for authors" shape (role="generic" —
|
|
16
|
-
*
|
|
17
|
-
* not be used by web authors" guidance).
|
|
17
|
+
* prohibited for authors" shape (role="generic" — per WAI-ARIA 1.2 and
|
|
18
|
+
* MDN's "It should not be used by web authors" guidance).
|
|
18
19
|
* @expectation
|
|
19
20
|
* The role in use is neither deprecated nor author-prohibited. This is a
|
|
20
21
|
* distinct, atomic decision from aria-roles-valid (existence/
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/* SPDX-License-Identifier: MPL-2.0 */
|
|
2
|
+
|
|
1
3
|
'use strict';
|
|
2
4
|
|
|
3
5
|
/**
|
|
@@ -14,10 +16,10 @@
|
|
|
14
16
|
* @expectation
|
|
15
17
|
* <body> does not have aria-hidden="true". Hiding the document body
|
|
16
18
|
* removes the entire page's content and structure from the
|
|
17
|
-
* accessibility tree at once —
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
19
|
+
* accessibility tree at once — both 1.3.1 (Info and Relationships: the
|
|
20
|
+
* page's structure becomes entirely non-determinable) and 4.1.2 (Name,
|
|
21
|
+
* Role, Value: nothing in the document exposes a role/name/value any
|
|
22
|
+
* longer) apply.
|
|
21
23
|
*/
|
|
22
24
|
|
|
23
25
|
const id = 'aria-hidden-body';
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/* SPDX-License-Identifier: MPL-2.0 */
|
|
2
|
+
|
|
1
3
|
'use strict';
|
|
2
4
|
|
|
3
5
|
/**
|
|
@@ -12,7 +14,7 @@
|
|
|
12
14
|
* No element with aria-hidden="true" may itself be focusable, and no focusable element
|
|
13
15
|
* may exist within an aria-hidden="true" subtree.
|
|
14
16
|
*
|
|
15
|
-
* Notes
|
|
17
|
+
* Notes:
|
|
16
18
|
* - Focusability is computed via ctx.helpers.getFocusableInfo (native + tabindex + contenteditable).
|
|
17
19
|
* - Elements that are not rendered (e.g., display:none, visibility:hidden, [hidden]) are excluded.
|
|
18
20
|
* - Elements hidden via CSS in ways that still allow keyboard focus (e.g., opacity:0, off-screen, clip)
|
|
@@ -485,14 +487,11 @@ function runInPage(ctx) {
|
|
|
485
487
|
// An explicit negative tabindex removes the element from the keyboard
|
|
486
488
|
// tab sequence entirely, regardless of tag — the standard, WAI-
|
|
487
489
|
// recommended technique for safely hiding focusable content behind
|
|
488
|
-
// aria-hidden (
|
|
489
|
-
//
|
|
490
|
-
//
|
|
491
|
-
//
|
|
492
|
-
//
|
|
493
|
-
// flagging as a false positive). Such an element is still
|
|
494
|
-
// programmatically focusable (script could call .focus()), but that's
|
|
495
|
-
// not what "no focusable content behind aria-hidden" cares about.
|
|
490
|
+
// aria-hidden (e.g. <button tabindex="-1"> / <a tabindex="-1"> inside
|
|
491
|
+
// an aria-hidden container). This cares about tabbability, not raw
|
|
492
|
+
// focusability. Such an element is still programmatically focusable
|
|
493
|
+
// (script could call .focus()), but that's not what "no focusable
|
|
494
|
+
// content behind aria-hidden" cares about.
|
|
496
495
|
const explicitTabindex = trim(el.getAttribute('tabindex'));
|
|
497
496
|
if (
|
|
498
497
|
explicitTabindex !== '' &&
|
|
@@ -640,10 +639,9 @@ function runInPage(ctx) {
|
|
|
640
639
|
// cheaper first cannot change which elements end up in the bucket —
|
|
641
640
|
// it only skips the expensive check for the (typically vast) majority
|
|
642
641
|
// of focusable candidates that were never inside an aria-hidden root
|
|
643
|
-
// in the first place. On
|
|
644
|
-
//
|
|
645
|
-
//
|
|
646
|
-
// a pure ordering change, not a behavior change.
|
|
642
|
+
// in the first place. On pages with many focusable candidates and a
|
|
643
|
+
// large stylesheet this is a big speedup, and a pure ordering change,
|
|
644
|
+
// not a behavior change.
|
|
647
645
|
const rootEl = closestAriaHiddenTrue(el);
|
|
648
646
|
if (!rootEl) continue;
|
|
649
647
|
|
|
@@ -777,6 +775,7 @@ function runInPage(ctx) {
|
|
|
777
775
|
hint: downgradedToCantTell
|
|
778
776
|
? 'Verify this is an intentional focus sentinel/focus-trap handoff and that keyboard users never remain on hidden focus targets.'
|
|
779
777
|
: 'Remove focusability from descendants or remove aria-hidden; ensure focus and accessibility trees stay aligned.',
|
|
778
|
+
occurrenceOutcome: downgradedToCantTell ? 'cantTell' : 'fail',
|
|
780
779
|
i18n: {
|
|
781
780
|
summaryKey: downgradedToCantTell
|
|
782
781
|
? 'ariaHidden_focus_summary_cantTell_redirect'
|