@dimina-kit/devtools 0.3.2-dev.20260611063505 → 0.3.2-dev.20260611135124

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 (84) hide show
  1. package/README.md +53 -40
  2. package/dist/main/api.d.ts +4 -2
  3. package/dist/main/api.js +5 -1
  4. package/dist/main/app/app.d.ts +1 -5
  5. package/dist/main/app/app.js +4 -19
  6. package/dist/main/index.bundle.js +238 -392
  7. package/dist/main/ipc/app.d.ts +1 -1
  8. package/dist/main/ipc/app.js +0 -7
  9. package/dist/main/ipc/index.d.ts +0 -2
  10. package/dist/main/ipc/index.js +0 -2
  11. package/dist/main/ipc/settings.d.ts +1 -1
  12. package/dist/main/ipc/settings.js +1 -13
  13. package/dist/main/ipc/simulator-module.d.ts +3 -3
  14. package/dist/main/ipc/simulator-module.js +3 -7
  15. package/dist/main/ipc/simulator.js +1 -12
  16. package/dist/main/runtime/miniapp-runtime.d.ts +138 -28
  17. package/dist/main/runtime/miniapp-runtime.js +6 -11
  18. package/dist/main/services/layout/index.d.ts +0 -11
  19. package/dist/main/services/layout/index.js +0 -23
  20. package/dist/main/services/notifications/renderer-notifier.d.ts +0 -6
  21. package/dist/main/services/notifications/renderer-notifier.js +1 -10
  22. package/dist/main/services/service-host-pool/pool.d.ts +5 -4
  23. package/dist/main/services/views/host-toolbar-port-channel.d.ts +93 -0
  24. package/dist/main/services/views/host-toolbar-port-channel.js +169 -0
  25. package/dist/main/services/views/host-toolbar-session-runtime.d.ts +35 -0
  26. package/dist/main/services/views/host-toolbar-session-runtime.js +55 -0
  27. package/dist/main/services/views/view-manager.d.ts +68 -39
  28. package/dist/main/services/views/view-manager.js +143 -182
  29. package/dist/main/services/workbench-context.d.ts +6 -26
  30. package/dist/main/services/workbench-context.js +0 -14
  31. package/dist/main/services/workspace/workspace-service.js +7 -13
  32. package/dist/main/utils/paths.d.ts +11 -2
  33. package/dist/main/utils/paths.js +11 -2
  34. package/dist/preload/index.d.ts +15 -0
  35. package/dist/preload/index.js +15 -0
  36. package/dist/preload/runtime/host-toolbar-port.d.ts +44 -0
  37. package/dist/preload/runtime/host-toolbar-port.js +127 -0
  38. package/dist/preload/runtime/host-toolbar-runtime.d.ts +41 -0
  39. package/dist/preload/runtime/host-toolbar-runtime.js +49 -0
  40. package/dist/preload/windows/{host-toolbar.cjs → host-toolbar-runtime.cjs} +94 -3
  41. package/dist/preload/windows/host-toolbar-runtime.cjs.map +7 -0
  42. package/dist/preload/windows/host-toolbar-runtime.d.ts +2 -0
  43. package/dist/preload/windows/host-toolbar-runtime.js +13 -0
  44. package/dist/preload/windows/simulator.cjs +0 -2
  45. package/dist/preload/windows/simulator.cjs.map +2 -2
  46. package/dist/preload/windows/simulator.js +0 -2
  47. package/dist/renderer/assets/constants-DI382mTP.js +2 -0
  48. package/dist/renderer/assets/index-DLzjz1wv.js +50 -0
  49. package/dist/renderer/assets/{input-CFHmbOgz.js → input-6fpCXV-n.js} +2 -2
  50. package/dist/renderer/assets/ipc-transport-Bs8Sf1B2.css +1 -0
  51. package/dist/renderer/assets/{ipc-transport-BkKKww3u.js → ipc-transport-D22e4dv7.js} +6 -6
  52. package/dist/renderer/assets/popover-CdGdKkkc.js +2 -0
  53. package/dist/renderer/assets/select-X0sbjwdP.js +2 -0
  54. package/dist/renderer/assets/{settings-Cl4IcWnG.js → settings-CS4jYaCw.js} +2 -2
  55. package/dist/renderer/assets/settings-api-CCWTAao_.js +2 -0
  56. package/dist/renderer/assets/workbenchSettings-Bpzaj3bm.js +8 -0
  57. package/dist/renderer/entries/main/index.html +7 -6
  58. package/dist/renderer/entries/popover/index.html +6 -6
  59. package/dist/renderer/entries/settings/index.html +5 -5
  60. package/dist/renderer/entries/workbench-settings/index.html +5 -5
  61. package/dist/shared/constants.d.ts +18 -0
  62. package/dist/shared/constants.js +18 -0
  63. package/dist/shared/ipc-channels.d.ts +10 -23
  64. package/dist/shared/ipc-channels.js +13 -34
  65. package/dist/shared/ipc-schemas.d.ts +0 -12
  66. package/dist/shared/ipc-schemas.js +0 -12
  67. package/dist/shared/types.d.ts +14 -67
  68. package/package.json +6 -6
  69. package/dist/main/ipc/panels.d.ts +0 -4
  70. package/dist/main/ipc/panels.js +0 -46
  71. package/dist/main/ipc/toolbar.d.ts +0 -4
  72. package/dist/main/ipc/toolbar.js +0 -21
  73. package/dist/main/services/toolbar/toolbar-store.d.ts +0 -23
  74. package/dist/main/services/toolbar/toolbar-store.js +0 -40
  75. package/dist/preload/windows/host-toolbar.cjs.map +0 -7
  76. package/dist/preload/windows/host-toolbar.d.ts +0 -2
  77. package/dist/preload/windows/host-toolbar.js +0 -12
  78. package/dist/renderer/assets/constants-CLAQX6VZ.js +0 -2
  79. package/dist/renderer/assets/index-CZQOpCvg.js +0 -50
  80. package/dist/renderer/assets/ipc-transport-Bv_BVu4T.css +0 -1
  81. package/dist/renderer/assets/popover-h1M7n3tt.js +0 -2
  82. package/dist/renderer/assets/select-kj0n1oYf.js +0 -2
  83. package/dist/renderer/assets/settings-api-CCwo2eOi.js +0 -2
  84. package/dist/renderer/assets/workbenchSettings-BzlpQnve.js +0 -8
@@ -1,6 +1,6 @@
1
1
  import { ipcMain, shell, WebContentsView, webContents } from 'electron';
2
2
  import path from 'path';
3
- import { cjsSiblingPreloadPath, hostToolbarPreloadPath, mainPreloadPath } from '../../utils/paths.js';
3
+ import { cjsSiblingPreloadPath, mainPreloadPath } from '../../utils/paths.js';
4
4
  import { applyNavigationHardening, handleWindowOpenExternal, } from '../../windows/navigation-hardening.js';
5
5
  import { SimulatorCustomApiBridgeChannel } from '../../../shared/ipc-channels.js';
6
6
  import { OPEN_IN_EDITOR_SCHEME, decodeOpenInEditorUrl, resourceUrlToProjectRelativePath, } from '../../../shared/open-in-editor.js';
@@ -9,9 +9,11 @@ import { buildCustomizeTabsScript } from './devtools-tabs.js';
9
9
  import { installElementsForward } from '../elements-forward/index.js';
10
10
  import * as layout from '../layout/index.js';
11
11
  import { handleCustomApiBridgeRequest, } from '../simulator/custom-apis.js';
12
- import { getDefaultTab } from '../workbench-context.js';
13
12
  import { configureMiniappSession, miniappPartition } from './miniapp-partition.js';
13
+ import { acquireHostToolbarSessionRuntime, releaseHostToolbarSessionRuntime, } from './host-toolbar-session-runtime.js';
14
+ import { createHostToolbarPortChannel, } from './host-toolbar-port-channel.js';
14
15
  import { parseRoute } from '../../../shared/simulator-route.js';
16
+ import { HEADER_H, HOST_TOOLBAR_RUNTIME_MARKER } from '../../../shared/constants.js';
15
17
  /**
16
18
  * Build a ViewManager bound to the given context. The returned object is the
17
19
  * only component allowed to instantiate or add/remove overlay WebContentsViews.
@@ -20,9 +22,6 @@ import { parseRoute } from '../../../shared/simulator-route.js';
20
22
  * on the context object.
21
23
  */
22
24
  export function createViewManager(ctx) {
23
- // Resolve once: full WorkbenchContext always provides headerHeight; partial
24
- // test contexts may omit it, in which case fall back to the default 40.
25
- const headerHeight = ctx.headerHeight ?? 40;
26
25
  // CSS env(safe-area-inset-*) simulation for render-host guests (per device).
27
26
  // Driven from did-attach-webview below; re-pushed on device change via
28
27
  // reapplySafeArea. Torn down in disposeAll.
@@ -56,7 +55,6 @@ export function createViewManager(ctx) {
56
55
  let settingsView = null;
57
56
  let settingsViewAdded = false;
58
57
  let popoverView = null;
59
- let lastSimWidth = 375;
60
58
  let simulatorWebContentsId = null;
61
59
  // NATIVE-HOST ONLY. The webContents the right-panel Chrome DevTools front-end
62
60
  // currently inspects. We point it at the SERVICE HOST (logic layer) — the
@@ -75,10 +73,10 @@ export function createViewManager(ctx) {
75
73
  let stopElementsForward = null;
76
74
  let nativeDevtoolsRetryTimer = null;
77
75
  let nativeDevtoolsRetryToken = 0;
78
- // Renderer-driven overlay bounds for the simulator DevTools view. When
79
- // non-null this takes precedence over the legacy layout computed from the
80
- // window content size. A zero-area rectangle means "hide" — the overlay is
81
- // removed from the contentView but its WebContents stays alive.
76
+ // Renderer-driven overlay bounds for the simulator DevTools view — the SOLE
77
+ // mount/geometry authority (no static-layout fallback). A zero-area
78
+ // rectangle means "hide" — the overlay is removed from the contentView but
79
+ // its WebContents stays alive.
82
80
  let simulatorBoundsOverride = null;
83
81
  // ── Host-controllable toolbar WebContentsView ───────────────────────────
84
82
  // A strip above the devtools header that the downstream host loads its own
@@ -88,6 +86,21 @@ export function createViewManager(ctx) {
88
86
  let hostToolbarView = null;
89
87
  let hostToolbarPreloadOverride = null;
90
88
  let hostToolbarViewAdded = false;
89
+ // Whether THIS manager holds a reference on the shared defaultSession
90
+ // registration of the toolbar-runtime preload (see
91
+ // host-toolbar-session-runtime.ts). Acquired on first toolbar need,
92
+ // released exactly once in disposeAll — a manager that never used the
93
+ // toolbar must not decrement a ref it never took.
94
+ let hostToolbarRuntimeAcquired = false;
95
+ // Placeholder height authority: 'auto' = advertiser reports forward to the
96
+ // renderer; { fixed } = host-pinned, advertiser reports are dropped.
97
+ let hostToolbarHeightMode = 'auto';
98
+ // Gated narrow channel to the toolbar PAGE (per-load MessagePort handshake;
99
+ // see host-toolbar-port-channel.ts). Control-level registry — created with
100
+ // the manager so onMessage() works before any toolbar view exists.
101
+ const hostToolbarPort = createHostToolbarPortChannel({
102
+ isCurrent: (wc) => liveHostToolbarWebContents() === wc,
103
+ });
91
104
  // ── Internal helpers ────────────────────────────────────────────────────
92
105
  function destroyViewInternal(view) {
93
106
  if (!view)
@@ -105,16 +118,6 @@ export function createViewManager(ctx) {
105
118
  }
106
119
  catch { /* ignore */ }
107
120
  }
108
- function applySimulatorBounds(simWidth) {
109
- if (!simulatorView || ctx.windows.mainWindow.isDestroyed())
110
- return;
111
- if (simulatorBoundsOverride) {
112
- simulatorView.setBounds(simulatorBoundsOverride);
113
- return;
114
- }
115
- const [w = 0, h = 0] = ctx.windows.mainWindow.getContentSize();
116
- simulatorView.setBounds(layout.computeSimulatorBounds(w, h, simWidth, headerHeight));
117
- }
118
121
  // Renderer publishes width/height = 0 to mean "hide overlay" — the
119
122
  // surrounding React panel is unmounted/collapsed. We keep the cached
120
123
  // value (so future republishes win over legacy layout) but remove the
@@ -145,11 +148,23 @@ export function createViewManager(ctx) {
145
148
  simulatorView.setBounds(bounds);
146
149
  }
147
150
  // ── Host-controllable toolbar WebContentsView ───────────────────────────
151
+ // The toolbar's webContents lifecycle belongs to the HOST, which may close
152
+ // it out from under us (the documented rebuild path). In real Electron a
153
+ // WebContentsView whose webContents was destroyed can report `webContents`
154
+ // as undefined — not merely a destroyed handle — so every access must
155
+ // tolerate BOTH (observed in the R1 e2e: `.isDestroyed()` on undefined threw
156
+ // inside the control surface after the host closed the wc).
157
+ function liveHostToolbarWebContents() {
158
+ const wc = hostToolbarView?.webContents;
159
+ if (!wc || wc.isDestroyed())
160
+ return null;
161
+ return wc;
162
+ }
148
163
  // Lazily create the host-toolbar view. Mirrors `showSettings` for the
149
164
  // webPreferences shape and the native simulator for nav hardening +
150
165
  // background color (the host may load arbitrary URLs / content). Idempotent.
151
166
  function ensureHostToolbarView() {
152
- if (hostToolbarView && !hostToolbarView.webContents.isDestroyed()) {
167
+ if (hostToolbarView && liveHostToolbarWebContents()) {
153
168
  return hostToolbarView;
154
169
  }
155
170
  // Rebuilding after the host destroyed the underlying webContents: detach the
@@ -163,21 +178,36 @@ export function createViewManager(ctx) {
163
178
  catch { /* already removed */ }
164
179
  hostToolbarViewAdded = false;
165
180
  }
166
- const view = new WebContentsView({
167
- webPreferences: {
168
- nodeIntegration: false,
169
- contextIsolation: true,
170
- sandbox: false,
171
- // Default: the reverse size-advertiser preload that measures the host
172
- // content's intrinsic height and posts it on the advertise channel so
173
- // main reserves exactly that strip height (dynamic height via
174
- // ViewAnchor). When the host-shell supplies its own toolbar preload
175
- // (workbench config.toolbar.preloadPath), use that instead — the host
176
- // owns the bridge and may install the advertiser itself.
177
- preload: hostToolbarPreloadOverride ?? hostToolbarPreloadPath,
178
- },
179
- });
181
+ // The framework's height-advertiser runtime is SESSION-resident: register
182
+ // it on session.defaultSession (ref-counted across coexisting managers)
183
+ // BEFORE the view exists, so the very first load already runs it. The
184
+ // toolbar WCV stays on the defaultSession (no partition/session override)
185
+ // — moving it onto its own partition would silently detach it from this
186
+ // registration and height advertising would die with no error.
187
+ if (!hostToolbarRuntimeAcquired) {
188
+ acquireHostToolbarSessionRuntime();
189
+ hostToolbarRuntimeAcquired = true;
190
+ }
191
+ // `webPreferences.preload` is the HOST's alone (setPreloadPath); the
192
+ // built-in advertiser no longer rides it (it would execute twice — the
193
+ // session copy + the webPreferences copy). The additionalArguments marker
194
+ // is what the session runtime's guard keys on to activate here and stay a
195
+ // zero-footprint no-op in every other defaultSession renderer.
196
+ const webPreferences = {
197
+ nodeIntegration: false,
198
+ contextIsolation: true,
199
+ sandbox: false,
200
+ additionalArguments: [HOST_TOOLBAR_RUNTIME_MARKER],
201
+ };
202
+ if (hostToolbarPreloadOverride !== null) {
203
+ webPreferences.preload = hostToolbarPreloadOverride;
204
+ }
205
+ const view = new WebContentsView({ webPreferences });
180
206
  hostToolbarView = view;
207
+ // Hook the per-load MessagePort handshake (did-finish-load) + dead-port
208
+ // cleanup (destroyed) on the fresh wc. AFTER the assignment above so the
209
+ // channel's isCurrent guard sees this wc as the live one.
210
+ hostToolbarPort.attach(view.webContents);
181
211
  // Paint the surface a neutral color so growing the reserved strip never
182
212
  // flashes white before the host content paints (mirrors the native
183
213
  // simulator's setBackgroundColor anti-flash).
@@ -200,7 +230,7 @@ export function createViewManager(ctx) {
200
230
  // WebContents (and the host's loaded content) alive. Do NOT create the
201
231
  // view just to immediately hide it.
202
232
  if (isHidden(bounds)) {
203
- if (hostToolbarView && hostToolbarViewAdded && !hostToolbarView.webContents.isDestroyed()) {
233
+ if (hostToolbarView && hostToolbarViewAdded && liveHostToolbarWebContents()) {
204
234
  try {
205
235
  ctx.windows.mainWindow.contentView.removeChildView(hostToolbarView);
206
236
  }
@@ -219,6 +249,12 @@ export function createViewManager(ctx) {
219
249
  view.setBounds(bounds);
220
250
  }
221
251
  function setHostToolbarHeight(extent) {
252
+ // While the host pins a fixed height, drop advertiser reports entirely —
253
+ // the session-resident advertiser is always installed, so forwarding its
254
+ // reports would make the strip oscillate between the pinned and measured
255
+ // heights on every content resize.
256
+ if (hostToolbarHeightMode !== 'auto')
257
+ return;
222
258
  // Push the reserved height back to the main-window renderer so its
223
259
  // placeholder div resizes (closing the dynamic-height loop). The height is
224
260
  // not retained in main — the renderer placeholder is the single source of
@@ -242,37 +278,62 @@ export function createViewManager(ctx) {
242
278
  const hostToolbar = {
243
279
  async loadURL(url) {
244
280
  const view = ensureHostToolbarView();
281
+ // Invalidate SYNCHRONOUSLY at initiation, before the load is issued:
282
+ // the current document is about to be replaced, so a same-tick send()
283
+ // must report false instead of confirming delivery into it. The channel
284
+ // recovers on the new document's did-finish-load handshake. (Cannot
285
+ // rely on did-start-navigation here — that only covers page-initiated
286
+ // navigations once the load is actually under way.)
287
+ hostToolbarPort.invalidate();
245
288
  await view.webContents.loadURL(url);
246
289
  },
247
290
  async loadFile(filePath) {
248
291
  const view = ensureHostToolbarView();
292
+ // Same initiation-invalidates contract as loadURL above.
293
+ hostToolbarPort.invalidate();
249
294
  await view.webContents.loadFile(filePath);
250
295
  },
251
296
  get webContents() {
252
- if (!hostToolbarView)
253
- return null;
254
- if (hostToolbarView.webContents.isDestroyed())
255
- return null;
256
- return hostToolbarView.webContents;
297
+ return liveHostToolbarWebContents();
257
298
  },
258
299
  hide() {
259
300
  hideHostToolbar();
260
301
  },
261
302
  setPreloadPath(path) {
303
+ // The HOST's own webPreferences.preload, applied when the view is next
304
+ // (re)created. `null` = no host preload. The framework advertiser is
305
+ // session-resident and unaffected either way (see ensureHostToolbarView).
262
306
  hostToolbarPreloadOverride = path;
263
307
  },
308
+ setHeightMode(mode) {
309
+ hostToolbarHeightMode = mode;
310
+ if (mode !== 'auto') {
311
+ // Pin immediately: a preload-less/static toolbar never advertises, so
312
+ // waiting for the next report would leave the strip at height 0.
313
+ ctx.notify.hostToolbarHeightChanged(mode.fixed);
314
+ }
315
+ // Switching back to 'auto' deliberately does NOT synthesize a notify —
316
+ // replaying a stale cached height would flash the old size; the NEXT
317
+ // advertiser report drives the placeholder again.
318
+ },
319
+ onMessage(channel, handler) {
320
+ return hostToolbarPort.onMessage(channel, handler);
321
+ },
322
+ send(channel, payload) {
323
+ return hostToolbarPort.send(channel, payload);
324
+ },
264
325
  };
265
326
  function applySettingsBounds() {
266
327
  if (!settingsView || ctx.windows.mainWindow.isDestroyed())
267
328
  return;
268
329
  const [w = 0, h = 0] = ctx.windows.mainWindow.getContentSize();
269
- settingsView.setBounds(layout.computeSettingsBounds(w, h, headerHeight));
330
+ settingsView.setBounds(layout.computeSettingsBounds(w, h, HEADER_H));
270
331
  }
271
332
  function applyPopoverBounds() {
272
333
  if (!popoverView || ctx.windows.mainWindow.isDestroyed())
273
334
  return;
274
335
  const [w = 0, h = 0] = ctx.windows.mainWindow.getContentSize();
275
- popoverView.setBounds(layout.computePopoverBounds(w, h, headerHeight));
336
+ popoverView.setBounds(layout.computePopoverBounds(w, h, HEADER_H));
276
337
  }
277
338
  function clearNativeDevtoolsRetry() {
278
339
  nativeDevtoolsRetryToken++;
@@ -511,84 +572,11 @@ export function createViewManager(ctx) {
511
572
  followNativeDevtoolsServiceHost(event.appId);
512
573
  }
513
574
  // ── ViewManager methods ─────────────────────────────────────────────────
514
- function attachSimulator(simWcId, simWidth) {
515
- const sim = webContents.fromId(simWcId);
516
- if (!sim) {
517
- console.error('[workbench] attachSimulator — simWc not found for id', simWcId);
518
- return;
519
- }
520
- lastSimWidth = simWidth;
521
- simulatorWebContentsId = simWcId;
522
- // Destroy old simulatorView to prevent WebContentsView leak
523
- if (simulatorView) {
524
- hideSimulator();
525
- try {
526
- if (!simulatorView.webContents.isDestroyed()) {
527
- simulatorView.webContents.close();
528
- }
529
- }
530
- catch { /* ignore */ }
531
- simulatorView = null;
532
- }
533
- simulatorView = new WebContentsView();
534
- sim.setDevToolsWebContents(simulatorView.webContents);
535
- // Embedded in the right-panel host view; never bring it to the foreground
536
- // (would steal focus from the user / disrupt e2e).
537
- sim.openDevTools({ mode: 'detach', activate: false });
538
- // Default DevTools to Console panel (Chrome DevTools defaults to Elements).
539
- // The DevTools UI lives inside closed shadow roots, so a light-DOM
540
- // querySelector('[role="tab"]') cannot reach the tab bar to click it.
541
- // Instead drive the front-end's own view manager: the bundled DevTools
542
- // exposes `UI.ViewManager.instance().showView(id)` on `globalThis.UI`
543
- // once the front-end has finished bootstrapping. We poll for it and
544
- // request the `console` view, and also persist the choice via the
545
- // `panel-selectedTab` localStorage key so subsequent reloads honor it.
546
- const devtoolsWc = simulatorView.webContents;
547
- // Keep only Elements/Console/Network tabs (front-most, in that order).
548
- customizeDevtoolsTabs(devtoolsWc);
549
- devtoolsWc.once('dom-ready', () => {
550
- devtoolsWc.executeJavaScript(`
551
- (function() {
552
- try { localStorage.setItem('panel-selectedTab', '"console"') } catch {}
553
- let tries = 0
554
- const timer = setInterval(() => {
555
- tries++
556
- try {
557
- const UI = globalThis.UI
558
- const vm = UI && UI.ViewManager && typeof UI.ViewManager.instance === 'function'
559
- ? UI.ViewManager.instance()
560
- : null
561
- if (vm && typeof vm.showView === 'function') {
562
- vm.showView('console')
563
- clearInterval(timer)
564
- return
565
- }
566
- } catch {}
567
- if (tries > 80) clearInterval(timer)
568
- }, 50)
569
- })()
570
- `).catch(() => { });
571
- });
572
- if (getDefaultTab(ctx) === 'simulator') {
573
- if (simulatorBoundsOverride) {
574
- if (!isHidden(simulatorBoundsOverride)) {
575
- ctx.windows.mainWindow.contentView.addChildView(simulatorView);
576
- simulatorViewAdded = true;
577
- simulatorView.setBounds(simulatorBoundsOverride);
578
- }
579
- }
580
- else {
581
- ctx.windows.mainWindow.contentView.addChildView(simulatorView);
582
- simulatorViewAdded = true;
583
- applySimulatorBounds(simWidth);
584
- }
585
- }
586
- }
587
- function attachNativeSimulatorDevtoolsHost(simWidth) {
575
+ function attachNativeSimulatorDevtoolsHost() {
588
576
  stopFollowingNativeServiceHost();
589
577
  // Destroy old simulatorView to prevent WebContentsView leak
590
578
  if (simulatorView) {
591
- hideSimulator();
579
+ removeSimulatorDevtoolsView();
592
580
  try {
593
581
  if (!simulatorView.webContents.isDestroyed()) {
594
582
  simulatorView.webContents.close();
@@ -657,19 +645,16 @@ export function createViewManager(ctx) {
657
645
  })()
658
646
  `).catch(() => { });
659
647
  });
660
- if (getDefaultTab(ctx) === 'simulator') {
661
- if (simulatorBoundsOverride) {
662
- if (!isHidden(simulatorBoundsOverride)) {
663
- ctx.windows.mainWindow.contentView.addChildView(simulatorView);
664
- simulatorViewAdded = true;
665
- simulatorView.setBounds(simulatorBoundsOverride);
666
- }
667
- }
668
- else {
669
- ctx.windows.mainWindow.contentView.addChildView(simulatorView);
670
- simulatorViewAdded = true;
671
- applySimulatorBounds(simWidth);
672
- }
648
+ // Anchor-only mount: the renderer's published rect is the SOLE authority.
649
+ // If a non-zero rect was already published (it can land before this attach
650
+ // on the project-open ordering), replay it; otherwise the view stays
651
+ // unadded and unsized until the first publish arrives. No static-layout
652
+ // fallback — an attach-time computed rect raced the precise anchor rect
653
+ // and flashed the overlay at the wrong rectangle.
654
+ if (simulatorBoundsOverride && !isHidden(simulatorBoundsOverride)) {
655
+ ctx.windows.mainWindow.contentView.addChildView(simulatorView);
656
+ simulatorViewAdded = true;
657
+ simulatorView.setBounds(simulatorBoundsOverride);
673
658
  }
674
659
  if (ctx.bridge?.isNativeHost()) {
675
660
  unsubscribeNativeRenderEvents = ctx.bridge.onRenderEvent(onNativeRenderEvent);
@@ -738,12 +723,11 @@ export function createViewManager(ctx) {
738
723
  // the defensive re-attach path calling it directly remains safe.
739
724
  ctx.connections.acquire(simWc).own(detachNativeCustomApiBridge);
740
725
  }
741
- function attachNativeSimulator(simulatorUrl, simWidth) {
726
+ function attachNativeSimulator(simulatorUrl, _simWidth) {
742
727
  if (!ctx.preloadPath) {
743
728
  console.error('[workbench] attachNativeSimulator — preloadPath unset; cannot mount native simulator');
744
729
  return;
745
730
  }
746
- lastSimWidth = simWidth;
747
731
  // Tear down any previous native simulator view (relaunch / re-open).
748
732
  if (nativeSimulatorView) {
749
733
  detachNativeCustomApiBridge();
@@ -901,7 +885,7 @@ export function createViewManager(ctx) {
901
885
  // service host so its Console/Network(fetch)/Sources reflect the logic layer.
902
886
  // (The UI/view layer's Elements equivalent is the native WXML panel +
903
887
  // render-guest highlight chain, which targets the active render guest.)
904
- attachNativeSimulatorDevtoolsHost(simWidth);
888
+ attachNativeSimulatorDevtoolsHost();
905
889
  }
906
890
  function detachSimulator() {
907
891
  stopFollowingNativeServiceHost();
@@ -955,23 +939,16 @@ export function createViewManager(ctx) {
955
939
  // session must not be replayed onto a fresh re-attach (stale slice/offset).
956
940
  lastRendererRect = null;
957
941
  }
958
- function showSimulator(simWidth) {
959
- lastSimWidth = simWidth;
960
- if (!simulatorView)
961
- return;
962
- if (!simulatorViewAdded) {
963
- ctx.windows.mainWindow.contentView.addChildView(simulatorView);
964
- simulatorViewAdded = true;
965
- }
966
- applySimulatorBounds(simWidth);
967
- }
968
- function hideSimulator() {
942
+ // Remove (but do not destroy) the DevTools overlay from the contentView.
943
+ // Internal teardown helper for re-attach; user-facing visibility is the
944
+ // anchor 0×0 single path (`setSimulatorDevtoolsBounds`).
945
+ function removeSimulatorDevtoolsView() {
969
946
  if (simulatorView && simulatorViewAdded) {
970
947
  try {
971
948
  ctx.windows.mainWindow.contentView.removeChildView(simulatorView);
972
949
  }
973
950
  catch (e) {
974
- console.error('[workbench] hideSimulator error', e);
951
+ console.error('[workbench] removeSimulatorDevtoolsView error', e);
975
952
  }
976
953
  simulatorViewAdded = false;
977
954
  }
@@ -1035,10 +1012,9 @@ export function createViewManager(ctx) {
1035
1012
  }
1036
1013
  }
1037
1014
  function repositionAll() {
1038
- // Native simulator: bounds owned by the renderer's reportBounds (Model A);
1039
- // its window-resize listener re-measures, so no coarse re-apply here.
1040
- if (simulatorView && simulatorViewAdded)
1041
- applySimulatorBounds(lastSimWidth);
1015
+ // Simulator + DevTools overlay: bounds owned by the renderer's anchor
1016
+ // publishes; their ResizeObserver/window-resize listeners re-measure, so
1017
+ // no static re-apply here.
1042
1018
  if (settingsView && settingsViewAdded)
1043
1019
  applySettingsBounds();
1044
1020
  if (popoverView)
@@ -1046,11 +1022,23 @@ export function createViewManager(ctx) {
1046
1022
  }
1047
1023
  function disposeAll() {
1048
1024
  detachSimulator();
1025
+ // Narrow channel first: close the live MessagePort + sweep the onMessage
1026
+ // registry, so a send() racing teardown reports false instead of posting
1027
+ // into a wc that is about to be closed.
1028
+ hostToolbarPort.dispose();
1049
1029
  // Host-controllable toolbar view: removed from the contentView + its
1050
1030
  // WebContents closed (the host's loaded content is torn down on app exit).
1051
1031
  destroyViewInternal(hostToolbarView);
1052
1032
  hostToolbarView = null;
1053
1033
  hostToolbarViewAdded = false;
1034
+ // Release this manager's reference on the shared defaultSession
1035
+ // toolbar-runtime registration (only if it ever acquired one — a manager
1036
+ // that never used the toolbar must not drive the shared count to zero).
1037
+ // The LAST release unregisters; other coexisting managers keep theirs.
1038
+ if (hostToolbarRuntimeAcquired) {
1039
+ releaseHostToolbarSessionRuntime();
1040
+ hostToolbarRuntimeAcquired = false;
1041
+ }
1054
1042
  safeArea.dispose();
1055
1043
  }
1056
1044
  function setNativeSimulatorViewBounds(params) {
@@ -1102,34 +1090,17 @@ export function createViewManager(ctx) {
1102
1090
  }
1103
1091
  catch { /* hostWebContents unavailable; guests get zoom on attach */ }
1104
1092
  }
1105
- function resize(simWidth) {
1106
- lastSimWidth = simWidth;
1107
- // Native simulator bounds are owned solely by the renderer's reportBounds
1108
- // (Model A) its ResizeObserver/window-resize listeners re-measure on this
1109
- // same resize. No coarse panel-size re-apply here (it raced the precise rect).
1110
- if (simulatorViewAdded)
1111
- applySimulatorBounds(simWidth);
1093
+ function resize(_simWidth) {
1094
+ // Simulator + DevTools overlay bounds are owned solely by the renderer's
1095
+ // anchor publishes its ResizeObserver/window-resize listeners re-measure
1096
+ // on this same resize. No static re-apply (it raced the precise rect).
1112
1097
  if (settingsViewAdded)
1113
1098
  applySettingsBounds();
1114
1099
  }
1115
- function setVisible(visible, simWidth) {
1116
- lastSimWidth = simWidth;
1117
- if (!simulatorView)
1118
- return;
1119
- if (visible && !simulatorViewAdded) {
1120
- showSimulator(simWidth);
1121
- }
1122
- else if (!visible) {
1123
- hideSimulator();
1124
- }
1125
- }
1126
1100
  return {
1127
- attachSimulator,
1128
1101
  attachNativeSimulator,
1129
1102
  detachSimulator,
1130
1103
  reapplySafeArea: (device) => safeArea.reapplyAll(device),
1131
- showSimulator,
1132
- hideSimulator,
1133
1104
  showSettings,
1134
1105
  hideSettings,
1135
1106
  showPopover,
@@ -1143,9 +1114,6 @@ export function createViewManager(ctx) {
1143
1114
  const wc = webContents.fromId(simulatorWebContentsId);
1144
1115
  return wc && !wc.isDestroyed() ? wc : null;
1145
1116
  },
1146
- getLastSimWidth: () => lastSimWidth,
1147
- isSimulatorAdded: () => simulatorViewAdded,
1148
- hasSimulatorView: () => simulatorView !== null,
1149
1117
  getSettingsWebContents: () => {
1150
1118
  if (!settingsView)
1151
1119
  return null;
@@ -1167,16 +1135,9 @@ export function createViewManager(ctx) {
1167
1135
  return null;
1168
1136
  return popoverView.webContents.id;
1169
1137
  },
1170
- getHostToolbarWebContentsId: () => {
1171
- if (!hostToolbarView)
1172
- return null;
1173
- if (hostToolbarView.webContents.isDestroyed())
1174
- return null;
1175
- return hostToolbarView.webContents.id;
1176
- },
1138
+ getHostToolbarWebContentsId: () => liveHostToolbarWebContents()?.id ?? null,
1177
1139
  setNativeSimulatorViewBounds,
1178
1140
  resize,
1179
- setVisible,
1180
1141
  setSimulatorDevtoolsBounds,
1181
1142
  setHostToolbarBounds,
1182
1143
  setHostToolbarHeight,
@@ -12,10 +12,9 @@ import { type ViewManager } from './views/view-manager.js';
12
12
  import { type WindowService } from './window-service.js';
13
13
  import { type WorkspaceService } from './workspace/workspace-service.js';
14
14
  import { type SimulatorApiRegistry } from './simulator/custom-apis.js';
15
- import { type ToolbarStore } from './toolbar/toolbar-store.js';
16
15
  import { sanitizeTemplates } from './projects/templates.js';
17
16
  import type { ProjectsProvider, ProjectTemplate } from './projects/types.js';
18
- import type { BeforeOpenProjectResult, CustomCreateProjectDialogResult } from '../../shared/types.js';
17
+ import type { CustomCreateProjectDialogResult } from '../../shared/types.js';
19
18
  /**
20
19
  * Shared mutable state for the workbench application.
21
20
  * Passed to each IPC module so they can read/write shared state without closures.
@@ -26,14 +25,10 @@ export interface WorkbenchContext {
26
25
  preloadPath: string;
27
26
  /** Absolute path to the renderer dist directory */
28
27
  rendererDir: string;
29
- /** Built-in panel IDs to display (default: all) */
30
- panels: string[];
31
28
  /** Custom API namespace names (e.g. ['qd']) passed to the simulator */
32
29
  apiNamespaces: string[];
33
30
  /** Branding name shown in title bar and getBranding IPC */
34
31
  appName: string;
35
- /** Header bar height in px, used for view layout and exposed to the renderer */
36
- headerHeight: number;
37
32
  /** Host-injected provider for branding info (overrides default appName) */
38
33
  brandingProvider?: () => Promise<{
39
34
  appName: string;
@@ -72,11 +67,6 @@ export interface WorkbenchContext {
72
67
  parentWindow: BrowserWindow;
73
68
  templates: ProjectTemplate[];
74
69
  }) => Promise<CustomCreateProjectDialogResult>;
75
- /**
76
- * Optional host gate for opening projects. Runs after basic directory
77
- * validation and before the current project session is closed.
78
- */
79
- beforeOpenProject?: (projectPath: string) => BeforeOpenProjectResult | Promise<BeforeOpenProjectResult>;
80
70
  /**
81
71
  * Trust predicate consulted by `IpcRegistry` for every incoming IPC.
82
72
  * Resolves the currently-trusted senders (main renderer + overlays)
@@ -99,12 +89,6 @@ export interface WorkbenchContext {
99
89
  * One registry per context — no process-global crosstalk.
100
90
  */
101
91
  simulatorApis: SimulatorApiRegistry;
102
- /**
103
- * Per-context store of host-registered toolbar actions. Populated via
104
- * `instance.toolbar.set`; read by the toolbar IPC handlers. One store per
105
- * context — no process-global crosstalk.
106
- */
107
- toolbar: ToolbarStore;
108
92
  /** Aggregates dispose handlers for every IPC handler, listener, watcher, and CDP session registered by the workbench. */
109
93
  registry: DisposableRegistry;
110
94
  /**
@@ -162,29 +146,27 @@ export interface WorkbenchContext {
162
146
  * run) and injects its raw Network.* CDP events into the DevTools FRONT-END wc
163
147
  * (`window.DevToolsAPI.dispatchMessage`) so the native Network tab renders them;
164
148
  * falls back to a `[网络]` service-host console line when the front-end is
165
- * unavailable. The ViewManager calls `attachSimulator` + `setDevtoolsHost` from
166
- * `attachNativeSimulator` once the simulator WCV + DevTools host exist.
167
- * Undefined until bootstrap wires it.
149
+ * unavailable. The ViewManager calls the forwarder's own `attachSimulator` +
150
+ * `setDevtoolsHost` from `attachNativeSimulator` once the simulator WCV +
151
+ * DevTools host exist. Undefined until bootstrap wires it.
168
152
  */
169
153
  networkForward?: NetworkForwarder;
170
154
  }
171
155
  /**
172
156
  * Inputs for `createWorkbenchContext`. The scalar config fields it shares with
173
- * `WorkbenchConfig` (`adapter` / `apiNamespaces` / `appName` / `headerHeight`)
157
+ * `WorkbenchConfig` (`adapter` / `apiNamespaces` / `appName`)
174
158
  * are derived from there via `Pick` so the two stay in lockstep — no
175
159
  * field-by-field re-declaration. The remaining fields are kept explicit
176
160
  * because their shapes intentionally differ from the config:
177
161
  * - `preloadPath` / `rendererDir` are REQUIRED here (the caller resolves the
178
162
  * defaults before constructing the context) but optional in the config;
179
- * - `panels` is the structural `string[]` (the context stores raw ids);
180
163
  * - `projectsProvider` / `projectTemplates` / `customCreateProjectDialog` use
181
164
  * the main-process types, not the structural mirrors in `shared/types`.
182
165
  */
183
- export interface CreateContextOptions extends Pick<WorkbenchConfig, 'adapter' | 'apiNamespaces' | 'appName' | 'headerHeight'> {
166
+ export interface CreateContextOptions extends Pick<WorkbenchConfig, 'adapter' | 'apiNamespaces' | 'appName'> {
184
167
  mainWindow: BrowserWindow;
185
168
  preloadPath: string;
186
169
  rendererDir: string;
187
- panels?: string[];
188
170
  brandingProvider?: WorkbenchContext['brandingProvider'];
189
171
  /** Host-supplied project list backend. Defaults to LocalProjectsProvider. */
190
172
  projectsProvider?: ProjectsProvider;
@@ -195,8 +177,6 @@ export interface CreateContextOptions extends Pick<WorkbenchConfig, 'adapter' |
195
177
  /** Host-supplied "新建项目" dialog hook. */
196
178
  customCreateProjectDialog?: WorkbenchContext['customCreateProjectDialog'];
197
179
  }
198
- export declare function hasBuiltinPanel(ctx: Pick<WorkbenchContext, 'panels'>, panelId: string): boolean;
199
- export declare function getDefaultTab(ctx: Pick<WorkbenchContext, 'panels'>): string;
200
180
  export declare function createWorkbenchContext(opts: CreateContextOptions): WorkbenchContext;
201
181
  export { sanitizeTemplates };
202
182
  //# sourceMappingURL=workbench-context.d.ts.map
@@ -7,28 +7,15 @@ import { createWindowService } from './window-service.js';
7
7
  import { createWorkspaceService, } from './workspace/workspace-service.js';
8
8
  import { createLocalProjectsProvider } from './projects/local-provider.js';
9
9
  import { createSimulatorApiRegistry, } from './simulator/custom-apis.js';
10
- import { createToolbarStore } from './toolbar/toolbar-store.js';
11
10
  import { resolveTemplates, sanitizeTemplates } from './projects/templates.js';
12
11
  import { BUILTIN_TEMPLATES } from './projects/builtin-templates.js';
13
- export function hasBuiltinPanel(ctx, panelId) {
14
- return ctx.panels.includes(panelId);
15
- }
16
- export function getDefaultTab(ctx) {
17
- if (hasBuiltinPanel(ctx, 'console'))
18
- return 'simulator';
19
- if (ctx.panels.length > 0)
20
- return ctx.panels[0];
21
- return 'simulator';
22
- }
23
12
  export function createWorkbenchContext(opts) {
24
13
  const ctx = {
25
14
  adapter: opts.adapter ?? defaultAdapter,
26
15
  preloadPath: opts.preloadPath,
27
16
  rendererDir: opts.rendererDir,
28
- panels: opts.panels ?? ['wxml', 'console', 'appdata', 'storage'],
29
17
  apiNamespaces: opts.apiNamespaces ?? [],
30
18
  appName: opts.appName ?? 'Dimina DevTools',
31
- headerHeight: opts.headerHeight ?? 40,
32
19
  brandingProvider: opts.brandingProvider,
33
20
  };
34
21
  ctx.registry = new DisposableRegistry();
@@ -39,7 +26,6 @@ export function createWorkbenchContext(opts) {
39
26
  ctx.connections = createConnectionRegistry();
40
27
  ctx.trustedWindowSenderIds = new Map();
41
28
  ctx.simulatorApis = createSimulatorApiRegistry();
42
- ctx.toolbar = createToolbarStore();
43
29
  ctx.windows = createWindowService(opts.mainWindow);
44
30
  ctx.views = createViewManager(ctx);
45
31
  ctx.notify = createRendererNotifier(ctx);