@surea11y/core 1.2.0 → 1.3.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 +37 -7
- package/LICENSE +373 -21
- package/README.md +67 -1
- package/bin/core.js +144 -19
- package/docs/API_STABILITY.md +1 -1
- package/docs/BINDING_AUTHORS_GUIDE.md +9 -9
- package/docs/CI_INTEGRATIONS.md +103 -0
- package/docs/CLI.md +54 -1
- package/docs/ENGINE_OPTIONS.md +2 -0
- package/docs/INTEGRATION.md +18 -0
- package/docs/OUTPUT_SCHEMA.md +1 -1
- package/docs/RULE_CATALOG.md +1 -1
- package/docs/SARIF.md +59 -0
- package/package.json +15 -4
- package/src/baseline.js +0 -0
- package/src/catalogs/composites.wcag.js +414 -450
- package/src/checks/automatic/area-alt-present.js +59 -25
- package/src/checks/automatic/aria-allowed-attr.js +193 -33
- package/src/checks/automatic/aria-allowed-role.js +21 -7
- package/src/checks/automatic/aria-braille-equivalent.js +32 -10
- package/src/checks/automatic/aria-conditional-attr.js +24 -7
- package/src/checks/automatic/aria-deprecated-role.js +22 -8
- package/src/checks/automatic/aria-hidden-body.js +42 -19
- package/src/checks/automatic/aria-hidden-focus.js +408 -53
- package/src/checks/automatic/aria-prohibited-attr.js +296 -22
- package/src/checks/automatic/aria-prohibited-children.js +55 -16
- package/src/checks/automatic/aria-required-attr.js +23 -8
- package/src/checks/automatic/aria-required-children.js +37 -14
- package/src/checks/automatic/aria-required-parent.js +48 -14
- package/src/checks/automatic/aria-role-name-present.js +47 -21
- package/src/checks/automatic/aria-roles-valid.js +22 -12
- package/src/checks/automatic/aria-valid-attr-value.js +28 -7
- package/src/checks/automatic/aria-valid-attr.js +17 -5
- package/src/checks/automatic/autocomplete-valid.js +74 -16
- package/src/checks/automatic/avoid-inline-spacing.js +20 -6
- package/src/checks/automatic/binary-control-name-present.js +60 -50
- package/src/checks/automatic/button-name-present.js +48 -18
- package/src/checks/automatic/bypass-blocks-present.js +42 -25
- package/src/checks/automatic/canvas-text-alternative-present.js +57 -26
- package/src/checks/automatic/combobox-name-present.js +38 -45
- package/src/checks/automatic/contrast-computable.js +361 -341
- package/src/checks/automatic/contrast-enhanced.js +487 -466
- package/src/checks/automatic/contrast-minimum.js +486 -465
- package/src/checks/automatic/css-orientation-lock.js +30 -8
- package/src/checks/automatic/definition-list-children-valid.js +40 -19
- package/src/checks/automatic/deprecated-elements-not-used.js +21 -7
- package/src/checks/automatic/dialog-name-present.js +37 -75
- package/src/checks/automatic/dlitem-parent-valid.js +23 -8
- package/src/checks/automatic/duplicate-id-aria.js +24 -6
- package/src/checks/automatic/embed-text-alternative-present.js +86 -35
- package/src/checks/automatic/form-control-programmatic-label-present.js +79 -196
- package/src/checks/automatic/form-control-single-label.js +47 -10
- package/src/checks/automatic/html-xml-lang-mismatch.js +34 -18
- package/src/checks/automatic/iframe-focusable-content.js +26 -11
- package/src/checks/automatic/iframe-name-present.js +31 -9
- package/src/checks/automatic/iframe-title-unique.js +29 -8
- package/src/checks/automatic/img-alt-present.js +47 -43
- package/src/checks/automatic/input-image-alt-present.js +143 -112
- package/src/checks/automatic/label-in-name.js +50 -22
- package/src/checks/automatic/language-page-present.js +109 -109
- package/src/checks/automatic/link-in-text-block.js +59 -19
- package/src/checks/automatic/link-name-present.js +45 -14
- package/src/checks/automatic/list-children-valid.js +26 -9
- package/src/checks/automatic/listbox-name-present.js +39 -19
- package/src/checks/automatic/listitem-parent-valid.js +18 -6
- package/src/checks/automatic/menuitem-name-present.js +39 -61
- package/src/checks/automatic/meta-refresh-no-exceptions.js +28 -7
- package/src/checks/automatic/meta-refresh-timing-absent.js +20 -6
- package/src/checks/automatic/meta-viewport-zoom-enabled.js +24 -7
- package/src/checks/automatic/meter-name-present.js +36 -33
- package/src/checks/automatic/nested-interactive-controls-absent.js +31 -10
- package/src/checks/automatic/object-text-alternative-present.js +91 -39
- package/src/checks/automatic/option-name-present.js +38 -21
- package/src/checks/automatic/page-title-present.js +17 -6
- package/src/checks/automatic/progressbar-name-present.js +41 -34
- package/src/checks/automatic/role-img-alt-present.js +209 -157
- package/src/checks/automatic/searchbox-name-present.js +39 -19
- package/src/checks/automatic/server-side-image-map-absent.js +23 -8
- package/src/checks/automatic/slider-name-present.js +40 -47
- package/src/checks/automatic/spinbutton-name-present.js +39 -19
- package/src/checks/automatic/summary-name-present.js +37 -17
- package/src/checks/automatic/svg-image-text-alternative-present.js +114 -47
- package/src/checks/automatic/svg-text-alternative-present.js +246 -226
- package/src/checks/automatic/tab-name-present.js +37 -60
- package/src/checks/automatic/table-headers-attr-valid.js +24 -8
- package/src/checks/automatic/table-th-has-data-cells.js +22 -8
- package/src/checks/automatic/target-size-minimum.js +118 -48
- package/src/checks/automatic/td-has-header.js +29 -11
- package/src/checks/automatic/textbox-name-present.js +39 -19
- package/src/checks/automatic/tooltip-name-present.js +37 -18
- package/src/checks/automatic/treeitem-name-present.js +38 -21
- package/src/checks/automatic/valid-lang.js +20 -6
- package/src/checks/automatic/video-poster-text-alternative-present.js +79 -36
- package/src/checks/manual/accesskeys-manual.js +14 -5
- package/src/checks/manual/area-alt-decorative-manual.js +192 -193
- package/src/checks/manual/area-alt-quality-manual.js +182 -141
- package/src/checks/manual/aria-checked-state-mismatch-manual.js +34 -11
- package/src/checks/manual/aria-text-manual.js +14 -6
- package/src/checks/manual/canvas-text-alternative-quality-manual.js +149 -114
- package/src/checks/manual/css-hidden-focus.js +196 -165
- package/src/checks/manual/embed-text-alternative-quality-manual.js +171 -160
- package/src/checks/manual/empty-heading-manual.js +24 -7
- package/src/checks/manual/empty-table-header-manual.js +17 -6
- package/src/checks/manual/focus-order-semantics-manual.js +45 -10
- package/src/checks/manual/form-control-programmatic-label-quality-manual.js +207 -246
- package/src/checks/manual/heading-order-manual.js +22 -7
- package/src/checks/manual/identical-links-same-purpose-manual.js +34 -12
- package/src/checks/manual/image-redundant-alt-manual.js +17 -7
- package/src/checks/manual/img-alt-decorative-manual.js +131 -96
- package/src/checks/manual/img-alt-quality-manual.js +176 -127
- package/src/checks/manual/input-image-alt-decorative-manual.js +125 -92
- package/src/checks/manual/input-image-alt-quality-manual.js +125 -92
- package/src/checks/manual/label-title-only-manual.js +14 -5
- package/src/checks/manual/landmark-banner-is-top-level-manual.js +66 -16
- package/src/checks/manual/landmark-contentinfo-is-top-level-manual.js +53 -16
- package/src/checks/manual/landmark-main-is-top-level-manual.js +35 -10
- package/src/checks/manual/landmark-no-duplicate-banner-manual.js +29 -10
- package/src/checks/manual/landmark-no-duplicate-contentinfo-manual.js +29 -10
- package/src/checks/manual/landmark-no-duplicate-main-manual.js +17 -8
- package/src/checks/manual/landmark-one-main-manual.js +26 -20
- package/src/checks/manual/landmark-unique-manual.js +41 -15
- package/src/checks/manual/link-name-quality-manual.js +43 -12
- package/src/checks/manual/media-transcript-present-manual.js +35 -22
- package/src/checks/manual/meta-viewport-large-manual.js +16 -5
- package/src/checks/manual/mouse-only-event-handlers-manual.js +38 -11
- package/src/checks/manual/no-autoplay-audio-manual.js +20 -6
- package/src/checks/manual/object-text-alternative-quality-manual.js +175 -154
- package/src/checks/manual/p-as-heading-manual.js +22 -7
- package/src/checks/manual/page-has-heading-one-manual.js +31 -22
- package/src/checks/manual/page-title-patterns-manual.js +78 -50
- package/src/checks/manual/presentation-role-conflict-manual.js +59 -19
- package/src/checks/manual/region-manual.js +241 -48
- package/src/checks/manual/scope-attr-valid-manual.js +10 -3
- package/src/checks/manual/scrollable-region-focusable-manual.js +37 -11
- package/src/checks/manual/skip-link-manual.js +35 -12
- package/src/checks/manual/svg-text-alternative-quality-manual.js +206 -165
- package/src/checks/manual/tabindex-manual.js +10 -3
- package/src/checks/manual/table-duplicate-name-manual.js +17 -7
- package/src/checks/manual/table-fake-caption-manual.js +24 -7
- package/src/checks/manual/video-caption-manual.js +15 -4
- package/src/checks/manual-review.js +56 -12
- package/src/core/aria-helpers.js +1127 -886
- package/src/core/contrast-helpers.js +1217 -1062
- package/src/core/dom-helpers.js +4175 -3917
- package/src/core/dom-runner.js +720 -604
- package/src/core/frame-messaging.js +189 -138
- package/src/core/frame-scan.js +94 -82
- package/src/core/rollup-composites.js +94 -102
- package/src/core/rule-meta.js +58 -41
- package/src/core.js +36552 -29111
- package/src/i18n/en.js +1194 -889
- package/src/i18n/fr.js +1136 -795
- package/src/policy/contracts.js +13 -13
- package/src/policy/resolvePolicy.js +48 -44
- package/src/report.js +63 -43
- package/src/sarif.js +175 -0
- package/surea11y.browser.js +36042 -0
|
@@ -20,7 +20,8 @@ const id = 'area-alt-present';
|
|
|
20
20
|
|
|
21
21
|
const meta = {
|
|
22
22
|
title: '<area> must have an alt attribute',
|
|
23
|
-
description:
|
|
23
|
+
description:
|
|
24
|
+
'Checks that <area> elements provide an alt attribute to support a text alternative mechanism.',
|
|
24
25
|
i18n: {
|
|
25
26
|
titleKey: 'area_altPresent_title',
|
|
26
27
|
descriptionKey: 'area_altPresent_description'
|
|
@@ -29,7 +30,13 @@ const meta = {
|
|
|
29
30
|
tags: ['wcag2a', 'wcag111', 'nontext', 'images', 'imagemap', 'atomic', 'automatic'],
|
|
30
31
|
wcagSc: ['1.1.1'],
|
|
31
32
|
normativeMappings: [
|
|
32
|
-
{
|
|
33
|
+
{
|
|
34
|
+
standard: 'WCAG',
|
|
35
|
+
version: '2.2',
|
|
36
|
+
requirement: '1.1.1',
|
|
37
|
+
title: 'Non-text Content',
|
|
38
|
+
conformanceLevel: 'A'
|
|
39
|
+
}
|
|
33
40
|
],
|
|
34
41
|
defaultSeverity: 'serious',
|
|
35
42
|
category: 'perceivable',
|
|
@@ -46,25 +53,29 @@ function runInPage(ctx) {
|
|
|
46
53
|
const { document, root, helpers, rule } = ctx;
|
|
47
54
|
const safeRoot = root || document;
|
|
48
55
|
|
|
49
|
-
const queryAllSmart =
|
|
50
|
-
|
|
56
|
+
const queryAllSmart =
|
|
57
|
+
helpers && typeof helpers.queryAllSmart === 'function' ? helpers.queryAllSmart : null;
|
|
58
|
+
const queryAll =
|
|
59
|
+
helpers && typeof helpers.queryAll === 'function'
|
|
51
60
|
? helpers.queryAll
|
|
52
61
|
: (sel) => {
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
62
|
+
try {
|
|
63
|
+
return safeRoot && safeRoot.querySelectorAll
|
|
64
|
+
? Array.from(safeRoot.querySelectorAll(sel))
|
|
65
|
+
: [];
|
|
66
|
+
} catch {
|
|
67
|
+
return [];
|
|
68
|
+
}
|
|
69
|
+
};
|
|
56
70
|
|
|
57
|
-
const getEligibilityInfo =
|
|
58
|
-
|
|
59
|
-
: null;
|
|
71
|
+
const getEligibilityInfo =
|
|
72
|
+
helpers && typeof helpers.getEligibilityInfo === 'function' ? helpers.getEligibilityInfo : null;
|
|
60
73
|
|
|
61
|
-
const isAccTreeEligible =
|
|
62
|
-
|
|
63
|
-
: null;
|
|
74
|
+
const isAccTreeEligible =
|
|
75
|
+
helpers && typeof helpers.isAccTreeEligible === 'function' ? helpers.isAccTreeEligible : null;
|
|
64
76
|
|
|
65
|
-
const getAriaNameInfo =
|
|
66
|
-
|
|
67
|
-
: null;
|
|
77
|
+
const getAriaNameInfo =
|
|
78
|
+
helpers && typeof helpers.getAriaNameInfo === 'function' ? helpers.getAriaNameInfo : null;
|
|
68
79
|
|
|
69
80
|
// --- image-map semantics (rule-local) ---
|
|
70
81
|
|
|
@@ -72,7 +83,7 @@ function runInPage(ctx) {
|
|
|
72
83
|
try {
|
|
73
84
|
const s = String(val || '').trim();
|
|
74
85
|
if (!s) return '';
|
|
75
|
-
return
|
|
86
|
+
return s[0] === '#' ? s.slice(1).trim().toLowerCase() : s.toLowerCase();
|
|
76
87
|
} catch {
|
|
77
88
|
return '';
|
|
78
89
|
}
|
|
@@ -92,7 +103,8 @@ function runInPage(ctx) {
|
|
|
92
103
|
const __usemapIndex = (() => {
|
|
93
104
|
const idx = new Map();
|
|
94
105
|
try {
|
|
95
|
-
const imgs =
|
|
106
|
+
const imgs =
|
|
107
|
+
document && document.querySelectorAll ? document.querySelectorAll('img[usemap]') : [];
|
|
96
108
|
for (const img of imgs) {
|
|
97
109
|
if (!img || !img.getAttribute) continue;
|
|
98
110
|
const u = normUsemap(img.getAttribute('usemap'));
|
|
@@ -120,8 +132,11 @@ function runInPage(ctx) {
|
|
|
120
132
|
}
|
|
121
133
|
|
|
122
134
|
const areas = (() => {
|
|
123
|
-
try {
|
|
124
|
-
|
|
135
|
+
try {
|
|
136
|
+
return Array.from((queryAllSmart ? queryAllSmart('area') : queryAll('area')) || []);
|
|
137
|
+
} catch {
|
|
138
|
+
return queryAll('area');
|
|
139
|
+
}
|
|
125
140
|
})();
|
|
126
141
|
|
|
127
142
|
if (!areas.length) {
|
|
@@ -143,7 +158,11 @@ function runInPage(ctx) {
|
|
|
143
158
|
// This is the "visibility of map/area doesn't matter; the image does" policy.
|
|
144
159
|
if (isAccTreeEligible) {
|
|
145
160
|
const imgElig = (() => {
|
|
146
|
-
try {
|
|
161
|
+
try {
|
|
162
|
+
return isAccTreeEligible(img, ctx);
|
|
163
|
+
} catch {
|
|
164
|
+
return { eligible: true, reasons: [] };
|
|
165
|
+
}
|
|
147
166
|
})();
|
|
148
167
|
if (imgElig && imgElig.eligible === false) continue;
|
|
149
168
|
}
|
|
@@ -151,7 +170,11 @@ function runInPage(ctx) {
|
|
|
151
170
|
// 2) The <area> itself must be eligible (aria-hidden/inert exceptions handled by helper).
|
|
152
171
|
if (isAccTreeEligible) {
|
|
153
172
|
const elig = (() => {
|
|
154
|
-
try {
|
|
173
|
+
try {
|
|
174
|
+
return isAccTreeEligible(el, ctx);
|
|
175
|
+
} catch {
|
|
176
|
+
return { eligible: true, reasons: [] };
|
|
177
|
+
}
|
|
155
178
|
})();
|
|
156
179
|
if (elig && elig.eligible === false) continue;
|
|
157
180
|
}
|
|
@@ -166,7 +189,7 @@ function runInPage(ctx) {
|
|
|
166
189
|
// text-alternative mechanism for <area> (HTML-AAM accessible name
|
|
167
190
|
// computation includes ARIA naming before falling back to alt).
|
|
168
191
|
if (getAriaNameInfo) {
|
|
169
|
-
let ariaName
|
|
192
|
+
let ariaName;
|
|
170
193
|
try {
|
|
171
194
|
ariaName = getAriaNameInfo(el, ctx);
|
|
172
195
|
} catch {
|
|
@@ -182,7 +205,13 @@ function runInPage(ctx) {
|
|
|
182
205
|
// sibling fix (2026-07-23, AliExpress's title-only logo <img>) for
|
|
183
206
|
// the real page this was found via -- same gap, same fix, different
|
|
184
207
|
// element.
|
|
185
|
-
const titleRaw = (() => {
|
|
208
|
+
const titleRaw = (() => {
|
|
209
|
+
try {
|
|
210
|
+
return el.getAttribute('title');
|
|
211
|
+
} catch {
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
})();
|
|
186
215
|
if (titleRaw !== null && String(titleRaw).trim()) continue;
|
|
187
216
|
|
|
188
217
|
const eligInfo = getEligibilityInfo ? getEligibilityInfo(el, ctx, { targetSet: 'acc' }) : null;
|
|
@@ -219,7 +248,12 @@ function runInPage(ctx) {
|
|
|
219
248
|
return { ruleId: rule.ruleId, outcome: 'pass', severity: 'minor', occurrences: [] };
|
|
220
249
|
}
|
|
221
250
|
|
|
222
|
-
return {
|
|
251
|
+
return {
|
|
252
|
+
ruleId: rule.ruleId,
|
|
253
|
+
outcome: 'fail',
|
|
254
|
+
severity: rule.defaultSeverity || 'minor',
|
|
255
|
+
occurrences
|
|
256
|
+
};
|
|
223
257
|
}
|
|
224
258
|
|
|
225
259
|
module.exports = { id, meta, runInPage };
|
|
@@ -43,15 +43,15 @@
|
|
|
43
43
|
* composite-widget properties, same family as the already-covered
|
|
44
44
|
* `tablist`.
|
|
45
45
|
* - SUPPORTED_ATTRS_BY_ROLE full-reconciliation pass 2026-07-28: the
|
|
46
|
-
* 2026-07-21 pass deliberately deferred ~61 roles where
|
|
47
|
-
* engine
|
|
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
48
|
* near-universal to import blindly. Checked against `aria-query`
|
|
49
49
|
* (tracks the published WAI-ARIA 1.2 Recommendation, 6 June 2023 — the
|
|
50
50
|
* latest actually-published version, as opposed to the in-progress 1.3
|
|
51
|
-
* Editor's Draft) instead of
|
|
51
|
+
* Editor's Draft) instead of that reference engine directly, because its own
|
|
52
52
|
* source comments (`// Spec difference: Aria-expanded removed in 1.2`)
|
|
53
53
|
* show that most of its `aria-expanded` allowances are deliberate
|
|
54
|
-
* ARIA-1.1 legacy/AT-compat carryovers
|
|
54
|
+
* ARIA-1.1 legacy/AT-compat carryovers it keeps on purpose, not
|
|
55
55
|
* current-spec facts — importing them wholesale would have re-added
|
|
56
56
|
* exactly the kind of unverified allowance the 07-21 pass was avoiding.
|
|
57
57
|
* `aria-query`'s per-role tables (with superclass inheritance resolved,
|
|
@@ -79,7 +79,8 @@ const id = 'aria-allowed-attr';
|
|
|
79
79
|
|
|
80
80
|
const meta = {
|
|
81
81
|
title: 'aria-* attributes must be permitted for the element’s role',
|
|
82
|
-
description:
|
|
82
|
+
description:
|
|
83
|
+
'Checks that every recognized aria-* attribute present on an element with an explicit role is either globally supported or supported by that role.',
|
|
83
84
|
i18n: {
|
|
84
85
|
titleKey: 'ariaAllowedAttr_title',
|
|
85
86
|
descriptionKey: 'ariaAllowedAttr_description'
|
|
@@ -88,7 +89,13 @@ const meta = {
|
|
|
88
89
|
tags: ['wcag2a', 'wcag412', 'aria', 'structure', 'atomic', 'automatic'],
|
|
89
90
|
wcagSc: ['4.1.2'],
|
|
90
91
|
normativeMappings: [
|
|
91
|
-
{
|
|
92
|
+
{
|
|
93
|
+
standard: 'WCAG',
|
|
94
|
+
version: '2.2',
|
|
95
|
+
requirement: '4.1.2',
|
|
96
|
+
title: 'Name, Role, Value',
|
|
97
|
+
conformanceLevel: 'A'
|
|
98
|
+
}
|
|
92
99
|
],
|
|
93
100
|
defaultSeverity: 'moderate',
|
|
94
101
|
category: 'robust',
|
|
@@ -98,7 +105,7 @@ const meta = {
|
|
|
98
105
|
};
|
|
99
106
|
|
|
100
107
|
function runInPage(ctx) {
|
|
101
|
-
const {
|
|
108
|
+
const { helpers, rule } = ctx;
|
|
102
109
|
|
|
103
110
|
const ariaHelpers = helpers && helpers.aria ? helpers.aria : null;
|
|
104
111
|
if (!ariaHelpers) {
|
|
@@ -111,12 +118,30 @@ function runInPage(ctx) {
|
|
|
111
118
|
// build inlines only this function's own source text — see
|
|
112
119
|
// scripts/build-core.js header ("runInPage MUST be self-contained").
|
|
113
120
|
const GLOBAL_ATTRS = [
|
|
114
|
-
'aria-atomic',
|
|
115
|
-
'aria-
|
|
116
|
-
'aria-
|
|
117
|
-
'aria-
|
|
118
|
-
'aria-
|
|
119
|
-
'aria-
|
|
121
|
+
'aria-atomic',
|
|
122
|
+
'aria-braillelabel',
|
|
123
|
+
'aria-brailleroledescription',
|
|
124
|
+
'aria-busy',
|
|
125
|
+
'aria-controls',
|
|
126
|
+
'aria-current',
|
|
127
|
+
'aria-describedby',
|
|
128
|
+
'aria-description',
|
|
129
|
+
'aria-details',
|
|
130
|
+
'aria-disabled',
|
|
131
|
+
'aria-dropeffect',
|
|
132
|
+
'aria-errormessage',
|
|
133
|
+
'aria-flowto',
|
|
134
|
+
'aria-grabbed',
|
|
135
|
+
'aria-haspopup',
|
|
136
|
+
'aria-hidden',
|
|
137
|
+
'aria-invalid',
|
|
138
|
+
'aria-keyshortcuts',
|
|
139
|
+
'aria-label',
|
|
140
|
+
'aria-labelledby',
|
|
141
|
+
'aria-live',
|
|
142
|
+
'aria-owns',
|
|
143
|
+
'aria-relevant',
|
|
144
|
+
'aria-roledescription'
|
|
120
145
|
];
|
|
121
146
|
|
|
122
147
|
// Per-role supported (non-global) states/properties. Deliberately
|
|
@@ -126,43 +151,173 @@ function runInPage(ctx) {
|
|
|
126
151
|
const SUPPORTED_ATTRS_BY_ROLE = {
|
|
127
152
|
alertdialog: ['aria-modal'],
|
|
128
153
|
checkbox: ['aria-checked', 'aria-readonly', 'aria-required', 'aria-expanded'],
|
|
129
|
-
columnheader: [
|
|
130
|
-
|
|
154
|
+
columnheader: [
|
|
155
|
+
'aria-sort',
|
|
156
|
+
'aria-colindex',
|
|
157
|
+
'aria-colspan',
|
|
158
|
+
'aria-readonly',
|
|
159
|
+
'aria-required',
|
|
160
|
+
'aria-rowindex',
|
|
161
|
+
'aria-rowspan',
|
|
162
|
+
'aria-selected',
|
|
163
|
+
'aria-expanded'
|
|
164
|
+
],
|
|
165
|
+
combobox: [
|
|
166
|
+
'aria-expanded',
|
|
167
|
+
'aria-autocomplete',
|
|
168
|
+
'aria-readonly',
|
|
169
|
+
'aria-required',
|
|
170
|
+
'aria-activedescendant'
|
|
171
|
+
],
|
|
131
172
|
dialog: ['aria-modal'],
|
|
132
|
-
grid: [
|
|
133
|
-
|
|
173
|
+
grid: [
|
|
174
|
+
'aria-multiselectable',
|
|
175
|
+
'aria-readonly',
|
|
176
|
+
'aria-colcount',
|
|
177
|
+
'aria-rowcount',
|
|
178
|
+
'aria-activedescendant'
|
|
179
|
+
],
|
|
180
|
+
gridcell: [
|
|
181
|
+
'aria-selected',
|
|
182
|
+
'aria-readonly',
|
|
183
|
+
'aria-required',
|
|
184
|
+
'aria-colindex',
|
|
185
|
+
'aria-colspan',
|
|
186
|
+
'aria-rowindex',
|
|
187
|
+
'aria-rowspan',
|
|
188
|
+
'aria-expanded'
|
|
189
|
+
],
|
|
134
190
|
heading: ['aria-level'],
|
|
135
|
-
listbox: [
|
|
191
|
+
listbox: [
|
|
192
|
+
'aria-multiselectable',
|
|
193
|
+
'aria-readonly',
|
|
194
|
+
'aria-required',
|
|
195
|
+
'aria-orientation',
|
|
196
|
+
'aria-expanded',
|
|
197
|
+
'aria-activedescendant'
|
|
198
|
+
],
|
|
136
199
|
listitem: ['aria-level', 'aria-posinset', 'aria-setsize'],
|
|
137
200
|
menu: ['aria-activedescendant', 'aria-orientation'],
|
|
138
201
|
menubar: ['aria-activedescendant', 'aria-orientation'],
|
|
139
|
-
menuitemcheckbox: [
|
|
140
|
-
|
|
202
|
+
menuitemcheckbox: [
|
|
203
|
+
'aria-checked',
|
|
204
|
+
'aria-expanded',
|
|
205
|
+
'aria-readonly',
|
|
206
|
+
'aria-required',
|
|
207
|
+
'aria-posinset',
|
|
208
|
+
'aria-setsize'
|
|
209
|
+
],
|
|
210
|
+
menuitemradio: [
|
|
211
|
+
'aria-checked',
|
|
212
|
+
'aria-expanded',
|
|
213
|
+
'aria-readonly',
|
|
214
|
+
'aria-required',
|
|
215
|
+
'aria-posinset',
|
|
216
|
+
'aria-setsize'
|
|
217
|
+
],
|
|
141
218
|
meter: ['aria-valuenow', 'aria-valuemin', 'aria-valuemax', 'aria-valuetext'],
|
|
142
219
|
option: ['aria-selected', 'aria-checked', 'aria-posinset', 'aria-setsize'],
|
|
143
220
|
progressbar: ['aria-valuenow', 'aria-valuemin', 'aria-valuemax', 'aria-valuetext'],
|
|
144
221
|
radio: ['aria-checked', 'aria-posinset', 'aria-setsize'],
|
|
145
222
|
radiogroup: ['aria-readonly', 'aria-required', 'aria-orientation', 'aria-activedescendant'],
|
|
146
|
-
row: [
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
223
|
+
row: [
|
|
224
|
+
'aria-selected',
|
|
225
|
+
'aria-level',
|
|
226
|
+
'aria-posinset',
|
|
227
|
+
'aria-setsize',
|
|
228
|
+
'aria-colindex',
|
|
229
|
+
'aria-rowindex',
|
|
230
|
+
'aria-expanded',
|
|
231
|
+
'aria-activedescendant'
|
|
232
|
+
],
|
|
233
|
+
rowheader: [
|
|
234
|
+
'aria-sort',
|
|
235
|
+
'aria-colindex',
|
|
236
|
+
'aria-colspan',
|
|
237
|
+
'aria-readonly',
|
|
238
|
+
'aria-required',
|
|
239
|
+
'aria-rowindex',
|
|
240
|
+
'aria-rowspan',
|
|
241
|
+
'aria-selected',
|
|
242
|
+
'aria-expanded'
|
|
243
|
+
],
|
|
244
|
+
scrollbar: [
|
|
245
|
+
'aria-valuenow',
|
|
246
|
+
'aria-valuemin',
|
|
247
|
+
'aria-valuemax',
|
|
248
|
+
'aria-valuetext',
|
|
249
|
+
'aria-orientation',
|
|
250
|
+
'aria-controls'
|
|
251
|
+
],
|
|
252
|
+
searchbox: [
|
|
253
|
+
'aria-activedescendant',
|
|
254
|
+
'aria-autocomplete',
|
|
255
|
+
'aria-multiline',
|
|
256
|
+
'aria-placeholder',
|
|
257
|
+
'aria-readonly',
|
|
258
|
+
'aria-required'
|
|
259
|
+
],
|
|
260
|
+
separator: [
|
|
261
|
+
'aria-valuenow',
|
|
262
|
+
'aria-valuemin',
|
|
263
|
+
'aria-valuemax',
|
|
264
|
+
'aria-valuetext',
|
|
265
|
+
'aria-orientation'
|
|
266
|
+
],
|
|
267
|
+
slider: [
|
|
268
|
+
'aria-valuenow',
|
|
269
|
+
'aria-valuemin',
|
|
270
|
+
'aria-valuemax',
|
|
271
|
+
'aria-valuetext',
|
|
272
|
+
'aria-orientation',
|
|
273
|
+
'aria-readonly'
|
|
274
|
+
],
|
|
275
|
+
spinbutton: [
|
|
276
|
+
'aria-valuenow',
|
|
277
|
+
'aria-valuemin',
|
|
278
|
+
'aria-valuemax',
|
|
279
|
+
'aria-valuetext',
|
|
280
|
+
'aria-readonly',
|
|
281
|
+
'aria-required',
|
|
282
|
+
'aria-activedescendant'
|
|
283
|
+
],
|
|
153
284
|
switch: ['aria-checked', 'aria-expanded', 'aria-readonly', 'aria-required'],
|
|
154
285
|
tab: ['aria-selected', 'aria-expanded', 'aria-posinset', 'aria-setsize'],
|
|
155
286
|
table: ['aria-colcount', 'aria-rowcount'],
|
|
156
287
|
tablist: ['aria-multiselectable', 'aria-orientation', 'aria-level', 'aria-activedescendant'],
|
|
157
|
-
textbox: [
|
|
288
|
+
textbox: [
|
|
289
|
+
'aria-activedescendant',
|
|
290
|
+
'aria-autocomplete',
|
|
291
|
+
'aria-multiline',
|
|
292
|
+
'aria-placeholder',
|
|
293
|
+
'aria-readonly',
|
|
294
|
+
'aria-required'
|
|
295
|
+
],
|
|
158
296
|
toolbar: ['aria-activedescendant', 'aria-orientation'],
|
|
159
297
|
tree: ['aria-multiselectable', 'aria-required', 'aria-orientation', 'aria-activedescendant'],
|
|
160
|
-
treegrid: [
|
|
161
|
-
|
|
298
|
+
treegrid: [
|
|
299
|
+
'aria-multiselectable',
|
|
300
|
+
'aria-readonly',
|
|
301
|
+
'aria-required',
|
|
302
|
+
'aria-orientation',
|
|
303
|
+
'aria-colcount',
|
|
304
|
+
'aria-rowcount',
|
|
305
|
+
'aria-activedescendant'
|
|
306
|
+
],
|
|
307
|
+
treeitem: [
|
|
308
|
+
'aria-checked',
|
|
309
|
+
'aria-selected',
|
|
310
|
+
'aria-expanded',
|
|
311
|
+
'aria-level',
|
|
312
|
+
'aria-posinset',
|
|
313
|
+
'aria-setsize'
|
|
314
|
+
]
|
|
162
315
|
};
|
|
163
316
|
|
|
164
317
|
const globalSet = new Set(GLOBAL_ATTRS);
|
|
165
|
-
const nodes = helpers.queryAllSmart
|
|
318
|
+
const nodes = helpers.queryAllSmart
|
|
319
|
+
? helpers.queryAllSmart('[role]')
|
|
320
|
+
: helpers.queryAll('[role]');
|
|
166
321
|
|
|
167
322
|
const occurrences = [];
|
|
168
323
|
let applicableCount = 0;
|
|
@@ -198,7 +353,7 @@ function runInPage(ctx) {
|
|
|
198
353
|
if (!disallowed || !disallowed.length) continue;
|
|
199
354
|
|
|
200
355
|
const stableSelector = helpers.buildSelector ? helpers.buildSelector(el) : 'html';
|
|
201
|
-
const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) :
|
|
356
|
+
const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : el.outerHTML || '';
|
|
202
357
|
|
|
203
358
|
for (const name of disallowed) {
|
|
204
359
|
occurrences.push({
|
|
@@ -222,7 +377,12 @@ function runInPage(ctx) {
|
|
|
222
377
|
return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
|
|
223
378
|
}
|
|
224
379
|
if (occurrences.length) {
|
|
225
|
-
return {
|
|
380
|
+
return {
|
|
381
|
+
ruleId: rule.ruleId,
|
|
382
|
+
outcome: 'fail',
|
|
383
|
+
severity: rule.defaultSeverity || 'moderate',
|
|
384
|
+
occurrences
|
|
385
|
+
};
|
|
226
386
|
}
|
|
227
387
|
return { ruleId: rule.ruleId, outcome: 'pass', severity: 'minor', occurrences: [] };
|
|
228
388
|
}
|
|
@@ -27,7 +27,8 @@ const id = 'aria-allowed-role';
|
|
|
27
27
|
|
|
28
28
|
const meta = {
|
|
29
29
|
title: 'Explicit role must be permitted for its host element',
|
|
30
|
-
description:
|
|
30
|
+
description:
|
|
31
|
+
'Checks that an explicit role="" attribute is one of the roles the ARIA-in-HTML specification permits for the host element (e.g. role="tab" is not permitted on <nav>).',
|
|
31
32
|
i18n: {
|
|
32
33
|
titleKey: 'ariaAllowedRole_title',
|
|
33
34
|
descriptionKey: 'ariaAllowedRole_description'
|
|
@@ -36,7 +37,13 @@ const meta = {
|
|
|
36
37
|
tags: ['wcag2a', 'wcag412', 'aria', 'structure', 'atomic', 'automatic'],
|
|
37
38
|
wcagSc: ['4.1.2'],
|
|
38
39
|
normativeMappings: [
|
|
39
|
-
{
|
|
40
|
+
{
|
|
41
|
+
standard: 'WCAG',
|
|
42
|
+
version: '2.2',
|
|
43
|
+
requirement: '4.1.2',
|
|
44
|
+
title: 'Name, Role, Value',
|
|
45
|
+
conformanceLevel: 'A'
|
|
46
|
+
}
|
|
40
47
|
],
|
|
41
48
|
defaultSeverity: 'moderate',
|
|
42
49
|
category: 'robust',
|
|
@@ -46,14 +53,16 @@ const meta = {
|
|
|
46
53
|
};
|
|
47
54
|
|
|
48
55
|
function runInPage(ctx) {
|
|
49
|
-
const {
|
|
56
|
+
const { helpers, rule } = ctx;
|
|
50
57
|
|
|
51
58
|
const ariaHelpers = helpers && helpers.aria ? helpers.aria : null;
|
|
52
59
|
if (!ariaHelpers) {
|
|
53
60
|
return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
|
|
54
61
|
}
|
|
55
62
|
|
|
56
|
-
const nodes = helpers.queryAllSmart
|
|
63
|
+
const nodes = helpers.queryAllSmart
|
|
64
|
+
? helpers.queryAllSmart('[role]')
|
|
65
|
+
: helpers.queryAll('[role]');
|
|
57
66
|
|
|
58
67
|
const occurrences = [];
|
|
59
68
|
let applicableCount = 0;
|
|
@@ -72,7 +81,7 @@ function runInPage(ctx) {
|
|
|
72
81
|
if (info.allowed) continue;
|
|
73
82
|
|
|
74
83
|
const stableSelector = helpers.buildSelector ? helpers.buildSelector(el) : 'html';
|
|
75
|
-
const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) :
|
|
84
|
+
const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : el.outerHTML || '';
|
|
76
85
|
const tag = (el.tagName || '').toLowerCase();
|
|
77
86
|
|
|
78
87
|
occurrences.push({
|
|
@@ -95,9 +104,14 @@ function runInPage(ctx) {
|
|
|
95
104
|
return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
|
|
96
105
|
}
|
|
97
106
|
if (occurrences.length) {
|
|
98
|
-
return {
|
|
107
|
+
return {
|
|
108
|
+
ruleId: rule.ruleId,
|
|
109
|
+
outcome: 'fail',
|
|
110
|
+
severity: rule.defaultSeverity || 'moderate',
|
|
111
|
+
occurrences
|
|
112
|
+
};
|
|
99
113
|
}
|
|
100
114
|
return { ruleId: rule.ruleId, outcome: 'pass', severity: 'minor', occurrences: [] };
|
|
101
115
|
}
|
|
102
116
|
|
|
103
|
-
module.exports = { id, meta, runInPage };
|
|
117
|
+
module.exports = { id, meta, runInPage };
|
|
@@ -42,7 +42,13 @@ const meta = {
|
|
|
42
42
|
tags: ['wcag2a', 'wcag412', 'aria', 'structure', 'atomic', 'automatic'],
|
|
43
43
|
wcagSc: ['4.1.2'],
|
|
44
44
|
normativeMappings: [
|
|
45
|
-
{
|
|
45
|
+
{
|
|
46
|
+
standard: 'WCAG',
|
|
47
|
+
version: '2.2',
|
|
48
|
+
requirement: '4.1.2',
|
|
49
|
+
title: 'Name, Role, Value',
|
|
50
|
+
conformanceLevel: 'A'
|
|
51
|
+
}
|
|
46
52
|
],
|
|
47
53
|
defaultSeverity: 'serious',
|
|
48
54
|
category: 'robust',
|
|
@@ -52,9 +58,11 @@ const meta = {
|
|
|
52
58
|
};
|
|
53
59
|
|
|
54
60
|
function runInPage(ctx) {
|
|
55
|
-
const {
|
|
61
|
+
const { helpers, rule } = ctx;
|
|
56
62
|
|
|
57
|
-
function trim(v) {
|
|
63
|
+
function trim(v) {
|
|
64
|
+
return (v == null ? '' : String(v)).trim();
|
|
65
|
+
}
|
|
58
66
|
|
|
59
67
|
function getConservativeSubtreeText(container) {
|
|
60
68
|
// "Name from content" — recurses into descendants and uses each one's
|
|
@@ -68,7 +76,7 @@ function runInPage(ctx) {
|
|
|
68
76
|
const info = helpers.getContentNameInfo(container, ctx);
|
|
69
77
|
return info && info.present ? info.value : '';
|
|
70
78
|
}
|
|
71
|
-
const t =
|
|
79
|
+
const t = container && container.textContent ? String(container.textContent) : '';
|
|
72
80
|
return t.replace(/\s+/g, ' ').trim();
|
|
73
81
|
}
|
|
74
82
|
|
|
@@ -91,21 +99,26 @@ function runInPage(ctx) {
|
|
|
91
99
|
const missing = [];
|
|
92
100
|
|
|
93
101
|
if (brailleLabel) {
|
|
94
|
-
const nameInfo = helpers.getAccessibleNameInfo
|
|
95
|
-
|
|
102
|
+
const nameInfo = helpers.getAccessibleNameInfo
|
|
103
|
+
? helpers.getAccessibleNameInfo(el, ctx)
|
|
104
|
+
: null;
|
|
105
|
+
const programmaticName = trim(
|
|
106
|
+
nameInfo && typeof nameInfo.value === 'string' ? nameInfo.value : ''
|
|
107
|
+
);
|
|
96
108
|
const name = programmaticName || getConservativeSubtreeText(el);
|
|
97
109
|
if (!name) missing.push({ attr: 'aria-braillelabel', requires: 'an accessible name' });
|
|
98
110
|
}
|
|
99
111
|
|
|
100
112
|
if (brailleRoleDesc) {
|
|
101
113
|
const roleDesc = trim(el.getAttribute('aria-roledescription'));
|
|
102
|
-
if (!roleDesc)
|
|
114
|
+
if (!roleDesc)
|
|
115
|
+
missing.push({ attr: 'aria-brailleroledescription', requires: 'aria-roledescription' });
|
|
103
116
|
}
|
|
104
117
|
|
|
105
118
|
if (!missing.length) continue;
|
|
106
119
|
|
|
107
120
|
const stableSelector = helpers.buildSelector ? helpers.buildSelector(el) : 'html';
|
|
108
|
-
const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) :
|
|
121
|
+
const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : el.outerHTML || '';
|
|
109
122
|
const tag = (el.tagName || '').toLowerCase();
|
|
110
123
|
|
|
111
124
|
for (const m of missing) {
|
|
@@ -120,7 +133,11 @@ function runInPage(ctx) {
|
|
|
120
133
|
params: { element: tag, attr: m.attr, requires: m.requires }
|
|
121
134
|
},
|
|
122
135
|
data: {
|
|
123
|
-
details: {
|
|
136
|
+
details: {
|
|
137
|
+
reasonCode: 'BRAILLE_ATTR_WITHOUT_EQUIVALENT',
|
|
138
|
+
attr: m.attr,
|
|
139
|
+
requires: m.requires
|
|
140
|
+
}
|
|
124
141
|
}
|
|
125
142
|
});
|
|
126
143
|
}
|
|
@@ -130,7 +147,12 @@ function runInPage(ctx) {
|
|
|
130
147
|
return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
|
|
131
148
|
}
|
|
132
149
|
if (occurrences.length) {
|
|
133
|
-
return {
|
|
150
|
+
return {
|
|
151
|
+
ruleId: rule.ruleId,
|
|
152
|
+
outcome: 'fail',
|
|
153
|
+
severity: rule.defaultSeverity || 'serious',
|
|
154
|
+
occurrences
|
|
155
|
+
};
|
|
134
156
|
}
|
|
135
157
|
return { ruleId: rule.ruleId, outcome: 'pass', severity: 'minor', occurrences: [] };
|
|
136
158
|
}
|