@surea11y/core 1.1.0 → 1.1.1
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 +10 -2
- package/docs/ENGINE_OPTIONS.md +2 -0
- package/docs/LIMITATIONS.md +1 -1
- package/package.json +1 -1
- package/src/checks/automatic/aria-allowed-attr.js +3 -1
- package/src/checks/automatic/aria-allowed-role.js +3 -1
- package/src/checks/automatic/aria-prohibited-attr.js +3 -1
- package/src/checks/automatic/aria-valid-attr-value.js +3 -1
- package/src/checks/automatic/aria-valid-attr.js +3 -1
- package/src/checks/automatic/deprecated-elements-not-used.js +3 -1
- package/src/checks/automatic/iframe-title-unique.js +3 -2
- package/src/checks/automatic/table-headers-attr-valid.js +3 -1
- package/src/checks/automatic/table-th-has-data-cells.js +3 -1
- package/src/checks/manual/aria-checked-state-mismatch-manual.js +4 -2
- package/src/core/dom-helpers.js +34 -1
- package/src/core/dom-runner.js +4 -0
- package/src/core.js +114 -3
package/CHANGELOG.md
CHANGED
|
@@ -4,6 +4,14 @@ All notable changes to this project are documented here, in [Keep a Changelog](h
|
|
|
4
4
|
|
|
5
5
|
## [Unreleased]
|
|
6
6
|
|
|
7
|
+
## [1.1.1] - 2026-07-29
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
- **`engineOptions.includeHiddenElements` (default `false`)**: helper-driven rules now skip elements hidden by `display:none` (on the element or any ancestor), `visibility:hidden`/`collapse`, the `[hidden]` attribute, closed `<details>`, and other structurally-non-rendered content by default, matching the visibility-aware behavior of other established engines. Filtering happens upstream in the shared `queryAllSmart` helper, before a rule's own pass/fail logic runs, so it's a candidate-list exclusion, not a post-hoc annotation. Set `engineOptions.includeHiddenElements: true` to restore the previous behavior and evaluate hidden/collapsed subtrees anyway (e.g. to catch a markup defect, like a broken ARIA ID reference, before a `<dialog>` ever opens). 10 rule files whose own logic intentionally doesn't call the underlying eligibility check directly (static-markup-validity rules such as `aria-valid-attr`, `aria-valid-attr-value`, `aria-allowed-attr`, `aria-allowed-role`, `aria-prohibited-attr`, `table-headers-attr-valid`, `table-th-has-data-cells`, `deprecated-elements-not-used`, `iframe-title-unique`, `aria-checked-state-mismatch-manual`) still inherit this filtering through `queryAllSmart`; their doc comments were updated to say so. See `docs/ENGINE_OPTIONS.md` and `docs/LIMITATIONS.md`.
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
- `docs/LIMITATIONS.md`: the `<dialog>`/UA-stylesheet-hidden-content note was stale — it claimed static-markup-validity checks still evaluate hidden content, which this release's default change makes no longer true. Corrected to describe the current default and how to opt back in.
|
|
14
|
+
|
|
7
15
|
## [1.1.0] - 2026-07-28
|
|
8
16
|
|
|
9
17
|
### Added
|
|
@@ -28,7 +36,7 @@ All notable changes to this project are documented here, in [Keep a Changelog](h
|
|
|
28
36
|
- README: the JSON output example referenced a nonexistent rule id (`link-name-quality`); corrected to the real id, `link-name-quality-manual`.
|
|
29
37
|
- README: Quick Start code samples labeled the runner's four positional arguments as `url, ruleFilter, options, policy`; corrected to the actual names (`pageUrl, contextSelector, engineOptions, runOnly`) used consistently elsewhere in the docs.
|
|
30
38
|
|
|
31
|
-
## [1.
|
|
39
|
+
## [1.0.0] - 2026-07-26
|
|
32
40
|
|
|
33
41
|
### Added
|
|
34
42
|
- 125 rules (77 automatic/`fail`-capable, 48 manual/advisory) — see `docs/RULE_CATALOG.md` for the full list.
|
|
@@ -61,7 +69,7 @@ See `docs/LIMITATIONS.md` — structural (keyboard-trap detection, reflow-at-zoo
|
|
|
61
69
|
|
|
62
70
|
---
|
|
63
71
|
|
|
64
|
-
|
|
72
|
+
# How to add an entry
|
|
65
73
|
|
|
66
74
|
When you ship a change worth calling out to consumers (not every commit):
|
|
67
75
|
1. Add a bullet under `[Unreleased]`, in the right subsection (`Added`, `Changed`, `Fixed`, `Deprecated`, `Removed`, `Security`) — create the subsection if it doesn't exist yet for this cycle.
|
package/docs/ENGINE_OPTIONS.md
CHANGED
|
@@ -72,6 +72,7 @@ runDomRulesInPage(url, null, {
|
|
|
72
72
|
```js
|
|
73
73
|
const engineOptions = {
|
|
74
74
|
locale: 'en', // default 'en'; falls back to 'en' per-string if a key is missing in the requested locale
|
|
75
|
+
includeHiddenElements: false, // default false — set true to evaluate hidden/collapsed subtrees too
|
|
75
76
|
includeShadowDom: true, // default true — opt OUT with `false` to skip open shadow roots
|
|
76
77
|
excludeSelectors: ['#cookie-banner', '.third-party-widget'], // array or comma-separated string
|
|
77
78
|
timestamp: '2026-07-20T12:00:00Z', // optional — engine has no built-in clock, see OUTPUT_SCHEMA.md
|
|
@@ -114,6 +115,7 @@ const engineOptions = {
|
|
|
114
115
|
| Option | Meaning |
|
|
115
116
|
|---|---|
|
|
116
117
|
| `locale` | Any string; resolution is per-string with graceful fallback (requested locale → `en` → the rule's literal English fallback text), so a partially-translated locale never produces missing text. See [`I18N.md`](./I18N.md) for current locale coverage. |
|
|
118
|
+
| `includeHiddenElements` | Default `false`: helper queries exclude elements hidden by structural/CSS mechanisms such as `display:none`, `[hidden]`, closed `<details>`, and hidden rendering-only host elements (with descendants excluded too). Set `true` to include those hidden/collapsed subtrees in evaluation (legacy/static-markup behavior). |
|
|
117
119
|
| `includeShadowDom` | Default `true`: rules using `helpers.queryAllSmart` traverse into open shadow roots. Set `false` to scan only the light DOM. Closed shadow roots are never reachable either way (no DOM API exposes them). |
|
|
118
120
|
| `excludeSelectors` | Elements matching any of these selectors (and their descendants) are skipped entirely, for **every** rule — useful for cookie banners, third-party embeds, or known-noisy widgets you don't control. To exclude something from just one specific rule instead, use `rules[ruleId].excludeSelectors` below. |
|
|
119
121
|
| `timestamp` | Passed straight through to the result's top-level `timestamp` field; the engine does not generate one itself (deterministic-by-design). |
|
package/docs/LIMITATIONS.md
CHANGED
|
@@ -13,7 +13,7 @@ surea11y is a **static DOM scan**: it reads the DOM tree and computed styles at
|
|
|
13
13
|
## Environment-dependent — depends on how you run it
|
|
14
14
|
|
|
15
15
|
- **jsdom (Node, no real browser) has no CSS layout engine.** Rules needing real geometry — most notably `target-size-minimum` (WCAG 2.5.8, needs real `getBoundingClientRect()`) — report `notApplicable` under plain jsdom rather than guess. Run under a real browser (Puppeteer/Playwright — see [`INTEGRATION.md`](./INTEGRATION.md) Pattern 2) to get real findings from these rules.
|
|
16
|
-
- **`<dialog>` and other elements hidden by the default UA stylesheet** (no `open` attribute, `display: none` by spec) are
|
|
16
|
+
- **`<dialog>` and other elements hidden by the default UA stylesheet** (no `open` attribute, `display: none` by spec), along with any other subtree hidden via `display:none`, `visibility:hidden`, `[hidden]`, or closed `<details>`, are **excluded from rule evaluation by default** — matching the visibility-aware behavior of other established engines. This is a deliberate default (`engineOptions.includeHiddenElements: false`), not an oversight: hidden content isn't reachable by assistive technology or keyboard until it's shown, so flagging a markup defect inside it by default would often be noise. Set `engineOptions.includeHiddenElements: true` to evaluate hidden/collapsed subtrees anyway — e.g. to catch a markup defect (like a broken ARIA ID reference) before a dialog ever opens. See [`ENGINE_OPTIONS.md`](./ENGINE_OPTIONS.md#engineoptions--the-rest) for the option and exactly which hiding mechanisms it covers.
|
|
17
17
|
- **Static markup vs. live/post-hydration DOM state.** The rule logic itself is DOM-source-agnostic — it evaluates whatever DOM it's handed, whether that's jsdom-parsed static HTML (Pattern 1) or an already-loaded, already-hydrated real browser tab (Pattern 2, see [`INTEGRATION.md`](./INTEGRATION.md)). But the CLI (`npx @surea11y/core scan <url>`) specifically fetches static HTML only, with no JS execution — see [`CLI.md`](./CLI.md). For a JS-framework-hydrated widget whose server-rendered markup intentionally ships one state before client JS syncs it (e.g. `<input type="checkbox" aria-checked="true">` shipped before client JS sets the native `checked` property to match on hydration — an extremely common, entirely legitimate pattern), a CLI scan only sees the pre-hydration markup. Other engines running inside an actual loaded browser tab see the post-hydration state instead, so the two can disagree on exactly this class of element for reasons that have nothing to do with either engine's rule correctness. `aria-checked-state-mismatch` is deliberately `manual`/`cantTell`-capped for this exact reason rather than a hard `fail`. If you need live-DOM accuracy for hydration-sensitive checks, run the library directly against an already-loaded page via Pattern 2, not the static-fetch CLI.
|
|
18
18
|
|
|
19
19
|
## Deliberately not attempted — judgment calls, not automatable safely
|
package/package.json
CHANGED
|
@@ -70,7 +70,9 @@
|
|
|
70
70
|
* menuitemcheckbox, menuitemradio. `aria-level` added to: tablist.
|
|
71
71
|
* - `tree`'s `aria-readonly` removed: not in aria-query's resolved
|
|
72
72
|
* props for `tree` (was an unverified carryover, not spec-backed).
|
|
73
|
-
* - Not gated on isAccTreeEligible: this
|
|
73
|
+
* - Not rule-gated on isAccTreeEligible: this remains a static-markup
|
|
74
|
+
* property, while engine-level hidden-subtree filtering still applies
|
|
75
|
+
* unless engineOptions.includeHiddenElements is true.
|
|
74
76
|
*/
|
|
75
77
|
|
|
76
78
|
const id = 'aria-allowed-attr';
|
|
@@ -18,7 +18,9 @@
|
|
|
18
18
|
* - Deliberately scoped to elements present in ALLOWED_ROLES_BY_ELEMENT;
|
|
19
19
|
* elements without an asserted constraint are treated as "no constraint"
|
|
20
20
|
* (not flagged) rather than guessed at — see that table's header comment.
|
|
21
|
-
* - Not gated on isAccTreeEligible: this
|
|
21
|
+
* - Not rule-gated on isAccTreeEligible: this remains a static-markup
|
|
22
|
+
* property, while engine-level hidden-subtree filtering still applies
|
|
23
|
+
* unless engineOptions.includeHiddenElements is true.
|
|
22
24
|
*/
|
|
23
25
|
|
|
24
26
|
const id = 'aria-allowed-role';
|
|
@@ -55,7 +55,9 @@
|
|
|
55
55
|
* `definition_role` page even demonstrates `aria-labelledby` usage on
|
|
56
56
|
* it directly. A real, confirmed documentation bug on MDN's side, not
|
|
57
57
|
* a gap here.
|
|
58
|
-
* - Not gated on isAccTreeEligible: this
|
|
58
|
+
* - Not rule-gated on isAccTreeEligible: this remains a static-markup
|
|
59
|
+
* property, while engine-level hidden-subtree filtering still applies
|
|
60
|
+
* unless engineOptions.includeHiddenElements is true.
|
|
59
61
|
*/
|
|
60
62
|
|
|
61
63
|
const id = 'aria-prohibited-attr';
|
|
@@ -17,7 +17,9 @@
|
|
|
17
17
|
* ID reference (list) that resolves to an existing element in the
|
|
18
18
|
* document.
|
|
19
19
|
* @implementation-notes
|
|
20
|
-
* - Not gated on isAccTreeEligible: this
|
|
20
|
+
* - Not rule-gated on isAccTreeEligible: this remains a static-markup
|
|
21
|
+
* property, while engine-level hidden-subtree filtering still applies
|
|
22
|
+
* unless engineOptions.includeHiddenElements is true.
|
|
21
23
|
* - ID-reference resolution (added 2026-07-20, see aria-helpers.js's
|
|
22
24
|
* idExists) only flags idref-list attributes (aria-labelledby,
|
|
23
25
|
* aria-describedby, aria-controls, aria-owns, etc.) when NONE of the
|
|
@@ -18,7 +18,9 @@
|
|
|
18
18
|
* - Distinct from aria-valid-attr-value (which validates the VALUE
|
|
19
19
|
* of a recognized attribute) — this rule only validates the attribute
|
|
20
20
|
* NAME.
|
|
21
|
-
* - Not gated on isAccTreeEligible: this
|
|
21
|
+
* - Not rule-gated on isAccTreeEligible: this remains a static-markup
|
|
22
|
+
* property, while engine-level hidden-subtree filtering still applies
|
|
23
|
+
* unless engineOptions.includeHiddenElements is true.
|
|
22
24
|
*/
|
|
23
25
|
|
|
24
26
|
const id = 'aria-valid-attr';
|
|
@@ -17,10 +17,12 @@
|
|
|
17
17
|
* rule has no partial-pass case, matching a widely-used reference engine's `blink`/`marquee`
|
|
18
18
|
* rules (which report only when the element is found).
|
|
19
19
|
* @implementation-notes
|
|
20
|
-
* - Not gated on isAccTreeEligible: presence in markup is itself the
|
|
20
|
+
* - Not rule-gated on isAccTreeEligible: presence in markup is itself the
|
|
21
21
|
* violation, independent of visibility (moving/blinking content inside a
|
|
22
22
|
* hidden ancestor could still become visible later without a code
|
|
23
23
|
* change, so hiding it today does not remove the underlying defect).
|
|
24
|
+
* Engine-level hidden-subtree filtering still applies unless
|
|
25
|
+
* engineOptions.includeHiddenElements is true.
|
|
24
26
|
*/
|
|
25
27
|
|
|
26
28
|
const id = 'deprecated-elements-not-used';
|
|
@@ -19,8 +19,9 @@
|
|
|
19
19
|
* - Compares the title ATTRIBUTE specifically, not the full computed
|
|
20
20
|
* accessible name (aria-label could legitimately differ in wording even
|
|
21
21
|
* when title happens to collide) — matches a widely-used reference engine's frame-title-unique.
|
|
22
|
-
* - Not gated on isAccTreeEligible:
|
|
23
|
-
* property
|
|
22
|
+
* - Not rule-gated on isAccTreeEligible: duplicate titles are a static
|
|
23
|
+
* markup property. Engine-level hidden-subtree filtering still applies
|
|
24
|
+
* unless engineOptions.includeHiddenElements is true.
|
|
24
25
|
*/
|
|
25
26
|
|
|
26
27
|
const id = 'iframe-title-unique';
|
|
@@ -16,7 +16,9 @@
|
|
|
16
16
|
* - One occurrence per offending cell (not per bad token), listing every
|
|
17
17
|
* invalid reference — matches a widely-used reference engine's
|
|
18
18
|
* td-headers-attr reporting granularity.
|
|
19
|
-
* - Not gated on isAccTreeEligible: this
|
|
19
|
+
* - Not rule-gated on isAccTreeEligible: this remains a static-markup
|
|
20
|
+
* property, while engine-level hidden-subtree filtering still applies
|
|
21
|
+
* unless engineOptions.includeHiddenElements is true.
|
|
20
22
|
*/
|
|
21
23
|
|
|
22
24
|
const id = 'table-headers-attr-valid';
|
|
@@ -28,7 +28,9 @@
|
|
|
28
28
|
* if some particular <th> in it doesn't actually describe any cell
|
|
29
29
|
* (false negative, not a false positive — acceptable under this
|
|
30
30
|
* engine's philosophy).
|
|
31
|
-
* - Not gated on isAccTreeEligible: this
|
|
31
|
+
* - Not rule-gated on isAccTreeEligible: this remains a static-markup
|
|
32
|
+
* property, while engine-level hidden-subtree filtering still applies
|
|
33
|
+
* unless engineOptions.includeHiddenElements is true.
|
|
32
34
|
*/
|
|
33
35
|
|
|
34
36
|
const id = 'table-th-has-data-cells';
|
|
@@ -42,8 +42,10 @@
|
|
|
42
42
|
* - Any `aria-checked` value other than "true"/"false"/"mixed" (checkbox)
|
|
43
43
|
* or "true"/"false" (radio) is treated as equivalent to "false" — also
|
|
44
44
|
* matches that reference engine's own normalization exactly.
|
|
45
|
-
* - Not gated on isAccTreeEligible: whether the accessible state
|
|
46
|
-
* is a markup-correctness property
|
|
45
|
+
* - Not rule-gated on isAccTreeEligible: whether the accessible state
|
|
46
|
+
* matches is still a markup-correctness property, while engine-level
|
|
47
|
+
* hidden-subtree filtering applies unless engineOptions.includeHiddenElements
|
|
48
|
+
* is true.
|
|
47
49
|
*/
|
|
48
50
|
|
|
49
51
|
const id = 'aria-checked-state-mismatch';
|
package/src/core/dom-helpers.js
CHANGED
|
@@ -115,6 +115,10 @@ function createDomHelpers(opts) {
|
|
|
115
115
|
})();
|
|
116
116
|
// Default on: opt OUT with `includeShadowDom: false`, not opt in.
|
|
117
117
|
const includeShadowDom = !(opts && opts.includeShadowDom === false);
|
|
118
|
+
// Default off: by default, helper queries skip structurally/CSS-hidden
|
|
119
|
+
// subtrees (display:none, [hidden], closed <details>, etc.). Callers can
|
|
120
|
+
// opt out with includeHiddenElements:true.
|
|
121
|
+
const includeHiddenElements = !!(opts && opts.includeHiddenElements === true);
|
|
118
122
|
const excludeSelectors = Array.isArray(opts && opts.excludeSelectors) ? opts.excludeSelectors : [];
|
|
119
123
|
|
|
120
124
|
// Rule-scoped excludes (engineOptions.rules[ruleId].excludeSelectors), set
|
|
@@ -1081,8 +1085,37 @@ function createDomHelpers(opts) {
|
|
|
1081
1085
|
return results;
|
|
1082
1086
|
}
|
|
1083
1087
|
|
|
1088
|
+
const HARD_HIDDEN_REASONS = new Set([
|
|
1089
|
+
'displayNone',
|
|
1090
|
+
'hiddenAttr',
|
|
1091
|
+
'detailsClosed',
|
|
1092
|
+
'templateContent',
|
|
1093
|
+
'nonRenderedElement',
|
|
1094
|
+
'inputHidden',
|
|
1095
|
+
'visibilityHidden'
|
|
1096
|
+
]);
|
|
1097
|
+
|
|
1084
1098
|
function queryAllSmart(sel) {
|
|
1085
|
-
|
|
1099
|
+
let list = includeShadowDom ? queryAllDeep(sel) : queryAll(sel);
|
|
1100
|
+
|
|
1101
|
+
// Global hidden-content policy: skip nodes that are fully excluded from
|
|
1102
|
+
// rendered visibility by default (unless includeHiddenElements:true).
|
|
1103
|
+
if (!includeHiddenElements) {
|
|
1104
|
+
list = list.filter((el) => {
|
|
1105
|
+
try {
|
|
1106
|
+
const vis = isAccTreeEligible(el);
|
|
1107
|
+
if (!vis || vis.eligible !== false) return true;
|
|
1108
|
+
const reasons = Array.isArray(vis.reasons) ? vis.reasons : [];
|
|
1109
|
+
for (const r of reasons) {
|
|
1110
|
+
if (HARD_HIDDEN_REASONS.has(r)) return false;
|
|
1111
|
+
}
|
|
1112
|
+
return true;
|
|
1113
|
+
} catch {
|
|
1114
|
+
return true;
|
|
1115
|
+
}
|
|
1116
|
+
});
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1086
1119
|
return __getEffectiveExcludeSelectors().length ? list.filter((el) => !isExcluded(el)) : list;
|
|
1087
1120
|
}
|
|
1088
1121
|
|
package/src/core/dom-runner.js
CHANGED
|
@@ -48,6 +48,9 @@ function runCore(pageUrl, contextSelector, engineOptions, runOnly, CHECK_DEFS, R
|
|
|
48
48
|
|
|
49
49
|
// Default on: opt OUT with `includeShadowDom: false`, not opt in.
|
|
50
50
|
const includeShadowDom = !(engineOptionsResolved && engineOptionsResolved.includeShadowDom === false);
|
|
51
|
+
// Default off: hidden/collapsed content is excluded from rule evaluation
|
|
52
|
+
// unless the caller explicitly opts in.
|
|
53
|
+
const includeHiddenElements = !!(engineOptionsResolved && engineOptionsResolved.includeHiddenElements === true);
|
|
51
54
|
const excludeSelectors = normalizeSelectorList(engineOptionsResolved && engineOptionsResolved.excludeSelectors);
|
|
52
55
|
|
|
53
56
|
const url = pageUrl || (document.location && document.location.href) || null;
|
|
@@ -63,6 +66,7 @@ function runCore(pageUrl, contextSelector, engineOptions, runOnly, CHECK_DEFS, R
|
|
|
63
66
|
window,
|
|
64
67
|
root: roots,
|
|
65
68
|
includeShadowDom,
|
|
69
|
+
includeHiddenElements,
|
|
66
70
|
excludeSelectors,
|
|
67
71
|
// Optional perf counters (bench/debug only). Deterministic and per-run.
|
|
68
72
|
perfStats: !!(engineOptionsResolved && engineOptionsResolved.perfStats)
|
package/src/core.js
CHANGED
|
@@ -10967,6 +10967,10 @@ const createDomHelpers = (function createDomHelpers(opts) {
|
|
|
10967
10967
|
})();
|
|
10968
10968
|
// Default on: opt OUT with `includeShadowDom: false`, not opt in.
|
|
10969
10969
|
const includeShadowDom = !(opts && opts.includeShadowDom === false);
|
|
10970
|
+
// Default off: by default, helper queries skip structurally/CSS-hidden
|
|
10971
|
+
// subtrees (display:none, [hidden], closed <details>, etc.). Callers can
|
|
10972
|
+
// opt out with includeHiddenElements:true.
|
|
10973
|
+
const includeHiddenElements = !!(opts && opts.includeHiddenElements === true);
|
|
10970
10974
|
const excludeSelectors = Array.isArray(opts && opts.excludeSelectors) ? opts.excludeSelectors : [];
|
|
10971
10975
|
|
|
10972
10976
|
// Rule-scoped excludes (engineOptions.rules[ruleId].excludeSelectors), set
|
|
@@ -11933,8 +11937,37 @@ const createDomHelpers = (function createDomHelpers(opts) {
|
|
|
11933
11937
|
return results;
|
|
11934
11938
|
}
|
|
11935
11939
|
|
|
11940
|
+
const HARD_HIDDEN_REASONS = new Set([
|
|
11941
|
+
'displayNone',
|
|
11942
|
+
'hiddenAttr',
|
|
11943
|
+
'detailsClosed',
|
|
11944
|
+
'templateContent',
|
|
11945
|
+
'nonRenderedElement',
|
|
11946
|
+
'inputHidden',
|
|
11947
|
+
'visibilityHidden'
|
|
11948
|
+
]);
|
|
11949
|
+
|
|
11936
11950
|
function queryAllSmart(sel) {
|
|
11937
|
-
|
|
11951
|
+
let list = includeShadowDom ? queryAllDeep(sel) : queryAll(sel);
|
|
11952
|
+
|
|
11953
|
+
// Global hidden-content policy: skip nodes that are fully excluded from
|
|
11954
|
+
// rendered visibility by default (unless includeHiddenElements:true).
|
|
11955
|
+
if (!includeHiddenElements) {
|
|
11956
|
+
list = list.filter((el) => {
|
|
11957
|
+
try {
|
|
11958
|
+
const vis = isAccTreeEligible(el);
|
|
11959
|
+
if (!vis || vis.eligible !== false) return true;
|
|
11960
|
+
const reasons = Array.isArray(vis.reasons) ? vis.reasons : [];
|
|
11961
|
+
for (const r of reasons) {
|
|
11962
|
+
if (HARD_HIDDEN_REASONS.has(r)) return false;
|
|
11963
|
+
}
|
|
11964
|
+
return true;
|
|
11965
|
+
} catch {
|
|
11966
|
+
return true;
|
|
11967
|
+
}
|
|
11968
|
+
});
|
|
11969
|
+
}
|
|
11970
|
+
|
|
11938
11971
|
return __getEffectiveExcludeSelectors().length ? list.filter((el) => !isExcluded(el)) : list;
|
|
11939
11972
|
}
|
|
11940
11973
|
|
|
@@ -15129,6 +15162,9 @@ const runCore = (function runCore(pageUrl, contextSelector, engineOptions, runOn
|
|
|
15129
15162
|
|
|
15130
15163
|
// Default on: opt OUT with `includeShadowDom: false`, not opt in.
|
|
15131
15164
|
const includeShadowDom = !(engineOptionsResolved && engineOptionsResolved.includeShadowDom === false);
|
|
15165
|
+
// Default off: hidden/collapsed content is excluded from rule evaluation
|
|
15166
|
+
// unless the caller explicitly opts in.
|
|
15167
|
+
const includeHiddenElements = !!(engineOptionsResolved && engineOptionsResolved.includeHiddenElements === true);
|
|
15132
15168
|
const excludeSelectors = normalizeSelectorList(engineOptionsResolved && engineOptionsResolved.excludeSelectors);
|
|
15133
15169
|
|
|
15134
15170
|
const url = pageUrl || (document.location && document.location.href) || null;
|
|
@@ -15144,6 +15180,7 @@ const runCore = (function runCore(pageUrl, contextSelector, engineOptions, runOn
|
|
|
15144
15180
|
window,
|
|
15145
15181
|
root: roots,
|
|
15146
15182
|
includeShadowDom,
|
|
15183
|
+
includeHiddenElements,
|
|
15147
15184
|
excludeSelectors,
|
|
15148
15185
|
// Optional perf counters (bench/debug only). Deterministic and per-run.
|
|
15149
15186
|
perfStats: !!(engineOptionsResolved && engineOptionsResolved.perfStats)
|
|
@@ -43056,6 +43093,10 @@ const createDomHelpers = (function createDomHelpers(opts) {
|
|
|
43056
43093
|
})();
|
|
43057
43094
|
// Default on: opt OUT with `includeShadowDom: false`, not opt in.
|
|
43058
43095
|
const includeShadowDom = !(opts && opts.includeShadowDom === false);
|
|
43096
|
+
// Default off: by default, helper queries skip structurally/CSS-hidden
|
|
43097
|
+
// subtrees (display:none, [hidden], closed <details>, etc.). Callers can
|
|
43098
|
+
// opt out with includeHiddenElements:true.
|
|
43099
|
+
const includeHiddenElements = !!(opts && opts.includeHiddenElements === true);
|
|
43059
43100
|
const excludeSelectors = Array.isArray(opts && opts.excludeSelectors) ? opts.excludeSelectors : [];
|
|
43060
43101
|
|
|
43061
43102
|
// Rule-scoped excludes (engineOptions.rules[ruleId].excludeSelectors), set
|
|
@@ -44022,8 +44063,37 @@ const createDomHelpers = (function createDomHelpers(opts) {
|
|
|
44022
44063
|
return results;
|
|
44023
44064
|
}
|
|
44024
44065
|
|
|
44066
|
+
const HARD_HIDDEN_REASONS = new Set([
|
|
44067
|
+
'displayNone',
|
|
44068
|
+
'hiddenAttr',
|
|
44069
|
+
'detailsClosed',
|
|
44070
|
+
'templateContent',
|
|
44071
|
+
'nonRenderedElement',
|
|
44072
|
+
'inputHidden',
|
|
44073
|
+
'visibilityHidden'
|
|
44074
|
+
]);
|
|
44075
|
+
|
|
44025
44076
|
function queryAllSmart(sel) {
|
|
44026
|
-
|
|
44077
|
+
let list = includeShadowDom ? queryAllDeep(sel) : queryAll(sel);
|
|
44078
|
+
|
|
44079
|
+
// Global hidden-content policy: skip nodes that are fully excluded from
|
|
44080
|
+
// rendered visibility by default (unless includeHiddenElements:true).
|
|
44081
|
+
if (!includeHiddenElements) {
|
|
44082
|
+
list = list.filter((el) => {
|
|
44083
|
+
try {
|
|
44084
|
+
const vis = isAccTreeEligible(el);
|
|
44085
|
+
if (!vis || vis.eligible !== false) return true;
|
|
44086
|
+
const reasons = Array.isArray(vis.reasons) ? vis.reasons : [];
|
|
44087
|
+
for (const r of reasons) {
|
|
44088
|
+
if (HARD_HIDDEN_REASONS.has(r)) return false;
|
|
44089
|
+
}
|
|
44090
|
+
return true;
|
|
44091
|
+
} catch {
|
|
44092
|
+
return true;
|
|
44093
|
+
}
|
|
44094
|
+
});
|
|
44095
|
+
}
|
|
44096
|
+
|
|
44027
44097
|
return __getEffectiveExcludeSelectors().length ? list.filter((el) => !isExcluded(el)) : list;
|
|
44028
44098
|
}
|
|
44029
44099
|
|
|
@@ -47218,6 +47288,9 @@ const runCore = (function runCore(pageUrl, contextSelector, engineOptions, runOn
|
|
|
47218
47288
|
|
|
47219
47289
|
// Default on: opt OUT with `includeShadowDom: false`, not opt in.
|
|
47220
47290
|
const includeShadowDom = !(engineOptionsResolved && engineOptionsResolved.includeShadowDom === false);
|
|
47291
|
+
// Default off: hidden/collapsed content is excluded from rule evaluation
|
|
47292
|
+
// unless the caller explicitly opts in.
|
|
47293
|
+
const includeHiddenElements = !!(engineOptionsResolved && engineOptionsResolved.includeHiddenElements === true);
|
|
47221
47294
|
const excludeSelectors = normalizeSelectorList(engineOptionsResolved && engineOptionsResolved.excludeSelectors);
|
|
47222
47295
|
|
|
47223
47296
|
const url = pageUrl || (document.location && document.location.href) || null;
|
|
@@ -47233,6 +47306,7 @@ const runCore = (function runCore(pageUrl, contextSelector, engineOptions, runOn
|
|
|
47233
47306
|
window,
|
|
47234
47307
|
root: roots,
|
|
47235
47308
|
includeShadowDom,
|
|
47309
|
+
includeHiddenElements,
|
|
47236
47310
|
excludeSelectors,
|
|
47237
47311
|
// Optional perf counters (bench/debug only). Deterministic and per-run.
|
|
47238
47312
|
perfStats: !!(engineOptionsResolved && engineOptionsResolved.perfStats)
|
|
@@ -75100,6 +75174,10 @@ const createDomHelpers = (function createDomHelpers(opts) {
|
|
|
75100
75174
|
})();
|
|
75101
75175
|
// Default on: opt OUT with `includeShadowDom: false`, not opt in.
|
|
75102
75176
|
const includeShadowDom = !(opts && opts.includeShadowDom === false);
|
|
75177
|
+
// Default off: by default, helper queries skip structurally/CSS-hidden
|
|
75178
|
+
// subtrees (display:none, [hidden], closed <details>, etc.). Callers can
|
|
75179
|
+
// opt out with includeHiddenElements:true.
|
|
75180
|
+
const includeHiddenElements = !!(opts && opts.includeHiddenElements === true);
|
|
75103
75181
|
const excludeSelectors = Array.isArray(opts && opts.excludeSelectors) ? opts.excludeSelectors : [];
|
|
75104
75182
|
|
|
75105
75183
|
// Rule-scoped excludes (engineOptions.rules[ruleId].excludeSelectors), set
|
|
@@ -76066,8 +76144,37 @@ const createDomHelpers = (function createDomHelpers(opts) {
|
|
|
76066
76144
|
return results;
|
|
76067
76145
|
}
|
|
76068
76146
|
|
|
76147
|
+
const HARD_HIDDEN_REASONS = new Set([
|
|
76148
|
+
'displayNone',
|
|
76149
|
+
'hiddenAttr',
|
|
76150
|
+
'detailsClosed',
|
|
76151
|
+
'templateContent',
|
|
76152
|
+
'nonRenderedElement',
|
|
76153
|
+
'inputHidden',
|
|
76154
|
+
'visibilityHidden'
|
|
76155
|
+
]);
|
|
76156
|
+
|
|
76069
76157
|
function queryAllSmart(sel) {
|
|
76070
|
-
|
|
76158
|
+
let list = includeShadowDom ? queryAllDeep(sel) : queryAll(sel);
|
|
76159
|
+
|
|
76160
|
+
// Global hidden-content policy: skip nodes that are fully excluded from
|
|
76161
|
+
// rendered visibility by default (unless includeHiddenElements:true).
|
|
76162
|
+
if (!includeHiddenElements) {
|
|
76163
|
+
list = list.filter((el) => {
|
|
76164
|
+
try {
|
|
76165
|
+
const vis = isAccTreeEligible(el);
|
|
76166
|
+
if (!vis || vis.eligible !== false) return true;
|
|
76167
|
+
const reasons = Array.isArray(vis.reasons) ? vis.reasons : [];
|
|
76168
|
+
for (const r of reasons) {
|
|
76169
|
+
if (HARD_HIDDEN_REASONS.has(r)) return false;
|
|
76170
|
+
}
|
|
76171
|
+
return true;
|
|
76172
|
+
} catch {
|
|
76173
|
+
return true;
|
|
76174
|
+
}
|
|
76175
|
+
});
|
|
76176
|
+
}
|
|
76177
|
+
|
|
76071
76178
|
return __getEffectiveExcludeSelectors().length ? list.filter((el) => !isExcluded(el)) : list;
|
|
76072
76179
|
}
|
|
76073
76180
|
|
|
@@ -79262,6 +79369,9 @@ const runCore = (function runCore(pageUrl, contextSelector, engineOptions, runOn
|
|
|
79262
79369
|
|
|
79263
79370
|
// Default on: opt OUT with `includeShadowDom: false`, not opt in.
|
|
79264
79371
|
const includeShadowDom = !(engineOptionsResolved && engineOptionsResolved.includeShadowDom === false);
|
|
79372
|
+
// Default off: hidden/collapsed content is excluded from rule evaluation
|
|
79373
|
+
// unless the caller explicitly opts in.
|
|
79374
|
+
const includeHiddenElements = !!(engineOptionsResolved && engineOptionsResolved.includeHiddenElements === true);
|
|
79265
79375
|
const excludeSelectors = normalizeSelectorList(engineOptionsResolved && engineOptionsResolved.excludeSelectors);
|
|
79266
79376
|
|
|
79267
79377
|
const url = pageUrl || (document.location && document.location.href) || null;
|
|
@@ -79277,6 +79387,7 @@ const runCore = (function runCore(pageUrl, contextSelector, engineOptions, runOn
|
|
|
79277
79387
|
window,
|
|
79278
79388
|
root: roots,
|
|
79279
79389
|
includeShadowDom,
|
|
79390
|
+
includeHiddenElements,
|
|
79280
79391
|
excludeSelectors,
|
|
79281
79392
|
// Optional perf counters (bench/debug only). Deterministic and per-run.
|
|
79282
79393
|
perfStats: !!(engineOptionsResolved && engineOptionsResolved.perfStats)
|