@surea11y/core 1.1.2 → 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 +48 -4
- package/LICENSE +373 -21
- package/README.md +70 -1
- package/bin/core.js +240 -11
- package/docs/API_STABILITY.md +61 -0
- package/docs/BASELINE.md +66 -0
- package/docs/BINDING_AUTHORS_GUIDE.md +9 -9
- package/docs/CI_INTEGRATIONS.md +103 -0
- package/docs/CLI.md +80 -1
- package/docs/ENGINE_OPTIONS.md +4 -0
- package/docs/INTEGRATION.md +19 -1
- package/docs/OUTPUT_SCHEMA.md +2 -2
- package/docs/REPORT.md +33 -0
- package/docs/RULE_AUTHORING.md +31 -0
- 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 +50 -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 +125 -29
- 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 +50 -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 +39 -9
- 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 +43 -19
- 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 +117 -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 +37 -8
- package/src/checks/automatic/meta-refresh-timing-absent.js +28 -6
- package/src/checks/automatic/meta-viewport-zoom-enabled.js +32 -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 +26 -7
- 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 -12
- package/src/checks/manual/empty-table-header-manual.js +21 -14
- 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 -12
- 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 +95 -29
- package/src/checks/manual/landmark-contentinfo-is-top-level-manual.js +82 -29
- package/src/checks/manual/landmark-main-is-top-level-manual.js +64 -23
- package/src/checks/manual/landmark-no-duplicate-banner-manual.js +54 -23
- package/src/checks/manual/landmark-no-duplicate-contentinfo-manual.js +54 -23
- package/src/checks/manual/landmark-no-duplicate-main-manual.js +17 -8
- package/src/checks/manual/landmark-one-main-manual.js +35 -21
- package/src/checks/manual/landmark-unique-manual.js +73 -45
- 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 +25 -6
- 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 +40 -23
- package/src/checks/manual/page-title-patterns-manual.js +87 -51
- package/src/checks/manual/presentation-role-conflict-manual.js +59 -19
- package/src/checks/manual/region-manual.js +275 -62
- package/src/checks/manual/scope-attr-valid-manual.js +11 -9
- 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 +11 -9
- 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 +1128 -823
- package/src/core/contrast-helpers.js +1217 -1062
- package/src/core/dom-helpers.js +4176 -3886
- package/src/core/dom-runner.js +720 -596
- 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 +71 -35
- package/src/core.js +37939 -29121
- package/src/i18n/en.js +1194 -887
- package/src/i18n/fr.js +1136 -793
- package/src/policy/contracts.js +13 -13
- package/src/policy/resolvePolicy.js +48 -44
- package/src/report.js +502 -0
- package/src/sarif.js +175 -0
- package/surea11y.browser.js +36042 -0
|
@@ -50,7 +50,13 @@ const meta = {
|
|
|
50
50
|
tags: ['wcag2a', 'wcag244', 'navigation', 'quality', 'atomic', 'manual'],
|
|
51
51
|
wcagSc: ['2.4.4'],
|
|
52
52
|
normativeMappings: [
|
|
53
|
-
{
|
|
53
|
+
{
|
|
54
|
+
standard: 'WCAG',
|
|
55
|
+
version: '2.2',
|
|
56
|
+
requirement: '2.4.4',
|
|
57
|
+
title: 'Link Purpose (In Context)',
|
|
58
|
+
conformanceLevel: 'A'
|
|
59
|
+
}
|
|
54
60
|
],
|
|
55
61
|
defaultSeverity: 'minor',
|
|
56
62
|
category: 'operable',
|
|
@@ -60,13 +66,28 @@ const meta = {
|
|
|
60
66
|
};
|
|
61
67
|
|
|
62
68
|
function runInPage(ctx) {
|
|
63
|
-
const {
|
|
69
|
+
const { helpers, rule } = ctx;
|
|
64
70
|
|
|
65
71
|
const GENERIC_LINK_TEXT = new Set([
|
|
66
|
-
'click here',
|
|
67
|
-
'
|
|
68
|
-
'
|
|
69
|
-
'
|
|
72
|
+
'click here',
|
|
73
|
+
'here',
|
|
74
|
+
'click',
|
|
75
|
+
'more',
|
|
76
|
+
'more info',
|
|
77
|
+
'more information',
|
|
78
|
+
'read more',
|
|
79
|
+
'learn more',
|
|
80
|
+
'continue reading',
|
|
81
|
+
'continue',
|
|
82
|
+
'details',
|
|
83
|
+
'more details',
|
|
84
|
+
'link',
|
|
85
|
+
'this link',
|
|
86
|
+
'go',
|
|
87
|
+
'download',
|
|
88
|
+
'view more',
|
|
89
|
+
'see more',
|
|
90
|
+
'info'
|
|
70
91
|
]);
|
|
71
92
|
|
|
72
93
|
function normalize(s) {
|
|
@@ -79,7 +100,9 @@ function runInPage(ctx) {
|
|
|
79
100
|
}
|
|
80
101
|
|
|
81
102
|
const selector = 'a[href]';
|
|
82
|
-
const nodes = helpers.queryAllSmart
|
|
103
|
+
const nodes = helpers.queryAllSmart
|
|
104
|
+
? helpers.queryAllSmart(selector)
|
|
105
|
+
: helpers.queryAll(selector);
|
|
83
106
|
|
|
84
107
|
const occurrences = [];
|
|
85
108
|
let applicableCount = 0;
|
|
@@ -88,11 +111,12 @@ function runInPage(ctx) {
|
|
|
88
111
|
if (!el || !el.getAttribute) continue;
|
|
89
112
|
|
|
90
113
|
const eligResult = helpers.isAccTreeEligible ? helpers.isAccTreeEligible(el, ctx) : true;
|
|
91
|
-
const eligible =
|
|
114
|
+
const eligible =
|
|
115
|
+
typeof eligResult === 'boolean' ? eligResult : !!(eligResult && eligResult.eligible);
|
|
92
116
|
if (!eligible) continue;
|
|
93
117
|
|
|
94
118
|
const nameInfo = helpers.getAccessibleNameInfo ? helpers.getAccessibleNameInfo(el, ctx) : null;
|
|
95
|
-
const programmaticName =
|
|
119
|
+
const programmaticName = nameInfo && typeof nameInfo.value === 'string' ? nameInfo.value : '';
|
|
96
120
|
|
|
97
121
|
let rawName = programmaticName;
|
|
98
122
|
if (!rawName.trim() && helpers.getContentNameInfo) {
|
|
@@ -108,8 +132,10 @@ function runInPage(ctx) {
|
|
|
108
132
|
if (!GENERIC_LINK_TEXT.has(normalized)) continue;
|
|
109
133
|
|
|
110
134
|
const stableSelector = helpers.buildSelector ? helpers.buildSelector(el) : 'html';
|
|
111
|
-
const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) :
|
|
112
|
-
const eligInfo = helpers.getEligibilityInfo
|
|
135
|
+
const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : el.outerHTML || '';
|
|
136
|
+
const eligInfo = helpers.getEligibilityInfo
|
|
137
|
+
? helpers.getEligibilityInfo(el, ctx, { targetSet: 'acc' })
|
|
138
|
+
: null;
|
|
113
139
|
|
|
114
140
|
occurrences.push({
|
|
115
141
|
selector: stableSelector,
|
|
@@ -133,7 +159,12 @@ function runInPage(ctx) {
|
|
|
133
159
|
}
|
|
134
160
|
|
|
135
161
|
if (occurrences.length) {
|
|
136
|
-
return {
|
|
162
|
+
return {
|
|
163
|
+
ruleId: rule.ruleId,
|
|
164
|
+
outcome: 'cantTell',
|
|
165
|
+
severity: rule.defaultSeverity || 'minor',
|
|
166
|
+
occurrences
|
|
167
|
+
};
|
|
137
168
|
}
|
|
138
169
|
|
|
139
170
|
return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
|
|
@@ -99,20 +99,23 @@ function runInPage(ctx) {
|
|
|
99
99
|
const __eligCache = new WeakMap();
|
|
100
100
|
|
|
101
101
|
function getEligibility(node) {
|
|
102
|
-
if (!node || node.nodeType !== 1)
|
|
102
|
+
if (!node || node.nodeType !== 1)
|
|
103
|
+
return { eligible: true, reasons: [], targetSet: 'acc', accEligible: null };
|
|
103
104
|
const cached = __eligCache.get(node);
|
|
104
105
|
if (cached) return cached;
|
|
105
106
|
|
|
106
|
-
let info
|
|
107
|
+
let info;
|
|
107
108
|
try {
|
|
108
|
-
info =
|
|
109
|
+
info =
|
|
110
|
+
helpers && typeof helpers.getEligibilityInfo === 'function'
|
|
109
111
|
? helpers.getEligibilityInfo(node, ctx, { targetSet: 'acc' })
|
|
110
112
|
: null;
|
|
111
113
|
} catch {
|
|
112
114
|
info = null;
|
|
113
115
|
}
|
|
114
116
|
|
|
115
|
-
const norm =
|
|
117
|
+
const norm =
|
|
118
|
+
info && typeof info === 'object'
|
|
116
119
|
? info
|
|
117
120
|
: { eligible: true, reasons: [], targetSet: 'acc', accEligible: null };
|
|
118
121
|
|
|
@@ -125,10 +128,6 @@ function runInPage(ctx) {
|
|
|
125
128
|
return !!(info && info.eligible);
|
|
126
129
|
}
|
|
127
130
|
|
|
128
|
-
function safeSelector(node) {
|
|
129
|
-
return helpers.buildSelector ? helpers.buildSelector(node) : 'html';
|
|
130
|
-
}
|
|
131
|
-
|
|
132
131
|
// Evidence object includes strength, so the rule can treat "unverified external link" as insufficient proof.
|
|
133
132
|
function evidenceNone() {
|
|
134
133
|
return {
|
|
@@ -173,7 +172,9 @@ function runInPage(ctx) {
|
|
|
173
172
|
if (hasToken || len >= MIN_DESCRIBEDBY_CHARS_WITHOUT_TOKEN) {
|
|
174
173
|
evidence.strength = 'strong';
|
|
175
174
|
evidence.method = 'aria-describedby';
|
|
176
|
-
evidence.notes.push(
|
|
175
|
+
evidence.notes.push(
|
|
176
|
+
'media has aria-describedby with explicit or substantial transcript text'
|
|
177
|
+
);
|
|
177
178
|
return evidence;
|
|
178
179
|
}
|
|
179
180
|
}
|
|
@@ -197,7 +198,9 @@ function runInPage(ctx) {
|
|
|
197
198
|
evidence.strength = 'strong';
|
|
198
199
|
evidence.method = 'adjacent-heading';
|
|
199
200
|
evidence.transcriptNodeSelector = nodeRef(h);
|
|
200
|
-
evidence.notes.push(
|
|
201
|
+
evidence.notes.push(
|
|
202
|
+
'found transcript heading with substantial visible adjacent text'
|
|
203
|
+
);
|
|
201
204
|
return evidence;
|
|
202
205
|
}
|
|
203
206
|
}
|
|
@@ -215,7 +218,9 @@ function runInPage(ctx) {
|
|
|
215
218
|
for (const a of links) {
|
|
216
219
|
if (!isEligible(a)) continue;
|
|
217
220
|
|
|
218
|
-
const nameInfo = helpers.getAccessibleNameInfo
|
|
221
|
+
const nameInfo = helpers.getAccessibleNameInfo
|
|
222
|
+
? helpers.getAccessibleNameInfo(a, ctx)
|
|
223
|
+
: null;
|
|
219
224
|
const linkName = nameInfo && nameInfo.value ? nameInfo.value : getNodeText(a);
|
|
220
225
|
if (!containsTranscriptToken(linkName)) continue;
|
|
221
226
|
|
|
@@ -227,7 +232,9 @@ function runInPage(ctx) {
|
|
|
227
232
|
if (href.startsWith('#')) {
|
|
228
233
|
const targetId = href.slice(1);
|
|
229
234
|
const target = targetId
|
|
230
|
-
?
|
|
235
|
+
? safeRoot.getElementById
|
|
236
|
+
? safeRoot.getElementById(targetId)
|
|
237
|
+
: document.getElementById(targetId)
|
|
231
238
|
: null;
|
|
232
239
|
|
|
233
240
|
if (isElement(target) && isEligible(target)) {
|
|
@@ -246,7 +253,9 @@ function runInPage(ctx) {
|
|
|
246
253
|
if (hasTranscriptHeading && textLen(targetText) >= MIN_TRANSCRIPT_CHARS) {
|
|
247
254
|
evidence.strength = 'strong';
|
|
248
255
|
evidence.method = 'anchor-target';
|
|
249
|
-
evidence.notes.push(
|
|
256
|
+
evidence.notes.push(
|
|
257
|
+
'resolved transcript link to an on-page section with transcript heading and substantial text'
|
|
258
|
+
);
|
|
250
259
|
return evidence;
|
|
251
260
|
}
|
|
252
261
|
}
|
|
@@ -254,7 +263,9 @@ function runInPage(ctx) {
|
|
|
254
263
|
// If anchor cannot be verified, treat as weak (still better than nothing, but not proof).
|
|
255
264
|
evidence.strength = 'weak';
|
|
256
265
|
evidence.method = 'anchor-unverified';
|
|
257
|
-
evidence.notes.push(
|
|
266
|
+
evidence.notes.push(
|
|
267
|
+
'transcript link found but anchor target could not be verified as a transcript section'
|
|
268
|
+
);
|
|
258
269
|
return evidence;
|
|
259
270
|
}
|
|
260
271
|
|
|
@@ -300,9 +311,8 @@ function runInPage(ctx) {
|
|
|
300
311
|
if (evidence.strength === 'none') {
|
|
301
312
|
const baseOccurrence = {
|
|
302
313
|
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).',
|
|
314
|
+
'A transcript or other text alternative for this time-based media is not strongly evidenced on the page.',
|
|
315
|
+
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
316
|
i18n: {
|
|
307
317
|
summaryKey: 'mediaTranscriptPresent_summary_cantTell_missing',
|
|
308
318
|
hintKey: 'mediaTranscriptPresent_hint_cantTell_missing',
|
|
@@ -327,12 +337,10 @@ function runInPage(ctx) {
|
|
|
327
337
|
}
|
|
328
338
|
|
|
329
339
|
if (evidence.strength === 'weak') {
|
|
330
|
-
|
|
331
340
|
const baseOccurrence = {
|
|
332
341
|
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.',
|
|
342
|
+
'A transcript or other text alternative may be available for this time-based media, but it could not be verified from the page content.',
|
|
343
|
+
hint: 'Ensure a clearly identified transcript or other text alternative is available and programmatically or visibly associated with the media on the page.',
|
|
336
344
|
i18n: {
|
|
337
345
|
summaryKey: 'mediaTranscriptPresent_summary_cantTell_unverified',
|
|
338
346
|
hintKey: 'mediaTranscriptPresent_hint_cantTell_unverified',
|
|
@@ -361,7 +369,12 @@ function runInPage(ctx) {
|
|
|
361
369
|
}
|
|
362
370
|
|
|
363
371
|
if (occurrences.length) {
|
|
364
|
-
return {
|
|
372
|
+
return {
|
|
373
|
+
ruleId: rule.ruleId,
|
|
374
|
+
outcome: 'cantTell',
|
|
375
|
+
severity: rule.defaultSeverity || 'minor',
|
|
376
|
+
occurrences
|
|
377
|
+
};
|
|
365
378
|
}
|
|
366
379
|
|
|
367
380
|
// Manual rules may only emit cantTell/notApplicable (never pass/fail).
|
|
@@ -27,7 +27,8 @@ const id = 'meta-viewport-large';
|
|
|
27
27
|
|
|
28
28
|
const meta = {
|
|
29
29
|
title: 'Viewport meta tag should allow zooming up to 500%',
|
|
30
|
-
description:
|
|
30
|
+
description:
|
|
31
|
+
'Checks that <meta name="viewport"> does not set user-scalable=no or maximum-scale below 5 (500%).',
|
|
31
32
|
i18n: {
|
|
32
33
|
titleKey: 'metaViewportLarge_title',
|
|
33
34
|
descriptionKey: 'metaViewportLarge_description'
|
|
@@ -43,6 +44,14 @@ const meta = {
|
|
|
43
44
|
coverage: {}
|
|
44
45
|
};
|
|
45
46
|
|
|
47
|
+
// This check is inherently whole-document (does the PAGE have this
|
|
48
|
+
// property?), not evaluable per-subtree -- notApplicable when contextSelector
|
|
49
|
+
// scoped this run narrower than the whole document, or when
|
|
50
|
+
// engineOptions.fragment:true was set (see helpers.isWholeDocumentScope).
|
|
51
|
+
function applicability(ctx) {
|
|
52
|
+
return ctx.helpers.isWholeDocumentScope ? ctx.helpers.isWholeDocumentScope() : true;
|
|
53
|
+
}
|
|
54
|
+
|
|
46
55
|
function runInPage(ctx) {
|
|
47
56
|
const { document, helpers, rule } = ctx;
|
|
48
57
|
|
|
@@ -52,13 +61,18 @@ function runInPage(ctx) {
|
|
|
52
61
|
const eq = pair.indexOf('=');
|
|
53
62
|
if (eq === -1) continue;
|
|
54
63
|
const key = pair.slice(0, eq).trim().toLowerCase();
|
|
55
|
-
const value = pair
|
|
64
|
+
const value = pair
|
|
65
|
+
.slice(eq + 1)
|
|
66
|
+
.trim()
|
|
67
|
+
.toLowerCase();
|
|
56
68
|
if (key) out[key] = value;
|
|
57
69
|
}
|
|
58
70
|
return out;
|
|
59
71
|
}
|
|
60
72
|
|
|
61
|
-
const nodes = document.querySelectorAll
|
|
73
|
+
const nodes = document.querySelectorAll
|
|
74
|
+
? document.querySelectorAll('meta[name="viewport" i]')
|
|
75
|
+
: [];
|
|
62
76
|
|
|
63
77
|
const occurrences = [];
|
|
64
78
|
let applicableCount = 0;
|
|
@@ -89,7 +103,7 @@ function runInPage(ctx) {
|
|
|
89
103
|
if (!reasons.length) continue;
|
|
90
104
|
|
|
91
105
|
const stableSelector = helpers.buildSelector ? helpers.buildSelector(el) : 'html';
|
|
92
|
-
const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) :
|
|
106
|
+
const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : el.outerHTML || '';
|
|
93
107
|
|
|
94
108
|
occurrences.push({
|
|
95
109
|
selector: stableSelector,
|
|
@@ -111,9 +125,14 @@ function runInPage(ctx) {
|
|
|
111
125
|
return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
|
|
112
126
|
}
|
|
113
127
|
if (occurrences.length) {
|
|
114
|
-
return {
|
|
128
|
+
return {
|
|
129
|
+
ruleId: rule.ruleId,
|
|
130
|
+
outcome: 'cantTell',
|
|
131
|
+
severity: rule.defaultSeverity || 'minor',
|
|
132
|
+
occurrences
|
|
133
|
+
};
|
|
115
134
|
}
|
|
116
135
|
return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
|
|
117
136
|
}
|
|
118
137
|
|
|
119
|
-
module.exports = { id, meta, runInPage };
|
|
138
|
+
module.exports = { id, meta, runInPage, applicability };
|
|
@@ -57,7 +57,13 @@ const meta = {
|
|
|
57
57
|
tags: ['wcag2a', 'wcag211', 'structure', 'atomic', 'manual'],
|
|
58
58
|
wcagSc: ['2.1.1'],
|
|
59
59
|
normativeMappings: [
|
|
60
|
-
{
|
|
60
|
+
{
|
|
61
|
+
standard: 'WCAG',
|
|
62
|
+
version: '2.2',
|
|
63
|
+
requirement: '2.1.1',
|
|
64
|
+
title: 'Keyboard',
|
|
65
|
+
conformanceLevel: 'A'
|
|
66
|
+
}
|
|
61
67
|
],
|
|
62
68
|
defaultSeverity: 'moderate',
|
|
63
69
|
category: 'operable',
|
|
@@ -67,18 +73,28 @@ const meta = {
|
|
|
67
73
|
};
|
|
68
74
|
|
|
69
75
|
function runInPage(ctx) {
|
|
70
|
-
const {
|
|
76
|
+
const { helpers, rule } = ctx;
|
|
71
77
|
|
|
72
78
|
const MOUSE_ONLY_ATTRS = [
|
|
73
|
-
'onmouseover',
|
|
74
|
-
'
|
|
79
|
+
'onmouseover',
|
|
80
|
+
'onmouseout',
|
|
81
|
+
'onmousedown',
|
|
82
|
+
'onmouseup',
|
|
83
|
+
'ondblclick',
|
|
84
|
+
'onmousemove',
|
|
85
|
+
'onmouseenter',
|
|
86
|
+
'onmouseleave'
|
|
75
87
|
];
|
|
76
88
|
const KEYBOARD_EQUIV_ATTRS = ['onkeydown', 'onkeyup', 'onkeypress', 'onfocus', 'onblur'];
|
|
77
89
|
|
|
78
|
-
function trim(v) {
|
|
90
|
+
function trim(v) {
|
|
91
|
+
return (v == null ? '' : String(v)).trim();
|
|
92
|
+
}
|
|
79
93
|
|
|
80
94
|
const selector = MOUSE_ONLY_ATTRS.map((a) => `[${a}]`).join(', ');
|
|
81
|
-
const nodes = helpers.queryAllSmart
|
|
95
|
+
const nodes = helpers.queryAllSmart
|
|
96
|
+
? helpers.queryAllSmart(selector)
|
|
97
|
+
: helpers.queryAll(selector);
|
|
82
98
|
|
|
83
99
|
const occurrences = [];
|
|
84
100
|
let applicableCount = 0;
|
|
@@ -90,7 +106,8 @@ function runInPage(ctx) {
|
|
|
90
106
|
if (!presentMouseAttrs.length) continue;
|
|
91
107
|
|
|
92
108
|
const eligResult = helpers.isAccTreeEligible ? helpers.isAccTreeEligible(el, ctx) : true;
|
|
93
|
-
const eligible =
|
|
109
|
+
const eligible =
|
|
110
|
+
typeof eligResult === 'boolean' ? eligResult : !!(eligResult && eligResult.eligible);
|
|
94
111
|
if (!eligible) continue;
|
|
95
112
|
|
|
96
113
|
applicableCount += 1;
|
|
@@ -99,8 +116,10 @@ function runInPage(ctx) {
|
|
|
99
116
|
if (hasKeyboardEquiv) continue;
|
|
100
117
|
|
|
101
118
|
const stableSelector = helpers.buildSelector ? helpers.buildSelector(el) : 'html';
|
|
102
|
-
const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) :
|
|
103
|
-
const eligInfo = helpers.getEligibilityInfo
|
|
119
|
+
const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : el.outerHTML || '';
|
|
120
|
+
const eligInfo = helpers.getEligibilityInfo
|
|
121
|
+
? helpers.getEligibilityInfo(el, ctx, { targetSet: 'acc' })
|
|
122
|
+
: null;
|
|
104
123
|
|
|
105
124
|
occurrences.push({
|
|
106
125
|
selector: stableSelector,
|
|
@@ -113,7 +132,10 @@ function runInPage(ctx) {
|
|
|
113
132
|
params: { attrs: presentMouseAttrs.join(', ') }
|
|
114
133
|
},
|
|
115
134
|
data: {
|
|
116
|
-
details: {
|
|
135
|
+
details: {
|
|
136
|
+
reasonCode: 'MOUSE_ONLY_HANDLER_NO_KEYBOARD_EQUIVALENT',
|
|
137
|
+
mouseAttrs: presentMouseAttrs
|
|
138
|
+
},
|
|
117
139
|
visibilityFilter: eligInfo || { targetSet: 'acc', accEligible: null, reasons: [] }
|
|
118
140
|
}
|
|
119
141
|
});
|
|
@@ -124,7 +146,12 @@ function runInPage(ctx) {
|
|
|
124
146
|
}
|
|
125
147
|
|
|
126
148
|
if (occurrences.length) {
|
|
127
|
-
return {
|
|
149
|
+
return {
|
|
150
|
+
ruleId: rule.ruleId,
|
|
151
|
+
outcome: 'cantTell',
|
|
152
|
+
severity: rule.defaultSeverity || 'moderate',
|
|
153
|
+
occurrences
|
|
154
|
+
};
|
|
128
155
|
}
|
|
129
156
|
|
|
130
157
|
return { ruleId: rule.ruleId, outcome: 'notApplicable', severity: 'minor', occurrences: [] };
|
|
@@ -48,7 +48,13 @@ const meta = {
|
|
|
48
48
|
tags: ['wcag2a', 'wcag142', 'media', 'atomic', 'manual'],
|
|
49
49
|
wcagSc: ['1.4.2'],
|
|
50
50
|
normativeMappings: [
|
|
51
|
-
{
|
|
51
|
+
{
|
|
52
|
+
standard: 'WCAG',
|
|
53
|
+
version: '2.2',
|
|
54
|
+
requirement: '1.4.2',
|
|
55
|
+
title: 'Audio Control',
|
|
56
|
+
conformanceLevel: 'A'
|
|
57
|
+
}
|
|
52
58
|
],
|
|
53
59
|
defaultSeverity: 'moderate',
|
|
54
60
|
category: 'perceivable',
|
|
@@ -58,9 +64,11 @@ const meta = {
|
|
|
58
64
|
};
|
|
59
65
|
|
|
60
66
|
function runInPage(ctx) {
|
|
61
|
-
const {
|
|
67
|
+
const { helpers, rule } = ctx;
|
|
62
68
|
|
|
63
|
-
const nodes = helpers.queryAllSmart
|
|
69
|
+
const nodes = helpers.queryAllSmart
|
|
70
|
+
? helpers.queryAllSmart('audio[autoplay], video[autoplay]')
|
|
71
|
+
: helpers.queryAll('audio[autoplay], video[autoplay]');
|
|
64
72
|
|
|
65
73
|
const occurrences = [];
|
|
66
74
|
let applicableCount = 0;
|
|
@@ -75,12 +83,13 @@ function runInPage(ctx) {
|
|
|
75
83
|
|
|
76
84
|
const mediaTag = (el.tagName || '').toLowerCase();
|
|
77
85
|
const stableSelector = helpers.buildSelector ? helpers.buildSelector(el) : 'html';
|
|
78
|
-
const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) :
|
|
86
|
+
const html = helpers.getOuterHtmlSnippet ? helpers.getOuterHtmlSnippet(el) : el.outerHTML || '';
|
|
79
87
|
|
|
80
88
|
const baseOccurrence = {
|
|
81
89
|
selector: stableSelector,
|
|
82
90
|
html,
|
|
83
|
-
summary:
|
|
91
|
+
summary:
|
|
92
|
+
'This element autoplays audio without a native pause/stop or volume-control mechanism.',
|
|
84
93
|
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
94
|
i18n: {
|
|
86
95
|
summaryKey: 'noAutoplayAudio_summary_cantTell',
|
|
@@ -104,7 +113,12 @@ function runInPage(ctx) {
|
|
|
104
113
|
}
|
|
105
114
|
|
|
106
115
|
if (occurrences.length) {
|
|
107
|
-
return {
|
|
116
|
+
return {
|
|
117
|
+
ruleId: rule.ruleId,
|
|
118
|
+
outcome: 'cantTell',
|
|
119
|
+
severity: rule.defaultSeverity || 'moderate',
|
|
120
|
+
occurrences
|
|
121
|
+
};
|
|
108
122
|
}
|
|
109
123
|
|
|
110
124
|
// Manual rules may only emit cantTell/notApplicable (never pass/fail):
|