@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
|
/**
|
|
@@ -50,7 +52,13 @@ const meta = {
|
|
|
50
52
|
tags: ['wcag2a', 'wcag244', 'navigation', 'quality', 'atomic', 'manual'],
|
|
51
53
|
wcagSc: ['2.4.4'],
|
|
52
54
|
normativeMappings: [
|
|
53
|
-
{
|
|
55
|
+
{
|
|
56
|
+
standard: 'WCAG',
|
|
57
|
+
version: '2.2',
|
|
58
|
+
requirement: '2.4.4',
|
|
59
|
+
title: 'Link Purpose (In Context)',
|
|
60
|
+
conformanceLevel: 'A'
|
|
61
|
+
}
|
|
54
62
|
],
|
|
55
63
|
defaultSeverity: 'minor',
|
|
56
64
|
category: 'operable',
|
|
@@ -60,13 +68,28 @@ const meta = {
|
|
|
60
68
|
};
|
|
61
69
|
|
|
62
70
|
function runInPage(ctx) {
|
|
63
|
-
const {
|
|
71
|
+
const { helpers, rule } = ctx;
|
|
64
72
|
|
|
65
73
|
const GENERIC_LINK_TEXT = new Set([
|
|
66
|
-
'click here',
|
|
67
|
-
'
|
|
68
|
-
'
|
|
69
|
-
'
|
|
74
|
+
'click here',
|
|
75
|
+
'here',
|
|
76
|
+
'click',
|
|
77
|
+
'more',
|
|
78
|
+
'more info',
|
|
79
|
+
'more information',
|
|
80
|
+
'read more',
|
|
81
|
+
'learn more',
|
|
82
|
+
'continue reading',
|
|
83
|
+
'continue',
|
|
84
|
+
'details',
|
|
85
|
+
'more details',
|
|
86
|
+
'link',
|
|
87
|
+
'this link',
|
|
88
|
+
'go',
|
|
89
|
+
'download',
|
|
90
|
+
'view more',
|
|
91
|
+
'see more',
|
|
92
|
+
'info'
|
|
70
93
|
]);
|
|
71
94
|
|
|
72
95
|
function normalize(s) {
|
|
@@ -79,7 +102,9 @@ function runInPage(ctx) {
|
|
|
79
102
|
}
|
|
80
103
|
|
|
81
104
|
const selector = 'a[href]';
|
|
82
|
-
const nodes = helpers.queryAllSmart
|
|
105
|
+
const nodes = helpers.queryAllSmart
|
|
106
|
+
? helpers.queryAllSmart(selector)
|
|
107
|
+
: helpers.queryAll(selector);
|
|
83
108
|
|
|
84
109
|
const occurrences = [];
|
|
85
110
|
let applicableCount = 0;
|
|
@@ -88,11 +113,12 @@ function runInPage(ctx) {
|
|
|
88
113
|
if (!el || !el.getAttribute) continue;
|
|
89
114
|
|
|
90
115
|
const eligResult = helpers.isAccTreeEligible ? helpers.isAccTreeEligible(el, ctx) : true;
|
|
91
|
-
const eligible =
|
|
116
|
+
const eligible =
|
|
117
|
+
typeof eligResult === 'boolean' ? eligResult : !!(eligResult && eligResult.eligible);
|
|
92
118
|
if (!eligible) continue;
|
|
93
119
|
|
|
94
120
|
const nameInfo = helpers.getAccessibleNameInfo ? helpers.getAccessibleNameInfo(el, ctx) : null;
|
|
95
|
-
const programmaticName =
|
|
121
|
+
const programmaticName = nameInfo && typeof nameInfo.value === 'string' ? nameInfo.value : '';
|
|
96
122
|
|
|
97
123
|
let rawName = programmaticName;
|
|
98
124
|
if (!rawName.trim() && helpers.getContentNameInfo) {
|
|
@@ -108,8 +134,10 @@ function runInPage(ctx) {
|
|
|
108
134
|
if (!GENERIC_LINK_TEXT.has(normalized)) continue;
|
|
109
135
|
|
|
110
136
|
const stableSelector = helpers.buildSelector ? helpers.buildSelector(el) : 'html';
|
|
111
|
-
const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) :
|
|
112
|
-
const eligInfo = helpers.getEligibilityInfo
|
|
137
|
+
const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : el.outerHTML || '';
|
|
138
|
+
const eligInfo = helpers.getEligibilityInfo
|
|
139
|
+
? helpers.getEligibilityInfo(el, ctx, { targetSet: 'acc' })
|
|
140
|
+
: null;
|
|
113
141
|
|
|
114
142
|
occurrences.push({
|
|
115
143
|
selector: stableSelector,
|
|
@@ -133,7 +161,12 @@ function runInPage(ctx) {
|
|
|
133
161
|
}
|
|
134
162
|
|
|
135
163
|
if (occurrences.length) {
|
|
136
|
-
return {
|
|
164
|
+
return {
|
|
165
|
+
ruleId: rule.ruleId,
|
|
166
|
+
outcome: 'cantTell',
|
|
167
|
+
severity: rule.defaultSeverity || 'minor',
|
|
168
|
+
occurrences
|
|
169
|
+
};
|
|
137
170
|
}
|
|
138
171
|
|
|
139
172
|
return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/* SPDX-License-Identifier: MPL-2.0 */
|
|
2
|
+
|
|
1
3
|
'use strict';
|
|
2
4
|
|
|
3
5
|
/**
|
|
@@ -99,20 +101,23 @@ function runInPage(ctx) {
|
|
|
99
101
|
const __eligCache = new WeakMap();
|
|
100
102
|
|
|
101
103
|
function getEligibility(node) {
|
|
102
|
-
if (!node || node.nodeType !== 1)
|
|
104
|
+
if (!node || node.nodeType !== 1)
|
|
105
|
+
return { eligible: true, reasons: [], targetSet: 'acc', accEligible: null };
|
|
103
106
|
const cached = __eligCache.get(node);
|
|
104
107
|
if (cached) return cached;
|
|
105
108
|
|
|
106
|
-
let info
|
|
109
|
+
let info;
|
|
107
110
|
try {
|
|
108
|
-
info =
|
|
111
|
+
info =
|
|
112
|
+
helpers && typeof helpers.getEligibilityInfo === 'function'
|
|
109
113
|
? helpers.getEligibilityInfo(node, ctx, { targetSet: 'acc' })
|
|
110
114
|
: null;
|
|
111
115
|
} catch {
|
|
112
116
|
info = null;
|
|
113
117
|
}
|
|
114
118
|
|
|
115
|
-
const norm =
|
|
119
|
+
const norm =
|
|
120
|
+
info && typeof info === 'object'
|
|
116
121
|
? info
|
|
117
122
|
: { eligible: true, reasons: [], targetSet: 'acc', accEligible: null };
|
|
118
123
|
|
|
@@ -125,10 +130,6 @@ function runInPage(ctx) {
|
|
|
125
130
|
return !!(info && info.eligible);
|
|
126
131
|
}
|
|
127
132
|
|
|
128
|
-
function safeSelector(node) {
|
|
129
|
-
return helpers.buildSelector ? helpers.buildSelector(node) : 'html';
|
|
130
|
-
}
|
|
131
|
-
|
|
132
133
|
// Evidence object includes strength, so the rule can treat "unverified external link" as insufficient proof.
|
|
133
134
|
function evidenceNone() {
|
|
134
135
|
return {
|
|
@@ -173,7 +174,9 @@ function runInPage(ctx) {
|
|
|
173
174
|
if (hasToken || len >= MIN_DESCRIBEDBY_CHARS_WITHOUT_TOKEN) {
|
|
174
175
|
evidence.strength = 'strong';
|
|
175
176
|
evidence.method = 'aria-describedby';
|
|
176
|
-
evidence.notes.push(
|
|
177
|
+
evidence.notes.push(
|
|
178
|
+
'media has aria-describedby with explicit or substantial transcript text'
|
|
179
|
+
);
|
|
177
180
|
return evidence;
|
|
178
181
|
}
|
|
179
182
|
}
|
|
@@ -197,7 +200,9 @@ function runInPage(ctx) {
|
|
|
197
200
|
evidence.strength = 'strong';
|
|
198
201
|
evidence.method = 'adjacent-heading';
|
|
199
202
|
evidence.transcriptNodeSelector = nodeRef(h);
|
|
200
|
-
evidence.notes.push(
|
|
203
|
+
evidence.notes.push(
|
|
204
|
+
'found transcript heading with substantial visible adjacent text'
|
|
205
|
+
);
|
|
201
206
|
return evidence;
|
|
202
207
|
}
|
|
203
208
|
}
|
|
@@ -215,7 +220,9 @@ function runInPage(ctx) {
|
|
|
215
220
|
for (const a of links) {
|
|
216
221
|
if (!isEligible(a)) continue;
|
|
217
222
|
|
|
218
|
-
const nameInfo = helpers.getAccessibleNameInfo
|
|
223
|
+
const nameInfo = helpers.getAccessibleNameInfo
|
|
224
|
+
? helpers.getAccessibleNameInfo(a, ctx)
|
|
225
|
+
: null;
|
|
219
226
|
const linkName = nameInfo && nameInfo.value ? nameInfo.value : getNodeText(a);
|
|
220
227
|
if (!containsTranscriptToken(linkName)) continue;
|
|
221
228
|
|
|
@@ -227,7 +234,9 @@ function runInPage(ctx) {
|
|
|
227
234
|
if (href.startsWith('#')) {
|
|
228
235
|
const targetId = href.slice(1);
|
|
229
236
|
const target = targetId
|
|
230
|
-
?
|
|
237
|
+
? safeRoot.getElementById
|
|
238
|
+
? safeRoot.getElementById(targetId)
|
|
239
|
+
: document.getElementById(targetId)
|
|
231
240
|
: null;
|
|
232
241
|
|
|
233
242
|
if (isElement(target) && isEligible(target)) {
|
|
@@ -246,7 +255,9 @@ function runInPage(ctx) {
|
|
|
246
255
|
if (hasTranscriptHeading && textLen(targetText) >= MIN_TRANSCRIPT_CHARS) {
|
|
247
256
|
evidence.strength = 'strong';
|
|
248
257
|
evidence.method = 'anchor-target';
|
|
249
|
-
evidence.notes.push(
|
|
258
|
+
evidence.notes.push(
|
|
259
|
+
'resolved transcript link to an on-page section with transcript heading and substantial text'
|
|
260
|
+
);
|
|
250
261
|
return evidence;
|
|
251
262
|
}
|
|
252
263
|
}
|
|
@@ -254,7 +265,9 @@ function runInPage(ctx) {
|
|
|
254
265
|
// If anchor cannot be verified, treat as weak (still better than nothing, but not proof).
|
|
255
266
|
evidence.strength = 'weak';
|
|
256
267
|
evidence.method = 'anchor-unverified';
|
|
257
|
-
evidence.notes.push(
|
|
268
|
+
evidence.notes.push(
|
|
269
|
+
'transcript link found but anchor target could not be verified as a transcript section'
|
|
270
|
+
);
|
|
258
271
|
return evidence;
|
|
259
272
|
}
|
|
260
273
|
|
|
@@ -300,9 +313,8 @@ function runInPage(ctx) {
|
|
|
300
313
|
if (evidence.strength === 'none') {
|
|
301
314
|
const baseOccurrence = {
|
|
302
315
|
summary:
|
|
303
|
-
|
|
304
|
-
hint:
|
|
305
|
-
'Provide a clearly identified transcript or other text alternative for audio-only/video-only prerecorded media (for example, a “Transcript” section or link).',
|
|
316
|
+
'A transcript or other text alternative for this time-based media is not strongly evidenced on the page.',
|
|
317
|
+
hint: 'Provide a clearly identified transcript or other text alternative for audio-only/video-only prerecorded media (for example, a “Transcript” section or link).',
|
|
306
318
|
i18n: {
|
|
307
319
|
summaryKey: 'mediaTranscriptPresent_summary_cantTell_missing',
|
|
308
320
|
hintKey: 'mediaTranscriptPresent_hint_cantTell_missing',
|
|
@@ -327,12 +339,10 @@ function runInPage(ctx) {
|
|
|
327
339
|
}
|
|
328
340
|
|
|
329
341
|
if (evidence.strength === 'weak') {
|
|
330
|
-
|
|
331
342
|
const baseOccurrence = {
|
|
332
343
|
summary:
|
|
333
|
-
|
|
334
|
-
hint:
|
|
335
|
-
'Ensure a clearly identified transcript or other text alternative is available and programmatically or visibly associated with the media on the page.',
|
|
344
|
+
'A transcript or other text alternative may be available for this time-based media, but it could not be verified from the page content.',
|
|
345
|
+
hint: 'Ensure a clearly identified transcript or other text alternative is available and programmatically or visibly associated with the media on the page.',
|
|
336
346
|
i18n: {
|
|
337
347
|
summaryKey: 'mediaTranscriptPresent_summary_cantTell_unverified',
|
|
338
348
|
hintKey: 'mediaTranscriptPresent_hint_cantTell_unverified',
|
|
@@ -361,7 +371,12 @@ function runInPage(ctx) {
|
|
|
361
371
|
}
|
|
362
372
|
|
|
363
373
|
if (occurrences.length) {
|
|
364
|
-
return {
|
|
374
|
+
return {
|
|
375
|
+
ruleId: rule.ruleId,
|
|
376
|
+
outcome: 'cantTell',
|
|
377
|
+
severity: rule.defaultSeverity || 'minor',
|
|
378
|
+
occurrences
|
|
379
|
+
};
|
|
365
380
|
}
|
|
366
381
|
|
|
367
382
|
// Manual rules may only emit cantTell/notApplicable (never pass/fail).
|
|
@@ -1,10 +1,12 @@
|
|
|
1
|
+
/* SPDX-License-Identifier: MPL-2.0 */
|
|
2
|
+
|
|
1
3
|
'use strict';
|
|
2
4
|
|
|
3
5
|
/**
|
|
4
6
|
* @check meta-viewport-large
|
|
5
7
|
* @atomic true
|
|
6
8
|
* @summary Viewport meta tag should allow zooming up to 500% (AAA-level)
|
|
7
|
-
* @standard Best Practices (
|
|
9
|
+
* @standard Best Practices (no formal WCAG Success Criterion — see ROADMAP.md Tier 1b)
|
|
8
10
|
* @applicability
|
|
9
11
|
* Applies to <meta name="viewport"> elements that carry a non-empty
|
|
10
12
|
* content attribute.
|
|
@@ -27,7 +29,8 @@ const id = 'meta-viewport-large';
|
|
|
27
29
|
|
|
28
30
|
const meta = {
|
|
29
31
|
title: 'Viewport meta tag should allow zooming up to 500%',
|
|
30
|
-
description:
|
|
32
|
+
description:
|
|
33
|
+
'Checks that <meta name="viewport"> does not set user-scalable=no or maximum-scale below 5 (500%).',
|
|
31
34
|
i18n: {
|
|
32
35
|
titleKey: 'metaViewportLarge_title',
|
|
33
36
|
descriptionKey: 'metaViewportLarge_description'
|
|
@@ -60,13 +63,18 @@ function runInPage(ctx) {
|
|
|
60
63
|
const eq = pair.indexOf('=');
|
|
61
64
|
if (eq === -1) continue;
|
|
62
65
|
const key = pair.slice(0, eq).trim().toLowerCase();
|
|
63
|
-
const value = pair
|
|
66
|
+
const value = pair
|
|
67
|
+
.slice(eq + 1)
|
|
68
|
+
.trim()
|
|
69
|
+
.toLowerCase();
|
|
64
70
|
if (key) out[key] = value;
|
|
65
71
|
}
|
|
66
72
|
return out;
|
|
67
73
|
}
|
|
68
74
|
|
|
69
|
-
const nodes = document.querySelectorAll
|
|
75
|
+
const nodes = document.querySelectorAll
|
|
76
|
+
? document.querySelectorAll('meta[name="viewport" i]')
|
|
77
|
+
: [];
|
|
70
78
|
|
|
71
79
|
const occurrences = [];
|
|
72
80
|
let applicableCount = 0;
|
|
@@ -97,7 +105,7 @@ function runInPage(ctx) {
|
|
|
97
105
|
if (!reasons.length) continue;
|
|
98
106
|
|
|
99
107
|
const stableSelector = helpers.buildSelector ? helpers.buildSelector(el) : 'html';
|
|
100
|
-
const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) :
|
|
108
|
+
const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : el.outerHTML || '';
|
|
101
109
|
|
|
102
110
|
occurrences.push({
|
|
103
111
|
selector: stableSelector,
|
|
@@ -119,7 +127,12 @@ function runInPage(ctx) {
|
|
|
119
127
|
return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
|
|
120
128
|
}
|
|
121
129
|
if (occurrences.length) {
|
|
122
|
-
return {
|
|
130
|
+
return {
|
|
131
|
+
ruleId: rule.ruleId,
|
|
132
|
+
outcome: 'cantTell',
|
|
133
|
+
severity: rule.defaultSeverity || 'minor',
|
|
134
|
+
occurrences
|
|
135
|
+
};
|
|
123
136
|
}
|
|
124
137
|
return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
|
|
125
138
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/* SPDX-License-Identifier: MPL-2.0 */
|
|
2
|
+
|
|
1
3
|
'use strict';
|
|
2
4
|
|
|
3
5
|
/**
|
|
@@ -57,7 +59,13 @@ const meta = {
|
|
|
57
59
|
tags: ['wcag2a', 'wcag211', 'structure', 'atomic', 'manual'],
|
|
58
60
|
wcagSc: ['2.1.1'],
|
|
59
61
|
normativeMappings: [
|
|
60
|
-
{
|
|
62
|
+
{
|
|
63
|
+
standard: 'WCAG',
|
|
64
|
+
version: '2.2',
|
|
65
|
+
requirement: '2.1.1',
|
|
66
|
+
title: 'Keyboard',
|
|
67
|
+
conformanceLevel: 'A'
|
|
68
|
+
}
|
|
61
69
|
],
|
|
62
70
|
defaultSeverity: 'moderate',
|
|
63
71
|
category: 'operable',
|
|
@@ -67,18 +75,28 @@ const meta = {
|
|
|
67
75
|
};
|
|
68
76
|
|
|
69
77
|
function runInPage(ctx) {
|
|
70
|
-
const {
|
|
78
|
+
const { helpers, rule } = ctx;
|
|
71
79
|
|
|
72
80
|
const MOUSE_ONLY_ATTRS = [
|
|
73
|
-
'onmouseover',
|
|
74
|
-
'
|
|
81
|
+
'onmouseover',
|
|
82
|
+
'onmouseout',
|
|
83
|
+
'onmousedown',
|
|
84
|
+
'onmouseup',
|
|
85
|
+
'ondblclick',
|
|
86
|
+
'onmousemove',
|
|
87
|
+
'onmouseenter',
|
|
88
|
+
'onmouseleave'
|
|
75
89
|
];
|
|
76
90
|
const KEYBOARD_EQUIV_ATTRS = ['onkeydown', 'onkeyup', 'onkeypress', 'onfocus', 'onblur'];
|
|
77
91
|
|
|
78
|
-
function trim(v) {
|
|
92
|
+
function trim(v) {
|
|
93
|
+
return (v == null ? '' : String(v)).trim();
|
|
94
|
+
}
|
|
79
95
|
|
|
80
96
|
const selector = MOUSE_ONLY_ATTRS.map((a) => `[${a}]`).join(', ');
|
|
81
|
-
const nodes = helpers.queryAllSmart
|
|
97
|
+
const nodes = helpers.queryAllSmart
|
|
98
|
+
? helpers.queryAllSmart(selector)
|
|
99
|
+
: helpers.queryAll(selector);
|
|
82
100
|
|
|
83
101
|
const occurrences = [];
|
|
84
102
|
let applicableCount = 0;
|
|
@@ -90,7 +108,8 @@ function runInPage(ctx) {
|
|
|
90
108
|
if (!presentMouseAttrs.length) continue;
|
|
91
109
|
|
|
92
110
|
const eligResult = helpers.isAccTreeEligible ? helpers.isAccTreeEligible(el, ctx) : true;
|
|
93
|
-
const eligible =
|
|
111
|
+
const eligible =
|
|
112
|
+
typeof eligResult === 'boolean' ? eligResult : !!(eligResult && eligResult.eligible);
|
|
94
113
|
if (!eligible) continue;
|
|
95
114
|
|
|
96
115
|
applicableCount += 1;
|
|
@@ -99,8 +118,10 @@ function runInPage(ctx) {
|
|
|
99
118
|
if (hasKeyboardEquiv) continue;
|
|
100
119
|
|
|
101
120
|
const stableSelector = helpers.buildSelector ? helpers.buildSelector(el) : 'html';
|
|
102
|
-
const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) :
|
|
103
|
-
const eligInfo = helpers.getEligibilityInfo
|
|
121
|
+
const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : el.outerHTML || '';
|
|
122
|
+
const eligInfo = helpers.getEligibilityInfo
|
|
123
|
+
? helpers.getEligibilityInfo(el, ctx, { targetSet: 'acc' })
|
|
124
|
+
: null;
|
|
104
125
|
|
|
105
126
|
occurrences.push({
|
|
106
127
|
selector: stableSelector,
|
|
@@ -113,7 +134,10 @@ function runInPage(ctx) {
|
|
|
113
134
|
params: { attrs: presentMouseAttrs.join(', ') }
|
|
114
135
|
},
|
|
115
136
|
data: {
|
|
116
|
-
details: {
|
|
137
|
+
details: {
|
|
138
|
+
reasonCode: 'MOUSE_ONLY_HANDLER_NO_KEYBOARD_EQUIVALENT',
|
|
139
|
+
mouseAttrs: presentMouseAttrs
|
|
140
|
+
},
|
|
117
141
|
visibilityFilter: eligInfo || { targetSet: 'acc', accEligible: null, reasons: [] }
|
|
118
142
|
}
|
|
119
143
|
});
|
|
@@ -124,7 +148,12 @@ function runInPage(ctx) {
|
|
|
124
148
|
}
|
|
125
149
|
|
|
126
150
|
if (occurrences.length) {
|
|
127
|
-
return {
|
|
151
|
+
return {
|
|
152
|
+
ruleId: rule.ruleId,
|
|
153
|
+
outcome: 'cantTell',
|
|
154
|
+
severity: rule.defaultSeverity || 'moderate',
|
|
155
|
+
occurrences
|
|
156
|
+
};
|
|
128
157
|
}
|
|
129
158
|
|
|
130
159
|
return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/* SPDX-License-Identifier: MPL-2.0 */
|
|
2
|
+
|
|
1
3
|
'use strict';
|
|
2
4
|
|
|
3
5
|
/**
|
|
@@ -48,7 +50,13 @@ const meta = {
|
|
|
48
50
|
tags: ['wcag2a', 'wcag142', 'media', 'atomic', 'manual'],
|
|
49
51
|
wcagSc: ['1.4.2'],
|
|
50
52
|
normativeMappings: [
|
|
51
|
-
{
|
|
53
|
+
{
|
|
54
|
+
standard: 'WCAG',
|
|
55
|
+
version: '2.2',
|
|
56
|
+
requirement: '1.4.2',
|
|
57
|
+
title: 'Audio Control',
|
|
58
|
+
conformanceLevel: 'A'
|
|
59
|
+
}
|
|
52
60
|
],
|
|
53
61
|
defaultSeverity: 'moderate',
|
|
54
62
|
category: 'perceivable',
|
|
@@ -58,9 +66,11 @@ const meta = {
|
|
|
58
66
|
};
|
|
59
67
|
|
|
60
68
|
function runInPage(ctx) {
|
|
61
|
-
const {
|
|
69
|
+
const { helpers, rule } = ctx;
|
|
62
70
|
|
|
63
|
-
const nodes = helpers.queryAllSmart
|
|
71
|
+
const nodes = helpers.queryAllSmart
|
|
72
|
+
? helpers.queryAllSmart('audio[autoplay], video[autoplay]')
|
|
73
|
+
: helpers.queryAll('audio[autoplay], video[autoplay]');
|
|
64
74
|
|
|
65
75
|
const occurrences = [];
|
|
66
76
|
let applicableCount = 0;
|
|
@@ -75,12 +85,13 @@ function runInPage(ctx) {
|
|
|
75
85
|
|
|
76
86
|
const mediaTag = (el.tagName || '').toLowerCase();
|
|
77
87
|
const stableSelector = helpers.buildSelector ? helpers.buildSelector(el) : 'html';
|
|
78
|
-
const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) :
|
|
88
|
+
const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : el.outerHTML || '';
|
|
79
89
|
|
|
80
90
|
const baseOccurrence = {
|
|
81
91
|
selector: stableSelector,
|
|
82
92
|
html,
|
|
83
|
-
summary:
|
|
93
|
+
summary:
|
|
94
|
+
'This element autoplays audio without a native pause/stop or volume-control mechanism.',
|
|
84
95
|
hint: 'If this clip plays for more than 3 seconds, add a `controls` attribute (or an equivalent custom mechanism) so users can pause/stop it or control its volume independently of the system volume.',
|
|
85
96
|
i18n: {
|
|
86
97
|
summaryKey: 'noAutoplayAudio_summary_cantTell',
|
|
@@ -104,7 +115,12 @@ function runInPage(ctx) {
|
|
|
104
115
|
}
|
|
105
116
|
|
|
106
117
|
if (occurrences.length) {
|
|
107
|
-
return {
|
|
118
|
+
return {
|
|
119
|
+
ruleId: rule.ruleId,
|
|
120
|
+
outcome: 'cantTell',
|
|
121
|
+
severity: rule.defaultSeverity || 'moderate',
|
|
122
|
+
occurrences
|
|
123
|
+
};
|
|
108
124
|
}
|
|
109
125
|
|
|
110
126
|
// Manual rules may only emit cantTell/notApplicable (never pass/fail):
|