@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
|
/**
|
|
@@ -37,7 +39,13 @@ const meta = {
|
|
|
37
39
|
tags: ['wcag2a', 'wcag111', 'media', 'video', 'atomic', 'automatic'],
|
|
38
40
|
wcagSc: ['1.1.1'],
|
|
39
41
|
normativeMappings: [
|
|
40
|
-
{
|
|
42
|
+
{
|
|
43
|
+
standard: 'WCAG',
|
|
44
|
+
version: '2.2',
|
|
45
|
+
requirement: '1.1.1',
|
|
46
|
+
title: 'Non-text Content',
|
|
47
|
+
conformanceLevel: 'A'
|
|
48
|
+
}
|
|
41
49
|
],
|
|
42
50
|
defaultSeverity: 'serious',
|
|
43
51
|
category: 'perceivable',
|
|
@@ -54,32 +62,28 @@ function runInPage(ctx) {
|
|
|
54
62
|
const { document, root, helpers, rule } = ctx;
|
|
55
63
|
const safeRoot = root || document;
|
|
56
64
|
|
|
57
|
-
const reportOccurrence =
|
|
58
|
-
|
|
59
|
-
: null;
|
|
65
|
+
const reportOccurrence =
|
|
66
|
+
helpers && typeof helpers.reportOccurrence === 'function' ? helpers.reportOccurrence : null;
|
|
60
67
|
|
|
61
|
-
const queryAllSmart =
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
: null;
|
|
68
|
+
const queryAllSmart =
|
|
69
|
+
helpers && typeof helpers.queryAllSmart === 'function' ? helpers.queryAllSmart : null;
|
|
70
|
+
const queryAll = helpers && typeof helpers.queryAll === 'function' ? helpers.queryAll : null;
|
|
65
71
|
|
|
66
|
-
const getEligibilityInfo =
|
|
67
|
-
? helpers.getEligibilityInfo
|
|
68
|
-
: null;
|
|
72
|
+
const getEligibilityInfo =
|
|
73
|
+
helpers && typeof helpers.getEligibilityInfo === 'function' ? helpers.getEligibilityInfo : null;
|
|
69
74
|
|
|
70
|
-
const isAccTreeEligible =
|
|
71
|
-
? helpers.isAccTreeEligible
|
|
72
|
-
: null;
|
|
75
|
+
const isAccTreeEligible =
|
|
76
|
+
helpers && typeof helpers.isAccTreeEligible === 'function' ? helpers.isAccTreeEligible : null;
|
|
73
77
|
|
|
74
|
-
const isFocusableInfo =
|
|
75
|
-
? helpers.getFocusableInfo
|
|
76
|
-
: null;
|
|
78
|
+
const isFocusableInfo =
|
|
79
|
+
helpers && typeof helpers.getFocusableInfo === 'function' ? helpers.getFocusableInfo : null;
|
|
77
80
|
|
|
78
|
-
const getAriaNameInfo =
|
|
79
|
-
? helpers.getAriaNameInfo
|
|
80
|
-
: null;
|
|
81
|
+
const getAriaNameInfo =
|
|
82
|
+
helpers && typeof helpers.getAriaNameInfo === 'function' ? helpers.getAriaNameInfo : null;
|
|
81
83
|
|
|
82
|
-
function trim(v) {
|
|
84
|
+
function trim(v) {
|
|
85
|
+
return (v == null ? '' : String(v)).trim();
|
|
86
|
+
}
|
|
83
87
|
|
|
84
88
|
function computeNameInfo(el) {
|
|
85
89
|
// <video> is not a labelable element (no browser computes an accessible
|
|
@@ -89,11 +93,22 @@ function runInPage(ctx) {
|
|
|
89
93
|
let aria = null;
|
|
90
94
|
|
|
91
95
|
if (getAriaNameInfo) {
|
|
92
|
-
aria = (() => {
|
|
96
|
+
aria = (() => {
|
|
97
|
+
try {
|
|
98
|
+
return getAriaNameInfo(el, ctx);
|
|
99
|
+
} catch {
|
|
100
|
+
return null;
|
|
101
|
+
}
|
|
102
|
+
})();
|
|
93
103
|
}
|
|
94
104
|
|
|
95
105
|
if (aria && aria.present && trim(aria.value)) {
|
|
96
|
-
return {
|
|
106
|
+
return {
|
|
107
|
+
present: true,
|
|
108
|
+
value: trim(aria.value),
|
|
109
|
+
mechanism: aria.mechanism || 'aria',
|
|
110
|
+
flags: (aria.flags || []).slice(0)
|
|
111
|
+
};
|
|
97
112
|
}
|
|
98
113
|
|
|
99
114
|
const title = trim(el.getAttribute && el.getAttribute('title'));
|
|
@@ -113,14 +128,22 @@ function runInPage(ctx) {
|
|
|
113
128
|
try {
|
|
114
129
|
if (queryAllSmart) return Array.from(queryAllSmart('video') || []);
|
|
115
130
|
if (queryAll) return Array.from(queryAll('video') || []);
|
|
116
|
-
return safeRoot && safeRoot.querySelectorAll
|
|
131
|
+
return safeRoot && safeRoot.querySelectorAll
|
|
132
|
+
? Array.from(safeRoot.querySelectorAll('video'))
|
|
133
|
+
: [];
|
|
117
134
|
} catch {
|
|
118
|
-
try {
|
|
119
|
-
|
|
135
|
+
try {
|
|
136
|
+
return safeRoot && safeRoot.querySelectorAll
|
|
137
|
+
? Array.from(safeRoot.querySelectorAll('video'))
|
|
138
|
+
: [];
|
|
139
|
+
} catch {
|
|
140
|
+
return [];
|
|
141
|
+
}
|
|
120
142
|
}
|
|
121
143
|
})();
|
|
122
144
|
|
|
123
|
-
if (!videos.length)
|
|
145
|
+
if (!videos.length)
|
|
146
|
+
return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
|
|
124
147
|
|
|
125
148
|
const occurrences = [];
|
|
126
149
|
let applicableCount = 0;
|
|
@@ -134,7 +157,11 @@ function runInPage(ctx) {
|
|
|
134
157
|
// Eligibility: only elements exposed to AT (with helper exceptions)
|
|
135
158
|
if (isAccTreeEligible) {
|
|
136
159
|
const elig = (() => {
|
|
137
|
-
try {
|
|
160
|
+
try {
|
|
161
|
+
return isAccTreeEligible(el, ctx);
|
|
162
|
+
} catch {
|
|
163
|
+
return { eligible: true, reasons: [] };
|
|
164
|
+
}
|
|
138
165
|
})();
|
|
139
166
|
if (elig && elig.eligible === false) continue;
|
|
140
167
|
}
|
|
@@ -142,13 +169,20 @@ function runInPage(ctx) {
|
|
|
142
169
|
// Role presentation/none excluded ONLY if not focusable (mirrors img behavior)
|
|
143
170
|
const role = trim(el.getAttribute('role')).toLowerCase();
|
|
144
171
|
if (role === 'presentation' || role === 'none') {
|
|
145
|
-
let focusable
|
|
172
|
+
let focusable;
|
|
146
173
|
if (isFocusableInfo) {
|
|
147
|
-
const fi = (() => {
|
|
174
|
+
const fi = (() => {
|
|
175
|
+
try {
|
|
176
|
+
return isFocusableInfo(el, ctx);
|
|
177
|
+
} catch {
|
|
178
|
+
return null;
|
|
179
|
+
}
|
|
180
|
+
})();
|
|
148
181
|
focusable = !!(fi && fi.focusable);
|
|
149
182
|
} else {
|
|
150
183
|
const tabindex = el.getAttribute('tabindex');
|
|
151
|
-
focusable =
|
|
184
|
+
focusable =
|
|
185
|
+
tabindex != null && trim(tabindex) !== '' && !Number.isNaN(Number(trim(tabindex)));
|
|
152
186
|
}
|
|
153
187
|
if (!focusable) continue;
|
|
154
188
|
}
|
|
@@ -160,12 +194,16 @@ function runInPage(ctx) {
|
|
|
160
194
|
|
|
161
195
|
let eligInfo = null;
|
|
162
196
|
if (getEligibilityInfo) {
|
|
163
|
-
try {
|
|
197
|
+
try {
|
|
198
|
+
eligInfo = getEligibilityInfo(el, ctx, { targetSet: 'acc' });
|
|
199
|
+
} catch {
|
|
200
|
+
eligInfo = null;
|
|
201
|
+
}
|
|
164
202
|
}
|
|
165
203
|
|
|
166
204
|
const baseOccurrence = {
|
|
167
205
|
summary: 'Missing text alternative for <video> poster.',
|
|
168
|
-
hint: 'Provide an accessible name (
|
|
206
|
+
hint: 'Provide an accessible name for the poster image (aria-label/aria-labelledby preferred, or a title attribute as a fallback).',
|
|
169
207
|
i18n: {
|
|
170
208
|
summaryKey: 'videoPoster_textAltPresent_summary_fail',
|
|
171
209
|
hintKey: 'videoPoster_textAltPresent_hint_fail',
|
|
@@ -185,9 +223,16 @@ function runInPage(ctx) {
|
|
|
185
223
|
}
|
|
186
224
|
}
|
|
187
225
|
|
|
188
|
-
if (applicableCount === 0)
|
|
189
|
-
|
|
190
|
-
|
|
226
|
+
if (applicableCount === 0)
|
|
227
|
+
return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
|
|
228
|
+
if (!occurrences.length)
|
|
229
|
+
return { ruleId: rule.ruleId, outcome: 'pass', severity: 'minor', occurrences: [] };
|
|
230
|
+
return {
|
|
231
|
+
ruleId: rule.ruleId,
|
|
232
|
+
outcome: 'fail',
|
|
233
|
+
severity: rule.defaultSeverity || 'minor',
|
|
234
|
+
occurrences
|
|
235
|
+
};
|
|
191
236
|
}
|
|
192
237
|
|
|
193
238
|
module.exports = { id, meta, runInPage };
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
/* SPDX-License-Identifier: MPL-2.0 */
|
|
2
|
+
|
|
1
3
|
'use strict';
|
|
2
4
|
|
|
3
5
|
/**
|
|
4
6
|
* @check accesskeys
|
|
5
7
|
* @atomic true
|
|
6
8
|
* @summary accesskey values must be unique on the page
|
|
7
|
-
* @standard Best Practices (
|
|
9
|
+
* @standard Best Practices (no formal WCAG Success Criterion — see ROADMAP.md Tier 1b)
|
|
8
10
|
* @applicability
|
|
9
11
|
* Applies whenever two or more elements share the same non-empty
|
|
10
12
|
* accesskey attribute value (case-insensitive).
|
|
@@ -40,8 +42,10 @@ const meta = {
|
|
|
40
42
|
};
|
|
41
43
|
|
|
42
44
|
function runInPage(ctx) {
|
|
43
|
-
const {
|
|
44
|
-
const nodes = helpers.queryAllSmart
|
|
45
|
+
const { helpers, rule } = ctx;
|
|
46
|
+
const nodes = helpers.queryAllSmart
|
|
47
|
+
? helpers.queryAllSmart('[accesskey]')
|
|
48
|
+
: helpers.queryAll('[accesskey]');
|
|
45
49
|
|
|
46
50
|
const groups = new Map(); // normalized key -> elements[]
|
|
47
51
|
for (const el of nodes) {
|
|
@@ -59,12 +63,14 @@ function runInPage(ctx) {
|
|
|
59
63
|
if (els.length <= 1) continue;
|
|
60
64
|
for (const el of els) {
|
|
61
65
|
const stableSelector = helpers.buildSelector ? helpers.buildSelector(el) : 'html';
|
|
62
|
-
const html = helpers.getOuterHtmlSnippet
|
|
66
|
+
const html = helpers.getOuterHtmlSnippet
|
|
67
|
+
? helpers.getOuterHtmlSnippet(el)
|
|
68
|
+
: el.outerHTML || '';
|
|
63
69
|
|
|
64
70
|
occurrences.push({
|
|
65
71
|
selector: stableSelector,
|
|
66
72
|
html,
|
|
67
|
-
summary:
|
|
73
|
+
summary: "This element's accesskey is shared with another element on the page.",
|
|
68
74
|
hint: 'Make each accesskey value unique across the page.',
|
|
69
75
|
i18n: {
|
|
70
76
|
summaryKey: 'accesskeys_summary_cantTell',
|
|
@@ -81,7 +87,12 @@ function runInPage(ctx) {
|
|
|
81
87
|
if (!occurrences.length) {
|
|
82
88
|
return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
|
|
83
89
|
}
|
|
84
|
-
return {
|
|
90
|
+
return {
|
|
91
|
+
ruleId: rule.ruleId,
|
|
92
|
+
outcome: 'cantTell',
|
|
93
|
+
severity: rule.defaultSeverity || 'minor',
|
|
94
|
+
occurrences
|
|
95
|
+
};
|
|
85
96
|
}
|
|
86
97
|
|
|
87
98
|
module.exports = { id, meta, runInPage };
|