@dimina-kit/devtools 0.4.0 → 0.5.0-dev.20260908120530

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 (507) hide show
  1. package/README.md +51 -660
  2. package/dist/main/api.d.ts +2 -1
  3. package/dist/main/app/app.d.ts +41 -12
  4. package/dist/main/app/app.js +299 -514
  5. package/dist/main/app/bootstrap.d.ts +8 -5
  6. package/dist/main/app/bootstrap.js +15 -15
  7. package/dist/main/app/editor-view.d.ts +21 -0
  8. package/dist/main/app/editor-view.js +71 -0
  9. package/dist/main/app/global-mirrors.d.ts +23 -0
  10. package/dist/main/app/global-mirrors.js +37 -0
  11. package/dist/main/app/host-sidebar-default.d.ts +19 -0
  12. package/dist/main/app/host-sidebar-default.js +44 -0
  13. package/dist/main/app/lifecycle.d.ts +12 -1
  14. package/dist/main/app/lifecycle.js +27 -1
  15. package/dist/main/app/menu-setup.d.ts +13 -0
  16. package/dist/main/app/menu-setup.js +49 -0
  17. package/dist/main/app/native-overview.d.ts +1 -1
  18. package/dist/main/app/open-project-ipc.d.ts +13 -0
  19. package/dist/main/app/open-project-ipc.js +21 -0
  20. package/dist/main/app/project-list-surface.d.ts +31 -0
  21. package/dist/main/app/project-list-surface.js +20 -0
  22. package/dist/main/app/project-window.d.ts +36 -0
  23. package/dist/main/app/project-window.js +93 -0
  24. package/dist/main/app/servers.d.ts +17 -0
  25. package/dist/main/app/servers.js +60 -0
  26. package/dist/main/app/ui-extension-targets.d.ts +42 -0
  27. package/dist/main/app/ui-extension-targets.js +101 -0
  28. package/dist/main/app/window-close-reveal.harness.d.ts +34 -0
  29. package/dist/main/app/window-close-reveal.harness.js +65 -0
  30. package/dist/main/app/window-events.d.ts +15 -0
  31. package/dist/main/app/window-events.js +45 -0
  32. package/dist/main/app/window-runtime-services.d.ts +41 -0
  33. package/dist/main/app/window-runtime-services.js +197 -0
  34. package/dist/main/app/workbench-modules.d.ts +24 -0
  35. package/dist/main/app/workbench-modules.js +51 -0
  36. package/dist/main/app/workbench-window.d.ts +85 -0
  37. package/dist/main/app/workbench-window.js +310 -0
  38. package/dist/main/index.bundle.js +11712 -9986
  39. package/dist/main/index.js +49 -1
  40. package/dist/main/ipc/app-lifecycle.d.ts +1 -31
  41. package/dist/main/ipc/app-lifecycle.js +1 -53
  42. package/dist/main/ipc/app.d.ts +4 -1
  43. package/dist/main/ipc/app.js +4 -3
  44. package/dist/main/ipc/bridge-router.d.ts +19 -116
  45. package/dist/main/ipc/bridge-router.js +28 -2128
  46. package/dist/main/ipc/device-picker.d.ts +16 -0
  47. package/dist/main/ipc/device-picker.js +27 -0
  48. package/dist/main/ipc/index.d.ts +5 -0
  49. package/dist/main/ipc/index.js +5 -0
  50. package/dist/main/ipc/internal-devtools.d.ts +16 -0
  51. package/dist/main/ipc/internal-devtools.js +10 -0
  52. package/dist/main/ipc/page-scroll.d.ts +1 -13
  53. package/dist/main/ipc/page-scroll.js +1 -16
  54. package/dist/main/ipc/popover.d.ts +4 -1
  55. package/dist/main/ipc/popover.js +26 -19
  56. package/dist/main/ipc/project-create.d.ts +16 -0
  57. package/dist/main/ipc/project-create.js +27 -0
  58. package/dist/main/ipc/project-fs.d.ts +38 -3
  59. package/dist/main/ipc/project-fs.js +53 -22
  60. package/dist/main/ipc/projects.d.ts +3 -3
  61. package/dist/main/ipc/projects.js +47 -11
  62. package/dist/main/ipc/session-listener-bag.d.ts +1 -40
  63. package/dist/main/ipc/session-listener-bag.js +1 -39
  64. package/dist/main/ipc/session.d.ts +4 -1
  65. package/dist/main/ipc/session.js +26 -26
  66. package/dist/main/ipc/settings.d.ts +4 -1
  67. package/dist/main/ipc/settings.js +17 -19
  68. package/dist/main/ipc/simulator-module.d.ts +9 -3
  69. package/dist/main/ipc/simulator-module.js +13 -10
  70. package/dist/main/ipc/simulator.d.ts +4 -1
  71. package/dist/main/ipc/simulator.js +12 -18
  72. package/dist/main/ipc/tooltip.d.ts +14 -0
  73. package/dist/main/ipc/tooltip.js +29 -0
  74. package/dist/main/ipc/views.d.ts +19 -2
  75. package/dist/main/ipc/views.js +76 -8
  76. package/dist/main/runtime/devtools-backend-before-quit.harness.d.ts +43 -0
  77. package/dist/main/runtime/devtools-backend-before-quit.harness.js +87 -0
  78. package/dist/main/runtime/devtools-backend.js +60 -4
  79. package/dist/main/runtime/devtools-runtime-mock.harness.d.ts +21 -0
  80. package/dist/main/runtime/devtools-runtime-mock.harness.js +313 -0
  81. package/dist/main/runtime/miniapp-runtime.d.ts +91 -22
  82. package/dist/main/services/app-services.d.ts +62 -0
  83. package/dist/main/services/app-services.js +64 -0
  84. package/dist/main/services/automation/connection-target.d.ts +36 -0
  85. package/dist/main/services/automation/connection-target.js +29 -0
  86. package/dist/main/services/automation/console-bridge.d.ts +28 -0
  87. package/dist/main/services/automation/console-bridge.js +91 -0
  88. package/dist/main/services/automation/handlers/app.js +4 -4
  89. package/dist/main/services/automation/handlers/element.js +55 -21
  90. package/dist/main/services/automation/index.d.ts +12 -1
  91. package/dist/main/services/automation/index.js +54 -133
  92. package/dist/main/services/cdp-session/index.d.ts +87 -0
  93. package/dist/main/services/cdp-session/index.js +173 -0
  94. package/dist/main/services/compile-standby.js +4 -0
  95. package/dist/main/services/console-forward/cdp-inject.d.ts +18 -0
  96. package/dist/main/services/console-forward/cdp-inject.js +30 -0
  97. package/dist/main/services/console-forward/global-console-mirror.d.ts +33 -0
  98. package/dist/main/services/console-forward/global-console-mirror.js +71 -0
  99. package/dist/main/services/console-forward/global-diagnostics-mirror.d.ts +33 -0
  100. package/dist/main/services/console-forward/global-diagnostics-mirror.js +73 -0
  101. package/dist/main/services/console-forward/index.d.ts +1 -46
  102. package/dist/main/services/console-forward/index.js +1 -185
  103. package/dist/main/services/console-forward/internal-log.d.ts +2 -0
  104. package/dist/main/services/console-forward/internal-log.js +2 -0
  105. package/dist/main/services/console-forward/open-gated-relay.d.ts +67 -0
  106. package/dist/main/services/console-forward/open-gated-relay.js +55 -0
  107. package/dist/main/services/default-adapter.js +1 -1
  108. package/dist/main/services/diagnostics/index.d.ts +1 -48
  109. package/dist/main/services/diagnostics/index.js +1 -60
  110. package/dist/main/services/dimina-resource-server.d.ts +1 -6
  111. package/dist/main/services/dimina-resource-server.js +1 -98
  112. package/dist/main/services/elements-forward/index.d.ts +132 -15
  113. package/dist/main/services/elements-forward/index.js +155 -304
  114. package/dist/main/services/fs-bridge-protocol.d.ts +61 -0
  115. package/dist/main/services/fs-bridge-protocol.js +34 -0
  116. package/dist/main/services/fs-watch-sse.d.ts +40 -0
  117. package/dist/main/services/fs-watch-sse.js +113 -0
  118. package/dist/main/services/http-json.d.ts +9 -0
  119. package/dist/main/services/http-json.js +6 -0
  120. package/dist/main/services/layout/index.d.ts +11 -0
  121. package/dist/main/services/layout/index.js +23 -0
  122. package/dist/main/services/mcp/connection-owner.d.ts +38 -0
  123. package/dist/main/services/mcp/connection-owner.js +79 -0
  124. package/dist/main/services/mcp/index.d.ts +3 -1
  125. package/dist/main/services/mcp/index.js +4 -3
  126. package/dist/main/services/mcp/opened-project.d.ts +88 -0
  127. package/dist/main/services/mcp/opened-project.js +60 -0
  128. package/dist/main/services/mcp/server.d.ts +2 -1
  129. package/dist/main/services/mcp/server.js +29 -4
  130. package/dist/main/services/mcp/target-manager.d.ts +71 -23
  131. package/dist/main/services/mcp/target-manager.js +269 -138
  132. package/dist/main/services/mcp/target-selection.d.ts +47 -0
  133. package/dist/main/services/mcp/target-selection.js +71 -0
  134. package/dist/main/services/mcp/tools/context-tools.js +20 -5
  135. package/dist/main/services/mcp/tools/project-tools.d.ts +72 -0
  136. package/dist/main/services/mcp/tools/project-tools.js +133 -0
  137. package/dist/main/services/module.d.ts +13 -5
  138. package/dist/main/services/network-forward/body-cache.d.ts +72 -0
  139. package/dist/main/services/network-forward/body-cache.js +179 -0
  140. package/dist/main/services/network-forward/frontend-dispatch.d.ts +91 -0
  141. package/dist/main/services/network-forward/frontend-dispatch.js +148 -0
  142. package/dist/main/services/network-forward/global-body-gate.d.ts +46 -0
  143. package/dist/main/services/network-forward/global-body-gate.js +93 -0
  144. package/dist/main/services/network-forward/global-mirror-test-fixtures.d.ts +48 -0
  145. package/dist/main/services/network-forward/global-mirror-test-fixtures.js +96 -0
  146. package/dist/main/services/network-forward/http.d.ts +47 -0
  147. package/dist/main/services/network-forward/http.js +162 -0
  148. package/dist/main/services/network-forward/index.d.ts +135 -15
  149. package/dist/main/services/network-forward/index.js +854 -224
  150. package/dist/main/services/network-forward/request-ids.d.ts +5 -0
  151. package/dist/main/services/network-forward/request-ids.js +5 -0
  152. package/dist/main/services/network-forward/user-facing.d.ts +21 -0
  153. package/dist/main/services/network-forward/user-facing.js +50 -0
  154. package/dist/main/services/network-forward/websocket.d.ts +36 -0
  155. package/dist/main/services/network-forward/websocket.js +120 -0
  156. package/dist/main/services/notifications/renderer-notifier.d.ts +71 -9
  157. package/dist/main/services/notifications/renderer-notifier.js +45 -8
  158. package/dist/main/services/projects/create-project-service.js +19 -5
  159. package/dist/main/services/projects/local-provider.js +8 -4
  160. package/dist/main/services/projects/project-repository.d.ts +59 -7
  161. package/dist/main/services/projects/project-repository.js +285 -51
  162. package/dist/main/services/projects/types.d.ts +44 -34
  163. package/dist/main/services/render-inspect/index.d.ts +12 -2
  164. package/dist/main/services/render-inspect/index.js +21 -102
  165. package/dist/main/services/safe-area/index.d.ts +21 -6
  166. package/dist/main/services/safe-area/index.js +92 -46
  167. package/dist/main/services/service-host-pool/pool.d.ts +1 -196
  168. package/dist/main/services/service-host-pool/pool.js +1 -363
  169. package/dist/main/services/settings/index.d.ts +7 -1
  170. package/dist/main/services/settings/index.js +22 -2
  171. package/dist/main/services/simulator/custom-apis.d.ts +1 -42
  172. package/dist/main/services/simulator/custom-apis.js +1 -46
  173. package/dist/main/services/simulator/referer.d.ts +1 -9
  174. package/dist/main/services/simulator/referer.js +1 -19
  175. package/dist/main/services/simulator/ui-extensions.d.ts +21 -0
  176. package/dist/main/services/simulator/ui-extensions.js +176 -0
  177. package/dist/main/services/simulator-appdata/index.d.ts +6 -0
  178. package/dist/main/services/simulator-appdata/index.js +21 -2
  179. package/dist/main/services/simulator-current-page/index.d.ts +5 -3
  180. package/dist/main/services/simulator-current-page/index.js +8 -1
  181. package/dist/main/services/simulator-storage/index.d.ts +34 -5
  182. package/dist/main/services/simulator-storage/index.js +73 -71
  183. package/dist/main/services/simulator-storage/service-storage-ops.d.ts +1 -39
  184. package/dist/main/services/simulator-storage/service-storage-ops.js +1 -66
  185. package/dist/main/services/simulator-storage/window-scope.d.ts +20 -0
  186. package/dist/main/services/simulator-storage/window-scope.js +20 -0
  187. package/dist/main/services/simulator-temp-files/disk.d.ts +1 -54
  188. package/dist/main/services/simulator-temp-files/disk.js +1 -237
  189. package/dist/main/services/simulator-temp-files/fs-channels.d.ts +1 -68
  190. package/dist/main/services/simulator-temp-files/fs-channels.js +1 -107
  191. package/dist/main/services/simulator-temp-files/index.d.ts +1 -32
  192. package/dist/main/services/simulator-temp-files/index.js +1 -181
  193. package/dist/main/services/simulator-temp-files/request-handler.d.ts +1 -29
  194. package/dist/main/services/simulator-temp-files/request-handler.js +1 -177
  195. package/dist/main/services/simulator-temp-files/resolver.d.ts +1 -21
  196. package/dist/main/services/simulator-temp-files/resolver.js +1 -21
  197. package/dist/main/services/simulator-temp-files/store.d.ts +1 -13
  198. package/dist/main/services/simulator-temp-files/store.js +1 -23
  199. package/dist/main/services/update/github-release-checker.js +22 -3
  200. package/dist/main/services/update/update-manager.d.ts +21 -4
  201. package/dist/main/services/update/update-manager.js +55 -11
  202. package/dist/main/services/views/clear-console-filter.d.ts +44 -0
  203. package/dist/main/services/views/clear-console-filter.js +86 -0
  204. package/dist/main/services/views/console-filter-test-fixtures.d.ts +94 -0
  205. package/dist/main/services/views/console-filter-test-fixtures.js +82 -0
  206. package/dist/main/services/views/console-filter.d.ts +94 -0
  207. package/dist/main/services/views/console-filter.js +190 -0
  208. package/dist/main/services/views/devtools-tabs.d.ts +24 -4
  209. package/dist/main/services/views/devtools-tabs.js +40 -21
  210. package/dist/main/services/views/frontend-bootstrap-gate.d.ts +59 -0
  211. package/dist/main/services/views/frontend-bootstrap-gate.js +117 -0
  212. package/dist/main/services/views/host-dialog-port-channel.d.ts +18 -0
  213. package/dist/main/services/views/host-dialog-port-channel.js +15 -0
  214. package/dist/main/services/views/host-dialog-session-runtime.d.ts +10 -0
  215. package/dist/main/services/views/host-dialog-session-runtime.js +17 -0
  216. package/dist/main/services/views/host-dialog-view.d.ts +95 -0
  217. package/dist/main/services/views/host-dialog-view.js +147 -0
  218. package/dist/main/services/views/host-sidebar-port-channel.d.ts +18 -0
  219. package/dist/main/services/views/host-sidebar-port-channel.js +15 -0
  220. package/dist/main/services/views/host-sidebar-session-runtime.d.ts +10 -0
  221. package/dist/main/services/views/host-sidebar-session-runtime.js +17 -0
  222. package/dist/main/services/views/host-sidebar-view.d.ts +59 -0
  223. package/dist/main/services/views/host-sidebar-view.js +44 -0
  224. package/dist/main/services/views/host-slot-port-channel.d.ts +124 -0
  225. package/dist/main/services/views/host-slot-port-channel.js +289 -0
  226. package/dist/main/services/views/host-slot-session-runtime.d.ts +40 -0
  227. package/dist/main/services/views/host-slot-session-runtime.js +50 -0
  228. package/dist/main/services/views/host-slot-view.d.ts +109 -0
  229. package/dist/main/services/views/host-slot-view.js +147 -0
  230. package/dist/main/services/views/host-toolbar-port-channel.d.ts +6 -91
  231. package/dist/main/services/views/host-toolbar-port-channel.js +10 -243
  232. package/dist/main/services/views/host-toolbar-session-runtime.d.ts +5 -30
  233. package/dist/main/services/views/host-toolbar-session-runtime.js +9 -47
  234. package/dist/main/services/views/host-toolbar-view.d.ts +34 -7
  235. package/dist/main/services/views/host-toolbar-view.js +30 -237
  236. package/dist/main/services/views/managed-web-contents-view.d.ts +48 -0
  237. package/dist/main/services/views/managed-web-contents-view.js +143 -0
  238. package/dist/main/services/views/miniapp-partition.d.ts +1 -89
  239. package/dist/main/services/views/miniapp-partition.js +1 -158
  240. package/dist/main/services/views/native-simulator-devtools-host.js +206 -68
  241. package/dist/main/services/views/native-simulator-view.d.ts +6 -0
  242. package/dist/main/services/views/native-simulator-view.js +47 -40
  243. package/dist/main/services/views/native-view-tree.d.ts +25 -0
  244. package/dist/main/services/views/native-view-tree.js +69 -0
  245. package/dist/main/services/views/overlay-panels-view.d.ts +63 -4
  246. package/dist/main/services/views/overlay-panels-view.js +257 -108
  247. package/dist/main/services/views/placement-reconciler.d.ts +18 -18
  248. package/dist/main/services/views/placement-reconciler.js +91 -29
  249. package/dist/main/services/views/placement-test-driver.d.ts +27 -0
  250. package/dist/main/services/views/placement-test-driver.js +29 -0
  251. package/dist/main/services/views/refresh-styles.d.ts +10 -0
  252. package/dist/main/services/views/refresh-styles.js +57 -0
  253. package/dist/main/services/views/simulator-session-policy.d.ts +1 -27
  254. package/dist/main/services/views/simulator-session-policy.js +1 -98
  255. package/dist/main/services/views/view-manager-devtools-host-test-fixtures.d.ts +76 -0
  256. package/dist/main/services/views/view-manager-devtools-host-test-fixtures.js +184 -0
  257. package/dist/main/services/views/view-manager.d.ts +69 -88
  258. package/dist/main/services/views/view-manager.js +62 -7
  259. package/dist/main/services/views/workbench-view.d.ts +2 -0
  260. package/dist/main/services/views/workbench-view.js +104 -17
  261. package/dist/main/services/window-contexts/context-router.d.ts +36 -0
  262. package/dist/main/services/window-contexts/context-router.js +55 -0
  263. package/dist/main/services/workbench-coi-host.d.ts +52 -0
  264. package/dist/main/services/workbench-coi-host.js +227 -0
  265. package/dist/main/services/workbench-coi-server.d.ts +11 -0
  266. package/dist/main/services/workbench-coi-server.js +126 -126
  267. package/dist/main/services/workbench-coi-shutdown.d.ts +40 -0
  268. package/dist/main/services/workbench-coi-shutdown.js +69 -0
  269. package/dist/main/services/workbench-coi-static.d.ts +23 -0
  270. package/dist/main/services/workbench-coi-static.js +87 -0
  271. package/dist/main/services/workbench-context.d.ts +88 -8
  272. package/dist/main/services/workbench-context.js +54 -6
  273. package/dist/main/services/workspace/compile-log-buffer.d.ts +40 -0
  274. package/dist/main/services/workspace/compile-log-buffer.js +41 -0
  275. package/dist/main/services/workspace/compile-mode-adapter.d.ts +18 -0
  276. package/dist/main/services/workspace/compile-mode-adapter.js +57 -0
  277. package/dist/main/services/workspace/compile-mode-store.d.ts +25 -0
  278. package/dist/main/services/workspace/compile-mode-store.js +71 -0
  279. package/dist/main/services/workspace/open-project-guards.d.ts +16 -0
  280. package/dist/main/services/workspace/open-project-guards.js +42 -0
  281. package/dist/main/services/workspace/rebuild-status.d.ts +37 -0
  282. package/dist/main/services/workspace/rebuild-status.js +36 -0
  283. package/dist/main/services/workspace/session-status-store.d.ts +47 -0
  284. package/dist/main/services/workspace/session-status-store.js +73 -0
  285. package/dist/main/services/workspace/status-tap.d.ts +20 -0
  286. package/dist/main/services/workspace/status-tap.js +32 -0
  287. package/dist/main/services/workspace/workspace-compile-modes.d.ts +59 -0
  288. package/dist/main/services/workspace/workspace-compile-modes.js +93 -0
  289. package/dist/main/services/workspace/workspace-lifecycle-race.testutil.d.ts +22 -0
  290. package/dist/main/services/workspace/workspace-lifecycle-race.testutil.js +62 -0
  291. package/dist/main/services/workspace/workspace-referer.d.ts +17 -0
  292. package/dist/main/services/workspace/workspace-referer.js +26 -0
  293. package/dist/main/services/workspace/workspace-service.d.ts +7 -5
  294. package/dist/main/services/workspace/workspace-service.js +96 -135
  295. package/dist/main/services/workspace/workspace-thumbnails.d.ts +22 -0
  296. package/dist/main/services/workspace/workspace-thumbnails.js +67 -0
  297. package/dist/main/utils/ipc-context-source.d.ts +40 -0
  298. package/dist/main/utils/ipc-context-source.js +23 -0
  299. package/dist/main/utils/ipc-registry.d.ts +46 -21
  300. package/dist/main/utils/ipc-registry.js +166 -76
  301. package/dist/main/utils/paths.d.ts +18 -0
  302. package/dist/main/utils/paths.js +18 -0
  303. package/dist/main/utils/sender-policy.d.ts +10 -1
  304. package/dist/main/utils/sender-policy.js +42 -4
  305. package/dist/main/utils/theme.d.ts +18 -8
  306. package/dist/main/utils/theme.js +19 -9
  307. package/dist/main/windows/internal-devtools-window/index.d.ts +66 -0
  308. package/dist/main/windows/internal-devtools-window/index.js +160 -0
  309. package/dist/main/windows/main-window/create.d.ts +16 -0
  310. package/dist/main/windows/main-window/create.js +19 -11
  311. package/dist/main/windows/main-window/events.d.ts +1 -1
  312. package/dist/main/windows/main-window/events.js +26 -13
  313. package/dist/main/windows/main-window/index.d.ts +1 -1
  314. package/dist/main/windows/main-window/index.js +1 -1
  315. package/dist/main/windows/service-host-window/create.d.ts +1 -92
  316. package/dist/main/windows/service-host-window/create.js +1 -154
  317. package/dist/native-host/common/common.js +173 -53
  318. package/dist/native-host/container/pageFrame.css +1 -1
  319. package/dist/native-host/render/render.js +8278 -3659
  320. package/dist/native-host/service/service.js +2 -2
  321. package/dist/preload/index.d.ts +2 -0
  322. package/dist/preload/index.js +1 -0
  323. package/dist/preload/instrumentation/app-data.d.ts +2 -2
  324. package/dist/preload/instrumentation/app-data.js +1 -1
  325. package/dist/preload/instrumentation/wxml.js +1 -1
  326. package/dist/preload/runtime/bridge.d.ts +3 -3
  327. package/dist/preload/runtime/bridge.js +2 -2
  328. package/dist/preload/runtime/clipboard.d.ts +6 -0
  329. package/dist/preload/runtime/clipboard.js +12 -0
  330. package/dist/preload/runtime/host-dialog-advertiser.d.ts +22 -0
  331. package/dist/preload/runtime/host-dialog-advertiser.js +37 -0
  332. package/dist/preload/runtime/host-dialog-port.d.ts +44 -0
  333. package/dist/preload/runtime/host-dialog-port.js +53 -0
  334. package/dist/preload/runtime/host-dialog-runtime.d.ts +28 -0
  335. package/dist/preload/runtime/host-dialog-runtime.js +36 -0
  336. package/dist/preload/runtime/host-sidebar-advertiser.d.ts +19 -0
  337. package/dist/preload/runtime/host-sidebar-advertiser.js +35 -0
  338. package/dist/preload/runtime/host-sidebar-port.d.ts +44 -0
  339. package/dist/preload/runtime/host-sidebar-port.js +53 -0
  340. package/dist/preload/runtime/host-sidebar-runtime.d.ts +28 -0
  341. package/dist/preload/runtime/host-sidebar-runtime.js +36 -0
  342. package/dist/preload/runtime/host-slot-port-bridge.d.ts +18 -0
  343. package/dist/preload/runtime/host-slot-port-bridge.js +112 -0
  344. package/dist/preload/runtime/host-toolbar-advertiser.d.ts +5 -13
  345. package/dist/preload/runtime/host-toolbar-advertiser.js +18 -38
  346. package/dist/preload/runtime/host-toolbar-port.d.ts +9 -0
  347. package/dist/preload/runtime/host-toolbar-port.js +11 -95
  348. package/dist/preload/runtime/native-host.d.ts +14 -0
  349. package/dist/preload/runtime/native-host.js +5 -8
  350. package/dist/preload/runtime/node-bindings.d.ts +18 -0
  351. package/dist/preload/runtime/node-bindings.js +44 -0
  352. package/dist/preload/runtime/temp-files.js +9 -2
  353. package/dist/preload/runtime/wait-for-slot-root.d.ts +23 -0
  354. package/dist/preload/runtime/wait-for-slot-root.js +80 -0
  355. package/dist/preload/shared/api-compat.js +63 -41
  356. package/dist/preload/windows/host-dialog-runtime.cjs +360 -0
  357. package/dist/preload/windows/host-dialog-runtime.cjs.map +7 -0
  358. package/dist/preload/windows/host-dialog-runtime.d.ts +2 -0
  359. package/dist/preload/windows/host-dialog-runtime.js +13 -0
  360. package/dist/preload/windows/host-sidebar-runtime.cjs +361 -0
  361. package/dist/preload/windows/host-sidebar-runtime.cjs.map +7 -0
  362. package/dist/preload/windows/host-sidebar-runtime.d.ts +2 -0
  363. package/dist/preload/windows/host-sidebar-runtime.js +13 -0
  364. package/dist/preload/windows/host-toolbar-runtime.cjs +142 -52
  365. package/dist/preload/windows/host-toolbar-runtime.cjs.map +3 -3
  366. package/dist/preload/windows/main.cjs +4922 -1246
  367. package/dist/preload/windows/main.cjs.map +4 -4
  368. package/dist/preload/windows/main.css +428 -0
  369. package/dist/preload/windows/main.css.map +7 -0
  370. package/dist/preload/windows/simulator.cjs +376 -322
  371. package/dist/preload/windows/simulator.cjs.map +4 -4
  372. package/dist/preload/windows/simulator.js +376 -322
  373. package/dist/render-host/pageFrame.html +4 -4
  374. package/dist/render-host/preload.cjs +30 -0
  375. package/dist/render-host/render-inspect.js +184 -115
  376. package/dist/renderer/assets/button-CB2MWjHY.js +2 -0
  377. package/dist/renderer/assets/constants-CkoGPQTS.js +2 -0
  378. package/dist/renderer/assets/constants-zQZ2-y21.js +2 -0
  379. package/dist/renderer/assets/createLucideIcon-cZ9FlZZw.js +2 -0
  380. package/dist/renderer/assets/devicePicker-H2eIO4e1.js +2 -0
  381. package/dist/renderer/assets/dialog-pHzEA8Vx.js +46 -0
  382. package/dist/renderer/assets/dist-B5HADirS.js +2 -0
  383. package/dist/renderer/assets/dist-DIFlRMKe.js +2 -0
  384. package/dist/renderer/assets/hostSidebarDefault-DRWh6OCt.js +2 -0
  385. package/dist/renderer/assets/index-CEL_XFge.js +2 -0
  386. package/dist/renderer/assets/input-CAObw95L.js +2 -0
  387. package/dist/renderer/assets/ipc-channels-overlays-gIPUFnqc.js +2 -0
  388. package/dist/renderer/assets/jsx-runtime-DICEQH_0.css +1 -0
  389. package/dist/renderer/assets/jsx-runtime-DIw5wnYl.js +9 -0
  390. package/dist/renderer/assets/plus-BQ9u5EbL.js +2 -0
  391. package/dist/renderer/assets/popover-Cw7G2Ctp.js +2 -0
  392. package/dist/renderer/assets/presets-DMZOb9ay.js +2 -0
  393. package/dist/renderer/assets/project-api-YTOr_Yvo.js +2 -0
  394. package/dist/renderer/assets/projectCreateDialog-BmLqiYxn.js +2 -0
  395. package/dist/renderer/assets/search-BMvuYF48.js +2 -0
  396. package/dist/renderer/assets/select-nFP95TxM.js +2 -0
  397. package/dist/renderer/assets/settings-CnpVfSWt.js +2 -0
  398. package/dist/renderer/assets/settings-api-Ccsf-Nsj.js +2 -0
  399. package/dist/renderer/assets/settings-tab-bar-CAzrv32m.js +2 -0
  400. package/dist/renderer/assets/smartphone-Bb2GdIKy.js +2 -0
  401. package/dist/renderer/assets/tabs-CPKN3Mmq.js +2 -0
  402. package/dist/renderer/assets/tooltip-D6AxXRk5.js +2 -0
  403. package/dist/renderer/assets/types-Bh0hA7El.js +2 -0
  404. package/dist/renderer/assets/updateDialog-CwndJ0Bl.js +2 -0
  405. package/dist/renderer/assets/utils-sEB5OZEQ.js +2 -0
  406. package/dist/renderer/assets/view-api-B-2zPBR5.js +2 -0
  407. package/dist/renderer/assets/view-ids-BfaDVV5P.js +2 -0
  408. package/dist/renderer/assets/workbench-C0Jkdlps.js +5 -0
  409. package/dist/renderer/assets/workbenchSettings-YD0Ctbm_.js +8 -0
  410. package/dist/renderer/entries/device-picker/index.html +28 -0
  411. package/dist/renderer/entries/host-sidebar-default/index.html +22 -0
  412. package/dist/renderer/entries/main/index.html +16 -7
  413. package/dist/renderer/entries/popover/index.html +16 -6
  414. package/dist/renderer/entries/project-create-dialog/index.html +27 -0
  415. package/dist/renderer/entries/settings/index.html +10 -5
  416. package/dist/renderer/entries/tooltip/index.html +21 -0
  417. package/dist/renderer/entries/update-dialog/index.html +25 -0
  418. package/dist/renderer/entries/workbench/index.html +49 -0
  419. package/dist/renderer/entries/workbench-settings/index.html +12 -5
  420. package/dist/service-host/appdata-set-data.cjs +39 -0
  421. package/dist/service-host/host-env-update.cjs +23 -0
  422. package/dist/service-host/preload.cjs +54 -48
  423. package/dist/service-host/service-message-queue.cjs +71 -0
  424. package/dist/service-host/sync-impls/menu-button-geometry.js +16 -5
  425. package/dist/service-host/sync-impls/menu-button.js +5 -3
  426. package/dist/service-host/sync-impls/system-info.js +31 -9
  427. package/dist/shared/bridge-channels.d.ts +1 -416
  428. package/dist/shared/bridge-channels.js +1 -89
  429. package/dist/shared/compile-mode-state.d.ts +73 -0
  430. package/dist/shared/compile-mode-state.js +100 -0
  431. package/dist/shared/compile-modes.d.ts +152 -0
  432. package/dist/shared/compile-modes.js +237 -0
  433. package/dist/shared/constants.d.ts +22 -0
  434. package/dist/shared/constants.js +22 -0
  435. package/dist/shared/dmb-resource-url.d.ts +2 -0
  436. package/dist/shared/dmb-resource-url.js +2 -0
  437. package/dist/shared/ipc-channels-overlays.d.ts +162 -0
  438. package/dist/shared/ipc-channels-overlays.js +203 -0
  439. package/dist/shared/ipc-channels.d.ts +24 -154
  440. package/dist/shared/ipc-channels.js +48 -96
  441. package/dist/shared/ipc-schemas.d.ts +143 -54
  442. package/dist/shared/ipc-schemas.js +137 -45
  443. package/dist/shared/node-bindings.d.ts +2 -0
  444. package/dist/shared/node-bindings.js +2 -0
  445. package/dist/shared/project-window-types.d.ts +42 -0
  446. package/dist/shared/project-window-types.js +2 -0
  447. package/dist/shared/simulator-route.d.ts +8 -0
  448. package/dist/shared/simulator-route.js +15 -0
  449. package/dist/shared/simulator-ui.d.ts +61 -0
  450. package/dist/shared/simulator-ui.js +12 -0
  451. package/dist/shared/types.d.ts +126 -41
  452. package/dist/shared/view-ids.d.ts +10 -0
  453. package/dist/shared/view-ids.js +23 -0
  454. package/dist/simulator/assets/bridge-channels-B3JAmRfl.js +2 -0
  455. package/dist/simulator/assets/device-shell-ATEhHV2_.css +1 -0
  456. package/dist/simulator/assets/device-shell-DucLepkm.js +431 -0
  457. package/dist/simulator/assets/simulator-B4rbcOVt.js +10 -0
  458. package/dist/simulator/assets/simulator-mini-app-DFo7fhi5.js +2 -0
  459. package/dist/simulator/assets/simulator-ui-BupATgKo.css +1 -0
  460. package/dist/simulator/assets/simulator-ui-i3GCc7YJ.js +2 -0
  461. package/dist/simulator/simulator.html +4 -3
  462. package/dist/simulator/temp-files.d.ts +35 -0
  463. package/dist/simulator/temp-files.js +121 -0
  464. package/dist/simulator/types.d.ts +50 -0
  465. package/dist/simulator/types.js +2 -0
  466. package/dist/vscode-workbench/assets/__vite-browser-external-JIkWewFf.js +1 -0
  467. package/dist/vscode-workbench/assets/{dist-TpGpmMGi.js → dist-CXG-u1VB.js} +3 -3
  468. package/dist/vscode-workbench/assets/fs-core-worker-urls-DZ1nod84.js +1 -0
  469. package/dist/vscode-workbench/assets/fs-core.worker-Bnmpp6fo.js +1 -0
  470. package/dist/vscode-workbench/assets/fs-query.worker-cZ-7Fjch.js +2 -0
  471. package/dist/vscode-workbench/assets/{iconv-lite-umd-C9tiCAJa.js → iconv-lite-umd-Dxx9Qyb4.js} +1 -1
  472. package/dist/vscode-workbench/assets/{index-DJ1HyMZ7.js → index-6VJMsaWj.js} +475 -475
  473. package/dist/vscode-workbench/assets/{jschardet-DE1jV513.js → jschardet-Ba3D16vt.js} +1 -1
  474. package/dist/vscode-workbench/index.html +21 -1
  475. package/package.json +23 -13
  476. package/dist/main/services/views/destroy-child-view.d.ts +0 -9
  477. package/dist/main/services/views/destroy-child-view.js +0 -23
  478. package/dist/preload/instrumentation/wxml-extract.d.ts +0 -11
  479. package/dist/preload/instrumentation/wxml-extract.js +0 -382
  480. package/dist/preload/shared/sid-registry.d.ts +0 -11
  481. package/dist/preload/shared/sid-registry.js +0 -38
  482. package/dist/renderer/assets/constants-EAMiwL9_.js +0 -2
  483. package/dist/renderer/assets/index-BMqyDx71.js +0 -49
  484. package/dist/renderer/assets/input-CDAEwpBO.js +0 -2
  485. package/dist/renderer/assets/ipc-transport-B7eFOR68.css +0 -1
  486. package/dist/renderer/assets/ipc-transport-_G1k5V1p.js +0 -9
  487. package/dist/renderer/assets/popover-DEdjK14m.js +0 -2
  488. package/dist/renderer/assets/select-DpKPCh3y.js +0 -2
  489. package/dist/renderer/assets/settings-M5q2elUr.js +0 -2
  490. package/dist/renderer/assets/settings-api-CDlZoHiO.js +0 -2
  491. package/dist/renderer/assets/workbenchSettings-CT_H1WhV.js +0 -8
  492. package/dist/shared/appdata-accumulator.d.ts +0 -95
  493. package/dist/shared/appdata-accumulator.js +0 -218
  494. package/dist/shared/request-core.d.ts +0 -84
  495. package/dist/shared/request-core.js +0 -168
  496. package/dist/shared/simulator-api-metadata.d.ts +0 -26
  497. package/dist/shared/simulator-api-metadata.js +0 -59
  498. package/dist/shared/vpath.d.ts +0 -42
  499. package/dist/shared/vpath.js +0 -105
  500. package/dist/simulator/assets/bridge-channels-zhsumfky.js +0 -2
  501. package/dist/simulator/assets/device-shell-C-wcFq3Z.js +0 -2
  502. package/dist/simulator/assets/device-shell-CS1rqAQz.css +0 -1
  503. package/dist/simulator/assets/jsx-runtime-BDTY6fEq.js +0 -2
  504. package/dist/simulator/assets/simulator-DU3-fS3v.js +0 -10
  505. package/dist/simulator/assets/simulator-mini-app-DzfMfnVM.js +0 -2
  506. package/dist/simulator/device-shell/menu-button-geometry.js +0 -8
  507. package/dist/vscode-workbench/assets/__vite-browser-external-CXi6Kz9W.js +0 -1
@@ -1,6 +1,15 @@
1
- import { DisposableRegistry, toDisposable } from '@dimina-kit/electron-deck/main';
2
- import { isFrontendSettled } from '../views/inject-when-ready.js';
3
- import { packDispatchBatch } from './dispatch-batch.js';
1
+ import { SyncDisposableRegistry, toDisposable, } from "@dimina-kit/electron-deck/main";
2
+ import { isFrontendSettled } from "../views/inject-when-ready.js";
3
+ import { packDispatchBatch } from "./dispatch-batch.js";
4
+ import { PrefetchCache, DEFAULT_PER_ENTRY_MAX_CHARS } from "./body-cache.js";
5
+ import { PROBE_DEVTOOLS_API, MAX_SINGLE_DISPATCH_CHARS, CHUNK_CHARS, buildChunkedDispatchScript, } from "./frontend-dispatch.js";
6
+ import { createCdpSessionBroker, } from "../cdp-session/index.js";
7
+ import { isUserFacingRequest } from "./user-facing.js";
8
+ import { installGlobalNetworkBodyGate } from "./global-body-gate.js";
9
+ import { WebSocketTraceSynthesizer } from "./websocket.js";
10
+ import { RequestTraceSynthesizer } from "./http.js";
11
+ import { VIRTUAL_REQUEST_ID_PREFIX } from "./request-ids.js";
12
+ export { VIRTUAL_REQUEST_ID_PREFIX } from "./request-ids.js";
4
13
  // ── requestId namespacing (pure, testable) ──────────────────────────────────
5
14
  /**
6
15
  * CDP events whose `params.requestId` we rewrite into the virtual namespace.
@@ -10,32 +19,32 @@ import { packDispatchBatch } from './dispatch-batch.js';
10
19
  * included for that reason (rewrite-only today, not forwarded).
11
20
  */
12
21
  export const REWRITE_REQUEST_ID_METHODS = new Set([
13
- 'Network.requestWillBeSent',
14
- 'Network.requestWillBeSentExtraInfo',
15
- 'Network.responseReceived',
16
- 'Network.responseReceivedExtraInfo',
17
- 'Network.dataReceived',
18
- 'Network.loadingFinished',
19
- 'Network.loadingFailed',
20
- 'Network.requestServedFromCache',
21
- 'Network.resourceChangedPriority',
22
+ "Network.requestWillBeSent",
23
+ "Network.requestWillBeSentExtraInfo",
24
+ "Network.responseReceived",
25
+ "Network.responseReceivedExtraInfo",
26
+ "Network.dataReceived",
27
+ "Network.loadingFinished",
28
+ "Network.loadingFailed",
29
+ "Network.requestServedFromCache",
30
+ "Network.resourceChangedPriority",
22
31
  // 二期 (when WebSocket/EventSource forwarding lands): Network.webSocket*,
23
32
  // Network.eventSourceMessageReceived — keep ids namespaced once added here.
24
33
  ]);
25
34
  /** The Network.* methods this one-shot pass forwards to the front-end. */
26
35
  export const FORWARDED_METHODS = new Set([
27
- 'Network.requestWillBeSent',
28
- 'Network.requestWillBeSentExtraInfo',
29
- 'Network.responseReceived',
30
- 'Network.responseReceivedExtraInfo',
31
- 'Network.loadingFinished',
32
- 'Network.loadingFailed',
36
+ "Network.requestWillBeSent",
37
+ "Network.requestWillBeSentExtraInfo",
38
+ "Network.responseReceived",
39
+ "Network.responseReceivedExtraInfo",
40
+ "Network.loadingFinished",
41
+ "Network.loadingFailed",
33
42
  // `dataReceived` deliberately omitted (二期) — see header.
34
43
  ]);
35
44
  /** Methods that mark a request finished, so its id mapping can age out. */
36
45
  const TERMINAL_METHODS = new Set([
37
- 'Network.loadingFinished',
38
- 'Network.loadingFailed',
46
+ "Network.loadingFinished",
47
+ "Network.loadingFailed",
39
48
  ]);
40
49
  /**
41
50
  * Bounded, TTL'd raw→virtual requestId map with an active/retired split.
@@ -86,7 +95,7 @@ export class RequestIdNamespace {
86
95
  this.map.set(rawId, existing);
87
96
  return existing.virtual;
88
97
  }
89
- const virtual = `dimina:sim:${this.epoch}:${this.seq++}:${rawId}`;
98
+ const virtual = `${VIRTUAL_REQUEST_ID_PREFIX}${this.epoch}:${this.seq++}:${rawId}`;
90
99
  this.map.set(rawId, { virtual, expires: t + this.ttlMs, active: true });
91
100
  this.evict(t);
92
101
  return virtual;
@@ -143,7 +152,7 @@ export function rewriteRequestId(method, params, ns) {
143
152
  if (!REWRITE_REQUEST_ID_METHODS.has(method))
144
153
  return { method, params };
145
154
  const p = params;
146
- if (!p || typeof p.requestId !== 'string')
155
+ if (!p || typeof p.requestId !== "string")
147
156
  return { method, params };
148
157
  const virtual = ns.resolve(p.requestId);
149
158
  if (TERMINAL_METHODS.has(method))
@@ -151,8 +160,6 @@ export function rewriteRequestId(method, params, ns) {
151
160
  return { method, params: { ...p, requestId: virtual } };
152
161
  }
153
162
  // ── DevTools front-end injection (the primary sink) ─────────────────────────
154
- /** Probe the front-end realm exposes `DevToolsAPI.dispatchMessage`. */
155
- const PROBE_DEVTOOLS_API = `(window.DevToolsAPI && typeof window.DevToolsAPI.dispatchMessage === 'function')`;
156
163
  /**
157
164
  * Build the `executeJavaScript` source that dispatches a BATCH of raw CDP
158
165
  * messages into the DevTools front-end. Each message is carried as a JSON
@@ -162,44 +169,13 @@ const PROBE_DEVTOOLS_API = `(window.DevToolsAPI && typeof window.DevToolsAPI.dis
162
169
  */
163
170
  function buildDispatchScript(messages) {
164
171
  const arr = JSON.stringify(messages);
165
- return `(()=>{try{`
166
- + `if(!${PROBE_DEVTOOLS_API})return false;`
167
- + `const ms=JSON.parse(${JSON.stringify(arr)});`
168
- + `for(const m of ms){try{window.DevToolsAPI.dispatchMessage(m)}catch(_){}}`
169
- + `return true;`
170
- + `}catch(_){return false}})()`;
172
+ return (`(()=>{try{` +
173
+ `if(!${PROBE_DEVTOOLS_API})return false;` +
174
+ `const ms=JSON.parse(${JSON.stringify(arr)});` +
175
+ `for(const m of ms){try{window.DevToolsAPI.dispatchMessage(m)}catch(_){}}` +
176
+ `return true;` +
177
+ `}catch(_){return false}})()`);
171
178
  }
172
- /**
173
- * Build the `executeJavaScript` source for a chunked dispatch of one large CDP
174
- * message — DevTools' own transport caps a single `dispatchMessage` payload, so
175
- * the front-end exposes `dispatchMessageChunk(messageChunk, messageSize)` where
176
- * the FIRST chunk carries the total size and EVERY SUBSEQUENT chunk is called
177
- * with the chunk ONLY (second arg omitted). This matches Chromium's
178
- * `devtools_ui_bindings.cc` DispatchProtocolMessage, whose front-end treats a
179
- * call with a second argument as the start of a new message and a call without
180
- * one as a continuation. Passing 0 (the previous behaviour) risked the
181
- * front-end mis-reading a continuation as a fresh 0-size message. Returns false
182
- * when the API isn't available.
183
- */
184
- function buildChunkedDispatchScript(chunks, totalSize) {
185
- const arr = JSON.stringify(chunks);
186
- return `(()=>{try{`
187
- + `if(!(window.DevToolsAPI&&typeof window.DevToolsAPI.dispatchMessageChunk==='function'))return false;`
188
- + `const cs=JSON.parse(${JSON.stringify(arr)});`
189
- + `for(let i=0;i<cs.length;i++){`
190
- + `try{`
191
- // First chunk: (chunk, totalSize). Subsequent chunks: (chunk) — second arg
192
- // omitted, NOT 0, per Chromium's continuation contract.
193
- + `if(i===0){window.DevToolsAPI.dispatchMessageChunk(cs[i], ${totalSize})}`
194
- + `else{window.DevToolsAPI.dispatchMessageChunk(cs[i])}`
195
- + `}catch(_){}`
196
- + `}`
197
- + `return true;`
198
- + `}catch(_){return false}})()`;
199
- }
200
- /** A single dispatch payload may not exceed this many UTF-16 chars; chunk above. */
201
- const MAX_SINGLE_DISPATCH_CHARS = 1_000_000;
202
- const CHUNK_CHARS = 256 * 1024;
203
179
  /**
204
180
  * Upper bound on the COMBINED size (in UTF-16 chars) of the messages packed into
205
181
  * one `executeJavaScript` batch. 2000 small messages otherwise stitch into a
@@ -216,13 +192,13 @@ const MAX_BATCH_CHARS = 512 * 1024;
216
192
  */
217
193
  function buildForwardScript(record) {
218
194
  const json = JSON.stringify(record);
219
- return `(()=>{try{const r=JSON.parse(${JSON.stringify(json)});`
220
- + `const tag='[网络]['+r.source+']';`
221
- + `const head=r.method+' '+(r.status||'-')+' '+r.url;`
222
- + `if(r.errorText){console.warn(tag,head,r.errorText)}`
223
- + `else if(r.status>=400){console.warn(tag,head)}`
224
- + `else{console.log(tag,head)}`
225
- + `}catch(_){}})()`;
195
+ return (`(()=>{try{const r=JSON.parse(${JSON.stringify(json)});` +
196
+ `const tag='[网络]['+r.source+']';` +
197
+ `const head=r.method+' '+(r.status||'-')+' '+r.url;` +
198
+ `if(r.errorText){console.warn(tag,head,r.errorText)}` +
199
+ `else if(r.status>=400){console.warn(tag,head)}` +
200
+ `else{console.log(tag,head)}` +
201
+ `}catch(_){}})()`);
226
202
  }
227
203
  /** Cap so a long session can't grow the fallback pending map unboundedly. */
228
204
  const MAX_PENDING = 1000;
@@ -238,17 +214,274 @@ const DEVTOOLS_READY_TIMEOUT_MS = 5_000;
238
214
  /** Poll interval while probing for the front-end API to become ready. */
239
215
  const READY_RETRY_MS = 100;
240
216
  export function createNetworkForwarder(bridge) {
241
- const registry = new DisposableRegistry();
242
- // The simulator WCV we currently have a debugger session on, and the
243
- // per-attach teardown (debugger listeners + detach). Null when not attached.
217
+ const registry = new SyncDisposableRegistry();
218
+ let disposed = false;
219
+ // ── Response body / post-data prefetch (serves the front-end's clicks) ─────
220
+ // Keyed by VIRTUAL requestId and owned by the forwarder (not the attach):
221
+ // epochs make ids non-colliding, so entries stay servable across a simulator
222
+ // detach/re-attach while the panel still shows the old rows. Bounded + TTL'd
223
+ // in the cache itself.
224
+ const bodyCache = new PrefetchCache((v) => v.body.length);
225
+ const postDataCache = new PrefetchCache((v) => v.postData.length);
226
+ // ── Prefetch admission control ──────────────────────────────────────────────
227
+ // Every completed request (simulator + all render guests combined — this
228
+ // counter is forwarder-wide, not per-attach) would otherwise trigger an
229
+ // unconditional `Network.getResponseBody` CDP round-trip that must
230
+ // materialize the FULL body into main-process memory before the cache's own
231
+ // per-entry/total-size limits can reject it (those limits apply only to
232
+ // SETTLED entries — a pending fetch counts 0 and is eviction-exempt). A page
233
+ // that finishes many large resources at once (e.g. many concurrent images —
234
+ // exactly the render-guest capture path) could otherwise pile up unboundedly
235
+ // many simultaneous full-body reads. This caps how many prefetches (body +
236
+ // post-data combined) may be in flight at once; once at the cap, further
237
+ // completions are simply skipped — the panel's Response tab 404s for those
238
+ // (same as any other not-found id) rather than the process risking a memory
239
+ // spike. `primeWithAdmission`'s bookkeeping only counts a slot when the cache
240
+ // actually started a new fetch (PrefetchCache.prime()'s idempotent no-op
241
+ // return doesn't consume one).
242
+ const MAX_CONCURRENT_PREFETCHES = 32;
243
+ let pendingPrefetchCount = 0;
244
+ /**
245
+ * `fetch` MUST be an `async` function (both current call sites in
246
+ * `prefetchBodies` are). An `async` function can never throw synchronously —
247
+ * any exception in its body is spec-guaranteed to surface as a rejected
248
+ * promise instead — which is what guarantees `release()` always eventually
249
+ * runs via the `.then()` below. A plain (non-async) function that throws
250
+ * synchronously when CALLED would bypass `.then()` entirely and leak this
251
+ * slot forever (`PrefetchCache.prime` catches that synchronous throw and
252
+ * still reports `started: true`), so never pass one here.
253
+ */
254
+ function primeWithAdmission(cache, id, fetch) {
255
+ if (pendingPrefetchCount >= MAX_CONCURRENT_PREFETCHES)
256
+ return;
257
+ pendingPrefetchCount++;
258
+ let released = false;
259
+ const release = () => {
260
+ if (!released) {
261
+ released = true;
262
+ pendingPrefetchCount--;
263
+ }
264
+ };
265
+ const started = cache.prime(id, () => fetch().then((v) => {
266
+ release();
267
+ return v;
268
+ }, (err) => {
269
+ release();
270
+ throw err;
271
+ }));
272
+ if (!started)
273
+ release();
274
+ }
275
+ // The simulator WCV we currently have a debugger session on, our broker
276
+ // lease for it, and the per-attach teardown (message listener). Null when
277
+ // not attached.
244
278
  let simWc = null;
279
+ let simLease = null;
245
280
  let attachDisposables = null;
281
+ // Render-host guests wired for capture: wc.id → SYNCHRONOUS per-guest
282
+ // teardown (message listener + broker lease). Attach/detach ownership for
283
+ // these sessions lives entirely in the shared broker now (see cdp-session/
284
+ // index.ts) — this map only tracks OUR OWN wiring (the 'message' listener
285
+ // wireNetworkCapture installs), not who may detach the underlying session.
286
+ const guestWired = new Map();
287
+ // wc.id → the generation token of the most recently scheduled render-guest
288
+ // retry (acquire refused, or an established session got detached).
289
+ // `guestWired` alone cannot de-duplicate repeat `attachRenderGuest` calls
290
+ // during this window — it is only populated on a SUCCESSFUL acquire — so
291
+ // without this, calling `attachRenderGuest(wc)` again while the exclusive
292
+ // holder keeps refusing spawns a second, independent 300ms retry chain.
293
+ // A bare presence flag is NOT enough: a stale timer from an EARLIER retry
294
+ // cycle (superseded by an intervening successful reattach, itself followed
295
+ // by a fresh detach that scheduled its own new retry) would, on firing,
296
+ // unconditionally clear whatever is recorded — including that newer retry's
297
+ // own marker — and then reschedule itself, forking back into two parallel
298
+ // chains. A monotonic generation token lets a fired timer recognize its own
299
+ // staleness (its captured token no longer matches the current one) and
300
+ // no-op instead of touching state it no longer owns.
301
+ const guestRetryGeneration = new Map();
302
+ let nextRenderGuestRetryGeneration = 0;
303
+ // Own (and dispose on this forwarder's own dispose()) a private broker only
304
+ // when the caller didn't supply a shared one.
305
+ const ownsBroker = !bridge.broker;
306
+ const broker = bridge.broker ??
307
+ createCdpSessionBroker({ connections: bridge.connections });
246
308
  // The DevTools front-end host wc (primary sink), set by the ViewManager.
247
309
  let devtoolsWc = null;
248
310
  // Teardown for the wc 'destroyed' watcher on the current host (clears the host).
249
311
  let devtoolsHostDisposable = null;
312
+ // ── Global mirror sink ──────────────────────────────────────────────────────
313
+ // The standalone internal (app-wide) DevTools window's front-end host wc.
314
+ // Deliberately NO probing/ready/degraded state machine and NO console
315
+ // fallback — see setGlobalDevtoolsHost's doc comment.
316
+ let globalDevtoolsWc = null;
317
+ let globalDevtoolsHostDisposable = null;
318
+ // Dedicated network-only outbound gate (body/post-data lookups) for the
319
+ // CURRENT global host — never elements-forward's gate, see
320
+ // global-body-gate.ts's header for why. Stopped whenever the host changes
321
+ // or clears.
322
+ let globalBodyGateStop = null;
323
+ // Bounded in-order hold for global-mirror messages arriving while the
324
+ // global host's front-end has not settled yet (its boot window) — without
325
+ // it those events are silently unrecoverable, breaking the window's
326
+ // full-stream promise. Scoped strictly to the CURRENT host: cleared when
327
+ // the host changes/clears, never replayed into a different host.
328
+ const MAX_GLOBAL_PENDING_QUEUE = 2000;
329
+ let globalPendingQueue = [];
330
+ let globalPendingOverflowWarned = false;
331
+ let globalFlushTimer = null;
332
+ // Wall-clock bound on the settle-poll (not on the queue itself): a
333
+ // front-end that never settles stops being polled after this, and the
334
+ // still-held queue flushes via the next incoming event instead.
335
+ const GLOBAL_FLUSH_POLL_MAX_MS = 60_000;
336
+ function clearGlobalPending() {
337
+ globalPendingQueue = [];
338
+ globalPendingOverflowWarned = false;
339
+ if (globalFlushTimer) {
340
+ clearTimeout(globalFlushTimer);
341
+ globalFlushTimer = null;
342
+ }
343
+ }
344
+ function scheduleGlobalFlush(startedAt = Date.now()) {
345
+ if (globalFlushTimer)
346
+ return;
347
+ if (Date.now() - startedAt >= GLOBAL_FLUSH_POLL_MAX_MS)
348
+ return;
349
+ // unref'd like scheduleRenderGuestRetry's timer: a settle-poll must
350
+ // never be what keeps the process (or a test file) alive.
351
+ globalFlushTimer = setTimeout(() => {
352
+ globalFlushTimer = null;
353
+ if (globalPendingQueue.length === 0)
354
+ return;
355
+ if (!globalDevtoolsWc || globalDevtoolsWc.isDestroyed()) {
356
+ clearGlobalPending();
357
+ return;
358
+ }
359
+ if (!isFrontendSettled(globalDevtoolsWc)) {
360
+ scheduleGlobalFlush(startedAt);
361
+ return;
362
+ }
363
+ flushGlobalPending();
364
+ }, READY_RETRY_MS);
365
+ globalFlushTimer.unref?.();
366
+ }
367
+ /** Drain the pending queue into the (settled) global host, oldest first,
368
+ * packed greedily under the same per-script size bound as the native sink —
369
+ * and through the SAME chunked transport for messages past
370
+ * MAX_SINGLE_DISPATCH_CHARS: a single oversized message stitched into one
371
+ * giant executeJavaScript overflows the IPC/script limit and the whole
372
+ * call rejects into a silent drop. */
373
+ function flushGlobalPending() {
374
+ if (globalPendingQueue.length === 0)
375
+ return;
376
+ const target = globalDevtoolsWc;
377
+ if (!target || target.isDestroyed()) {
378
+ clearGlobalPending();
379
+ return;
380
+ }
381
+ while (globalPendingQueue.length > 0) {
382
+ const { batch, chunked, remaining } = packDispatchBatch(globalPendingQueue, MAX_SINGLE_DISPATCH_CHARS, MAX_BATCH_CHARS);
383
+ globalPendingQueue = remaining;
384
+ for (const msg of chunked)
385
+ dispatchChunked(target, msg);
386
+ if (batch.length > 0) {
387
+ target.executeJavaScript(buildDispatchScript(batch), true).catch(() => {
388
+ /* best-effort */
389
+ });
390
+ }
391
+ }
392
+ globalPendingOverflowWarned = false;
393
+ }
394
+ function enqueueGlobalPending(json) {
395
+ if (globalPendingQueue.length >= MAX_GLOBAL_PENDING_QUEUE) {
396
+ globalPendingQueue.shift();
397
+ // Not silent: dropping past the cap means the window's full-stream
398
+ // promise is degraded — say so once per overflow episode, not per event.
399
+ if (!globalPendingOverflowWarned) {
400
+ globalPendingOverflowWarned = true;
401
+ console.warn(`[network-forward] global mirror queue overflow (cap ${MAX_GLOBAL_PENDING_QUEUE}): dropping oldest events while the debug window front-end is still loading`);
402
+ }
403
+ }
404
+ globalPendingQueue.push(json);
405
+ scheduleGlobalFlush();
406
+ }
407
+ function applyGlobalDevtoolsHost(host) {
408
+ globalDevtoolsHostDisposable?.dispose();
409
+ globalDevtoolsHostDisposable = null;
410
+ globalBodyGateStop?.();
411
+ globalBodyGateStop = null;
412
+ // Queued events belong to the PREVIOUS host's boot window — never carry
413
+ // them across a host change (or into "no host").
414
+ clearGlobalPending();
415
+ globalDevtoolsWc = host && !host.isDestroyed() ? host : null;
416
+ if (!globalDevtoolsWc)
417
+ return;
418
+ const target = globalDevtoolsWc;
419
+ const onHostDestroyed = () => {
420
+ if (globalDevtoolsWc === target)
421
+ globalDevtoolsWc = null;
422
+ globalBodyGateStop?.();
423
+ globalBodyGateStop = null;
424
+ clearGlobalPending();
425
+ };
426
+ const reg = bridge.connections;
427
+ if (reg && typeof target.once === "function") {
428
+ const owned = reg.acquire(target).own(onHostDestroyed);
429
+ globalDevtoolsHostDisposable = toDisposable(() => owned.dispose());
430
+ }
431
+ else {
432
+ if (typeof target.once === "function")
433
+ target.once("destroyed", onHostDestroyed);
434
+ globalDevtoolsHostDisposable = toDisposable(() => {
435
+ try {
436
+ target.removeListener?.("destroyed", onHostDestroyed);
437
+ }
438
+ catch {
439
+ /* gone */
440
+ }
441
+ });
442
+ }
443
+ globalBodyGateStop = installGlobalNetworkBodyGate(target, {
444
+ getResponseBody: (requestId) => bodyCache.lookup(requestId),
445
+ getRequestPostData: (requestId) => postDataCache.lookup(requestId),
446
+ });
447
+ }
448
+ /** Best-effort mirror of one raw CDP message into the global host. */
449
+ function dispatchToGlobal(method, params) {
450
+ if (!globalDevtoolsWc || globalDevtoolsWc.isDestroyed())
451
+ return;
452
+ let json;
453
+ try {
454
+ json = JSON.stringify({ method, params });
455
+ }
456
+ catch {
457
+ return;
458
+ }
459
+ // Same settled gate every other injection point in this file uses: an
460
+ // unsettled front-end wipes its state on load anyway, and executeJavaScript
461
+ // against it queues one did-stop-loading waiter per call — piling toward
462
+ // the MaxListeners ceiling during the global host's boot window. Unlike
463
+ // those injection points, the events here are NOT re-derivable later, so
464
+ // they queue for a post-settle flush instead of dropping.
465
+ if (!isFrontendSettled(globalDevtoolsWc)) {
466
+ enqueueGlobalPending(json);
467
+ return;
468
+ }
469
+ // Anything still queued flushes first so delivery order matches arrival.
470
+ flushGlobalPending();
471
+ // Oversized single messages take the chunked transport, mirroring the
472
+ // native sink's flushDispatch — see flushGlobalPending's doc.
473
+ if (json.length > MAX_SINGLE_DISPATCH_CHARS) {
474
+ dispatchChunked(globalDevtoolsWc, json);
475
+ return;
476
+ }
477
+ globalDevtoolsWc
478
+ .executeJavaScript(buildDispatchScript([json]), true)
479
+ .catch(() => {
480
+ /* best-effort */
481
+ });
482
+ }
250
483
  // ── Native-sink state machine ─────────────────────────────────────────────
251
- let sink = 'idle';
484
+ let sink = "idle";
252
485
  // Buffered completed-request records while 'probing' — flushed to console if we
253
486
  // degrade, dropped if we go ready (so a request shows in exactly one sink).
254
487
  let probeConsoleBuffer = [];
@@ -276,22 +509,22 @@ export function createNetworkForwarder(bridge) {
276
509
  }
277
510
  /** Enter the 'probing' state and arm the ready-timeout (idempotent). */
278
511
  function beginProbing() {
279
- if (sink === 'probing')
512
+ if (sink === "probing")
280
513
  return;
281
- sink = 'probing';
514
+ sink = "probing";
282
515
  probeDeadline = Date.now() + DEVTOOLS_READY_TIMEOUT_MS;
283
516
  if (readyTimeoutTimer)
284
517
  clearTimeout(readyTimeoutTimer);
285
518
  readyTimeoutTimer = setTimeout(() => {
286
519
  readyTimeoutTimer = null;
287
520
  // Still not ready after the grace period → abandon native for this host.
288
- if (sink === 'probing')
521
+ if (sink === "probing")
289
522
  degradeToConsole();
290
523
  }, DEVTOOLS_READY_TIMEOUT_MS);
291
524
  }
292
525
  /** Native path confirmed live: console buffer is moot, drop it. */
293
526
  function markReady() {
294
- sink = 'ready';
527
+ sink = "ready";
295
528
  probeDeadline = null;
296
529
  if (readyTimeoutTimer) {
297
530
  clearTimeout(readyTimeoutTimer);
@@ -310,7 +543,7 @@ export function createNetworkForwarder(bridge) {
310
543
  * and flush the buffered completed-request records to the console sink.
311
544
  */
312
545
  function degradeToConsole() {
313
- sink = 'degraded';
546
+ sink = "degraded";
314
547
  probeDeadline = null;
315
548
  dispatchQueue = [];
316
549
  if (readyTimeoutTimer) {
@@ -333,14 +566,17 @@ export function createNetworkForwarder(bridge) {
333
566
  queueMicrotask(flushDispatch);
334
567
  }
335
568
  /** Trim the native queue to its cap, preferring to keep request-opening events.
336
- * Active requests' first events (requestWillBeSent / ...ExtraInfo) are retained
337
- * so later responseReceived/loadingFinished never become orphans in the panel;
338
- * we drop the oldest NON-opening (low-value / completion) events first. */
569
+ * Active requests' first events (requestWillBeSent / ...ExtraInfo /
570
+ * webSocketCreated) are retained so later responseReceived/loadingFinished/
571
+ * webSocket* events never become orphans in the panel (the front-end
572
+ * silently drops events for an unknown requestId); we drop the oldest
573
+ * NON-opening (low-value / completion) events first. */
339
574
  function trimQueue() {
340
575
  if (dispatchQueue.length <= MAX_DISPATCH_QUEUE)
341
576
  return;
342
- const isOpener = (json) => json.includes('"Network.requestWillBeSent"')
343
- || json.includes('"Network.requestWillBeSentExtraInfo"');
577
+ const isOpener = (json) => json.includes('"Network.requestWillBeSent"') ||
578
+ json.includes('"Network.requestWillBeSentExtraInfo"') ||
579
+ json.includes('"Network.webSocketCreated"');
344
580
  // First pass: drop oldest non-opener events.
345
581
  const kept = [];
346
582
  let over = dispatchQueue.length - MAX_DISPATCH_QUEUE;
@@ -363,7 +599,7 @@ export function createNetworkForwarder(bridge) {
363
599
  flushScheduled = false;
364
600
  if (dispatchQueue.length === 0)
365
601
  return;
366
- if (sink === 'degraded') {
602
+ if (sink === "degraded") {
367
603
  dispatchQueue = [];
368
604
  return;
369
605
  }
@@ -386,7 +622,7 @@ export function createNetworkForwarder(bridge) {
386
622
  scheduleReadyRetry();
387
623
  return;
388
624
  }
389
- if (sink === 'idle')
625
+ if (sink === "idle")
390
626
  beginProbing();
391
627
  // Pack greedily up to MAX_BATCH_CHARS so one executeJavaScript stays sized;
392
628
  // oversized single messages go via the chunked transport (see packDispatchBatch).
@@ -415,24 +651,26 @@ export function createNetworkForwarder(bridge) {
415
651
  dispatchQueue = remaining;
416
652
  if (remaining.length > 0)
417
653
  scheduleFlush();
418
- wc.executeJavaScript(script, true).then((ok) => {
654
+ wc.executeJavaScript(script, true)
655
+ .then((ok) => {
419
656
  if (ok === true) {
420
- if (sink !== 'ready')
657
+ if (sink !== "ready")
421
658
  markReady();
422
659
  return;
423
660
  }
424
661
  // API not present yet — front-end still booting. Re-queue this batch ahead
425
662
  // of any newer events and poll until ready (or the timeout degrades us).
426
- if (sink === 'degraded')
663
+ if (sink === "degraded")
427
664
  return;
428
665
  dispatchQueue = batch.concat(dispatchQueue);
429
666
  trimQueue();
430
667
  scheduleReadyRetry();
431
- }).catch(() => {
668
+ })
669
+ .catch(() => {
432
670
  // wc navigated / torn down mid-call, OR the script overflowed IPC. Re-queue
433
671
  // (bounded) and let the next flush re-resolve the host. Best-effort; the
434
672
  // ready-timeout still governs giving up. Backoff is via the retry timer.
435
- if (sink === 'degraded')
673
+ if (sink === "degraded")
436
674
  return;
437
675
  dispatchQueue = batch.concat(dispatchQueue);
438
676
  trimQueue();
@@ -452,10 +690,12 @@ export function createNetworkForwarder(bridge) {
452
690
  catch {
453
691
  return;
454
692
  }
455
- wc.executeJavaScript(script, true).catch(() => { });
693
+ wc.executeJavaScript(script, true).catch(() => {
694
+ /* best-effort */
695
+ });
456
696
  }
457
697
  function scheduleReadyRetry() {
458
- if (readyRetryTimer || sink === 'ready' || sink === 'degraded')
698
+ if (readyRetryTimer || sink === "ready" || sink === "degraded")
459
699
  return;
460
700
  // Self-terminate on the same deadline readyTimeoutTimer enforces, instead of
461
701
  // trusting that timer to win a same-instant race against this one (see the
@@ -466,7 +706,7 @@ export function createNetworkForwarder(bridge) {
466
706
  }
467
707
  readyRetryTimer = setTimeout(() => {
468
708
  readyRetryTimer = null;
469
- if (sink === 'degraded')
709
+ if (sink === "degraded")
470
710
  return;
471
711
  if (probeDeadline !== null && Date.now() >= probeDeadline) {
472
712
  degradeToConsole();
@@ -483,9 +723,9 @@ export function createNetworkForwarder(bridge) {
483
723
  // requests — queueing them would let them double-render if a host later
484
724
  // arrives / swaps in. resolveDevtoolsWc() guards the idle case where a host
485
725
  // is set on the bridge but applyDevtoolsHost hasn't run yet.
486
- if (sink === 'degraded')
726
+ if (sink === "degraded")
487
727
  return;
488
- if (sink === 'idle' && !resolveDevtoolsWc())
728
+ if (sink === "idle" && !resolveDevtoolsWc())
489
729
  return;
490
730
  let json;
491
731
  try {
@@ -514,20 +754,39 @@ export function createNetworkForwarder(bridge) {
514
754
  }
515
755
  wc.executeJavaScript(script, true).catch(() => { });
516
756
  }
757
+ /**
758
+ * Stop USING the simulator wc's session. Releases our own lease (message
759
+ * listener + Network capture wiring) but never forces a physical
760
+ * `debugger.detach()` — this same simulator wc's debugger session is also
761
+ * independently used by simulator-storage (DOMStorage capture), and an
762
+ * unconditional detach here would kill ITS capture too, exactly like an
763
+ * unconditional detach on ITS side would kill ours. The broker (see
764
+ * cdp-session/index.ts) is the only thing that decides when an actual
765
+ * detach happens (wc destroy, or its own top-level dispose() for a
766
+ * self-attached session) — never a single consumer switching away.
767
+ */
517
768
  function detachSimulator() {
518
- const wc = simWc;
519
769
  simWc = null;
770
+ const lease = simLease;
771
+ simLease = null;
520
772
  const ad = attachDisposables;
521
773
  attachDisposables = null;
522
- if (ad)
523
- void ad.disposeAll().catch(() => { });
774
+ if (ad) {
775
+ try {
776
+ ad.disposeAll();
777
+ }
778
+ catch {
779
+ /* best-effort teardown */
780
+ }
781
+ }
782
+ lease?.dispose();
524
783
  // Drop any queued-but-unflushed messages so a new attach starts clean, and
525
784
  // reset the native-sink state machine (the next attach re-probes the host).
526
785
  dispatchQueue = [];
527
786
  probeConsoleBuffer = [];
528
787
  // A new simulator attach restarts the native sink: 'idle' until the next
529
788
  // event re-probes the (possibly already-set) host.
530
- sink = 'idle';
789
+ sink = "idle";
531
790
  probeDeadline = null;
532
791
  if (readyRetryTimer) {
533
792
  clearTimeout(readyRetryTimer);
@@ -537,141 +796,403 @@ export function createNetworkForwarder(bridge) {
537
796
  clearTimeout(readyTimeoutTimer);
538
797
  readyTimeoutTimer = null;
539
798
  }
540
- if (!wc || wc.isDestroyed())
541
- return;
542
- try {
543
- if (wc.debugger.isAttached())
544
- wc.debugger.detach();
545
- }
546
- catch { /* already detached / mid-destroy */ }
547
799
  }
548
- function attachSimulator(wc) {
549
- if (!wc || wc.isDestroyed())
550
- return;
551
- if (simWc === wc && !wc.isDestroyed())
552
- return;
553
- detachSimulator();
554
- try {
555
- if (!wc.debugger.isAttached()) {
556
- wc.debugger.attach('1.3');
557
- }
558
- }
559
- catch (err) {
560
- console.warn('[network-forward] debugger.attach failed; simulator network not captured:', err instanceof Error ? err.message : err);
561
- return;
562
- }
563
- simWc = wc;
564
- const attach = new DisposableRegistry();
565
- attachDisposables = attach;
566
- // Per-attach requestId namespace: a fresh epoch each attach guarantees ids
567
- // from a previous simulator session can never collide with this one.
568
- const ns = new RequestIdNamespace(String(Date.now()));
800
+ /**
801
+ * Wire one wc's already-usable debugger session for Network capture: a fresh
802
+ * per-attach requestId namespace (epochs keep ids from ever colliding across
803
+ * sources/attaches), the raw-event forward into the DevTools front-end, the
804
+ * loadingFinished-time body/post-data prefetch, and the console-fallback
805
+ * bookkeeping (tagged with `source`). Shared verbatim by the simulator
806
+ * attach (owner semantics) and every render-guest attach (shared-session
807
+ * semantics) — the capture pipeline is identical, only session ownership
808
+ * differs. The 'message' listener teardown is registered on `attach`.
809
+ */
810
+ function wireNetworkCapture(wc, source, attach, epoch) {
811
+ const ns = new RequestIdNamespace(epoch);
569
812
  // Fallback bookkeeping: requestId → in-flight request, for the console line
570
813
  // when the native dispatch path is unusable.
571
814
  const pending = new Map();
815
+ // User-facing classification, decided ONCE at
816
+ // requestWillBeSent (the only event carrying a url) and consulted by every
817
+ // later event on the same rawId — later events (responseReceived/
818
+ // loadingFinished/loadingFailed) carry no url, so they cannot re-derive
819
+ // the verdict; they must remember it.
820
+ const userFacingByRawId = new Map();
821
+ // Raw ids whose requestWillBeSent announced a post body that was NOT
822
+ // inlined (`hasPostData` without `postData`) — the only case the front-end
823
+ // round-trips `Network.getRequestPostData`. Consumed at loadingFinished.
824
+ const postDataWanted = new Set();
825
+ /**
826
+ * Prefetch the response body (and, when flagged, the post data) from this
827
+ * wc's CDP session the moment the request finishes — the renderer may
828
+ * evict the resource soon after, so a click-time fetch would race it.
829
+ * Gated exactly like event forwarding: with no devtools host to serve
830
+ * (idle-without-host / degraded) there is no panel to click, so buffering
831
+ * bodies would only burn memory.
832
+ */
833
+ const prefetchBodies = (rawId, encodedDataLength) => {
834
+ // Skip only when NEITHER consumer can use the body: the user-facing
835
+ // sink is unusable (degraded, or idle with no host) AND the global
836
+ // mirror has no host either. Either alone is enough reason to prefetch
837
+ // — global-body-gate answers Network.getResponseBody/getRequestPostData
838
+ // from this same cache, and it must not 404 just because the
839
+ // user-facing sink happens to be closed/degraded.
840
+ const userSinkUsable = sink !== "degraded" && !(sink === "idle" && !resolveDevtoolsWc());
841
+ const globalUsable = globalDevtoolsWc !== null && !globalDevtoolsWc.isDestroyed();
842
+ if (!userSinkUsable && !globalUsable)
843
+ return;
844
+ const virtualId = ns.resolve(rawId);
845
+ // Skip the CDP round-trip entirely for a response already known (from
846
+ // the wire size CDP reports at completion) to exceed the cache's own
847
+ // per-entry ceiling — no point materializing a full body into main-
848
+ // process memory just to have the cache reject it after the fact. This
849
+ // is a best-effort heuristic (encodedDataLength is the ON-THE-WIRE size;
850
+ // a decompressed body can be larger), not a hard guarantee — the
851
+ // concurrency cap below is what actually bounds worst-case memory.
852
+ const knownOversized = typeof encodedDataLength === "number" &&
853
+ encodedDataLength > DEFAULT_PER_ENTRY_MAX_CHARS;
854
+ if (!knownOversized) {
855
+ primeWithAdmission(bodyCache, virtualId, async () => {
856
+ const raw = await wc.debugger.sendCommand("Network.getResponseBody", { requestId: rawId });
857
+ const r = raw;
858
+ if (!r || typeof r.body !== "string")
859
+ throw new Error("response body unavailable");
860
+ return { body: r.body, base64Encoded: r.base64Encoded === true };
861
+ });
862
+ }
863
+ if (!postDataWanted.delete(rawId))
864
+ return;
865
+ primeWithAdmission(postDataCache, virtualId, async () => {
866
+ const raw = await wc.debugger.sendCommand("Network.getRequestPostData", { requestId: rawId });
867
+ const r = raw;
868
+ if (!r || typeof r.postData !== "string")
869
+ throw new Error("post data unavailable");
870
+ return { postData: r.postData };
871
+ });
872
+ };
873
+ // ── Fallback bookkeeping handlers — one per Network.* method, each kept
874
+ // simple so `onMessage` itself stays a flat dispatch (not a branchy switch). ──
875
+ function onRequestWillBeSent(params) {
876
+ const p = params;
877
+ if (!p?.request)
878
+ return;
879
+ if (pending.size >= MAX_PENDING)
880
+ pending.clear();
881
+ pending.set(p.requestId, {
882
+ url: p.request.url,
883
+ method: p.request.method,
884
+ status: 0,
885
+ });
886
+ // A body announced but not inlined is the one case the panel will
887
+ // round-trip `Network.getRequestPostData` — flag it for prefetch.
888
+ if (p.request.hasPostData === true &&
889
+ typeof p.request.postData !== "string") {
890
+ if (postDataWanted.size >= MAX_PENDING)
891
+ postDataWanted.clear();
892
+ postDataWanted.add(p.requestId);
893
+ }
894
+ }
895
+ function onResponseReceived(params) {
896
+ const p = params;
897
+ const req = pending.get(p.requestId);
898
+ if (req)
899
+ req.status = p.response?.status ?? 0;
900
+ }
901
+ /** Shared "request terminated" bookkeeping for both loadingFinished and
902
+ * loadingFailed: drop the classification + pending record, returning the
903
+ * pending entry (or undefined if it was already gone/never seen). */
904
+ function retirePending(rawId) {
905
+ userFacingByRawId.delete(rawId);
906
+ const req = pending.get(rawId);
907
+ if (!req)
908
+ return undefined;
909
+ pending.delete(rawId);
910
+ return req;
911
+ }
912
+ function onLoadingFinished(params) {
913
+ const p = params;
914
+ if (typeof p?.requestId === "string")
915
+ prefetchBodies(p.requestId, p.encodedDataLength);
916
+ const req = retirePending(p?.requestId);
917
+ if (!req)
918
+ return;
919
+ maybeFallback({
920
+ source,
921
+ url: req.url,
922
+ method: req.method,
923
+ status: req.status,
924
+ });
925
+ }
926
+ function onLoadingFailed(params) {
927
+ const p = params;
928
+ postDataWanted.delete(p?.requestId);
929
+ const req = retirePending(p?.requestId);
930
+ if (!req)
931
+ return;
932
+ maybeFallback({
933
+ source,
934
+ url: req.url,
935
+ method: req.method,
936
+ status: req.status,
937
+ errorText: p.canceled ? "canceled" : p.errorText || "failed",
938
+ });
939
+ }
940
+ const FALLBACK_HANDLERS = {
941
+ "Network.requestWillBeSent": onRequestWillBeSent,
942
+ "Network.responseReceived": onResponseReceived,
943
+ "Network.loadingFinished": onLoadingFinished,
944
+ "Network.loadingFailed": onLoadingFailed,
945
+ };
946
+ /**
947
+ * Resolve (and, on `requestWillBeSent`, record) whether `rawId` is
948
+ * user-facing. MUST be computed eagerly here — not deferred to
949
+ * `onRequestWillBeSent` below, which runs AFTER this in `onMessage` — a
950
+ * `requestWillBeSent` is itself a FORWARDED_METHODS event whose own
951
+ * routing decision depends on the very verdict it establishes. Every
952
+ * later event for the same rawId (no url of its own) just looks up the
953
+ * recorded verdict; an unknown rawId fails open (user-facing) rather
954
+ * than silently hiding it.
955
+ */
956
+ function resolveUserFacing(method, params, rawId) {
957
+ if (method === "Network.requestWillBeSent") {
958
+ const url = params?.request
959
+ ?.url;
960
+ const verdict = typeof url === "string"
961
+ ? isUserFacingRequest(url, [
962
+ bridge.getResourceServerBaseUrl?.(),
963
+ bridge.getSimulatorServerBaseUrl?.(),
964
+ ])
965
+ : true;
966
+ if (rawId) {
967
+ if (userFacingByRawId.size >= MAX_PENDING)
968
+ userFacingByRawId.clear();
969
+ userFacingByRawId.set(rawId, verdict);
970
+ }
971
+ return verdict;
972
+ }
973
+ if (rawId && userFacingByRawId.has(rawId))
974
+ return userFacingByRawId.get(rawId);
975
+ // `Network.requestWillBeSentExtraInfo` can arrive BEFORE its own
976
+ // `requestWillBeSent` (real CDP ordering) — it carries no url, so an
977
+ // unrecorded rawId here means "classification not yet known", not
978
+ // "capture attached mid-flight". Fail CLOSED for this one case: the
979
+ // very next event for the same rawId is (almost always) the real
980
+ // requestWillBeSent, which establishes the true verdict immediately —
981
+ // dropping this one early line is a far smaller cost than leaking an
982
+ // internal resource's ExtraInfo into the user-facing panel before its
983
+ // classification is even known. Every OTHER "rawId never seen" case
984
+ // (e.g. capture attached mid-flight, no requestWillBeSent ever coming)
985
+ // keeps failing open — there is no better signal available there.
986
+ if (method === "Network.requestWillBeSentExtraInfo")
987
+ return false;
988
+ return true;
989
+ }
572
990
  const onMessage = (_event, method, params) => {
573
991
  // ── Primary sink: forward the raw CDP event into the DevTools front-end ──
574
992
  if (FORWARDED_METHODS.has(method)) {
575
993
  const rewritten = rewriteRequestId(method, params, ns);
576
- enqueueNative(rewritten.method, rewritten.params);
994
+ // Global mirror: full, unfiltered — independent of the
995
+ // user-facing sink's classification and state machine.
996
+ dispatchToGlobal(rewritten.method, rewritten.params);
997
+ // User-facing sink: only requests isUserFacingRequest() judged as the
998
+ // developer's own business traffic — internal/framework resource
999
+ // loads are mirrored to the global window ONLY.
1000
+ const rawId = params
1001
+ ?.requestId;
1002
+ const userFacing = resolveUserFacing(method, params, typeof rawId === "string" ? rawId : undefined);
1003
+ if (userFacing) {
1004
+ enqueueNative(rewritten.method, rewritten.params);
1005
+ }
577
1006
  }
578
1007
  else if (REWRITE_REQUEST_ID_METHODS.has(method)) {
579
- // Methods we namespace but don't forward (dataReceived, 二期): still
580
- // resolve so the id mapping stays coherent if forwarding is added later.
1008
+ // Methods we namespace but don't forward (dataReceived): still resolve
1009
+ // so the id mapping stays coherent if forwarding is added later.
581
1010
  rewriteRequestId(method, params, ns);
582
1011
  }
583
1012
  // ── Fallback bookkeeping (used only when native dispatch is unusable) ──
584
- switch (method) {
585
- case 'Network.requestWillBeSent': {
586
- const p = params;
587
- if (!p?.request)
588
- return;
589
- if (pending.size >= MAX_PENDING)
590
- pending.clear();
591
- pending.set(p.requestId, { url: p.request.url, method: p.request.method, status: 0 });
592
- break;
593
- }
594
- case 'Network.responseReceived': {
595
- const p = params;
596
- const req = pending.get(p.requestId);
597
- if (req)
598
- req.status = p.response?.status ?? 0;
599
- break;
600
- }
601
- case 'Network.loadingFinished': {
602
- const p = params;
603
- const req = pending.get(p.requestId);
604
- if (!req)
605
- return;
606
- pending.delete(p.requestId);
607
- maybeFallback({ source: 'service', url: req.url, method: req.method, status: req.status });
608
- break;
609
- }
610
- case 'Network.loadingFailed': {
611
- const p = params;
612
- const req = pending.get(p.requestId);
613
- if (!req)
614
- return;
615
- pending.delete(p.requestId);
616
- maybeFallback({
617
- source: 'service',
618
- url: req.url,
619
- method: req.method,
620
- status: req.status,
621
- errorText: p.canceled ? 'canceled' : (p.errorText || 'failed'),
622
- });
623
- break;
624
- }
625
- default:
626
- break;
627
- }
1013
+ FALLBACK_HANDLERS[method]?.(params);
628
1014
  };
629
- wc.debugger.on('message', onMessage);
1015
+ wc.debugger.on("message", onMessage);
630
1016
  attach.add(() => {
631
1017
  try {
632
- wc.debugger.removeListener('message', onMessage);
1018
+ wc.debugger.removeListener("message", onMessage);
633
1019
  }
634
- catch { /* wc gone */ }
635
- });
636
- const onDetach = () => { if (simWc === wc) {
637
- simWc = null;
638
- } };
639
- wc.debugger.on('detach', onDetach);
640
- attach.add(() => {
641
- try {
642
- wc.debugger.removeListener('detach', onDetach);
1020
+ catch {
1021
+ /* wc gone */
643
1022
  }
644
- catch { /* wc gone */ }
645
1023
  });
646
- const onDestroyed = () => {
647
- if (simWc === wc) {
648
- simWc = null;
649
- const ad = attachDisposables;
650
- attachDisposables = null;
651
- if (ad)
652
- void ad.disposeAll().catch(() => { });
653
- }
654
- };
655
- // Route the destroyed-teardown through the connection registry when one is
656
- // available (deterministic disposal on wc destroy / connection reset);
657
- // otherwise keep the bespoke `once('destroyed')` watcher. WHAT onDestroyed
658
- // does is unchanged only WHERE it's registered.
659
- const reg = bridge.connections;
660
- if (reg) {
661
- const owned = reg.acquire(wc).own(onDestroyed);
662
- attach.add(() => owned.dispose());
1024
+ }
1025
+ /** Drop one guest's wiring (message listener + broker lease). Attach/detach
1026
+ * ownership of the underlying session is entirely the broker's concern. */
1027
+ function cleanupGuest(wcId) {
1028
+ const teardown = guestWired.get(wcId);
1029
+ guestWired.delete(wcId);
1030
+ teardown?.disposeAll();
1031
+ }
1032
+ /**
1033
+ * A hot-reload respawn replaces the render-host `<webview>` guest with a
1034
+ * fresh one (see simulator-app.tsx's ready-then-swap session commit); the
1035
+ * OLD guest's wc is closed asynchronously as part of the old session's
1036
+ * teardown (bridge-router's `closeSessionPages`), so there is a window
1037
+ * where `wc.isDestroyed()` is still `false` but the debugger session is
1038
+ * already gone (Electron reports `detach` with reason `"target closed"` —
1039
+ * confirmed via instrumentation against a real respawn: every detach in
1040
+ * that run carried this exact reason, one per closing guest, never a
1041
+ * repeated storm on one guest). Retrying the re-attach INLINE on every
1042
+ * `detach` (no delay) raced that async teardown: each re-attach attempt
1043
+ * landed inside the still-closing window and immediately produced another
1044
+ * `detach`, self-sustaining a tight synchronous loop (confirmed by
1045
+ * instrumentation: thousands of attach/detach cycles within a couple
1046
+ * seconds) until the wc finally finished closing — there is no public
1047
+ * Electron event for "this wc's close is about to complete", so the only
1048
+ * available signal to end the loop is `wc.isDestroyed()` itself, which the
1049
+ * retry already checks. This delay does not wait for any timing to
1050
+ * "settle" — it only paces the retry slower than the async close, so the
1051
+ * loop's real termination check (`wc.isDestroyed()` / `disposed`) has a
1052
+ * chance to observe the close having finished before the next attempt.
1053
+ */
1054
+ const RENDER_GUEST_REATTACH_DELAY_MS = 300;
1055
+ /**
1056
+ * Schedule one more `wireRenderGuest(wc)` attempt after
1057
+ * `RENDER_GUEST_REATTACH_DELAY_MS`, guarded so a guest that has since been
1058
+ * destroyed (or a forwarder that has since been disposed) never gets a
1059
+ * stray retry. Shared by both places capture can fail to be wired: an
1060
+ * established session later getting detached, AND `broker.acquire()`
1061
+ * itself refusing on the very first attempt (the session is exclusively
1062
+ * held elsewhere, e.g. a real Chrome DevTools window) — that second case
1063
+ * used to give up permanently instead of retrying, so capture never
1064
+ * recovered even once the holder released it.
1065
+ *
1066
+ * De-duplicated via `guestRetryGeneration`: a repeat `attachRenderGuest(wc)`
1067
+ * call while one guest's retry is already pending must join the SAME
1068
+ * chain, not start an independent second one — `guestWired` alone cannot
1069
+ * catch this because it is only populated once acquire actually succeeds.
1070
+ * Each schedule mints a fresh generation token; the fired timer only acts
1071
+ * if its captured token is still the current one, so a stale timer whose
1072
+ * generation has since been superseded silently no-ops instead of clearing
1073
+ * (and forking) a newer retry's bookkeeping.
1074
+ */
1075
+ function scheduleRenderGuestRetry(wc) {
1076
+ if (wc.isDestroyed())
1077
+ return;
1078
+ if (guestRetryGeneration.has(wc.id))
1079
+ return;
1080
+ const generation = ++nextRenderGuestRetryGeneration;
1081
+ guestRetryGeneration.set(wc.id, generation);
1082
+ const timer = setTimeout(() => {
1083
+ if (guestRetryGeneration.get(wc.id) !== generation)
1084
+ return;
1085
+ guestRetryGeneration.delete(wc.id);
1086
+ if (!disposed && !wc.isDestroyed())
1087
+ wireRenderGuest(wc);
1088
+ }, RENDER_GUEST_REATTACH_DELAY_MS);
1089
+ // Best-effort: if the whole forwarder tears down before this fires, there
1090
+ // is nothing to clear it from — the disposed check above is the guard.
1091
+ timer.unref?.();
1092
+ }
1093
+ /**
1094
+ * Wire one render guest for Network capture via the shared broker.
1095
+ *
1096
+ * The lease's `onDetach` fires on EITHER an external detach (another owner
1097
+ * releasing the shared session, or a real Chrome DevTools window stealing
1098
+ * it) OR the guest being destroyed (see cdp-session/index.ts) — either way
1099
+ * our wiring is torn down and, if the guest is still alive, re-wired (after
1100
+ * `RENDER_GUEST_REATTACH_DELAY_MS`) so capture self-heals. Previously
1101
+ * `attachRenderGuest` only ever ran ONCE per guest (at webview creation,
1102
+ * from `did-attach-webview`), so any detach permanently killed capture for
1103
+ * that guest's remaining lifetime — nothing else ever called it again.
1104
+ *
1105
+ * `guestWired` alone guards re-entry once successfully wired. A repeat call
1106
+ * while acquire keeps failing is deliberately NOT blocked here (only the
1107
+ * TIMER that call would schedule is de-duplicated, in
1108
+ * `scheduleRenderGuestRetry`) — an explicit re-attach right after a detach
1109
+ * must still get its own immediate `acquire()` attempt (the exclusive
1110
+ * holder may have already released it by then), not be forced to wait out
1111
+ * a stale pending window.
1112
+ */
1113
+ function wireRenderGuest(wc) {
1114
+ if (guestWired.has(wc.id))
1115
+ return;
1116
+ const lease = broker.acquire(wc);
1117
+ if (!lease) {
1118
+ // Not terminal — the exclusive holder may release the session later.
1119
+ // Retry on the same cadence as the onDetach self-heal below, or this
1120
+ // guest's capture would die for its whole remaining lifetime the very
1121
+ // first time acquire() lost the race.
1122
+ scheduleRenderGuestRetry(wc);
1123
+ return;
663
1124
  }
664
- else {
665
- wc.once('destroyed', onDestroyed);
666
- attach.add(() => {
1125
+ // This wc is now genuinely wired — drop any stale pending-retry
1126
+ // generation (e.g. from an earlier detach cycle superseded by THIS
1127
+ // successful acquire) so a LATER detach can freely mint its own fresh
1128
+ // generation instead of being silently swallowed by a leftover entry.
1129
+ guestRetryGeneration.delete(wc.id);
1130
+ const teardown = new SyncDisposableRegistry();
1131
+ guestWired.set(wc.id, teardown);
1132
+ wireNetworkCapture(wc, "render", teardown, `g${wc.id}-${Date.now()}`);
1133
+ const detachSub = lease.onDetach(() => {
1134
+ cleanupGuest(wc.id);
1135
+ scheduleRenderGuestRetry(wc);
1136
+ });
1137
+ teardown.add(() => {
1138
+ detachSub.dispose();
1139
+ lease.dispose();
1140
+ });
1141
+ void lease.send("Network.enable").catch((err) => {
1142
+ console.warn("[network-forward] guest Network.enable failed:", err instanceof Error ? err.message : err);
1143
+ });
1144
+ }
1145
+ function attachRenderGuest(wc) {
1146
+ if (!wc || wc.isDestroyed())
1147
+ return;
1148
+ wireRenderGuest(wc);
1149
+ }
1150
+ /**
1151
+ * Attach (or reuse) the simulator wc's shared session via the broker. The
1152
+ * SAME wc is independently used by simulator-storage (DOMStorage capture) —
1153
+ * `broker.acquire` reuses its session rather than fighting it for exclusive
1154
+ * ownership, and our own switch-away/dispose only ever releases OUR lease
1155
+ * (see detachSimulator), never forces a physical detach that would kill
1156
+ * simulator-storage's capture too.
1157
+ */
1158
+ function attachSimulator(wc) {
1159
+ if (!wc || wc.isDestroyed())
1160
+ return;
1161
+ if (simWc === wc && !wc.isDestroyed())
1162
+ return;
1163
+ detachSimulator();
1164
+ const lease = broker.acquire(wc);
1165
+ if (!lease) {
1166
+ console.warn("[network-forward] debugger session unavailable; simulator network not captured");
1167
+ return;
1168
+ }
1169
+ simWc = wc;
1170
+ simLease = lease;
1171
+ const attach = new SyncDisposableRegistry();
1172
+ attachDisposables = attach;
1173
+ wireNetworkCapture(wc, "service", attach, String(Date.now()));
1174
+ const detachSub = lease.onDetach(() => {
1175
+ if (simWc !== wc)
1176
+ return;
1177
+ simWc = null;
1178
+ simLease = null;
1179
+ // Tear down OUR OWN wiring (wireNetworkCapture's 'message' listener) —
1180
+ // the broker already removed its own; without this, our listener would
1181
+ // keep receiving events from a session we no longer track as "current".
1182
+ const ad = attachDisposables;
1183
+ attachDisposables = null;
1184
+ if (ad) {
667
1185
  try {
668
- wc.removeListener('destroyed', onDestroyed);
1186
+ ad.disposeAll();
669
1187
  }
670
- catch { /* wc gone */ }
671
- });
672
- }
673
- void wc.debugger.sendCommand('Network.enable').catch((err) => {
674
- console.warn('[network-forward] Network.enable failed:', err instanceof Error ? err.message : err);
1188
+ catch {
1189
+ /* best-effort teardown */
1190
+ }
1191
+ }
1192
+ });
1193
+ attach.add(() => detachSub.dispose());
1194
+ void lease.send("Network.enable").catch((err) => {
1195
+ console.warn("[network-forward] Network.enable failed:", err instanceof Error ? err.message : err);
675
1196
  });
676
1197
  }
677
1198
  /**
@@ -687,18 +1208,18 @@ export function createNetworkForwarder(bridge) {
687
1208
  // 'idle' but a host is resolvable (set via the bridge, applyDevtoolsHost not
688
1209
  // run): the native path is in play, so promote to 'probing' instead of
689
1210
  // console — otherwise this completion would later double-render natively.
690
- if (sink === 'idle' && resolveDevtoolsWc())
1211
+ if (sink === "idle" && resolveDevtoolsWc())
691
1212
  beginProbing();
692
1213
  switch (sink) {
693
- case 'ready':
1214
+ case "ready":
694
1215
  return;
695
- case 'probing':
1216
+ case "probing":
696
1217
  if (probeConsoleBuffer.length >= MAX_PENDING)
697
1218
  probeConsoleBuffer.shift();
698
1219
  probeConsoleBuffer.push(record);
699
1220
  return;
700
- case 'degraded':
701
- case 'idle':
1221
+ case "degraded":
1222
+ case "idle":
702
1223
  default:
703
1224
  forwardToConsole(record);
704
1225
  }
@@ -724,7 +1245,7 @@ export function createNetworkForwarder(bridge) {
724
1245
  if (!devtoolsWc) {
725
1246
  // No host → 'idle': completions go straight to console; native queue is
726
1247
  // moot, drop it so it can't double-render if a host later appears.
727
- sink = 'idle';
1248
+ sink = "idle";
728
1249
  dispatchQueue = [];
729
1250
  return;
730
1251
  }
@@ -732,7 +1253,9 @@ export function createNetworkForwarder(bridge) {
732
1253
  // WITHOUT touching view-manager (host-destroyed cleanup lives here). Begin
733
1254
  // probing and flush anything already queued.
734
1255
  const host = devtoolsWc;
735
- const onHostDestroyed = () => { applyDevtoolsHost(null); };
1256
+ const onHostDestroyed = () => {
1257
+ applyDevtoolsHost(null);
1258
+ };
736
1259
  // Route host-destroyed teardown through the connection registry when present
737
1260
  // (the Connection fires onHostDestroyed on wc destroy / reset, and the
738
1261
  // returned Disposable releases the ownership early on host swap/clear);
@@ -744,18 +1267,20 @@ export function createNetworkForwarder(bridge) {
744
1267
  // gated by the SAME `typeof host.once === 'function'` guard the fallback
745
1268
  // uses — otherwise a minimal/fake DevTools host (no emitter) throws on the
746
1269
  // connection path where the fallback would safely no-op.
747
- if (reg && typeof host.once === 'function') {
1270
+ if (reg && typeof host.once === "function") {
748
1271
  const owned = reg.acquire(host).own(onHostDestroyed);
749
1272
  devtoolsHostDisposable = toDisposable(() => owned.dispose());
750
1273
  }
751
1274
  else {
752
- if (typeof host.once === 'function')
753
- host.once('destroyed', onHostDestroyed);
1275
+ if (typeof host.once === "function")
1276
+ host.once("destroyed", onHostDestroyed);
754
1277
  devtoolsHostDisposable = toDisposable(() => {
755
1278
  try {
756
- host.removeListener?.('destroyed', onHostDestroyed);
1279
+ host.removeListener?.("destroyed", onHostDestroyed);
1280
+ }
1281
+ catch {
1282
+ /* gone */
757
1283
  }
758
- catch { /* gone */ }
759
1284
  });
760
1285
  }
761
1286
  // A host swap while still 'probing' the OLD host must not inherit its
@@ -763,11 +1288,14 @@ export function createNetworkForwarder(bridge) {
763
1288
  // (re-)arming readyTimeoutTimer/probeDeadline for the NEW host, leaving it
764
1289
  // probing forever with no timeout. Force through 'idle' so beginProbing()
765
1290
  // always arms a fresh window for whichever host is now current.
766
- sink = 'idle';
1291
+ sink = "idle";
767
1292
  beginProbing();
768
1293
  if (dispatchQueue.length > 0)
769
1294
  scheduleFlush();
770
1295
  }
1296
+ registry.add(() => {
1297
+ disposed = true;
1298
+ });
771
1299
  registry.add(() => {
772
1300
  devtoolsHostDisposable?.dispose();
773
1301
  devtoolsHostDisposable = null;
@@ -780,14 +1308,116 @@ export function createNetworkForwarder(bridge) {
780
1308
  readyRetryTimer = null;
781
1309
  }
782
1310
  });
1311
+ registry.add(() => {
1312
+ globalDevtoolsHostDisposable?.dispose();
1313
+ globalDevtoolsHostDisposable = null;
1314
+ globalBodyGateStop?.();
1315
+ globalBodyGateStop = null;
1316
+ globalDevtoolsWc = null;
1317
+ // A live settle-poll timer at dispose time is a real-timer leak (this
1318
+ // suite's flaky-test history: undisposed timers adopted by later tests'
1319
+ // fake clocks).
1320
+ clearGlobalPending();
1321
+ });
1322
+ registry.add(() => {
1323
+ bodyCache.clear();
1324
+ postDataCache.clear();
1325
+ });
1326
+ // Every debugger session (simulator + all render guests) must already be
1327
+ // detached and every 'message' listener already removed before `dispose()`
1328
+ // returns control to an un-awaited caller (every real call site is
1329
+ // `fwd.dispose()` with no `await`) — a guest event arriving in the SAME
1330
+ // tick as dispose() must never be forwarded. `registry` is a
1331
+ // SyncDisposableRegistry, so every entry below runs to completion before
1332
+ // disposeAll() returns — no ordering dependency between them.
783
1333
  registry.add(() => detachSimulator());
1334
+ registry.add(() => {
1335
+ for (const wcId of [...guestWired.keys()])
1336
+ cleanupGuest(wcId);
1337
+ });
1338
+ // Only detach sessions we self-attached if we own the broker's lifecycle —
1339
+ // a shared/injected broker keeps serving other consumers past our dispose().
1340
+ registry.add(() => {
1341
+ if (ownsBroker)
1342
+ broker.dispose();
1343
+ });
1344
+ // ── Main-process WebSocket trace → synthesized Network.webSocket* CDP ─────
1345
+ // One synthesizer for the forwarder's lifetime: its virtual-id epoch keeps
1346
+ // `dimina:ws:` ids collision-free across simulator re-attaches, and its
1347
+ // per-socket verdict cache mirrors resolveUserFacing's decide-once rule.
1348
+ const wsSynthesizer = new WebSocketTraceSynthesizer({
1349
+ epoch: String(Date.now()),
1350
+ internalOrigins: () => [
1351
+ bridge.getResourceServerBaseUrl?.(),
1352
+ bridge.getSimulatorServerBaseUrl?.(),
1353
+ ],
1354
+ });
1355
+ function reportWebSocketTrace(sessionId, event) {
1356
+ if (disposed)
1357
+ return;
1358
+ const message = wsSynthesizer.synthesize(sessionId, event);
1359
+ if (!message)
1360
+ return;
1361
+ // Global mirror first (full, unfiltered), then the user-facing native
1362
+ // sink gated on the created-time verdict — the same routing
1363
+ // wireNetworkCapture's onMessage uses for simulator traffic.
1364
+ dispatchToGlobal(message.method, message.params);
1365
+ if (message.userFacing)
1366
+ enqueueNative(message.method, message.params);
1367
+ }
1368
+ // ── Main-process HTTP request trace → synthesized Network.request*/
1369
+ // Network.loading* CDP ──────────────────────────────────────────────────
1370
+ // Same shape as the WebSocket trace bridge above, for the same reason:
1371
+ // wx.request now runs on Node http/https in this process (that's what
1372
+ // stops Chromium's Fetch/CORS algorithm from attaching a spurious OPTIONS
1373
+ // preflight to it), so no webContents.debugger can observe it either.
1374
+ const httpSynthesizer = new RequestTraceSynthesizer({
1375
+ epoch: String(Date.now()),
1376
+ internalOrigins: () => [
1377
+ bridge.getResourceServerBaseUrl?.(),
1378
+ bridge.getSimulatorServerBaseUrl?.(),
1379
+ ],
1380
+ });
1381
+ function reportNativeRequestTrace(sessionId, event) {
1382
+ if (disposed)
1383
+ return;
1384
+ const message = httpSynthesizer.synthesize(sessionId, event);
1385
+ if (!message)
1386
+ return;
1387
+ const params = message.params;
1388
+ if (event.type === "redirect") {
1389
+ bodyCache.delete(params.requestId);
1390
+ postDataCache.delete(params.requestId);
1391
+ }
1392
+ // The response is already fully buffered in-process at `finished` — prime
1393
+ // the SAME cache a simulator-CDP prefetch would populate, so the
1394
+ // front-end's Get Response Body click resolves without a debugger
1395
+ // round-trip that (for this requestId) has nowhere to go.
1396
+ if (message.body) {
1397
+ bodyCache.prime(params.requestId, () => Promise.resolve(message.body));
1398
+ }
1399
+ if (message.postData !== undefined) {
1400
+ postDataCache.prime(params.requestId, () => Promise.resolve({ postData: message.postData }));
1401
+ }
1402
+ dispatchToGlobal(message.method, message.params);
1403
+ if (message.userFacing)
1404
+ enqueueNative(message.method, message.params);
1405
+ }
784
1406
  return {
785
1407
  attachSimulator,
786
1408
  detachSimulator,
1409
+ attachRenderGuest,
787
1410
  setDevtoolsHost: (wc) => applyDevtoolsHost(wc),
1411
+ setGlobalDevtoolsHost: (wc) => applyGlobalDevtoolsHost(wc),
788
1412
  // report() has no observing debugger, so there's no live CDP event to push
789
1413
  // natively — surface it via the console fallback line.
790
1414
  report: (record) => forwardToConsole(record),
1415
+ reportWebSocketTrace,
1416
+ reportNativeRequestTrace,
1417
+ bodies: {
1418
+ getResponseBody: (requestId) => bodyCache.lookup(requestId),
1419
+ getRequestPostData: (requestId) => postDataCache.lookup(requestId),
1420
+ },
791
1421
  dispose: () => registry.disposeAll(),
792
1422
  };
793
1423
  }