@surea11y/core 1.2.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 +81 -7
- package/LICENSE +373 -21
- package/README.md +175 -35
- package/bin/surea11y-core.js +20 -0
- package/docs/API_STABILITY.md +27 -1
- package/docs/BINDING_AUTHORS_GUIDE.md +9 -9
- package/docs/CI_INTEGRATIONS.md +103 -0
- package/docs/ENGINE_OPTIONS.md +2 -0
- package/docs/I18N.md +12 -9
- package/docs/INTEGRATION.md +19 -1
- package/docs/LIMITATIONS.md +1 -1
- package/docs/OUTPUT_SCHEMA.md +1 -1
- package/docs/REPORT.md +1 -1
- package/docs/RULE_CATALOG.md +1 -1
- package/docs/SARIF.md +59 -0
- package/package.json +63 -18
- package/src/baseline.js +0 -0
- package/src/checks/automatic/area-alt-present.js +63 -31
- package/src/checks/automatic/aria-allowed-attr.js +204 -80
- package/src/checks/automatic/aria-allowed-role.js +23 -7
- package/src/checks/automatic/aria-braille-equivalent.js +34 -10
- package/src/checks/automatic/aria-conditional-attr.js +32 -14
- package/src/checks/automatic/aria-deprecated-role.js +26 -11
- package/src/checks/automatic/aria-hidden-body.js +48 -23
- package/src/checks/automatic/aria-hidden-focus.js +420 -66
- package/src/checks/automatic/aria-prohibited-attr.js +327 -60
- package/src/checks/automatic/aria-prohibited-children.js +111 -103
- package/src/checks/automatic/aria-required-attr.js +29 -15
- package/src/checks/automatic/aria-required-children.js +44 -24
- package/src/checks/automatic/aria-required-parent.js +64 -35
- package/src/checks/automatic/aria-role-name-present.js +49 -21
- package/src/checks/automatic/aria-roles-valid.js +24 -12
- package/src/checks/automatic/aria-valid-attr-value.js +46 -22
- package/src/checks/automatic/aria-valid-attr.js +19 -5
- package/src/checks/automatic/autocomplete-valid.js +76 -16
- package/src/checks/automatic/avoid-inline-spacing.js +23 -8
- package/src/checks/automatic/binary-control-name-present.js +62 -50
- package/src/checks/automatic/button-name-present.js +54 -24
- package/src/checks/automatic/bypass-blocks-present.js +51 -32
- package/src/checks/automatic/canvas-text-alternative-present.js +59 -26
- package/src/checks/automatic/combobox-name-present.js +40 -45
- package/src/checks/automatic/contrast-computable.js +363 -341
- package/src/checks/automatic/contrast-enhanced.js +489 -466
- package/src/checks/automatic/contrast-minimum.js +488 -465
- package/src/checks/automatic/css-orientation-lock.js +51 -35
- package/src/checks/automatic/definition-list-children-valid.js +46 -25
- package/src/checks/automatic/deprecated-elements-not-used.js +25 -9
- package/src/checks/automatic/dialog-name-present.js +47 -85
- package/src/checks/automatic/dlitem-parent-valid.js +25 -8
- package/src/checks/automatic/duplicate-id-aria.js +28 -9
- package/src/checks/automatic/embed-text-alternative-present.js +88 -35
- package/src/checks/automatic/form-control-programmatic-label-present.js +81 -196
- package/src/checks/automatic/form-control-single-label.js +50 -14
- package/src/checks/automatic/html-xml-lang-mismatch.js +36 -18
- package/src/checks/automatic/iframe-focusable-content.js +265 -22
- package/src/checks/automatic/iframe-name-present.js +33 -9
- package/src/checks/automatic/iframe-title-unique.js +32 -9
- package/src/checks/automatic/img-alt-present.js +54 -52
- package/src/checks/automatic/input-image-alt-present.js +141 -112
- package/src/checks/automatic/label-in-name.js +65 -41
- package/src/checks/automatic/language-page-present.js +111 -109
- package/src/checks/automatic/link-in-text-block.js +61 -19
- package/src/checks/automatic/link-name-present.js +47 -14
- package/src/checks/automatic/list-children-valid.js +40 -33
- package/src/checks/automatic/listbox-name-present.js +41 -19
- package/src/checks/automatic/listitem-parent-valid.js +48 -13
- package/src/checks/automatic/menuitem-name-present.js +41 -61
- package/src/checks/automatic/meta-refresh-no-exceptions.js +32 -11
- package/src/checks/automatic/meta-refresh-timing-absent.js +22 -6
- package/src/checks/automatic/meta-viewport-zoom-enabled.js +26 -7
- package/src/checks/automatic/meter-name-present.js +40 -36
- package/src/checks/automatic/nested-interactive-controls-absent.js +58 -15
- package/src/checks/automatic/object-text-alternative-present.js +93 -39
- package/src/checks/automatic/option-name-present.js +40 -21
- package/src/checks/automatic/page-title-present.js +19 -6
- package/src/checks/automatic/progressbar-name-present.js +49 -44
- package/src/checks/automatic/role-img-alt-present.js +211 -159
- package/src/checks/automatic/searchbox-name-present.js +41 -19
- package/src/checks/automatic/server-side-image-map-absent.js +27 -11
- package/src/checks/automatic/slider-name-present.js +42 -47
- package/src/checks/automatic/spinbutton-name-present.js +41 -19
- package/src/checks/automatic/summary-name-present.js +39 -17
- package/src/checks/automatic/svg-image-text-alternative-present.js +116 -47
- package/src/checks/automatic/svg-text-alternative-present.js +262 -230
- package/src/checks/automatic/tab-name-present.js +39 -60
- package/src/checks/automatic/table-headers-attr-valid.js +27 -10
- package/src/checks/automatic/table-th-has-data-cells.js +24 -8
- package/src/checks/automatic/target-size-minimum.js +123 -48
- package/src/checks/automatic/td-has-header.js +53 -12
- package/src/checks/automatic/textbox-name-present.js +41 -19
- package/src/checks/automatic/tooltip-name-present.js +39 -18
- package/src/checks/automatic/treeitem-name-present.js +40 -21
- package/src/checks/automatic/valid-lang.js +22 -6
- package/src/checks/automatic/video-poster-text-alternative-present.js +81 -36
- package/src/checks/manual/accesskeys-manual.js +17 -6
- package/src/checks/manual/area-alt-decorative-manual.js +194 -193
- package/src/checks/manual/area-alt-quality-manual.js +184 -141
- package/src/checks/manual/aria-checked-state-mismatch-manual.js +48 -34
- package/src/checks/manual/aria-text-manual.js +20 -11
- package/src/checks/manual/canvas-text-alternative-quality-manual.js +151 -114
- package/src/checks/manual/css-hidden-focus.js +375 -169
- package/src/checks/manual/embed-text-alternative-quality-manual.js +178 -162
- package/src/checks/manual/empty-heading-manual.js +41 -24
- package/src/checks/manual/empty-table-header-manual.js +69 -31
- package/src/checks/manual/focus-order-semantics-manual.js +60 -13
- package/src/checks/manual/form-control-programmatic-label-quality-manual.js +209 -246
- package/src/checks/manual/heading-order-manual.js +50 -8
- package/src/checks/manual/identical-links-same-purpose-manual.js +36 -12
- package/src/checks/manual/image-redundant-alt-manual.js +38 -8
- package/src/checks/manual/img-alt-decorative-manual.js +133 -96
- package/src/checks/manual/img-alt-quality-manual.js +178 -127
- package/src/checks/manual/input-image-alt-decorative-manual.js +127 -92
- package/src/checks/manual/input-image-alt-quality-manual.js +127 -92
- package/src/checks/manual/label-title-only-manual.js +44 -28
- package/src/checks/manual/landmark-banner-is-top-level-manual.js +95 -38
- package/src/checks/manual/landmark-contentinfo-is-top-level-manual.js +85 -32
- package/src/checks/manual/landmark-main-is-top-level-manual.js +69 -27
- package/src/checks/manual/landmark-no-duplicate-banner-manual.js +45 -33
- package/src/checks/manual/landmark-no-duplicate-contentinfo-manual.js +43 -31
- package/src/checks/manual/landmark-no-duplicate-main-manual.js +27 -21
- package/src/checks/manual/landmark-one-main-manual.js +38 -43
- package/src/checks/manual/landmark-unique-manual.js +78 -67
- package/src/checks/manual/link-name-quality-manual.js +45 -12
- package/src/checks/manual/media-transcript-present-manual.js +37 -22
- package/src/checks/manual/meta-viewport-large-manual.js +19 -6
- package/src/checks/manual/mouse-only-event-handlers-manual.js +40 -11
- package/src/checks/manual/no-autoplay-audio-manual.js +22 -6
- package/src/checks/manual/object-text-alternative-quality-manual.js +177 -154
- package/src/checks/manual/p-as-heading-manual.js +24 -7
- package/src/checks/manual/page-has-heading-one-manual.js +42 -32
- package/src/checks/manual/page-title-patterns-manual.js +80 -50
- package/src/checks/manual/presentation-role-conflict-manual.js +101 -47
- package/src/checks/manual/region-manual.js +244 -60
- package/src/checks/manual/scope-attr-valid-manual.js +13 -4
- package/src/checks/manual/scrollable-region-focusable-manual.js +39 -11
- package/src/checks/manual/skip-link-manual.js +42 -18
- package/src/checks/manual/svg-text-alternative-quality-manual.js +208 -165
- package/src/checks/manual/tabindex-manual.js +13 -4
- package/src/checks/manual/table-duplicate-name-manual.js +22 -11
- package/src/checks/manual/table-fake-caption-manual.js +48 -10
- package/src/checks/manual/video-caption-manual.js +17 -4
- package/src/checks/manual-review.js +58 -12
- package/src/core.js +41705 -29650
- package/src/index.js +2 -0
- package/src/report.js +109 -47
- package/src/sarif.js +190 -0
- package/surea11y.browser.js +37774 -0
- package/bin/core.js +0 -348
- package/docs/CLI.md +0 -75
- package/src/catalogs/composites.wcag.js +0 -490
- 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 -970
- package/src/core/contrast-helpers.js +0 -1147
- package/src/core/dom-helpers.js +0 -4235
- package/src/core/dom-runner.js +0 -671
- package/src/core/frame-messaging.js +0 -210
- package/src/core/frame-scan.js +0 -178
- package/src/core/rollup-composites.js +0 -135
- package/src/core/rule-meta.js +0 -159
- package/src/coverage/wcag-facets.js +0 -1079
- package/src/coverage/wcag-version-map.js +0 -84
- package/src/i18n/en.js +0 -923
- package/src/i18n/fr.js +0 -844
- package/src/policy/contracts.js +0 -18
- package/src/policy/resolvePolicy.js +0 -55
- package/src/policy/schemas/engine-options.schema.json +0 -103
- package/src/policy/schemas/policy-contract.schema.json +0 -40
|
@@ -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 the reference
|
|
47
|
-
* engine (axe-core) 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 axe-core directly, because axe-core's 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 axe-core 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.
|
|
@@ -79,7 +43,8 @@ const id = 'aria-allowed-attr';
|
|
|
79
43
|
|
|
80
44
|
const meta = {
|
|
81
45
|
title: 'aria-* attributes must be permitted for the element’s role',
|
|
82
|
-
description:
|
|
46
|
+
description:
|
|
47
|
+
'Checks that every recognized aria-* attribute present on an element with an explicit role is either globally supported or supported by that role.',
|
|
83
48
|
i18n: {
|
|
84
49
|
titleKey: 'ariaAllowedAttr_title',
|
|
85
50
|
descriptionKey: 'ariaAllowedAttr_description'
|
|
@@ -88,7 +53,13 @@ const meta = {
|
|
|
88
53
|
tags: ['wcag2a', 'wcag412', 'aria', 'structure', 'atomic', 'automatic'],
|
|
89
54
|
wcagSc: ['4.1.2'],
|
|
90
55
|
normativeMappings: [
|
|
91
|
-
{
|
|
56
|
+
{
|
|
57
|
+
standard: 'WCAG',
|
|
58
|
+
version: '2.2',
|
|
59
|
+
requirement: '4.1.2',
|
|
60
|
+
title: 'Name, Role, Value',
|
|
61
|
+
conformanceLevel: 'A'
|
|
62
|
+
}
|
|
92
63
|
],
|
|
93
64
|
defaultSeverity: 'moderate',
|
|
94
65
|
category: 'robust',
|
|
@@ -98,7 +69,7 @@ const meta = {
|
|
|
98
69
|
};
|
|
99
70
|
|
|
100
71
|
function runInPage(ctx) {
|
|
101
|
-
const {
|
|
72
|
+
const { helpers, rule } = ctx;
|
|
102
73
|
|
|
103
74
|
const ariaHelpers = helpers && helpers.aria ? helpers.aria : null;
|
|
104
75
|
if (!ariaHelpers) {
|
|
@@ -111,12 +82,30 @@ function runInPage(ctx) {
|
|
|
111
82
|
// build inlines only this function's own source text — see
|
|
112
83
|
// scripts/build-core.js header ("runInPage MUST be self-contained").
|
|
113
84
|
const GLOBAL_ATTRS = [
|
|
114
|
-
'aria-atomic',
|
|
115
|
-
'aria-
|
|
116
|
-
'aria-
|
|
117
|
-
'aria-
|
|
118
|
-
'aria-
|
|
119
|
-
'aria-
|
|
85
|
+
'aria-atomic',
|
|
86
|
+
'aria-braillelabel',
|
|
87
|
+
'aria-brailleroledescription',
|
|
88
|
+
'aria-busy',
|
|
89
|
+
'aria-controls',
|
|
90
|
+
'aria-current',
|
|
91
|
+
'aria-describedby',
|
|
92
|
+
'aria-description',
|
|
93
|
+
'aria-details',
|
|
94
|
+
'aria-disabled',
|
|
95
|
+
'aria-dropeffect',
|
|
96
|
+
'aria-errormessage',
|
|
97
|
+
'aria-flowto',
|
|
98
|
+
'aria-grabbed',
|
|
99
|
+
'aria-haspopup',
|
|
100
|
+
'aria-hidden',
|
|
101
|
+
'aria-invalid',
|
|
102
|
+
'aria-keyshortcuts',
|
|
103
|
+
'aria-label',
|
|
104
|
+
'aria-labelledby',
|
|
105
|
+
'aria-live',
|
|
106
|
+
'aria-owns',
|
|
107
|
+
'aria-relevant',
|
|
108
|
+
'aria-roledescription'
|
|
120
109
|
];
|
|
121
110
|
|
|
122
111
|
// Per-role supported (non-global) states/properties. Deliberately
|
|
@@ -126,43 +115,173 @@ function runInPage(ctx) {
|
|
|
126
115
|
const SUPPORTED_ATTRS_BY_ROLE = {
|
|
127
116
|
alertdialog: ['aria-modal'],
|
|
128
117
|
checkbox: ['aria-checked', 'aria-readonly', 'aria-required', 'aria-expanded'],
|
|
129
|
-
columnheader: [
|
|
130
|
-
|
|
118
|
+
columnheader: [
|
|
119
|
+
'aria-sort',
|
|
120
|
+
'aria-colindex',
|
|
121
|
+
'aria-colspan',
|
|
122
|
+
'aria-readonly',
|
|
123
|
+
'aria-required',
|
|
124
|
+
'aria-rowindex',
|
|
125
|
+
'aria-rowspan',
|
|
126
|
+
'aria-selected',
|
|
127
|
+
'aria-expanded'
|
|
128
|
+
],
|
|
129
|
+
combobox: [
|
|
130
|
+
'aria-expanded',
|
|
131
|
+
'aria-autocomplete',
|
|
132
|
+
'aria-readonly',
|
|
133
|
+
'aria-required',
|
|
134
|
+
'aria-activedescendant'
|
|
135
|
+
],
|
|
131
136
|
dialog: ['aria-modal'],
|
|
132
|
-
grid: [
|
|
133
|
-
|
|
137
|
+
grid: [
|
|
138
|
+
'aria-multiselectable',
|
|
139
|
+
'aria-readonly',
|
|
140
|
+
'aria-colcount',
|
|
141
|
+
'aria-rowcount',
|
|
142
|
+
'aria-activedescendant'
|
|
143
|
+
],
|
|
144
|
+
gridcell: [
|
|
145
|
+
'aria-selected',
|
|
146
|
+
'aria-readonly',
|
|
147
|
+
'aria-required',
|
|
148
|
+
'aria-colindex',
|
|
149
|
+
'aria-colspan',
|
|
150
|
+
'aria-rowindex',
|
|
151
|
+
'aria-rowspan',
|
|
152
|
+
'aria-expanded'
|
|
153
|
+
],
|
|
134
154
|
heading: ['aria-level'],
|
|
135
|
-
listbox: [
|
|
155
|
+
listbox: [
|
|
156
|
+
'aria-multiselectable',
|
|
157
|
+
'aria-readonly',
|
|
158
|
+
'aria-required',
|
|
159
|
+
'aria-orientation',
|
|
160
|
+
'aria-expanded',
|
|
161
|
+
'aria-activedescendant'
|
|
162
|
+
],
|
|
136
163
|
listitem: ['aria-level', 'aria-posinset', 'aria-setsize'],
|
|
137
164
|
menu: ['aria-activedescendant', 'aria-orientation'],
|
|
138
165
|
menubar: ['aria-activedescendant', 'aria-orientation'],
|
|
139
|
-
menuitemcheckbox: [
|
|
140
|
-
|
|
166
|
+
menuitemcheckbox: [
|
|
167
|
+
'aria-checked',
|
|
168
|
+
'aria-expanded',
|
|
169
|
+
'aria-readonly',
|
|
170
|
+
'aria-required',
|
|
171
|
+
'aria-posinset',
|
|
172
|
+
'aria-setsize'
|
|
173
|
+
],
|
|
174
|
+
menuitemradio: [
|
|
175
|
+
'aria-checked',
|
|
176
|
+
'aria-expanded',
|
|
177
|
+
'aria-readonly',
|
|
178
|
+
'aria-required',
|
|
179
|
+
'aria-posinset',
|
|
180
|
+
'aria-setsize'
|
|
181
|
+
],
|
|
141
182
|
meter: ['aria-valuenow', 'aria-valuemin', 'aria-valuemax', 'aria-valuetext'],
|
|
142
183
|
option: ['aria-selected', 'aria-checked', 'aria-posinset', 'aria-setsize'],
|
|
143
184
|
progressbar: ['aria-valuenow', 'aria-valuemin', 'aria-valuemax', 'aria-valuetext'],
|
|
144
185
|
radio: ['aria-checked', 'aria-posinset', 'aria-setsize'],
|
|
145
186
|
radiogroup: ['aria-readonly', 'aria-required', 'aria-orientation', 'aria-activedescendant'],
|
|
146
|
-
row: [
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
187
|
+
row: [
|
|
188
|
+
'aria-selected',
|
|
189
|
+
'aria-level',
|
|
190
|
+
'aria-posinset',
|
|
191
|
+
'aria-setsize',
|
|
192
|
+
'aria-colindex',
|
|
193
|
+
'aria-rowindex',
|
|
194
|
+
'aria-expanded',
|
|
195
|
+
'aria-activedescendant'
|
|
196
|
+
],
|
|
197
|
+
rowheader: [
|
|
198
|
+
'aria-sort',
|
|
199
|
+
'aria-colindex',
|
|
200
|
+
'aria-colspan',
|
|
201
|
+
'aria-readonly',
|
|
202
|
+
'aria-required',
|
|
203
|
+
'aria-rowindex',
|
|
204
|
+
'aria-rowspan',
|
|
205
|
+
'aria-selected',
|
|
206
|
+
'aria-expanded'
|
|
207
|
+
],
|
|
208
|
+
scrollbar: [
|
|
209
|
+
'aria-valuenow',
|
|
210
|
+
'aria-valuemin',
|
|
211
|
+
'aria-valuemax',
|
|
212
|
+
'aria-valuetext',
|
|
213
|
+
'aria-orientation',
|
|
214
|
+
'aria-controls'
|
|
215
|
+
],
|
|
216
|
+
searchbox: [
|
|
217
|
+
'aria-activedescendant',
|
|
218
|
+
'aria-autocomplete',
|
|
219
|
+
'aria-multiline',
|
|
220
|
+
'aria-placeholder',
|
|
221
|
+
'aria-readonly',
|
|
222
|
+
'aria-required'
|
|
223
|
+
],
|
|
224
|
+
separator: [
|
|
225
|
+
'aria-valuenow',
|
|
226
|
+
'aria-valuemin',
|
|
227
|
+
'aria-valuemax',
|
|
228
|
+
'aria-valuetext',
|
|
229
|
+
'aria-orientation'
|
|
230
|
+
],
|
|
231
|
+
slider: [
|
|
232
|
+
'aria-valuenow',
|
|
233
|
+
'aria-valuemin',
|
|
234
|
+
'aria-valuemax',
|
|
235
|
+
'aria-valuetext',
|
|
236
|
+
'aria-orientation',
|
|
237
|
+
'aria-readonly'
|
|
238
|
+
],
|
|
239
|
+
spinbutton: [
|
|
240
|
+
'aria-valuenow',
|
|
241
|
+
'aria-valuemin',
|
|
242
|
+
'aria-valuemax',
|
|
243
|
+
'aria-valuetext',
|
|
244
|
+
'aria-readonly',
|
|
245
|
+
'aria-required',
|
|
246
|
+
'aria-activedescendant'
|
|
247
|
+
],
|
|
153
248
|
switch: ['aria-checked', 'aria-expanded', 'aria-readonly', 'aria-required'],
|
|
154
249
|
tab: ['aria-selected', 'aria-expanded', 'aria-posinset', 'aria-setsize'],
|
|
155
250
|
table: ['aria-colcount', 'aria-rowcount'],
|
|
156
251
|
tablist: ['aria-multiselectable', 'aria-orientation', 'aria-level', 'aria-activedescendant'],
|
|
157
|
-
textbox: [
|
|
252
|
+
textbox: [
|
|
253
|
+
'aria-activedescendant',
|
|
254
|
+
'aria-autocomplete',
|
|
255
|
+
'aria-multiline',
|
|
256
|
+
'aria-placeholder',
|
|
257
|
+
'aria-readonly',
|
|
258
|
+
'aria-required'
|
|
259
|
+
],
|
|
158
260
|
toolbar: ['aria-activedescendant', 'aria-orientation'],
|
|
159
261
|
tree: ['aria-multiselectable', 'aria-required', 'aria-orientation', 'aria-activedescendant'],
|
|
160
|
-
treegrid: [
|
|
161
|
-
|
|
262
|
+
treegrid: [
|
|
263
|
+
'aria-multiselectable',
|
|
264
|
+
'aria-readonly',
|
|
265
|
+
'aria-required',
|
|
266
|
+
'aria-orientation',
|
|
267
|
+
'aria-colcount',
|
|
268
|
+
'aria-rowcount',
|
|
269
|
+
'aria-activedescendant'
|
|
270
|
+
],
|
|
271
|
+
treeitem: [
|
|
272
|
+
'aria-checked',
|
|
273
|
+
'aria-selected',
|
|
274
|
+
'aria-expanded',
|
|
275
|
+
'aria-level',
|
|
276
|
+
'aria-posinset',
|
|
277
|
+
'aria-setsize'
|
|
278
|
+
]
|
|
162
279
|
};
|
|
163
280
|
|
|
164
281
|
const globalSet = new Set(GLOBAL_ATTRS);
|
|
165
|
-
const nodes = helpers.queryAllSmart
|
|
282
|
+
const nodes = helpers.queryAllSmart
|
|
283
|
+
? helpers.queryAllSmart('[role]')
|
|
284
|
+
: helpers.queryAll('[role]');
|
|
166
285
|
|
|
167
286
|
const occurrences = [];
|
|
168
287
|
let applicableCount = 0;
|
|
@@ -198,7 +317,7 @@ function runInPage(ctx) {
|
|
|
198
317
|
if (!disallowed || !disallowed.length) continue;
|
|
199
318
|
|
|
200
319
|
const stableSelector = helpers.buildSelector ? helpers.buildSelector(el) : 'html';
|
|
201
|
-
const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) :
|
|
320
|
+
const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : el.outerHTML || '';
|
|
202
321
|
|
|
203
322
|
for (const name of disallowed) {
|
|
204
323
|
occurrences.push({
|
|
@@ -222,7 +341,12 @@ function runInPage(ctx) {
|
|
|
222
341
|
return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
|
|
223
342
|
}
|
|
224
343
|
if (occurrences.length) {
|
|
225
|
-
return {
|
|
344
|
+
return {
|
|
345
|
+
ruleId: rule.ruleId,
|
|
346
|
+
outcome: 'fail',
|
|
347
|
+
severity: rule.defaultSeverity || 'moderate',
|
|
348
|
+
occurrences
|
|
349
|
+
};
|
|
226
350
|
}
|
|
227
351
|
return { ruleId: rule.ruleId, outcome: 'pass', severity: 'minor', occurrences: [] };
|
|
228
352
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/* SPDX-License-Identifier: MPL-2.0 */
|
|
2
|
+
|
|
1
3
|
'use strict';
|
|
2
4
|
|
|
3
5
|
/**
|
|
@@ -27,7 +29,8 @@ const id = 'aria-allowed-role';
|
|
|
27
29
|
|
|
28
30
|
const meta = {
|
|
29
31
|
title: 'Explicit role must be permitted for its host element',
|
|
30
|
-
description:
|
|
32
|
+
description:
|
|
33
|
+
'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
34
|
i18n: {
|
|
32
35
|
titleKey: 'ariaAllowedRole_title',
|
|
33
36
|
descriptionKey: 'ariaAllowedRole_description'
|
|
@@ -36,7 +39,13 @@ const meta = {
|
|
|
36
39
|
tags: ['wcag2a', 'wcag412', 'aria', 'structure', 'atomic', 'automatic'],
|
|
37
40
|
wcagSc: ['4.1.2'],
|
|
38
41
|
normativeMappings: [
|
|
39
|
-
{
|
|
42
|
+
{
|
|
43
|
+
standard: 'WCAG',
|
|
44
|
+
version: '2.2',
|
|
45
|
+
requirement: '4.1.2',
|
|
46
|
+
title: 'Name, Role, Value',
|
|
47
|
+
conformanceLevel: 'A'
|
|
48
|
+
}
|
|
40
49
|
],
|
|
41
50
|
defaultSeverity: 'moderate',
|
|
42
51
|
category: 'robust',
|
|
@@ -46,14 +55,16 @@ const meta = {
|
|
|
46
55
|
};
|
|
47
56
|
|
|
48
57
|
function runInPage(ctx) {
|
|
49
|
-
const {
|
|
58
|
+
const { helpers, rule } = ctx;
|
|
50
59
|
|
|
51
60
|
const ariaHelpers = helpers && helpers.aria ? helpers.aria : null;
|
|
52
61
|
if (!ariaHelpers) {
|
|
53
62
|
return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
|
|
54
63
|
}
|
|
55
64
|
|
|
56
|
-
const nodes = helpers.queryAllSmart
|
|
65
|
+
const nodes = helpers.queryAllSmart
|
|
66
|
+
? helpers.queryAllSmart('[role]')
|
|
67
|
+
: helpers.queryAll('[role]');
|
|
57
68
|
|
|
58
69
|
const occurrences = [];
|
|
59
70
|
let applicableCount = 0;
|
|
@@ -72,7 +83,7 @@ function runInPage(ctx) {
|
|
|
72
83
|
if (info.allowed) continue;
|
|
73
84
|
|
|
74
85
|
const stableSelector = helpers.buildSelector ? helpers.buildSelector(el) : 'html';
|
|
75
|
-
const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) :
|
|
86
|
+
const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : el.outerHTML || '';
|
|
76
87
|
const tag = (el.tagName || '').toLowerCase();
|
|
77
88
|
|
|
78
89
|
occurrences.push({
|
|
@@ -95,9 +106,14 @@ function runInPage(ctx) {
|
|
|
95
106
|
return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
|
|
96
107
|
}
|
|
97
108
|
if (occurrences.length) {
|
|
98
|
-
return {
|
|
109
|
+
return {
|
|
110
|
+
ruleId: rule.ruleId,
|
|
111
|
+
outcome: 'fail',
|
|
112
|
+
severity: rule.defaultSeverity || 'moderate',
|
|
113
|
+
occurrences
|
|
114
|
+
};
|
|
99
115
|
}
|
|
100
116
|
return { ruleId: rule.ruleId, outcome: 'pass', severity: 'minor', occurrences: [] };
|
|
101
117
|
}
|
|
102
118
|
|
|
103
|
-
module.exports = { id, meta, runInPage };
|
|
119
|
+
module.exports = { id, meta, runInPage };
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/* SPDX-License-Identifier: MPL-2.0 */
|
|
2
|
+
|
|
1
3
|
'use strict';
|
|
2
4
|
|
|
3
5
|
/**
|
|
@@ -42,7 +44,13 @@ const meta = {
|
|
|
42
44
|
tags: ['wcag2a', 'wcag412', 'aria', 'structure', 'atomic', 'automatic'],
|
|
43
45
|
wcagSc: ['4.1.2'],
|
|
44
46
|
normativeMappings: [
|
|
45
|
-
{
|
|
47
|
+
{
|
|
48
|
+
standard: 'WCAG',
|
|
49
|
+
version: '2.2',
|
|
50
|
+
requirement: '4.1.2',
|
|
51
|
+
title: 'Name, Role, Value',
|
|
52
|
+
conformanceLevel: 'A'
|
|
53
|
+
}
|
|
46
54
|
],
|
|
47
55
|
defaultSeverity: 'serious',
|
|
48
56
|
category: 'robust',
|
|
@@ -52,9 +60,11 @@ const meta = {
|
|
|
52
60
|
};
|
|
53
61
|
|
|
54
62
|
function runInPage(ctx) {
|
|
55
|
-
const {
|
|
63
|
+
const { helpers, rule } = ctx;
|
|
56
64
|
|
|
57
|
-
function trim(v) {
|
|
65
|
+
function trim(v) {
|
|
66
|
+
return (v == null ? '' : String(v)).trim();
|
|
67
|
+
}
|
|
58
68
|
|
|
59
69
|
function getConservativeSubtreeText(container) {
|
|
60
70
|
// "Name from content" — recurses into descendants and uses each one's
|
|
@@ -68,7 +78,7 @@ function runInPage(ctx) {
|
|
|
68
78
|
const info = helpers.getContentNameInfo(container, ctx);
|
|
69
79
|
return info && info.present ? info.value : '';
|
|
70
80
|
}
|
|
71
|
-
const t =
|
|
81
|
+
const t = container && container.textContent ? String(container.textContent) : '';
|
|
72
82
|
return t.replace(/\s+/g, ' ').trim();
|
|
73
83
|
}
|
|
74
84
|
|
|
@@ -91,21 +101,26 @@ function runInPage(ctx) {
|
|
|
91
101
|
const missing = [];
|
|
92
102
|
|
|
93
103
|
if (brailleLabel) {
|
|
94
|
-
const nameInfo = helpers.getAccessibleNameInfo
|
|
95
|
-
|
|
104
|
+
const nameInfo = helpers.getAccessibleNameInfo
|
|
105
|
+
? helpers.getAccessibleNameInfo(el, ctx)
|
|
106
|
+
: null;
|
|
107
|
+
const programmaticName = trim(
|
|
108
|
+
nameInfo && typeof nameInfo.value === 'string' ? nameInfo.value : ''
|
|
109
|
+
);
|
|
96
110
|
const name = programmaticName || getConservativeSubtreeText(el);
|
|
97
111
|
if (!name) missing.push({ attr: 'aria-braillelabel', requires: 'an accessible name' });
|
|
98
112
|
}
|
|
99
113
|
|
|
100
114
|
if (brailleRoleDesc) {
|
|
101
115
|
const roleDesc = trim(el.getAttribute('aria-roledescription'));
|
|
102
|
-
if (!roleDesc)
|
|
116
|
+
if (!roleDesc)
|
|
117
|
+
missing.push({ attr: 'aria-brailleroledescription', requires: 'aria-roledescription' });
|
|
103
118
|
}
|
|
104
119
|
|
|
105
120
|
if (!missing.length) continue;
|
|
106
121
|
|
|
107
122
|
const stableSelector = helpers.buildSelector ? helpers.buildSelector(el) : 'html';
|
|
108
|
-
const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) :
|
|
123
|
+
const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : el.outerHTML || '';
|
|
109
124
|
const tag = (el.tagName || '').toLowerCase();
|
|
110
125
|
|
|
111
126
|
for (const m of missing) {
|
|
@@ -120,7 +135,11 @@ function runInPage(ctx) {
|
|
|
120
135
|
params: { element: tag, attr: m.attr, requires: m.requires }
|
|
121
136
|
},
|
|
122
137
|
data: {
|
|
123
|
-
details: {
|
|
138
|
+
details: {
|
|
139
|
+
reasonCode: 'BRAILLE_ATTR_WITHOUT_EQUIVALENT',
|
|
140
|
+
attr: m.attr,
|
|
141
|
+
requires: m.requires
|
|
142
|
+
}
|
|
124
143
|
}
|
|
125
144
|
});
|
|
126
145
|
}
|
|
@@ -130,7 +149,12 @@ function runInPage(ctx) {
|
|
|
130
149
|
return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
|
|
131
150
|
}
|
|
132
151
|
if (occurrences.length) {
|
|
133
|
-
return {
|
|
152
|
+
return {
|
|
153
|
+
ruleId: rule.ruleId,
|
|
154
|
+
outcome: 'fail',
|
|
155
|
+
severity: rule.defaultSeverity || 'serious',
|
|
156
|
+
occurrences
|
|
157
|
+
};
|
|
134
158
|
}
|
|
135
159
|
return { ruleId: rule.ruleId, outcome: 'pass', severity: 'minor', occurrences: [] };
|
|
136
160
|
}
|
|
@@ -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
|
|
@@ -43,7 +44,13 @@ const meta = {
|
|
|
43
44
|
tags: ['wcag2a', 'wcag412', 'aria', 'structure', 'atomic', 'automatic'],
|
|
44
45
|
wcagSc: ['4.1.2'],
|
|
45
46
|
normativeMappings: [
|
|
46
|
-
{
|
|
47
|
+
{
|
|
48
|
+
standard: 'WCAG',
|
|
49
|
+
version: '2.2',
|
|
50
|
+
requirement: '4.1.2',
|
|
51
|
+
title: 'Name, Role, Value',
|
|
52
|
+
conformanceLevel: 'A'
|
|
53
|
+
}
|
|
47
54
|
],
|
|
48
55
|
defaultSeverity: 'serious',
|
|
49
56
|
category: 'robust',
|
|
@@ -53,9 +60,11 @@ const meta = {
|
|
|
53
60
|
};
|
|
54
61
|
|
|
55
62
|
function runInPage(ctx) {
|
|
56
|
-
const {
|
|
63
|
+
const { helpers, rule } = ctx;
|
|
57
64
|
|
|
58
|
-
function trim(v) {
|
|
65
|
+
function trim(v) {
|
|
66
|
+
return (v == null ? '' : String(v)).trim();
|
|
67
|
+
}
|
|
59
68
|
|
|
60
69
|
const TRUTHY_INVALID_VALUES = new Set(['true', 'grammar', 'spelling']);
|
|
61
70
|
|
|
@@ -78,13 +87,14 @@ function runInPage(ctx) {
|
|
|
78
87
|
if (TRUTHY_INVALID_VALUES.has(invalidValue)) continue;
|
|
79
88
|
|
|
80
89
|
const stableSelector = helpers.buildSelector ? helpers.buildSelector(el) : 'html';
|
|
81
|
-
const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) :
|
|
90
|
+
const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : el.outerHTML || '';
|
|
82
91
|
const tag = (el.tagName || '').toLowerCase();
|
|
83
92
|
|
|
84
93
|
occurrences.push({
|
|
85
94
|
selector: stableSelector,
|
|
86
95
|
html,
|
|
87
|
-
summary:
|
|
96
|
+
summary:
|
|
97
|
+
'This element has aria-errormessage but aria-invalid is missing or "false", so the error message is not exposed.',
|
|
88
98
|
hint: 'Set aria-invalid to "true" (or "grammar"/"spelling") whenever aria-errormessage should be exposed to assistive technology.',
|
|
89
99
|
i18n: {
|
|
90
100
|
summaryKey: 'ariaConditionalAttr_summary_fail',
|
|
@@ -92,7 +102,10 @@ function runInPage(ctx) {
|
|
|
92
102
|
params: { element: tag, ariaInvalid: invalidValue || '(absent)' }
|
|
93
103
|
},
|
|
94
104
|
data: {
|
|
95
|
-
details: {
|
|
105
|
+
details: {
|
|
106
|
+
reasonCode: 'ARIA_ERRORMESSAGE_WITHOUT_TRUTHY_INVALID',
|
|
107
|
+
ariaInvalid: invalidValue
|
|
108
|
+
}
|
|
96
109
|
}
|
|
97
110
|
});
|
|
98
111
|
}
|
|
@@ -101,7 +114,12 @@ function runInPage(ctx) {
|
|
|
101
114
|
return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
|
|
102
115
|
}
|
|
103
116
|
if (occurrences.length) {
|
|
104
|
-
return {
|
|
117
|
+
return {
|
|
118
|
+
ruleId: rule.ruleId,
|
|
119
|
+
outcome: 'fail',
|
|
120
|
+
severity: rule.defaultSeverity || 'serious',
|
|
121
|
+
occurrences
|
|
122
|
+
};
|
|
105
123
|
}
|
|
106
124
|
return { ruleId: rule.ruleId, outcome: 'pass', severity: 'minor', occurrences: [] };
|
|
107
125
|
}
|