@surea11y/core 1.3.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 +46 -2
- package/README.md +109 -35
- package/bin/surea11y-core.js +20 -0
- package/docs/API_STABILITY.md +26 -0
- package/docs/CI_INTEGRATIONS.md +7 -7
- package/docs/ENGINE_OPTIONS.md +1 -1
- package/docs/I18N.md +12 -9
- package/docs/INTEGRATION.md +1 -1
- package/docs/LIMITATIONS.md +1 -1
- package/docs/REPORT.md +1 -1
- package/package.json +50 -16
- package/src/baseline.js +0 -0
- package/src/checks/automatic/area-alt-present.js +4 -6
- package/src/checks/automatic/aria-allowed-attr.js +15 -51
- package/src/checks/automatic/aria-allowed-role.js +2 -0
- package/src/checks/automatic/aria-braille-equivalent.js +2 -0
- package/src/checks/automatic/aria-conditional-attr.js +8 -7
- package/src/checks/automatic/aria-deprecated-role.js +4 -3
- package/src/checks/automatic/aria-hidden-body.js +6 -4
- package/src/checks/automatic/aria-hidden-focus.js +12 -13
- package/src/checks/automatic/aria-prohibited-attr.js +98 -105
- package/src/checks/automatic/aria-prohibited-children.js +56 -87
- package/src/checks/automatic/aria-required-attr.js +6 -7
- package/src/checks/automatic/aria-required-children.js +7 -10
- package/src/checks/automatic/aria-required-parent.js +20 -25
- package/src/checks/automatic/aria-role-name-present.js +2 -0
- package/src/checks/automatic/aria-roles-valid.js +2 -0
- package/src/checks/automatic/aria-valid-attr-value.js +18 -15
- package/src/checks/automatic/aria-valid-attr.js +2 -0
- package/src/checks/automatic/autocomplete-valid.js +2 -0
- package/src/checks/automatic/avoid-inline-spacing.js +3 -2
- package/src/checks/automatic/binary-control-name-present.js +2 -0
- package/src/checks/automatic/button-name-present.js +7 -7
- package/src/checks/automatic/bypass-blocks-present.js +9 -7
- package/src/checks/automatic/canvas-text-alternative-present.js +2 -0
- package/src/checks/automatic/combobox-name-present.js +2 -0
- package/src/checks/automatic/contrast-computable.js +2 -0
- package/src/checks/automatic/contrast-enhanced.js +2 -0
- package/src/checks/automatic/contrast-minimum.js +2 -0
- package/src/checks/automatic/css-orientation-lock.js +21 -27
- package/src/checks/automatic/definition-list-children-valid.js +6 -6
- package/src/checks/automatic/deprecated-elements-not-used.js +4 -2
- package/src/checks/automatic/dialog-name-present.js +10 -10
- package/src/checks/automatic/dlitem-parent-valid.js +2 -0
- package/src/checks/automatic/duplicate-id-aria.js +4 -3
- package/src/checks/automatic/embed-text-alternative-present.js +2 -0
- package/src/checks/automatic/form-control-programmatic-label-present.js +2 -0
- package/src/checks/automatic/form-control-single-label.js +6 -7
- package/src/checks/automatic/html-xml-lang-mismatch.js +2 -0
- package/src/checks/automatic/iframe-focusable-content.js +246 -18
- package/src/checks/automatic/iframe-name-present.js +2 -0
- package/src/checks/automatic/iframe-title-unique.js +3 -1
- package/src/checks/automatic/img-alt-present.js +7 -9
- package/src/checks/automatic/input-image-alt-present.js +4 -6
- package/src/checks/automatic/label-in-name.js +15 -19
- package/src/checks/automatic/language-page-present.js +2 -0
- package/src/checks/automatic/link-in-text-block.js +2 -0
- package/src/checks/automatic/link-name-present.js +2 -0
- package/src/checks/automatic/list-children-valid.js +14 -24
- package/src/checks/automatic/listbox-name-present.js +2 -0
- package/src/checks/automatic/listitem-parent-valid.js +30 -7
- package/src/checks/automatic/menuitem-name-present.js +2 -0
- package/src/checks/automatic/meta-refresh-no-exceptions.js +4 -4
- package/src/checks/automatic/meta-refresh-timing-absent.js +2 -0
- package/src/checks/automatic/meta-viewport-zoom-enabled.js +2 -0
- package/src/checks/automatic/meter-name-present.js +4 -3
- package/src/checks/automatic/nested-interactive-controls-absent.js +27 -5
- package/src/checks/automatic/object-text-alternative-present.js +2 -0
- package/src/checks/automatic/option-name-present.js +2 -0
- package/src/checks/automatic/page-title-present.js +2 -0
- package/src/checks/automatic/progressbar-name-present.js +8 -10
- package/src/checks/automatic/role-img-alt-present.js +4 -4
- package/src/checks/automatic/searchbox-name-present.js +2 -0
- package/src/checks/automatic/server-side-image-map-absent.js +4 -3
- package/src/checks/automatic/slider-name-present.js +2 -0
- package/src/checks/automatic/spinbutton-name-present.js +2 -0
- package/src/checks/automatic/summary-name-present.js +2 -0
- package/src/checks/automatic/svg-image-text-alternative-present.js +2 -0
- package/src/checks/automatic/svg-text-alternative-present.js +17 -5
- package/src/checks/automatic/tab-name-present.js +2 -0
- package/src/checks/automatic/table-headers-attr-valid.js +3 -2
- package/src/checks/automatic/table-th-has-data-cells.js +2 -0
- package/src/checks/automatic/target-size-minimum.js +5 -0
- package/src/checks/automatic/td-has-header.js +24 -1
- package/src/checks/automatic/textbox-name-present.js +2 -0
- package/src/checks/automatic/tooltip-name-present.js +2 -0
- package/src/checks/automatic/treeitem-name-present.js +2 -0
- package/src/checks/automatic/valid-lang.js +2 -0
- package/src/checks/automatic/video-poster-text-alternative-present.js +2 -0
- package/src/checks/manual/accesskeys-manual.js +3 -1
- package/src/checks/manual/area-alt-decorative-manual.js +2 -0
- package/src/checks/manual/area-alt-quality-manual.js +2 -0
- package/src/checks/manual/aria-checked-state-mismatch-manual.js +14 -23
- package/src/checks/manual/aria-text-manual.js +6 -5
- package/src/checks/manual/canvas-text-alternative-quality-manual.js +2 -0
- package/src/checks/manual/css-hidden-focus.js +184 -9
- package/src/checks/manual/embed-text-alternative-quality-manual.js +18 -13
- package/src/checks/manual/empty-heading-manual.js +17 -17
- package/src/checks/manual/empty-table-header-manual.js +52 -25
- package/src/checks/manual/focus-order-semantics-manual.js +16 -4
- package/src/checks/manual/form-control-programmatic-label-quality-manual.js +2 -0
- package/src/checks/manual/heading-order-manual.js +28 -1
- package/src/checks/manual/identical-links-same-purpose-manual.js +2 -0
- package/src/checks/manual/image-redundant-alt-manual.js +21 -1
- package/src/checks/manual/img-alt-decorative-manual.js +2 -0
- package/src/checks/manual/img-alt-quality-manual.js +2 -0
- package/src/checks/manual/input-image-alt-decorative-manual.js +2 -0
- package/src/checks/manual/input-image-alt-quality-manual.js +2 -0
- package/src/checks/manual/label-title-only-manual.js +29 -22
- package/src/checks/manual/landmark-banner-is-top-level-manual.js +54 -47
- package/src/checks/manual/landmark-contentinfo-is-top-level-manual.js +42 -26
- package/src/checks/manual/landmark-main-is-top-level-manual.js +41 -24
- package/src/checks/manual/landmark-no-duplicate-banner-manual.js +16 -23
- package/src/checks/manual/landmark-no-duplicate-contentinfo-manual.js +14 -21
- package/src/checks/manual/landmark-no-duplicate-main-manual.js +10 -13
- package/src/checks/manual/landmark-one-main-manual.js +12 -23
- package/src/checks/manual/landmark-unique-manual.js +37 -52
- package/src/checks/manual/link-name-quality-manual.js +2 -0
- package/src/checks/manual/media-transcript-present-manual.js +2 -0
- package/src/checks/manual/meta-viewport-large-manual.js +3 -1
- package/src/checks/manual/mouse-only-event-handlers-manual.js +2 -0
- package/src/checks/manual/no-autoplay-audio-manual.js +2 -0
- package/src/checks/manual/object-text-alternative-quality-manual.js +2 -0
- package/src/checks/manual/p-as-heading-manual.js +2 -0
- package/src/checks/manual/page-has-heading-one-manual.js +12 -11
- package/src/checks/manual/page-title-patterns-manual.js +2 -0
- package/src/checks/manual/presentation-role-conflict-manual.js +51 -37
- package/src/checks/manual/region-manual.js +27 -36
- package/src/checks/manual/scope-attr-valid-manual.js +3 -1
- package/src/checks/manual/scrollable-region-focusable-manual.js +2 -0
- package/src/checks/manual/skip-link-manual.js +7 -6
- package/src/checks/manual/svg-text-alternative-quality-manual.js +2 -0
- package/src/checks/manual/tabindex-manual.js +3 -1
- package/src/checks/manual/table-duplicate-name-manual.js +5 -4
- package/src/checks/manual/table-fake-caption-manual.js +24 -3
- package/src/checks/manual/video-caption-manual.js +2 -0
- package/src/checks/manual-review.js +2 -0
- package/src/core.js +6772 -2158
- package/src/index.js +2 -0
- package/src/report.js +51 -9
- package/src/sarif.js +18 -3
- package/surea11y.browser.js +2731 -999
- package/bin/core.js +0 -473
- package/docs/CLI.md +0 -128
- package/src/catalogs/composites.wcag.js +0 -454
- 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 -1211
- package/src/core/contrast-helpers.js +0 -1302
- package/src/core/dom-helpers.js +0 -4493
- package/src/core/dom-runner.js +0 -787
- package/src/core/frame-messaging.js +0 -261
- package/src/core/frame-scan.js +0 -190
- package/src/core/rollup-composites.js +0 -127
- package/src/core/rule-meta.js +0 -176
- package/src/coverage/wcag-facets.js +0 -1079
- package/src/coverage/wcag-version-map.js +0 -84
- package/src/i18n/en.js +0 -1228
- package/src/i18n/fr.js +0 -1185
- package/src/policy/contracts.js +0 -18
- package/src/policy/resolvePolicy.js +0 -59
- 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
|
/**
|
|
@@ -13,25 +15,26 @@
|
|
|
13
15
|
* @expectation
|
|
14
16
|
* Each attribute's value conforms to its WAI-ARIA-declared value type:
|
|
15
17
|
* boolean ("true"/"false"), tristate ("true"/"false"/"mixed"), a token
|
|
16
|
-
* from a fixed enumerated set, an integer, a real number, or
|
|
17
|
-
* ID reference (list) that resolves to an existing element in
|
|
18
|
-
* document.
|
|
18
|
+
* from a fixed enumerated set, an integer, a real number, or an empty
|
|
19
|
+
* value or ID reference (list) that resolves to an existing element in
|
|
20
|
+
* the document.
|
|
19
21
|
* @implementation-notes
|
|
20
22
|
* - Not rule-gated on isAccTreeEligible: this remains a static-markup
|
|
21
23
|
* property, while engine-level hidden-subtree filtering still applies
|
|
22
24
|
* unless engineOptions.includeHiddenElements is true.
|
|
23
|
-
* - ID-reference resolution (
|
|
24
|
-
*
|
|
25
|
-
* aria-
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
25
|
+
* - ID-reference resolution (see aria-helpers.js's idExists) only flags
|
|
26
|
+
* idref-list attributes (aria-labelledby, aria-describedby,
|
|
27
|
+
* aria-controls, aria-owns, etc.) when NONE of the space-separated ids
|
|
28
|
+
* resolve — a partially-dangling list (some ids exist, some don't) is
|
|
29
|
+
* left unflagged (only invalidate when every token fails to resolve).
|
|
30
|
+
* Single-idref attributes (aria-activedescendant, aria-errormessage) are
|
|
31
|
+
* flagged whenever their one id doesn't resolve.
|
|
32
|
+
* - An explicitly-EMPTY idref/idref-list value (e.g.
|
|
33
|
+
* `aria-describedby=""`) is valid, not a violation — `allowEmpty` holds
|
|
34
|
+
* for every idref/idref-list ARIA attribute. A common, deliberate pattern
|
|
35
|
+
* in templated markup (e.g. React conditionally rendering
|
|
36
|
+
* `aria-describedby={hasError ? errorId : ''}`); flagging it would be a
|
|
37
|
+
* false positive.
|
|
35
38
|
*/
|
|
36
39
|
|
|
37
40
|
const id = 'aria-valid-attr-value';
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/* SPDX-License-Identifier: MPL-2.0 */
|
|
2
|
+
|
|
1
3
|
'use strict';
|
|
2
4
|
|
|
3
5
|
/**
|
|
@@ -19,8 +21,7 @@
|
|
|
19
21
|
* @implementation-notes
|
|
20
22
|
* - Static text-based parsing of the style attribute string (regex for
|
|
21
23
|
* the three property names followed by `!important`), not computed
|
|
22
|
-
* style
|
|
23
|
-
* rule checks.
|
|
24
|
+
* style.
|
|
24
25
|
*/
|
|
25
26
|
|
|
26
27
|
const id = 'avoid-inline-spacing';
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/* SPDX-License-Identifier: MPL-2.0 */
|
|
2
|
+
|
|
1
3
|
'use strict';
|
|
2
4
|
|
|
3
5
|
// NOTE: Repo ruleId contract requires ENGINE_TAG prefix in the rule id.
|
|
@@ -47,9 +49,8 @@ function runInPage(ctx) {
|
|
|
47
49
|
// own accessible name (img alt, aria-label/aria-labelledby, title) when
|
|
48
50
|
// it has one, not just literal text nodes. See getContentNameInfo's
|
|
49
51
|
// header comment in src/core/dom-helpers.js for the full rationale
|
|
50
|
-
// (
|
|
51
|
-
// "<
|
|
52
|
-
// icon-button pattern).
|
|
52
|
+
// (covers the common "<a><img alt='...'></a>" logo-link /
|
|
53
|
+
// "<button><img alt='...'></button>" icon-button pattern).
|
|
53
54
|
if (helpers.getContentNameInfo) {
|
|
54
55
|
const info = helpers.getContentNameInfo(container, ctx);
|
|
55
56
|
return info && info.present ? info.value : '';
|
|
@@ -107,10 +108,9 @@ function runInPage(ctx) {
|
|
|
107
108
|
// one of these roles is no longer semantically a button — per the WAI-ARIA
|
|
108
109
|
// Accessible Name and Description Computation spec these roles are
|
|
109
110
|
// name-from-author-only, and their rendered content represents a VALUE,
|
|
110
|
-
// not a NAME.
|
|
111
|
-
//
|
|
112
|
-
//
|
|
113
|
-
// crediting it as the accessible name masked a real missing-name bug.
|
|
111
|
+
// not a NAME. E.g. <button role="combobox">List</button> where "List" is
|
|
112
|
+
// the combobox's currently selected value, not a label — crediting it as
|
|
113
|
+
// the accessible name would mask a missing name.
|
|
114
114
|
const VALUE_ROLES = [
|
|
115
115
|
'textbox',
|
|
116
116
|
'progressbar',
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/* SPDX-License-Identifier: MPL-2.0 */
|
|
2
|
+
|
|
1
3
|
'use strict';
|
|
2
4
|
|
|
3
5
|
/**
|
|
@@ -27,8 +29,8 @@
|
|
|
27
29
|
* mechanism (e.g. a screen reader's "jump by heading" command).
|
|
28
30
|
* @implementation-notes
|
|
29
31
|
* - This rule intentionally checks presence, not position, for the
|
|
30
|
-
* same-page-anchor condition (b): a
|
|
31
|
-
*
|
|
32
|
+
* same-page-anchor condition (b): a full bypass algorithm is heuristic
|
|
33
|
+
* (see ROADMAP.md's Tier 1a note on why this rule was
|
|
32
34
|
* deferred from the rest of that batch), and getting DOM-order /
|
|
33
35
|
* keyboard-focus-order positioning exactly right without introducing
|
|
34
36
|
* false positives is materially harder than the rest of Tier 1a. Being
|
|
@@ -115,12 +117,12 @@ function runInPage(ctx) {
|
|
|
115
117
|
}
|
|
116
118
|
|
|
117
119
|
// Filters through isAccTreeEligible (hidden/aria-hidden/display:none/inert
|
|
118
|
-
// don't count as "a bypass mechanism is present") -- see
|
|
119
|
-
// one-manual.js
|
|
120
|
-
// page
|
|
120
|
+
// don't count as "a bypass mechanism is present") -- see
|
|
121
|
+
// page-has-heading-one-manual.js for the identical real-world trigger
|
|
122
|
+
// (e.g. a page whose only <h1> sits inside a display:none ancestor,
|
|
121
123
|
// unreachable by sighted and screen reader users alike). A fully
|
|
122
|
-
// non-rendered <main>/heading
|
|
123
|
-
//
|
|
124
|
+
// non-rendered <main>/heading must not be credited here, since that would
|
|
125
|
+
// wrongly return `pass` for a page with zero actual bypass mechanisms.
|
|
124
126
|
function hasMainLandmark() {
|
|
125
127
|
for (const el of queryAll('main, [role="main"]')) {
|
|
126
128
|
if (el && isExposedToAt(el)) return true;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/* SPDX-License-Identifier: MPL-2.0 */
|
|
2
|
+
|
|
1
3
|
'use strict';
|
|
2
4
|
|
|
3
5
|
/**
|
|
@@ -20,25 +22,17 @@
|
|
|
20
22
|
* display orientation unless that orientation is essential.
|
|
21
23
|
* @implementation-notes
|
|
22
24
|
* - The rotation DEGREE is what makes this the exploit signature, not
|
|
23
|
-
* merely the presence of a `rotate()` function
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
* version of this rule flagged the bare presence of "rotate(" in an
|
|
29
|
-
* orientation-media-query rule regardless of angle — a real, confirmed
|
|
30
|
-
* false positive found via the live-DOM cross-engine corpus 2026-07-23
|
|
31
|
-
* on a real page (Times of India) whose `@media (orientation:portrait)`
|
|
32
|
-
* block only rotated a small decorative arrow icon 45 degrees, nothing
|
|
33
|
-
* resembling a page-orientation lock.
|
|
25
|
+
* merely the presence of a `rotate()` function: a small decorative icon
|
|
26
|
+
* rotated 45 degrees inside an orientation media query is not a page
|
|
27
|
+
* lock. Compute the actual rotation angle and only flag ~90/~270 degrees,
|
|
28
|
+
* excluding ~0/~180 (a no-op or a flip, neither of which changes
|
|
29
|
+
* portrait<->landscape).
|
|
34
30
|
* - Only `rotate`/`rotateZ` (transform functions) and the standalone CSS
|
|
35
|
-
* `rotate` property are parsed for degrees
|
|
36
|
-
*
|
|
37
|
-
*
|
|
38
|
-
*
|
|
39
|
-
*
|
|
40
|
-
* value" work already deferred elsewhere in this engine, e.g.
|
|
41
|
-
* `table-th-has-data-cells`'s narrower positional-header algorithm).
|
|
31
|
+
* `rotate` property are parsed for degrees, from the two sources
|
|
32
|
+
* `style.transform`/`style.rotate`; `matrix()`/`matrix3d()`/`rotate3d()`
|
|
33
|
+
* are not decomposed into an equivalent angle (deliberately deferred as
|
|
34
|
+
* the same class of higher-complexity/lower-value work deferred elsewhere,
|
|
35
|
+
* e.g. `table-th-has-data-cells`'s narrower positional-header algorithm).
|
|
42
36
|
* - Cross-origin stylesheets throw on `.cssRules` access (browser
|
|
43
37
|
* security model) and are skipped — same class of limitation as any
|
|
44
38
|
* check that can only see same-origin/inspectable content (compare
|
|
@@ -98,9 +92,9 @@ function runInPage(ctx) {
|
|
|
98
92
|
}
|
|
99
93
|
|
|
100
94
|
// Converts a rotate()/rotateZ() angle argument (with unit) to degrees.
|
|
101
|
-
//
|
|
102
|
-
//
|
|
103
|
-
//
|
|
95
|
+
// Only deg/grad/rad/turn are recognized; a unitless or unrecognized value
|
|
96
|
+
// contributes 0 (ignored, not treated as a lock -- an unparseable angle
|
|
97
|
+
// isn't a confirmed exploit).
|
|
104
98
|
function angleToDegrees(raw) {
|
|
105
99
|
const m = /(-?[\d.]+)\s*(deg|grad|rad|turn)/i.exec(raw);
|
|
106
100
|
if (!m) return 0;
|
|
@@ -127,17 +121,17 @@ function runInPage(ctx) {
|
|
|
127
121
|
return total;
|
|
128
122
|
}
|
|
129
123
|
|
|
130
|
-
// Whole-page-orientation-lock detection
|
|
131
|
-
//
|
|
132
|
-
//
|
|
133
|
-
//
|
|
134
|
-
//
|
|
124
|
+
// Whole-page-orientation-lock detection (see @implementation-notes): a
|
|
125
|
+
// rotation near 0 or 180 degrees (mod 180) is a no-op or a flip, neither
|
|
126
|
+
// of which changes portrait<->landscape, so it's NOT a lock; only a
|
|
127
|
+
// rotation near 90 or 270 degrees (mod 90, once the 0/180 case is
|
|
128
|
+
// excluded) is.
|
|
135
129
|
function isLockingRotation(styleDecl) {
|
|
136
130
|
if (!styleDecl) return false;
|
|
137
131
|
const transformVal =
|
|
138
132
|
trim(styleDecl.transform) ||
|
|
139
133
|
trim(styleDecl.getPropertyValue ? styleDecl.getPropertyValue('-webkit-transform') : '');
|
|
140
|
-
// The standalone CSS `rotate` property (distinct from `transform: rotate()`)
|
|
134
|
+
// The standalone CSS `rotate` property (distinct from `transform: rotate()`).
|
|
141
135
|
const rotatePropVal = trim(
|
|
142
136
|
styleDecl.getPropertyValue ? styleDecl.getPropertyValue('rotate') : ''
|
|
143
137
|
);
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/* SPDX-License-Identifier: MPL-2.0 */
|
|
2
|
+
|
|
1
3
|
'use strict';
|
|
2
4
|
|
|
3
5
|
/**
|
|
@@ -18,12 +20,11 @@
|
|
|
18
20
|
* violation (see implementation-notes). Any other direct or wrapped child
|
|
19
21
|
* breaks the description-list semantics assistive technologies rely on.
|
|
20
22
|
* @implementation-notes
|
|
21
|
-
* - Only one level of <div> wrapping is flattened
|
|
22
|
-
* reference engine's definition-list algorithm — a <div> nested inside
|
|
23
|
+
* - Only one level of <div> wrapping is flattened — a <div> nested inside
|
|
23
24
|
* another wrapping <div> is not flattened further and its contents are
|
|
24
25
|
* reported invalid.
|
|
25
|
-
* -
|
|
26
|
-
*
|
|
26
|
+
* - The dt/dd pairing is only required "when not empty". A flattened set
|
|
27
|
+
* with NEITHER dt nor dd
|
|
27
28
|
* — whether from an empty wrapping <div>, only <script>/<template>/
|
|
28
29
|
* <style> content, or a genuinely childless <dl> — is not flagged; only
|
|
29
30
|
* an unbalanced dt/dd pairing is a real structural problem.
|
|
@@ -108,8 +109,7 @@ function runInPage(ctx) {
|
|
|
108
109
|
}
|
|
109
110
|
const dedupedInvalidTags = [...new Set(invalidTags)];
|
|
110
111
|
|
|
111
|
-
//
|
|
112
|
-
// the dt/dd pairing is only required "when not empty" — a <dl> with
|
|
112
|
+
// The dt/dd pairing is only required "when not empty" — a <dl> with
|
|
113
113
|
// NEITHER dt nor dd (whether genuinely childless after flattening, only
|
|
114
114
|
// passthrough script/template/style content, or an empty wrapping div)
|
|
115
115
|
// is vacuously fine, not a violation. Only an UNBALANCED pairing (dt
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/* SPDX-License-Identifier: MPL-2.0 */
|
|
2
|
+
|
|
1
3
|
'use strict';
|
|
2
4
|
|
|
3
5
|
/**
|
|
@@ -14,8 +16,8 @@
|
|
|
14
16
|
* @expectation
|
|
15
17
|
* Neither element is present. Since their movement can never be paused,
|
|
16
18
|
* stopped, or hidden by the user, presence is itself the violation — this
|
|
17
|
-
* rule has no partial-pass case
|
|
18
|
-
*
|
|
19
|
+
* rule has no partial-pass case (it reports only when the element is
|
|
20
|
+
* found).
|
|
19
21
|
* @implementation-notes
|
|
20
22
|
* - Not rule-gated on isAccTreeEligible: presence in markup is itself the
|
|
21
23
|
* violation, independent of visibility (moving/blinking content inside a
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/* SPDX-License-Identifier: MPL-2.0 */
|
|
2
|
+
|
|
1
3
|
'use strict';
|
|
2
4
|
|
|
3
5
|
const id = 'dialog-name-present';
|
|
@@ -55,16 +57,14 @@ function runInPage(ctx) {
|
|
|
55
57
|
// Resolve via the shared getTextFromIdRefs helper — computes each
|
|
56
58
|
// referenced element's own ACCESSIBLE NAME (aria-label, then
|
|
57
59
|
// aria-labelledby, then a value-like name, then content, then title),
|
|
58
|
-
// not just its content text.
|
|
59
|
-
//
|
|
60
|
-
//
|
|
61
|
-
// title attribute
|
|
62
|
-
// HTML-AAM, so "name from content" is always
|
|
63
|
-
//
|
|
64
|
-
//
|
|
65
|
-
//
|
|
66
|
-
// pattern was hand-copied into 15 other *-name-present rules; all
|
|
67
|
-
// fixed the same way.
|
|
60
|
+
// not just its content text. This matters when aria-labelledby points
|
|
61
|
+
// at an <iframe title="..."> (e.g. BBC News' cookie-consent dialog,
|
|
62
|
+
// pointing at an `<iframe title="SP Consent Message">`), whose only
|
|
63
|
+
// name source is its title attribute — an iframe's content is
|
|
64
|
+
// opaque/cross-origin per HTML-AAM, so "name from content" is always
|
|
65
|
+
// empty. Computing only name-from-content of the referenced node would
|
|
66
|
+
// silently miss the title fallback and report no accessible name at
|
|
67
|
+
// all, the same pattern every other *-name-present rule guards against.
|
|
68
68
|
if (helpers.getTextFromIdRefs) {
|
|
69
69
|
try {
|
|
70
70
|
const r = helpers.getTextFromIdRefs(raw, ctx, { maxRefs: maxRefs || 8 });
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/* SPDX-License-Identifier: MPL-2.0 */
|
|
2
|
+
|
|
1
3
|
'use strict';
|
|
2
4
|
|
|
3
5
|
/**
|
|
@@ -18,9 +20,8 @@
|
|
|
18
20
|
* which element the reference resolves to (typically the first, silently
|
|
19
21
|
* dropping the others).
|
|
20
22
|
* @implementation-notes
|
|
21
|
-
* - Scoped deliberately to ids referenced by ARIA
|
|
22
|
-
*
|
|
23
|
-
* check — see ROADMAP.md's "Skip" list).
|
|
23
|
+
* - Scoped deliberately to ids referenced by ARIA, not the broader/
|
|
24
|
+
* deprecated page-wide duplicate-id check (see ROADMAP.md's "Skip" list).
|
|
24
25
|
* - Document-wide by design: id uniqueness and ARIA id references are a
|
|
25
26
|
* whole-document property, not scoped to a sub-root.
|
|
26
27
|
*/
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
/* SPDX-License-Identifier: MPL-2.0 */
|
|
2
|
+
|
|
1
3
|
'use strict';
|
|
2
4
|
|
|
3
5
|
/**
|
|
@@ -17,16 +19,13 @@
|
|
|
17
19
|
* are ambiguous: many screen readers only announce one of them, and it
|
|
18
20
|
* is not deterministic which.
|
|
19
21
|
* @implementation-notes
|
|
20
|
-
* -
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* one).
|
|
22
|
+
* - Distinct, atomic decision from form-control-programmatic-label-present
|
|
23
|
+
* (that rule checks a label exists at all; this one checks there is at
|
|
24
|
+
* most one).
|
|
24
25
|
* - A label that isn't accessibility-tree-eligible (display:none,
|
|
25
26
|
* aria-hidden, etc.) is excluded before counting: it can't contribute to
|
|
26
27
|
* the control's accessible name, so it can't create the ambiguity this
|
|
27
|
-
* rule exists to catch.
|
|
28
|
-
* multiple-label check, which filters hidden labels the same way before
|
|
29
|
-
* deciding whether there's more than one.
|
|
28
|
+
* rule exists to catch.
|
|
30
29
|
*/
|
|
31
30
|
|
|
32
31
|
const id = 'form-control-single-label';
|