@dimina-kit/devtools 0.4.0-dev.20260718143821 → 0.4.0-dev.20260728063215

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 (68) hide show
  1. package/dist/main/app/app.js +67 -1
  2. package/dist/main/app/lifecycle.js +9 -0
  3. package/dist/main/index.bundle.js +928 -162
  4. package/dist/main/ipc/bridge-router.d.ts +36 -0
  5. package/dist/main/ipc/bridge-router.js +56 -0
  6. package/dist/main/ipc/index.d.ts +1 -0
  7. package/dist/main/ipc/index.js +1 -0
  8. package/dist/main/ipc/internal-devtools.d.ts +15 -0
  9. package/dist/main/ipc/internal-devtools.js +9 -0
  10. package/dist/main/services/compile-standby.js +4 -0
  11. package/dist/main/services/console-forward/cdp-inject.d.ts +18 -0
  12. package/dist/main/services/console-forward/cdp-inject.js +30 -0
  13. package/dist/main/services/console-forward/global-console-mirror.d.ts +33 -0
  14. package/dist/main/services/console-forward/global-console-mirror.js +71 -0
  15. package/dist/main/services/console-forward/global-diagnostics-mirror.d.ts +33 -0
  16. package/dist/main/services/console-forward/global-diagnostics-mirror.js +73 -0
  17. package/dist/main/services/console-forward/index.d.ts +15 -1
  18. package/dist/main/services/console-forward/index.js +42 -2
  19. package/dist/main/services/console-forward/internal-log.d.ts +11 -0
  20. package/dist/main/services/console-forward/internal-log.js +23 -0
  21. package/dist/main/services/console-forward/open-gated-relay.d.ts +67 -0
  22. package/dist/main/services/console-forward/open-gated-relay.js +55 -0
  23. package/dist/main/services/diagnostics/index.d.ts +10 -0
  24. package/dist/main/services/elements-forward/index.js +14 -96
  25. package/dist/main/services/network-forward/frontend-dispatch.d.ts +64 -0
  26. package/dist/main/services/network-forward/frontend-dispatch.js +113 -8
  27. package/dist/main/services/network-forward/global-body-gate.d.ts +46 -0
  28. package/dist/main/services/network-forward/global-body-gate.js +93 -0
  29. package/dist/main/services/network-forward/global-mirror-test-fixtures.d.ts +48 -0
  30. package/dist/main/services/network-forward/global-mirror-test-fixtures.js +96 -0
  31. package/dist/main/services/network-forward/index.d.ts +31 -0
  32. package/dist/main/services/network-forward/index.js +256 -8
  33. package/dist/main/services/network-forward/user-facing.d.ts +21 -0
  34. package/dist/main/services/network-forward/user-facing.js +50 -0
  35. package/dist/main/services/views/console-filter.d.ts +145 -0
  36. package/dist/main/services/views/console-filter.js +233 -0
  37. package/dist/main/services/views/frontend-bootstrap-gate.d.ts +59 -0
  38. package/dist/main/services/views/frontend-bootstrap-gate.js +117 -0
  39. package/dist/main/services/views/native-simulator-devtools-host.js +138 -30
  40. package/dist/main/services/views/view-manager-devtools-host-test-fixtures.d.ts +76 -0
  41. package/dist/main/services/views/view-manager-devtools-host-test-fixtures.js +182 -0
  42. package/dist/main/services/workbench-context.d.ts +10 -0
  43. package/dist/main/utils/theme.d.ts +10 -6
  44. package/dist/main/utils/theme.js +10 -6
  45. package/dist/main/windows/internal-devtools-window/index.d.ts +66 -0
  46. package/dist/main/windows/internal-devtools-window/index.js +160 -0
  47. package/dist/native-host/render/render.js +89 -73
  48. package/dist/native-host/service/service.js +2 -2
  49. package/dist/preload/windows/host-toolbar-runtime.cjs.map +1 -1
  50. package/dist/preload/windows/simulator.cjs.map +1 -1
  51. package/dist/renderer/assets/index-M5NDq_vi.js +49 -0
  52. package/dist/renderer/assets/{input-c2OcrrZy.js → input-dsgeTmVX.js} +2 -2
  53. package/dist/renderer/assets/ipc-transport-BgFdT9bT.js +9 -0
  54. package/dist/renderer/assets/{popover-kv2qspWz.js → popover-Disfu1cx.js} +2 -2
  55. package/dist/renderer/assets/{select-DKqzMtTh.js → select-DAw1hzkl.js} +2 -2
  56. package/dist/renderer/assets/{settings-dJNKTGr8.js → settings-M_8GFW2M.js} +2 -2
  57. package/dist/renderer/assets/settings-api-DFR5eDl_.js +2 -0
  58. package/dist/renderer/assets/{workbenchSettings-DQ_VP5-W.js → workbenchSettings-CxdXeoOu.js} +2 -2
  59. package/dist/renderer/entries/main/index.html +5 -5
  60. package/dist/renderer/entries/popover/index.html +4 -4
  61. package/dist/renderer/entries/settings/index.html +4 -4
  62. package/dist/renderer/entries/workbench-settings/index.html +3 -3
  63. package/dist/shared/ipc-channels.d.ts +3 -0
  64. package/dist/shared/ipc-channels.js +6 -0
  65. package/package.json +8 -8
  66. package/dist/renderer/assets/index-DfDIvgvK.js +0 -49
  67. package/dist/renderer/assets/ipc-transport-CuDJ07aE.js +0 -9
  68. package/dist/renderer/assets/settings-api-qpXKDRXt.js +0 -2
@@ -1,6 +1,8 @@
1
- import { WebContentsView } from 'electron';
1
+ import { WebContentsView, webContents as electronWebContents } from 'electron';
2
2
  import { buildDevtoolsProjectSourceLinksScript, decodeOpenInEditorUrl, projectSourceContextFromServiceHostUrl, } from '../../../shared/open-in-editor.js';
3
3
  import { buildCustomizeTabsScript } from './devtools-tabs.js';
4
+ import { buildConsoleFilterScript, buildLiveConsoleFilterScript } from './console-filter.js';
5
+ import { whenFrontendBootstrapped } from './frontend-bootstrap-gate.js';
4
6
  import { installElementsForward } from '../elements-forward/index.js';
5
7
  import { installServiceConsoleForward } from '../service-console/index.js';
6
8
  import { VIEW_ID } from '../../../shared/view-ids.js';
@@ -54,6 +56,7 @@ export function createDevtoolsHost(ctx, reconciler, deps) {
54
56
  // (`rebuildDevtoolsHostView`) rather than target it again.
55
57
  let devtoolsHostUsed = false;
56
58
  let unsubscribeNativeRenderEvents = null;
59
+ let unsubscribeNativeServiceHostReady = null;
57
60
  // Disposer for the Elements-forward feature (routes the front-end's Elements
58
61
  // CDP traffic at the active render guest). Installed in
59
62
  // `attach`, stopped on detach / host destroyed.
@@ -106,6 +109,13 @@ export function createDevtoolsHost(ctx, reconciler, deps) {
106
109
  catch { /* ignore */ }
107
110
  unsubscribeNativeRenderEvents = null;
108
111
  }
112
+ if (unsubscribeNativeServiceHostReady) {
113
+ try {
114
+ unsubscribeNativeServiceHostReady();
115
+ }
116
+ catch { /* ignore */ }
117
+ unsubscribeNativeServiceHostReady = null;
118
+ }
109
119
  closeNativeDevtoolsSource();
110
120
  }
111
121
  function injectOpenResourceHandler(serviceWc, devtoolsWc) {
@@ -158,6 +168,39 @@ export function createDevtoolsHost(ctx, reconciler, deps) {
158
168
  }
159
169
  catch { /* wc surface incomplete / torn down; degrade silently */ }
160
170
  }
171
+ // User-facing Console de-noise: hide dimina
172
+ // framework internal log lines from THIS right-panel Console — the
173
+ // standalone internal DevTools window (native-only, wired elsewhere) shows
174
+ // them unfiltered. Re-applied on every re-point, same policy as
175
+ // customizeDevtoolsTabs. Runs BOTH the persisted-preference write (for a
176
+ // not-yet-constructed panel) AND the live-object drive (for a panel that's
177
+ // already constructed by the time this injection lands, or finishes
178
+ // constructing shortly after — see console-filter.ts's header comment for
179
+ // why the persisted write alone cannot cover that case).
180
+ // Bootstrap-gated: the live script touches `Console.ConsoleView.instance()`,
181
+ // which — fired before the front-end's own bootstrap completes —
182
+ // constructs IssuesManager early and permanently kills that bootstrap (see
183
+ // frontend-bootstrap-gate.ts). Gate resolving false = degrade silently,
184
+ // this module's standing convention.
185
+ function applyConsoleFilter(devtoolsWc) {
186
+ try {
187
+ if (devtoolsWc.isDestroyed())
188
+ return;
189
+ const inject = () => {
190
+ void whenFrontendBootstrapped(devtoolsWc).then((ready) => {
191
+ if (!ready || devtoolsWc.isDestroyed())
192
+ return;
193
+ try {
194
+ void devtoolsWc.executeJavaScript(buildConsoleFilterScript()).catch(() => { });
195
+ void devtoolsWc.executeJavaScript(buildLiveConsoleFilterScript()).catch(() => { });
196
+ }
197
+ catch { /* wc torn down mid-call */ }
198
+ });
199
+ };
200
+ injectWhenReady(devtoolsWc, 'console-filter', inject);
201
+ }
202
+ catch { /* wc surface incomplete / torn down; degrade silently */ }
203
+ }
161
204
  function wireOpenInEditor(serviceWc, devtoolsWc) {
162
205
  // Inject the front-end handler on every (re)attach — the DevTools front-end
163
206
  // wc is recreated per simulatorView, so a fresh host needs the handler set.
@@ -246,6 +289,9 @@ export function createDevtoolsHost(ctx, reconciler, deps) {
246
289
  // Re-applied on every re-point so a service-host pool swap (fresh
247
290
  // openDevTools) re-asserts the custom tab bar.
248
291
  customizeDevtoolsTabs(simulatorView.webContents);
292
+ // De-noise the user-facing Console: hide
293
+ // dimina framework internal log lines from THIS panel by default.
294
+ applyConsoleFilter(simulatorView.webContents);
249
295
  // Capture service-layer console via CDP (NOT a preload monkeypatch, which
250
296
  // would clobber native source attribution) and feed it to the console
251
297
  // fan-out (automation `App.logAdded`). Bound to THIS service wc; replaced
@@ -286,8 +332,25 @@ export function createDevtoolsHost(ctx, reconciler, deps) {
286
332
  return false;
287
333
  return pointNativeDevtoolsAtServiceWc(wc);
288
334
  }
289
- function scheduleNativeDevtoolsFollow(appId, attempt = 0) {
290
- if (attempt >= 20)
335
+ // Bound on how long the FALLBACK poll (below) keeps retrying before giving
336
+ // up a pure safety valve, not the real mechanism. The real mechanism is
337
+ // `onNativeServiceHostReady` (an event, not a poll): it fires the moment
338
+ // `ctx.bridge.getServiceWc` is GUARANTEED to resolve, so this fallback only
339
+ // exists for the residual gap where `followNativeDevtoolsServiceHost` runs
340
+ // from a render event that races ahead of that event somehow. A fixed short
341
+ // attempt count here (originally 20×50ms = 1s) was the actual production
342
+ // bug this module was fixed for: under real machine load,
343
+ // `ctx.bridge.getServiceWc(appId)` can take longer than 1s to resolve, and
344
+ // once the fixed budget ran out the right-panel DevTools attach was
345
+ // silently, permanently lost for the rest of that session (confirmed via
346
+ // real reproduction + timing instrumentation). Bounded by WALL-CLOCK time
347
+ // (generous — normal resolution is well under a second) rather than a
348
+ // small attempt count, since the fallback's whole job is to outlast a slow
349
+ // machine, not a slow algorithm.
350
+ const NATIVE_DEVTOOLS_FALLBACK_POLL_MAX_MS = 60_000;
351
+ const NATIVE_DEVTOOLS_FALLBACK_POLL_INTERVAL_MS = 50;
352
+ function scheduleNativeDevtoolsFollow(appId, startedAt = Date.now()) {
353
+ if (Date.now() - startedAt >= NATIVE_DEVTOOLS_FALLBACK_POLL_MAX_MS)
291
354
  return;
292
355
  if (!ctx.bridge?.isNativeHost())
293
356
  return;
@@ -300,8 +363,8 @@ export function createDevtoolsHost(ctx, reconciler, deps) {
300
363
  return;
301
364
  if (pointNativeDevtoolsAtActiveServiceHost(appId))
302
365
  return;
303
- scheduleNativeDevtoolsFollow(appId, attempt + 1);
304
- }, 50);
366
+ scheduleNativeDevtoolsFollow(appId, startedAt);
367
+ }, NATIVE_DEVTOOLS_FALLBACK_POLL_INTERVAL_MS);
305
368
  }
306
369
  function followNativeDevtoolsServiceHost(appId) {
307
370
  clearNativeDevtoolsRetry();
@@ -309,10 +372,53 @@ export function createDevtoolsHost(ctx, reconciler, deps) {
309
372
  return;
310
373
  scheduleNativeDevtoolsFollow(appId);
311
374
  }
375
+ // The real mechanism: fires the moment a service host's `service.html`
376
+ // did-finish-load's, i.e. the exact instant `serviceWcId` is guaranteed
377
+ // resolvable — no polling, no race against a fixed retry budget. Resolves
378
+ // the wc DIRECTLY via the id the event carries (never re-resolving through
379
+ // `getServiceWc(appId)`, which during same-app respawn overlap returns the
380
+ // newest session, not necessarily the one that fired this event), then
381
+ // validates the event against that same authority before acting — see the
382
+ // generation gate below.
383
+ function onNativeServiceHostReady(event) {
384
+ if (!ctx.bridge?.isNativeHost())
385
+ return;
386
+ if (!simulatorView || simulatorView.webContents.isDestroyed())
387
+ return;
388
+ const wc = electronWebContents.fromId(event.serviceWcId);
389
+ if (!wc || wc.isDestroyed())
390
+ return;
391
+ // Generation gate: `getServiceWc` resolves the NEWEST live session
392
+ // (bridge-router's findAppSessionByAppId is last-inserted-wins), so an
393
+ // event it CONTRADICTS is a superseded session's late-arriving ready —
394
+ // repointing there would resurrect a dying host (the newer session's own
395
+ // ready does/did the real repoint). An authority that cannot resolve at
396
+ // all fails OPEN: the event is the only signal available, and dropping
397
+ // it would strand the attach on nothing. Fail-open cannot stick to a
398
+ // dying wc in practice — the direct emit is same-tick with bootServiceHost
399
+ // (session still registered), the catch-up path re-validates the session
400
+ // before delivering, and even a hypothetical miss just makes point()
401
+ // return false into the wall-clock-bounded fallback.
402
+ const current = ctx.bridge.getServiceWc(event.appId);
403
+ if (current && current.id !== event.serviceWcId)
404
+ return;
405
+ // Only a SUCCESSFUL repoint may retire the standing fallback poll — a
406
+ // failed attempt (service wc torn down mid-point is the common race)
407
+ // must leave/re-arm it, or this very event cancels the retry that would
408
+ // have eventually attached.
409
+ if (pointNativeDevtoolsAtServiceWc(wc)) {
410
+ clearNativeDevtoolsRetry();
411
+ return;
412
+ }
413
+ scheduleNativeDevtoolsFollow(event.appId);
414
+ }
312
415
  // Render-side activity (a page DOM mounting / the visible page changing)
313
416
  // always follows a spawn or respawn, by which point the service window exists
314
417
  // (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.
418
+ // at the now-current service host on every such event. Belt-and-suspenders
419
+ // alongside `onNativeServiceHostReady` — by the time a render event fires,
420
+ // the service host that produced it is definitionally already resolvable,
421
+ // so this path should normally resolve immediately (no fallback poll needed).
316
422
  function onNativeRenderEvent(event) {
317
423
  if (event.kind !== 'activePage' && event.kind !== 'domReady')
318
424
  return;
@@ -341,11 +447,17 @@ export function createDevtoolsHost(ctx, reconciler, deps) {
341
447
  // Default DevTools to Console panel (Chrome DevTools defaults to Elements).
342
448
  // The DevTools UI lives inside closed shadow roots, so a light-DOM
343
449
  // 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.
450
+ // Instead drive the front-end's own view manager via `globalThis.EUI`
451
+ // (the bundle's real exposure of the ui/legacy namespace) NOT
452
+ // `globalThis.UI`, which never exists on this front-end build (same
453
+ // finding devtools-tabs.ts records; an earlier `UI.…`-polling version of
454
+ // this script was dead code and the panel silently defaulted to
455
+ // Elements). Gated on frontend bootstrap: `ViewManager.instance()` is a
456
+ // singleton construction, only safe after the front-end's own MainImpl
457
+ // finished wiring (see frontend-bootstrap-gate.ts). (A persisted
458
+ // `panel-selected-tab` preference write is NOT an alternative mechanism —
459
+ // this custom devtools:// host persists nothing to disk; see
460
+ // console-filter.ts's header.)
349
461
  const devtoolsWc = simulatorView.webContents;
350
462
  // The boot-time burst of `executeJavaScript()` injects on this host wc (tab
351
463
  // customization / console default / Elements+Network forwarding below) each
@@ -400,27 +512,22 @@ export function createDevtoolsHost(ctx, reconciler, deps) {
400
512
  });
401
513
  }
402
514
  injectWhenReady(devtoolsWc, 'console-default', () => {
403
- devtoolsWc.executeJavaScript(`
404
- (function() {
405
- try { localStorage.setItem('panel-selectedTab', '"console"') } catch {}
406
- let tries = 0
407
- const timer = setInterval(() => {
408
- tries++
515
+ void whenFrontendBootstrapped(devtoolsWc).then((ready) => {
516
+ if (!ready || devtoolsWc.isDestroyed())
517
+ return;
518
+ devtoolsWc.executeJavaScript(`
519
+ (function() {
409
520
  try {
410
- const UI = globalThis.UI
411
- const vm = UI && UI.ViewManager && typeof UI.ViewManager.instance === 'function'
412
- ? UI.ViewManager.instance()
521
+ var EUI = globalThis.EUI
522
+ var vm = EUI && EUI.ViewManager && EUI.ViewManager.ViewManager
523
+ && typeof EUI.ViewManager.ViewManager.instance === 'function'
524
+ ? EUI.ViewManager.ViewManager.instance()
413
525
  : null
414
- if (vm && typeof vm.showView === 'function') {
415
- vm.showView('console')
416
- clearInterval(timer)
417
- return
418
- }
419
- } catch {}
420
- if (tries > 80) clearInterval(timer)
421
- }, 50)
422
- })()
423
- `).catch(() => { });
526
+ if (vm && typeof vm.showView === 'function') void vm.showView('console')
527
+ } catch (_) {}
528
+ })()
529
+ `).catch(() => { });
530
+ });
424
531
  });
425
532
  // Anchor-only mount: the renderer's published rect is the SOLE authority.
426
533
  // If a non-zero rect was already published (it can land before this attach
@@ -440,6 +547,7 @@ export function createDevtoolsHost(ctx, reconciler, deps) {
440
547
  rebuildDevtoolsHostView();
441
548
  if (ctx.bridge?.isNativeHost()) {
442
549
  unsubscribeNativeRenderEvents = ctx.bridge.onRenderEvent(onNativeRenderEvent);
550
+ unsubscribeNativeServiceHostReady = ctx.bridge.onServiceHostReady(onNativeServiceHostReady);
443
551
  followNativeDevtoolsServiceHost();
444
552
  }
445
553
  }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * Shared harness for the simulator-DevTools host suites
3
+ * (`view-manager-devtools-host-repoint.test.ts` +
4
+ * `view-manager-service-host-ready.test.ts`), split out so each test file
5
+ * stays under the repo's 500-line-per-file ratchet. Pure test scaffolding,
6
+ * not code under test — same pattern as
7
+ * network-forward/global-mirror-test-fixtures.ts.
8
+ *
9
+ * Consumers register the module mocks themselves (vi.mock factories are
10
+ * hoisted per test file, so they live there, thin):
11
+ * vi.mock('electron', async () =>
12
+ * (await import('./view-manager-devtools-host-test-fixtures.js')).electronModuleMock())
13
+ * vi.mock('../../utils/paths.js', async () =>
14
+ * (await import('./view-manager-devtools-host-test-fixtures.js')).pathsModuleMock())
15
+ * The electron factory closes over this module's `constructed` /
16
+ * `serviceWcRegistry` registries, so test assertions and the mock see the
17
+ * same objects (vitest gives each test FILE its own instance of this module —
18
+ * no cross-file bleed).
19
+ */
20
+ import { vi } from 'vitest';
21
+ import type { RenderEvent, ServiceHostReadyEvent } from '../../ipc/bridge-router.js';
22
+ import type { ViewManagerContext } from './view-manager.js';
23
+ export type StubWebContents = {
24
+ destroyed: boolean;
25
+ id: number;
26
+ isDestroyed: () => boolean;
27
+ close: ReturnType<typeof vi.fn>;
28
+ loadURL: ReturnType<typeof vi.fn>;
29
+ loadFile: ReturnType<typeof vi.fn>;
30
+ on: ReturnType<typeof vi.fn>;
31
+ once: ReturnType<typeof vi.fn>;
32
+ removeListener: ReturnType<typeof vi.fn>;
33
+ setWindowOpenHandler: ReturnType<typeof vi.fn>;
34
+ setZoomFactor: ReturnType<typeof vi.fn>;
35
+ send: ReturnType<typeof vi.fn>;
36
+ setMaxListeners: ReturnType<typeof vi.fn>;
37
+ getMaxListeners: ReturnType<typeof vi.fn>;
38
+ isLoading: ReturnType<typeof vi.fn>;
39
+ executeJavaScript: ReturnType<typeof vi.fn>;
40
+ setDevToolsWebContents: ReturnType<typeof vi.fn>;
41
+ openDevTools: ReturnType<typeof vi.fn>;
42
+ isDevToolsOpened: ReturnType<typeof vi.fn>;
43
+ closeDevTools: ReturnType<typeof vi.fn>;
44
+ getURL: ReturnType<typeof vi.fn>;
45
+ };
46
+ export type StubView = {
47
+ webContents: StubWebContents;
48
+ setBounds: ReturnType<typeof vi.fn>;
49
+ setBackgroundColor: ReturnType<typeof vi.fn>;
50
+ };
51
+ /** Every WebContentsView ever constructed by the code under test, in
52
+ * construction order: attachNativeSimulator builds [0] = the native simulator
53
+ * content view, then attachNativeSimulatorDevtoolsHost builds [1] = the
54
+ * DevTools front-end host view. Reset in each file's beforeEach. */
55
+ export declare const constructed: StubView[];
56
+ /** Service-host wcs registered by `makeServiceWc`, looked up by
57
+ * `electron.webContents.fromId` — mirrors real Electron's global wc registry
58
+ * so `onNativeServiceHostReady`'s `webContents.fromId(event.serviceWcId)`
59
+ * resolution has something real to find. */
60
+ export declare const serviceWcRegistry: Map<number, StubWebContents>;
61
+ /** The `electron` module surface view-manager and its injectors touch. */
62
+ export declare function electronModuleMock(): Record<string, unknown>;
63
+ export declare function pathsModuleMock(): Record<string, unknown>;
64
+ export declare const SIM_URL = "http://localhost:7788/simulator.html?appId=repoint";
65
+ /** A hidden SERVICE-HOST BrowserWindow wc (top-level, can host DevTools). */
66
+ export declare function makeServiceWc(id: number): StubWebContents;
67
+ export interface DevtoolsHostHarness {
68
+ addChildView: ReturnType<typeof vi.fn>;
69
+ removeChildView: ReturnType<typeof vi.fn>;
70
+ setActiveServiceWc: (wc: StubWebContents) => void;
71
+ emitRenderEvent: (event: RenderEvent) => void;
72
+ emitServiceHostReady: (event: ServiceHostReadyEvent) => void;
73
+ ctx: ViewManagerContext;
74
+ }
75
+ export declare function makeContext(): DevtoolsHostHarness;
76
+ //# sourceMappingURL=view-manager-devtools-host-test-fixtures.d.ts.map
@@ -0,0 +1,182 @@
1
+ /**
2
+ * Shared harness for the simulator-DevTools host suites
3
+ * (`view-manager-devtools-host-repoint.test.ts` +
4
+ * `view-manager-service-host-ready.test.ts`), split out so each test file
5
+ * stays under the repo's 500-line-per-file ratchet. Pure test scaffolding,
6
+ * not code under test — same pattern as
7
+ * network-forward/global-mirror-test-fixtures.ts.
8
+ *
9
+ * Consumers register the module mocks themselves (vi.mock factories are
10
+ * hoisted per test file, so they live there, thin):
11
+ * vi.mock('electron', async () =>
12
+ * (await import('./view-manager-devtools-host-test-fixtures.js')).electronModuleMock())
13
+ * vi.mock('../../utils/paths.js', async () =>
14
+ * (await import('./view-manager-devtools-host-test-fixtures.js')).pathsModuleMock())
15
+ * The electron factory closes over this module's `constructed` /
16
+ * `serviceWcRegistry` registries, so test assertions and the mock see the
17
+ * same objects (vitest gives each test FILE its own instance of this module —
18
+ * no cross-file bleed).
19
+ */
20
+ import { vi } from 'vitest';
21
+ import { createConnectionRegistry } from '@dimina-kit/electron-deck/main';
22
+ /** Every WebContentsView ever constructed by the code under test, in
23
+ * construction order: attachNativeSimulator builds [0] = the native simulator
24
+ * content view, then attachNativeSimulatorDevtoolsHost builds [1] = the
25
+ * DevTools front-end host view. Reset in each file's beforeEach. */
26
+ export const constructed = [];
27
+ /** Service-host wcs registered by `makeServiceWc`, looked up by
28
+ * `electron.webContents.fromId` — mirrors real Electron's global wc registry
29
+ * so `onNativeServiceHostReady`'s `webContents.fromId(event.serviceWcId)`
30
+ * resolution has something real to find. */
31
+ export const serviceWcRegistry = new Map();
32
+ /** One stub wc surface shared by BOTH stub flavors (the constructed
33
+ * WebContentsView host wcs and the standalone service-host wcs) — a single
34
+ * factory instead of two hand-maintained near-identical literals. */
35
+ function makeStubWc(id, overrides = {}) {
36
+ return {
37
+ destroyed: false,
38
+ id,
39
+ isDestroyed() { return this.destroyed; },
40
+ close: vi.fn(),
41
+ loadURL: vi.fn(() => Promise.resolve()),
42
+ loadFile: vi.fn(() => Promise.resolve()),
43
+ on: vi.fn(),
44
+ once: vi.fn(),
45
+ removeListener: vi.fn(),
46
+ setWindowOpenHandler: vi.fn(),
47
+ setZoomFactor: vi.fn(),
48
+ send: vi.fn(),
49
+ setMaxListeners: vi.fn(),
50
+ getMaxListeners: vi.fn(() => 10),
51
+ // DevTools front-end host wc surface (the WebContentsView flavor is used
52
+ // for `simulatorView.webContents`, i.e. the right-panel DevTools host).
53
+ isLoading: vi.fn(() => false),
54
+ executeJavaScript: vi.fn(() => Promise.resolve()),
55
+ setDevToolsWebContents: vi.fn(),
56
+ openDevTools: vi.fn(),
57
+ isDevToolsOpened: vi.fn(() => false),
58
+ closeDevTools: vi.fn(),
59
+ getURL: vi.fn(() => ''),
60
+ ...overrides,
61
+ };
62
+ }
63
+ /** The `electron` module surface view-manager and its injectors touch. */
64
+ export function electronModuleMock() {
65
+ let nextId = 1;
66
+ class WebContentsView {
67
+ webContents;
68
+ setBounds = vi.fn();
69
+ setBackgroundColor = vi.fn();
70
+ constructor(_opts) {
71
+ this.webContents = makeStubWc(nextId++, {
72
+ close: vi.fn(function () { this.destroyed = true; }),
73
+ });
74
+ constructed.push(this);
75
+ }
76
+ }
77
+ const ipcMain = {
78
+ on: vi.fn(),
79
+ removeListener: vi.fn(),
80
+ handle: vi.fn(),
81
+ removeHandler: vi.fn(),
82
+ };
83
+ return {
84
+ WebContentsView,
85
+ ipcMain,
86
+ shell: { openExternal: vi.fn() },
87
+ nativeTheme: { shouldUseDarkColors: false, on: vi.fn(), removeListener: vi.fn() },
88
+ webContents: {
89
+ fromId: vi.fn((id) => serviceWcRegistry.get(id)),
90
+ getAllWebContents: vi.fn(() => []),
91
+ },
92
+ default: { ipcMain },
93
+ };
94
+ }
95
+ export function pathsModuleMock() {
96
+ return {
97
+ mainPreloadPath: '/stub/preload.js',
98
+ hostToolbarPreloadPath: '/stub/host-toolbar-preload.js',
99
+ cjsSiblingPreloadPath: (p) => p.replace(/\.js$/, '.cjs'),
100
+ devtoolsPackageRoot: '/stub/devtools-pkg-root',
101
+ };
102
+ }
103
+ export const SIM_URL = 'http://localhost:7788/simulator.html?appId=repoint';
104
+ /** A hidden SERVICE-HOST BrowserWindow wc (top-level, can host DevTools). */
105
+ export function makeServiceWc(id) {
106
+ const wc = makeStubWc(id, { isDevToolsOpened: vi.fn(() => true) });
107
+ serviceWcRegistry.set(id, wc);
108
+ return wc;
109
+ }
110
+ export function makeContext() {
111
+ const addChildView = vi.fn();
112
+ const removeChildView = vi.fn();
113
+ const contentView = { addChildView, removeChildView, children: [] };
114
+ const mainWindow = {
115
+ destroyed: false,
116
+ contentView,
117
+ isDestroyed() { return this.destroyed; },
118
+ getContentSize: () => [1280, 980],
119
+ };
120
+ const notify = {
121
+ popoverInit: vi.fn(),
122
+ popoverClosed: vi.fn(),
123
+ hostToolbarHeightChanged: vi.fn(),
124
+ };
125
+ // ── ctx.bridge stub ───────────────────────────────────────────────────────
126
+ // Minimal `BridgeRouterHandle`: `getServiceWc` resolves to whatever the test
127
+ // currently points `currentServiceWc` at (simulating a pre-warm-pool swap
128
+ // between render events), and `onRenderEvent` fans a manually-driven
129
+ // `RenderEvent` out to every subscriber (view-manager's own follow-path AND
130
+ // elements-forward's internal subscription both register here).
131
+ let currentServiceWc = null;
132
+ const renderEventListeners = new Set();
133
+ const serviceHostReadyListeners = new Set();
134
+ const bridge = {
135
+ isNativeHost: () => true,
136
+ resolveRenderWc: () => null,
137
+ getServiceWc: vi.fn(() => currentServiceWc),
138
+ getServiceWcForBridge: () => null,
139
+ getActiveRenderWc: () => null,
140
+ getActiveBridgeId: () => null,
141
+ onRenderEvent: (listener) => {
142
+ renderEventListeners.add(listener);
143
+ return () => renderEventListeners.delete(listener);
144
+ },
145
+ onServiceHostReady: (listener) => {
146
+ serviceHostReadyListeners.add(listener);
147
+ return () => serviceHostReadyListeners.delete(listener);
148
+ },
149
+ getDevice: () => null,
150
+ setDevice: () => { },
151
+ disposeSessionsForSimulator: () => Promise.resolve(),
152
+ };
153
+ function setActiveServiceWc(wc) {
154
+ currentServiceWc = wc;
155
+ }
156
+ function emitRenderEvent(event) {
157
+ for (const l of [...renderEventListeners])
158
+ l(event);
159
+ }
160
+ function emitServiceHostReady(event) {
161
+ for (const l of [...serviceHostReadyListeners])
162
+ l(event);
163
+ }
164
+ return {
165
+ addChildView,
166
+ removeChildView,
167
+ setActiveServiceWc,
168
+ emitRenderEvent,
169
+ emitServiceHostReady,
170
+ ctx: {
171
+ windows: {
172
+ mainWindow: mainWindow,
173
+ },
174
+ rendererDir: '/stub/renderer',
175
+ notify: notify,
176
+ connections: createConnectionRegistry(),
177
+ preloadPath: '/stub/sim-preload.js',
178
+ bridge: bridge,
179
+ },
180
+ };
181
+ }
182
+ //# sourceMappingURL=view-manager-devtools-host-test-fixtures.js.map
@@ -4,6 +4,7 @@ import type { BridgeRouterHandle } from '../ipc/bridge-router.js';
4
4
  import type { ConsoleForwarder } from './console-forward/index.js';
5
5
  import type { DiagnosticsBus } from './diagnostics/index.js';
6
6
  import type { NetworkForwarder } from './network-forward/index.js';
7
+ import type { InternalDevtoolsWindow } from '../windows/internal-devtools-window/index.js';
7
8
  import { type CdpSessionBroker } from './cdp-session/index.js';
8
9
  import type { AppDataTap } from './simulator-appdata/index.js';
9
10
  import type { StorageApi } from './simulator-storage/index.js';
@@ -197,6 +198,15 @@ export interface WorkbenchContext {
197
198
  * DevTools host exist. Undefined until bootstrap wires it.
198
199
  */
199
200
  networkForward?: NetworkForwarder;
201
+ /**
202
+ * Controller for the standalone internal (app-wide) DevTools debug window
203
+ * — the independent floating CDP panel that debugs the whole Electron app
204
+ * (as opposed to the right-panel CDP, which inspects only the user's
205
+ * mini-program). Assembled in app bootstrap via
206
+ * `createInternalDevtoolsWindow(mainWindow)`; undefined only in contexts
207
+ * that skip that wiring (e.g. narrow test doubles).
208
+ */
209
+ internalDevtoolsWindow?: InternalDevtoolsWindow;
200
210
  }
201
211
  /**
202
212
  * Inputs for `createWorkbenchContext`. The scalar config fields it shares with
@@ -35,12 +35,16 @@ export declare function simDeskBg(): string;
35
35
  * The defect is platform-agnostic, so the fix is too: one `nativeTheme`
36
36
  * `updated` listener re-syncs every current and future BrowserWindow.
37
37
  *
38
- * Known limitation: on Linux the `updated` event does not fire for OS-level
39
- * system theme changes (electron/electron#25925) only for explicit
40
- * `nativeTheme.themeSource` assignments. In-app theme switches go through
41
- * `applyTheme()` and are unaffected on every platform; the gap is limited to
42
- * Linux + `theme: 'system'` + an OS theme change, an upstream Electron
43
- * limitation that also leaves the renderer's `prefers-color-scheme` stale.
38
+ * Known limitation: on some Linux desktops (KDE Plasma + Wayland and other
39
+ * wlroots compositors) an OS-level system theme change flips `updated` with
40
+ * a stale/inverted `shouldUseDarkColors` instead of the correct value — a
41
+ * Chromium ≥142 regression (electron/electron#48736, crbug 462191707, open
42
+ * as of 2026-06) distinct from the older #25925 (fixed since Electron 13,
43
+ * 2021). GNOME/GTK-portal desktops are unaffected. In-app theme switches go
44
+ * through `applyTheme()` and are unaffected on every platform; the gap is
45
+ * limited to the affected Linux desktops + `theme: 'system'` + an OS theme
46
+ * change, an upstream Electron limitation that also leaves the renderer's
47
+ * `prefers-color-scheme` stale.
44
48
  *
45
49
  * Returns a Disposable that detaches the listener. Install it once during
46
50
  * app setup and hand the Disposable to the workbench registry.
@@ -41,12 +41,16 @@ export function simDeskBg() {
41
41
  * The defect is platform-agnostic, so the fix is too: one `nativeTheme`
42
42
  * `updated` listener re-syncs every current and future BrowserWindow.
43
43
  *
44
- * Known limitation: on Linux the `updated` event does not fire for OS-level
45
- * system theme changes (electron/electron#25925) only for explicit
46
- * `nativeTheme.themeSource` assignments. In-app theme switches go through
47
- * `applyTheme()` and are unaffected on every platform; the gap is limited to
48
- * Linux + `theme: 'system'` + an OS theme change, an upstream Electron
49
- * limitation that also leaves the renderer's `prefers-color-scheme` stale.
44
+ * Known limitation: on some Linux desktops (KDE Plasma + Wayland and other
45
+ * wlroots compositors) an OS-level system theme change flips `updated` with
46
+ * a stale/inverted `shouldUseDarkColors` instead of the correct value — a
47
+ * Chromium ≥142 regression (electron/electron#48736, crbug 462191707, open
48
+ * as of 2026-06) distinct from the older #25925 (fixed since Electron 13,
49
+ * 2021). GNOME/GTK-portal desktops are unaffected. In-app theme switches go
50
+ * through `applyTheme()` and are unaffected on every platform; the gap is
51
+ * limited to the affected Linux desktops + `theme: 'system'` + an OS theme
52
+ * change, an upstream Electron limitation that also leaves the renderer's
53
+ * `prefers-color-scheme` stale.
50
54
  *
51
55
  * Returns a Disposable that detaches the listener. Install it once during
52
56
  * app setup and hand the Disposable to the workbench registry.
@@ -0,0 +1,66 @@
1
+ import type { WebContents } from 'electron';
2
+ import { BrowserWindow } from 'electron';
3
+ /**
4
+ * Standalone (non-dock) DevTools window for the whole Electron app — the
5
+ * independent floating CDP debug panel's host window. Unlike the right-panel
6
+ * CDP (`native-simulator-devtools-host.ts`, which re-points at a swappable
7
+ * service-host wc via a reconciler-driven overlay), `target` here is the main
8
+ * window's own webContents and never swaps, so this controller needs none of
9
+ * that machinery: just a plain window with a WebContentsView filling it as
10
+ * the DevTools front-end host.
11
+ *
12
+ * The window + its `setDevToolsWebContents` attachment are built ONCE (on
13
+ * the first `open()`) and never rebuilt afterward — the close button hides
14
+ * rather than destroys. This is deliberate, not an oversight: real-repro
15
+ * diagnostic instrumentation + a source-level Electron/Chromium
16
+ * investigation this session established that destroying and rebuilding
17
+ * this attachment on every close/reopen cycle cannot be made reliable.
18
+ * `closeDevTools()` is effectively a no-op on the `setDevToolsWebContents`
19
+ * external-front-end path (Electron's `InspectableWebContents::CloseDevTools`
20
+ * only does real cleanup when `managed_devtools_web_contents_` is set, which
21
+ * external attachments never populate) — the ONLY authoritative "detached"
22
+ * signal, `'devtools-closed'`, is tied to the OLD front-end host's underlying
23
+ * `content::WebContents` C++ object being destroyed, which was observed
24
+ * (real timestamped log, this session) to take 20+ seconds — the same
25
+ * order of magnitude as this app's own independently-discovered
26
+ * `'close'`-to-`'closed'` BrowserWindow teardown lag. This is a long-standing,
27
+ * unresolved upstream behavior (electron/electron#14095, #17168, #27110,
28
+ * #37356), not something fixable from this module. Electron's own
29
+ * `setDevToolsWebContents` documentation example builds its host window
30
+ * exactly once for the app's lifetime and never rebuilds it — reusing the
31
+ * same host sidesteps the entire bug class instead of chasing a fast
32
+ * "really detached now" signal that does not exist.
33
+ */
34
+ export interface InternalDevtoolsWindowOptions {
35
+ /** Authority for "the app is quitting right now" (see app/lifecycle.ts).
36
+ * During a real quit every window is closing for real — the controller's
37
+ * habitual close-interception (preventDefault + hide) would CANCEL the
38
+ * quit and leave the process alive with a hidden window nothing will ever
39
+ * show again, so a truthy answer here lets the native close proceed. */
40
+ isAppQuitting?: () => boolean;
41
+ }
42
+ export interface InternalDevtoolsWindow {
43
+ /** Create (on the very first call) and show/focus the window, attaching
44
+ * its DevTools front-end host to `target`'s DevTools exactly once. Every
45
+ * later call just re-shows/focuses the SAME window and host — the
46
+ * underlying DevTools attachment is never rebuilt (see module doc). */
47
+ open(): void;
48
+ /** Actually destroy the window and release the DevTools attachment.
49
+ * Distinct from the user closing the window (which only hides it) — this
50
+ * is for real app-level teardown. Safe to call even if `open()` was never
51
+ * called. */
52
+ dispose(): void;
53
+ /** Subscribe to the front-end host's visibility: fires with the host wc
54
+ * when the window is built (first `open()`) or shown again, and with null
55
+ * when the window is hidden (user close) or destroyed (`dispose()`).
56
+ * Global CDP consumers (network-forward, service-console) gate their
57
+ * dispatch target here — hidden means "stop spending work mirroring into
58
+ * a window nobody can see," not "the attachment is gone." A subscriber
59
+ * registering while the window is ALREADY visible gets the current host
60
+ * replayed one microtask later (same catch-up contract as bridge-router's
61
+ * `onServiceHostReady`) — consumers wired up after `open()` must not stay
62
+ * stuck believing there is no host. Returns an unsubscribe. */
63
+ onHostChanged(handler: (hostWc: WebContents | null) => void): () => void;
64
+ }
65
+ export declare function createInternalDevtoolsWindow(target: BrowserWindow, opts?: InternalDevtoolsWindowOptions): InternalDevtoolsWindow;
66
+ //# sourceMappingURL=index.d.ts.map