browser-debugger-cli 0.9.0 → 0.10.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.
Files changed (133) hide show
  1. package/README.md +4 -1
  2. package/dist/commands/dom/a11y.js +2 -1
  3. package/dist/commands/dom/formInteraction.js +56 -25
  4. package/dist/commands/dom/helpers/keyAttributes.d.ts +20 -0
  5. package/dist/commands/dom/helpers/keyAttributes.js +54 -0
  6. package/dist/commands/dom/helpers/query.d.ts +1 -1
  7. package/dist/commands/dom/helpers/query.js +66 -19
  8. package/dist/commands/dom/helpers/runElementCommand.js +4 -3
  9. package/dist/commands/dom/helpers/screenshot.js +85 -12
  10. package/dist/commands/dom/index.d.ts +1 -0
  11. package/dist/commands/dom/index.js +8 -3
  12. package/dist/commands/dom/inspect.d.ts +15 -0
  13. package/dist/commands/dom/inspect.js +82 -0
  14. package/dist/commands/dom/layout.js +2 -2
  15. package/dist/commands/dom/listeners.js +2 -2
  16. package/dist/commands/dom/semanticUtils.d.ts +14 -1
  17. package/dist/commands/dom/semanticUtils.js +44 -3
  18. package/dist/commands/network/list.js +13 -2
  19. package/dist/commands/optionBehaviors.js +48 -6
  20. package/dist/commands/page.d.ts +1 -1
  21. package/dist/commands/page.js +62 -3
  22. package/dist/commands/shared/commonOptions.d.ts +4 -0
  23. package/dist/commands/shared/commonOptions.js +9 -0
  24. package/dist/commands/shared/optionTypes.d.ts +21 -0
  25. package/dist/commands/shared/startHelpers.d.ts +66 -0
  26. package/dist/commands/shared/startHelpers.js +91 -10
  27. package/dist/commands/shared/validation.d.ts +11 -0
  28. package/dist/commands/shared/validation.js +16 -0
  29. package/dist/daemon/launcher.d.ts +8 -1
  30. package/dist/daemon/launcher.js +3 -1
  31. package/dist/daemon/session/Session.d.ts +7 -0
  32. package/dist/daemon/session/Session.js +23 -1
  33. package/dist/daemon/session/commandRegistry.d.ts +14 -1
  34. package/dist/daemon/session/commandRegistry.js +65 -9
  35. package/dist/daemon/session/interactions.d.ts +18 -5
  36. package/dist/daemon/session/interactions.js +22 -12
  37. package/dist/daemon.js +3565 -329
  38. package/dist/errors/messages.d.ts +69 -0
  39. package/dist/errors/messages.js +102 -1
  40. package/dist/index.js +2416 -1320
  41. package/dist/ipc/client.d.ts +9 -0
  42. package/dist/ipc/client.js +13 -0
  43. package/dist/ipc/protocol/commands.d.ts +56 -1
  44. package/dist/ipc/protocol/commands.js +2 -0
  45. package/dist/ipc/protocol/domTypes.d.ts +35 -2
  46. package/dist/ipc/protocol/inspectTypes.d.ts +388 -0
  47. package/dist/ipc/protocol/inspectTypes.js +10 -0
  48. package/dist/runtime/dom/actionEffects.d.ts +94 -15
  49. package/dist/runtime/dom/actionEffects.js +173 -27
  50. package/dist/runtime/dom/actionEffectsScripts.d.ts +52 -14
  51. package/dist/runtime/dom/actionEffectsScripts.js +224 -32
  52. package/dist/runtime/dom/elementInfo.d.ts +26 -0
  53. package/dist/runtime/dom/elementInfo.js +65 -0
  54. package/dist/runtime/dom/eventListeners.js +14 -4
  55. package/dist/runtime/dom/formFillHelpers/fill.d.ts +3 -4
  56. package/dist/runtime/dom/formFillHelpers/fill.js +77 -28
  57. package/dist/runtime/dom/frameSelection.d.ts +11 -0
  58. package/dist/runtime/dom/frameSelection.js +20 -1
  59. package/dist/runtime/dom/frames.d.ts +38 -5
  60. package/dist/runtime/dom/frames.js +136 -21
  61. package/dist/runtime/dom/inspect.d.ts +28 -0
  62. package/dist/runtime/dom/inspect.js +557 -0
  63. package/dist/runtime/dom/inspectAllStyles.d.ts +62 -0
  64. package/dist/runtime/dom/inspectAllStyles.js +385 -0
  65. package/dist/runtime/dom/inspectCascade.d.ts +94 -0
  66. package/dist/runtime/dom/inspectCascade.js +371 -0
  67. package/dist/runtime/dom/inspectCascadeModel.d.ts +39 -0
  68. package/dist/runtime/dom/inspectCascadeModel.js +232 -0
  69. package/dist/runtime/dom/inspectHints.d.ts +62 -0
  70. package/dist/runtime/dom/inspectHints.js +305 -0
  71. package/dist/runtime/dom/inspectLayoutModel.d.ts +87 -0
  72. package/dist/runtime/dom/inspectLayoutModel.js +346 -0
  73. package/dist/runtime/dom/inspectModel.d.ts +74 -0
  74. package/dist/runtime/dom/inspectModel.js +184 -0
  75. package/dist/runtime/dom/inspectPaintModel.d.ts +157 -0
  76. package/dist/runtime/dom/inspectPaintModel.js +461 -0
  77. package/dist/runtime/dom/inspectRules.d.ts +37 -0
  78. package/dist/runtime/dom/inspectRules.js +101 -0
  79. package/dist/runtime/dom/inspectScripts.d.ts +132 -0
  80. package/dist/runtime/dom/inspectScripts.js +263 -0
  81. package/dist/runtime/dom/inspectTree.d.ts +40 -0
  82. package/dist/runtime/dom/inspectTree.js +134 -0
  83. package/dist/runtime/dom/inspectVariables.d.ts +33 -0
  84. package/dist/runtime/dom/inspectVariables.js +94 -0
  85. package/dist/runtime/dom/inspectWhyModel.d.ts +20 -0
  86. package/dist/runtime/dom/inspectWhyModel.js +134 -0
  87. package/dist/runtime/dom/layout.d.ts +5 -1
  88. package/dist/runtime/dom/layout.js +10 -3
  89. package/dist/runtime/dom/listenerPageScripts.d.ts +11 -5
  90. package/dist/runtime/dom/listenerPageScripts.js +95 -9
  91. package/dist/runtime/dom/listenerSummary.d.ts +4 -0
  92. package/dist/runtime/dom/listenerSummary.js +26 -9
  93. package/dist/runtime/dom/reactEventHelpers.d.ts +5 -0
  94. package/dist/runtime/dom/reactEventHelpers.js +12 -4
  95. package/dist/runtime/page/emulation.d.ts +20 -0
  96. package/dist/runtime/page/emulation.js +37 -0
  97. package/dist/telemetry/a11y.d.ts +10 -0
  98. package/dist/telemetry/a11y.js +78 -1
  99. package/dist/telemetry/console.d.ts +1 -0
  100. package/dist/telemetry/console.js +100 -5
  101. package/dist/telemetry/network.js +3 -1
  102. package/dist/types.d.ts +32 -0
  103. package/dist/ui/formatters/details.d.ts +8 -0
  104. package/dist/ui/formatters/details.js +59 -3
  105. package/dist/ui/formatters/dom.d.ts +2 -1
  106. package/dist/ui/formatters/dom.js +25 -9
  107. package/dist/ui/formatters/inspect.d.ts +39 -0
  108. package/dist/ui/formatters/inspect.js +596 -0
  109. package/dist/ui/formatters/keyAttributes.d.ts +19 -0
  110. package/dist/ui/formatters/keyAttributes.js +84 -0
  111. package/dist/ui/formatters/layout.js +2 -2
  112. package/dist/ui/formatters/networkHeaders.d.ts +13 -0
  113. package/dist/ui/formatters/networkHeaders.js +23 -3
  114. package/dist/ui/formatters/networkList.d.ts +29 -1
  115. package/dist/ui/formatters/networkList.js +86 -20
  116. package/dist/ui/formatters/status.js +1 -1
  117. package/dist/ui/formatting.d.ts +9 -0
  118. package/dist/ui/formatting.js +6 -3
  119. package/dist/ui/messages/commands.d.ts +123 -7
  120. package/dist/ui/messages/commands.js +181 -10
  121. package/dist/ui/messages/networkMessages.d.ts +14 -0
  122. package/dist/ui/messages/networkMessages.js +18 -0
  123. package/dist/ui/messages/session.d.ts +14 -0
  124. package/dist/ui/messages/session.js +20 -0
  125. package/dist/utils/async.d.ts +9 -0
  126. package/dist/utils/async.js +17 -0
  127. package/dist/utils/color.d.ts +84 -0
  128. package/dist/utils/color.js +376 -0
  129. package/dist/utils/cssValues.d.ts +109 -0
  130. package/dist/utils/cssValues.js +236 -0
  131. package/dist/utils/selectorFilters.d.ts +12 -0
  132. package/dist/utils/selectorFilters.js +29 -0
  133. package/package.json +1 -1
@@ -0,0 +1,101 @@
1
+ /**
2
+ * CDP side of `dom inspect`'s cascade: the stylesheet headers (where a rule
3
+ * comes from), the matched rules of an element with a time budget, and
4
+ * labels for a declaration's source.
5
+ */
6
+ import { createLogger } from '../../ui/logging/index.js';
7
+ import { raceTimeout } from '../../utils/async.js';
8
+ const log = createLogger('dom');
9
+ /** Stylesheet headers by id, per connection (from `CSS.styleSheetAdded`) */
10
+ const headersByConnection = new WeakMap();
11
+ /**
12
+ * Start collecting stylesheet headers. Call before `CSS.enable`, which
13
+ * reports every existing stylesheet; later ones arrive as they are added,
14
+ * removed ones (navigations) are dropped.
15
+ *
16
+ * @param cdp - CDP connection
17
+ */
18
+ export function trackStyleSheets(cdp) {
19
+ if (headersByConnection.has(cdp))
20
+ return;
21
+ const headers = new Map();
22
+ headersByConnection.set(cdp, headers);
23
+ cdp.on('CSS.styleSheetAdded', ({ header }) => {
24
+ headers.set(header.styleSheetId, header);
25
+ });
26
+ cdp.on('CSS.styleSheetRemoved', ({ styleSheetId }) => {
27
+ headers.delete(styleSheetId);
28
+ });
29
+ }
30
+ /**
31
+ * The rules that match an element, with its inline style and what its
32
+ * ancestors pass down, or why they are missing.
33
+ *
34
+ * @param cdp - CDP connection
35
+ * @param nodeId - Node id of the element
36
+ * @param budgetMs - Time allowed
37
+ * @returns Matched styles, `timeout` (longer than the budget) or `failed` (CDP error)
38
+ */
39
+ export async function matchedStyles(cdp, nodeId, budgetMs) {
40
+ const request = cdp
41
+ .send('CSS.getMatchedStylesForNode', { nodeId })
42
+ .then((response) => response)
43
+ .catch((error) => {
44
+ log.debug(`CSS.getMatchedStylesForNode failed: ${String(error)}`);
45
+ return 'failed';
46
+ });
47
+ return (await raceTimeout(request, budgetMs)) ?? 'timeout';
48
+ }
49
+ /**
50
+ * File name of a URL, without query or hash; the host for a site's root.
51
+ *
52
+ * @param url - Stylesheet (or page) URL
53
+ * @returns e.g. `bootstrap.min.css`, `localhost:8765`
54
+ */
55
+ function fileName(url) {
56
+ const path = url.split(/[?#]/)[0] ?? url;
57
+ const segments = path.split('/').filter(Boolean);
58
+ return segments.at(-1) ?? path;
59
+ }
60
+ /**
61
+ * Where a declaration comes from, for people: the selector and the file
62
+ * with its line (and column, for single-line minified files), or what kind
63
+ * of style it is.
64
+ *
65
+ * @param declaration - Declaration
66
+ * @param cdp - CDP connection whose stylesheet headers to use
67
+ * @returns e.g. `.btn-primary (bootstrap.min.css:5:52628)`, `style attribute`, `browser default`
68
+ */
69
+ export function sourceLabel(declaration, cdp) {
70
+ const { source } = declaration;
71
+ if (source.kind === 'inline')
72
+ return 'style attribute';
73
+ if (source.kind === 'attribute')
74
+ return 'HTML attribute';
75
+ if (source.origin === 'user-agent')
76
+ return `${source.selector ?? ''} (browser default)`.trim();
77
+ const header = source.styleSheetId
78
+ ? headersByConnection.get(cdp)?.get(source.styleSheetId)
79
+ : undefined;
80
+ return `${source.selector ?? ''} (${fileLabel(header, source.line, source.column)})`;
81
+ }
82
+ /**
83
+ * A stylesheet position as `file:line[:column]`.
84
+ *
85
+ * @param header - Stylesheet header (unknown: just the line)
86
+ * @param line - 0-based line within the stylesheet
87
+ * @param column - 0-based column
88
+ * @returns e.g. `app.css:12`, `bootstrap.min.css:5:52628`, `<style> in index.html:40`, `constructed stylesheet`
89
+ */
90
+ function fileLabel(header, line, column) {
91
+ if (!header)
92
+ return line === undefined ? 'stylesheet' : `stylesheet:${line + 1}`;
93
+ if (header.isConstructed)
94
+ return 'constructed stylesheet';
95
+ const file = fileName(header.sourceURL) || 'page';
96
+ const absoluteLine = (line ?? 0) + (header.isInline ? header.startLine : 0) + 1;
97
+ const where = header.isInline ? `<style> in ${file}` : file;
98
+ const minified = header.endLine - header.startLine < 10 && (column ?? 0) > 200;
99
+ return minified ? `${where}:${absoluteLine}:${(column ?? 0) + 1}` : `${where}:${absoluteLine}`;
100
+ }
101
+ //# sourceMappingURL=inspectRules.js.map
@@ -0,0 +1,132 @@
1
+ /**
2
+ * Page-side part of `bdg dom inspect`: one call on the element reads what
3
+ * computed styles do not say. Its text as a user sees it (`innerText`, form
4
+ * values, secrets masked), where it sits in its parent (distances to the
5
+ * parent's content edges and to its neighbouring siblings), the
6
+ * backgrounds behind it (for the contrast of its text), the computed (not
7
+ * resolved) width and height (`auto` vs a length, for hug/fill/fixed) and
8
+ * the child tree, all in one walk: children are never styled one CDP call at
9
+ * a time.
10
+ *
11
+ * Children are those of the flat tree: an open shadow root's children
12
+ * instead of the light DOM, slots replaced by what is assigned to them, and
13
+ * `display: contents` wrappers by their children.
14
+ */
15
+ /** Elements walked for the child tree at most (the rest are counted) */
16
+ export declare const TREE_NODE_CAP = 400;
17
+ /** A child as the page-side walk reports it */
18
+ export interface RawTreeNode {
19
+ /** `tag#id` or `tag.firstClass` */
20
+ label: string;
21
+ /** Position of the border box relative to the parent's */
22
+ x: number;
23
+ y: number;
24
+ w: number;
25
+ h: number;
26
+ display: string;
27
+ /** Text of an element without block-level children */
28
+ text?: string;
29
+ children?: RawTreeNode[];
30
+ /** Rendered element children (at the depth limit) */
31
+ childCount?: number;
32
+ /** Element children that are not rendered */
33
+ hidden?: number;
34
+ }
35
+ /** A background behind the element (the element's own first) */
36
+ export interface RawBackground {
37
+ /** Computed `background-color` */
38
+ color: string;
39
+ /** Has a background image or gradient */
40
+ image: boolean;
41
+ }
42
+ /** What {@link INSPECT_PAGE_JS} returns */
43
+ export interface RawInspect {
44
+ tag: string;
45
+ id: string;
46
+ classes: string[];
47
+ context: string;
48
+ /** Text (innerText) or form value, whitespace collapsed (at most 200 characters) */
49
+ content: string;
50
+ /** Placeholder of an empty text field */
51
+ placeholder?: string;
52
+ /** Computed color of its `::placeholder` */
53
+ placeholderColor?: string;
54
+ /** Font style and weight of the placeholder (they decide whether it is large text) */
55
+ placeholderFont?: {
56
+ style: string;
57
+ weight: string;
58
+ };
59
+ /** Has text to describe: own text, only inline content with text, or a form control */
60
+ textual: boolean;
61
+ formControl: boolean;
62
+ /** Replaced element (img, svg and its shapes, video, canvas, iframe, embed, object, form controls): sized by its content */
63
+ replaced?: boolean;
64
+ /** An SVG element (SVG properties are not noise for it) */
65
+ svg?: boolean;
66
+ /** A text node is a direct child (the rendered font is read from the element itself) */
67
+ ownText: boolean;
68
+ /** Computed (not resolved) width and height: `auto`, `200px`, `50%`, … */
69
+ typed: {
70
+ width: string;
71
+ height: string;
72
+ };
73
+ /** Short description of the parent that lays it out */
74
+ parent?: string;
75
+ /** Distances from that parent's content box edges */
76
+ inParent?: {
77
+ left: number;
78
+ top: number;
79
+ right: number;
80
+ bottom: number;
81
+ };
82
+ /** Gaps to the previous and next rendered in-flow siblings, by the side they are on */
83
+ siblings?: {
84
+ top?: number;
85
+ bottom?: number;
86
+ left?: number;
87
+ right?: number;
88
+ };
89
+ /** Content size when it overflows the box */
90
+ scroll?: {
91
+ w: number;
92
+ h: number;
93
+ clientW: number;
94
+ clientH: number;
95
+ };
96
+ /** Backgrounds from the element up to the root */
97
+ backgrounds: RawBackground[];
98
+ /** The page canvas behind everything is dark (color-scheme) */
99
+ canvasDark: boolean;
100
+ /** Opacity of the element times that of its ancestors */
101
+ opacity?: number;
102
+ tree?: RawTreeNode[];
103
+ hiddenChildren: number;
104
+ /** Children the walk did not reach ({@link TREE_NODE_CAP}) */
105
+ treeSkipped: number;
106
+ /**
107
+ * `--props` values from `getComputedStyle` (shorthands and custom
108
+ * properties too); a shorthand whose sides differ (no single value) is
109
+ * given per side, e.g. `top 1px solid … / right 0px none …`
110
+ */
111
+ props?: Record<string, string>;
112
+ /** `--props` names the browser does not know as CSS properties */
113
+ unknownProps?: string[];
114
+ /** Properties of running CSS transitions, and names of running animations */
115
+ animating: string[];
116
+ }
117
+ /**
118
+ * Page-side function run on the element (`this`), see {@link RawInspect}.
119
+ * Arguments: tree depth, and the property names asked for with `--props`
120
+ * (or null).
121
+ */
122
+ export declare const INSPECT_PAGE_JS = "function (depth, props) {\n const el = this;\n const view = el.ownerDocument.defaultView;\n const tree = ((view) => {\n const style = (n, pseudo) => view.getComputedStyle(n, pseudo);\n const skipped = /^(script|style|template|link|meta|noscript|title|head|base)$/;\n const flatParent = (n) => n.assignedSlot || n.parentElement || (n.parentNode && n.parentNode.host) || null;\n const layoutParent = (n) => {\n let p = flatParent(n);\n while (p && style(p).display === 'contents') p = flatParent(p);\n return p;\n };\n const children = (n) => {\n const out = [];\n const add = (c) => {\n if (skipped.test(c.localName)) return;\n if (c.localName === 'slot') {\n const assigned = c.assignedElements({ flatten: true });\n (assigned.length ? assigned : Array.from(c.children)).forEach(add);\n } else if (style(c).display === 'contents') {\n Array.from((c.shadowRoot || c).children).forEach(add);\n } else {\n out.push(c);\n }\n };\n Array.from((n.shadowRoot || n).children).forEach(add);\n return out;\n };\n const rendered = (n) => n.getClientRects().length > 0 &&\n (!n.checkVisibility || n.checkVisibility({ visibilityProperty: true }));\n const container = (n) => children(n).some((c) => rendered(c) && !/^inline/.test(style(c).display));\n return { style, flatParent, layoutParent, children, rendered, container };\n})(view);\n const textOf = (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 clean = (text) => String(text || '').replace(/\\s+/g, ' ').trim().slice(0, 200);\n if (el.localName === 'input' && el.type === 'hidden') return '';\n if (el.localName === 'input' || el.localName === 'textarea') {\n return el.value && isSensitive(el) ? '\u2022\u2022\u2022\u2022' : clean(el.value);\n }\n if (el.localName === 'select') {\n const chosen = clean(Array.from(el.selectedOptions || [], (o) => o.label).join(', '));\n return chosen && isSensitive(el) ? '\u2022\u2022\u2022\u2022' : chosen;\n }\n const outsideControls = () => {\n const parts = [];\n const walker = el.ownerDocument.createTreeWalker(el, NodeFilter.SHOW_TEXT);\n for (let n = walker.nextNode(); n && parts.length < 400; n = walker.nextNode()) {\n const holder = n.parentElement;\n if (!holder || holder.closest('select, textarea, script, style, template, noscript')) continue;\n if (holder.checkVisibility && !holder.checkVisibility({ visibilityProperty: true })) continue;\n parts.push(n.data);\n }\n return parts.join(' ');\n };\n const holdsControls = Boolean(el.querySelector && el.querySelector('select, textarea'));\n const raw = holdsControls ? outsideControls() : typeof el.innerText === 'string' ? el.innerText : el.textContent;\n const text = clean(raw);\n const view = el.ownerDocument.defaultView;\n const security = view ? view.getComputedStyle(el).getPropertyValue('-webkit-text-security') : '';\n return text && security && security !== 'none' ? '\u2022\u2022\u2022\u2022' : text;\n};\n const describe = (node) => node.tagName.toLowerCase() +\n (node.id ? '#' + node.id : '') +\n (node.classList && node.classList.length ? '.' + Array.from(node.classList).slice(0, 2).join('.') : '');\n const s = tree.style(el);\n const formControl = /^(input|textarea|select|button)$/.test(el.localName);\n const content = textOf(el);\n const parent = tree.layoutParent(el);\n const typedMap = el.computedStyleMap ? el.computedStyleMap() : null;\n const typed = (name) => { try { return typedMap ? String(typedMap.get(name)) : ''; } catch (e) { return ''; } };\n const result = {\n tag: el.localName,\n id: el.id || '',\n classes: el.classList ? Array.from(el.classList) : [],\n context: ((el) => {\n const describe = (node) => node.tagName.toLowerCase() + (node.id ? '#' + node.id : '');\n const parts = [];\n for (let doc = el.ownerDocument; doc && doc.defaultView && doc.defaultView.frameElement; ) {\n const frame = doc.defaultView.frameElement;\n parts.unshift(describe(frame));\n doc = frame.ownerDocument;\n }\n const root = el.getRootNode();\n if (root.host) parts.push('shadow root of <' + describe(root.host) + '>');\n return parts.join(' > ');\n})(el),\n content: content,\n textual: formControl || (content !== '' && !tree.container(el)),\n formControl: formControl,\n replaced: el instanceof SVGElement || /^(img|video|canvas|iframe|embed|object|input|textarea|select|button|meter|progress)$/.test(el.localName),\n svg: el.namespaceURI === 'http://www.w3.org/2000/svg',\n ownText: Array.from(el.childNodes).some((n) => n.nodeType === 3 && n.data.trim() !== ''),\n typed: { width: typed('width'), height: typed('height') },\n parent: parent ? describe(parent) : undefined,\n animating: el.getAnimations ? [...new Set(el.getAnimations().filter((a) => a.playState === 'running').map((a) => a.transitionProperty || a.animationName || 'animation'))] : []\n };\n if (/^(input|textarea)$/.test(el.localName) && el.placeholder && !el.value) {\n result.placeholder = el.placeholder.replace(/\\s+/g, ' ').trim();\n const placeholderStyle = tree.style(el, '::placeholder');\n result.placeholderColor = placeholderStyle.color;\n result.placeholderFont = { style: placeholderStyle.fontStyle, weight: placeholderStyle.fontWeight };\n }\n if (el.clientWidth > 0 && (el.scrollWidth > el.clientWidth + 1 || el.scrollHeight > el.clientHeight + 1)) {\n result.scroll = { w: el.scrollWidth, h: el.scrollHeight, clientW: el.clientWidth, clientH: el.clientHeight };\n }\n if (props) {\n const known = (name) => name.startsWith('--') || CSS.supports(name, 'inherit');\n const valueOf = (name) => {\n const value = s.getPropertyValue(name).trim();\n if (value !== '' || name.startsWith('--')) return value;\n const sides = ['top', 'right', 'bottom', 'left'].map((side) => name + '-' + side).filter(known);\n return sides.map((side) => side.slice(name.length + 1) + ' ' + s.getPropertyValue(side).trim()).join(' / ');\n };\n result.props = Object.fromEntries(props.filter(known).map((name) => [name, valueOf(name)]));\n result.unknownProps = props.filter((name) => !known(name));\n }\n return Object.assign(result,\n ((el, tree) => {\n const parent = tree.layoutParent(el);\n if (!parent || tree.style(el).position === 'fixed' || !tree.rendered(el)) return {};\n const r = el.getBoundingClientRect();\n const p = parent.getBoundingClientRect();\n const ps = tree.style(parent);\n const edge = (side) => parseFloat(ps['border-' + side + '-width']) + parseFloat(ps['padding-' + side]);\n const inParent = {\n left: r.left - (p.left + edge('left')),\n top: r.top - (p.top + edge('top')),\n right: (p.right - edge('right')) - r.right,\n bottom: (p.bottom - edge('bottom')) - r.bottom\n };\n const inFlow = (n) => n !== el && tree.rendered(n) && !/^(absolute|fixed)$/.test(tree.style(n).position);\n const siblings = tree.children(parent);\n const at = siblings.indexOf(el);\n const before = siblings.slice(0, Math.max(at, 0)).reverse().find(inFlow);\n const after = at < 0 ? undefined : siblings.slice(at + 1).find(inFlow);\n const gaps = {};\n for (const sibling of [before, after]) {\n if (!sibling) continue;\n const s = sibling.getBoundingClientRect();\n if (s.bottom <= r.top + 0.5) gaps.top = r.top - s.bottom;\n else if (s.top >= r.bottom - 0.5) gaps.bottom = s.top - r.bottom;\n else if (s.right <= r.left + 0.5) gaps.left = r.left - s.right;\n else if (s.left >= r.right - 0.5) gaps.right = s.left - r.right;\n }\n return { inParent: inParent, siblings: gaps };\n})(el, tree),\n ((el, tree, view) => {\n const backgrounds = [];\n let opacity = 1;\n for (let n = el; n && backgrounds.length < 60; n = tree.flatParent(n)) {\n const s = tree.style(n);\n backgrounds.push({ color: s.backgroundColor, image: s.backgroundImage !== 'none' });\n opacity *= Number(s.opacity) || 0;\n }\n const doc = el.ownerDocument;\n const meta = doc.querySelector('meta[name=\"color-scheme\"]');\n const rootScheme = tree.style(doc.documentElement).colorScheme;\n const scheme = rootScheme && rootScheme !== 'normal' ? rootScheme : (meta && meta.content) || '';\n const prefersDark = view.matchMedia('(prefers-color-scheme: dark)').matches;\n const canvasDark = /dark/.test(scheme) && (!/light/.test(scheme) || prefersDark);\n return { backgrounds: backgrounds, canvasDark: canvasDark, opacity: opacity };\n})(el, tree, view),\n ((el, tree, textOf, depth) => {\n let budget = 400;\n let skipped = 0;\n const labelOf = (n) => n.localName + (n.id ? '#' + n.id : n.classList && n.classList.length ? '.' + n.classList[0] : '');\n const walk = (kids, level, origin) => {\n const shown = kids.filter(tree.rendered);\n const nodes = [];\n for (const k of shown) {\n if (budget <= 0) { skipped++; continue; }\n budget--;\n nodes.push(nodeOf(k, level, origin));\n }\n return { nodes: nodes, hidden: kids.length - shown.length };\n };\n const nodeOf = (n, level, origin) => {\n const r = n.getBoundingClientRect();\n const node = { label: labelOf(n), x: r.left - origin.left, y: r.top - origin.top, w: r.width, h: r.height, display: tree.style(n).display };\n const kids = tree.children(n);\n if (!tree.container(n)) node.text = textOf(n).slice(0, 60);\n if (level >= depth) {\n node.childCount = kids.filter(tree.rendered).length;\n return node;\n }\n const sub = walk(kids, level + 1, r);\n node.children = sub.nodes;\n node.hidden = sub.hidden;\n return node;\n };\n if (depth <= 0) return { hiddenChildren: 0, treeSkipped: 0 };\n const top = walk(tree.children(el), 1, el.getBoundingClientRect());\n return { tree: top.nodes, hiddenChildren: top.hidden, treeSkipped: skipped };\n})(el, tree, textOf, depth));\n}";
123
+ /**
124
+ * Page-side function run on the element (`this`) that returns another
125
+ * element whose styles CDP reads: `"parent"`, the parent that lays it out
126
+ * (`display: contents` skipped, through shadow roots and slots), or
127
+ * `"textHolder"`, for an element whose text is in a descendant
128
+ * (`<button><span>Buy</span></button>`), the first element holding text,
129
+ * whose rendered font is the one shown. Null when there is none.
130
+ */
131
+ export declare const RELATED_NODE_JS = "function (which) {\n const tree = ((view) => {\n const style = (n, pseudo) => view.getComputedStyle(n, pseudo);\n const skipped = /^(script|style|template|link|meta|noscript|title|head|base)$/;\n const flatParent = (n) => n.assignedSlot || n.parentElement || (n.parentNode && n.parentNode.host) || null;\n const layoutParent = (n) => {\n let p = flatParent(n);\n while (p && style(p).display === 'contents') p = flatParent(p);\n return p;\n };\n const children = (n) => {\n const out = [];\n const add = (c) => {\n if (skipped.test(c.localName)) return;\n if (c.localName === 'slot') {\n const assigned = c.assignedElements({ flatten: true });\n (assigned.length ? assigned : Array.from(c.children)).forEach(add);\n } else if (style(c).display === 'contents') {\n Array.from((c.shadowRoot || c).children).forEach(add);\n } else {\n out.push(c);\n }\n };\n Array.from((n.shadowRoot || n).children).forEach(add);\n return out;\n };\n const rendered = (n) => n.getClientRects().length > 0 &&\n (!n.checkVisibility || n.checkVisibility({ visibilityProperty: true }));\n const container = (n) => children(n).some((c) => rendered(c) && !/^inline/.test(style(c).display));\n return { style, flatParent, layoutParent, children, rendered, container };\n})(this.ownerDocument.defaultView);\n if (which === 'parent') return tree.layoutParent(this);\n const walker = this.ownerDocument.createTreeWalker(this, NodeFilter.SHOW_TEXT);\n while (walker.nextNode()) {\n if (walker.currentNode.data.trim() !== '') return walker.currentNode.parentElement;\n }\n return null;\n}";
132
+ //# sourceMappingURL=inspectScripts.d.ts.map
@@ -0,0 +1,263 @@
1
+ /**
2
+ * Page-side part of `bdg dom inspect`: one call on the element reads what
3
+ * computed styles do not say. Its text as a user sees it (`innerText`, form
4
+ * values, secrets masked), where it sits in its parent (distances to the
5
+ * parent's content edges and to its neighbouring siblings), the
6
+ * backgrounds behind it (for the contrast of its text), the computed (not
7
+ * resolved) width and height (`auto` vs a length, for hug/fill/fixed) and
8
+ * the child tree, all in one walk: children are never styled one CDP call at
9
+ * a time.
10
+ *
11
+ * Children are those of the flat tree: an open shadow root's children
12
+ * instead of the light DOM, slots replaced by what is assigned to them, and
13
+ * `display: contents` wrappers by their children.
14
+ */
15
+ import { ELEMENT_CONTEXT_JS, ELEMENT_DESCRIPTION_JS, MASKED_VALUE, SENSITIVE_FIELD_JS, } from './elementInfo.js';
16
+ /** Elements walked for the child tree at most (the rest are counted) */
17
+ export const TREE_NODE_CAP = 400;
18
+ /**
19
+ * Page-side flat-tree helpers: the element's computed style, its flat-tree
20
+ * parent, the parent that lays it out (skipping `display: contents`), its
21
+ * element children in the flat tree and whether an element is rendered.
22
+ */
23
+ const FLAT_TREE_JS = `(view) => {
24
+ const style = (n, pseudo) => view.getComputedStyle(n, pseudo);
25
+ const skipped = /^(script|style|template|link|meta|noscript|title|head|base)$/;
26
+ const flatParent = (n) => n.assignedSlot || n.parentElement || (n.parentNode && n.parentNode.host) || null;
27
+ const layoutParent = (n) => {
28
+ let p = flatParent(n);
29
+ while (p && style(p).display === 'contents') p = flatParent(p);
30
+ return p;
31
+ };
32
+ const children = (n) => {
33
+ const out = [];
34
+ const add = (c) => {
35
+ if (skipped.test(c.localName)) return;
36
+ if (c.localName === 'slot') {
37
+ const assigned = c.assignedElements({ flatten: true });
38
+ (assigned.length ? assigned : Array.from(c.children)).forEach(add);
39
+ } else if (style(c).display === 'contents') {
40
+ Array.from((c.shadowRoot || c).children).forEach(add);
41
+ } else {
42
+ out.push(c);
43
+ }
44
+ };
45
+ Array.from((n.shadowRoot || n).children).forEach(add);
46
+ return out;
47
+ };
48
+ const rendered = (n) => n.getClientRects().length > 0 &&
49
+ (!n.checkVisibility || n.checkVisibility({ visibilityProperty: true }));
50
+ const container = (n) => children(n).some((c) => rendered(c) && !/^inline/.test(style(c).display));
51
+ return { style, flatParent, layoutParent, children, rendered, container };
52
+ }`;
53
+ /**
54
+ * Page-side text of an element: a form control's value or a select's chosen
55
+ * options (masked for secrets, {@link SENSITIVE_FIELD_JS}), else its
56
+ * `innerText` (`textContent` for SVG), masked when CSS hides it as a secret
57
+ * (`-webkit-text-security`), whitespace collapsed. A container holding a
58
+ * select or textarea gets its visible text without theirs, so a form's text
59
+ * never carries a chosen option or typed text.
60
+ */
61
+ const TEXT_JS = `(el) => {
62
+ const isSensitive = ${SENSITIVE_FIELD_JS};
63
+ const clean = (text) => String(text || '').replace(/\\s+/g, ' ').trim().slice(0, 200);
64
+ if (el.localName === 'input' && el.type === 'hidden') return '';
65
+ if (el.localName === 'input' || el.localName === 'textarea') {
66
+ return el.value && isSensitive(el) ? '${MASKED_VALUE}' : clean(el.value);
67
+ }
68
+ if (el.localName === 'select') {
69
+ const chosen = clean(Array.from(el.selectedOptions || [], (o) => o.label).join(', '));
70
+ return chosen && isSensitive(el) ? '${MASKED_VALUE}' : chosen;
71
+ }
72
+ const outsideControls = () => {
73
+ const parts = [];
74
+ const walker = el.ownerDocument.createTreeWalker(el, NodeFilter.SHOW_TEXT);
75
+ for (let n = walker.nextNode(); n && parts.length < 400; n = walker.nextNode()) {
76
+ const holder = n.parentElement;
77
+ if (!holder || holder.closest('select, textarea, script, style, template, noscript')) continue;
78
+ if (holder.checkVisibility && !holder.checkVisibility({ visibilityProperty: true })) continue;
79
+ parts.push(n.data);
80
+ }
81
+ return parts.join(' ');
82
+ };
83
+ const holdsControls = Boolean(el.querySelector && el.querySelector('select, textarea'));
84
+ const raw = holdsControls ? outsideControls() : typeof el.innerText === 'string' ? el.innerText : el.textContent;
85
+ const text = clean(raw);
86
+ const view = el.ownerDocument.defaultView;
87
+ const security = view ? view.getComputedStyle(el).getPropertyValue('-webkit-text-security') : '';
88
+ return text && security && security !== 'none' ? '${MASKED_VALUE}' : text;
89
+ }`;
90
+ /**
91
+ * Page-side position of the element in its layout parent: distances from
92
+ * the parent's content box edges and gaps to the previous and next rendered
93
+ * in-flow siblings (not absolutely positioned or fixed), named by the side
94
+ * the sibling is on. Not for fixed elements, whose box is not placed by
95
+ * the parent.
96
+ */
97
+ const PLACEMENT_JS = `(el, tree) => {
98
+ const parent = tree.layoutParent(el);
99
+ if (!parent || tree.style(el).position === 'fixed' || !tree.rendered(el)) return {};
100
+ const r = el.getBoundingClientRect();
101
+ const p = parent.getBoundingClientRect();
102
+ const ps = tree.style(parent);
103
+ const edge = (side) => parseFloat(ps['border-' + side + '-width']) + parseFloat(ps['padding-' + side]);
104
+ const inParent = {
105
+ left: r.left - (p.left + edge('left')),
106
+ top: r.top - (p.top + edge('top')),
107
+ right: (p.right - edge('right')) - r.right,
108
+ bottom: (p.bottom - edge('bottom')) - r.bottom
109
+ };
110
+ const inFlow = (n) => n !== el && tree.rendered(n) && !/^(absolute|fixed)$/.test(tree.style(n).position);
111
+ const siblings = tree.children(parent);
112
+ const at = siblings.indexOf(el);
113
+ const before = siblings.slice(0, Math.max(at, 0)).reverse().find(inFlow);
114
+ const after = at < 0 ? undefined : siblings.slice(at + 1).find(inFlow);
115
+ const gaps = {};
116
+ for (const sibling of [before, after]) {
117
+ if (!sibling) continue;
118
+ const s = sibling.getBoundingClientRect();
119
+ if (s.bottom <= r.top + 0.5) gaps.top = r.top - s.bottom;
120
+ else if (s.top >= r.bottom - 0.5) gaps.bottom = s.top - r.bottom;
121
+ else if (s.right <= r.left + 0.5) gaps.left = r.left - s.right;
122
+ else if (s.left >= r.right - 0.5) gaps.right = s.left - r.right;
123
+ }
124
+ return { inParent: inParent, siblings: gaps };
125
+ }`;
126
+ /**
127
+ * Page-side backgrounds behind the element (its own first, then its flat-tree
128
+ * ancestors up to the root), the product of its and their opacity, and
129
+ * whether the page canvas is dark: the root's
130
+ * `color-scheme` (or the `color-scheme` meta tag) allows dark and either
131
+ * only dark or the page prefers dark.
132
+ */
133
+ const BACKGROUNDS_JS = `(el, tree, view) => {
134
+ const backgrounds = [];
135
+ let opacity = 1;
136
+ for (let n = el; n && backgrounds.length < 60; n = tree.flatParent(n)) {
137
+ const s = tree.style(n);
138
+ backgrounds.push({ color: s.backgroundColor, image: s.backgroundImage !== 'none' });
139
+ opacity *= Number(s.opacity) || 0;
140
+ }
141
+ const doc = el.ownerDocument;
142
+ const meta = doc.querySelector('meta[name="color-scheme"]');
143
+ const rootScheme = tree.style(doc.documentElement).colorScheme;
144
+ const scheme = rootScheme && rootScheme !== 'normal' ? rootScheme : (meta && meta.content) || '';
145
+ const prefersDark = view.matchMedia('(prefers-color-scheme: dark)').matches;
146
+ const canvasDark = /dark/.test(scheme) && (!/light/.test(scheme) || prefersDark);
147
+ return { backgrounds: backgrounds, canvasDark: canvasDark, opacity: opacity };
148
+ }`;
149
+ /**
150
+ * Page-side child tree to `depth` levels: per element its label, size,
151
+ * display, position relative to its parent's border box (like Figma's x/y
152
+ * in a frame), text (only for elements without block-level children), and its
153
+ * rendered children (or their count at the depth limit) with a count of the
154
+ * hidden ones. Stops after {@link TREE_NODE_CAP} elements and counts the
155
+ * children it did not reach.
156
+ */
157
+ const TREE_JS = `(el, tree, textOf, depth) => {
158
+ let budget = ${TREE_NODE_CAP};
159
+ let skipped = 0;
160
+ const labelOf = (n) => n.localName + (n.id ? '#' + n.id : n.classList && n.classList.length ? '.' + n.classList[0] : '');
161
+ const walk = (kids, level, origin) => {
162
+ const shown = kids.filter(tree.rendered);
163
+ const nodes = [];
164
+ for (const k of shown) {
165
+ if (budget <= 0) { skipped++; continue; }
166
+ budget--;
167
+ nodes.push(nodeOf(k, level, origin));
168
+ }
169
+ return { nodes: nodes, hidden: kids.length - shown.length };
170
+ };
171
+ const nodeOf = (n, level, origin) => {
172
+ const r = n.getBoundingClientRect();
173
+ const node = { label: labelOf(n), x: r.left - origin.left, y: r.top - origin.top, w: r.width, h: r.height, display: tree.style(n).display };
174
+ const kids = tree.children(n);
175
+ if (!tree.container(n)) node.text = textOf(n).slice(0, 60);
176
+ if (level >= depth) {
177
+ node.childCount = kids.filter(tree.rendered).length;
178
+ return node;
179
+ }
180
+ const sub = walk(kids, level + 1, r);
181
+ node.children = sub.nodes;
182
+ node.hidden = sub.hidden;
183
+ return node;
184
+ };
185
+ if (depth <= 0) return { hiddenChildren: 0, treeSkipped: 0 };
186
+ const top = walk(tree.children(el), 1, el.getBoundingClientRect());
187
+ return { tree: top.nodes, hiddenChildren: top.hidden, treeSkipped: skipped };
188
+ }`;
189
+ /**
190
+ * Page-side function run on the element (`this`), see {@link RawInspect}.
191
+ * Arguments: tree depth, and the property names asked for with `--props`
192
+ * (or null).
193
+ */
194
+ export const INSPECT_PAGE_JS = `function (depth, props) {
195
+ const el = this;
196
+ const view = el.ownerDocument.defaultView;
197
+ const tree = (${FLAT_TREE_JS})(view);
198
+ const textOf = ${TEXT_JS};
199
+ const describe = ${ELEMENT_DESCRIPTION_JS};
200
+ const s = tree.style(el);
201
+ const formControl = /^(input|textarea|select|button)$/.test(el.localName);
202
+ const content = textOf(el);
203
+ const parent = tree.layoutParent(el);
204
+ const typedMap = el.computedStyleMap ? el.computedStyleMap() : null;
205
+ const typed = (name) => { try { return typedMap ? String(typedMap.get(name)) : ''; } catch (e) { return ''; } };
206
+ const result = {
207
+ tag: el.localName,
208
+ id: el.id || '',
209
+ classes: el.classList ? Array.from(el.classList) : [],
210
+ context: (${ELEMENT_CONTEXT_JS})(el),
211
+ content: content,
212
+ textual: formControl || (content !== '' && !tree.container(el)),
213
+ formControl: formControl,
214
+ replaced: el instanceof SVGElement || /^(img|video|canvas|iframe|embed|object|input|textarea|select|button|meter|progress)$/.test(el.localName),
215
+ svg: el.namespaceURI === 'http://www.w3.org/2000/svg',
216
+ ownText: Array.from(el.childNodes).some((n) => n.nodeType === 3 && n.data.trim() !== ''),
217
+ typed: { width: typed('width'), height: typed('height') },
218
+ parent: parent ? describe(parent) : undefined,
219
+ animating: el.getAnimations ? [...new Set(el.getAnimations().filter((a) => a.playState === 'running').map((a) => a.transitionProperty || a.animationName || 'animation'))] : []
220
+ };
221
+ if (/^(input|textarea)$/.test(el.localName) && el.placeholder && !el.value) {
222
+ result.placeholder = el.placeholder.replace(/\\s+/g, ' ').trim();
223
+ const placeholderStyle = tree.style(el, '::placeholder');
224
+ result.placeholderColor = placeholderStyle.color;
225
+ result.placeholderFont = { style: placeholderStyle.fontStyle, weight: placeholderStyle.fontWeight };
226
+ }
227
+ if (el.clientWidth > 0 && (el.scrollWidth > el.clientWidth + 1 || el.scrollHeight > el.clientHeight + 1)) {
228
+ result.scroll = { w: el.scrollWidth, h: el.scrollHeight, clientW: el.clientWidth, clientH: el.clientHeight };
229
+ }
230
+ if (props) {
231
+ const known = (name) => name.startsWith('--') || CSS.supports(name, 'inherit');
232
+ const valueOf = (name) => {
233
+ const value = s.getPropertyValue(name).trim();
234
+ if (value !== '' || name.startsWith('--')) return value;
235
+ const sides = ['top', 'right', 'bottom', 'left'].map((side) => name + '-' + side).filter(known);
236
+ return sides.map((side) => side.slice(name.length + 1) + ' ' + s.getPropertyValue(side).trim()).join(' / ');
237
+ };
238
+ result.props = Object.fromEntries(props.filter(known).map((name) => [name, valueOf(name)]));
239
+ result.unknownProps = props.filter((name) => !known(name));
240
+ }
241
+ return Object.assign(result,
242
+ (${PLACEMENT_JS})(el, tree),
243
+ (${BACKGROUNDS_JS})(el, tree, view),
244
+ (${TREE_JS})(el, tree, textOf, depth));
245
+ }`;
246
+ /**
247
+ * Page-side function run on the element (`this`) that returns another
248
+ * element whose styles CDP reads: `"parent"`, the parent that lays it out
249
+ * (`display: contents` skipped, through shadow roots and slots), or
250
+ * `"textHolder"`, for an element whose text is in a descendant
251
+ * (`<button><span>Buy</span></button>`), the first element holding text,
252
+ * whose rendered font is the one shown. Null when there is none.
253
+ */
254
+ export const RELATED_NODE_JS = `function (which) {
255
+ const tree = (${FLAT_TREE_JS})(this.ownerDocument.defaultView);
256
+ if (which === 'parent') return tree.layoutParent(this);
257
+ const walker = this.ownerDocument.createTreeWalker(this, NodeFilter.SHOW_TEXT);
258
+ while (walker.nextNode()) {
259
+ if (walker.currentNode.data.trim() !== '') return walker.currentNode.parentElement;
260
+ }
261
+ return null;
262
+ }`;
263
+ //# sourceMappingURL=inspectScripts.js.map
@@ -0,0 +1,40 @@
1
+ /**
2
+ * The child tree of `bdg dom inspect`: the page-side walk's children
3
+ * ({@link RawTreeNode}) with sizes rounded, consecutive identical siblings
4
+ * (same label, size and layout) grouped into one row with a count
5
+ * (`li.card ×33 266x107`), and cut to a number of rows in document order.
6
+ */
7
+ import type { InspectTreeNode } from '../../ipc/protocol/inspectTypes.js';
8
+ import type { RawTreeNode } from './inspectScripts.js';
9
+ /** Default depth of the tree */
10
+ export declare const DEFAULT_TREE_DEPTH = 2;
11
+ /** Default number of rows */
12
+ export declare const DEFAULT_TREE_LIMIT = 20;
13
+ /**
14
+ * Cut a row's text on a whole character.
15
+ *
16
+ * @param text - Text
17
+ * @returns At most {@link ROW_TEXT_LENGTH} characters, with `…` when cut
18
+ */
19
+ export declare function rowText(text: string): string;
20
+ /**
21
+ * Group runs of identical siblings into one row with a count. A group keeps
22
+ * its text only when every member has the same, and lists no children.
23
+ *
24
+ * @param nodes - Sibling rows
25
+ * @returns Rows with runs grouped
26
+ */
27
+ export declare function groupSiblings(nodes: readonly InspectTreeNode[]): InspectTreeNode[];
28
+ /**
29
+ * The child tree from the page-side walk: grouped, then cut to `limit` rows.
30
+ *
31
+ * @param raw - Children from the walk
32
+ * @param limit - Rows shown at most
33
+ * @param skipped - Children the walk did not reach (counted as left out)
34
+ * @returns Rows and the number of rows left out
35
+ */
36
+ export declare function buildTree(raw: readonly RawTreeNode[], limit: number, skipped?: number): {
37
+ children: InspectTreeNode[];
38
+ moreRows: number;
39
+ };
40
+ //# sourceMappingURL=inspectTree.d.ts.map