browser-debugger-cli 0.12.0 → 0.14.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 +100 -186
- package/README.md +5 -4
- 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 +67 -13
- package/dist/commands/dom/DomElementResolver.d.ts +3 -1
- package/dist/commands/dom/DomElementResolver.js +10 -3
- package/dist/commands/dom/a11y.d.ts +1 -1
- package/dist/commands/dom/a11y.js +23 -22
- package/dist/commands/dom/eval.d.ts +4 -2
- package/dist/commands/dom/eval.js +31 -7
- package/dist/commands/dom/form.js +10 -9
- package/dist/commands/dom/formInteraction.js +9 -8
- package/dist/commands/dom/get.js +32 -14
- 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 +10 -3
- package/dist/commands/dom/query.d.ts +20 -2
- package/dist/commands/dom/query.js +39 -6
- package/dist/commands/dom/screenshot.js +3 -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 +112 -41
- package/dist/commands/helpTopic.d.ts +16 -1
- package/dist/commands/helpTopic.js +59 -1
- package/dist/commands/installSkill.d.ts +15 -5
- package/dist/commands/installSkill.js +86 -16
- package/dist/commands/network/list.js +65 -12
- package/dist/commands/optionBehaviors.d.ts +25 -2
- package/dist/commands/optionBehaviors.js +81 -46
- package/dist/commands/peek.js +3 -0
- package/dist/commands/shared/CommandRunner.js +13 -13
- package/dist/commands/shared/daemonErrorHandler.d.ts +5 -2
- package/dist/commands/shared/daemonErrorHandler.js +21 -10
- package/dist/commands/shared/dataFetcher.d.ts +14 -4
- package/dist/commands/shared/dataFetcher.js +20 -4
- package/dist/commands/shared/followMode.d.ts +9 -1
- package/dist/commands/shared/followMode.js +22 -4
- package/dist/commands/shared/handleValidationError.js +3 -3
- package/dist/commands/shared/optionTypes.d.ts +17 -3
- package/dist/commands/shared/outputFile.js +6 -1
- package/dist/commands/shared/startHelpers.js +3 -3
- 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/chromeIdentity.d.ts +8 -2
- package/dist/connection/chromeIdentity.js +85 -13
- package/dist/connection/launcher.js +3 -2
- package/dist/constants.d.ts +29 -1
- package/dist/constants.js +35 -1
- package/dist/daemon/SessionController.js +8 -1
- package/dist/daemon/launcher.d.ts +3 -2
- package/dist/daemon/launcher.js +47 -3
- package/dist/daemon/session/Session.d.ts +5 -1
- package/dist/daemon/session/Session.js +42 -3
- package/dist/daemon/session/TelemetryStore.d.ts +15 -1
- package/dist/daemon/session/TelemetryStore.js +19 -1
- package/dist/daemon/session/commandRegistry.js +52 -18
- package/dist/daemon/session/interactions.d.ts +2 -1
- package/dist/daemon/session/interactions.js +13 -1
- package/dist/daemon/session/matchedStylesReset.d.ts +26 -0
- package/dist/daemon/session/matchedStylesReset.js +46 -0
- package/dist/daemon/session/plugins.js +17 -2
- package/dist/daemon/session/teardown.js +1 -1
- package/dist/daemon/session/triggeredRequests.d.ts +0 -5
- package/dist/daemon/session/triggeredRequests.js +13 -7
- package/dist/daemon.js +2385 -1229
- package/dist/errors/messages.d.ts +62 -11
- package/dist/errors/messages.js +119 -22
- package/dist/index.js +14995 -9866
- 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 +16 -0
- package/dist/ipc/protocol/domTypes.d.ts +12 -0
- package/dist/ipc/protocol/inspectTypes.d.ts +7 -2
- package/dist/ipc/session/types.d.ts +7 -1
- package/dist/program.d.ts +14 -0
- package/dist/program.js +53 -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 +33 -3
- package/dist/runtime/dom/elementGeometry.js +44 -19
- package/dist/runtime/dom/elementInfo.d.ts +76 -18
- package/dist/runtime/dom/elementInfo.js +190 -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/frameScopedConnection.d.ts +7 -0
- package/dist/runtime/dom/frameScopedConnection.js +2 -2
- package/dist/runtime/dom/inspect.d.ts +17 -3
- package/dist/runtime/dom/inspect.js +45 -32
- package/dist/runtime/dom/inspectAllStyles.js +1 -0
- package/dist/runtime/dom/inspectHints.d.ts +1 -1
- package/dist/runtime/dom/inspectModel.d.ts +5 -4
- 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/inspectRules.d.ts +29 -3
- package/dist/runtime/dom/inspectRules.js +205 -11
- package/dist/runtime/dom/inspectScripts.d.ts +29 -2
- package/dist/runtime/dom/inspectScripts.js +49 -10
- package/dist/runtime/dom/layout.d.ts +0 -2
- package/dist/runtime/dom/layout.js +10 -9
- package/dist/runtime/dom/reactEventHelpers.d.ts +17 -4
- package/dist/runtime/dom/reactEventHelpers.js +71 -28
- package/dist/runtime/dom/targetNode.d.ts +27 -10
- package/dist/runtime/dom/targetNode.js +283 -16
- 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.d.ts +15 -1
- package/dist/telemetry/a11y.js +85 -2
- package/dist/telemetry/console.d.ts +2 -1
- package/dist/telemetry/console.js +30 -21
- package/dist/telemetry/har/builder.js +1 -1
- package/dist/telemetry/network.d.ts +13 -16
- package/dist/telemetry/network.js +30 -52
- package/dist/telemetry/networkRetention.d.ts +83 -0
- package/dist/telemetry/networkRetention.js +117 -0
- package/dist/telemetry/pageCrash.d.ts +26 -0
- package/dist/telemetry/pageCrash.js +53 -0
- package/dist/types.d.ts +42 -0
- package/dist/ui/OutputBuilder.d.ts +10 -0
- package/dist/ui/OutputBuilder.js +12 -0
- package/dist/ui/formatters/a11y.d.ts +5 -7
- package/dist/ui/formatters/a11y.js +7 -61
- 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 +7 -5
- package/dist/ui/formatters/console/follow.d.ts +5 -2
- package/dist/ui/formatters/console/follow.js +7 -4
- package/dist/ui/formatters/console/json.d.ts +4 -7
- package/dist/ui/formatters/console/json.js +16 -14
- package/dist/ui/formatters/console/shared.d.ts +47 -2
- package/dist/ui/formatters/console/shared.js +33 -0
- package/dist/ui/formatters/console/summarize.d.ts +9 -2
- package/dist/ui/formatters/console/summarize.js +57 -11
- package/dist/ui/formatters/console.d.ts +3 -2
- package/dist/ui/formatters/console.js +8 -10
- package/dist/ui/formatters/details.js +4 -2
- package/dist/ui/formatters/dom.d.ts +14 -5
- package/dist/ui/formatters/dom.js +30 -13
- 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 +4 -2
- package/dist/ui/formatters/longValues.d.ts +14 -0
- package/dist/ui/formatters/longValues.js +23 -0
- package/dist/ui/formatters/networkList.d.ts +8 -2
- package/dist/ui/formatters/networkList.js +11 -3
- package/dist/ui/formatters/preview.d.ts +6 -1
- package/dist/ui/formatters/preview.js +67 -15
- package/dist/ui/formatters/sessions.d.ts +2 -2
- package/dist/ui/formatters/sessions.js +9 -2
- package/dist/ui/formatters/status.js +7 -0
- package/dist/ui/formatters/triggeredRequests.js +2 -1
- package/dist/ui/logging/logger.d.ts +1 -1
- package/dist/ui/messages/chrome.d.ts +20 -1
- package/dist/ui/messages/chrome.js +29 -3
- package/dist/ui/messages/commands.d.ts +153 -12
- package/dist/ui/messages/commands.js +198 -15
- package/dist/ui/messages/consoleMessages.d.ts +24 -0
- package/dist/ui/messages/consoleMessages.js +32 -0
- package/dist/ui/messages/networkMessages.d.ts +24 -0
- package/dist/ui/messages/networkMessages.js +45 -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/http.d.ts +9 -2
- package/dist/utils/http.js +4 -3
- package/dist/utils/process.d.ts +12 -0
- package/dist/utils/process.js +25 -0
- package/dist/utils/strings.d.ts +19 -0
- package/dist/utils/strings.js +16 -0
- package/package.json +2 -2
|
@@ -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,91 @@ 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, unless `fields` is set: then selects
|
|
88
|
+
* (all their options) and editable regions are read as `innerText` reads
|
|
89
|
+
* them (inputs and textareas it leaves out too). Raw text keeps
|
|
90
|
+
* `innerText`'s collapsed whitespace. The text is cut at `limit` characters, and a
|
|
91
|
+
* part whose text alone passes the limit is read from its text nodes
|
|
92
|
+
* (`textContent`) instead of `innerText`, which would lay out all of it.
|
|
93
|
+
*/
|
|
94
|
+
export const FLAT_TEXT_JS = `(el, limit, fields) => {
|
|
95
|
+
const composed = ${COMPOSED_JS};
|
|
96
|
+
const view = el.ownerDocument.defaultView;
|
|
97
|
+
let text = '';
|
|
98
|
+
const nodesOf = (node) =>
|
|
99
|
+
node.localName === 'slot' ? node.assignedNodes({ flatten: true }) : Array.from((node.shadowRoot || node).childNodes);
|
|
100
|
+
const read = (node, visible) => {
|
|
101
|
+
if (text.length >= limit) return;
|
|
102
|
+
if (node.nodeType === 3 && visible) text += node.data.replace(/\\s+/g, ' ');
|
|
103
|
+
if (node.nodeType !== 1) return;
|
|
104
|
+
if (/^(input|textarea)$/.test(node.localName)) return;
|
|
105
|
+
if (!fields && (node.localName === 'select' || node.isContentEditable)) return;
|
|
106
|
+
const style = view.getComputedStyle(node);
|
|
107
|
+
if (node.checkVisibility && !node.checkVisibility() && style.display !== 'contents') return;
|
|
108
|
+
if (node.localName === 'br') text += '\\n';
|
|
109
|
+
const apart = style.display.startsWith('inline') || style.display === 'contents' ? '' : '\\n';
|
|
110
|
+
text += apart;
|
|
111
|
+
const own = node.textContent || '';
|
|
112
|
+
if (composed(node)) nodesOf(node).forEach((child) => read(child, style.visibility === 'visible'));
|
|
113
|
+
else if (own.length > limit - text.length) text += own.slice(0, limit - text.length);
|
|
114
|
+
else text += typeof node.innerText === 'string' ? node.innerText : own;
|
|
115
|
+
text += apart;
|
|
116
|
+
};
|
|
117
|
+
const visible = view.getComputedStyle(el).visibility === 'visible';
|
|
118
|
+
nodesOf(el).forEach((child) => read(child, visible));
|
|
119
|
+
return text.slice(0, limit);
|
|
120
|
+
}`;
|
|
39
121
|
/**
|
|
40
122
|
* Page-side text of an element as a user sees it: `innerText` for a rendered
|
|
41
123
|
* element (CSS-hidden parts left out, inline elements not split apart), none
|
|
42
124
|
* for an element that is not rendered, `textContent` for SVG and other
|
|
43
125
|
* elements without `innerText` and for `display: contents` wrappers (no box
|
|
44
126
|
* of their own, but their children are shown), and the label of an
|
|
45
|
-
* `<option>` (which its `<select>` renders).
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
127
|
+
* `<option>` (which its `<select>` renders). A web component, a slot, and
|
|
128
|
+
* an element holding either are read through the flat tree
|
|
129
|
+
* ({@link FLAT_TEXT_JS}): the text a component renders from its shadow
|
|
130
|
+
* root, slotted content in place of its slots, blocks set apart. For large
|
|
131
|
+
* containers (more than 2000 characters of text) only the start is read,
|
|
132
|
+
* from the text nodes whose parent is rendered, so a preview never lays out
|
|
133
|
+
* a whole page's text, unless `full` is set. Decorations are left out
|
|
134
|
+
* ({@link WITHOUT_DECORATIONS_JS}).
|
|
49
135
|
*/
|
|
50
136
|
export const ELEMENT_TEXT_JS = `(el, full) => {
|
|
51
137
|
const withoutDecorations = ${WITHOUT_DECORATIONS_JS};
|
|
138
|
+
const composed = ${COMPOSED_JS};
|
|
139
|
+
const flatText = ${FLAT_TEXT_JS};
|
|
52
140
|
const all = el.textContent || '';
|
|
53
141
|
if (el.tagName === 'OPTION') return el.label;
|
|
54
142
|
if (typeof el.innerText !== 'string') return full ? all : all.slice(0, 2000);
|
|
55
143
|
const rendered = (node) => !node.checkVisibility || node.checkVisibility();
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
144
|
+
const shown = rendered(el);
|
|
145
|
+
const boxless = !shown && el.ownerDocument.defaultView.getComputedStyle(el).display === 'contents';
|
|
146
|
+
if (!shown && !boxless) return '';
|
|
147
|
+
if (composed(el)) return withoutDecorations(el, flatText(el, full ? Infinity : 2000));
|
|
148
|
+
if (boxless) return withoutDecorations(el, full ? all : all.slice(0, 2000));
|
|
60
149
|
if (full || all.length <= 2000) return withoutDecorations(el, el.innerText);
|
|
61
150
|
const walker = el.ownerDocument.createTreeWalker(el, NodeFilter.SHOW_TEXT);
|
|
62
151
|
let start = '';
|
|
@@ -132,12 +221,38 @@ export const ELEMENT_STATE_JS = `(el) => {
|
|
|
132
221
|
}
|
|
133
222
|
}`;
|
|
134
223
|
/**
|
|
135
|
-
* Page-side short description of an element: tag, id and up to two classes
|
|
136
|
-
* e.g. `button#save.primary.large`.
|
|
224
|
+
* Page-side short description of an element: tag, id and up to two classes
|
|
225
|
+
* ({@link LABEL_CLASSES_JS}), e.g. `button#save.primary.large`.
|
|
137
226
|
*/
|
|
138
|
-
export const ELEMENT_DESCRIPTION_JS = `(node) =>
|
|
139
|
-
|
|
140
|
-
|
|
227
|
+
export const ELEMENT_DESCRIPTION_JS = `(node) => {
|
|
228
|
+
const classes = (${LABEL_CLASSES_JS})(node);
|
|
229
|
+
return node.tagName.toLowerCase() + (node.id ? '#' + node.id : '') + (classes[0] ? '.' + classes[0] : '') + (classes[1] ? '.' + classes[1] : '');
|
|
230
|
+
}`;
|
|
231
|
+
/**
|
|
232
|
+
* Page-side reason an element is disabled, or null when it is not. A native
|
|
233
|
+
* control (`button`, `input`, `select`, `textarea`, `fieldset`, `optgroup`,
|
|
234
|
+
* `option`) is disabled by its `disabled attribute` or by being `inside a
|
|
235
|
+
* disabled <fieldset>` (not in its first `<legend>`), an `<option>` by
|
|
236
|
+
* being `inside a disabled <optgroup>`; any other element
|
|
237
|
+
* (e.g. a custom element) when its `disabled` property is set (empty
|
|
238
|
+
* reason). Reads the element's own attributes rather than
|
|
239
|
+
* `matches(':disabled')`, which pages replace.
|
|
240
|
+
*/
|
|
241
|
+
export const DISABLED_CAUSE_JS = `(el) => {
|
|
242
|
+
if (!/^(button|input|select|textarea|fieldset|optgroup|option)$/.test(el.localName)) return el.disabled ? '' : null;
|
|
243
|
+
if (el.hasAttribute('disabled')) return 'disabled attribute';
|
|
244
|
+
if (el.localName === 'option') {
|
|
245
|
+
const group = el.parentElement;
|
|
246
|
+
return group && group.localName === 'optgroup' && group.hasAttribute('disabled') ? 'inside a disabled <optgroup>' : null;
|
|
247
|
+
}
|
|
248
|
+
for (let node = el.parentElement; node; node = node.parentElement) {
|
|
249
|
+
if (node.localName !== 'fieldset' || !node.hasAttribute('disabled')) continue;
|
|
250
|
+
let legend = node.firstElementChild;
|
|
251
|
+
while (legend && legend.localName !== 'legend') legend = legend.nextElementSibling;
|
|
252
|
+
if (!legend || !legend.contains(el)) return 'inside a disabled <fieldset>';
|
|
253
|
+
}
|
|
254
|
+
return null;
|
|
255
|
+
}`;
|
|
141
256
|
/**
|
|
142
257
|
* Page-side position of an element among its parent's children with the
|
|
143
258
|
* same short description ({@link ELEMENT_DESCRIPTION_JS}), e.g. `(2nd of 3)`;
|
|
@@ -159,12 +274,15 @@ export const SIBLING_POSITION_JS = `(el) => {
|
|
|
159
274
|
* Page-side identity of an element an action hit, so the output says which
|
|
160
275
|
* one it was, as one string: its short description
|
|
161
276
|
* ({@link ELEMENT_DESCRIPTION_JS}) and visible text (button value for button
|
|
162
|
-
* inputs, decorations left out
|
|
163
|
-
*
|
|
277
|
+
* inputs, decorations left out; read through the flat tree for web
|
|
278
|
+
* components and slots, {@link FLAT_TEXT_JS}), e.g.
|
|
279
|
+
* `button#add.btn "Add to cart"`. A `<select>` is named by its label
|
|
280
|
+
* (`aria-labelledby` or `<label>`), aria-label or name, else by its selected
|
|
164
281
|
* option, e.g. `select.sort "Sort products"`. An element without visible
|
|
165
282
|
* text is described by itself first: by its position among same-looking
|
|
166
283
|
* siblings ({@link SIBLING_POSITION_JS}), e.g. `div.figure (2nd of 3)`, with
|
|
167
|
-
* its
|
|
284
|
+
* its name when it has one: aria-label, label, placeholder, title, or the
|
|
285
|
+
* alt text of an image (in it). Only an element
|
|
168
286
|
* without an id that is the only one of its kind is named by the nearest of
|
|
169
287
|
* three ancestors that has text, e.g.
|
|
170
288
|
* `input.toggle in div.view "Write report"` (rows of a list share their
|
|
@@ -172,7 +290,7 @@ export const SIBLING_POSITION_JS = `(el) => {
|
|
|
172
290
|
* of `<select>`s in it (read like `innerText`: CSS-hidden text is left out of
|
|
173
291
|
* a rendered ancestor; at most 500 text nodes; selects in shadow roots are
|
|
174
292
|
* not looked into), and never past an editable ancestor, whose text may be
|
|
175
|
-
* typed input; otherwise its
|
|
293
|
+
* typed input; otherwise its name is used. Texts
|
|
176
294
|
* are cut at 40 characters. Contenteditable elements count as controls: what
|
|
177
295
|
* was typed into them is never echoed (nor their ancestors' text).
|
|
178
296
|
*/
|
|
@@ -180,19 +298,36 @@ export const ELEMENT_IDENTITY_JS = `(el) => {
|
|
|
180
298
|
const describe = ${ELEMENT_DESCRIPTION_JS};
|
|
181
299
|
const siblingPosition = ${SIBLING_POSITION_JS};
|
|
182
300
|
const withoutDecorations = ${WITHOUT_DECORATIONS_JS};
|
|
301
|
+
const composed = ${COMPOSED_JS};
|
|
302
|
+
const flatText = ${FLAT_TEXT_JS};
|
|
183
303
|
const clean = (text) => (text || '').replace(/\\s+/g, ' ').trim();
|
|
184
304
|
const cut = (text) => {
|
|
185
305
|
const characters = Array.from(text);
|
|
186
306
|
return characters.length > 40 ? characters.slice(0, 40).join('') + '…' : text;
|
|
187
307
|
};
|
|
188
308
|
const isControl = (node) => /^(input|select|textarea)$/.test(node.localName) || node.isContentEditable;
|
|
189
|
-
const
|
|
190
|
-
|
|
309
|
+
const textOf = (node) =>
|
|
310
|
+
composed(node) ? flatText(node, 200) : typeof node.innerText === 'string' ? node.innerText : node.textContent;
|
|
311
|
+
const shownText = (node) => (isControl(node) ? '' : clean(withoutDecorations(node, textOf(node))));
|
|
191
312
|
const buttonValue = (node) => (node.localName === 'input' && /^(submit|button|reset)$/i.test(node.type) ? clean(node.value) : '');
|
|
313
|
+
const labelText = (node) => {
|
|
314
|
+
const ids = (node.getAttribute('aria-labelledby') || '').split(/\\s+/).filter(Boolean);
|
|
315
|
+
const root = node.getRootNode();
|
|
316
|
+
const labels = ids.length ? ids.map((id) => root.getElementById(id)) : Array.from(node.labels || []);
|
|
317
|
+
return clean(labels.filter(Boolean).map(textOf).join(' '));
|
|
318
|
+
};
|
|
319
|
+
const imageAlt = (node) => {
|
|
320
|
+
const image = node.localName === 'img' ? node : node.querySelector('img[alt]');
|
|
321
|
+
return image ? clean(image.getAttribute('alt')) : '';
|
|
322
|
+
};
|
|
192
323
|
const attributeText = (node) =>
|
|
193
|
-
clean(node.getAttribute('aria-label')) ||
|
|
324
|
+
clean(node.getAttribute('aria-label')) ||
|
|
325
|
+
labelText(node) ||
|
|
326
|
+
clean(node.getAttribute('placeholder')) ||
|
|
327
|
+
clean(node.getAttribute('title')) ||
|
|
328
|
+
imageAlt(node);
|
|
194
329
|
const selectName = (node) =>
|
|
195
|
-
|
|
330
|
+
labelText(node) ||
|
|
196
331
|
clean(node.getAttribute('aria-label')) ||
|
|
197
332
|
clean(node.getAttribute('name')) ||
|
|
198
333
|
clean(node.selectedOptions && node.selectedOptions[0] && node.selectedOptions[0].label);
|
|
@@ -225,27 +360,42 @@ export const ELEMENT_IDENTITY_JS = `(el) => {
|
|
|
225
360
|
}`;
|
|
226
361
|
/**
|
|
227
362
|
* 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
|
-
*
|
|
363
|
+
* and the shadow root holding it, with the host's first class and the text
|
|
364
|
+
* a user sees of it ({@link ELEMENT_TEXT_JS}, cut at 30 characters with
|
|
365
|
+
* `…`), e.g. `iframe#pay > shadow root of <sl-button.primary "Save">`;
|
|
366
|
+
* empty for the main document. Each host's text is read once per script
|
|
367
|
+
* run, as many matches share a host.
|
|
231
368
|
*/
|
|
232
|
-
export const ELEMENT_CONTEXT_JS = `(
|
|
369
|
+
export const ELEMENT_CONTEXT_JS = `(() => {
|
|
370
|
+
const textOf = ${ELEMENT_TEXT_JS};
|
|
371
|
+
const hostTexts = new WeakMap();
|
|
372
|
+
const hostText = (host) => {
|
|
373
|
+
if (!hostTexts.has(host)) {
|
|
374
|
+
const characters = Array.from(String(textOf(host, false)).replace(/\\s+/g, ' ').trim());
|
|
375
|
+
hostTexts.set(host, characters.length > 30 ? characters.slice(0, 30).join('') + '…' : characters.join(''));
|
|
376
|
+
}
|
|
377
|
+
return hostTexts.get(host);
|
|
378
|
+
};
|
|
233
379
|
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
|
-
|
|
380
|
+
const labelClasses = ${LABEL_CLASSES_JS};
|
|
381
|
+
return (el) => {
|
|
382
|
+
const parts = [];
|
|
383
|
+
for (let doc = el.ownerDocument; doc && doc.defaultView && doc.defaultView.frameElement; ) {
|
|
384
|
+
const frame = doc.defaultView.frameElement;
|
|
385
|
+
parts.unshift(describe(frame));
|
|
386
|
+
doc = frame.ownerDocument;
|
|
387
|
+
}
|
|
388
|
+
const root = el.getRootNode();
|
|
389
|
+
if (root.host) {
|
|
390
|
+
const host = root.host;
|
|
391
|
+
const firstClass = labelClasses(host)[0];
|
|
392
|
+
const label = describe(host) + (!host.id && firstClass ? '.' + firstClass : '');
|
|
393
|
+
const text = hostText(host);
|
|
394
|
+
parts.push('shadow root of <' + label + (text ? ' "' + text + '"' : '') + '>');
|
|
395
|
+
}
|
|
396
|
+
return parts.join(' > ');
|
|
397
|
+
};
|
|
398
|
+
})()`;
|
|
249
399
|
/**
|
|
250
400
|
* Short text preview of an element's text: whitespace collapsed, cut on a
|
|
251
401
|
* 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 */
|
|
@@ -2,8 +2,10 @@ import { CDPProtocolError } from '../../connection/errors.js';
|
|
|
2
2
|
import { CommandError } from '../../errors/index.js';
|
|
3
3
|
import { navigationPendingError, pageBusyError, pageClosedDuringEvalError, pageNavigatedDuringEvalError, promiseTimeoutError, scriptExecutionError, scriptTimeoutError, } from '../../errors/messages.js';
|
|
4
4
|
import { pendingNavigationUrl } from '../page/navigation.js';
|
|
5
|
+
import { findReplacedBuiltins } from '../page/replacedBuiltins.js';
|
|
5
6
|
import { senderFor } from '../../telemetry/objectExpander.js';
|
|
6
7
|
import { formatRemoteObject } from '../../telemetry/remoteObject.js';
|
|
8
|
+
import { evalCopiedByBrowserWarning, evalPreviewWarning } from '../../ui/messages/commands.js';
|
|
7
9
|
import { createLogger } from '../../ui/logging/index.js';
|
|
8
10
|
import { getErrorMessage } from '../../utils/errors.js';
|
|
9
11
|
import { EXIT_CODES } from '../../utils/exitCodes.js';
|
|
@@ -121,7 +123,7 @@ async function settlePromise(cdp, result, script) {
|
|
|
121
123
|
* @param details - Exception details
|
|
122
124
|
* @returns Readable message
|
|
123
125
|
*/
|
|
124
|
-
function describeException(details) {
|
|
126
|
+
export function describeException(details) {
|
|
125
127
|
const exception = details.exception;
|
|
126
128
|
if (exception?.type === 'object' && exception.subtype !== 'error' && exception.preview) {
|
|
127
129
|
return `Uncaught ${formatRemoteObject(exception)}`;
|
|
@@ -164,7 +166,8 @@ const BRIEF_SUBTYPES = new Set(['node', 'arraybuffer', 'dataview']);
|
|
|
164
166
|
* `12n`, functions `function name()`, and cycles `[Circular]` (an object
|
|
165
167
|
* shared by two properties is copied twice). Works for objects of iframes
|
|
166
168
|
* (other realms); lists and objects are cut after 1000 entries, and a
|
|
167
|
-
* throwing getter becomes `[Error: …]`.
|
|
169
|
+
* throwing getter becomes `[Error: …]`. It uses the page's built-ins
|
|
170
|
+
* ({@link COPY_BUILTINS}), so it only runs when the page left them alone.
|
|
168
171
|
*/
|
|
169
172
|
export const JSON_SAFE_COPY_FUNCTION = `function () {
|
|
170
173
|
const MAX_ITEMS = 1000;
|
|
@@ -178,6 +181,13 @@ export const JSON_SAFE_COPY_FUNCTION = `function () {
|
|
|
178
181
|
};
|
|
179
182
|
const items = (list, next) => {
|
|
180
183
|
const result = [];
|
|
184
|
+
if (typeof list.length === 'number') {
|
|
185
|
+
for (let i = 0; i < list.length; i++) {
|
|
186
|
+
if (i === MAX_ITEMS) { result.push('…'); break; }
|
|
187
|
+
result.push(next(list[i]));
|
|
188
|
+
}
|
|
189
|
+
return result;
|
|
190
|
+
}
|
|
181
191
|
let count = 0;
|
|
182
192
|
for (const item of list) {
|
|
183
193
|
if (count++ === MAX_ITEMS) { result.push('…'); break; }
|
|
@@ -226,6 +236,33 @@ export const JSON_SAFE_COPY_FUNCTION = `function () {
|
|
|
226
236
|
};
|
|
227
237
|
return copy(this, 0);
|
|
228
238
|
}`;
|
|
239
|
+
/** Built-ins {@link JSON_SAFE_COPY_FUNCTION} uses (it runs in the page's world, next to the result) */
|
|
240
|
+
const COPY_BUILTINS = [
|
|
241
|
+
'Object.keys',
|
|
242
|
+
'Object.is',
|
|
243
|
+
'Object.prototype.toString',
|
|
244
|
+
'Function.prototype.call',
|
|
245
|
+
'Array.from',
|
|
246
|
+
'Array.isArray',
|
|
247
|
+
'Array.prototype.push',
|
|
248
|
+
'Array.prototype.slice',
|
|
249
|
+
'Array.prototype.join',
|
|
250
|
+
'ArrayBuffer.isView',
|
|
251
|
+
'Number.isNaN',
|
|
252
|
+
'Number.isFinite',
|
|
253
|
+
'String',
|
|
254
|
+
'String.prototype.slice',
|
|
255
|
+
'String.prototype.toLowerCase',
|
|
256
|
+
'Set',
|
|
257
|
+
'Set.prototype.add',
|
|
258
|
+
'Set.prototype.has',
|
|
259
|
+
'Set.prototype.delete',
|
|
260
|
+
'Set.prototype.values',
|
|
261
|
+
'Map.prototype.entries',
|
|
262
|
+
'Date.prototype.toISOString',
|
|
263
|
+
];
|
|
264
|
+
/** Returns the object it is called on (for the browser to copy it by value) */
|
|
265
|
+
const SELF_FUNCTION = 'function () { return this; }';
|
|
229
266
|
/**
|
|
230
267
|
* Whether an exception is V8 terminating a script that ran too long.
|
|
231
268
|
*
|
|
@@ -240,7 +277,10 @@ function isTerminated(details) {
|
|
|
240
277
|
*
|
|
241
278
|
* Plain objects and arrays are copied by value; values JSON cannot represent
|
|
242
279
|
* (NaN, -0, BigInt, functions, symbols, DOM nodes, dates, maps, errors, the
|
|
243
|
-
* window, cyclic objects) are given as their readable description.
|
|
280
|
+
* window, cyclic objects) are given as their readable description. On a
|
|
281
|
+
* page that replaced built-ins that copy uses, the browser copies the
|
|
282
|
+
* object (JSON-like values exactly, the others as null or `{}`), or else
|
|
283
|
+
* its preview is given, with a warning naming them.
|
|
244
284
|
*
|
|
245
285
|
* @param cdp - CDP connection
|
|
246
286
|
* @param remote - Remote object returned by Runtime.evaluate
|
|
@@ -260,19 +300,39 @@ async function toEvalResult(cdp, remote) {
|
|
|
260
300
|
if (remote.type !== 'object' || DESCRIBED_SUBTYPES.has(remote.subtype ?? '')) {
|
|
261
301
|
return { value: formatRemoteObject(remote), ...kind };
|
|
262
302
|
}
|
|
303
|
+
const replaced = await findReplacedBuiltins(cdp, COPY_BUILTINS, remote.objectId);
|
|
304
|
+
const copy = await copyByValue(cdp, remote.objectId, replaced.length === 0 ? JSON_SAFE_COPY_FUNCTION : SELF_FUNCTION);
|
|
305
|
+
const value = copy ? copy.value : formatRemoteObject(remote);
|
|
306
|
+
if (replaced.length === 0)
|
|
307
|
+
return { value, ...kind };
|
|
308
|
+
const warning = copy ? evalCopiedByBrowserWarning(replaced) : evalPreviewWarning(replaced);
|
|
309
|
+
return { value, ...kind, warning };
|
|
310
|
+
}
|
|
311
|
+
/**
|
|
312
|
+
* Copy a result by value: what a function called on it returns, which the
|
|
313
|
+
* browser serialises itself (without the page's built-ins).
|
|
314
|
+
*
|
|
315
|
+
* @param cdp - CDP connection
|
|
316
|
+
* @param objectId - The result
|
|
317
|
+
* @param functionDeclaration - {@link JSON_SAFE_COPY_FUNCTION}, or
|
|
318
|
+
* {@link SELF_FUNCTION} for the browser's plain copy (it fails on cycles
|
|
319
|
+
* and BigInts)
|
|
320
|
+
* @returns The copy, or undefined when it failed
|
|
321
|
+
*/
|
|
322
|
+
async function copyByValue(cdp, objectId, functionDeclaration) {
|
|
263
323
|
try {
|
|
264
324
|
const copy = (await cdp.send('Runtime.callFunctionOn', {
|
|
265
|
-
objectId
|
|
266
|
-
functionDeclaration
|
|
325
|
+
objectId,
|
|
326
|
+
functionDeclaration,
|
|
267
327
|
returnByValue: true,
|
|
268
328
|
}));
|
|
269
329
|
if (!copy.exceptionDetails)
|
|
270
|
-
return { value: copy.result?.value
|
|
330
|
+
return { value: copy.result?.value };
|
|
271
331
|
}
|
|
272
332
|
catch (error) {
|
|
273
333
|
log.debug(`Could not copy eval result by value: ${getErrorMessage(error)}`);
|
|
274
334
|
}
|
|
275
|
-
return
|
|
335
|
+
return undefined;
|
|
276
336
|
}
|
|
277
337
|
/**
|
|
278
338
|
* Wait for `work`, or fail when it takes longer than `ms`.
|