browser-debugger-cli 0.12.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 +1 -0
- 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/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/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 +8 -7
- 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/query.d.ts +27 -3
- package/dist/commands/dom/helpers/query.js +152 -64
- package/dist/commands/dom/helpers/screenshot.js +13 -13
- package/dist/commands/dom/index.js +4 -2
- package/dist/commands/dom/query.d.ts +19 -2
- package/dist/commands/dom/query.js +37 -6
- package/dist/commands/dom/screenshot.js +2 -1
- package/dist/commands/dom/semanticUtils.d.ts +3 -2
- package/dist/commands/dom/semanticUtils.js +40 -9
- package/dist/commands/helpJson.d.ts +82 -19
- package/dist/commands/helpJson.js +111 -40
- 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 +33 -11
- 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 +4 -1
- package/dist/commands/shared/outputFile.js +6 -1
- package/dist/commands/start.d.ts +7 -5
- package/dist/commands/start.js +65 -21
- package/dist/commands/stop.d.ts +11 -0
- package/dist/commands/stop.js +24 -1
- package/dist/commands.js +1 -1
- 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 +29 -13
- package/dist/daemon/session/interactions.d.ts +2 -1
- package/dist/daemon/session/interactions.js +13 -1
- package/dist/daemon/session/plugins.js +16 -2
- package/dist/daemon/session/teardown.js +1 -1
- package/dist/daemon.js +1622 -748
- package/dist/errors/messages.d.ts +54 -11
- package/dist/errors/messages.js +109 -22
- package/dist/index.js +13733 -8796
- package/dist/ipc/client.d.ts +18 -2
- package/dist/ipc/client.js +26 -5
- package/dist/ipc/protocol/auditTypes.d.ts +8 -2
- package/dist/ipc/protocol/commands.d.ts +12 -0
- package/dist/ipc/protocol/domTypes.d.ts +12 -0
- package/dist/ipc/protocol/inspectTypes.d.ts +2 -0
- package/dist/ipc/session/types.d.ts +2 -0
- package/dist/runtime/dom/actionEffects.d.ts +5 -1
- package/dist/runtime/dom/actionEffects.js +26 -14
- package/dist/runtime/dom/audit.js +3 -2
- package/dist/runtime/dom/auditModel.js +6 -1
- package/dist/runtime/dom/auditScripts.d.ts +9 -3
- package/dist/runtime/dom/auditScripts.js +41 -5
- package/dist/runtime/dom/elementGeometry.d.ts +10 -3
- package/dist/runtime/dom/elementGeometry.js +27 -4
- package/dist/runtime/dom/elementInfo.d.ts +74 -18
- package/dist/runtime/dom/elementInfo.js +187 -40
- 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 +7 -11
- 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.js +5 -6
- package/dist/runtime/dom/inspectAllStyles.js +1 -0
- package/dist/runtime/dom/inspectHints.d.ts +1 -1
- package/dist/runtime/dom/inspectModel.d.ts +2 -1
- package/dist/runtime/dom/inspectModel.js +7 -3
- package/dist/runtime/dom/inspectPaintModel.d.ts +2 -0
- package/dist/runtime/dom/inspectPaintModel.js +3 -1
- package/dist/runtime/dom/inspectScripts.d.ts +29 -2
- package/dist/runtime/dom/inspectScripts.js +49 -10
- package/dist/runtime/dom/layout.js +9 -7
- package/dist/runtime/dom/reactEventHelpers.d.ts +14 -4
- package/dist/runtime/dom/reactEventHelpers.js +63 -27
- 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/replacedBuiltins.d.ts +28 -0
- package/dist/runtime/page/replacedBuiltins.js +136 -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 +16 -0
- package/dist/ui/formatters/audit.js +14 -5
- 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 +1 -1
- package/dist/ui/formatters/dom.js +5 -6
- package/dist/ui/formatters/helpFormatters.js +1 -1
- package/dist/ui/formatters/inspect.js +9 -3
- 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/logging/logger.d.ts +1 -1
- package/dist/ui/messages/commands.d.ts +124 -4
- package/dist/ui/messages/commands.js +162 -7
- package/dist/ui/messages/consoleMessages.d.ts +24 -0
- package/dist/ui/messages/consoleMessages.js +32 -0
- package/dist/ui/messages/preview.d.ts +6 -0
- package/dist/ui/messages/preview.js +9 -1
- package/dist/ui/messages/session.d.ts +13 -2
- package/dist/ui/messages/session.js +22 -3
- 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/package.json +1 -1
|
@@ -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,18 +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, with the host's first class and
|
|
91
|
-
*
|
|
92
|
-
*
|
|
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.
|
|
93
149
|
*/
|
|
94
|
-
export declare const ELEMENT_CONTEXT_JS
|
|
150
|
+
export declare const ELEMENT_CONTEXT_JS: string;
|
|
95
151
|
/**
|
|
96
152
|
* Short text preview of an element's text: whitespace collapsed, cut on a
|
|
97
153
|
* whole character (an emoji is never split, which would make JSON invalid).
|
|
@@ -3,6 +3,31 @@
|
|
|
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
|
+
* A class that is a fragment of a declaration-like class attribute
|
|
8
|
+
* (`class="brush: html"` has the class `brush:`), left out of labels.
|
|
9
|
+
*/
|
|
10
|
+
const CLASS_FRAGMENT = /[:;,]$/;
|
|
11
|
+
/**
|
|
12
|
+
* Whether a class name is shown in element labels: not empty and not a
|
|
13
|
+
* fragment of a declaration-like class attribute ({@link CLASS_FRAGMENT}).
|
|
14
|
+
*
|
|
15
|
+
* @param name - Class name
|
|
16
|
+
* @returns True to show it
|
|
17
|
+
*/
|
|
18
|
+
export function isLabelClass(name) {
|
|
19
|
+
return name !== '' && !CLASS_FRAGMENT.test(name);
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Page-side `(node) => string[]`: the element's classes shown in labels
|
|
23
|
+
* ({@link isLabelClass}), read without array helpers a page may replace
|
|
24
|
+
*/
|
|
25
|
+
export const LABEL_CLASSES_JS = `(node) => {
|
|
26
|
+
const shown = [];
|
|
27
|
+
const list = node.classList || [];
|
|
28
|
+
for (let i = 0; i < list.length; i++) if (!${CLASS_FRAGMENT}.test(list[i])) shown[shown.length] = list[i];
|
|
29
|
+
return shown;
|
|
30
|
+
}`;
|
|
6
31
|
/** Length of the text preview shown for elements. */
|
|
7
32
|
const PREVIEW_LENGTH = 80;
|
|
8
33
|
/** Length of the text `dom get` shows for an element. */
|
|
@@ -36,27 +61,88 @@ export const WITHOUT_DECORATIONS_JS = `(el, text) => {
|
|
|
36
61
|
}
|
|
37
62
|
return result;
|
|
38
63
|
}`;
|
|
64
|
+
/**
|
|
65
|
+
* Page-side check whether `innerText` misses part of what an element shows:
|
|
66
|
+
* it is a `<slot>` or hosts an open shadow root, or one of its descendants
|
|
67
|
+
* is or does. `innerText` follows neither shadow roots nor slots.
|
|
68
|
+
*/
|
|
69
|
+
export const COMPOSED_JS = `(node) => {
|
|
70
|
+
const composes = (n) => Boolean(n.shadowRoot) || n.localName === 'slot';
|
|
71
|
+
if (composes(node)) return true;
|
|
72
|
+
const walker = node.ownerDocument.createTreeWalker(node, NodeFilter.SHOW_ELEMENT);
|
|
73
|
+
while (walker.nextNode()) if (composes(walker.currentNode)) return true;
|
|
74
|
+
return false;
|
|
75
|
+
}`;
|
|
76
|
+
/**
|
|
77
|
+
* Page-side text of an element as the flat tree renders it, for elements
|
|
78
|
+
* `innerText` cannot read ({@link COMPOSED_JS}): a shadow host is read from
|
|
79
|
+
* its open shadow root (its own labels and fallback content; light children
|
|
80
|
+
* only where a slot shows them), and each slot is replaced by what it
|
|
81
|
+
* shows, its assigned nodes flattened through nested slots (or its fallback
|
|
82
|
+
* content when nothing is assigned). Parts without slots or shadow roots
|
|
83
|
+
* are read with `innerText`; elements that are not rendered and text under
|
|
84
|
+
* `visibility: hidden` are left out, elements that are not inline are set
|
|
85
|
+
* apart by line breaks, as is a `<br>`. Fields and editable regions inside
|
|
86
|
+
* it (inputs, textareas, selects, a contenteditable editor) are skipped, so
|
|
87
|
+
* what a user typed is never read; raw text keeps `innerText`'s collapsed
|
|
88
|
+
* whitespace. The text is cut at `limit` characters, and a
|
|
89
|
+
* part whose text alone passes the limit is read from its text nodes
|
|
90
|
+
* (`textContent`) instead of `innerText`, which would lay out all of it.
|
|
91
|
+
*/
|
|
92
|
+
export const FLAT_TEXT_JS = `(el, limit) => {
|
|
93
|
+
const composed = ${COMPOSED_JS};
|
|
94
|
+
const view = el.ownerDocument.defaultView;
|
|
95
|
+
let text = '';
|
|
96
|
+
const nodesOf = (node) =>
|
|
97
|
+
node.localName === 'slot' ? node.assignedNodes({ flatten: true }) : Array.from((node.shadowRoot || node).childNodes);
|
|
98
|
+
const read = (node, visible) => {
|
|
99
|
+
if (text.length >= limit) return;
|
|
100
|
+
if (node.nodeType === 3 && visible) text += node.data.replace(/\\s+/g, ' ');
|
|
101
|
+
if (node.nodeType !== 1) return;
|
|
102
|
+
if (/^(input|textarea|select)$/.test(node.localName) || node.isContentEditable) return;
|
|
103
|
+
const style = view.getComputedStyle(node);
|
|
104
|
+
if (node.checkVisibility && !node.checkVisibility() && style.display !== 'contents') return;
|
|
105
|
+
if (node.localName === 'br') text += '\\n';
|
|
106
|
+
const apart = style.display.startsWith('inline') || style.display === 'contents' ? '' : '\\n';
|
|
107
|
+
text += apart;
|
|
108
|
+
const own = node.textContent || '';
|
|
109
|
+
if (composed(node)) nodesOf(node).forEach((child) => read(child, style.visibility === 'visible'));
|
|
110
|
+
else if (own.length > limit - text.length) text += own.slice(0, limit - text.length);
|
|
111
|
+
else text += typeof node.innerText === 'string' ? node.innerText : own;
|
|
112
|
+
text += apart;
|
|
113
|
+
};
|
|
114
|
+
const visible = view.getComputedStyle(el).visibility === 'visible';
|
|
115
|
+
nodesOf(el).forEach((child) => read(child, visible));
|
|
116
|
+
return text.slice(0, limit);
|
|
117
|
+
}`;
|
|
39
118
|
/**
|
|
40
119
|
* Page-side text of an element as a user sees it: `innerText` for a rendered
|
|
41
120
|
* element (CSS-hidden parts left out, inline elements not split apart), none
|
|
42
121
|
* for an element that is not rendered, `textContent` for SVG and other
|
|
43
122
|
* elements without `innerText` and for `display: contents` wrappers (no box
|
|
44
123
|
* of their own, but their children are shown), and the label of an
|
|
45
|
-
* `<option>` (which its `<select>` renders).
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
124
|
+
* `<option>` (which its `<select>` renders). A web component, a slot, and
|
|
125
|
+
* an element holding either are read through the flat tree
|
|
126
|
+
* ({@link FLAT_TEXT_JS}): the text a component renders from its shadow
|
|
127
|
+
* root, slotted content in place of its slots, blocks set apart. For large
|
|
128
|
+
* containers (more than 2000 characters of text) only the start is read,
|
|
129
|
+
* from the text nodes whose parent is rendered, so a preview never lays out
|
|
130
|
+
* a whole page's text, unless `full` is set. Decorations are left out
|
|
131
|
+
* ({@link WITHOUT_DECORATIONS_JS}).
|
|
49
132
|
*/
|
|
50
133
|
export const ELEMENT_TEXT_JS = `(el, full) => {
|
|
51
134
|
const withoutDecorations = ${WITHOUT_DECORATIONS_JS};
|
|
135
|
+
const composed = ${COMPOSED_JS};
|
|
136
|
+
const flatText = ${FLAT_TEXT_JS};
|
|
52
137
|
const all = el.textContent || '';
|
|
53
138
|
if (el.tagName === 'OPTION') return el.label;
|
|
54
139
|
if (typeof el.innerText !== 'string') return full ? all : all.slice(0, 2000);
|
|
55
140
|
const rendered = (node) => !node.checkVisibility || node.checkVisibility();
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
141
|
+
const shown = rendered(el);
|
|
142
|
+
const boxless = !shown && el.ownerDocument.defaultView.getComputedStyle(el).display === 'contents';
|
|
143
|
+
if (!shown && !boxless) return '';
|
|
144
|
+
if (composed(el)) return withoutDecorations(el, flatText(el, full ? Infinity : 2000));
|
|
145
|
+
if (boxless) return withoutDecorations(el, full ? all : all.slice(0, 2000));
|
|
60
146
|
if (full || all.length <= 2000) return withoutDecorations(el, el.innerText);
|
|
61
147
|
const walker = el.ownerDocument.createTreeWalker(el, NodeFilter.SHOW_TEXT);
|
|
62
148
|
let start = '';
|
|
@@ -132,12 +218,38 @@ export const ELEMENT_STATE_JS = `(el) => {
|
|
|
132
218
|
}
|
|
133
219
|
}`;
|
|
134
220
|
/**
|
|
135
|
-
* Page-side short description of an element: tag, id and up to two classes
|
|
136
|
-
* e.g. `button#save.primary.large`.
|
|
221
|
+
* Page-side short description of an element: tag, id and up to two classes
|
|
222
|
+
* ({@link LABEL_CLASSES_JS}), e.g. `button#save.primary.large`.
|
|
137
223
|
*/
|
|
138
|
-
export const ELEMENT_DESCRIPTION_JS = `(node) =>
|
|
139
|
-
|
|
140
|
-
|
|
224
|
+
export const ELEMENT_DESCRIPTION_JS = `(node) => {
|
|
225
|
+
const classes = (${LABEL_CLASSES_JS})(node);
|
|
226
|
+
return node.tagName.toLowerCase() + (node.id ? '#' + node.id : '') + (classes[0] ? '.' + classes[0] : '') + (classes[1] ? '.' + classes[1] : '');
|
|
227
|
+
}`;
|
|
228
|
+
/**
|
|
229
|
+
* Page-side reason an element is disabled, or null when it is not. A native
|
|
230
|
+
* control (`button`, `input`, `select`, `textarea`, `fieldset`, `optgroup`,
|
|
231
|
+
* `option`) is disabled by its `disabled attribute` or by being `inside a
|
|
232
|
+
* disabled <fieldset>` (not in its first `<legend>`), an `<option>` by
|
|
233
|
+
* being `inside a disabled <optgroup>`; any other element
|
|
234
|
+
* (e.g. a custom element) when its `disabled` property is set (empty
|
|
235
|
+
* reason). Reads the element's own attributes rather than
|
|
236
|
+
* `matches(':disabled')`, which pages replace.
|
|
237
|
+
*/
|
|
238
|
+
export const DISABLED_CAUSE_JS = `(el) => {
|
|
239
|
+
if (!/^(button|input|select|textarea|fieldset|optgroup|option)$/.test(el.localName)) return el.disabled ? '' : null;
|
|
240
|
+
if (el.hasAttribute('disabled')) return 'disabled attribute';
|
|
241
|
+
if (el.localName === 'option') {
|
|
242
|
+
const group = el.parentElement;
|
|
243
|
+
return group && group.localName === 'optgroup' && group.hasAttribute('disabled') ? 'inside a disabled <optgroup>' : null;
|
|
244
|
+
}
|
|
245
|
+
for (let node = el.parentElement; node; node = node.parentElement) {
|
|
246
|
+
if (node.localName !== 'fieldset' || !node.hasAttribute('disabled')) continue;
|
|
247
|
+
let legend = node.firstElementChild;
|
|
248
|
+
while (legend && legend.localName !== 'legend') legend = legend.nextElementSibling;
|
|
249
|
+
if (!legend || !legend.contains(el)) return 'inside a disabled <fieldset>';
|
|
250
|
+
}
|
|
251
|
+
return null;
|
|
252
|
+
}`;
|
|
141
253
|
/**
|
|
142
254
|
* Page-side position of an element among its parent's children with the
|
|
143
255
|
* same short description ({@link ELEMENT_DESCRIPTION_JS}), e.g. `(2nd of 3)`;
|
|
@@ -159,12 +271,15 @@ export const SIBLING_POSITION_JS = `(el) => {
|
|
|
159
271
|
* Page-side identity of an element an action hit, so the output says which
|
|
160
272
|
* one it was, as one string: its short description
|
|
161
273
|
* ({@link ELEMENT_DESCRIPTION_JS}) and visible text (button value for button
|
|
162
|
-
* inputs, decorations left out
|
|
163
|
-
*
|
|
274
|
+
* inputs, decorations left out; read through the flat tree for web
|
|
275
|
+
* components and slots, {@link FLAT_TEXT_JS}), e.g.
|
|
276
|
+
* `button#add.btn "Add to cart"`. A `<select>` is named by its label
|
|
277
|
+
* (`aria-labelledby` or `<label>`), aria-label or name, else by its selected
|
|
164
278
|
* option, e.g. `select.sort "Sort products"`. An element without visible
|
|
165
279
|
* text is described by itself first: by its position among same-looking
|
|
166
280
|
* siblings ({@link SIBLING_POSITION_JS}), e.g. `div.figure (2nd of 3)`, with
|
|
167
|
-
* its
|
|
281
|
+
* its name when it has one: aria-label, label, placeholder, title, or the
|
|
282
|
+
* alt text of an image (in it). Only an element
|
|
168
283
|
* without an id that is the only one of its kind is named by the nearest of
|
|
169
284
|
* three ancestors that has text, e.g.
|
|
170
285
|
* `input.toggle in div.view "Write report"` (rows of a list share their
|
|
@@ -172,7 +287,7 @@ export const SIBLING_POSITION_JS = `(el) => {
|
|
|
172
287
|
* of `<select>`s in it (read like `innerText`: CSS-hidden text is left out of
|
|
173
288
|
* a rendered ancestor; at most 500 text nodes; selects in shadow roots are
|
|
174
289
|
* not looked into), and never past an editable ancestor, whose text may be
|
|
175
|
-
* typed input; otherwise its
|
|
290
|
+
* typed input; otherwise its name is used. Texts
|
|
176
291
|
* are cut at 40 characters. Contenteditable elements count as controls: what
|
|
177
292
|
* was typed into them is never echoed (nor their ancestors' text).
|
|
178
293
|
*/
|
|
@@ -180,19 +295,36 @@ export const ELEMENT_IDENTITY_JS = `(el) => {
|
|
|
180
295
|
const describe = ${ELEMENT_DESCRIPTION_JS};
|
|
181
296
|
const siblingPosition = ${SIBLING_POSITION_JS};
|
|
182
297
|
const withoutDecorations = ${WITHOUT_DECORATIONS_JS};
|
|
298
|
+
const composed = ${COMPOSED_JS};
|
|
299
|
+
const flatText = ${FLAT_TEXT_JS};
|
|
183
300
|
const clean = (text) => (text || '').replace(/\\s+/g, ' ').trim();
|
|
184
301
|
const cut = (text) => {
|
|
185
302
|
const characters = Array.from(text);
|
|
186
303
|
return characters.length > 40 ? characters.slice(0, 40).join('') + '…' : text;
|
|
187
304
|
};
|
|
188
305
|
const isControl = (node) => /^(input|select|textarea)$/.test(node.localName) || node.isContentEditable;
|
|
189
|
-
const
|
|
190
|
-
|
|
306
|
+
const textOf = (node) =>
|
|
307
|
+
composed(node) ? flatText(node, 200) : typeof node.innerText === 'string' ? node.innerText : node.textContent;
|
|
308
|
+
const shownText = (node) => (isControl(node) ? '' : clean(withoutDecorations(node, textOf(node))));
|
|
191
309
|
const buttonValue = (node) => (node.localName === 'input' && /^(submit|button|reset)$/i.test(node.type) ? clean(node.value) : '');
|
|
310
|
+
const labelText = (node) => {
|
|
311
|
+
const ids = (node.getAttribute('aria-labelledby') || '').split(/\\s+/).filter(Boolean);
|
|
312
|
+
const root = node.getRootNode();
|
|
313
|
+
const labels = ids.length ? ids.map((id) => root.getElementById(id)) : Array.from(node.labels || []);
|
|
314
|
+
return clean(labels.filter(Boolean).map(textOf).join(' '));
|
|
315
|
+
};
|
|
316
|
+
const imageAlt = (node) => {
|
|
317
|
+
const image = node.localName === 'img' ? node : node.querySelector('img[alt]');
|
|
318
|
+
return image ? clean(image.getAttribute('alt')) : '';
|
|
319
|
+
};
|
|
192
320
|
const attributeText = (node) =>
|
|
193
|
-
clean(node.getAttribute('aria-label')) ||
|
|
321
|
+
clean(node.getAttribute('aria-label')) ||
|
|
322
|
+
labelText(node) ||
|
|
323
|
+
clean(node.getAttribute('placeholder')) ||
|
|
324
|
+
clean(node.getAttribute('title')) ||
|
|
325
|
+
imageAlt(node);
|
|
194
326
|
const selectName = (node) =>
|
|
195
|
-
|
|
327
|
+
labelText(node) ||
|
|
196
328
|
clean(node.getAttribute('aria-label')) ||
|
|
197
329
|
clean(node.getAttribute('name')) ||
|
|
198
330
|
clean(node.selectedOptions && node.selectedOptions[0] && node.selectedOptions[0].label);
|
|
@@ -225,27 +357,42 @@ export const ELEMENT_IDENTITY_JS = `(el) => {
|
|
|
225
357
|
}`;
|
|
226
358
|
/**
|
|
227
359
|
* Page-side location of an element: the iframes it is in (outermost first)
|
|
228
|
-
* and the shadow root holding it, with the host's first class and
|
|
229
|
-
*
|
|
230
|
-
*
|
|
360
|
+
* and the shadow root holding it, with the host's first class and the text
|
|
361
|
+
* a user sees of it ({@link ELEMENT_TEXT_JS}, cut at 30 characters with
|
|
362
|
+
* `…`), e.g. `iframe#pay > shadow root of <sl-button.primary "Save">`;
|
|
363
|
+
* empty for the main document. Each host's text is read once per script
|
|
364
|
+
* run, as many matches share a host.
|
|
231
365
|
*/
|
|
232
|
-
export const ELEMENT_CONTEXT_JS = `(
|
|
366
|
+
export const ELEMENT_CONTEXT_JS = `(() => {
|
|
367
|
+
const textOf = ${ELEMENT_TEXT_JS};
|
|
368
|
+
const hostTexts = new WeakMap();
|
|
369
|
+
const hostText = (host) => {
|
|
370
|
+
if (!hostTexts.has(host)) {
|
|
371
|
+
const characters = Array.from(String(textOf(host, false)).replace(/\\s+/g, ' ').trim());
|
|
372
|
+
hostTexts.set(host, characters.length > 30 ? characters.slice(0, 30).join('') + '…' : characters.join(''));
|
|
373
|
+
}
|
|
374
|
+
return hostTexts.get(host);
|
|
375
|
+
};
|
|
233
376
|
const describe = (node) => node.tagName.toLowerCase() + (node.id ? '#' + node.id : '');
|
|
234
|
-
const
|
|
235
|
-
|
|
236
|
-
const
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
const
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
377
|
+
const labelClasses = ${LABEL_CLASSES_JS};
|
|
378
|
+
return (el) => {
|
|
379
|
+
const parts = [];
|
|
380
|
+
for (let doc = el.ownerDocument; doc && doc.defaultView && doc.defaultView.frameElement; ) {
|
|
381
|
+
const frame = doc.defaultView.frameElement;
|
|
382
|
+
parts.unshift(describe(frame));
|
|
383
|
+
doc = frame.ownerDocument;
|
|
384
|
+
}
|
|
385
|
+
const root = el.getRootNode();
|
|
386
|
+
if (root.host) {
|
|
387
|
+
const host = root.host;
|
|
388
|
+
const firstClass = labelClasses(host)[0];
|
|
389
|
+
const label = describe(host) + (!host.id && firstClass ? '.' + firstClass : '');
|
|
390
|
+
const text = hostText(host);
|
|
391
|
+
parts.push('shadow root of <' + label + (text ? ' "' + text + '"' : '') + '>');
|
|
392
|
+
}
|
|
393
|
+
return parts.join(' > ');
|
|
394
|
+
};
|
|
395
|
+
})()`;
|
|
249
396
|
/**
|
|
250
397
|
* Short text preview of an element's text: whitespace collapsed, cut on a
|
|
251
398
|
* whole character (an emoji is never split, which would make JSON invalid).
|
|
@@ -11,6 +11,13 @@ import { type CDPSender } from '../../telemetry/objectExpander.js';
|
|
|
11
11
|
* @throws Error When script execution throws exception or returns invalid response
|
|
12
12
|
*/
|
|
13
13
|
export declare function executeScript(cdp: CDPSender, script: string, options?: Omit<Protocol.Runtime.EvaluateRequest, 'expression'>): Promise<Protocol.Runtime.EvaluateResponse>;
|
|
14
|
+
/**
|
|
15
|
+
* Message of an exception, including thrown non-Error values (`throw "x"`).
|
|
16
|
+
*
|
|
17
|
+
* @param details - Exception details
|
|
18
|
+
* @returns Readable message
|
|
19
|
+
*/
|
|
20
|
+
export declare function describeException(details: Protocol.Runtime.ExceptionDetails): string;
|
|
14
21
|
/** Result of `bdg dom eval`: a JSON-safe value plus its JavaScript type. */
|
|
15
22
|
export interface EvalResult {
|
|
16
23
|
/** JSON value, or a readable description for values JSON cannot represent */
|
|
@@ -19,6 +26,8 @@ export interface EvalResult {
|
|
|
19
26
|
type: string;
|
|
20
27
|
/** Object subtype (`node`, `date`, `map`, `array`, ...) */
|
|
21
28
|
subtype?: string;
|
|
29
|
+
/** Set when the page replaced built-ins bdg's copy of the result uses, so the browser copied it */
|
|
30
|
+
warning?: string;
|
|
22
31
|
}
|
|
23
32
|
/**
|
|
24
33
|
* Page-side copy of an object as JSON, keeping what JSON would lose inside
|
|
@@ -28,9 +37,10 @@ export interface EvalResult {
|
|
|
28
37
|
* `12n`, functions `function name()`, and cycles `[Circular]` (an object
|
|
29
38
|
* shared by two properties is copied twice). Works for objects of iframes
|
|
30
39
|
* (other realms); lists and objects are cut after 1000 entries, and a
|
|
31
|
-
* throwing getter becomes `[Error: …]`.
|
|
40
|
+
* throwing getter becomes `[Error: …]`. It uses the page's built-ins
|
|
41
|
+
* ({@link COPY_BUILTINS}), so it only runs when the page left them alone.
|
|
32
42
|
*/
|
|
33
|
-
export declare const JSON_SAFE_COPY_FUNCTION = "function () {\n const MAX_ITEMS = 1000;\n const ancestors = new Set();\n const kind = (value) => Object.prototype.toString.call(value).slice(8, -1);\n const isNode = (value) => typeof value.nodeType === 'number' && typeof value.nodeName === 'string';\n const describeNode = (node) => {\n if (node.nodeType !== 1) return node.nodeName.toLowerCase();\n return node.tagName.toLowerCase() + (node.id ? '#' + node.id : '') +\n (node.classList && node.classList.length ? '.' + Array.from(node.classList).join('.') : '');\n };\n const items = (list, next) => {\n const result = [];\n let count = 0;\n for (const item of list) {\n if (count++ === MAX_ITEMS) { result.push('\u2026'); break; }\n result.push(next(item));\n }\n return result;\n };\n const leaf = (value) => {\n if (value === undefined) return null;\n if (typeof value === 'number') {\n if (Number.isNaN(value) || !Number.isFinite(value)) return String(value);\n return Object.is(value, -0) ? '-0' : value;\n }\n if (typeof value === 'bigint') return value + 'n';\n if (typeof value === 'symbol') return value.toString();\n if (typeof value === 'function') return 'function ' + (value.name || '(anonymous)') + '()';\n return value;\n };\n const copy = (value, depth) => {\n if (value === null || typeof value !== 'object') return leaf(value);\n if (ancestors.has(value)) return '[Circular]';\n if (depth > 20) return '[\u2026]';\n const next = (item) => copy(item, depth + 1);\n const type = kind(value);\n if (isNode(value)) return describeNode(value);\n if (value.window === value) return 'Window';\n if (type === 'Date') return isNaN(value) ? 'Invalid Date' : value.toISOString();\n if (type === 'RegExp') return String(value);\n if (type === 'Error' || value instanceof Error) return value.name + ': ' + value.message;\n ancestors.add(value);\n try {\n if (type === 'Map') return items(value, ([k, v]) => [next(k), next(v)]);\n if (type === 'Set' || type === 'NodeList' || type === 'HTMLCollection') return items(value, next);\n if (ArrayBuffer.isView(value) && type !== 'DataView') return items(value, next);\n if (Array.isArray(value)) return items(value, next);\n const result = {};\n const keys = Object.keys(value);\n for (const key of keys.slice(0, MAX_ITEMS)) {\n try { result[key] = next(value[key]); } catch (e) { result[key] = '[Error: ' + (e && e.message) + ']'; }\n }\n if (keys.length > MAX_ITEMS) result['\u2026'] = (keys.length - MAX_ITEMS) + ' more keys';\n return result;\n } finally {\n ancestors.delete(value);\n }\n };\n return copy(this, 0);\n}";
|
|
43
|
+
export declare const JSON_SAFE_COPY_FUNCTION = "function () {\n const MAX_ITEMS = 1000;\n const ancestors = new Set();\n const kind = (value) => Object.prototype.toString.call(value).slice(8, -1);\n const isNode = (value) => typeof value.nodeType === 'number' && typeof value.nodeName === 'string';\n const describeNode = (node) => {\n if (node.nodeType !== 1) return node.nodeName.toLowerCase();\n return node.tagName.toLowerCase() + (node.id ? '#' + node.id : '') +\n (node.classList && node.classList.length ? '.' + Array.from(node.classList).join('.') : '');\n };\n const items = (list, next) => {\n const result = [];\n if (typeof list.length === 'number') {\n for (let i = 0; i < list.length; i++) {\n if (i === MAX_ITEMS) { result.push('\u2026'); break; }\n result.push(next(list[i]));\n }\n return result;\n }\n let count = 0;\n for (const item of list) {\n if (count++ === MAX_ITEMS) { result.push('\u2026'); break; }\n result.push(next(item));\n }\n return result;\n };\n const leaf = (value) => {\n if (value === undefined) return null;\n if (typeof value === 'number') {\n if (Number.isNaN(value) || !Number.isFinite(value)) return String(value);\n return Object.is(value, -0) ? '-0' : value;\n }\n if (typeof value === 'bigint') return value + 'n';\n if (typeof value === 'symbol') return value.toString();\n if (typeof value === 'function') return 'function ' + (value.name || '(anonymous)') + '()';\n return value;\n };\n const copy = (value, depth) => {\n if (value === null || typeof value !== 'object') return leaf(value);\n if (ancestors.has(value)) return '[Circular]';\n if (depth > 20) return '[\u2026]';\n const next = (item) => copy(item, depth + 1);\n const type = kind(value);\n if (isNode(value)) return describeNode(value);\n if (value.window === value) return 'Window';\n if (type === 'Date') return isNaN(value) ? 'Invalid Date' : value.toISOString();\n if (type === 'RegExp') return String(value);\n if (type === 'Error' || value instanceof Error) return value.name + ': ' + value.message;\n ancestors.add(value);\n try {\n if (type === 'Map') return items(value, ([k, v]) => [next(k), next(v)]);\n if (type === 'Set' || type === 'NodeList' || type === 'HTMLCollection') return items(value, next);\n if (ArrayBuffer.isView(value) && type !== 'DataView') return items(value, next);\n if (Array.isArray(value)) return items(value, next);\n const result = {};\n const keys = Object.keys(value);\n for (const key of keys.slice(0, MAX_ITEMS)) {\n try { result[key] = next(value[key]); } catch (e) { result[key] = '[Error: ' + (e && e.message) + ']'; }\n }\n if (keys.length > MAX_ITEMS) result['\u2026'] = (keys.length - MAX_ITEMS) + ' more keys';\n return result;\n } finally {\n ancestors.delete(value);\n }\n };\n return copy(this, 0);\n}";
|
|
34
44
|
/** What a busy target is called in messages: the page, or an iframe (`--frame`) */
|
|
35
45
|
export type BusyScope = 'page' | 'frame';
|
|
36
46
|
/** When {@link withBusyPageRecovery} checks the target, and what it calls it */
|