@dimina-kit/devtools 0.3.2-dev.20260611063505 → 0.4.0-dev.20260612025610

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 (91) hide show
  1. package/README.md +56 -41
  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 +25 -30
  6. package/dist/main/app/launch.d.ts +4 -4
  7. package/dist/main/app/launch.js +5 -17
  8. package/dist/main/index.bundle.js +535 -593
  9. package/dist/main/ipc/app.d.ts +1 -1
  10. package/dist/main/ipc/app.js +0 -7
  11. package/dist/main/ipc/index.d.ts +0 -2
  12. package/dist/main/ipc/index.js +0 -2
  13. package/dist/main/ipc/settings.d.ts +1 -1
  14. package/dist/main/ipc/settings.js +1 -13
  15. package/dist/main/ipc/simulator-module.d.ts +3 -3
  16. package/dist/main/ipc/simulator-module.js +3 -7
  17. package/dist/main/ipc/simulator.js +1 -12
  18. package/dist/main/menu/index.d.ts +2 -2
  19. package/dist/main/menu/index.js +4 -2
  20. package/dist/main/runtime/miniapp-runtime.d.ts +206 -28
  21. package/dist/main/runtime/miniapp-runtime.js +6 -11
  22. package/dist/main/services/layout/index.d.ts +0 -11
  23. package/dist/main/services/layout/index.js +0 -23
  24. package/dist/main/services/notifications/renderer-notifier.d.ts +0 -6
  25. package/dist/main/services/notifications/renderer-notifier.js +1 -10
  26. package/dist/main/services/service-host-pool/pool.d.ts +5 -4
  27. package/dist/main/services/views/host-toolbar-port-channel.d.ts +103 -0
  28. package/dist/main/services/views/host-toolbar-port-channel.js +248 -0
  29. package/dist/main/services/views/host-toolbar-session-runtime.d.ts +35 -0
  30. package/dist/main/services/views/host-toolbar-session-runtime.js +55 -0
  31. package/dist/main/services/views/view-manager.d.ts +80 -39
  32. package/dist/main/services/views/view-manager.js +153 -182
  33. package/dist/main/services/workbench-context.d.ts +13 -26
  34. package/dist/main/services/workbench-context.js +5 -14
  35. package/dist/main/services/workspace/workspace-service.d.ts +3 -7
  36. package/dist/main/services/workspace/workspace-service.js +34 -13
  37. package/dist/main/utils/paths.d.ts +11 -2
  38. package/dist/main/utils/paths.js +11 -2
  39. package/dist/main/windows/settings-window/index.d.ts +33 -0
  40. package/dist/main/windows/settings-window/index.js +59 -0
  41. package/dist/preload/index.d.ts +15 -0
  42. package/dist/preload/index.js +15 -0
  43. package/dist/preload/runtime/host-toolbar-port.d.ts +49 -0
  44. package/dist/preload/runtime/host-toolbar-port.js +151 -0
  45. package/dist/preload/runtime/host-toolbar-runtime.d.ts +41 -0
  46. package/dist/preload/runtime/host-toolbar-runtime.js +49 -0
  47. package/dist/preload/windows/{host-toolbar.cjs → host-toolbar-runtime.cjs} +103 -3
  48. package/dist/preload/windows/host-toolbar-runtime.cjs.map +7 -0
  49. package/dist/preload/windows/host-toolbar-runtime.d.ts +2 -0
  50. package/dist/preload/windows/host-toolbar-runtime.js +13 -0
  51. package/dist/preload/windows/simulator.cjs +0 -2
  52. package/dist/preload/windows/simulator.cjs.map +2 -2
  53. package/dist/preload/windows/simulator.js +0 -2
  54. package/dist/renderer/assets/constants-DI382mTP.js +2 -0
  55. package/dist/renderer/assets/index-DLzjz1wv.js +50 -0
  56. package/dist/renderer/assets/{input-CFHmbOgz.js → input-6fpCXV-n.js} +2 -2
  57. package/dist/renderer/assets/ipc-transport-Bs8Sf1B2.css +1 -0
  58. package/dist/renderer/assets/{ipc-transport-BkKKww3u.js → ipc-transport-D22e4dv7.js} +6 -6
  59. package/dist/renderer/assets/popover-CdGdKkkc.js +2 -0
  60. package/dist/renderer/assets/select-X0sbjwdP.js +2 -0
  61. package/dist/renderer/assets/{settings-Cl4IcWnG.js → settings-CS4jYaCw.js} +2 -2
  62. package/dist/renderer/assets/settings-api-CCWTAao_.js +2 -0
  63. package/dist/renderer/assets/workbenchSettings-Bpzaj3bm.js +8 -0
  64. package/dist/renderer/entries/main/index.html +7 -6
  65. package/dist/renderer/entries/popover/index.html +6 -6
  66. package/dist/renderer/entries/settings/index.html +5 -5
  67. package/dist/renderer/entries/workbench-settings/index.html +5 -5
  68. package/dist/shared/constants.d.ts +18 -0
  69. package/dist/shared/constants.js +18 -0
  70. package/dist/shared/ipc-channels.d.ts +10 -23
  71. package/dist/shared/ipc-channels.js +13 -34
  72. package/dist/shared/ipc-schemas.d.ts +0 -12
  73. package/dist/shared/ipc-schemas.js +0 -12
  74. package/dist/shared/types.d.ts +60 -76
  75. package/package.json +6 -6
  76. package/dist/main/ipc/panels.d.ts +0 -4
  77. package/dist/main/ipc/panels.js +0 -46
  78. package/dist/main/ipc/toolbar.d.ts +0 -4
  79. package/dist/main/ipc/toolbar.js +0 -21
  80. package/dist/main/services/toolbar/toolbar-store.d.ts +0 -23
  81. package/dist/main/services/toolbar/toolbar-store.js +0 -40
  82. package/dist/preload/windows/host-toolbar.cjs.map +0 -7
  83. package/dist/preload/windows/host-toolbar.d.ts +0 -2
  84. package/dist/preload/windows/host-toolbar.js +0 -12
  85. package/dist/renderer/assets/constants-CLAQX6VZ.js +0 -2
  86. package/dist/renderer/assets/index-CZQOpCvg.js +0 -50
  87. package/dist/renderer/assets/ipc-transport-Bv_BVu4T.css +0 -1
  88. package/dist/renderer/assets/popover-h1M7n3tt.js +0 -2
  89. package/dist/renderer/assets/select-kj0n1oYf.js +0 -2
  90. package/dist/renderer/assets/settings-api-CCwo2eOi.js +0 -2
  91. 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,72 @@ 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
+ // Validate BEFORE touching any state: a poisoned `{ fixed }` (NaN /
310
+ // ±Infinity / negative) must neither reach the renderer placeholder
311
+ // (`height: NaNpx` corrupts the strip with no error anywhere) nor
312
+ // clobber the standing mode — fail-closed, not fail-corrupt.
313
+ if (mode !== 'auto' && !(Number.isFinite(mode.fixed) && mode.fixed >= 0)) {
314
+ throw new TypeError(`hostToolbar.setHeightMode: fixed height must be a finite, non-negative number (got ${mode.fixed})`);
315
+ }
316
+ hostToolbarHeightMode = mode;
317
+ if (mode !== 'auto') {
318
+ // Pin immediately: a preload-less/static toolbar never advertises, so
319
+ // waiting for the next report would leave the strip at height 0.
320
+ ctx.notify.hostToolbarHeightChanged(mode.fixed);
321
+ }
322
+ // Switching back to 'auto' deliberately does NOT synthesize a notify —
323
+ // replaying a stale cached height would flash the old size; the NEXT
324
+ // advertiser report drives the placeholder again.
325
+ },
326
+ onMessage(channel, handler) {
327
+ return hostToolbarPort.onMessage(channel, handler);
328
+ },
329
+ onReady(handler) {
330
+ return hostToolbarPort.onReady(handler);
331
+ },
332
+ send(channel, payload) {
333
+ return hostToolbarPort.send(channel, payload);
334
+ },
264
335
  };
265
336
  function applySettingsBounds() {
266
337
  if (!settingsView || ctx.windows.mainWindow.isDestroyed())
267
338
  return;
268
339
  const [w = 0, h = 0] = ctx.windows.mainWindow.getContentSize();
269
- settingsView.setBounds(layout.computeSettingsBounds(w, h, headerHeight));
340
+ settingsView.setBounds(layout.computeSettingsBounds(w, h, HEADER_H));
270
341
  }
271
342
  function applyPopoverBounds() {
272
343
  if (!popoverView || ctx.windows.mainWindow.isDestroyed())
273
344
  return;
274
345
  const [w = 0, h = 0] = ctx.windows.mainWindow.getContentSize();
275
- popoverView.setBounds(layout.computePopoverBounds(w, h, headerHeight));
346
+ popoverView.setBounds(layout.computePopoverBounds(w, h, HEADER_H));
276
347
  }
277
348
  function clearNativeDevtoolsRetry() {
278
349
  nativeDevtoolsRetryToken++;
@@ -511,84 +582,11 @@ export function createViewManager(ctx) {
511
582
  followNativeDevtoolsServiceHost(event.appId);
512
583
  }
513
584
  // ── 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) {
585
+ function attachNativeSimulatorDevtoolsHost() {
588
586
  stopFollowingNativeServiceHost();
589
587
  // Destroy old simulatorView to prevent WebContentsView leak
590
588
  if (simulatorView) {
591
- hideSimulator();
589
+ removeSimulatorDevtoolsView();
592
590
  try {
593
591
  if (!simulatorView.webContents.isDestroyed()) {
594
592
  simulatorView.webContents.close();
@@ -657,19 +655,16 @@ export function createViewManager(ctx) {
657
655
  })()
658
656
  `).catch(() => { });
659
657
  });
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
- }
658
+ // Anchor-only mount: the renderer's published rect is the SOLE authority.
659
+ // If a non-zero rect was already published (it can land before this attach
660
+ // on the project-open ordering), replay it; otherwise the view stays
661
+ // unadded and unsized until the first publish arrives. No static-layout
662
+ // fallback — an attach-time computed rect raced the precise anchor rect
663
+ // and flashed the overlay at the wrong rectangle.
664
+ if (simulatorBoundsOverride && !isHidden(simulatorBoundsOverride)) {
665
+ ctx.windows.mainWindow.contentView.addChildView(simulatorView);
666
+ simulatorViewAdded = true;
667
+ simulatorView.setBounds(simulatorBoundsOverride);
673
668
  }
674
669
  if (ctx.bridge?.isNativeHost()) {
675
670
  unsubscribeNativeRenderEvents = ctx.bridge.onRenderEvent(onNativeRenderEvent);
@@ -738,12 +733,11 @@ export function createViewManager(ctx) {
738
733
  // the defensive re-attach path calling it directly remains safe.
739
734
  ctx.connections.acquire(simWc).own(detachNativeCustomApiBridge);
740
735
  }
741
- function attachNativeSimulator(simulatorUrl, simWidth) {
736
+ function attachNativeSimulator(simulatorUrl, _simWidth) {
742
737
  if (!ctx.preloadPath) {
743
738
  console.error('[workbench] attachNativeSimulator — preloadPath unset; cannot mount native simulator');
744
739
  return;
745
740
  }
746
- lastSimWidth = simWidth;
747
741
  // Tear down any previous native simulator view (relaunch / re-open).
748
742
  if (nativeSimulatorView) {
749
743
  detachNativeCustomApiBridge();
@@ -901,7 +895,7 @@ export function createViewManager(ctx) {
901
895
  // service host so its Console/Network(fetch)/Sources reflect the logic layer.
902
896
  // (The UI/view layer's Elements equivalent is the native WXML panel +
903
897
  // render-guest highlight chain, which targets the active render guest.)
904
- attachNativeSimulatorDevtoolsHost(simWidth);
898
+ attachNativeSimulatorDevtoolsHost();
905
899
  }
906
900
  function detachSimulator() {
907
901
  stopFollowingNativeServiceHost();
@@ -955,23 +949,16 @@ export function createViewManager(ctx) {
955
949
  // session must not be replayed onto a fresh re-attach (stale slice/offset).
956
950
  lastRendererRect = null;
957
951
  }
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() {
952
+ // Remove (but do not destroy) the DevTools overlay from the contentView.
953
+ // Internal teardown helper for re-attach; user-facing visibility is the
954
+ // anchor 0×0 single path (`setSimulatorDevtoolsBounds`).
955
+ function removeSimulatorDevtoolsView() {
969
956
  if (simulatorView && simulatorViewAdded) {
970
957
  try {
971
958
  ctx.windows.mainWindow.contentView.removeChildView(simulatorView);
972
959
  }
973
960
  catch (e) {
974
- console.error('[workbench] hideSimulator error', e);
961
+ console.error('[workbench] removeSimulatorDevtoolsView error', e);
975
962
  }
976
963
  simulatorViewAdded = false;
977
964
  }
@@ -1035,10 +1022,9 @@ export function createViewManager(ctx) {
1035
1022
  }
1036
1023
  }
1037
1024
  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);
1025
+ // Simulator + DevTools overlay: bounds owned by the renderer's anchor
1026
+ // publishes; their ResizeObserver/window-resize listeners re-measure, so
1027
+ // no static re-apply here.
1042
1028
  if (settingsView && settingsViewAdded)
1043
1029
  applySettingsBounds();
1044
1030
  if (popoverView)
@@ -1046,11 +1032,23 @@ export function createViewManager(ctx) {
1046
1032
  }
1047
1033
  function disposeAll() {
1048
1034
  detachSimulator();
1035
+ // Narrow channel first: close the live MessagePort + sweep the onMessage
1036
+ // registry, so a send() racing teardown reports false instead of posting
1037
+ // into a wc that is about to be closed.
1038
+ hostToolbarPort.dispose();
1049
1039
  // Host-controllable toolbar view: removed from the contentView + its
1050
1040
  // WebContents closed (the host's loaded content is torn down on app exit).
1051
1041
  destroyViewInternal(hostToolbarView);
1052
1042
  hostToolbarView = null;
1053
1043
  hostToolbarViewAdded = false;
1044
+ // Release this manager's reference on the shared defaultSession
1045
+ // toolbar-runtime registration (only if it ever acquired one — a manager
1046
+ // that never used the toolbar must not drive the shared count to zero).
1047
+ // The LAST release unregisters; other coexisting managers keep theirs.
1048
+ if (hostToolbarRuntimeAcquired) {
1049
+ releaseHostToolbarSessionRuntime();
1050
+ hostToolbarRuntimeAcquired = false;
1051
+ }
1054
1052
  safeArea.dispose();
1055
1053
  }
1056
1054
  function setNativeSimulatorViewBounds(params) {
@@ -1102,34 +1100,17 @@ export function createViewManager(ctx) {
1102
1100
  }
1103
1101
  catch { /* hostWebContents unavailable; guests get zoom on attach */ }
1104
1102
  }
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);
1103
+ function resize(_simWidth) {
1104
+ // Simulator + DevTools overlay bounds are owned solely by the renderer's
1105
+ // anchor publishes its ResizeObserver/window-resize listeners re-measure
1106
+ // on this same resize. No static re-apply (it raced the precise rect).
1112
1107
  if (settingsViewAdded)
1113
1108
  applySettingsBounds();
1114
1109
  }
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
1110
  return {
1127
- attachSimulator,
1128
1111
  attachNativeSimulator,
1129
1112
  detachSimulator,
1130
1113
  reapplySafeArea: (device) => safeArea.reapplyAll(device),
1131
- showSimulator,
1132
- hideSimulator,
1133
1114
  showSettings,
1134
1115
  hideSettings,
1135
1116
  showPopover,
@@ -1143,9 +1124,6 @@ export function createViewManager(ctx) {
1143
1124
  const wc = webContents.fromId(simulatorWebContentsId);
1144
1125
  return wc && !wc.isDestroyed() ? wc : null;
1145
1126
  },
1146
- getLastSimWidth: () => lastSimWidth,
1147
- isSimulatorAdded: () => simulatorViewAdded,
1148
- hasSimulatorView: () => simulatorView !== null,
1149
1127
  getSettingsWebContents: () => {
1150
1128
  if (!settingsView)
1151
1129
  return null;
@@ -1167,16 +1145,9 @@ export function createViewManager(ctx) {
1167
1145
  return null;
1168
1146
  return popoverView.webContents.id;
1169
1147
  },
1170
- getHostToolbarWebContentsId: () => {
1171
- if (!hostToolbarView)
1172
- return null;
1173
- if (hostToolbarView.webContents.isDestroyed())
1174
- return null;
1175
- return hostToolbarView.webContents.id;
1176
- },
1148
+ getHostToolbarWebContentsId: () => liveHostToolbarWebContents()?.id ?? null,
1177
1149
  setNativeSimulatorViewBounds,
1178
1150
  resize,
1179
- setVisible,
1180
1151
  setSimulatorDevtoolsBounds,
1181
1152
  setHostToolbarBounds,
1182
1153
  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;
@@ -46,6 +41,13 @@ export interface WorkbenchContext {
46
41
  windows: WindowService;
47
42
  /** Unified main → renderer event dispatcher */
48
43
  notify: RendererNotifier;
44
+ /**
45
+ * Open (or re-focus) the standalone workbench-settings window. First-class
46
+ * member so contract holders (`MiniappRuntime` / `MenuContext`) can open
47
+ * settings without reaching into `windows`/`notify` plumbing. Wired by
48
+ * `createWorkbenchContext` to the real `openSettingsWindow` path.
49
+ */
50
+ openSettings: () => Promise<void>;
49
51
  /** Single source of truth for project + session + per-project settings */
50
52
  workspace: WorkspaceService;
51
53
  /**
@@ -72,11 +74,6 @@ export interface WorkbenchContext {
72
74
  parentWindow: BrowserWindow;
73
75
  templates: ProjectTemplate[];
74
76
  }) => 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
77
  /**
81
78
  * Trust predicate consulted by `IpcRegistry` for every incoming IPC.
82
79
  * Resolves the currently-trusted senders (main renderer + overlays)
@@ -99,12 +96,6 @@ export interface WorkbenchContext {
99
96
  * One registry per context — no process-global crosstalk.
100
97
  */
101
98
  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
99
  /** Aggregates dispose handlers for every IPC handler, listener, watcher, and CDP session registered by the workbench. */
109
100
  registry: DisposableRegistry;
110
101
  /**
@@ -162,29 +153,27 @@ export interface WorkbenchContext {
162
153
  * run) and injects its raw Network.* CDP events into the DevTools FRONT-END wc
163
154
  * (`window.DevToolsAPI.dispatchMessage`) so the native Network tab renders them;
164
155
  * 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.
156
+ * unavailable. The ViewManager calls the forwarder's own `attachSimulator` +
157
+ * `setDevtoolsHost` from `attachNativeSimulator` once the simulator WCV +
158
+ * DevTools host exist. Undefined until bootstrap wires it.
168
159
  */
169
160
  networkForward?: NetworkForwarder;
170
161
  }
171
162
  /**
172
163
  * Inputs for `createWorkbenchContext`. The scalar config fields it shares with
173
- * `WorkbenchConfig` (`adapter` / `apiNamespaces` / `appName` / `headerHeight`)
164
+ * `WorkbenchConfig` (`adapter` / `apiNamespaces` / `appName`)
174
165
  * are derived from there via `Pick` so the two stay in lockstep — no
175
166
  * field-by-field re-declaration. The remaining fields are kept explicit
176
167
  * because their shapes intentionally differ from the config:
177
168
  * - `preloadPath` / `rendererDir` are REQUIRED here (the caller resolves the
178
169
  * defaults before constructing the context) but optional in the config;
179
- * - `panels` is the structural `string[]` (the context stores raw ids);
180
170
  * - `projectsProvider` / `projectTemplates` / `customCreateProjectDialog` use
181
171
  * the main-process types, not the structural mirrors in `shared/types`.
182
172
  */
183
- export interface CreateContextOptions extends Pick<WorkbenchConfig, 'adapter' | 'apiNamespaces' | 'appName' | 'headerHeight'> {
173
+ export interface CreateContextOptions extends Pick<WorkbenchConfig, 'adapter' | 'apiNamespaces' | 'appName'> {
184
174
  mainWindow: BrowserWindow;
185
175
  preloadPath: string;
186
176
  rendererDir: string;
187
- panels?: string[];
188
177
  brandingProvider?: WorkbenchContext['brandingProvider'];
189
178
  /** Host-supplied project list backend. Defaults to LocalProjectsProvider. */
190
179
  projectsProvider?: ProjectsProvider;
@@ -195,8 +184,6 @@ export interface CreateContextOptions extends Pick<WorkbenchConfig, 'adapter' |
195
184
  /** Host-supplied "新建项目" dialog hook. */
196
185
  customCreateProjectDialog?: WorkbenchContext['customCreateProjectDialog'];
197
186
  }
198
- export declare function hasBuiltinPanel(ctx: Pick<WorkbenchContext, 'panels'>, panelId: string): boolean;
199
- export declare function getDefaultTab(ctx: Pick<WorkbenchContext, 'panels'>): string;
200
187
  export declare function createWorkbenchContext(opts: CreateContextOptions): WorkbenchContext;
201
188
  export { sanitizeTemplates };
202
189
  //# sourceMappingURL=workbench-context.d.ts.map