browser-debugger-cli 0.8.0 → 0.10.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (304) hide show
  1. package/README.md +7 -1
  2. package/dist/cdp/schema.d.ts +4 -1
  3. package/dist/cdp/schema.js +48 -7
  4. package/dist/commands/cdp.js +3 -2
  5. package/dist/commands/cleanup.d.ts +11 -0
  6. package/dist/commands/cleanup.js +161 -57
  7. package/dist/commands/console.d.ts +20 -1
  8. package/dist/commands/console.js +57 -17
  9. package/dist/commands/details.js +3 -2
  10. package/dist/commands/dom/DomElementResolver.d.ts +10 -3
  11. package/dist/commands/dom/DomElementResolver.js +35 -17
  12. package/dist/commands/dom/a11y.d.ts +10 -0
  13. package/dist/commands/dom/a11y.js +29 -6
  14. package/dist/commands/dom/eval.d.ts +3 -1
  15. package/dist/commands/dom/eval.js +29 -4
  16. package/dist/commands/dom/form.js +16 -62
  17. package/dist/commands/dom/formInteraction.js +189 -119
  18. package/dist/commands/dom/formSummary.d.ts +49 -0
  19. package/dist/commands/dom/formSummary.js +180 -0
  20. package/dist/commands/dom/frames.d.ts +2 -1
  21. package/dist/commands/dom/frames.js +17 -2
  22. package/dist/commands/dom/get.d.ts +6 -5
  23. package/dist/commands/dom/get.js +92 -82
  24. package/dist/commands/dom/helpers/index.d.ts +1 -1
  25. package/dist/commands/dom/helpers/index.js +1 -1
  26. package/dist/commands/dom/helpers/keyAttributes.d.ts +20 -0
  27. package/dist/commands/dom/helpers/keyAttributes.js +54 -0
  28. package/dist/commands/dom/helpers/query.d.ts +44 -17
  29. package/dist/commands/dom/helpers/query.js +300 -106
  30. package/dist/commands/dom/helpers/runElementCommand.d.ts +10 -2
  31. package/dist/commands/dom/helpers/runElementCommand.js +98 -30
  32. package/dist/commands/dom/helpers/screenshot.d.ts +4 -1
  33. package/dist/commands/dom/helpers/screenshot.js +239 -51
  34. package/dist/commands/dom/index.d.ts +4 -1
  35. package/dist/commands/dom/index.js +22 -7
  36. package/dist/commands/dom/inspect.d.ts +15 -0
  37. package/dist/commands/dom/inspect.js +82 -0
  38. package/dist/commands/dom/layout.d.ts +14 -0
  39. package/dist/commands/dom/layout.js +54 -0
  40. package/dist/commands/dom/listeners.d.ts +5 -1
  41. package/dist/commands/dom/listeners.js +15 -5
  42. package/dist/commands/dom/query.js +2 -3
  43. package/dist/commands/dom/screenshot.d.ts +12 -2
  44. package/dist/commands/dom/screenshot.js +27 -3
  45. package/dist/commands/dom/semanticUtils.d.ts +16 -10
  46. package/dist/commands/dom/semanticUtils.js +53 -16
  47. package/dist/commands/dom/wait.d.ts +13 -0
  48. package/dist/commands/dom/wait.js +83 -0
  49. package/dist/commands/helpJson.js +2 -2
  50. package/dist/commands/network/list.js +17 -13
  51. package/dist/commands/optionBehaviors.js +154 -21
  52. package/dist/commands/page.d.ts +3 -2
  53. package/dist/commands/page.js +100 -5
  54. package/dist/commands/peek.js +4 -11
  55. package/dist/commands/sessions.d.ts +8 -0
  56. package/dist/commands/sessions.js +19 -0
  57. package/dist/commands/shared/CommandRunner.js +4 -4
  58. package/dist/commands/shared/commonOptions.d.ts +4 -0
  59. package/dist/commands/shared/commonOptions.js +9 -0
  60. package/dist/commands/shared/dataFetcher.js +2 -2
  61. package/dist/commands/shared/followMode.d.ts +21 -1
  62. package/dist/commands/shared/followMode.js +29 -2
  63. package/dist/commands/shared/handleValidationError.d.ts +2 -2
  64. package/dist/commands/shared/handleValidationError.js +12 -3
  65. package/dist/commands/shared/optionTypes.d.ts +61 -5
  66. package/dist/commands/shared/startHelpers.d.ts +66 -0
  67. package/dist/commands/shared/startHelpers.js +103 -13
  68. package/dist/commands/shared/validation.d.ts +14 -2
  69. package/dist/commands/shared/validation.js +20 -3
  70. package/dist/commands/start.d.ts +63 -0
  71. package/dist/commands/start.js +115 -15
  72. package/dist/commands/status.js +29 -7
  73. package/dist/commands/stop.js +7 -6
  74. package/dist/commands/tail.js +4 -11
  75. package/dist/commands/types.d.ts +2 -0
  76. package/dist/commands.js +2 -0
  77. package/dist/connection/chromeIdentity.d.ts +65 -0
  78. package/dist/connection/chromeIdentity.js +143 -0
  79. package/dist/connection/launcher/profilePreferences.d.ts +47 -0
  80. package/dist/connection/launcher/profilePreferences.js +151 -0
  81. package/dist/connection/launcher.d.ts +21 -2
  82. package/dist/connection/launcher.js +42 -16
  83. package/dist/connection/portReservation.d.ts +14 -4
  84. package/dist/connection/portReservation.js +21 -6
  85. package/dist/connection/startupExit.d.ts +8 -0
  86. package/dist/connection/startupExit.js +15 -6
  87. package/dist/constants.d.ts +6 -2
  88. package/dist/constants.js +9 -2
  89. package/dist/daemon/SessionController.js +23 -7
  90. package/dist/daemon/errors.d.ts +1 -1
  91. package/dist/daemon/errors.js +1 -1
  92. package/dist/daemon/launcher.d.ts +10 -2
  93. package/dist/daemon/launcher.js +8 -7
  94. package/dist/daemon/server/SocketServer.js +1 -2
  95. package/dist/daemon/session/Session.d.ts +20 -0
  96. package/dist/daemon/session/Session.js +80 -9
  97. package/dist/daemon/session/chromeConnection.d.ts +9 -0
  98. package/dist/daemon/session/chromeConnection.js +45 -8
  99. package/dist/daemon/session/commandRegistry.d.ts +14 -1
  100. package/dist/daemon/session/commandRegistry.js +113 -67
  101. package/dist/daemon/session/interactions.d.ts +48 -9
  102. package/dist/daemon/session/interactions.js +46 -9
  103. package/dist/daemon/session/triggeredRequests.d.ts +67 -0
  104. package/dist/daemon/session/triggeredRequests.js +157 -0
  105. package/dist/daemon/session/types.d.ts +5 -1
  106. package/dist/daemon.js +10630 -3601
  107. package/dist/errors/messages.d.ts +456 -24
  108. package/dist/errors/messages.js +862 -67
  109. package/dist/index.js +6915 -3401
  110. package/dist/ipc/client.d.ts +21 -1
  111. package/dist/ipc/client.js +35 -3
  112. package/dist/ipc/protocol/commands.d.ts +145 -5
  113. package/dist/ipc/protocol/commands.js +4 -0
  114. package/dist/ipc/protocol/domTypes.d.ts +291 -7
  115. package/dist/ipc/protocol/inspectTypes.d.ts +388 -0
  116. package/dist/ipc/protocol/inspectTypes.js +10 -0
  117. package/dist/ipc/session/lifecycle.d.ts +8 -1
  118. package/dist/ipc/session/queries.d.ts +5 -1
  119. package/dist/ipc/session/types.d.ts +5 -0
  120. package/dist/ipc/transport/index.d.ts +2 -1
  121. package/dist/ipc/transport/index.js +2 -2
  122. package/dist/runtime/dom/actionEffects.d.ts +185 -0
  123. package/dist/runtime/dom/actionEffects.js +402 -0
  124. package/dist/runtime/dom/actionEffectsScripts.d.ts +90 -0
  125. package/dist/runtime/dom/actionEffectsScripts.js +426 -0
  126. package/dist/runtime/dom/elementGeometry.d.ts +170 -0
  127. package/dist/runtime/dom/elementGeometry.js +553 -0
  128. package/dist/runtime/dom/elementInfo.d.ts +103 -0
  129. package/dist/runtime/dom/elementInfo.js +256 -0
  130. package/dist/runtime/dom/evalHelpers.d.ts +51 -6
  131. package/dist/runtime/dom/evalHelpers.js +136 -26
  132. package/dist/runtime/dom/eventListeners.d.ts +2 -1
  133. package/dist/runtime/dom/eventListeners.js +184 -47
  134. package/dist/runtime/dom/formDiscovery.d.ts +1 -1
  135. package/dist/runtime/dom/formDiscovery.js +116 -16
  136. package/dist/runtime/dom/formFillHelpers/fill.d.ts +9 -0
  137. package/dist/runtime/dom/formFillHelpers/fill.js +178 -14
  138. package/dist/runtime/dom/formFillHelpers/index.d.ts +2 -2
  139. package/dist/runtime/dom/formFillHelpers/index.js +2 -2
  140. package/dist/runtime/dom/formFillHelpers/pressKey.js +14 -3
  141. package/dist/runtime/dom/formFillHelpers/scroll.d.ts +3 -0
  142. package/dist/runtime/dom/formFillHelpers/scroll.js +60 -18
  143. package/dist/runtime/dom/formFillHelpers/shared.d.ts +17 -0
  144. package/dist/runtime/dom/formFillHelpers/shared.js +25 -1
  145. package/dist/runtime/dom/formFillHelpers/stability.d.ts +20 -6
  146. package/dist/runtime/dom/formFillHelpers/stability.js +50 -19
  147. package/dist/runtime/dom/formSubmitHelpers.d.ts +3 -0
  148. package/dist/runtime/dom/formSubmitHelpers.js +89 -15
  149. package/dist/runtime/dom/frameLayout.d.ts +60 -0
  150. package/dist/runtime/dom/frameLayout.js +140 -0
  151. package/dist/runtime/dom/frameOrigin.d.ts +50 -0
  152. package/dist/runtime/dom/frameOrigin.js +62 -0
  153. package/dist/runtime/dom/frameScopedConnection.d.ts +92 -0
  154. package/dist/runtime/dom/frameScopedConnection.js +252 -0
  155. package/dist/runtime/dom/frameSelection.d.ts +12 -1
  156. package/dist/runtime/dom/frameSelection.js +22 -3
  157. package/dist/runtime/dom/frames.d.ts +61 -5
  158. package/dist/runtime/dom/frames.js +329 -75
  159. package/dist/runtime/dom/inspect.d.ts +28 -0
  160. package/dist/runtime/dom/inspect.js +557 -0
  161. package/dist/runtime/dom/inspectAllStyles.d.ts +62 -0
  162. package/dist/runtime/dom/inspectAllStyles.js +385 -0
  163. package/dist/runtime/dom/inspectCascade.d.ts +94 -0
  164. package/dist/runtime/dom/inspectCascade.js +371 -0
  165. package/dist/runtime/dom/inspectCascadeModel.d.ts +39 -0
  166. package/dist/runtime/dom/inspectCascadeModel.js +232 -0
  167. package/dist/runtime/dom/inspectHints.d.ts +62 -0
  168. package/dist/runtime/dom/inspectHints.js +305 -0
  169. package/dist/runtime/dom/inspectLayoutModel.d.ts +87 -0
  170. package/dist/runtime/dom/inspectLayoutModel.js +346 -0
  171. package/dist/runtime/dom/inspectModel.d.ts +74 -0
  172. package/dist/runtime/dom/inspectModel.js +184 -0
  173. package/dist/runtime/dom/inspectPaintModel.d.ts +157 -0
  174. package/dist/runtime/dom/inspectPaintModel.js +461 -0
  175. package/dist/runtime/dom/inspectRules.d.ts +37 -0
  176. package/dist/runtime/dom/inspectRules.js +101 -0
  177. package/dist/runtime/dom/inspectScripts.d.ts +132 -0
  178. package/dist/runtime/dom/inspectScripts.js +263 -0
  179. package/dist/runtime/dom/inspectTree.d.ts +40 -0
  180. package/dist/runtime/dom/inspectTree.js +134 -0
  181. package/dist/runtime/dom/inspectVariables.d.ts +33 -0
  182. package/dist/runtime/dom/inspectVariables.js +94 -0
  183. package/dist/runtime/dom/inspectWhyModel.d.ts +20 -0
  184. package/dist/runtime/dom/inspectWhyModel.js +134 -0
  185. package/dist/runtime/dom/layout.d.ts +71 -0
  186. package/dist/runtime/dom/layout.js +340 -0
  187. package/dist/runtime/dom/listenerPageScripts.d.ts +72 -0
  188. package/dist/runtime/dom/listenerPageScripts.js +365 -0
  189. package/dist/runtime/dom/listenerSummary.d.ts +136 -11
  190. package/dist/runtime/dom/listenerSummary.js +361 -22
  191. package/dist/runtime/dom/pageActivity.d.ts +41 -0
  192. package/dist/runtime/dom/pageActivity.js +123 -0
  193. package/dist/runtime/dom/reactEventHelpers.d.ts +63 -2
  194. package/dist/runtime/dom/reactEventHelpers.js +220 -41
  195. package/dist/runtime/dom/targetNode.d.ts +80 -27
  196. package/dist/runtime/dom/targetNode.js +249 -33
  197. package/dist/runtime/dom/wait.d.ts +25 -0
  198. package/dist/runtime/dom/wait.js +199 -0
  199. package/dist/runtime/dom/waitCondition.d.ts +71 -0
  200. package/dist/runtime/dom/waitCondition.js +75 -0
  201. package/dist/runtime/page/emulation.d.ts +71 -0
  202. package/dist/runtime/page/emulation.js +117 -0
  203. package/dist/runtime/page/loadingState.d.ts +36 -0
  204. package/dist/runtime/page/loadingState.js +86 -0
  205. package/dist/runtime/page/navigation.d.ts +46 -2
  206. package/dist/runtime/page/navigation.js +69 -33
  207. package/dist/session/QueryCacheManager.d.ts +11 -1
  208. package/dist/session/QueryCacheManager.js +25 -3
  209. package/dist/session/chromeOwners.d.ts +34 -0
  210. package/dist/session/chromeOwners.js +51 -0
  211. package/dist/session/cleanup/staleSession.d.ts +11 -1
  212. package/dist/session/cleanup/staleSession.js +17 -6
  213. package/dist/session/cleanup/userCommands.js +2 -4
  214. package/dist/session/metadata.d.ts +5 -1
  215. package/dist/session/metadata.js +2 -1
  216. package/dist/session/paths.d.ts +77 -3
  217. package/dist/session/paths.js +111 -5
  218. package/dist/session/port.d.ts +31 -7
  219. package/dist/session/port.js +50 -43
  220. package/dist/session/portClaims.d.ts +66 -0
  221. package/dist/session/portClaims.js +284 -0
  222. package/dist/session/sessionList.d.ts +58 -0
  223. package/dist/session/sessionList.js +199 -0
  224. package/dist/session/sessionName.d.ts +46 -0
  225. package/dist/session/sessionName.js +97 -0
  226. package/dist/telemetry/a11y.d.ts +18 -3
  227. package/dist/telemetry/a11y.js +170 -29
  228. package/dist/telemetry/console.d.ts +1 -0
  229. package/dist/telemetry/console.js +100 -5
  230. package/dist/telemetry/network.js +3 -1
  231. package/dist/telemetry/requestKinds.d.ts +32 -0
  232. package/dist/telemetry/requestKinds.js +61 -0
  233. package/dist/telemetry/requestState.d.ts +31 -0
  234. package/dist/telemetry/requestState.js +38 -0
  235. package/dist/types.d.ts +112 -3
  236. package/dist/ui/formatters/a11y.js +3 -0
  237. package/dist/ui/formatters/console/chronological.d.ts +8 -0
  238. package/dist/ui/formatters/console/chronological.js +17 -4
  239. package/dist/ui/formatters/console/json.js +3 -4
  240. package/dist/ui/formatters/console/shared.d.ts +12 -0
  241. package/dist/ui/formatters/console.d.ts +2 -2
  242. package/dist/ui/formatters/console.js +1 -1
  243. package/dist/ui/formatters/details.d.ts +8 -0
  244. package/dist/ui/formatters/details.js +61 -4
  245. package/dist/ui/formatters/dom.d.ts +27 -14
  246. package/dist/ui/formatters/dom.js +88 -59
  247. package/dist/ui/formatters/form.js +29 -18
  248. package/dist/ui/formatters/inspect.d.ts +39 -0
  249. package/dist/ui/formatters/inspect.js +596 -0
  250. package/dist/ui/formatters/keyAttributes.d.ts +19 -0
  251. package/dist/ui/formatters/keyAttributes.js +84 -0
  252. package/dist/ui/formatters/layout.d.ts +31 -0
  253. package/dist/ui/formatters/layout.js +53 -0
  254. package/dist/ui/formatters/listeners.d.ts +3 -2
  255. package/dist/ui/formatters/listeners.js +73 -9
  256. package/dist/ui/formatters/networkHeaders.d.ts +13 -0
  257. package/dist/ui/formatters/networkHeaders.js +36 -3
  258. package/dist/ui/formatters/networkList.d.ts +29 -1
  259. package/dist/ui/formatters/networkList.js +86 -20
  260. package/dist/ui/formatters/preview.js +2 -1
  261. package/dist/ui/formatters/requestStatus.d.ts +1 -17
  262. package/dist/ui/formatters/requestStatus.js +2 -30
  263. package/dist/ui/formatters/sessions.d.ts +12 -0
  264. package/dist/ui/formatters/sessions.js +40 -0
  265. package/dist/ui/formatters/status.d.ts +21 -2
  266. package/dist/ui/formatters/status.js +47 -10
  267. package/dist/ui/formatters/triggeredRequests.d.ts +36 -0
  268. package/dist/ui/formatters/triggeredRequests.js +65 -0
  269. package/dist/ui/formatting.d.ts +19 -0
  270. package/dist/ui/formatting.js +31 -36
  271. package/dist/ui/messages/chrome.d.ts +9 -0
  272. package/dist/ui/messages/chrome.js +17 -5
  273. package/dist/ui/messages/commands.d.ts +504 -14
  274. package/dist/ui/messages/commands.js +835 -21
  275. package/dist/ui/messages/consoleMessages.d.ts +10 -0
  276. package/dist/ui/messages/consoleMessages.js +17 -0
  277. package/dist/ui/messages/hints.js +2 -1
  278. package/dist/ui/messages/networkMessages.d.ts +14 -0
  279. package/dist/ui/messages/networkMessages.js +18 -0
  280. package/dist/ui/messages/preview.js +5 -4
  281. package/dist/ui/messages/session.d.ts +30 -21
  282. package/dist/ui/messages/session.js +48 -26
  283. package/dist/ui/messages/sessionCommand.d.ts +43 -0
  284. package/dist/ui/messages/sessionCommand.js +52 -0
  285. package/dist/utils/async.d.ts +17 -0
  286. package/dist/utils/async.js +36 -0
  287. package/dist/utils/color.d.ts +84 -0
  288. package/dist/utils/color.js +376 -0
  289. package/dist/utils/cssValues.d.ts +109 -0
  290. package/dist/utils/cssValues.js +236 -0
  291. package/dist/utils/http.d.ts +22 -1
  292. package/dist/utils/http.js +28 -9
  293. package/dist/utils/selectorFilters.d.ts +48 -8
  294. package/dist/utils/selectorFilters.js +296 -53
  295. package/dist/utils/shellDetection.d.ts +8 -2
  296. package/dist/utils/shellDetection.js +120 -33
  297. package/dist/utils/suggestions.d.ts +26 -0
  298. package/dist/utils/suggestions.js +73 -0
  299. package/dist/utils/taskMappings.js +10 -0
  300. package/dist/utils/url.d.ts +12 -2
  301. package/dist/utils/url.js +69 -7
  302. package/package.json +1 -1
  303. package/dist/ui/formatters/sessionFormatters.d.ts +0 -58
  304. package/dist/ui/formatters/sessionFormatters.js +0 -121
package/dist/constants.js CHANGED
@@ -64,8 +64,12 @@ export const DOCKER_CHROME_FLAGS = [
64
64
  ];
65
65
  /**
66
66
  * BDG-specific Chrome preferences for automation
67
- * These preferences are automatically merged with user preferences when launching Chrome
68
- * User preferences take precedence over these defaults
67
+ * Written into the launched profile's Default/Preferences (dotted names become nested paths)
68
+ * before every launch, so persistent profiles get them too. User preferences take precedence.
69
+ *
70
+ * The password manager and its leak check are off: after a login their bubble
71
+ * (e.g. "Change your password") captures all input in headless Chrome, so
72
+ * later clicks silently never reach the page.
69
73
  *
70
74
  * Note: Crash/restore popup suppression is handled by Chrome flags (--disable-session-crashed-bubble, --disable-infobars)
71
75
  * which are more reliable than preference-based approaches.
@@ -75,6 +79,9 @@ export const BDG_CHROME_PREFS = {
75
79
  'translate.enabled': false, // Disable Google Translate popup
76
80
  translate_site_blacklist: ['*'], // Block translate for all sites
77
81
  'profile.exit_type': 'Normal', // Trick Chrome into thinking it shut down correctly
82
+ credentials_enable_service: false, // No "Save password?" bubble
83
+ 'profile.password_manager_enabled': false, // Password manager off
84
+ 'profile.password_manager_leak_detection': false, // No "Change your password" breach dialog
78
85
  };
79
86
  // ============================================================================
80
87
  // DATA COLLECTION LIMITS
@@ -9,20 +9,36 @@ import { CDPConnectionError, CDPProtocolError, ChromeLaunchError, ConnectionErro
9
9
  import { Session, StartCancelledError } from './session/Session.js';
10
10
  import { detectTargetMismatch } from './session/targetMismatch.js';
11
11
  import { CommandError } from '../errors/index.js';
12
- import { LAUNCHED_CHROME_DESCRIPTION, cdpRequestRejectedError, sessionEndedDuringCommandError, } from '../errors/messages.js';
12
+ import { LAUNCHED_CHROME_DESCRIPTION, cdpRequestRejectedError, sessionAlreadyRunningMessage, sessionEndedDuringCommandError, sessionUnavailableSuggestion, } from '../errors/messages.js';
13
13
  import { IPCErrorCode, } from '../ipc/index.js';
14
14
  import { clearLastSessionEnd, writeLastSessionEnd } from '../session/lastSession.js';
15
15
  import { createLogger } from '../ui/logging/index.js';
16
16
  import { formatChromeIssue } from '../ui/messages/chrome.js';
17
+ import { noActiveSessionMessage } from '../ui/messages/sessionCommand.js';
17
18
  import { getErrorMessage } from '../utils/errors.js';
18
19
  import { EXIT_CODES } from '../utils/exitCodes.js';
19
20
  import { filterDefined } from '../utils/objects.js';
20
21
  const log = createLogger('daemon');
21
- const NO_SESSION_ERROR = 'No active session';
22
22
  /** Error for requests that need the session while `bdg <url>` is still starting it */
23
23
  const STARTING_ERROR = 'The session is still starting';
24
24
  const COMMAND_TIMEOUT_MS = 30000;
25
25
  const QUERY_TIMEOUT_MS = 5000;
26
+ /** Time a `dom wait` gets beyond its own --timeout to report the timeout itself */
27
+ const WAIT_COMMAND_MARGIN_MS = 5000;
28
+ /**
29
+ * How long a session command may run: `dom wait` runs for its own
30
+ * --timeout, everything else gets {@link COMMAND_TIMEOUT_MS}.
31
+ *
32
+ * @param name - Command name
33
+ * @param params - Command parameters
34
+ * @returns Timeout in milliseconds
35
+ */
36
+ function commandTimeoutMs(name, params) {
37
+ if (name !== 'dom_wait')
38
+ return COMMAND_TIMEOUT_MS;
39
+ const { timeout } = params;
40
+ return Math.max(COMMAND_TIMEOUT_MS, (timeout ?? 0) + WAIT_COMMAND_MARGIN_MS);
41
+ }
26
42
  /**
27
43
  * Run a promise with a timeout, clearing the timer either way.
28
44
  *
@@ -139,11 +155,11 @@ export class SessionController {
139
155
  */
140
156
  noSessionError() {
141
157
  if (!this.launching)
142
- return { error: NO_SESSION_ERROR };
158
+ return { error: noActiveSessionMessage() };
143
159
  return {
144
160
  error: STARTING_ERROR,
145
161
  exitCode: EXIT_CODES.RESOURCE_BUSY,
146
- suggestion: 'Wait until "bdg <url>" returns, then retry',
162
+ suggestion: sessionUnavailableSuggestion(EXIT_CODES.RESOURCE_BUSY),
147
163
  };
148
164
  }
149
165
  /**
@@ -270,7 +286,7 @@ export class SessionController {
270
286
  const { sessionId: _sessionId, type: _type, ...params } = request;
271
287
  try {
272
288
  const execute = this.session.execute.bind(this.session);
273
- const data = await withTimeout(execute(name, params), COMMAND_TIMEOUT_MS, 'Command');
289
+ const data = await withTimeout(execute(name, params), commandTimeoutMs(name, params), 'Command');
274
290
  return { ...base, status: 'ok', data };
275
291
  }
276
292
  catch (error) {
@@ -367,7 +383,7 @@ export class SessionController {
367
383
  return {
368
384
  ...base,
369
385
  status: 'error',
370
- message: 'No active session found',
386
+ message: noActiveSessionMessage(),
371
387
  errorCode: IPCErrorCode.NO_SESSION,
372
388
  };
373
389
  }
@@ -430,7 +446,7 @@ export class SessionController {
430
446
  };
431
447
  }
432
448
  return {
433
- message: `Session already running (PID ${process.pid}). Stop it first with: bdg stop`,
449
+ message: sessionAlreadyRunningMessage(process.pid),
434
450
  errorCode: IPCErrorCode.SESSION_ALREADY_RUNNING,
435
451
  existingSession: { ...existingSession, targetUrl },
436
452
  };
@@ -54,7 +54,7 @@ export declare class SessionDirError extends DaemonError {
54
54
  /**
55
55
  * @param message - What is wrong with the directory
56
56
  * @param suggestion - How to fix it
57
- * @param exitCode - 82 for permissions, 103 otherwise
57
+ * @param exitCode - 82 for permissions, 81 for a too-long path, 103 otherwise
58
58
  */
59
59
  constructor(message: string, suggestion: string, exitCode?: number);
60
60
  }
@@ -69,7 +69,7 @@ export class SessionDirError extends DaemonError {
69
69
  /**
70
70
  * @param message - What is wrong with the directory
71
71
  * @param suggestion - How to fix it
72
- * @param exitCode - 82 for permissions, 103 otherwise
72
+ * @param exitCode - 82 for permissions, 81 for a too-long path, 103 otherwise
73
73
  */
74
74
  constructor(message, suggestion, exitCode = EXIT_CODES.SESSION_FILE_ERROR) {
75
75
  super(message, 'SESSION_DIR_UNUSABLE', exitCode);
@@ -6,18 +6,26 @@
6
6
  * SocketServer), so concurrent launches are safe: a losing daemon exits and
7
7
  * the CLI talks to the winner.
8
8
  */
9
+ /** A daemon this process spawned */
10
+ export interface SpawnedDaemon {
11
+ pid: number | undefined;
12
+ /** Whether it has exited (from the child process's exit event) */
13
+ hasExited: () => boolean;
14
+ }
9
15
  /**
10
16
  * Ensure a daemon is running, spawning one if needed.
11
17
  *
18
+ * @returns The daemon it spawned, or undefined when one was already running
12
19
  * @throws DaemonStartupError if the daemon script is missing or the daemon
13
20
  * does not accept connections in time
14
21
  */
15
- export declare function launchDaemon(): Promise<void>;
22
+ export declare function launchDaemon(): Promise<SpawnedDaemon | undefined>;
16
23
  /**
17
24
  * Check that the session directory can hold the daemon's files before
18
25
  * spawning it (otherwise the daemon dies and only its log says why).
19
26
  *
20
- * @throws SessionDirError (103) for a file or a too-long path, (82) when not writable
27
+ * @throws SessionDirError (103) for a file, (81) for a too-long path like a
28
+ * named session's, (82) when not writable
21
29
  */
22
30
  export declare function assertUsableSessionDir(): void;
23
31
  //# sourceMappingURL=launcher.d.ts.map
@@ -12,7 +12,7 @@ import { join } from 'path';
12
12
  import { DaemonStartupError, SessionDirError } from './errors.js';
13
13
  import { sessionDirIsFileError, sessionDirNotWritableError, socketPathTooLongError, } from '../errors/messages.js';
14
14
  import { isDaemonAlive } from '../session/daemonSocket.js';
15
- import { ensureSessionDir, getSessionDir, getSessionFilePath } from '../session/paths.js';
15
+ import { MAX_DAEMON_SOCKET_PATH_BYTES, ensureSessionDir, getSessionDir, getSessionFilePath, } from '../session/paths.js';
16
16
  import { createLogger } from '../ui/logging/index.js';
17
17
  import { delay } from '../utils/async.js';
18
18
  import { getErrorMessage } from '../utils/errors.js';
@@ -24,13 +24,14 @@ const DAEMON_READY_POLL_MS = 20;
24
24
  /**
25
25
  * Ensure a daemon is running, spawning one if needed.
26
26
  *
27
+ * @returns The daemon it spawned, or undefined when one was already running
27
28
  * @throws DaemonStartupError if the daemon script is missing or the daemon
28
29
  * does not accept connections in time
29
30
  */
30
31
  export async function launchDaemon() {
31
32
  if (await isDaemonAlive()) {
32
33
  log.debug('Daemon already running');
33
- return;
34
+ return undefined;
34
35
  }
35
36
  if (!fs.existsSync(DAEMON_SCRIPT_PATH)) {
36
37
  throw new DaemonStartupError(`Daemon script not found at ${DAEMON_SCRIPT_PATH}. Did you run 'npm run build'?`, 'DAEMON_SCRIPT_NOT_FOUND');
@@ -51,14 +52,14 @@ export async function launchDaemon() {
51
52
  });
52
53
  daemon.unref();
53
54
  await waitForDaemonReady(() => exited);
55
+ return { pid: daemon.pid, hasExited: () => exited };
54
56
  }
55
- /** Longest Unix socket path (sun_path is 104 bytes on macOS, 108 on Linux, NUL included) */
56
- const MAX_SOCKET_PATH = process.platform === 'darwin' ? 103 : 107;
57
57
  /**
58
58
  * Check that the session directory can hold the daemon's files before
59
59
  * spawning it (otherwise the daemon dies and only its log says why).
60
60
  *
61
- * @throws SessionDirError (103) for a file or a too-long path, (82) when not writable
61
+ * @throws SessionDirError (103) for a file, (81) for a too-long path like a
62
+ * named session's, (82) when not writable
62
63
  */
63
64
  export function assertUsableSessionDir() {
64
65
  const dir = getSessionDir();
@@ -68,8 +69,8 @@ export function assertUsableSessionDir() {
68
69
  if (fs.existsSync(dir) && !fs.statSync(dir).isDirectory())
69
70
  fail(sessionDirIsFileError(dir));
70
71
  const socketPath = getSessionFilePath('DAEMON_SOCKET');
71
- if (Buffer.byteLength(socketPath) > MAX_SOCKET_PATH) {
72
- fail(socketPathTooLongError(socketPath, MAX_SOCKET_PATH));
72
+ if (Buffer.byteLength(socketPath) > MAX_DAEMON_SOCKET_PATH_BYTES) {
73
+ fail(socketPathTooLongError(socketPath, MAX_DAEMON_SOCKET_PATH_BYTES), EXIT_CODES.INVALID_ARGUMENTS);
73
74
  }
74
75
  try {
75
76
  ensureSessionDir();
@@ -2,11 +2,10 @@ import { linkSync, lstatSync, unlinkSync } from 'fs';
2
2
  import { createServer } from 'net';
3
3
  import { DaemonError } from '../errors.js';
4
4
  import { probeDaemonSocket } from '../../session/daemonSocket.js';
5
+ import { MAX_SOCKET_PATH_BYTES } from '../../session/paths.js';
5
6
  import { createLogger } from '../../ui/logging/index.js';
6
7
  import { getErrorMessage } from '../../utils/errors.js';
7
8
  import { EXIT_CODES } from '../../utils/exitCodes.js';
8
- /** Unix socket path limit (sun_path is 104 bytes on macOS incl. NUL; 108 on Linux). */
9
- const MAX_SOCKET_PATH_BYTES = 103;
10
9
  /** Error code thrown when another daemon already owns the socket path. */
11
10
  export const DAEMON_ALREADY_RUNNING_CODE = 'DAEMON_ALREADY_RUNNING';
12
11
  /**
@@ -6,6 +6,7 @@
6
6
  * separate worker process: the daemon process *is* the session.
7
7
  */
8
8
  import type { CommandName, CommandSchemas } from '../../ipc/index.js';
9
+ import type { PageLoadingState } from '../../ipc/protocol/commands.js';
9
10
  import type { SessionOptions } from '../../ipc/session/lifecycle.js';
10
11
  import type { StatusResponseData } from '../../ipc/session/queries.js';
11
12
  /** Why a session ended. */
@@ -18,6 +19,8 @@ export interface SessionInfo {
18
19
  targetTitle?: string;
19
20
  /** HTTP status of the page's main document, when known */
20
21
  documentStatus?: number;
22
+ /** The page had not finished loading when the readiness wait ended */
23
+ loading?: PageLoadingState;
21
24
  }
22
25
  /** Session metadata as reported in status responses. */
23
26
  export type SessionStatusMetadata = NonNullable<StatusResponseData['sessionMetadata']>;
@@ -50,6 +53,7 @@ export declare class Session {
50
53
  private readonly launchAbort;
51
54
  private started;
52
55
  private documentRequestId;
56
+ private loading;
53
57
  private constructor();
54
58
  /**
55
59
  * Validate the request and create a session that has not been launched yet.
@@ -95,6 +99,13 @@ export declare class Session {
95
99
  * @returns Session metadata
96
100
  */
97
101
  metadata(): SessionStatusMetadata;
102
+ /**
103
+ * Record a page emulation changed mid-session, so screenshots restore it
104
+ * and `bdg status` reports it.
105
+ *
106
+ * @param emulation - Viewport and color scheme now emulated
107
+ */
108
+ private setEmulation;
98
109
  /**
99
110
  * Whether stop() has been called.
100
111
  *
@@ -115,6 +126,15 @@ export declare class Session {
115
126
  * Acquire Chrome, CDP and collectors, checking for a concurrent stop after each step.
116
127
  */
117
128
  private acquireResources;
129
+ /**
130
+ * Choose the port and launch Chrome on it.
131
+ *
132
+ * A Chrome left by this session's crashed daemon is reaped first, so the
133
+ * session gets its remembered port back. If another process took an
134
+ * automatically chosen port before Chrome could listen on it, another port
135
+ * is chosen (the taken one now answers, so it is skipped).
136
+ */
137
+ private launchOwnChrome;
118
138
  /**
119
139
  * End the session after its page connection was lost. A launched Chrome
120
140
  * still running a moment later means the tab was closed (e.g.
@@ -5,6 +5,7 @@
5
5
  * telemetry collectors and executes session commands directly. There is no
6
6
  * separate worker process: the daemon process *is* the session.
7
7
  */
8
+ import { ChromeLaunchError } from '../../connection/errors.js';
8
9
  import { TelemetryStore } from './TelemetryStore.js';
9
10
  import { connectCDP, navigateToTarget } from './cdpSetup.js';
10
11
  import { externalChromePort, findPageTarget, setupChromeConnection, } from './chromeConnection.js';
@@ -12,12 +13,15 @@ import { startTelemetryCollectors } from './collectors.js';
12
13
  import { createCommandRegistry } from './commandRegistry.js';
13
14
  import { teardownSession } from './teardown.js';
14
15
  import { CommandError } from '../../errors/index.js';
16
+ import { applySessionEmulation } from '../../runtime/page/emulation.js';
17
+ import { readPageLoadingState } from '../../runtime/page/loadingState.js';
15
18
  import { reapOrphanedChrome, removeSessionFiles } from '../../session/cleanup/staleSession.js';
16
19
  import { writeSessionMetadata } from '../../session/metadata.js';
17
20
  import { getSessionPort } from '../../session/port.js';
18
21
  import { createLogger } from '../../ui/logging/index.js';
19
22
  import { formatChromeNotice } from '../../ui/messages/chrome.js';
20
23
  import { delay } from '../../utils/async.js';
24
+ import { getErrorMessage } from '../../utils/errors.js';
21
25
  import { EXIT_CODES } from '../../utils/exitCodes.js';
22
26
  import { filterDefined } from '../../utils/objects.js';
23
27
  import { isProcessAlive } from '../../utils/process.js';
@@ -25,6 +29,17 @@ import { validateUrl } from '../../utils/url.js';
25
29
  const log = createLogger('session');
26
30
  /** How long Chrome gets to exit after the page connection drops, before the end is called a closed tab */
27
31
  const CRASH_SETTLE_MS = 500;
32
+ /** Ports tried when automatically chosen ports turn out to be taken */
33
+ const PORT_ATTEMPTS = 3;
34
+ /**
35
+ * Whether a launch failed because another process holds the port.
36
+ *
37
+ * @param error - Launch error
38
+ * @returns True for a PORT_IN_USE launch error
39
+ */
40
+ function isPortConflict(error) {
41
+ return error instanceof ChromeLaunchError && error.issue?.code === 'PORT_IN_USE';
42
+ }
28
43
  /**
29
44
  * A launch that failed because the session was stopped while starting.
30
45
  */
@@ -41,7 +56,10 @@ export class Session {
41
56
  config;
42
57
  onEnded;
43
58
  store = new TelemetryStore();
44
- registry = createCommandRegistry(this.store);
59
+ registry = createCommandRegistry(this.store, {
60
+ get: () => filterDefined({ viewport: this.config.viewport, colorScheme: this.config.colorScheme }),
61
+ set: (emulation) => this.setEmulation(emulation),
62
+ });
45
63
  notify = (notice) => log.info(formatChromeNotice(notice));
46
64
  chrome = null;
47
65
  cdp = null;
@@ -56,6 +74,7 @@ export class Session {
56
74
  launchAbort = new AbortController();
57
75
  started = false;
58
76
  documentRequestId;
77
+ loading;
59
78
  constructor(config, onEnded) {
60
79
  this.config = config;
61
80
  this.onEnded = onEnded;
@@ -134,6 +153,7 @@ export class Session {
134
153
  targetUrl: target?.url ?? this.config.url,
135
154
  ...(target?.title && { targetTitle: target.title }),
136
155
  ...(documentStatus !== undefined && { documentStatus }),
156
+ ...(this.loading && { loading: this.loading }),
137
157
  };
138
158
  }
139
159
  /**
@@ -153,9 +173,29 @@ export class Session {
153
173
  autoStopAt: this.autoStopAt,
154
174
  targetId: target?.id,
155
175
  webSocketDebuggerUrl: target?.webSocketDebuggerUrl,
176
+ viewport: this.config.viewport,
177
+ colorScheme: this.config.colorScheme,
156
178
  }),
157
179
  };
158
180
  }
181
+ /**
182
+ * Record a page emulation changed mid-session, so screenshots restore it
183
+ * and `bdg status` reports it.
184
+ *
185
+ * @param emulation - Viewport and color scheme now emulated
186
+ */
187
+ setEmulation(emulation) {
188
+ const { viewport: _viewport, colorScheme: _colorScheme, ...rest } = this.config;
189
+ this.config = { ...rest, ...emulation };
190
+ if (!this.started)
191
+ return;
192
+ try {
193
+ writeSessionMetadata(this.metadata());
194
+ }
195
+ catch (error) {
196
+ log.debug(`Session metadata not updated: ${getErrorMessage(error)}`);
197
+ }
198
+ }
159
199
  /**
160
200
  * Whether stop() has been called.
161
201
  *
@@ -194,15 +234,14 @@ export class Session {
194
234
  async acquireResources() {
195
235
  this.store.resetSessionStart();
196
236
  const { chromeWsUrl } = this.config;
197
- const port = chromeWsUrl
198
- ? externalChromePort(chromeWsUrl)
199
- : await getSessionPort(this.config.port || undefined);
200
- this.config = { ...this.config, port };
201
- this.throwIfStopping();
202
- if (!this.config.chromeWsUrl) {
203
- await reapOrphanedChrome();
237
+ if (chromeWsUrl) {
238
+ this.config = { ...this.config, port: externalChromePort(chromeWsUrl) };
239
+ this.throwIfStopping();
240
+ this.chrome = await setupChromeConnection(this.config, this.store, log, this.notify);
241
+ }
242
+ else {
243
+ await this.launchOwnChrome();
204
244
  }
205
- this.chrome = await setupChromeConnection(this.config, this.store, log, this.notify);
206
245
  this.throwIfStopping();
207
246
  if (this.chrome) {
208
247
  await findPageTarget(this.config, this.store, log);
@@ -213,10 +252,40 @@ export class Session {
213
252
  signal: this.launchAbort.signal,
214
253
  });
215
254
  this.throwIfStopping();
255
+ await applySessionEmulation(this.cdp, this.config);
256
+ this.throwIfStopping();
216
257
  this.cleanupFunctions = await startTelemetryCollectors(this.cdp, this.config, this.store, log);
217
258
  this.throwIfStopping();
218
259
  this.documentRequestId = await navigateToTarget(this.cdp, this.config, this.store, this.chrome, log);
219
260
  this.throwIfStopping();
261
+ this.loading = await readPageLoadingState(this.cdp, this.store.pendingNetworkRequests.values());
262
+ this.throwIfStopping();
263
+ }
264
+ /**
265
+ * Choose the port and launch Chrome on it.
266
+ *
267
+ * A Chrome left by this session's crashed daemon is reaped first, so the
268
+ * session gets its remembered port back. If another process took an
269
+ * automatically chosen port before Chrome could listen on it, another port
270
+ * is chosen (the taken one now answers, so it is skipped).
271
+ */
272
+ async launchOwnChrome() {
273
+ await reapOrphanedChrome();
274
+ const explicitPort = this.config.port || undefined;
275
+ for (let attempt = 1;; attempt++) {
276
+ this.config = { ...this.config, port: await getSessionPort(explicitPort) };
277
+ this.throwIfStopping();
278
+ try {
279
+ this.chrome = await setupChromeConnection(this.config, this.store, log, this.notify);
280
+ return;
281
+ }
282
+ catch (error) {
283
+ const retry = !explicitPort && attempt < PORT_ATTEMPTS && isPortConflict(error);
284
+ if (!retry)
285
+ throw error;
286
+ log.info(`Port ${this.config.port} was taken by another process; choosing another`);
287
+ }
288
+ }
220
289
  }
221
290
  /**
222
291
  * End the session after its page connection was lost. A launched Chrome
@@ -282,6 +351,8 @@ function buildConfig(url, port, options) {
282
351
  maxBodySize: options.maxBodySize,
283
352
  chromeWsUrl: options.chromeWsUrl,
284
353
  chromeFlags: options.chromeFlags,
354
+ viewport: options.viewport,
355
+ colorScheme: options.colorScheme,
285
356
  }),
286
357
  };
287
358
  }
@@ -22,6 +22,15 @@ export declare function setupChromeConnection(config: SessionConfig, telemetrySt
22
22
  * @returns Port in the URL, or the protocol default
23
23
  */
24
24
  export declare function externalChromePort(wsUrl: string): number;
25
+ /**
26
+ * Chrome flags of a launched Chrome: with `--viewport`, a window of that size
27
+ * first (so tabs the page opens get it too), then the user's own flags, which
28
+ * win over it.
29
+ *
30
+ * @param config - Session configuration
31
+ * @returns Flags, or undefined without any
32
+ */
33
+ export declare function windowSizeFlags(config: SessionConfig): string[] | undefined;
25
34
  /**
26
35
  * Find the page target of a Chrome that bdg launched.
27
36
  *
@@ -9,11 +9,12 @@ import { launchChrome } from '../../connection/launcher.js';
9
9
  import { HTTP_LOCALHOST } from '../../constants.js';
10
10
  import { ConfigError } from '../errors.js';
11
11
  import { CommandError } from '../../errors/index.js';
12
- import { externalBrowserIdMismatchError, externalChromeUnreachableError, externalPageNotFoundError, } from '../../errors/messages.js';
12
+ import { chromeInUseBySessionError, externalBrowserIdMismatchError, externalChromeUnreachableError, externalPageNotFoundError, notDevToolsEndpointError, } from '../../errors/messages.js';
13
13
  import { writeChromePid } from '../../session/chrome.js';
14
+ import { findConflictingOwner } from '../../session/chromeOwners.js';
14
15
  import { getSessionDir } from '../../session/paths.js';
15
16
  import { EXIT_CODES } from '../../utils/exitCodes.js';
16
- import { createPageTarget, fetchBrowserWsUrl, fetchCDPTargets } from '../../utils/http.js';
17
+ import { createPageTarget, fetchCDPTargets, probeDevToolsEndpoint } from '../../utils/http.js';
17
18
  import { filterDefined } from '../../utils/objects.js';
18
19
  /**
19
20
  * Setup Chrome connection - either launch new instance or connect to existing.
@@ -71,23 +72,30 @@ async function resolveExternalTarget(wsUrl, config, log) {
71
72
  const { hostname, pathname, protocol, port } = new URL(wsUrl);
72
73
  const http = { host: hostname, secure: protocol === 'wss:' };
73
74
  const endpoint = `${http.secure ? 'https' : 'http'}://${hostname}:${config.port}`;
74
- const browserWsUrl = await fetchBrowserWsUrl(config.port, log, http);
75
- if (!browserWsUrl)
76
- fail(externalChromeUnreachableError(endpoint, http.secure), EXIT_CODES.CDP_CONNECTION_FAILURE);
75
+ const probe = await probeDevToolsEndpoint(config.port, log, http);
76
+ if (probe.kind !== 'devtools') {
77
+ const err = probe.kind === 'not-devtools'
78
+ ? notDevToolsEndpointError(endpoint)
79
+ : externalChromeUnreachableError(endpoint, http.secure);
80
+ fail(err, EXIT_CODES.CDP_CONNECTION_FAILURE);
81
+ }
82
+ const browserWsUrl = probe.wsUrl;
77
83
  const targets = await fetchCDPTargets(config.port, log, http);
78
84
  const targetId = pathname.split('/').pop() ?? '';
79
85
  if (!pathname.startsWith(BROWSER_WS_PATH)) {
80
86
  const known = targets.find((t) => t.id === targetId);
81
87
  if (!known)
82
88
  fail(externalPageNotFoundError(targetId, endpoint), EXIT_CODES.RESOURCE_NOT_FOUND);
89
+ await assertNotOwned(targets, known.id, endpoint);
83
90
  return { ...known, webSocketDebuggerUrl: wsUrl };
84
91
  }
85
92
  if (new URL(browserWsUrl).pathname !== pathname) {
86
93
  const actual = withEndpoint(browserWsUrl, protocol, hostname, port);
87
94
  fail(externalBrowserIdMismatchError(endpoint, actual), EXIT_CODES.RESOURCE_NOT_FOUND);
88
95
  }
89
- const page = targets.find((t) => t.type === 'page') ??
90
- (await createPageTarget(hostname, config.port, log, http));
96
+ const firstPage = targets.find((t) => t.type === 'page');
97
+ await assertNotOwned(targets, firstPage?.id ?? '', endpoint);
98
+ const page = firstPage ?? (await createPageTarget(hostname, config.port, log, http));
91
99
  if (!page)
92
100
  fail(externalChromeUnreachableError(endpoint, http.secure), EXIT_CODES.CDP_CONNECTION_FAILURE);
93
101
  log.info(`Attaching to page ${page.url} of the external Chrome`);
@@ -96,6 +104,21 @@ async function resolveExternalTarget(wsUrl, config, log) {
96
104
  webSocketDebuggerUrl: withEndpoint(page.webSocketDebuggerUrl, protocol, hostname, port),
97
105
  };
98
106
  }
107
+ /**
108
+ * Refuse to attach when another running bdg session launched this Chrome or
109
+ * drives the tab this session would use: taking it over would mix both
110
+ * sessions' telemetry and let one end the other.
111
+ *
112
+ * @param targets - Every target of the Chrome
113
+ * @param targetId - Tab this session would drive ('' for a tab still to be opened)
114
+ * @param endpoint - e.g. http://127.0.0.1:9222
115
+ * @throws CommandError (90) naming the other session
116
+ */
117
+ async function assertNotOwned(targets, targetId, endpoint) {
118
+ const owner = await findConflictingOwner(targets.map((t) => t.id), targetId);
119
+ if (owner)
120
+ fail(chromeInUseBySessionError(endpoint, owner), EXIT_CODES.RESOURCE_CONFLICT);
121
+ }
99
122
  /**
100
123
  * A DevTools WebSocket URL from Chrome, reached the way the user's URL is
101
124
  * (Chrome reports its own host, e.g. 127.0.0.1, even behind a proxy).
@@ -123,6 +146,20 @@ function withEndpoint(url, protocol, hostname, port) {
123
146
  function fail(err, exitCode) {
124
147
  throw new CommandError(err.message, { suggestion: err.suggestion }, exitCode);
125
148
  }
149
+ /**
150
+ * Chrome flags of a launched Chrome: with `--viewport`, a window of that size
151
+ * first (so tabs the page opens get it too), then the user's own flags, which
152
+ * win over it.
153
+ *
154
+ * @param config - Session configuration
155
+ * @returns Flags, or undefined without any
156
+ */
157
+ export function windowSizeFlags(config) {
158
+ if (!config.viewport)
159
+ return config.chromeFlags;
160
+ const { width, height } = config.viewport;
161
+ return [`--window-size=${width},${height}`, ...(config.chromeFlags ?? [])];
162
+ }
126
163
  /**
127
164
  * Launch a new Chrome instance and record its PID for crash cleanup.
128
165
  */
@@ -134,7 +171,7 @@ async function setupLaunchedChrome(config, log) {
134
171
  ...filterDefined({
135
172
  userDataDir: config.userDataDir,
136
173
  headless: config.headless,
137
- chromeFlags: config.chromeFlags,
174
+ chromeFlags: windowSizeFlags(config),
138
175
  }),
139
176
  });
140
177
  log.info(`Chrome launched (PID ${chrome.pid})`);
@@ -1,10 +1,23 @@
1
1
  import type { TelemetryStore } from './TelemetryStore.js';
2
2
  import type { CDPConnection } from '../../connection/cdp.js';
3
3
  import type { CommandName, CommandSchemas } from '../../ipc/index.js';
4
+ import { type SessionEmulation } from '../../runtime/page/emulation.js';
4
5
  type Handler<K extends CommandName> = (cdp: CDPConnection, params: CommandSchemas[K]['requestSchema']) => Promise<CommandSchemas[K]['responseSchema']>;
5
6
  export type CommandRegistry = {
6
7
  [K in CommandName]: Handler<K>;
7
8
  };
8
- export declare function createCommandRegistry(store: TelemetryStore): CommandRegistry;
9
+ /** The session's page emulation, which `page emulate` reads and changes */
10
+ export interface EmulationState {
11
+ get: () => SessionEmulation;
12
+ set: (emulation: SessionEmulation) => void;
13
+ }
14
+ /**
15
+ * The handlers of the session commands.
16
+ *
17
+ * @param store - Telemetry of the session
18
+ * @param emulation - The session's page emulation
19
+ * @returns Command registry
20
+ */
21
+ export declare function createCommandRegistry(store: TelemetryStore, emulation: EmulationState): CommandRegistry;
9
22
  export {};
10
23
  //# sourceMappingURL=commandRegistry.d.ts.map