browser-debugger-cli 0.11.0 → 0.13.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/.claude/skills/bdg/SKILL.md +4 -4
- package/README.md +143 -79
- package/dist/commands/cdp.d.ts +22 -1
- package/dist/commands/cdp.js +100 -43
- package/dist/commands/console.d.ts +12 -0
- package/dist/commands/console.js +62 -12
- package/dist/commands/css.d.ts +13 -0
- package/dist/commands/css.js +53 -0
- package/dist/commands/dom/DomElementResolver.d.ts +3 -1
- package/dist/commands/dom/DomElementResolver.js +10 -3
- package/dist/commands/dom/a11y.js +3 -2
- package/dist/commands/dom/audit.d.ts +14 -0
- package/dist/commands/dom/audit.js +87 -0
- package/dist/commands/dom/eval.d.ts +3 -2
- package/dist/commands/dom/eval.js +11 -5
- package/dist/commands/dom/form.js +10 -9
- package/dist/commands/dom/formInteraction.js +42 -11
- package/dist/commands/dom/get.js +8 -8
- package/dist/commands/dom/helpers/index.d.ts +1 -1
- package/dist/commands/dom/helpers/index.js +1 -1
- package/dist/commands/dom/helpers/keyAttributes.d.ts +3 -2
- package/dist/commands/dom/helpers/keyAttributes.js +6 -4
- package/dist/commands/dom/helpers/query.d.ts +27 -3
- package/dist/commands/dom/helpers/query.js +152 -64
- package/dist/commands/dom/helpers/screenshot.d.ts +1 -0
- package/dist/commands/dom/helpers/screenshot.js +169 -49
- package/dist/commands/dom/index.js +7 -2
- package/dist/commands/dom/query.d.ts +19 -2
- package/dist/commands/dom/query.js +37 -6
- package/dist/commands/dom/screenshot.js +12 -7
- package/dist/commands/dom/semanticUtils.d.ts +3 -2
- package/dist/commands/dom/semanticUtils.js +40 -9
- package/dist/commands/dom/wait.js +5 -3
- package/dist/commands/helpJson.d.ts +82 -19
- package/dist/commands/helpJson.js +112 -41
- package/dist/commands/helpTopic.d.ts +16 -1
- package/dist/commands/helpTopic.js +59 -1
- package/dist/commands/installSkill.d.ts +15 -5
- package/dist/commands/installSkill.js +86 -16
- package/dist/commands/network/list.js +22 -12
- package/dist/commands/optionBehaviors.js +53 -16
- package/dist/commands/page.js +7 -4
- package/dist/commands/peek.d.ts +7 -0
- package/dist/commands/peek.js +65 -23
- package/dist/commands/shared/daemonErrorHandler.d.ts +5 -2
- package/dist/commands/shared/daemonErrorHandler.js +20 -9
- package/dist/commands/shared/dataFetcher.d.ts +12 -4
- package/dist/commands/shared/dataFetcher.js +12 -4
- package/dist/commands/shared/followMode.d.ts +9 -1
- package/dist/commands/shared/followMode.js +22 -4
- package/dist/commands/shared/optionTypes.d.ts +9 -2
- package/dist/commands/shared/outputFile.js +6 -1
- package/dist/commands/start.d.ts +20 -5
- package/dist/commands/start.js +84 -23
- package/dist/commands/stop.d.ts +11 -0
- package/dist/commands/stop.js +24 -1
- package/dist/commands/tail.d.ts +7 -1
- package/dist/commands/tail.js +13 -62
- package/dist/commands.js +2 -0
- package/dist/connection/cdp.d.ts +7 -0
- package/dist/connection/cdp.js +9 -0
- package/dist/connection/launcher.js +3 -2
- package/dist/daemon/SessionController.js +6 -1
- package/dist/daemon/launcher.d.ts +3 -2
- package/dist/daemon/launcher.js +47 -3
- package/dist/daemon/session/Session.d.ts +4 -1
- package/dist/daemon/session/Session.js +33 -2
- package/dist/daemon/session/TelemetryStore.d.ts +8 -1
- package/dist/daemon/session/TelemetryStore.js +13 -1
- package/dist/daemon/session/commandRegistry.js +36 -14
- package/dist/daemon/session/interactions.d.ts +2 -1
- package/dist/daemon/session/interactions.js +13 -1
- package/dist/daemon/session/plugins.js +19 -53
- package/dist/daemon/session/teardown.js +1 -1
- package/dist/daemon.js +9234 -7222
- package/dist/errors/messages.d.ts +88 -15
- package/dist/errors/messages.js +177 -27
- package/dist/index.js +19322 -13961
- package/dist/ipc/client.d.ts +22 -2
- package/dist/ipc/client.js +34 -5
- package/dist/ipc/protocol/auditTypes.d.ts +135 -0
- package/dist/ipc/protocol/auditTypes.js +6 -0
- package/dist/ipc/protocol/commands.d.ts +35 -0
- package/dist/ipc/protocol/commands.js +2 -0
- package/dist/ipc/protocol/domTypes.d.ts +16 -0
- package/dist/ipc/protocol/inspectTypes.d.ts +73 -8
- package/dist/ipc/session/types.d.ts +2 -0
- package/dist/runtime/css/search.d.ts +39 -0
- package/dist/runtime/css/search.js +122 -0
- package/dist/runtime/dom/actionEffects.d.ts +9 -2
- package/dist/runtime/dom/actionEffects.js +30 -14
- package/dist/runtime/dom/audit.d.ts +19 -0
- package/dist/runtime/dom/audit.js +37 -0
- package/dist/runtime/dom/auditModel.d.ts +45 -0
- package/dist/runtime/dom/auditModel.js +220 -0
- package/dist/runtime/dom/auditScripts.d.ts +113 -0
- package/dist/runtime/dom/auditScripts.js +148 -0
- package/dist/runtime/dom/elementGeometry.d.ts +16 -3
- package/dist/runtime/dom/elementGeometry.js +49 -10
- package/dist/runtime/dom/elementInfo.d.ts +74 -17
- package/dist/runtime/dom/elementInfo.js +187 -34
- package/dist/runtime/dom/evalHelpers.d.ts +12 -2
- package/dist/runtime/dom/evalHelpers.js +67 -7
- package/dist/runtime/dom/formDiscovery.d.ts +6 -2
- package/dist/runtime/dom/formDiscovery.js +20 -3
- package/dist/runtime/dom/formFillHelpers/fill.js +8 -12
- package/dist/runtime/dom/formFillHelpers/pressKey.js +2 -2
- package/dist/runtime/dom/formFillHelpers/shared.d.ts +16 -10
- package/dist/runtime/dom/formFillHelpers/shared.js +19 -52
- package/dist/runtime/dom/formSubmitHelpers.js +4 -3
- package/dist/runtime/dom/frameLayout.js +1 -0
- package/dist/runtime/dom/inspect.d.ts +7 -0
- package/dist/runtime/dom/inspect.js +92 -28
- package/dist/runtime/dom/inspectAllStyles.d.ts +16 -4
- package/dist/runtime/dom/inspectAllStyles.js +90 -7
- package/dist/runtime/dom/inspectCascade.d.ts +19 -2
- package/dist/runtime/dom/inspectCascade.js +214 -44
- package/dist/runtime/dom/inspectCascadeModel.d.ts +8 -0
- package/dist/runtime/dom/inspectCascadeModel.js +108 -34
- package/dist/runtime/dom/inspectHints.d.ts +26 -3
- package/dist/runtime/dom/inspectHints.js +125 -9
- package/dist/runtime/dom/inspectModel.d.ts +5 -1
- package/dist/runtime/dom/inspectModel.js +37 -10
- package/dist/runtime/dom/inspectPaintModel.d.ts +50 -22
- package/dist/runtime/dom/inspectPaintModel.js +182 -68
- package/dist/runtime/dom/inspectRules.d.ts +19 -0
- package/dist/runtime/dom/inspectRules.js +21 -5
- package/dist/runtime/dom/inspectScripts.d.ts +112 -12
- package/dist/runtime/dom/inspectScripts.js +357 -32
- package/dist/runtime/dom/inspectTree.js +10 -2
- package/dist/runtime/dom/inspectWhyModel.d.ts +2 -1
- package/dist/runtime/dom/inspectWhyModel.js +52 -10
- package/dist/runtime/dom/layout.js +40 -16
- package/dist/runtime/dom/reactEventHelpers.d.ts +21 -4
- package/dist/runtime/dom/reactEventHelpers.js +90 -36
- package/dist/runtime/dom/targetNode.d.ts +18 -5
- package/dist/runtime/dom/targetNode.js +268 -8
- package/dist/runtime/dom/wait.js +2 -1
- package/dist/runtime/page/bdgWorld.d.ts +57 -0
- package/dist/runtime/page/bdgWorld.js +180 -0
- package/dist/runtime/page/emulation.d.ts +13 -4
- package/dist/runtime/page/emulation.js +69 -4
- package/dist/runtime/page/replacedBuiltins.d.ts +28 -0
- package/dist/runtime/page/replacedBuiltins.js +136 -0
- package/dist/runtime/page/userAgent.d.ts +17 -0
- package/dist/runtime/page/userAgent.js +57 -0
- package/dist/session/QueryCacheManager.d.ts +4 -1
- package/dist/session/QueryCacheManager.js +5 -2
- package/dist/session/chrome.d.ts +4 -1
- package/dist/session/chrome.js +7 -1
- package/dist/session/cleanup/staleSession.d.ts +21 -4
- package/dist/session/cleanup/staleSession.js +79 -9
- package/dist/session/cleanup/userCommands.d.ts +4 -1
- package/dist/session/cleanup/userCommands.js +10 -5
- package/dist/session/daemonSocket.d.ts +10 -0
- package/dist/session/daemonSocket.js +22 -0
- package/dist/session/lastSession.d.ts +6 -3
- package/dist/session/lastSession.js +11 -5
- package/dist/session/paths.d.ts +3 -1
- package/dist/session/paths.js +5 -5
- package/dist/session/portClaims.js +4 -3
- package/dist/session/sessionList.d.ts +13 -5
- package/dist/session/sessionList.js +31 -7
- package/dist/telemetry/a11y.js +2 -2
- package/dist/telemetry/console.d.ts +2 -1
- package/dist/telemetry/console.js +30 -21
- package/dist/telemetry/pageCrash.d.ts +26 -0
- package/dist/telemetry/pageCrash.js +53 -0
- package/dist/types.d.ts +20 -0
- package/dist/ui/formatters/audit.d.ts +19 -0
- package/dist/ui/formatters/audit.js +115 -0
- package/dist/ui/formatters/cdp.d.ts +138 -0
- package/dist/ui/formatters/cdp.js +131 -0
- package/dist/ui/formatters/console/chronological.js +3 -1
- package/dist/ui/formatters/console/follow.d.ts +2 -1
- package/dist/ui/formatters/console/follow.js +2 -2
- package/dist/ui/formatters/console/json.d.ts +2 -2
- package/dist/ui/formatters/console/json.js +11 -5
- package/dist/ui/formatters/console/shared.d.ts +30 -0
- package/dist/ui/formatters/console/shared.js +16 -0
- package/dist/ui/formatters/console/summarize.d.ts +9 -2
- package/dist/ui/formatters/console/summarize.js +40 -9
- package/dist/ui/formatters/console.d.ts +2 -1
- package/dist/ui/formatters/console.js +7 -5
- package/dist/ui/formatters/details.js +3 -1
- package/dist/ui/formatters/dom.d.ts +2 -2
- package/dist/ui/formatters/dom.js +10 -8
- package/dist/ui/formatters/helpFormatters.js +1 -1
- package/dist/ui/formatters/inspect.js +50 -17
- package/dist/ui/formatters/installSkill.d.ts +9 -1
- package/dist/ui/formatters/installSkill.js +32 -6
- package/dist/ui/formatters/layout.js +2 -1
- package/dist/ui/formatters/networkList.d.ts +1 -1
- package/dist/ui/formatters/networkList.js +1 -2
- package/dist/ui/formatters/preview.d.ts +2 -0
- package/dist/ui/formatters/preview.js +17 -7
- package/dist/ui/formatters/sessions.d.ts +2 -2
- package/dist/ui/formatters/sessions.js +9 -2
- package/dist/ui/formatters/status.js +1 -1
- package/dist/ui/logging/logger.d.ts +1 -1
- package/dist/ui/messages/commands.d.ts +168 -11
- package/dist/ui/messages/commands.js +245 -18
- package/dist/ui/messages/consoleMessages.d.ts +24 -0
- package/dist/ui/messages/consoleMessages.js +32 -0
- package/dist/ui/messages/preview.d.ts +12 -0
- package/dist/ui/messages/preview.js +18 -2
- package/dist/ui/messages/session.d.ts +13 -2
- package/dist/ui/messages/session.js +22 -3
- package/dist/utils/cssValues.js +36 -4
- package/dist/utils/decisionTrees.js +0 -5
- package/dist/utils/directories.d.ts +34 -0
- package/dist/utils/directories.js +88 -0
- package/dist/utils/display.d.ts +16 -0
- package/dist/utils/display.js +42 -0
- package/dist/utils/exitCodes.d.ts +1 -0
- package/dist/utils/exitCodes.js +6 -0
- package/dist/utils/process.d.ts +12 -0
- package/dist/utils/process.js +25 -0
- package/dist/utils/suggestions.d.ts +4 -2
- package/dist/utils/suggestions.js +7 -5
- package/dist/utils/taskMappings.js +1 -1
- package/package.json +3 -2
|
@@ -0,0 +1,148 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Page-side part of `bdg dom audit`: one walk over the rendered elements of
|
|
3
|
+
* the page (open shadow roots and the light DOM slotted into them, at most
|
|
4
|
+
* {@link AUDIT_ELEMENT_CAP}; nothing inside a fixed element counts as making
|
|
5
|
+
* the page wider; SVG images and placeholders of 2px or less are skipped)
|
|
6
|
+
* that collects what each check needs. Colors and contrast are computed in
|
|
7
|
+
* the daemon ({@link buildAudit}), the same way `dom inspect` computes them.
|
|
8
|
+
*/
|
|
9
|
+
import { LABEL_CLASSES_JS } from './elementInfo.js';
|
|
10
|
+
import { FLAT_TREE_JS, HIT_EVERYTHING_JS, PAINT_RISKS_JS, } from './inspectScripts.js';
|
|
11
|
+
import { AUDIT_OUT_OF_VIEW_RISK } from '../../ui/messages/commands.js';
|
|
12
|
+
/** Elements the walk looks at, at most */
|
|
13
|
+
export const AUDIT_ELEMENT_CAP = 20000;
|
|
14
|
+
/**
|
|
15
|
+
* Page-side audit walk. Arguments: the checks to collect for
|
|
16
|
+
* (`contrast`, `overflow`, `layers`, `animations`).
|
|
17
|
+
*/
|
|
18
|
+
export const AUDIT_PAGE_JS = `function (checks) {
|
|
19
|
+
const want = (name) => checks.includes(name);
|
|
20
|
+
const doc = document;
|
|
21
|
+
const view = window;
|
|
22
|
+
const style = (n) => view.getComputedStyle(n);
|
|
23
|
+
const scroller = doc.scrollingElement || doc.documentElement;
|
|
24
|
+
const viewport = { width: doc.documentElement.clientWidth, height: view.innerHeight };
|
|
25
|
+
const firstClass = (n) => (${LABEL_CLASSES_JS})(n)[0];
|
|
26
|
+
const label = (n) => n.localName + (n.id ? '#' + n.id : firstClass(n) ? '.' + firstClass(n) : '');
|
|
27
|
+
const parentOf = (n) => n.assignedSlot || n.parentElement || (n.parentNode && n.parentNode.host) || null;
|
|
28
|
+
const short = (text) => text.replace(/\\s+/g, ' ').trim().slice(0, 60);
|
|
29
|
+
const ownText = (n) => Array.from(n.childNodes).filter((c) => c.nodeType === 3).map((c) => c.data).join(' ').replace(/\\s+/g, ' ').trim();
|
|
30
|
+
const inView = (r) => r.bottom > 0 && r.right > 0 && r.top < viewport.height && r.left < viewport.width;
|
|
31
|
+
const meta = doc.querySelector('meta[name="color-scheme"]');
|
|
32
|
+
const rootScheme = style(doc.documentElement).colorScheme;
|
|
33
|
+
const scheme = rootScheme && rootScheme !== 'normal' ? rootScheme : (meta && meta.content) || '';
|
|
34
|
+
const canvasDark = /dark/.test(scheme) && (!/light/.test(scheme) || view.matchMedia('(prefers-color-scheme: dark)').matches);
|
|
35
|
+
const result = { viewport: viewport, pixelRatio: view.devicePixelRatio || 1, pageWidth: scroller.scrollWidth, canvasDark: canvasDark, walked: 0, capped: false };
|
|
36
|
+
const texts = [], wide = [], truncated = [], images = [], layers = [], scrollers = [];
|
|
37
|
+
let canvases = 0;
|
|
38
|
+
const tree = (${FLAT_TREE_JS})(view);
|
|
39
|
+
const paintRisks = ${PAINT_RISKS_JS};
|
|
40
|
+
const clear = (color) => color === 'transparent' || /^rgba\\(.*,\\s*0\\)$/.test(color) || /\\/\\s*0\\)$/.test(color);
|
|
41
|
+
const chainOf = (n) => {
|
|
42
|
+
const backgrounds = [];
|
|
43
|
+
const risks = [];
|
|
44
|
+
let opacity = 1;
|
|
45
|
+
let painted = false;
|
|
46
|
+
let solid = false;
|
|
47
|
+
for (let p = n; p && backgrounds.length < 60; p = parentOf(p)) {
|
|
48
|
+
const s = style(p);
|
|
49
|
+
const own = Number(s.opacity) || 0;
|
|
50
|
+
const background = { color: s.backgroundColor, image: s.backgroundImage !== 'none' };
|
|
51
|
+
backgrounds.push(own < 1 ? Object.assign(background, { opacity: own }) : background);
|
|
52
|
+
opacity *= own;
|
|
53
|
+
if (p !== doc.documentElement && p !== doc.body && (background.image || !clear(s.backgroundColor))) painted = true;
|
|
54
|
+
if (p !== doc.documentElement && p !== doc.body && !background.image && /^rgb\\(/.test(s.backgroundColor)) solid = true;
|
|
55
|
+
if (s.mixBlendMode !== 'normal') risks.push('mix-blend-mode ' + s.mixBlendMode + ' on ' + label(p));
|
|
56
|
+
if (s.filter !== 'none') risks.push('filter on ' + label(p));
|
|
57
|
+
}
|
|
58
|
+
return { backgrounds: backgrounds, risks: risks, opacity: opacity, painted: painted, solid: solid };
|
|
59
|
+
};
|
|
60
|
+
const unhit = [];
|
|
61
|
+
const painters = [];
|
|
62
|
+
const markPaintedBehind = () => {
|
|
63
|
+
for (const text of unhit) {
|
|
64
|
+
const painter = painters.find((p) => p.el !== text.el && !p.el.contains(text.el) && !text.el.contains(p.el) &&
|
|
65
|
+
text.x >= p.left && text.x <= p.right && text.y >= p.top && text.y <= p.bottom);
|
|
66
|
+
if (painter && !text.entry.risks.includes(${JSON.stringify(AUDIT_OUT_OF_VIEW_RISK)})) text.entry.risks.push(label(painter.el) + ' under or over it (out of view, not hit-tested)');
|
|
67
|
+
}
|
|
68
|
+
};
|
|
69
|
+
const risksOf = (n, slot, chain, shown) => {
|
|
70
|
+
if (!shown) return chain.painted ? chain.risks : chain.risks.concat(${JSON.stringify(AUDIT_OUT_OF_VIEW_RISK)});
|
|
71
|
+
const text = slot && n.assignedNodes({ flatten: true }).find((c) => c.nodeType === 3 && c.data.trim() !== '');
|
|
72
|
+
return paintRisks(n, tree, text ? text.parentElement || n : n);
|
|
73
|
+
};
|
|
74
|
+
const visit = (n, fixed) => {
|
|
75
|
+
if (result.walked >= ${AUDIT_ELEMENT_CAP}) { result.capped = true; return; }
|
|
76
|
+
if (/^(script|style|template|noscript|head|meta|link|title)$/.test(n.localName)) return;
|
|
77
|
+
const s = style(n);
|
|
78
|
+
if (s.display === 'none') return;
|
|
79
|
+
result.walked++;
|
|
80
|
+
const inFixed = fixed || s.position === 'fixed';
|
|
81
|
+
const kids = n.shadowRoot ? [...Array.from(n.shadowRoot.children), ...Array.from(n.children)] : Array.from(n.children);
|
|
82
|
+
const slot = n.localName === 'slot';
|
|
83
|
+
const box = slot && parentOf(n) ? parentOf(n) : n;
|
|
84
|
+
const r = box.getBoundingClientRect();
|
|
85
|
+
const shown = r.width > 0 && r.height > 0 && s.visibility === 'visible';
|
|
86
|
+
const gradientText = /rgba\\(0, 0, 0, 0\\)|transparent/.test(s.webkitTextFillColor);
|
|
87
|
+
if (shown && want('contrast') && !gradientText) {
|
|
88
|
+
const text = slot ? short(n.assignedNodes({ flatten: true }).filter((c) => c.nodeType === 3).map((c) => c.data).join(' ')) : n.shadowRoot ? '' : ownText(n);
|
|
89
|
+
if (text && Number(s.opacity) > 0 && r.width > 2 && r.height > 2) {
|
|
90
|
+
const chain = chainOf(n);
|
|
91
|
+
const seen = inView(r);
|
|
92
|
+
if (chain.opacity > 0) {
|
|
93
|
+
texts.push({ label: label(n), text: short(text), color: s.color, fontSize: s.fontSize, fontWeight: s.fontWeight, inView: seen, backgrounds: chain.backgrounds, opacity: chain.opacity, risks: risksOf(n, slot, chain, seen) });
|
|
94
|
+
if (!seen && !chain.solid) unhit.push({ entry: texts[texts.length - 1], el: box, x: (r.left + r.right) / 2 + view.scrollX, y: (r.top + r.bottom) / 2 + view.scrollY });
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
if (shown && want('contrast') && (/^(img|video|canvas|picture|iframe)$/.test(n.localName) || s.backgroundImage !== 'none')) {
|
|
99
|
+
painters.push({ el: n, left: r.left + view.scrollX, top: r.top + view.scrollY, right: r.right + view.scrollX, bottom: r.bottom + view.scrollY });
|
|
100
|
+
}
|
|
101
|
+
if (shown && want('overflow')) {
|
|
102
|
+
if (r.right > viewport.width + 1 && !inFixed) wide.push({ label: label(n), right: r.right + view.scrollX, width: r.width });
|
|
103
|
+
const cut = s.overflowX !== 'visible' && n.scrollWidth > n.clientWidth + 1 ? (s.textOverflow === 'ellipsis' ? 'ellipsis' : 'clip')
|
|
104
|
+
: s.webkitLineClamp !== 'none' && n.scrollHeight > n.clientHeight + 1 ? 'clamp' : null;
|
|
105
|
+
const textContent = cut && short(n.innerText || '');
|
|
106
|
+
const visuallyHidden = r.width <= 2 || r.height <= 2;
|
|
107
|
+
if (cut && textContent && !visuallyHidden) truncated.push({ label: label(n), text: textContent, kind: cut });
|
|
108
|
+
const vector = /\\.svg([?#]|$)|^data:image\\/svg/i.test(n.currentSrc || '');
|
|
109
|
+
if (n.localName === 'img' && n.naturalWidth > 2 && n.naturalHeight > 2 && n.complete && !vector) images.push({ label: label(n), natural: { w: n.naturalWidth, h: n.naturalHeight }, rendered: { w: r.width, h: r.height }, objectFit: s.objectFit });
|
|
110
|
+
}
|
|
111
|
+
if (shown && want('overflow') && /^(auto|scroll)$/.test(s.overflowX) && n !== doc.documentElement && n !== doc.body && n.scrollWidth > n.clientWidth + 1) {
|
|
112
|
+
scrollers.push({ label: label(n), scrollWidth: n.scrollWidth, width: n.clientWidth });
|
|
113
|
+
}
|
|
114
|
+
if (want('animations') && shown && n.localName === 'canvas') canvases++;
|
|
115
|
+
if (want('layers') && shown && (s.position === 'fixed' || s.position === 'sticky')) {
|
|
116
|
+
layers.push({ label: label(n), position: s.position, zIndex: s.zIndex, rect: { x: r.left, y: r.top, w: r.width, h: r.height }, inView: inView(r) });
|
|
117
|
+
}
|
|
118
|
+
if (s.overflowX !== 'visible' && s.overflowX !== 'clip' && n !== doc.documentElement && n !== doc.body) {
|
|
119
|
+
const wideBefore = wide.length;
|
|
120
|
+
for (const c of kids) visit(c, inFixed);
|
|
121
|
+
wide.length = wideBefore;
|
|
122
|
+
return;
|
|
123
|
+
}
|
|
124
|
+
for (const c of kids) visit(c, inFixed);
|
|
125
|
+
};
|
|
126
|
+
(${HIT_EVERYTHING_JS})([doc], () => visit(doc.documentElement, false));
|
|
127
|
+
markPaintedBehind();
|
|
128
|
+
if (want('contrast')) result.texts = texts;
|
|
129
|
+
if (want('overflow')) Object.assign(result, { wide: wide, truncated: truncated, images: images, scrollers: scrollers });
|
|
130
|
+
if (want('layers')) result.layers = layers;
|
|
131
|
+
if (want('animations')) {
|
|
132
|
+
result.canvases = canvases;
|
|
133
|
+
result.animations = doc.getAnimations().filter((a) => a.playState === 'running').slice(0, 200).map((a) => {
|
|
134
|
+
const timing = a.effect && a.effect.getTiming ? a.effect.getTiming() : {};
|
|
135
|
+
const target = a.effect && a.effect.target;
|
|
136
|
+
return {
|
|
137
|
+
label: target && target.localName ? label(target) : '(none)',
|
|
138
|
+
name: a.animationName || a.transitionProperty || a.id || 'animation',
|
|
139
|
+
type: a.constructor.name,
|
|
140
|
+
duration: typeof timing.duration === 'number' ? timing.duration : String(timing.duration),
|
|
141
|
+
iterations: timing.iterations === Infinity ? 'infinite' : timing.iterations,
|
|
142
|
+
scrollDriven: Boolean(a.timeline) && a.timeline !== doc.timeline
|
|
143
|
+
};
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
return result;
|
|
147
|
+
}`;
|
|
148
|
+
//# sourceMappingURL=auditScripts.js.map
|
|
@@ -14,6 +14,13 @@ import type { ViewportPosition } from '../../types.js';
|
|
|
14
14
|
export interface ElementGeometry {
|
|
15
15
|
/** Border box in top-level viewport coordinates */
|
|
16
16
|
rect: LayoutBox;
|
|
17
|
+
/**
|
|
18
|
+
* For a zero-size element whose children show (an inline list around
|
|
19
|
+
* floated items, a `display: contents` web component whose shadow root
|
|
20
|
+
* shows a dialog): the box around those children in the flat tree, which
|
|
21
|
+
* is what can be seen of it
|
|
22
|
+
*/
|
|
23
|
+
content?: LayoutBox | null;
|
|
17
24
|
/** Area its iframes and overflow-clipping ancestors leave visible, in the same coordinates */
|
|
18
25
|
clip: LayoutBox | null;
|
|
19
26
|
/** Whether overlay scrollbars of the clipping containers show along the clip's right and bottom edges */
|
|
@@ -27,6 +34,11 @@ export interface ElementGeometry {
|
|
|
27
34
|
* e.g. `opacity: 0 on div#menu`
|
|
28
35
|
*/
|
|
29
36
|
invisible: string | null;
|
|
37
|
+
/**
|
|
38
|
+
* Masked by `mask-image` on it or an ancestor ({@link MASKED_BY_JS}), e.g.
|
|
39
|
+
* `mask-image on div.hero`: part or all of it may not show
|
|
40
|
+
*/
|
|
41
|
+
masked?: string | null;
|
|
30
42
|
/** Inside an `inert` element: shown, but not interactive */
|
|
31
43
|
inert: boolean;
|
|
32
44
|
/** Fixed to the top-level viewport (it or a container is `position: fixed`): page scroll does not move it */
|
|
@@ -120,7 +132,8 @@ export declare const CLIP_RECT_CUTS_ALL_JS = "(position, clip) => {\n if ((posi
|
|
|
120
132
|
* top-level viewport coordinates, its clip by ancestors
|
|
121
133
|
* ({@link ANCESTOR_CLIP_JS}) and by the viewports of its iframes, why it
|
|
122
134
|
* cannot be seen at all (not rendered, or inside a clipping container
|
|
123
|
-
* collapsed to zero size
|
|
135
|
+
* collapsed to zero size; a `display: contents` element is measured by
|
|
136
|
+
* its children in the flat tree and is hidden only when none shows), why a rendered one is still invisible
|
|
124
137
|
* ({@link INVISIBLE_REASON_JS}), whether it is inert (an `inert` element
|
|
125
138
|
* around it, through shadow roots) or fixed to the top-level viewport, and how
|
|
126
139
|
* far the top-level page can scroll ({@link SCROLL_RANGE_JS}) or what locks
|
|
@@ -161,10 +174,10 @@ export declare function scrollTarget(start: number, size: number, viewSize: numb
|
|
|
161
174
|
* out of a list), and `hidden` when it is not rendered at all. Being inert
|
|
162
175
|
* does not change the position: inert elements are shown.
|
|
163
176
|
*
|
|
164
|
-
* @param
|
|
177
|
+
* @param measured - Page-side measurements (a zero-size holder is placed by its children)
|
|
165
178
|
* @param viewport - Top-level viewport size
|
|
166
179
|
* @returns Position, with the visible share, hidden reason, clipping
|
|
167
180
|
* ancestor or page scroll when relevant
|
|
168
181
|
*/
|
|
169
|
-
export declare function classifyViewportPosition(
|
|
182
|
+
export declare function classifyViewportPosition(measured: ElementGeometry, viewport: LayoutSize): ViewportPlacement;
|
|
170
183
|
//# sourceMappingURL=elementGeometry.d.ts.map
|
|
@@ -268,12 +268,28 @@ const INVISIBLE_REASON_JS = `(el, describe) => {
|
|
|
268
268
|
}
|
|
269
269
|
return null;
|
|
270
270
|
}`;
|
|
271
|
+
/**
|
|
272
|
+
* Page-side mask over an element: the nearest of it and its ancestors in the
|
|
273
|
+
* flat tree (slots, shadow hosts, iframes) with a `mask-image`. How much the
|
|
274
|
+
* mask hides is not evaluated. Null without one.
|
|
275
|
+
*/
|
|
276
|
+
const MASKED_BY_JS = `(el, describe) => {
|
|
277
|
+
const flatParent = ${FLAT_PARENT_JS};
|
|
278
|
+
const parentOf = (n) => flatParent(n) || n.ownerDocument.defaultView.frameElement || null;
|
|
279
|
+
for (let n = el; n; n = parentOf(n)) {
|
|
280
|
+
const style = n.ownerDocument.defaultView.getComputedStyle(n);
|
|
281
|
+
const mask = style.maskImage || style.webkitMaskImage;
|
|
282
|
+
if (mask && mask !== 'none') return n === el ? 'mask-image' : 'mask-image on ' + describe(n);
|
|
283
|
+
}
|
|
284
|
+
return null;
|
|
285
|
+
}`;
|
|
271
286
|
/**
|
|
272
287
|
* Page-side measurement of an element ({@link ElementGeometry}): its box in
|
|
273
288
|
* top-level viewport coordinates, its clip by ancestors
|
|
274
289
|
* ({@link ANCESTOR_CLIP_JS}) and by the viewports of its iframes, why it
|
|
275
290
|
* cannot be seen at all (not rendered, or inside a clipping container
|
|
276
|
-
* collapsed to zero size
|
|
291
|
+
* collapsed to zero size; a `display: contents` element is measured by
|
|
292
|
+
* its children in the flat tree and is hidden only when none shows), why a rendered one is still invisible
|
|
277
293
|
* ({@link INVISIBLE_REASON_JS}), whether it is inert (an `inert` element
|
|
278
294
|
* around it, through shadow roots) or fixed to the top-level viewport, and how
|
|
279
295
|
* far the top-level page can scroll ({@link SCROLL_RANGE_JS}) or what locks
|
|
@@ -292,6 +308,7 @@ export const ELEMENT_GEOMETRY_JS = `(el) => {
|
|
|
292
308
|
const scrollRange = ${SCROLL_RANGE_JS};
|
|
293
309
|
const scrollLock = ${SCROLL_LOCK_JS};
|
|
294
310
|
const invisibleReason = ${INVISIBLE_REASON_JS};
|
|
311
|
+
const maskedBy = ${MASKED_BY_JS};
|
|
295
312
|
const describe = ${ELEMENT_DESCRIPTION_JS};
|
|
296
313
|
const reasons = ${JSON.stringify(LAYOUT_REASONS)};
|
|
297
314
|
const styleOf = (node) => node.ownerDocument.defaultView.getComputedStyle(node);
|
|
@@ -311,16 +328,34 @@ export const ELEMENT_GEOMETRY_JS = `(el) => {
|
|
|
311
328
|
}
|
|
312
329
|
return 'not rendered';
|
|
313
330
|
};
|
|
314
|
-
const
|
|
331
|
+
const flatChildren = (node) =>
|
|
332
|
+
Array.from((node.shadowRoot || node).children).flatMap((c) => {
|
|
333
|
+
const shown = c.localName === 'slot' ? c.assignedElements({ flatten: true }) : [c];
|
|
334
|
+
return shown.flatMap((s) => (styleOf(s).display === 'contents' ? flatChildren(s) : [s]));
|
|
335
|
+
});
|
|
336
|
+
const shownChildren = (node) => {
|
|
337
|
+
const s = styleOf(node);
|
|
338
|
+
if (s.overflowX !== 'visible' || s.overflowY !== 'visible') return null;
|
|
339
|
+
let union = null;
|
|
340
|
+
for (const c of flatChildren(node).slice(0, 50)) {
|
|
341
|
+
const r = c.getBoundingClientRect();
|
|
342
|
+
if (r.width <= 0 || r.height <= 0 || styleOf(c).visibility !== 'visible') continue;
|
|
343
|
+
union = union
|
|
344
|
+
? { left: Math.min(union.left, r.left), top: Math.min(union.top, r.top), right: Math.max(union.right, r.right), bottom: Math.max(union.bottom, r.bottom) }
|
|
345
|
+
: { left: r.left, top: r.top, right: r.right, bottom: r.bottom };
|
|
346
|
+
}
|
|
347
|
+
return union;
|
|
348
|
+
};
|
|
349
|
+
const hiddenReason = (style, box, content) => {
|
|
315
350
|
if (style.display === 'none') return 'display: none';
|
|
316
|
-
if (style.display === 'contents') return 'display: contents (no box of its own)';
|
|
351
|
+
if (style.display === 'contents') return content ? null : 'display: contents (no box of its own)';
|
|
317
352
|
if (el.getClientRects().length === 0) {
|
|
318
353
|
return el.tagName === 'OPTION' ? reasons.option : 'not rendered (an ancestor has display: none)';
|
|
319
354
|
}
|
|
320
355
|
const skipped = skippedReason(el);
|
|
321
356
|
if (skipped) return skipped;
|
|
322
357
|
if (style.visibility !== 'visible') return 'visibility: ' + style.visibility;
|
|
323
|
-
if (box.width === 0 || box.height === 0) return 'zero size';
|
|
358
|
+
if ((box.width === 0 || box.height === 0) && !content) return 'zero size';
|
|
324
359
|
return null;
|
|
325
360
|
};
|
|
326
361
|
const isSticky = () => {
|
|
@@ -343,7 +378,8 @@ export const ELEMENT_GEOMETRY_JS = `(el) => {
|
|
|
343
378
|
let clipper = own.clipper;
|
|
344
379
|
let fixed = own.fixed;
|
|
345
380
|
let fixedBy = own.fixedBy;
|
|
346
|
-
let
|
|
381
|
+
let content = box.width === 0 || box.height === 0 ? shownChildren(el) : null;
|
|
382
|
+
let hidden = hiddenReason(styleOf(el), box, content) || own.collapsed;
|
|
347
383
|
let x = 0;
|
|
348
384
|
let y = 0;
|
|
349
385
|
for (let view = el.ownerDocument.defaultView; view && view.frameElement; view = view.parent) {
|
|
@@ -355,6 +391,7 @@ export const ELEMENT_GEOMETRY_JS = `(el) => {
|
|
|
355
391
|
overlay = { right: !!unframed && unframed.right <= clip.right && overlay.right, bottom: !!unframed && unframed.bottom <= clip.bottom && overlay.bottom };
|
|
356
392
|
const offset = frameOffset(frame);
|
|
357
393
|
rect = shift(rect, offset.x, offset.y);
|
|
394
|
+
content = shift(content, offset.x, offset.y);
|
|
358
395
|
const outer = ancestorClip(frame, rect, describe);
|
|
359
396
|
const inner = shift(clip, offset.x, offset.y);
|
|
360
397
|
clip = intersect(inner, outer.clip);
|
|
@@ -371,9 +408,10 @@ export const ELEMENT_GEOMETRY_JS = `(el) => {
|
|
|
371
408
|
while (top.frameElement) top = top.parent;
|
|
372
409
|
const toBox = (r) => r && { x: r.left, y: r.top, width: r.right - r.left, height: r.bottom - r.top };
|
|
373
410
|
const invisible = hidden ? null : invisibleReason(el, describe);
|
|
411
|
+
const masked = hidden || invisible ? null : maskedBy(el, describe);
|
|
374
412
|
const lock = scrollLock(top, describe);
|
|
375
413
|
if (lock && fixedBy === top.document.body) fixed = false;
|
|
376
|
-
return { rect: toBox(rect), clip: toBox(clip), clipOverlay: overlay, clipper: clipper, hidden: hidden, invisible: invisible, inert: isInert(), fixed: fixed, sticky: !fixed && isSticky(), pageScroll: scrollRange(top), scrollLock: lock, offset: { x: x, y: y } };
|
|
414
|
+
return { rect: toBox(rect), content: toBox(content), clip: toBox(clip), clipOverlay: overlay, clipper: clipper, hidden: hidden, invisible: invisible, masked: masked, inert: isInert(), fixed: fixed, sticky: !fixed && isSticky(), pageScroll: scrollRange(top), scrollLock: lock, offset: { x: x, y: y } };
|
|
377
415
|
}`;
|
|
378
416
|
/**
|
|
379
417
|
* Edges of a box.
|
|
@@ -481,14 +519,15 @@ function axisScroll(start, size, viewSize, range, aim) {
|
|
|
481
519
|
* out of a list), and `hidden` when it is not rendered at all. Being inert
|
|
482
520
|
* does not change the position: inert elements are shown.
|
|
483
521
|
*
|
|
484
|
-
* @param
|
|
522
|
+
* @param measured - Page-side measurements (a zero-size holder is placed by its children)
|
|
485
523
|
* @param viewport - Top-level viewport size
|
|
486
524
|
* @returns Position, with the visible share, hidden reason, clipping
|
|
487
525
|
* ancestor or page scroll when relevant
|
|
488
526
|
*/
|
|
489
|
-
export function classifyViewportPosition(
|
|
490
|
-
if (
|
|
491
|
-
return { inViewport: 'hidden', hiddenReason:
|
|
527
|
+
export function classifyViewportPosition(measured, viewport) {
|
|
528
|
+
if (measured.hidden)
|
|
529
|
+
return { inViewport: 'hidden', hiddenReason: measured.hidden };
|
|
530
|
+
const geometry = measured.content ? { ...measured, rect: measured.content } : measured;
|
|
492
531
|
const rect = edgesOf(geometry.rect);
|
|
493
532
|
const screen = edgesOf({ x: 0, y: 0, ...viewport });
|
|
494
533
|
const view = geometry.clip ? overlap(screen, edgesOf(geometry.clip)) : screen;
|
|
@@ -3,6 +3,19 @@
|
|
|
3
3
|
* `dom layout`, DOM context of console messages): their text as a user sees
|
|
4
4
|
* it and where they live (iframes, shadow roots).
|
|
5
5
|
*/
|
|
6
|
+
/**
|
|
7
|
+
* Whether a class name is shown in element labels: not empty and not a
|
|
8
|
+
* fragment of a declaration-like class attribute ({@link CLASS_FRAGMENT}).
|
|
9
|
+
*
|
|
10
|
+
* @param name - Class name
|
|
11
|
+
* @returns True to show it
|
|
12
|
+
*/
|
|
13
|
+
export declare function isLabelClass(name: string): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Page-side `(node) => string[]`: the element's classes shown in labels
|
|
16
|
+
* ({@link isLabelClass}), read without array helpers a page may replace
|
|
17
|
+
*/
|
|
18
|
+
export declare const LABEL_CLASSES_JS: string;
|
|
6
19
|
/** Length of the text `dom get` shows for an element. */
|
|
7
20
|
export declare const ELEMENT_TEXT_LENGTH = 500;
|
|
8
21
|
/**
|
|
@@ -14,18 +27,45 @@ export declare const ELEMENT_TEXT_LENGTH = 500;
|
|
|
14
27
|
* text is removed once, from the end.
|
|
15
28
|
*/
|
|
16
29
|
export declare const WITHOUT_DECORATIONS_JS = "(el, text) => {\n if (!text || typeof el.querySelectorAll !== 'function') return text;\n const glyph = /^\\s*[\u00D7\u2715\u2716\u2717\u2A2F]\\s*$/;\n const textOf = (node) => (typeof node.innerText === 'string' ? node.innerText : node.textContent || '').trim();\n const closer = '.close, [aria-label=\"close\" i], [aria-label=\"dismiss\" i]';\n const rendered = (node) => !node.checkVisibility || node.checkVisibility();\n const found = Array.from(el.querySelectorAll(closer + ', [aria-hidden=\"true\"]'))\n .filter((node) => rendered(node) && (node.matches(closer) || !/[\\p{L}\\p{N}]/u.test(textOf(node))));\n if (/[\u00D7\u2715\u2716\u2717\u2A2F]/.test(text)) {\n found.push(...Array.from(el.querySelectorAll('button, a, [role=\"button\"]')).filter((node) => glyph.test(node.textContent || '')));\n }\n const unique = Array.from(new Set(found));\n const outermost = unique.filter((node) => !unique.some((other) => other !== node && other.contains(node)));\n let result = text;\n for (const node of outermost) {\n const part = textOf(node);\n const at = part ? result.lastIndexOf(part) : -1;\n if (at >= 0) result = result.slice(0, at) + result.slice(at + part.length);\n }\n return result;\n}";
|
|
30
|
+
/**
|
|
31
|
+
* Page-side check whether `innerText` misses part of what an element shows:
|
|
32
|
+
* it is a `<slot>` or hosts an open shadow root, or one of its descendants
|
|
33
|
+
* is or does. `innerText` follows neither shadow roots nor slots.
|
|
34
|
+
*/
|
|
35
|
+
export declare const COMPOSED_JS = "(node) => {\n const composes = (n) => Boolean(n.shadowRoot) || n.localName === 'slot';\n if (composes(node)) return true;\n const walker = node.ownerDocument.createTreeWalker(node, NodeFilter.SHOW_ELEMENT);\n while (walker.nextNode()) if (composes(walker.currentNode)) return true;\n return false;\n}";
|
|
36
|
+
/**
|
|
37
|
+
* Page-side text of an element as the flat tree renders it, for elements
|
|
38
|
+
* `innerText` cannot read ({@link COMPOSED_JS}): a shadow host is read from
|
|
39
|
+
* its open shadow root (its own labels and fallback content; light children
|
|
40
|
+
* only where a slot shows them), and each slot is replaced by what it
|
|
41
|
+
* shows, its assigned nodes flattened through nested slots (or its fallback
|
|
42
|
+
* content when nothing is assigned). Parts without slots or shadow roots
|
|
43
|
+
* are read with `innerText`; elements that are not rendered and text under
|
|
44
|
+
* `visibility: hidden` are left out, elements that are not inline are set
|
|
45
|
+
* apart by line breaks, as is a `<br>`. Fields and editable regions inside
|
|
46
|
+
* it (inputs, textareas, selects, a contenteditable editor) are skipped, so
|
|
47
|
+
* what a user typed is never read; raw text keeps `innerText`'s collapsed
|
|
48
|
+
* whitespace. The text is cut at `limit` characters, and a
|
|
49
|
+
* part whose text alone passes the limit is read from its text nodes
|
|
50
|
+
* (`textContent`) instead of `innerText`, which would lay out all of it.
|
|
51
|
+
*/
|
|
52
|
+
export declare const FLAT_TEXT_JS = "(el, limit) => {\n const composed = (node) => {\n const composes = (n) => Boolean(n.shadowRoot) || n.localName === 'slot';\n if (composes(node)) return true;\n const walker = node.ownerDocument.createTreeWalker(node, NodeFilter.SHOW_ELEMENT);\n while (walker.nextNode()) if (composes(walker.currentNode)) return true;\n return false;\n};\n const view = el.ownerDocument.defaultView;\n let text = '';\n const nodesOf = (node) =>\n node.localName === 'slot' ? node.assignedNodes({ flatten: true }) : Array.from((node.shadowRoot || node).childNodes);\n const read = (node, visible) => {\n if (text.length >= limit) return;\n if (node.nodeType === 3 && visible) text += node.data.replace(/\\s+/g, ' ');\n if (node.nodeType !== 1) return;\n if (/^(input|textarea|select)$/.test(node.localName) || node.isContentEditable) return;\n const style = view.getComputedStyle(node);\n if (node.checkVisibility && !node.checkVisibility() && style.display !== 'contents') return;\n if (node.localName === 'br') text += '\\n';\n const apart = style.display.startsWith('inline') || style.display === 'contents' ? '' : '\\n';\n text += apart;\n const own = node.textContent || '';\n if (composed(node)) nodesOf(node).forEach((child) => read(child, style.visibility === 'visible'));\n else if (own.length > limit - text.length) text += own.slice(0, limit - text.length);\n else text += typeof node.innerText === 'string' ? node.innerText : own;\n text += apart;\n };\n const visible = view.getComputedStyle(el).visibility === 'visible';\n nodesOf(el).forEach((child) => read(child, visible));\n return text.slice(0, limit);\n}";
|
|
17
53
|
/**
|
|
18
54
|
* Page-side text of an element as a user sees it: `innerText` for a rendered
|
|
19
55
|
* element (CSS-hidden parts left out, inline elements not split apart), none
|
|
20
56
|
* for an element that is not rendered, `textContent` for SVG and other
|
|
21
57
|
* elements without `innerText` and for `display: contents` wrappers (no box
|
|
22
58
|
* of their own, but their children are shown), and the label of an
|
|
23
|
-
* `<option>` (which its `<select>` renders).
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
59
|
+
* `<option>` (which its `<select>` renders). A web component, a slot, and
|
|
60
|
+
* an element holding either are read through the flat tree
|
|
61
|
+
* ({@link FLAT_TEXT_JS}): the text a component renders from its shadow
|
|
62
|
+
* root, slotted content in place of its slots, blocks set apart. For large
|
|
63
|
+
* containers (more than 2000 characters of text) only the start is read,
|
|
64
|
+
* from the text nodes whose parent is rendered, so a preview never lays out
|
|
65
|
+
* a whole page's text, unless `full` is set. Decorations are left out
|
|
66
|
+
* ({@link WITHOUT_DECORATIONS_JS}).
|
|
27
67
|
*/
|
|
28
|
-
export declare const ELEMENT_TEXT_JS = "(el, full) => {\n const withoutDecorations = (el, text) => {\n if (!text || typeof el.querySelectorAll !== 'function') return text;\n const glyph = /^\\s*[\u00D7\u2715\u2716\u2717\u2A2F]\\s*$/;\n const textOf = (node) => (typeof node.innerText === 'string' ? node.innerText : node.textContent || '').trim();\n const closer = '.close, [aria-label=\"close\" i], [aria-label=\"dismiss\" i]';\n const rendered = (node) => !node.checkVisibility || node.checkVisibility();\n const found = Array.from(el.querySelectorAll(closer + ', [aria-hidden=\"true\"]'))\n .filter((node) => rendered(node) && (node.matches(closer) || !/[\\p{L}\\p{N}]/u.test(textOf(node))));\n if (/[\u00D7\u2715\u2716\u2717\u2A2F]/.test(text)) {\n found.push(...Array.from(el.querySelectorAll('button, a, [role=\"button\"]')).filter((node) => glyph.test(node.textContent || '')));\n }\n const unique = Array.from(new Set(found));\n const outermost = unique.filter((node) => !unique.some((other) => other !== node && other.contains(node)));\n let result = text;\n for (const node of outermost) {\n const part = textOf(node);\n const at = part ? result.lastIndexOf(part) : -1;\n if (at >= 0) result = result.slice(0, at) + result.slice(at + part.length);\n }\n return result;\n};\n const all = el.textContent || '';\n if (el.tagName === 'OPTION') return el.label;\n if (typeof el.innerText !== 'string') return full ? all : all.slice(0, 2000);\n const rendered = (node) => !node.checkVisibility || node.checkVisibility();\n
|
|
68
|
+
export declare const ELEMENT_TEXT_JS = "(el, full) => {\n const withoutDecorations = (el, text) => {\n if (!text || typeof el.querySelectorAll !== 'function') return text;\n const glyph = /^\\s*[\u00D7\u2715\u2716\u2717\u2A2F]\\s*$/;\n const textOf = (node) => (typeof node.innerText === 'string' ? node.innerText : node.textContent || '').trim();\n const closer = '.close, [aria-label=\"close\" i], [aria-label=\"dismiss\" i]';\n const rendered = (node) => !node.checkVisibility || node.checkVisibility();\n const found = Array.from(el.querySelectorAll(closer + ', [aria-hidden=\"true\"]'))\n .filter((node) => rendered(node) && (node.matches(closer) || !/[\\p{L}\\p{N}]/u.test(textOf(node))));\n if (/[\u00D7\u2715\u2716\u2717\u2A2F]/.test(text)) {\n found.push(...Array.from(el.querySelectorAll('button, a, [role=\"button\"]')).filter((node) => glyph.test(node.textContent || '')));\n }\n const unique = Array.from(new Set(found));\n const outermost = unique.filter((node) => !unique.some((other) => other !== node && other.contains(node)));\n let result = text;\n for (const node of outermost) {\n const part = textOf(node);\n const at = part ? result.lastIndexOf(part) : -1;\n if (at >= 0) result = result.slice(0, at) + result.slice(at + part.length);\n }\n return result;\n};\n const composed = (node) => {\n const composes = (n) => Boolean(n.shadowRoot) || n.localName === 'slot';\n if (composes(node)) return true;\n const walker = node.ownerDocument.createTreeWalker(node, NodeFilter.SHOW_ELEMENT);\n while (walker.nextNode()) if (composes(walker.currentNode)) return true;\n return false;\n};\n const flatText = (el, limit) => {\n const composed = (node) => {\n const composes = (n) => Boolean(n.shadowRoot) || n.localName === 'slot';\n if (composes(node)) return true;\n const walker = node.ownerDocument.createTreeWalker(node, NodeFilter.SHOW_ELEMENT);\n while (walker.nextNode()) if (composes(walker.currentNode)) return true;\n return false;\n};\n const view = el.ownerDocument.defaultView;\n let text = '';\n const nodesOf = (node) =>\n node.localName === 'slot' ? node.assignedNodes({ flatten: true }) : Array.from((node.shadowRoot || node).childNodes);\n const read = (node, visible) => {\n if (text.length >= limit) return;\n if (node.nodeType === 3 && visible) text += node.data.replace(/\\s+/g, ' ');\n if (node.nodeType !== 1) return;\n if (/^(input|textarea|select)$/.test(node.localName) || node.isContentEditable) return;\n const style = view.getComputedStyle(node);\n if (node.checkVisibility && !node.checkVisibility() && style.display !== 'contents') return;\n if (node.localName === 'br') text += '\\n';\n const apart = style.display.startsWith('inline') || style.display === 'contents' ? '' : '\\n';\n text += apart;\n const own = node.textContent || '';\n if (composed(node)) nodesOf(node).forEach((child) => read(child, style.visibility === 'visible'));\n else if (own.length > limit - text.length) text += own.slice(0, limit - text.length);\n else text += typeof node.innerText === 'string' ? node.innerText : own;\n text += apart;\n };\n const visible = view.getComputedStyle(el).visibility === 'visible';\n nodesOf(el).forEach((child) => read(child, visible));\n return text.slice(0, limit);\n};\n const all = el.textContent || '';\n if (el.tagName === 'OPTION') return el.label;\n if (typeof el.innerText !== 'string') return full ? all : all.slice(0, 2000);\n const rendered = (node) => !node.checkVisibility || node.checkVisibility();\n const shown = rendered(el);\n const boxless = !shown && el.ownerDocument.defaultView.getComputedStyle(el).display === 'contents';\n if (!shown && !boxless) return '';\n if (composed(el)) return withoutDecorations(el, flatText(el, full ? Infinity : 2000));\n if (boxless) return withoutDecorations(el, full ? all : all.slice(0, 2000));\n if (full || all.length <= 2000) return withoutDecorations(el, el.innerText);\n const walker = el.ownerDocument.createTreeWalker(el, NodeFilter.SHOW_TEXT);\n let start = '';\n while (start.length < 1000 && walker.nextNode()) {\n const parent = walker.currentNode.parentElement;\n if (!parent || rendered(parent)) start += walker.currentNode.data;\n }\n return withoutDecorations(el, start);\n}";
|
|
29
69
|
/** Shown instead of a secret field value (the same for every length) */
|
|
30
70
|
export declare const MASKED_VALUE = "\u2022\u2022\u2022\u2022";
|
|
31
71
|
/**
|
|
@@ -53,26 +93,40 @@ export declare const SENSITIVE_FIELD_JS = "(el) => {\n const autocomplete = el.
|
|
|
53
93
|
*/
|
|
54
94
|
export declare const ELEMENT_STATE_JS = "(el) => {\n const isSensitive = (el) => {\n const autocomplete = el.getAttribute('autocomplete') || '';\n if (/(^|\\s)(cc-[a-z-]+|one-time-code|current-password|new-password)(\\s|$)/i.test(autocomplete)) return true;\n if (el.type === 'password' || /^password$/i.test(el.getAttribute('type') || '')) return true;\n const names = [el.getAttribute('name'), el.id, autocomplete].join(' ');\n if (/passw|passwd|pwd|passcode|(^|[^a-z])otp([^a-z]|$)|cvv|cvc/i.test(names)) return true;\n try {\n const security = el.ownerDocument.defaultView.getComputedStyle(el).getPropertyValue('-webkit-text-security');\n return Boolean(security) && security !== 'none';\n } catch (e) {\n return false;\n }\n};\n const mask = (value) => (value ? '\u2022\u2022\u2022\u2022' : '');\n const guarded = (state) => {\n if (!isSensitive(el)) return state;\n const result = { ...state, sensitive: true };\n if ('value' in result) result.value = mask(result.value);\n if ('selected' in result) result.selected = mask(result.selected);\n return result;\n };\n switch (el.localName) {\n case 'input':\n if (el.type === 'hidden') return { type: 'hidden' };\n return guarded(\n /^(checkbox|radio)$/.test(el.type)\n ? { type: el.type, checked: el.checked, value: el.getAttribute('value') || '' }\n : { type: el.type, value: el.value }\n );\n case 'textarea':\n return guarded({ value: el.value });\n case 'select':\n return guarded({ selected: Array.from(el.selectedOptions || [], (option) => option.label).join(', ') });\n case 'button':\n return el.form ? { type: el.type } : {};\n default:\n return {};\n }\n}";
|
|
55
95
|
/**
|
|
56
|
-
* Page-side short description of an element: tag, id and up to two classes
|
|
57
|
-
* e.g. `button#save.primary.large`.
|
|
96
|
+
* Page-side short description of an element: tag, id and up to two classes
|
|
97
|
+
* ({@link LABEL_CLASSES_JS}), e.g. `button#save.primary.large`.
|
|
98
|
+
*/
|
|
99
|
+
export declare const ELEMENT_DESCRIPTION_JS: string;
|
|
100
|
+
/**
|
|
101
|
+
* Page-side reason an element is disabled, or null when it is not. A native
|
|
102
|
+
* control (`button`, `input`, `select`, `textarea`, `fieldset`, `optgroup`,
|
|
103
|
+
* `option`) is disabled by its `disabled attribute` or by being `inside a
|
|
104
|
+
* disabled <fieldset>` (not in its first `<legend>`), an `<option>` by
|
|
105
|
+
* being `inside a disabled <optgroup>`; any other element
|
|
106
|
+
* (e.g. a custom element) when its `disabled` property is set (empty
|
|
107
|
+
* reason). Reads the element's own attributes rather than
|
|
108
|
+
* `matches(':disabled')`, which pages replace.
|
|
58
109
|
*/
|
|
59
|
-
export declare const
|
|
110
|
+
export declare const DISABLED_CAUSE_JS = "(el) => {\n if (!/^(button|input|select|textarea|fieldset|optgroup|option)$/.test(el.localName)) return el.disabled ? '' : null;\n if (el.hasAttribute('disabled')) return 'disabled attribute';\n if (el.localName === 'option') {\n const group = el.parentElement;\n return group && group.localName === 'optgroup' && group.hasAttribute('disabled') ? 'inside a disabled <optgroup>' : null;\n }\n for (let node = el.parentElement; node; node = node.parentElement) {\n if (node.localName !== 'fieldset' || !node.hasAttribute('disabled')) continue;\n let legend = node.firstElementChild;\n while (legend && legend.localName !== 'legend') legend = legend.nextElementSibling;\n if (!legend || !legend.contains(el)) return 'inside a disabled <fieldset>';\n }\n return null;\n}";
|
|
60
111
|
/**
|
|
61
112
|
* Page-side position of an element among its parent's children with the
|
|
62
113
|
* same short description ({@link ELEMENT_DESCRIPTION_JS}), e.g. `(2nd of 3)`;
|
|
63
114
|
* empty when it is the only one.
|
|
64
115
|
*/
|
|
65
|
-
export declare const SIBLING_POSITION_JS
|
|
116
|
+
export declare const SIBLING_POSITION_JS: string;
|
|
66
117
|
/**
|
|
67
118
|
* Page-side identity of an element an action hit, so the output says which
|
|
68
119
|
* one it was, as one string: its short description
|
|
69
120
|
* ({@link ELEMENT_DESCRIPTION_JS}) and visible text (button value for button
|
|
70
|
-
* inputs, decorations left out
|
|
71
|
-
*
|
|
121
|
+
* inputs, decorations left out; read through the flat tree for web
|
|
122
|
+
* components and slots, {@link FLAT_TEXT_JS}), e.g.
|
|
123
|
+
* `button#add.btn "Add to cart"`. A `<select>` is named by its label
|
|
124
|
+
* (`aria-labelledby` or `<label>`), aria-label or name, else by its selected
|
|
72
125
|
* option, e.g. `select.sort "Sort products"`. An element without visible
|
|
73
126
|
* text is described by itself first: by its position among same-looking
|
|
74
127
|
* siblings ({@link SIBLING_POSITION_JS}), e.g. `div.figure (2nd of 3)`, with
|
|
75
|
-
* its
|
|
128
|
+
* its name when it has one: aria-label, label, placeholder, title, or the
|
|
129
|
+
* alt text of an image (in it). Only an element
|
|
76
130
|
* without an id that is the only one of its kind is named by the nearest of
|
|
77
131
|
* three ancestors that has text, e.g.
|
|
78
132
|
* `input.toggle in div.view "Write report"` (rows of a list share their
|
|
@@ -80,17 +134,20 @@ export declare const SIBLING_POSITION_JS = "(el) => {\n const describe = (node)
|
|
|
80
134
|
* of `<select>`s in it (read like `innerText`: CSS-hidden text is left out of
|
|
81
135
|
* a rendered ancestor; at most 500 text nodes; selects in shadow roots are
|
|
82
136
|
* not looked into), and never past an editable ancestor, whose text may be
|
|
83
|
-
* typed input; otherwise its
|
|
137
|
+
* typed input; otherwise its name is used. Texts
|
|
84
138
|
* are cut at 40 characters. Contenteditable elements count as controls: what
|
|
85
139
|
* was typed into them is never echoed (nor their ancestors' text).
|
|
86
140
|
*/
|
|
87
|
-
export declare const ELEMENT_IDENTITY_JS
|
|
141
|
+
export declare const ELEMENT_IDENTITY_JS: string;
|
|
88
142
|
/**
|
|
89
143
|
* Page-side location of an element: the iframes it is in (outermost first)
|
|
90
|
-
* and the shadow root holding it,
|
|
91
|
-
*
|
|
144
|
+
* and the shadow root holding it, with the host's first class and the text
|
|
145
|
+
* a user sees of it ({@link ELEMENT_TEXT_JS}, cut at 30 characters with
|
|
146
|
+
* `…`), e.g. `iframe#pay > shadow root of <sl-button.primary "Save">`;
|
|
147
|
+
* empty for the main document. Each host's text is read once per script
|
|
148
|
+
* run, as many matches share a host.
|
|
92
149
|
*/
|
|
93
|
-
export declare const ELEMENT_CONTEXT_JS
|
|
150
|
+
export declare const ELEMENT_CONTEXT_JS: string;
|
|
94
151
|
/**
|
|
95
152
|
* Short text preview of an element's text: whitespace collapsed, cut on a
|
|
96
153
|
* whole character (an emoji is never split, which would make JSON invalid).
|