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
|
@@ -9,16 +9,87 @@
|
|
|
9
9
|
* {@link BOUND_TARGET_SELECTOR}.
|
|
10
10
|
*/
|
|
11
11
|
import { CommandError } from '../../errors/index.js';
|
|
12
|
-
import { emptySelectorError, indexOutOfRangeError, noNodesFoundError, staleNodeError, } from '../../errors/messages.js';
|
|
12
|
+
import { actionBrokenByPageError, actionScriptFailedError, emptySelectorError, indexOutOfRangeError, noNodesFoundError, staleNodeError, } from '../../errors/messages.js';
|
|
13
|
+
import { COMPOSED_JS, FLAT_TEXT_JS } from './elementInfo.js';
|
|
14
|
+
import { ActionScriptError, throwIfInvalidSelector } from './formFillHelpers/shared.js';
|
|
13
15
|
import { frameScopedConnection } from './frameScopedConnection.js';
|
|
16
|
+
import { evaluateInBdgWorld } from '../page/bdgWorld.js';
|
|
17
|
+
import { findReplacedBuiltins } from '../page/replacedBuiltins.js';
|
|
14
18
|
import { createLogger } from '../../ui/logging/index.js';
|
|
19
|
+
import { brokenByReplacedBuiltinsSuggestion, replacedBuiltinsWarning, } from '../../ui/messages/commands.js';
|
|
15
20
|
import { getErrorMessage } from '../../utils/errors.js';
|
|
16
21
|
import { EXIT_CODES } from '../../utils/exitCodes.js';
|
|
17
22
|
import { parseSelectorFilters, } from '../../utils/selectorFilters.js';
|
|
18
23
|
/** Selector placeholder that makes page scripts use the bound node. */
|
|
19
24
|
export const BOUND_TARGET_SELECTOR = '__bdg_bound_target__';
|
|
20
|
-
/** Removes the node bound for index-based commands from the window
|
|
21
|
-
export const UNBIND_TARGET_SCRIPT = 'delete window.__bdgTarget';
|
|
25
|
+
/** Removes the node bound for index-based commands, and matches bound for a selector, from the window they were stored on */
|
|
26
|
+
export const UNBIND_TARGET_SCRIPT = 'delete window.__bdgTarget; delete window.__bdgMatches';
|
|
27
|
+
/**
|
|
28
|
+
* Built-ins the selector search ({@link DEEP_QUERY_JS}) cannot do without:
|
|
29
|
+
* when the page replaced one, the search runs in bdg's world instead.
|
|
30
|
+
* Helpers that libraries replace with working versions (Prototype.js's
|
|
31
|
+
* `Array.prototype.map`) are left to {@link SCRIPT_BUILTINS}.
|
|
32
|
+
*/
|
|
33
|
+
const SELECTION_BUILTINS = [
|
|
34
|
+
'Element.prototype.querySelectorAll',
|
|
35
|
+
'Element.prototype.querySelector',
|
|
36
|
+
'Document.prototype.querySelectorAll',
|
|
37
|
+
'Document.prototype.querySelector',
|
|
38
|
+
'DocumentFragment.prototype.querySelectorAll',
|
|
39
|
+
'Element.prototype.matches',
|
|
40
|
+
'Element.prototype.closest',
|
|
41
|
+
'Node.prototype.getRootNode',
|
|
42
|
+
'Node.prototype.compareDocumentPosition',
|
|
43
|
+
'Node.prototype.contains',
|
|
44
|
+
'NodeList.prototype[Symbol.iterator]',
|
|
45
|
+
'Array.prototype[Symbol.iterator]',
|
|
46
|
+
'Array.prototype.push',
|
|
47
|
+
'Set',
|
|
48
|
+
'Map',
|
|
49
|
+
];
|
|
50
|
+
/** Other built-ins the interaction scripts use */
|
|
51
|
+
const SCRIPT_BUILTINS = [
|
|
52
|
+
'Array.from',
|
|
53
|
+
'Array.prototype.map',
|
|
54
|
+
'Array.prototype.filter',
|
|
55
|
+
'Array.prototype.forEach',
|
|
56
|
+
'Array.prototype.some',
|
|
57
|
+
'Array.prototype.find',
|
|
58
|
+
'Array.prototype.includes',
|
|
59
|
+
'Object.keys',
|
|
60
|
+
'Object.assign',
|
|
61
|
+
'Object.getOwnPropertyDescriptor',
|
|
62
|
+
'Function.prototype.call',
|
|
63
|
+
'JSON.stringify',
|
|
64
|
+
'JSON.parse',
|
|
65
|
+
];
|
|
66
|
+
/** DOM built-ins the interaction scripts act through (anti-bot scripts often make them throw) */
|
|
67
|
+
const DOM_ACTION_BUILTINS = [
|
|
68
|
+
'Element.prototype.getBoundingClientRect',
|
|
69
|
+
'Element.prototype.getClientRects',
|
|
70
|
+
'Element.prototype.scrollIntoView',
|
|
71
|
+
'window.getComputedStyle',
|
|
72
|
+
'Document.prototype.elementFromPoint',
|
|
73
|
+
'Document.prototype.createEvent',
|
|
74
|
+
'EventTarget.prototype.dispatchEvent',
|
|
75
|
+
'HTMLElement.prototype.focus',
|
|
76
|
+
'HTMLElement.prototype.blur',
|
|
77
|
+
'HTMLElement.prototype.click',
|
|
78
|
+
'Event',
|
|
79
|
+
'MouseEvent',
|
|
80
|
+
'HTMLInputElement.prototype.value',
|
|
81
|
+
'HTMLTextAreaElement.prototype.value',
|
|
82
|
+
'HTMLSelectElement.prototype.value',
|
|
83
|
+
];
|
|
84
|
+
/** Matches bound for a selector when the page replaced the selector search built-ins (enough for --index) */
|
|
85
|
+
const MATCH_BIND_LIMIT = 100;
|
|
86
|
+
/** Page-side: stores a node as match `i` of the selector being bound on the top window (runs on the node, in its frame; a cross-origin frame cannot reach the top window and fails) */
|
|
87
|
+
const BIND_MATCH_FUNCTION = `function (selector, i) {
|
|
88
|
+
const w = window.top;
|
|
89
|
+
if (!w.__bdgMatches || w.__bdgMatches.selector !== selector) return false;
|
|
90
|
+
w.__bdgMatches.nodes[i] = this;
|
|
91
|
+
return true;
|
|
92
|
+
}`;
|
|
22
93
|
const log = createLogger('dom');
|
|
23
94
|
/**
|
|
24
95
|
* Page-side matching of filters ({@link SelectorFilter}) and scoped steps
|
|
@@ -32,9 +103,13 @@ const log = createLogger('dom');
|
|
|
32
103
|
* the text nodes of hidden ones (display or visibility; not those of
|
|
33
104
|
* `<script>`, `<style>` or `<noscript>`), so text filters match hidden
|
|
34
105
|
* elements like Playwright's and `:visible` decides visibility; button inputs
|
|
35
|
-
* use their value.
|
|
36
|
-
*
|
|
37
|
-
*
|
|
106
|
+
* use their value. Text is read in the flat tree, as `bdg dom query` shows
|
|
107
|
+
* it: the text a web component's open shadow root renders, slotted content
|
|
108
|
+
* in place of its slots. A visible component, slot or element holding either
|
|
109
|
+
* ({@link COMPOSED_JS}) is read with {@link FLAT_TEXT_JS}, including the
|
|
110
|
+
* selects and editable regions `innerText` reads. Whitespace is collapsed;
|
|
111
|
+
* filter texts arrive normalized (`has-text` lowercased). `:visible` is
|
|
112
|
+
* checked before the text filters, which read the text.
|
|
38
113
|
*
|
|
39
114
|
* Steps and `:has()` match CSS relative to an element (`:scope > css`). A
|
|
40
115
|
* descendant step also searches the open shadow roots under the element (the
|
|
@@ -42,14 +117,15 @@ const log = createLogger('dom');
|
|
|
42
117
|
* element's own tree.
|
|
43
118
|
*/
|
|
44
119
|
export const FILTER_MATCHING_JS = `(shadowRoots) => {
|
|
120
|
+
const composed = ${COMPOSED_JS};
|
|
121
|
+
const flatText = ${FLAT_TEXT_JS};
|
|
45
122
|
const skipped = /^(script|style|noscript|template)$/;
|
|
46
123
|
const hiddenText = (el) => {
|
|
47
|
-
const
|
|
48
|
-
acceptNode: (node) => (node.nodeType === 1 && skipped.test(node.localName) ? 2 : 1)
|
|
49
|
-
});
|
|
124
|
+
const nodes = el.localName === 'slot' ? el.assignedNodes({ flatten: true }) : (el.shadowRoot || el).childNodes;
|
|
50
125
|
let text = '';
|
|
51
|
-
for (
|
|
126
|
+
for (const node of nodes) {
|
|
52
127
|
if (node.nodeType === 3) text += node.data;
|
|
128
|
+
else if (node.nodeType === 1 && !skipped.test(node.localName)) text += hiddenText(node);
|
|
53
129
|
}
|
|
54
130
|
return text;
|
|
55
131
|
};
|
|
@@ -57,7 +133,8 @@ export const FILTER_MATCHING_JS = `(shadowRoots) => {
|
|
|
57
133
|
if (el.localName === 'input' && /^(submit|button|reset)$/i.test(el.type)) return el.value;
|
|
58
134
|
const rendered = typeof el.innerText === 'string' &&
|
|
59
135
|
(typeof el.checkVisibility !== 'function' || el.checkVisibility({ visibilityProperty: true }));
|
|
60
|
-
|
|
136
|
+
const text = !rendered ? hiddenText(el) : composed(el) ? flatText(el, Infinity, true) : el.innerText;
|
|
137
|
+
return text.replace(/\\s+/g, ' ').trim();
|
|
61
138
|
};
|
|
62
139
|
const passes = (el, filter) => {
|
|
63
140
|
if (filter.kind === 'visible') {
|
|
@@ -151,13 +228,23 @@ export const DEEP_QUERY_JS = `function (selector, parts) {
|
|
|
151
228
|
* Page-side element lookup shared by the interaction scripts.
|
|
152
229
|
*
|
|
153
230
|
* Returns the bound node (if still in the page) for the placeholder selector,
|
|
154
|
-
*
|
|
231
|
+
* the matches bound for the selector when the page replaced the search
|
|
232
|
+
* built-ins ({@link bindMatches}), otherwise all matches of the selector
|
|
233
|
+
* ({@link DEEP_QUERY_JS}).
|
|
155
234
|
*/
|
|
156
235
|
export const FIND_ELEMENTS_JS = `function (selector, parts) {
|
|
157
236
|
if (selector === '${BOUND_TARGET_SELECTOR}') {
|
|
158
237
|
const el = window.__bdgTarget;
|
|
159
238
|
return el && el.isConnected ? [el] : [];
|
|
160
239
|
}
|
|
240
|
+
const bound = window.__bdgMatches;
|
|
241
|
+
if (bound && bound.selector === selector) {
|
|
242
|
+
const connected = [];
|
|
243
|
+
for (let i = 0; i < bound.nodes.length; i++) {
|
|
244
|
+
if (bound.nodes[i] && bound.nodes[i].isConnected) connected[connected.length] = bound.nodes[i];
|
|
245
|
+
}
|
|
246
|
+
return connected;
|
|
247
|
+
}
|
|
161
248
|
return (${DEEP_QUERY_JS})(selector, parts);
|
|
162
249
|
}`;
|
|
163
250
|
/**
|
|
@@ -269,13 +356,124 @@ async function bindTargetNode(cdp, backendNodeId) {
|
|
|
269
356
|
*/
|
|
270
357
|
async function resolveScriptTarget(cdp, params) {
|
|
271
358
|
if (params.backendNodeId === undefined) {
|
|
359
|
+
const selector = params.selector ?? '';
|
|
360
|
+
const replaced = await replacedBuiltins(cdp);
|
|
361
|
+
const searchReplaced = replaced.some((name) => SELECTION_BUILTINS.includes(name));
|
|
362
|
+
const bound = searchReplaced && (await bindMatches(cdp, selector, params.index ?? 0));
|
|
272
363
|
return {
|
|
273
|
-
selector
|
|
364
|
+
selector,
|
|
274
365
|
...(params.index !== undefined && { index: params.index }),
|
|
275
366
|
cdp,
|
|
367
|
+
...(replaced.length > 0 && { replacedBuiltins: replaced }),
|
|
368
|
+
...(bound && { boundInBdgWorld: true }),
|
|
276
369
|
};
|
|
277
370
|
}
|
|
278
|
-
|
|
371
|
+
const scriptCdp = await bindTargetNode(cdp, params.backendNodeId);
|
|
372
|
+
const replaced = await replacedBuiltins(scriptCdp);
|
|
373
|
+
return {
|
|
374
|
+
selector: BOUND_TARGET_SELECTOR,
|
|
375
|
+
cdp: scriptCdp,
|
|
376
|
+
...(replaced.length > 0 && { replacedBuiltins: replaced }),
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
/**
|
|
380
|
+
* The built-ins the interaction scripts use that the page replaced.
|
|
381
|
+
*
|
|
382
|
+
* @param cdp - CDP connection
|
|
383
|
+
* @returns Their dotted names (empty when all are the browser's, or the check failed)
|
|
384
|
+
*/
|
|
385
|
+
function replacedBuiltins(cdp) {
|
|
386
|
+
return findReplacedBuiltins(cdp, [
|
|
387
|
+
...SELECTION_BUILTINS,
|
|
388
|
+
...SCRIPT_BUILTINS,
|
|
389
|
+
...DOM_ACTION_BUILTINS,
|
|
390
|
+
]);
|
|
391
|
+
}
|
|
392
|
+
/**
|
|
393
|
+
* Search the selector in bdg's own world, where the page's replacements do
|
|
394
|
+
* not apply, and hand the matches (the first {@link MATCH_BIND_LIMIT}, or up
|
|
395
|
+
* to the index asked for) to the page scripts, which run in the page's world.
|
|
396
|
+
* When a match cannot be handed over (or the search fails other than on an
|
|
397
|
+
* invalid selector), nothing is bound and the scripts search themselves.
|
|
398
|
+
*
|
|
399
|
+
* @param cdp - CDP connection
|
|
400
|
+
* @param selector - Selector as the user gave it
|
|
401
|
+
* @param index - Match the action is for
|
|
402
|
+
* @returns Whether the matches were bound
|
|
403
|
+
* @throws CommandError (81) for an invalid selector
|
|
404
|
+
*/
|
|
405
|
+
async function bindMatches(cdp, selector, index) {
|
|
406
|
+
const objectGroup = `bdg-bind-${Date.now()}`;
|
|
407
|
+
const limit = Math.max(MATCH_BIND_LIMIT, index + 1);
|
|
408
|
+
try {
|
|
409
|
+
const found = await evaluateInBdgWorld(cdp, {
|
|
410
|
+
expression: `(${DEEP_QUERY_JS})(${selectorArgsJS(selector)}).slice(0, ${limit})`,
|
|
411
|
+
objectGroup,
|
|
412
|
+
});
|
|
413
|
+
if (found.exceptionDetails)
|
|
414
|
+
throwIfInvalidSelector(found.exceptionDetails, selector);
|
|
415
|
+
const arrayId = found.result.objectId;
|
|
416
|
+
if (found.exceptionDetails || !arrayId)
|
|
417
|
+
return false;
|
|
418
|
+
const { result } = (await cdp.send('Runtime.getProperties', {
|
|
419
|
+
objectId: arrayId,
|
|
420
|
+
ownProperties: true,
|
|
421
|
+
}));
|
|
422
|
+
const matches = result
|
|
423
|
+
.filter((property) => /^\d+$/.test(property.name))
|
|
424
|
+
.map((property) => ({ i: Number(property.name), objectId: property.value?.objectId }));
|
|
425
|
+
await cdp.send('Runtime.evaluate', {
|
|
426
|
+
expression: `window.__bdgMatches = { selector: ${JSON.stringify(selector)}, nodes: [] }`,
|
|
427
|
+
});
|
|
428
|
+
const bound = await Promise.all(matches.map(({ i, objectId }) => objectId ? bindMatch(cdp, selector, i, objectId) : Promise.resolve(false)));
|
|
429
|
+
if (bound.every(Boolean))
|
|
430
|
+
return true;
|
|
431
|
+
await cdp.send('Runtime.evaluate', { expression: 'delete window.__bdgMatches' });
|
|
432
|
+
return false;
|
|
433
|
+
}
|
|
434
|
+
catch (error) {
|
|
435
|
+
if (error instanceof CommandError)
|
|
436
|
+
throw error;
|
|
437
|
+
log.debug(`Matches not bound: ${getErrorMessage(error)}`);
|
|
438
|
+
return false;
|
|
439
|
+
}
|
|
440
|
+
finally {
|
|
441
|
+
await cdp
|
|
442
|
+
.send('Runtime.releaseObjectGroup', { objectGroup })
|
|
443
|
+
.catch((error) => log.debug(`Matches not released: ${getErrorMessage(error)}`));
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* Hand one match from bdg's world to the page's world as match `i`.
|
|
448
|
+
*
|
|
449
|
+
* @param cdp - CDP connection
|
|
450
|
+
* @param selector - Selector the matches are for
|
|
451
|
+
* @param i - Its position among the matches
|
|
452
|
+
* @param objectId - The match in bdg's world
|
|
453
|
+
* @returns Whether it was handed over
|
|
454
|
+
*/
|
|
455
|
+
async function bindMatch(cdp, selector, i, objectId) {
|
|
456
|
+
try {
|
|
457
|
+
const { node } = (await cdp.send('DOM.describeNode', {
|
|
458
|
+
objectId,
|
|
459
|
+
}));
|
|
460
|
+
const resolved = (await cdp.send('DOM.resolveNode', {
|
|
461
|
+
backendNodeId: node.backendNodeId,
|
|
462
|
+
}));
|
|
463
|
+
if (!resolved.object.objectId)
|
|
464
|
+
return false;
|
|
465
|
+
const stored = (await cdp.send('Runtime.callFunctionOn', {
|
|
466
|
+
objectId: resolved.object.objectId,
|
|
467
|
+
functionDeclaration: BIND_MATCH_FUNCTION,
|
|
468
|
+
arguments: [{ value: selector }, { value: i }],
|
|
469
|
+
returnByValue: true,
|
|
470
|
+
}));
|
|
471
|
+
return stored.result.value === true;
|
|
472
|
+
}
|
|
473
|
+
catch (error) {
|
|
474
|
+
log.debug(`Match ${i} not bound: ${getErrorMessage(error)}`);
|
|
475
|
+
return false;
|
|
476
|
+
}
|
|
279
477
|
}
|
|
280
478
|
/**
|
|
281
479
|
* Whether an error is about the bound node: the placeholder only appears in
|
|
@@ -291,19 +489,28 @@ function boundNodeMissing(text) {
|
|
|
291
489
|
* Run an interaction on the element a request targets. Results and errors
|
|
292
490
|
* never show the internal placeholder: a bound node the page scripts could
|
|
293
491
|
* not find is reported as stale (87), and results carry the user's selector.
|
|
492
|
+
* On a page that replaced the selector search, a result warns that the
|
|
493
|
+
* element was found in bdg's world; on a page that replaced built-ins the
|
|
494
|
+
* scripts use, a failure adds that they may be the cause, and a script that
|
|
495
|
+
* threw is reported as broken by the page (90), naming them.
|
|
294
496
|
*
|
|
295
497
|
* @param cdp - CDP connection
|
|
296
498
|
* @param params - Request with a selector (and optional index) or a backend node id
|
|
297
499
|
* @param work - The interaction, given the script target
|
|
298
500
|
* @returns The interaction's result
|
|
299
|
-
* @throws CommandError (87) when the bound node left the page during the
|
|
501
|
+
* @throws CommandError (87) when the bound node left the page during the
|
|
502
|
+
* action, (90) when its script threw on a page that replaced built-ins
|
|
300
503
|
*/
|
|
301
504
|
export async function onScriptTarget(cdp, params, work) {
|
|
302
505
|
const err = staleNodeError();
|
|
303
506
|
let target;
|
|
304
507
|
try {
|
|
305
508
|
target = await resolveScriptTarget(cdp, params);
|
|
306
|
-
const
|
|
509
|
+
const replaced = target.replacedBuiltins ?? [];
|
|
510
|
+
const result = withReplacedBuiltins(withUserSelector(await work(target), params.selector), target.boundInBdgWorld ? replaced : []);
|
|
511
|
+
if (result.error && replaced.length > 0) {
|
|
512
|
+
return { ...result, suggestion: withBrokenHint(result.suggestion, replaced) };
|
|
513
|
+
}
|
|
307
514
|
if (!boundNodeMissing(result.error))
|
|
308
515
|
return result;
|
|
309
516
|
return {
|
|
@@ -314,6 +521,16 @@ export async function onScriptTarget(cdp, params, work) {
|
|
|
314
521
|
};
|
|
315
522
|
}
|
|
316
523
|
catch (error) {
|
|
524
|
+
const replaced = target?.replacedBuiltins ?? [];
|
|
525
|
+
if (error instanceof ActionScriptError) {
|
|
526
|
+
throw actionScriptFailure(error, replaced, params.selector ?? '');
|
|
527
|
+
}
|
|
528
|
+
if (error instanceof CommandError && replaced.length > 0) {
|
|
529
|
+
const suggestion = error.metadata['suggestion'];
|
|
530
|
+
throw new CommandError(error.message, {
|
|
531
|
+
suggestion: withBrokenHint(typeof suggestion === 'string' ? suggestion : undefined, replaced),
|
|
532
|
+
}, error.exitCode);
|
|
533
|
+
}
|
|
317
534
|
if (!(error instanceof CommandError) || !boundNodeMissing(error.message))
|
|
318
535
|
throw error;
|
|
319
536
|
throw new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.STALE_CACHE);
|
|
@@ -323,6 +540,24 @@ export async function onScriptTarget(cdp, params, work) {
|
|
|
323
540
|
unbindInFrame(target.cdp);
|
|
324
541
|
}
|
|
325
542
|
}
|
|
543
|
+
/**
|
|
544
|
+
* The error for an action script that threw: on a page that replaced
|
|
545
|
+
* built-ins the scripts use, that the page broke it (90, naming them);
|
|
546
|
+
* otherwise what it threw (110), with the user's selector.
|
|
547
|
+
*
|
|
548
|
+
* @param error - What the action reported
|
|
549
|
+
* @param replaced - Built-ins the page replaced
|
|
550
|
+
* @param selector - Selector the user gave (or the cached query's selector)
|
|
551
|
+
* @returns Error to throw
|
|
552
|
+
*/
|
|
553
|
+
function actionScriptFailure(error, replaced, selector) {
|
|
554
|
+
if (replaced.length > 0) {
|
|
555
|
+
const err = actionBrokenByPageError(error.action, error.exception, replaced);
|
|
556
|
+
return new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.RESOURCE_CONFLICT);
|
|
557
|
+
}
|
|
558
|
+
const err = actionScriptFailedError(error.action, error.exception, selector);
|
|
559
|
+
return new CommandError(err.message, { suggestion: err.suggestion }, EXIT_CODES.SOFTWARE_ERROR);
|
|
560
|
+
}
|
|
326
561
|
/**
|
|
327
562
|
* Remove a node bound in a cross-origin frame from that frame's window
|
|
328
563
|
* (through the scoped connection, so the script runs where the bind ran; the
|
|
@@ -336,6 +571,38 @@ function unbindInFrame(frameConnection) {
|
|
|
336
571
|
.send('Runtime.evaluate', { expression: UNBIND_TARGET_SCRIPT })
|
|
337
572
|
.catch((error) => log.debug(`Frame target not unbound: ${getErrorMessage(error)}`));
|
|
338
573
|
}
|
|
574
|
+
/**
|
|
575
|
+
* A failed action's suggestion with the hint that the page's replaced
|
|
576
|
+
* built-ins may have broken it.
|
|
577
|
+
*
|
|
578
|
+
* @param suggestion - The action's own suggestion
|
|
579
|
+
* @param replaced - Built-ins the page replaced
|
|
580
|
+
* @returns Both, the action's first
|
|
581
|
+
*/
|
|
582
|
+
function withBrokenHint(suggestion, replaced) {
|
|
583
|
+
const hint = brokenByReplacedBuiltinsSuggestion(replaced);
|
|
584
|
+
return suggestion ? `${suggestion}. ${hint}` : hint;
|
|
585
|
+
}
|
|
586
|
+
/**
|
|
587
|
+
* Warn that the page replaced built-ins the page scripts use: they run in
|
|
588
|
+
* the page's world, so the action may misbehave (the element itself was
|
|
589
|
+
* found in bdg's world). The warning names the first few; `replacedBuiltins`
|
|
590
|
+
* lists them all.
|
|
591
|
+
*
|
|
592
|
+
* @param result - Script result
|
|
593
|
+
* @param replaced - Built-ins the page replaced
|
|
594
|
+
* @returns Result with the warning added to any it has
|
|
595
|
+
*/
|
|
596
|
+
function withReplacedBuiltins(result, replaced) {
|
|
597
|
+
if (!replaced || replaced.length === 0)
|
|
598
|
+
return result;
|
|
599
|
+
const warning = replacedBuiltinsWarning(replaced);
|
|
600
|
+
return {
|
|
601
|
+
...result,
|
|
602
|
+
warning: result.warning ? `${result.warning}; ${warning}` : warning,
|
|
603
|
+
replacedBuiltins: replaced,
|
|
604
|
+
};
|
|
605
|
+
}
|
|
339
606
|
/**
|
|
340
607
|
* Report the user's selector instead of the internal placeholder.
|
|
341
608
|
*
|
package/dist/runtime/dom/wait.js
CHANGED
|
@@ -15,6 +15,7 @@ import { invalidSelectorError, waitTimeoutError } from '../../errors/messages.js
|
|
|
15
15
|
import { isContextLostError } from './evalHelpers.js';
|
|
16
16
|
import { DEEP_QUERY_JS, FILTER_MATCHING_JS, selectorArgsJS } from './targetNode.js';
|
|
17
17
|
import { isWaitConditionMet, needsGoneConfirmation, normalizeWaitText, } from './waitCondition.js';
|
|
18
|
+
import { evaluateInBdgWorld } from '../page/bdgWorld.js';
|
|
18
19
|
import { createLogger } from '../../ui/logging/index.js';
|
|
19
20
|
import { delay, raceTimeout } from '../../utils/async.js';
|
|
20
21
|
import { getErrorMessage } from '../../utils/errors.js';
|
|
@@ -157,7 +158,7 @@ async function nextSnapshot(cdp, condition, previous, deadline) {
|
|
|
157
158
|
const selectorArgs = condition.selector === undefined ? 'null, null' : selectorArgsJS(condition.selector);
|
|
158
159
|
const text = condition.text === undefined ? null : normalizeWaitText(condition.text);
|
|
159
160
|
const args = `${selectorArgs}, ${JSON.stringify(text)}, ${JSON.stringify(previous)}, ${Math.min(remaining, WAIT_SLICE_MS)}`;
|
|
160
|
-
const evaluated = cdp
|
|
161
|
+
const evaluated = evaluateInBdgWorld(cdp, {
|
|
161
162
|
expression: `(${WAIT_SNAPSHOT_JS})(${args})`,
|
|
162
163
|
awaitPromise: true,
|
|
163
164
|
returnByValue: true,
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* bdg's own JavaScript world in the page (a CDP isolated world): it shares
|
|
3
|
+
* the DOM with the page but has its own built-ins, so a page that replaces
|
|
4
|
+
* `Element.prototype.querySelectorAll`, `JSON.stringify` or
|
|
5
|
+
* `Array.prototype.map` (polyfills, old frameworks, anti-bot scripts) cannot
|
|
6
|
+
* change what bdg's page scripts find or return.
|
|
7
|
+
*
|
|
8
|
+
* Only the entry points need the world: `Runtime.evaluate` runs in it with
|
|
9
|
+
* its `contextId`, and `DOM.resolveNode` hands out objects of it with its
|
|
10
|
+
* `executionContextId`; `Runtime.callFunctionOn` on such an object runs in
|
|
11
|
+
* the same world. The world belongs to the top frame (same-origin iframes are
|
|
12
|
+
* reached through it); a node of another frame, a connection without the
|
|
13
|
+
* Page domain (a frame-scoped one) or a failure to create it fall back to the
|
|
14
|
+
* main world, as before.
|
|
15
|
+
*/
|
|
16
|
+
import type { CDPConnection } from '../../connection/cdp.js';
|
|
17
|
+
import type { Protocol } from '../../connection/typed-cdp.js';
|
|
18
|
+
/**
|
|
19
|
+
* What the world needs of a connection: commands, and events to forget the
|
|
20
|
+
* world when the page navigates (a sender without events, such as a test
|
|
21
|
+
* double, runs scripts in the main world)
|
|
22
|
+
*/
|
|
23
|
+
type PageConnection = Pick<CDPConnection, 'send'> & Partial<Pick<CDPConnection, 'on'>>;
|
|
24
|
+
/** Name of bdg's isolated world (shown in DevTools' context selector) */
|
|
25
|
+
export declare const BDG_WORLD_NAME = "bdg";
|
|
26
|
+
/**
|
|
27
|
+
* `Runtime.evaluate` in bdg's world of the top frame. A call that names its
|
|
28
|
+
* own context is sent as it is.
|
|
29
|
+
*
|
|
30
|
+
* @param cdp - Connection to the page
|
|
31
|
+
* @param params - Evaluate parameters
|
|
32
|
+
* @returns Evaluate response
|
|
33
|
+
*/
|
|
34
|
+
export declare function evaluateInBdgWorld(cdp: PageConnection, params: Protocol.Runtime.EvaluateRequest): Promise<Protocol.Runtime.EvaluateResponse>;
|
|
35
|
+
/**
|
|
36
|
+
* `DOM.resolveNode` into bdg's world of the top frame, so functions called on
|
|
37
|
+
* the node run there. A node the world cannot use (one of a cross-origin
|
|
38
|
+
* frame, or one no longer in the page) is resolved in its own frame's main
|
|
39
|
+
* world, as before.
|
|
40
|
+
*
|
|
41
|
+
* @param cdp - Connection to the page
|
|
42
|
+
* @param params - Resolve parameters
|
|
43
|
+
* @returns Resolve response
|
|
44
|
+
*/
|
|
45
|
+
export declare function resolveNodeInBdgWorld(cdp: PageConnection, params: Protocol.DOM.ResolveNodeRequest): Promise<Protocol.DOM.ResolveNodeResponse>;
|
|
46
|
+
/**
|
|
47
|
+
* Send a CDP command for bdg's own scripts: `Runtime.evaluate` and
|
|
48
|
+
* `DOM.resolveNode` go to bdg's world, other methods as they are.
|
|
49
|
+
*
|
|
50
|
+
* @param cdp - Connection to the page
|
|
51
|
+
* @param method - CDP method
|
|
52
|
+
* @param params - Its parameters
|
|
53
|
+
* @returns The command's result
|
|
54
|
+
*/
|
|
55
|
+
export declare function sendForBdgScript(cdp: PageConnection, method: string, params: Record<string, unknown>): Promise<unknown>;
|
|
56
|
+
export {};
|
|
57
|
+
//# sourceMappingURL=bdgWorld.d.ts.map
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* bdg's own JavaScript world in the page (a CDP isolated world): it shares
|
|
3
|
+
* the DOM with the page but has its own built-ins, so a page that replaces
|
|
4
|
+
* `Element.prototype.querySelectorAll`, `JSON.stringify` or
|
|
5
|
+
* `Array.prototype.map` (polyfills, old frameworks, anti-bot scripts) cannot
|
|
6
|
+
* change what bdg's page scripts find or return.
|
|
7
|
+
*
|
|
8
|
+
* Only the entry points need the world: `Runtime.evaluate` runs in it with
|
|
9
|
+
* its `contextId`, and `DOM.resolveNode` hands out objects of it with its
|
|
10
|
+
* `executionContextId`; `Runtime.callFunctionOn` on such an object runs in
|
|
11
|
+
* the same world. The world belongs to the top frame (same-origin iframes are
|
|
12
|
+
* reached through it); a node of another frame, a connection without the
|
|
13
|
+
* Page domain (a frame-scoped one) or a failure to create it fall back to the
|
|
14
|
+
* main world, as before.
|
|
15
|
+
*/
|
|
16
|
+
import { createLogger } from '../../ui/logging/index.js';
|
|
17
|
+
import { getErrorMessage } from '../../utils/errors.js';
|
|
18
|
+
const log = createLogger('cdp');
|
|
19
|
+
/** Name of bdg's isolated world (shown in DevTools' context selector) */
|
|
20
|
+
export const BDG_WORLD_NAME = 'bdg';
|
|
21
|
+
/** Errors of a context that is gone (navigation, reload) */
|
|
22
|
+
const CONTEXT_GONE = /Cannot find context with specified id|Execution context was destroyed/;
|
|
23
|
+
/** The world's context id per connection, until the page navigates */
|
|
24
|
+
const worlds = new WeakMap();
|
|
25
|
+
/**
|
|
26
|
+
* The execution context id of bdg's world in the top frame, created on first
|
|
27
|
+
* use and forgotten when the top frame navigates or its contexts are cleared.
|
|
28
|
+
*
|
|
29
|
+
* @param cdp - Connection to the page
|
|
30
|
+
* @returns Context id, or null when no world can be made on this connection
|
|
31
|
+
*/
|
|
32
|
+
function worldContext(cdp) {
|
|
33
|
+
const known = worlds.get(cdp);
|
|
34
|
+
if (known)
|
|
35
|
+
return known;
|
|
36
|
+
const created = createWorld(cdp);
|
|
37
|
+
worlds.set(cdp, created);
|
|
38
|
+
return created;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Create the world in the top frame and forget it when the page changes.
|
|
42
|
+
*
|
|
43
|
+
* @param cdp - Connection to the page
|
|
44
|
+
* @returns Context id, or null when it cannot be created
|
|
45
|
+
*/
|
|
46
|
+
async function createWorld(cdp) {
|
|
47
|
+
const { on } = cdp;
|
|
48
|
+
if (!on)
|
|
49
|
+
return null;
|
|
50
|
+
try {
|
|
51
|
+
const { frameTree } = (await cdp.send('Page.getFrameTree'));
|
|
52
|
+
const { executionContextId } = (await cdp.send('Page.createIsolatedWorld', {
|
|
53
|
+
frameId: frameTree.frame.id,
|
|
54
|
+
worldName: BDG_WORLD_NAME,
|
|
55
|
+
grantUniveralAccess: false,
|
|
56
|
+
}));
|
|
57
|
+
const forget = () => {
|
|
58
|
+
if (worlds.get(cdp) === created)
|
|
59
|
+
worlds.delete(cdp);
|
|
60
|
+
stopNavigated();
|
|
61
|
+
stopCleared();
|
|
62
|
+
};
|
|
63
|
+
const created = worlds.get(cdp);
|
|
64
|
+
const stopNavigated = on.call(cdp, 'Page.frameNavigated', (params) => {
|
|
65
|
+
if (params.frame.parentId === undefined)
|
|
66
|
+
forget();
|
|
67
|
+
});
|
|
68
|
+
const stopCleared = on.call(cdp, 'Runtime.executionContextsCleared', forget);
|
|
69
|
+
return executionContextId;
|
|
70
|
+
}
|
|
71
|
+
catch (error) {
|
|
72
|
+
log.debug(`bdg's isolated world not available: ${getErrorMessage(error)}`);
|
|
73
|
+
worlds.delete(cdp);
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Send a command with the world's context, once more with a new world when
|
|
79
|
+
* the context is gone, and in the main world when there is none.
|
|
80
|
+
*
|
|
81
|
+
* @param cdp - Connection to the page
|
|
82
|
+
* @param method - CDP method
|
|
83
|
+
* @param params - Its parameters
|
|
84
|
+
* @param withContext - The parameters with the world's context id
|
|
85
|
+
* @returns The command's result
|
|
86
|
+
*/
|
|
87
|
+
async function sendInWorld(cdp, method, params, withContext) {
|
|
88
|
+
for (let attempt = 0; attempt < 2; attempt++) {
|
|
89
|
+
const contextId = await worldContext(cdp);
|
|
90
|
+
if (contextId === null)
|
|
91
|
+
break;
|
|
92
|
+
try {
|
|
93
|
+
return await cdp.send(method, withContext(contextId));
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
if (!CONTEXT_GONE.test(getErrorMessage(error)))
|
|
97
|
+
throw error;
|
|
98
|
+
worlds.delete(cdp);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
return cdp.send(method, params);
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* `Runtime.evaluate` in bdg's world of the top frame. A call that names its
|
|
105
|
+
* own context is sent as it is.
|
|
106
|
+
*
|
|
107
|
+
* @param cdp - Connection to the page
|
|
108
|
+
* @param params - Evaluate parameters
|
|
109
|
+
* @returns Evaluate response
|
|
110
|
+
*/
|
|
111
|
+
export async function evaluateInBdgWorld(cdp, params) {
|
|
112
|
+
if (params.contextId !== undefined || params.uniqueContextId !== undefined) {
|
|
113
|
+
return (await cdp.send('Runtime.evaluate', { ...params }));
|
|
114
|
+
}
|
|
115
|
+
return (await sendInWorld(cdp, 'Runtime.evaluate', { ...params }, (contextId) => ({
|
|
116
|
+
...params,
|
|
117
|
+
contextId,
|
|
118
|
+
})));
|
|
119
|
+
}
|
|
120
|
+
/**
|
|
121
|
+
* `DOM.resolveNode` into bdg's world of the top frame, so functions called on
|
|
122
|
+
* the node run there. A node the world cannot use (one of a cross-origin
|
|
123
|
+
* frame, or one no longer in the page) is resolved in its own frame's main
|
|
124
|
+
* world, as before.
|
|
125
|
+
*
|
|
126
|
+
* @param cdp - Connection to the page
|
|
127
|
+
* @param params - Resolve parameters
|
|
128
|
+
* @returns Resolve response
|
|
129
|
+
*/
|
|
130
|
+
export async function resolveNodeInBdgWorld(cdp, params) {
|
|
131
|
+
if (params.executionContextId !== undefined) {
|
|
132
|
+
return (await cdp.send('DOM.resolveNode', { ...params }));
|
|
133
|
+
}
|
|
134
|
+
try {
|
|
135
|
+
const resolved = (await sendInWorld(cdp, 'DOM.resolveNode', { ...params }, (executionContextId) => ({ ...params, executionContextId })));
|
|
136
|
+
if (await usableInWorld(cdp, resolved.object.objectId))
|
|
137
|
+
return resolved;
|
|
138
|
+
}
|
|
139
|
+
catch (error) {
|
|
140
|
+
log.debug(`Node resolved in the page's world: ${getErrorMessage(error)}`);
|
|
141
|
+
}
|
|
142
|
+
return (await cdp.send('DOM.resolveNode', { ...params }));
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Whether a node resolved into bdg's world can be used there: a node of a
|
|
146
|
+
* cross-origin frame resolves, but every access to it throws.
|
|
147
|
+
*
|
|
148
|
+
* @param cdp - Connection to the page
|
|
149
|
+
* @param objectId - The node in bdg's world
|
|
150
|
+
* @returns True when it can be read
|
|
151
|
+
*/
|
|
152
|
+
async function usableInWorld(cdp, objectId) {
|
|
153
|
+
if (!objectId)
|
|
154
|
+
return false;
|
|
155
|
+
const probe = (await cdp.send('Runtime.callFunctionOn', {
|
|
156
|
+
objectId,
|
|
157
|
+
functionDeclaration: 'function () { try { return this.isConnected === true; } catch (e) { return false; } }',
|
|
158
|
+
returnByValue: true,
|
|
159
|
+
}));
|
|
160
|
+
return probe.result.value === true;
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Send a CDP command for bdg's own scripts: `Runtime.evaluate` and
|
|
164
|
+
* `DOM.resolveNode` go to bdg's world, other methods as they are.
|
|
165
|
+
*
|
|
166
|
+
* @param cdp - Connection to the page
|
|
167
|
+
* @param method - CDP method
|
|
168
|
+
* @param params - Its parameters
|
|
169
|
+
* @returns The command's result
|
|
170
|
+
*/
|
|
171
|
+
export function sendForBdgScript(cdp, method, params) {
|
|
172
|
+
if (method === 'Runtime.evaluate') {
|
|
173
|
+
return evaluateInBdgWorld(cdp, params);
|
|
174
|
+
}
|
|
175
|
+
if (method === 'DOM.resolveNode') {
|
|
176
|
+
return resolveNodeInBdgWorld(cdp, params);
|
|
177
|
+
}
|
|
178
|
+
return cdp.send(method, params);
|
|
179
|
+
}
|
|
180
|
+
//# sourceMappingURL=bdgWorld.js.map
|