browser-debugger-cli 0.14.0 → 0.15.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 (69) hide show
  1. package/.claude/skills/bdg/SKILL.md +1 -1
  2. package/dist/commands/cdp.js +1 -0
  3. package/dist/commands/cleanup.js +3 -0
  4. package/dist/commands/dom/eval.d.ts +2 -1
  5. package/dist/commands/dom/eval.js +6 -21
  6. package/dist/commands/dom/helpers/evalResult.d.ts +36 -0
  7. package/dist/commands/dom/helpers/evalResult.js +59 -0
  8. package/dist/commands/helpJson.d.ts +1 -1
  9. package/dist/commands/helpJson.js +3 -3
  10. package/dist/commands/helpTopic.js +10 -4
  11. package/dist/commands/network/har.js +18 -14
  12. package/dist/commands/optionBehaviors.js +8 -3
  13. package/dist/commands/shared/optionTypes.d.ts +1 -0
  14. package/dist/commands/shared/outputFile.d.ts +2 -1
  15. package/dist/commands/shared/outputFile.js +7 -4
  16. package/dist/commands/status.js +3 -1
  17. package/dist/commands/stop.js +2 -1
  18. package/dist/connection/launcher/flagsBuilder.d.ts +46 -0
  19. package/dist/connection/launcher/flagsBuilder.js +107 -23
  20. package/dist/connection/launcher.d.ts +1 -1
  21. package/dist/connection/launcher.js +1 -2
  22. package/dist/constants.d.ts +2 -4
  23. package/dist/constants.js +2 -4
  24. package/dist/daemon/launcher.d.ts +17 -3
  25. package/dist/daemon/launcher.js +37 -7
  26. package/dist/daemon/session/commandRegistry.js +2 -2
  27. package/dist/daemon.js +8107 -7962
  28. package/dist/errors/messages.d.ts +23 -0
  29. package/dist/errors/messages.js +86 -6
  30. package/dist/index.js +555 -166
  31. package/dist/ipc/client.d.ts +6 -1
  32. package/dist/ipc/client.js +11 -2
  33. package/dist/ipc/protocol/commands.d.ts +4 -0
  34. package/dist/ipc/transport/index.d.ts +6 -0
  35. package/dist/ipc/transport/index.js +16 -1
  36. package/dist/runtime/dom/elementInfo.d.ts +7 -0
  37. package/dist/runtime/dom/elementInfo.js +8 -1
  38. package/dist/runtime/dom/evalHelpers.d.ts +24 -4
  39. package/dist/runtime/dom/evalHelpers.js +40 -12
  40. package/dist/runtime/dom/frames.d.ts +2 -1
  41. package/dist/runtime/dom/frames.js +3 -1
  42. package/dist/runtime/page/emulation.js +6 -5
  43. package/dist/runtime/page/userAgent.d.ts +86 -2
  44. package/dist/runtime/page/userAgent.js +154 -33
  45. package/dist/session/paths.d.ts +38 -3
  46. package/dist/session/paths.js +154 -7
  47. package/dist/session/portClaims.d.ts +0 -8
  48. package/dist/session/portClaims.js +1 -22
  49. package/dist/session/sessionList.d.ts +5 -1
  50. package/dist/session/sessionList.js +5 -1
  51. package/dist/telemetry/har/builder.d.ts +12 -1
  52. package/dist/telemetry/har/builder.js +10 -2
  53. package/dist/telemetry/har/sanitize.d.ts +24 -0
  54. package/dist/telemetry/har/sanitize.js +138 -0
  55. package/dist/telemetry/har/sanitizeBody.d.ts +38 -0
  56. package/dist/telemetry/har/sanitizeBody.js +168 -0
  57. package/dist/ui/formatters/sessions.d.ts +3 -2
  58. package/dist/ui/formatters/sessions.js +10 -3
  59. package/dist/ui/messages/chrome.d.ts +14 -6
  60. package/dist/ui/messages/chrome.js +52 -12
  61. package/dist/ui/messages/networkMessages.d.ts +26 -0
  62. package/dist/ui/messages/networkMessages.js +21 -0
  63. package/dist/ui/messages/session.d.ts +8 -0
  64. package/dist/ui/messages/session.js +10 -0
  65. package/dist/utils/atomicFile.d.ts +2 -1
  66. package/dist/utils/atomicFile.js +5 -2
  67. package/dist/utils/directories.d.ts +41 -0
  68. package/dist/utils/directories.js +48 -0
  69. package/package.json +1 -1
@@ -68,6 +68,103 @@ export function isDocker() {
68
68
  return false;
69
69
  }
70
70
  }
71
+ const DISABLE_FEATURES = '--disable-features=';
72
+ const ENABLE_FEATURES = '--enable-features=';
73
+ /**
74
+ * Flags that bdg merges into one: Chrome reads only the last occurrence of
75
+ * these comma-separated feature lists, and chrome-launcher's defaults, bdg and
76
+ * users all pass them. `--disable-blink-features`/`--enable-blink-features`
77
+ * are not merged (neither bdg nor the defaults pass them), nor is `--js-flags`
78
+ * (space-separated V8 flags, not a feature list).
79
+ */
80
+ const FEATURE_LIST_FLAGS = [DISABLE_FEATURES, ENABLE_FEATURES];
81
+ /**
82
+ * Bare name of a feature-list entry, without a `<Trial` or `:params` suffix.
83
+ *
84
+ * @param entry - Entry such as `Foo<Trial` or `Foo:param/1`
85
+ * @returns Feature name
86
+ */
87
+ function featureName(entry) {
88
+ return entry.split(/[<:]/)[0] ?? entry;
89
+ }
90
+ /**
91
+ * Remove disabled features that are also enabled.
92
+ *
93
+ * Chrome lets `--disable-features` win over `--enable-features`, so without
94
+ * this a user's `--enable-features=MediaRouter` could not re-enable a feature
95
+ * chrome-launcher disables by default.
96
+ *
97
+ * @param features - Merged values per feature-list flag (changed in place)
98
+ */
99
+ function dropReEnabledFeatures(features) {
100
+ const enabled = new Set([...(features.get(ENABLE_FEATURES) ?? [])].map(featureName));
101
+ const disabled = features.get(DISABLE_FEATURES);
102
+ disabled?.forEach((entry) => {
103
+ if (enabled.has(featureName(entry)))
104
+ disabled.delete(entry);
105
+ });
106
+ }
107
+ /**
108
+ * Merge every `--disable-features=` (and `--enable-features=`) flag into one
109
+ * and drop repeated flags.
110
+ *
111
+ * Chrome applies only the last occurrence of a feature-list flag, so bdg's or
112
+ * the user's list would otherwise switch chrome-launcher's defaults back on.
113
+ * The merged flag keeps the first one's position; its values keep their order
114
+ * without repeats, and an enabled feature is never also disabled. Only
115
+ * entries starting with `-` are deduplicated, so positional arguments such as
116
+ * URLs are passed as given.
117
+ *
118
+ * @param flags - Chrome flags in launch order
119
+ * @returns Flags with at most one flag per feature list and no repeated flags
120
+ */
121
+ function mergeFeatureFlags(flags) {
122
+ const features = new Map();
123
+ const merged = [];
124
+ for (const flag of flags) {
125
+ const prefix = FEATURE_LIST_FLAGS.find((p) => flag.startsWith(p));
126
+ if (prefix) {
127
+ if (!features.has(prefix))
128
+ merged.push(prefix);
129
+ const values = flag.slice(prefix.length).split(',').filter(Boolean);
130
+ features.set(prefix, new Set([...(features.get(prefix) ?? []), ...values]));
131
+ }
132
+ else if (!flag.startsWith('-') || !merged.includes(flag)) {
133
+ merged.push(flag);
134
+ }
135
+ }
136
+ dropReEnabledFeatures(features);
137
+ return merged.flatMap((flag) => {
138
+ const values = features.get(flag);
139
+ if (!values)
140
+ return [flag];
141
+ return values.size > 0 ? [flag + [...values].join(',')] : [];
142
+ });
143
+ }
144
+ /**
145
+ * Remove `HEADLESS` from this process's environment.
146
+ *
147
+ * chrome-launcher adds a bare `--headless` whenever the launching process has
148
+ * a non-empty `HEADLESS` variable (even `0` or `false`), whatever its
149
+ * `envVars` option says, which would make `--no-headless` launch headless.
150
+ * The daemon calls this before it launches Chrome.
151
+ *
152
+ * @param env - Environment to clean (defaults to `process.env`)
153
+ */
154
+ export function dropLauncherHeadlessEnv(env = process.env) {
155
+ delete env['HEADLESS'];
156
+ }
157
+ /**
158
+ * chrome-launcher's default flags (plus its Linux sandbox flag). bdg passes
159
+ * them itself and tells chrome-launcher to skip its own copy, so each flag
160
+ * appears once; chrome-launcher adds `--remote-debugging-port`.
161
+ *
162
+ * @returns Default flags
163
+ */
164
+ function defaultFlags() {
165
+ const flags = chromeLauncher.Launcher.defaultFlags();
166
+ return process.platform === 'linux' ? [...flags, '--disable-setuid-sandbox'] : flags;
167
+ }
71
168
  /**
72
169
  * Build Chrome flags array from launch options.
73
170
  *
@@ -80,6 +177,8 @@ export function isDocker() {
80
177
  *
81
178
  * Custom flags are passed via the chromeFlags option. The BDG_CHROME_FLAGS env var
82
179
  * is parsed by the CLI and merged into chromeFlags before reaching this function.
180
+ * Feature lists from all sources end up in one `--disable-features` (and one
181
+ * `--enable-features`) flag, and each flag appears once.
83
182
  *
84
183
  * @param options - Launch options containing flag preferences
85
184
  * @returns Array of Chrome command-line flags
@@ -101,17 +200,6 @@ export function isDocker() {
101
200
  * // Includes --disable-gpu, --no-sandbox if in Docker
102
201
  * ```
103
202
  */
104
- /**
105
- * chrome-launcher's default flags (plus its Linux sandbox flag). bdg passes
106
- * them itself and tells chrome-launcher to skip its own copy, so each flag
107
- * appears once; chrome-launcher adds `--remote-debugging-port`.
108
- *
109
- * @returns Default flags
110
- */
111
- function defaultFlags() {
112
- const flags = chromeLauncher.Launcher.defaultFlags();
113
- return process.platform === 'linux' ? [...flags, '--disable-setuid-sandbox'] : flags;
114
- }
115
203
  export function buildChromeFlags(options) {
116
204
  const baseFlags = options.ignoreDefaultFlags ? [] : defaultFlags();
117
205
  const bdgFlags = [
@@ -120,18 +208,14 @@ export function buildChromeFlags(options) {
120
208
  ];
121
209
  const dockerFlags = isDocker() ? DOCKER_CHROME_FLAGS : [];
122
210
  const sandboxFlags = needsNoSandbox() ? ['--no-sandbox'] : [];
123
- // Custom flags from CLI option (env var BDG_CHROME_FLAGS is parsed by CLI and passed here)
124
211
  const customFlags = options.chromeFlags ?? [];
125
- if (options.headless) {
126
- return [
127
- HEADLESS_FLAG,
128
- ...baseFlags,
129
- ...bdgFlags,
130
- ...dockerFlags,
131
- ...sandboxFlags,
132
- ...customFlags,
133
- ];
134
- }
135
- return [...baseFlags, ...bdgFlags, ...dockerFlags, ...sandboxFlags, ...customFlags];
212
+ return mergeFeatureFlags([
213
+ ...(options.headless ? [HEADLESS_FLAG] : []),
214
+ ...baseFlags,
215
+ ...bdgFlags,
216
+ ...dockerFlags,
217
+ ...sandboxFlags,
218
+ ...customFlags,
219
+ ]);
136
220
  }
137
221
  //# sourceMappingURL=flagsBuilder.js.map
@@ -4,7 +4,7 @@ import type { Options as ChromeLaunchOptions } from 'chrome-launcher';
4
4
  * Options that control how Chrome is launched for CDP sessions.
5
5
  * Extended to support chrome-launcher advanced features.
6
6
  */
7
- export interface LaunchOptions extends Pick<ChromeLaunchOptions, 'logLevel' | 'connectionPollInterval' | 'maxConnectionRetries' | 'portStrictMode' | 'envVars' | 'handleSIGINT' | 'ignoreDefaultFlags' | 'chromeFlags' | 'chromePath'> {
7
+ export interface LaunchOptions extends Pick<ChromeLaunchOptions, 'logLevel' | 'connectionPollInterval' | 'maxConnectionRetries' | 'portStrictMode' | 'envVars' | 'ignoreDefaultFlags' | 'chromeFlags' | 'chromePath'> {
8
8
  /** Remote debugging port (defaults to 9222 when omitted) */
9
9
  port?: number;
10
10
  /**
@@ -2,7 +2,7 @@ import * as fs from 'fs';
2
2
  import * as os from 'os';
3
3
  import * as path from 'path';
4
4
  import * as chromeLauncher from 'chrome-launcher';
5
- import { BDG_CHROME_PREFS, DEFAULT_CDP_PORT, CHROME_PROFILE_DIR, DEFAULT_CHROME_LOG_LEVEL, DEFAULT_CHROME_HANDLE_SIGINT, } from '../constants.js';
5
+ import { BDG_CHROME_PREFS, DEFAULT_CDP_PORT, CHROME_PROFILE_DIR, DEFAULT_CHROME_LOG_LEVEL, } from '../constants.js';
6
6
  import { makeDirectory } from '../utils/directories.js';
7
7
  import { getErrorMessage } from '../utils/errors.js';
8
8
  import { filterDefined } from '../utils/objects.js';
@@ -224,7 +224,6 @@ function buildChromeOptions(options) {
224
224
  const chromePathOverride = resolveChromeBinary(options);
225
225
  return {
226
226
  logLevel: options.logLevel ?? DEFAULT_CHROME_LOG_LEVEL,
227
- handleSIGINT: options.handleSIGINT ?? DEFAULT_CHROME_HANDLE_SIGINT,
228
227
  ignoreDefaultFlags: true,
229
228
  chromeFlags: buildChromeFlags(options),
230
229
  userDataDir,
@@ -12,10 +12,6 @@ export declare const DEFAULT_CDP_PORT = 9222;
12
12
  * Default Chrome launcher log level for quiet operation
13
13
  */
14
14
  export declare const DEFAULT_CHROME_LOG_LEVEL = "silent";
15
- /**
16
- * Default SIGINT handling - bdg handles signals, not chrome-launcher
17
- */
18
- export declare const DEFAULT_CHROME_HANDLE_SIGINT = false;
19
15
  /**
20
16
  * Persistent Chrome profile directory path (relative to user home)
21
17
  */
@@ -124,6 +120,8 @@ export declare const CHROME_NETWORK_BUFFER_PER_RESOURCE: number;
124
120
  export declare const CHROME_POST_DATA_LIMIT: number;
125
121
  /** Matches `dom query` and `dom a11y query` list with `--json` and no `--limit` */
126
122
  export declare const QUERY_JSON_LIST_LIMIT = 100;
123
+ /** Elements of an array result `dom eval --json` lists (the rest are counted as omitted) */
124
+ export declare const EVAL_JSON_ARRAY_LIMIT = 100;
127
125
  /** Matches `dom layout` measures per command (the rest are counted as omitted) */
128
126
  export declare const LAYOUT_ELEMENT_LIMIT = 100;
129
127
  /**
package/dist/constants.js CHANGED
@@ -15,10 +15,6 @@ export const DEFAULT_CDP_PORT = 9222;
15
15
  * Default Chrome launcher log level for quiet operation
16
16
  */
17
17
  export const DEFAULT_CHROME_LOG_LEVEL = 'silent';
18
- /**
19
- * Default SIGINT handling - bdg handles signals, not chrome-launcher
20
- */
21
- export const DEFAULT_CHROME_HANDLE_SIGINT = false;
22
18
  /**
23
19
  * Persistent Chrome profile directory path (relative to user home)
24
20
  */
@@ -165,6 +161,8 @@ export const CHROME_POST_DATA_LIMIT = 1 * 1024 * 1024; // 1MB
165
161
  // ============================================================================
166
162
  /** Matches `dom query` and `dom a11y query` list with `--json` and no `--limit` */
167
163
  export const QUERY_JSON_LIST_LIMIT = 100;
164
+ /** Elements of an array result `dom eval --json` lists (the rest are counted as omitted) */
165
+ export const EVAL_JSON_ARRAY_LIMIT = 100;
168
166
  /** Matches `dom layout` measures per command (the rest are counted as omitted) */
169
167
  export const LAYOUT_ELEMENT_LIMIT = 100;
170
168
  /**
@@ -16,6 +16,8 @@ export interface SpawnedDaemon {
16
16
  * Ensure a daemon is running, spawning one if needed.
17
17
  *
18
18
  * @returns The daemon it spawned, or undefined when one was already running
19
+ * @throws SessionDirError if the session directory is unusable or untrusted
20
+ * (checked first, so a socket planted there is never taken for a daemon)
19
21
  * @throws DaemonStartupError if the daemon script is missing or the daemon
20
22
  * does not accept connections in time
21
23
  */
@@ -24,9 +26,21 @@ export declare function launchDaemon(): Promise<SpawnedDaemon | undefined>;
24
26
  * Check that the session directory can hold the daemon's files before
25
27
  * spawning it (otherwise the daemon dies and only its log says why).
26
28
  *
27
- * @throws SessionDirError (103) for a file or a path that cannot hold a
28
- * directory (a pseudo-filesystem like `/proc`), (81) for a too-long path
29
- * like a named session's, (82) when not writable
29
+ * @throws SessionDirError (103) for a file, a path that cannot hold a
30
+ * directory (a pseudo-filesystem like `/proc`) or an untrusted directory
31
+ * (see {@link secureSessionDir}, which also tightens the user's own 0755
32
+ * directories to 0700), (81) for a too-long path like a named session's,
33
+ * (82) when not writable
30
34
  */
31
35
  export declare function assertUsableSessionDir(): void;
36
+ /**
37
+ * Open the daemon log for the daemon's output, creating it 0600. A symlink in
38
+ * its place is refused, not followed: it would append the log to the file it
39
+ * points to.
40
+ *
41
+ * @param logPath - Daemon log path
42
+ * @returns File descriptor
43
+ * @throws SessionDirError (103) when it cannot be opened (`ELOOP` for a symlink)
44
+ */
45
+ export declare function openDaemonLog(logPath: string): number;
32
46
  //# sourceMappingURL=launcher.d.ts.map
@@ -10,10 +10,10 @@ import { spawn } from 'child_process';
10
10
  import fs from 'fs';
11
11
  import { join } from 'path';
12
12
  import { DaemonStartupError, SessionDirError } from './errors.js';
13
- import { sessionDirIsFileError, sessionDirNotWritableError, socketPathTooLongError, } from '../errors/messages.js';
13
+ import { daemonLogNotOpenedError, sessionDirIsFileError, sessionDirNotWritableError, socketPathTooLongError, untrustedSessionDirError, } from '../errors/messages.js';
14
14
  import { killSessionChromes, readLiveDaemonPid } from '../session/cleanup/staleSession.js';
15
15
  import { isDaemonAlive, isDaemonSocketGone } from '../session/daemonSocket.js';
16
- import { MAX_DAEMON_SOCKET_PATH_BYTES, ensureSessionDir, getSessionDir, getSessionFilePath, } from '../session/paths.js';
16
+ import { MAX_DAEMON_SOCKET_PATH_BYTES, ensureSessionDir, getSessionDir, getSessionFilePath, secureSessionDir, } from '../session/paths.js';
17
17
  import { createLogger } from '../ui/logging/index.js';
18
18
  import { delay } from '../utils/async.js';
19
19
  import { directoryProblem } from '../utils/directories.js';
@@ -28,10 +28,13 @@ const DAEMON_READY_POLL_MS = 20;
28
28
  * Ensure a daemon is running, spawning one if needed.
29
29
  *
30
30
  * @returns The daemon it spawned, or undefined when one was already running
31
+ * @throws SessionDirError if the session directory is unusable or untrusted
32
+ * (checked first, so a socket planted there is never taken for a daemon)
31
33
  * @throws DaemonStartupError if the daemon script is missing or the daemon
32
34
  * does not accept connections in time
33
35
  */
34
36
  export async function launchDaemon() {
37
+ assertUsableSessionDir();
35
38
  if (await isDaemonAlive()) {
36
39
  log.debug('Daemon already running');
37
40
  return undefined;
@@ -39,11 +42,10 @@ export async function launchDaemon() {
39
42
  if (!fs.existsSync(DAEMON_SCRIPT_PATH)) {
40
43
  throw new DaemonStartupError(`Daemon script not found at ${DAEMON_SCRIPT_PATH}. Did you run 'npm run build'?`, 'DAEMON_SCRIPT_NOT_FOUND');
41
44
  }
42
- assertUsableSessionDir();
43
45
  await stopUnreachableDaemon();
44
46
  const logPath = join(getSessionDir(), 'daemon.log');
45
47
  rotateLog(logPath);
46
- const logFd = fs.openSync(logPath, 'a');
48
+ const logFd = openDaemonLog(logPath);
47
49
  log.debug(`Starting daemon: ${DAEMON_SCRIPT_PATH}`);
48
50
  const daemon = spawn(process.execPath, [DAEMON_SCRIPT_PATH], {
49
51
  detached: true,
@@ -97,9 +99,11 @@ async function stopUnreachableDaemon() {
97
99
  * Check that the session directory can hold the daemon's files before
98
100
  * spawning it (otherwise the daemon dies and only its log says why).
99
101
  *
100
- * @throws SessionDirError (103) for a file or a path that cannot hold a
101
- * directory (a pseudo-filesystem like `/proc`), (81) for a too-long path
102
- * like a named session's, (82) when not writable
102
+ * @throws SessionDirError (103) for a file, a path that cannot hold a
103
+ * directory (a pseudo-filesystem like `/proc`) or an untrusted directory
104
+ * (see {@link secureSessionDir}, which also tightens the user's own 0755
105
+ * directories to 0700), (81) for a too-long path like a named session's,
106
+ * (82) when not writable
103
107
  */
104
108
  export function assertUsableSessionDir() {
105
109
  const dir = getSessionDir();
@@ -116,6 +120,9 @@ export function assertUsableSessionDir() {
116
120
  if (problem) {
117
121
  fail(sessionDirNotWritableError(dir, problem.reason), problem.denied ? EXIT_CODES.PERMISSION_DENIED : EXIT_CODES.SESSION_FILE_ERROR);
118
122
  }
123
+ const untrusted = secureSessionDir(dir);
124
+ if (untrusted)
125
+ fail(untrustedSessionDirError(untrusted));
119
126
  try {
120
127
  ensureSessionDir();
121
128
  fs.accessSync(dir, fs.constants.W_OK);
@@ -126,6 +133,29 @@ export function assertUsableSessionDir() {
126
133
  fail(sessionDirNotWritableError(dir, code ?? getErrorMessage(error)), denied ? EXIT_CODES.PERMISSION_DENIED : EXIT_CODES.SESSION_FILE_ERROR);
127
134
  }
128
135
  }
136
+ /** Flags opening the daemon log for appending, refusing (not following) a symlink */
137
+ const DAEMON_LOG_FLAGS = fs.constants.O_WRONLY |
138
+ fs.constants.O_APPEND |
139
+ fs.constants.O_CREAT |
140
+ (fs.constants.O_NOFOLLOW ?? 0);
141
+ /**
142
+ * Open the daemon log for the daemon's output, creating it 0600. A symlink in
143
+ * its place is refused, not followed: it would append the log to the file it
144
+ * points to.
145
+ *
146
+ * @param logPath - Daemon log path
147
+ * @returns File descriptor
148
+ * @throws SessionDirError (103) when it cannot be opened (`ELOOP` for a symlink)
149
+ */
150
+ export function openDaemonLog(logPath) {
151
+ try {
152
+ return fs.openSync(logPath, DAEMON_LOG_FLAGS, 0o600);
153
+ }
154
+ catch (error) {
155
+ const err = daemonLogNotOpenedError(logPath, error.code ?? getErrorMessage(error));
156
+ throw new SessionDirError(err.message, err.suggestion);
157
+ }
158
+ }
129
159
  /** Size above which the daemon log is rotated when a daemon starts */
130
160
  const MAX_LOG_BYTES = 5 * 1024 * 1024;
131
161
  /**
@@ -452,8 +452,8 @@ export function createCommandRegistry(store, emulation) {
452
452
  return { result, ...(hint !== undefined && { hint }) };
453
453
  },
454
454
  dom_eval: async (cdp, params) => params.frame === undefined
455
- ? evaluateScript(cdp, params.script)
456
- : evaluateInFrame(cdp, pageWebSocketUrl(store), params.script, params.frame, listedFrameIds),
455
+ ? evaluateScript(cdp, params.script, { full: params.full ?? false })
456
+ : evaluateInFrame(cdp, pageWebSocketUrl(store), params.script, params.frame, listedFrameIds, params.full ?? false),
457
457
  dom_frames: async (cdp) => {
458
458
  const { frames, frameIds } = await listFrames(cdp, pageWebSocketUrl(store));
459
459
  listedFrameIds = frameIds;