@dimina-kit/devtools 0.4.0-dev.20260701155140 → 0.4.0-dev.20260702145840

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 (62) hide show
  1. package/dist/main/app/max-listeners-diagnostic.js +5 -1
  2. package/dist/main/index.bundle.js +1532 -1236
  3. package/dist/main/ipc/bridge-router.d.ts +28 -0
  4. package/dist/main/ipc/bridge-router.js +175 -50
  5. package/dist/main/ipc/session-listener-bag.d.ts +41 -0
  6. package/dist/main/ipc/session-listener-bag.js +40 -0
  7. package/dist/main/ipc/simulator.js +5 -1
  8. package/dist/main/services/elements-forward/index.js +19 -0
  9. package/dist/main/services/network-forward/index.js +13 -0
  10. package/dist/main/services/views/destroy-child-view.d.ts +9 -0
  11. package/dist/main/services/views/destroy-child-view.js +23 -0
  12. package/dist/main/services/views/host-toolbar-view.d.ts +21 -0
  13. package/dist/main/services/views/host-toolbar-view.js +249 -0
  14. package/dist/main/services/views/inject-when-ready.d.ts +41 -0
  15. package/dist/main/services/views/inject-when-ready.js +62 -0
  16. package/dist/main/services/views/native-simulator-devtools-host.d.ts +26 -0
  17. package/dist/main/services/views/native-simulator-devtools-host.js +484 -0
  18. package/dist/main/services/views/native-simulator-view.d.ts +28 -0
  19. package/dist/main/services/views/native-simulator-view.js +428 -0
  20. package/dist/main/services/views/overlay-panels-view.d.ts +29 -0
  21. package/dist/main/services/views/overlay-panels-view.js +150 -0
  22. package/dist/main/services/views/placement-reconciler.d.ts +73 -0
  23. package/dist/main/services/views/placement-reconciler.js +120 -0
  24. package/dist/main/services/views/resolve-project-editor-target.d.ts +13 -0
  25. package/dist/main/services/views/resolve-project-editor-target.js +33 -0
  26. package/dist/main/services/views/view-manager.d.ts +20 -16
  27. package/dist/main/services/views/view-manager.js +66 -1545
  28. package/dist/main/services/views/workbench-view.d.ts +15 -0
  29. package/dist/main/services/views/workbench-view.js +170 -0
  30. package/dist/preload/windows/host-toolbar-runtime.cjs.map +2 -2
  31. package/dist/preload/windows/main.cjs +10 -1
  32. package/dist/preload/windows/main.cjs.map +2 -2
  33. package/dist/preload/windows/simulator.cjs +6 -0
  34. package/dist/preload/windows/simulator.cjs.map +2 -2
  35. package/dist/preload/windows/simulator.js +6 -0
  36. package/dist/renderer/assets/index-ChF9V4r-.js +49 -0
  37. package/dist/renderer/assets/{input-Djg2NR4U.js → input-Cjk0wSEh.js} +2 -2
  38. package/dist/renderer/assets/ipc-transport-D5dKIti1.css +1 -0
  39. package/dist/renderer/assets/{ipc-transport-Be-AzCub.js → ipc-transport-OZS-KmOW.js} +2 -2
  40. package/dist/renderer/assets/{popover-eDUh4NYe.js → popover-CQVvOe90.js} +2 -2
  41. package/dist/renderer/assets/{select-DesFfkVV.js → select-BQDXi5ih.js} +2 -2
  42. package/dist/renderer/assets/{settings-api-HQ7ZFPWi.js → settings-api-B6x2mhaD.js} +2 -2
  43. package/dist/renderer/assets/{settings-CRmO7zUG.js → settings-tP3Px2KR.js} +2 -2
  44. package/dist/renderer/assets/{workbenchSettings-CZBWsliT.js → workbenchSettings-Bim9ol9R.js} +2 -2
  45. package/dist/renderer/entries/main/index.html +6 -6
  46. package/dist/renderer/entries/popover/index.html +5 -5
  47. package/dist/renderer/entries/settings/index.html +5 -5
  48. package/dist/renderer/entries/workbench-settings/index.html +4 -4
  49. package/dist/shared/bridge-channels.d.ts +17 -0
  50. package/dist/shared/bridge-channels.js +9 -0
  51. package/dist/shared/ipc-channels.d.ts +1 -0
  52. package/dist/shared/ipc-channels.js +6 -0
  53. package/dist/shared/ipc-schemas.d.ts +6 -0
  54. package/dist/shared/ipc-schemas.js +10 -0
  55. package/dist/simulator/assets/{bridge-channels-BUQ5AbvJ.js → bridge-channels-zhsumfky.js} +2 -2
  56. package/dist/simulator/assets/{device-shell-CT3LRnTB.js → device-shell-CUl0ILfn.js} +2 -2
  57. package/dist/simulator/assets/{simulator-BizhGGE3.js → simulator-Dz8iGcgy.js} +5 -5
  58. package/dist/simulator/assets/{simulator-mini-app-BfNuDF3R.js → simulator-mini-app-Bt639XL_.js} +2 -2
  59. package/dist/simulator/simulator.html +2 -1
  60. package/package.json +5 -5
  61. package/dist/renderer/assets/index-jDrNHghH.js +0 -49
  62. package/dist/renderer/assets/ipc-transport-C6-KPMqw.css +0 -1
@@ -0,0 +1,484 @@
1
+ import { WebContentsView } from 'electron';
2
+ import { buildDevtoolsProjectSourceLinksScript, decodeOpenInEditorUrl, projectSourceContextFromServiceHostUrl, } from '../../../shared/open-in-editor.js';
3
+ import { buildCustomizeTabsScript } from './devtools-tabs.js';
4
+ import { installElementsForward } from '../elements-forward/index.js';
5
+ import { installServiceConsoleForward } from '../service-console/index.js';
6
+ import { VIEW_ID } from '../../../shared/view-ids.js';
7
+ import { resolveProjectEditorTarget } from './resolve-project-editor-target.js';
8
+ import { destroyChildView } from './destroy-child-view.js';
9
+ import { createLoadDeferredInjector } from './inject-when-ready.js';
10
+ // `webContents.executeJavaScript()` registers a transient `did-stop-loading`
11
+ // listener on the target wc whenever that wc is still loading (Electron's
12
+ // `waitTillCanExecuteJavaScript` defers the eval until load finishes, then
13
+ // removes the listener). The DevTools front-end host wc takes a burst of injects
14
+ // during its boot window — tab customization, default-panel selection, the 150ms
15
+ // Elements-forward reconcile, and Network-forward probing all call
16
+ // `executeJavaScript()` on it before the front-end has finished loading — so the
17
+ // pending listeners can transiently exceed Node's default EventEmitter ceiling
18
+ // of 10 and print a spurious `MaxListenersExceededWarning: 11 did-stop-loading
19
+ // listeners`. They drain once the wc stops loading; this is a boot-time
20
+ // concurrency spike, not a per-event leak. Raise the ceiling on the DevTools
21
+ // host wc, which we knowingly inject into repeatedly during its boot window, so
22
+ // the benign spike stays quiet. (Confirmed at runtime: the tripped emitter is
23
+ // this host wc's `did-stop-loading`, count 11 — see the `process.on('warning')`
24
+ // diagnostic in app bootstrap.)
25
+ const DEVTOOLS_HOST_EXEC_JS_MAX_LISTENERS = 50;
26
+ function raiseExecuteJavaScriptListenerCeiling(wc) {
27
+ try {
28
+ if (wc.isDestroyed())
29
+ return;
30
+ if (wc.getMaxListeners() >= DEVTOOLS_HOST_EXEC_JS_MAX_LISTENERS)
31
+ return;
32
+ wc.setMaxListeners(DEVTOOLS_HOST_EXEC_JS_MAX_LISTENERS);
33
+ }
34
+ catch { /* torn-down / stub wc — best effort, warning is non-fatal */ }
35
+ }
36
+ export function createDevtoolsHost(ctx, reconciler, deps) {
37
+ // The right-panel Chrome DevTools front-end HOST WebContentsView. Created by
38
+ // rebuildDevtoolsHostView; positioned by the renderer's simulatorDevtools anchor.
39
+ let simulatorView = null;
40
+ let simulatorViewAdded = false;
41
+ // The webContents the right-panel Chrome DevTools front-end currently inspects.
42
+ // We point it at the SERVICE HOST (logic layer) — the hidden BrowserWindow
43
+ // where the mini-app's page code runs (`console.log`, `wx.request`,
44
+ // Sources/Network(fetch) all live there). The UI/view layer (Elements/Styles/
45
+ // WXML tree) is served separately by the native WXML panel + render-guest
46
+ // highlight chain, so a single DevTools front-end is enough. The service window
47
+ // can be swapped on respawn (pre-warm pool recycles it), so this is re-resolved
48
+ // fresh via `ctx.bridge.getServiceWc()` and re-pointed on every render-side event.
49
+ let nativeDevtoolsSourceWc = null;
50
+ // `setDevToolsWebContents` requires its host argument to have never navigated
51
+ // (Electron contract). The front-end host wc navigates the moment it is first
52
+ // pointed at a service wc, so it is a one-shot resource: once used, re-pointing
53
+ // to a different (pool-swapped) service wc must rebuild the host
54
+ // (`rebuildDevtoolsHostView`) rather than target it again.
55
+ let devtoolsHostUsed = false;
56
+ let unsubscribeNativeRenderEvents = null;
57
+ // Disposer for the Elements-forward feature (routes the front-end's Elements
58
+ // CDP traffic at the active render guest). Installed in
59
+ // `attach`, stopped on detach / host destroyed.
60
+ let stopElementsForward = null;
61
+ // Disposer for the service-layer console capture (CDP `consoleAPICalled` on the
62
+ // service host wc → console fan-out). Installed when the DevTools is pointed at
63
+ // a service host, stopped when that source is closed / swapped.
64
+ let stopServiceConsole = null;
65
+ let nativeDevtoolsRetryTimer = null;
66
+ let nativeDevtoolsRetryToken = 0;
67
+ // Front-end injects re-fire on EVERY re-point; while the host is loading each
68
+ // raw executeJavaScript would queue one did-stop-loading waiter per call.
69
+ // The deferred injector keeps one hook per (wc, kind) with latest-wins.
70
+ const injectWhenReady = createLoadDeferredInjector();
71
+ // The open-in-editor: click a console file link → workbench. The right-panel
72
+ // console is the embedded Chromium DevTools front-end. Once a sourcemap maps a
73
+ // console frame back to source, we redirect a source-link click to OUR
74
+ // workbench editor instead of the DevTools Sources panel.
75
+ const openInEditorWiredWcIds = new Set();
76
+ function clearNativeDevtoolsRetry() {
77
+ nativeDevtoolsRetryToken++;
78
+ if (nativeDevtoolsRetryTimer) {
79
+ clearTimeout(nativeDevtoolsRetryTimer);
80
+ nativeDevtoolsRetryTimer = null;
81
+ }
82
+ }
83
+ function closeNativeDevtoolsSource() {
84
+ const source = nativeDevtoolsSourceWc;
85
+ nativeDevtoolsSourceWc = null;
86
+ try {
87
+ stopServiceConsole?.();
88
+ }
89
+ catch { /* already stopped */ }
90
+ stopServiceConsole = null;
91
+ if (!source || source.isDestroyed())
92
+ return;
93
+ try {
94
+ if (source.isDevToolsOpened()) {
95
+ source.closeDevTools();
96
+ }
97
+ }
98
+ catch { /* source may be mid-destroy */ }
99
+ }
100
+ function stopFollowingNativeServiceHost() {
101
+ clearNativeDevtoolsRetry();
102
+ if (unsubscribeNativeRenderEvents) {
103
+ try {
104
+ unsubscribeNativeRenderEvents();
105
+ }
106
+ catch { /* ignore */ }
107
+ unsubscribeNativeRenderEvents = null;
108
+ }
109
+ closeNativeDevtoolsSource();
110
+ }
111
+ function injectOpenResourceHandler(serviceWc, devtoolsWc) {
112
+ // Inject the front-end glue that routes a project source-link click to our
113
+ // Monaco editor instead of the DevTools Sources panel: a capture-phase click
114
+ // interceptor re-emits an encoded sentinel via
115
+ // `InspectorFrontendHost.openInNewTab` → Electron `devtools-open-url`. (The
116
+ // legacy `setOpenResourceHandler` hook this used to rely on is gone in
117
+ // current Chromium, so the script keeps it only as a fallback.)
118
+ //
119
+ // Destroy-race chokepoint: this runs from a deferred `dom-ready` callback
120
+ // (see wireOpenInEditor), by which time the inspected service-host wc may
121
+ // already be torn down (pool swap / project close / relaunch). Reading
122
+ // `serviceWc.getURL()` on a destroyed wc throws "Object has been destroyed"
123
+ // synchronously, which would escape the event listener. Guard both wc and
124
+ // wrap the body so a teardown mid-call degrades silently (mirrors
125
+ // customizeDevtoolsTabs).
126
+ if (serviceWc.isDestroyed() || devtoolsWc.isDestroyed())
127
+ return;
128
+ try {
129
+ const sourceContext = projectSourceContextFromServiceHostUrl(serviceWc.getURL(), ctx.workspace?.getProjectPath?.());
130
+ if (!sourceContext)
131
+ return;
132
+ devtoolsWc.executeJavaScript(buildDevtoolsProjectSourceLinksScript(sourceContext)).catch(() => { });
133
+ }
134
+ catch { /* wc torn down mid-call — degrade silently */ }
135
+ }
136
+ // ── DevTools tab customization: keep only Elements/Console/Network ──────────
137
+ // Inject into the same DevTools front-end host wc that the console/network
138
+ // injectors target. Reorders Elements/Console/Network to the front and closes
139
+ // every other panel tab by driving the front-end's UI.ViewManager /
140
+ // InspectorView.tabbedLocation (see ./devtools-tabs.ts). Best-effort: the
141
+ // injected script bounded-polls for the lazily-registered panels, wraps
142
+ // everything in try/catch, and silently no-ops if the API never appears —
143
+ // DevTools default tab behaviour is preserved on any failure. Re-injected on
144
+ // every (re)point so a re-`openDevTools` (service-host pool swap) re-applies.
145
+ function customizeDevtoolsTabs(devtoolsWc) {
146
+ try {
147
+ if (devtoolsWc.isDestroyed())
148
+ return;
149
+ const inject = () => {
150
+ if (devtoolsWc.isDestroyed())
151
+ return;
152
+ try {
153
+ void devtoolsWc.executeJavaScript(buildCustomizeTabsScript()).catch(() => { });
154
+ }
155
+ catch { /* wc torn down mid-call */ }
156
+ };
157
+ injectWhenReady(devtoolsWc, 'customize-tabs', inject);
158
+ }
159
+ catch { /* wc surface incomplete / torn down; degrade silently */ }
160
+ }
161
+ function wireOpenInEditor(serviceWc, devtoolsWc) {
162
+ // Inject the front-end handler on every (re)attach — the DevTools front-end
163
+ // wc is recreated per simulatorView, so a fresh host needs the handler set.
164
+ if (!devtoolsWc.isDestroyed()) {
165
+ injectWhenReady(devtoolsWc, 'open-in-editor', () => injectOpenResourceHandler(serviceWc, devtoolsWc));
166
+ }
167
+ // Attach the `devtools-open-url` decoder to the inspected service wc once
168
+ // (the listener is keyed off the encoded sentinel scheme, so it only acts on
169
+ // OUR redirected links; any other devtools "open in new tab" falls through).
170
+ if (openInEditorWiredWcIds.has(serviceWc.id))
171
+ return;
172
+ openInEditorWiredWcIds.add(serviceWc.id);
173
+ const onOpenUrl = (_event, url) => {
174
+ const req = decodeOpenInEditorUrl(url);
175
+ if (!req)
176
+ return; // not our sentinel — leave it to Electron's default path
177
+ const target = resolveProjectEditorTarget(serviceWc.getURL(), ctx.workspace?.getProjectPath?.(), req);
178
+ if (!target)
179
+ return;
180
+ // Drive the workbench WCV (the sole editor) when it is attached, AND
181
+ // always emit `editor:openFile` — it carries the editor-agnostic mapping
182
+ // (project-relative path + 1-based line) that downstream/consumers (and the
183
+ // open-in-editor contract test) observe; with Monaco gone it has no
184
+ // renderer subscriber, so emitting it is harmless when the workbench
185
+ // handles the actual reveal.
186
+ deps.openFileInWorkbench(target.path, target.line ?? 1, target.column ?? 1);
187
+ ctx.notify.editorOpenFile(target);
188
+ };
189
+ serviceWc.on('devtools-open-url', onOpenUrl);
190
+ // Consolidate teardown onto the connection layer,
191
+ // but as a wc-LIFETIME resource: the open-in-editor wiring inspects this
192
+ // service-host wc and must SURVIVE pool reuse (`reset`) — the dedup set
193
+ // entry + listener stay valid across sessions reusing the same wc, and the
194
+ // early-return dedup above correctly skips re-wiring after a reset. So we
195
+ // register on `'closed'` (fires only on real wc destroy), NOT `own()`
196
+ // (which also fires on `reset` and would leave the wc un-wired after reuse
197
+ // because re-pointing the same wc.id early-returns). acquire() is
198
+ // idempotent — bridge-router owning session-scoped resources on the same
199
+ // serviceWc connection coexists cleanly.
200
+ const conn = ctx.connections.acquire(serviceWc);
201
+ conn.on('closed', () => {
202
+ openInEditorWiredWcIds.delete(serviceWc.id);
203
+ try {
204
+ serviceWc.removeListener('devtools-open-url', onOpenUrl);
205
+ }
206
+ catch { /* wc gone */ }
207
+ });
208
+ }
209
+ // Point the right-panel Chrome DevTools front-end at `next` — the SERVICE HOST
210
+ // webContents (logic layer). Idempotent: if we already inspect this wc, no-op.
211
+ function pointNativeDevtoolsAtServiceWc(next) {
212
+ if (!simulatorView || simulatorView.webContents.isDestroyed())
213
+ return true;
214
+ if (nativeDevtoolsSourceWc?.id === next.id && !nativeDevtoolsSourceWc.isDestroyed()) {
215
+ return true;
216
+ }
217
+ closeNativeDevtoolsSource();
218
+ nativeDevtoolsSourceWc = next;
219
+ // The host wc is a one-shot `setDevToolsWebContents` target (Electron
220
+ // forbids re-pointing at an already-navigated host): once used, a
221
+ // service-wc swap must repoint onto a freshly rebuilt host instead.
222
+ if (devtoolsHostUsed) {
223
+ rebuildDevtoolsHostView();
224
+ }
225
+ if (!simulatorView || simulatorView.webContents.isDestroyed())
226
+ return true;
227
+ try {
228
+ next.setDevToolsWebContents(simulatorView.webContents);
229
+ // DevTools renders into the right-panel host view (simulatorView); with a
230
+ // custom host the `mode` is overridden, and `activate:false` prevents it
231
+ // stealing focus — this re-points whenever the service window is swapped,
232
+ // so a focusing window would yank focus repeatedly (disrupting the user /
233
+ // e2e).
234
+ next.openDevTools({ mode: 'detach', activate: false });
235
+ // The host is factually USED the moment it navigates (openDevTools) —
236
+ // NOT when the whole wiring below succeeds. Flagging late lies to the
237
+ // retry path: a mid-wiring throw (service wc torn down mid-point) left
238
+ // the flag false, so the 50ms follow-retry re-pointed the SAME navigated
239
+ // host over and over, re-running the front-end injects each attempt
240
+ // until the host wc's pending executeJavaScript waiters tripped the
241
+ // MaxListeners ceiling. Flag first: a failed attempt rebuilds fresh.
242
+ devtoolsHostUsed = true;
243
+ // Redirect console source-link clicks to the workbench editor.
244
+ wireOpenInEditor(next, simulatorView.webContents);
245
+ // Keep only Elements/Console/Network tabs (front-most, in that order).
246
+ // Re-applied on every re-point so a service-host pool swap (fresh
247
+ // openDevTools) re-asserts the custom tab bar.
248
+ customizeDevtoolsTabs(simulatorView.webContents);
249
+ // Capture service-layer console via CDP (NOT a preload monkeypatch, which
250
+ // would clobber native source attribution) and feed it to the console
251
+ // fan-out (automation `App.logAdded`). Bound to THIS service wc; replaced
252
+ // on the next re-point via closeNativeDevtoolsSource.
253
+ try {
254
+ stopServiceConsole?.();
255
+ }
256
+ catch { /* already stopped */ }
257
+ stopServiceConsole = installServiceConsoleForward({
258
+ serviceWc: next,
259
+ connections: ctx.connections,
260
+ emit: (entry) => ctx.consoleForwarder?.emit(entry),
261
+ }).stop;
262
+ return true;
263
+ }
264
+ catch (err) {
265
+ // Not silent: a throw here (service wc torn down mid-point is the common
266
+ // one) makes the follow-retry loop spin — leave a trace so the spin has
267
+ // a name in the log instead of hiding for weeks.
268
+ console.warn('[devtools-host] point-at-service-wc failed; retry will rebuild:', err);
269
+ if (nativeDevtoolsSourceWc?.id === next.id) {
270
+ nativeDevtoolsSourceWc = null;
271
+ }
272
+ return false;
273
+ }
274
+ }
275
+ // Resolve the SERVICE HOST webContents for the active (or named) app. This is
276
+ // the hidden service BrowserWindow's wc — a top-level wc that CAN host a
277
+ // Chrome DevTools front-end (unlike a `<webview>` guest's). Re-resolved fresh
278
+ // so a pre-warm-pool swap on respawn is tolerated.
279
+ function pointNativeDevtoolsAtActiveServiceHost(appId) {
280
+ if (!ctx.bridge?.isNativeHost())
281
+ return true;
282
+ if (!simulatorView || simulatorView.webContents.isDestroyed())
283
+ return true;
284
+ const wc = ctx.bridge.getServiceWc(appId);
285
+ if (!wc || wc.isDestroyed())
286
+ return false;
287
+ return pointNativeDevtoolsAtServiceWc(wc);
288
+ }
289
+ function scheduleNativeDevtoolsFollow(appId, attempt = 0) {
290
+ if (attempt >= 20)
291
+ return;
292
+ if (!ctx.bridge?.isNativeHost())
293
+ return;
294
+ const token = nativeDevtoolsRetryToken;
295
+ if (nativeDevtoolsRetryTimer)
296
+ clearTimeout(nativeDevtoolsRetryTimer);
297
+ nativeDevtoolsRetryTimer = setTimeout(() => {
298
+ nativeDevtoolsRetryTimer = null;
299
+ if (token !== nativeDevtoolsRetryToken)
300
+ return;
301
+ if (pointNativeDevtoolsAtActiveServiceHost(appId))
302
+ return;
303
+ scheduleNativeDevtoolsFollow(appId, attempt + 1);
304
+ }, 50);
305
+ }
306
+ function followNativeDevtoolsServiceHost(appId) {
307
+ clearNativeDevtoolsRetry();
308
+ if (pointNativeDevtoolsAtActiveServiceHost(appId))
309
+ return;
310
+ scheduleNativeDevtoolsFollow(appId);
311
+ }
312
+ // Render-side activity (a page DOM mounting / the visible page changing)
313
+ // always follows a spawn or respawn, by which point the service window exists
314
+ // (and may have been swapped by the pool). Re-resolve + re-point the DevTools
315
+ // at the now-current service host on every such event.
316
+ function onNativeRenderEvent(event) {
317
+ if (event.kind !== 'activePage' && event.kind !== 'domReady')
318
+ return;
319
+ followNativeDevtoolsServiceHost(event.appId);
320
+ }
321
+ // (Re)build the right-panel DevTools front-end host — a fresh, never-navigated
322
+ // `simulatorView` WebContentsView. `setDevToolsWebContents` may only target a
323
+ // host that has never navigated, so this is the sole path that produces a
324
+ // usable host: called once on attach, and again by `pointNativeDevtoolsAtServiceWc`
325
+ // whenever the current host has already been pointed at a service wc (a
326
+ // service-host pool swap must repoint onto a fresh host, not reuse the
327
+ // already-navigated one).
328
+ function rebuildDevtoolsHostView() {
329
+ // Destroy old simulatorView to prevent WebContentsView leak
330
+ if (simulatorView) {
331
+ removeSimulatorDevtoolsView();
332
+ try {
333
+ if (!simulatorView.webContents.isDestroyed()) {
334
+ simulatorView.webContents.close();
335
+ }
336
+ }
337
+ catch { /* ignore */ }
338
+ simulatorView = null;
339
+ }
340
+ simulatorView = new WebContentsView();
341
+ // Default DevTools to Console panel (Chrome DevTools defaults to Elements).
342
+ // The DevTools UI lives inside closed shadow roots, so a light-DOM
343
+ // querySelector('[role="tab"]') cannot reach the tab bar to click it.
344
+ // Instead drive the front-end's own view manager: the bundled DevTools
345
+ // exposes `UI.ViewManager.instance().showView(id)` on `globalThis.UI`
346
+ // once the front-end has finished bootstrapping. We poll for it and
347
+ // request the `console` view, and also persist the choice via the
348
+ // `panel-selectedTab` localStorage key so subsequent reloads honor it.
349
+ const devtoolsWc = simulatorView.webContents;
350
+ // The boot-time burst of `executeJavaScript()` injects on this host wc (tab
351
+ // customization / console default / Elements+Network forwarding below) each
352
+ // queue a pending `did-stop-loading` waiter while the front-end is still
353
+ // loading (see raiseExecuteJavaScriptListenerCeiling).
354
+ raiseExecuteJavaScriptListenerCeiling(devtoolsWc);
355
+ // Hand the network forwarder THIS front-end host wc — it injects the
356
+ // simulator WCV's Network.* CDP events into `window.DevToolsAPI.dispatchMessage`
357
+ // here so the native Network tab renders them (falls back to the service-host
358
+ // console line when null / the API never appears). Cleared on detach.
359
+ ctx.networkForward?.setDevtoolsHost(devtoolsWc);
360
+ // Elements forwarding (production, always on for the native simulator): route
361
+ // the front-end's Elements-panel CDP traffic (DOM/CSS/Overlay/DOMSnapshot/
362
+ // DOMDebugger) onto the ACTIVE RENDER GUEST so the panel reflects the page's
363
+ // live DOM tree instead of the service host it natively inspects. Reuses the
364
+ // safe-area-attached debugger session (never detaches one it doesn't own);
365
+ // Emulation.* and every other domain stay on the service-host path. Degrades
366
+ // to the native service-host DOM if the front-end hook is unavailable. The
367
+ // disposer is stopped on detach / host destroyed.
368
+ if (ctx.bridge) {
369
+ try {
370
+ stopElementsForward?.();
371
+ }
372
+ catch { /* prior disposer already gone */ }
373
+ // Capture THIS instance's disposer in the destroy handler — NOT the mutable
374
+ // `stopElementsForward`. A respawn creates a new simulatorView whose old
375
+ // devtools host wc is destroyed AFTER the new instance is already installed;
376
+ // a handler closing over `stopElementsForward` would then dispose the CURRENT
377
+ // instance (its reconcile loop never gets to install the hook, so Elements
378
+ // falls back to the natively-inspected service host). Stop only this wc's own
379
+ // forward, and clear the module pointer only while it still points here.
380
+ const thisForward = installElementsForward({ devtoolsWc, bridge: ctx.bridge, connections: ctx.connections });
381
+ stopElementsForward = thisForward;
382
+ devtoolsWc.once('destroyed', () => {
383
+ try {
384
+ thisForward();
385
+ }
386
+ catch { /* already stopped */ }
387
+ if (stopElementsForward === thisForward)
388
+ stopElementsForward = null;
389
+ });
390
+ }
391
+ injectWhenReady(devtoolsWc, 'console-default', () => {
392
+ devtoolsWc.executeJavaScript(`
393
+ (function() {
394
+ try { localStorage.setItem('panel-selectedTab', '"console"') } catch {}
395
+ let tries = 0
396
+ const timer = setInterval(() => {
397
+ tries++
398
+ try {
399
+ const UI = globalThis.UI
400
+ const vm = UI && UI.ViewManager && typeof UI.ViewManager.instance === 'function'
401
+ ? UI.ViewManager.instance()
402
+ : null
403
+ if (vm && typeof vm.showView === 'function') {
404
+ vm.showView('console')
405
+ clearInterval(timer)
406
+ return
407
+ }
408
+ } catch {}
409
+ if (tries > 80) clearInterval(timer)
410
+ }, 50)
411
+ })()
412
+ `).catch(() => { });
413
+ });
414
+ // Anchor-only mount: the renderer's published rect is the SOLE authority.
415
+ // If a non-zero rect was already published (it can land before this attach
416
+ // on the project-open ordering), replay it; otherwise the view stays
417
+ // unadded and unsized until the first publish arrives. No static-layout
418
+ // fallback — an attach-time computed rect raced the precise anchor rect
419
+ // and flashed the overlay at the wrong rectangle.
420
+ // The view now exists: re-run the reconciler so a placement published before
421
+ // this attach (project-open ordering) attaches it once the gate re-opens.
422
+ reconciler.reconcileNow();
423
+ // A freshly built host has never navigated — it may be targeted by
424
+ // `setDevToolsWebContents` again.
425
+ devtoolsHostUsed = false;
426
+ }
427
+ function attachNativeSimulatorDevtoolsHost() {
428
+ stopFollowingNativeServiceHost();
429
+ rebuildDevtoolsHostView();
430
+ if (ctx.bridge?.isNativeHost()) {
431
+ unsubscribeNativeRenderEvents = ctx.bridge.onRenderEvent(onNativeRenderEvent);
432
+ followNativeDevtoolsServiceHost();
433
+ }
434
+ }
435
+ // Remove (but do not destroy) the DevTools overlay from the contentView.
436
+ // Internal teardown helper for re-attach; user-facing visibility is the
437
+ // anchor 0×0 single path (`setSimulatorDevtoolsBounds`).
438
+ function removeSimulatorDevtoolsView() {
439
+ if (simulatorView && simulatorViewAdded) {
440
+ try {
441
+ ctx.windows.mainWindow.contentView.removeChildView(simulatorView);
442
+ }
443
+ catch (e) {
444
+ console.error('[workbench] removeSimulatorDevtoolsView error', e);
445
+ }
446
+ simulatorViewAdded = false;
447
+ }
448
+ // The instance is being replaced/destroyed — forget its reconciled mount
449
+ // state so the next rebuilt host is treated as a fresh attach. Without this,
450
+ // the level-triggered reconciler still records `simulatorDevtools` as
451
+ // `attached` (the manual removeChildView above bypasses it), so it never
452
+ // emits the `attach` op for the rebuilt view and the new host is never
453
+ // addChildView'd — embedded but invisible. Mirrors the hostToolbar /
454
+ // simulator instance-replacement handling (ensureHostToolbarView /
455
+ // tearDownNativeSimulatorView).
456
+ reconciler.forgetActual(VIEW_ID.simulatorDevtools);
457
+ }
458
+ reconciler.registerView(VIEW_ID.simulatorDevtools, {
459
+ getView: () => simulatorView,
460
+ setAdded: (added) => { simulatorViewAdded = added; },
461
+ gateHidden: () => !simulatorView,
462
+ });
463
+ return {
464
+ attach: attachNativeSimulatorDevtoolsHost,
465
+ followServiceHost: followNativeDevtoolsServiceHost,
466
+ stopFollowing: stopFollowingNativeServiceHost,
467
+ stopElementsForwarding: () => {
468
+ // Stop Elements forwarding (detaches only the debugger sessions IT attached;
469
+ // safe-area's sessions are untouched). Idempotent — the host 'destroyed'
470
+ // handler may have already run.
471
+ try {
472
+ stopElementsForward?.();
473
+ }
474
+ catch { /* already stopped */ }
475
+ stopElementsForward = null;
476
+ },
477
+ destroyHostView: () => {
478
+ destroyChildView(ctx.windows.mainWindow, simulatorView);
479
+ simulatorView = null;
480
+ simulatorViewAdded = false;
481
+ },
482
+ };
483
+ }
484
+ //# sourceMappingURL=native-simulator-devtools-host.js.map
@@ -0,0 +1,28 @@
1
+ import type { WebContents } from 'electron';
2
+ import type { SafeAreaController } from '../safe-area/index.js';
3
+ import type { DevtoolsHost } from './native-simulator-devtools-host.js';
4
+ import type { OverlayPanelsView } from './overlay-panels-view.js';
5
+ import type { PlacementReconciler } from './placement-reconciler.js';
6
+ import type { ViewManagerContext } from './view-manager.js';
7
+ /**
8
+ * NATIVE-HOST ONLY. The native simulator is a top-level WebContentsView (the
9
+ * DeviceShell host) that IS the simulator webContents, so bridge-router resolves
10
+ * `ap.simulatorWc = event.sender = this wc` and SIMULATOR_EVENTS flow through
11
+ * it. It hosts per-page render-host `<webview>`s (impossible under a `<webview>`
12
+ * guest — the whole point of Option A). Its DevTools live in the right panel
13
+ * (the separate DevtoolsHost).
14
+ */
15
+ export interface NativeSimulatorView {
16
+ attachNativeSimulator(simulatorUrl: string, simWidth: number): Promise<void>;
17
+ softReloadNativeSimulator(simulatorUrl: string): boolean;
18
+ detachSimulator(): void;
19
+ getSimulatorWebContentsId(): number | null;
20
+ getSimulatorWebContents(): WebContents | null;
21
+ getSimulatorProjectPath(): string | null;
22
+ }
23
+ export declare function createNativeSimulatorView(ctx: ViewManagerContext, reconciler: PlacementReconciler, deps: {
24
+ safeArea: SafeAreaController;
25
+ devtoolsHost: DevtoolsHost;
26
+ overlayPanels: OverlayPanelsView;
27
+ }): NativeSimulatorView;
28
+ //# sourceMappingURL=native-simulator-view.d.ts.map