@dimina-kit/devtools 0.4.0-dev.20260824071518 → 0.4.0-dev.20260828092243

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 (153) hide show
  1. package/dist/main/api.d.ts +1 -1
  2. package/dist/main/app/app.js +55 -2
  3. package/dist/main/index.bundle.js +887 -234
  4. package/dist/main/ipc/index.d.ts +2 -0
  5. package/dist/main/ipc/index.js +2 -0
  6. package/dist/main/ipc/popover.js +1 -1
  7. package/dist/main/ipc/project-create.d.ts +15 -0
  8. package/dist/main/ipc/project-create.js +26 -0
  9. package/dist/main/ipc/projects.d.ts +1 -1
  10. package/dist/main/ipc/projects.js +11 -0
  11. package/dist/main/ipc/settings.js +2 -1
  12. package/dist/main/ipc/simulator-module.d.ts +4 -3
  13. package/dist/main/ipc/simulator-module.js +4 -5
  14. package/dist/main/ipc/tooltip.js +1 -1
  15. package/dist/main/ipc/views.d.ts +15 -1
  16. package/dist/main/ipc/views.js +61 -4
  17. package/dist/main/runtime/miniapp-runtime.d.ts +87 -19
  18. package/dist/main/services/notifications/renderer-notifier.d.ts +30 -1
  19. package/dist/main/services/notifications/renderer-notifier.js +23 -1
  20. package/dist/main/services/projects/project-repository.d.ts +4 -1
  21. package/dist/main/services/projects/project-repository.js +63 -5
  22. package/dist/main/services/update/update-manager.d.ts +17 -3
  23. package/dist/main/services/update/update-manager.js +25 -6
  24. package/dist/main/services/views/host-dialog-port-channel.d.ts +18 -0
  25. package/dist/main/services/views/host-dialog-port-channel.js +15 -0
  26. package/dist/main/services/views/host-dialog-session-runtime.d.ts +10 -0
  27. package/dist/main/services/views/host-dialog-session-runtime.js +17 -0
  28. package/dist/main/services/views/host-dialog-view.d.ts +95 -0
  29. package/dist/main/services/views/host-dialog-view.js +147 -0
  30. package/dist/main/services/views/host-sidebar-port-channel.d.ts +18 -0
  31. package/dist/main/services/views/host-sidebar-port-channel.js +15 -0
  32. package/dist/main/services/views/host-sidebar-session-runtime.d.ts +10 -0
  33. package/dist/main/services/views/host-sidebar-session-runtime.js +17 -0
  34. package/dist/main/services/views/host-sidebar-view.d.ts +59 -0
  35. package/dist/main/services/views/host-sidebar-view.js +44 -0
  36. package/dist/main/services/views/host-slot-port-channel.d.ts +107 -0
  37. package/dist/main/services/views/host-slot-port-channel.js +261 -0
  38. package/dist/main/services/views/host-slot-session-runtime.d.ts +40 -0
  39. package/dist/main/services/views/host-slot-session-runtime.js +50 -0
  40. package/dist/main/services/views/host-slot-view.d.ts +109 -0
  41. package/dist/main/services/views/host-slot-view.js +147 -0
  42. package/dist/main/services/views/host-toolbar-port-channel.d.ts +6 -91
  43. package/dist/main/services/views/host-toolbar-port-channel.js +10 -243
  44. package/dist/main/services/views/host-toolbar-session-runtime.d.ts +5 -30
  45. package/dist/main/services/views/host-toolbar-session-runtime.js +9 -47
  46. package/dist/main/services/views/host-toolbar-view.d.ts +34 -7
  47. package/dist/main/services/views/host-toolbar-view.js +30 -224
  48. package/dist/main/services/views/managed-web-contents-view.d.ts +48 -0
  49. package/dist/main/services/views/managed-web-contents-view.js +123 -0
  50. package/dist/main/services/views/overlay-panels-view.d.ts +23 -0
  51. package/dist/main/services/views/overlay-panels-view.js +93 -4
  52. package/dist/main/services/views/placement-reconciler.d.ts +14 -2
  53. package/dist/main/services/views/placement-reconciler.js +27 -0
  54. package/dist/main/services/views/placement-test-driver.d.ts +1 -0
  55. package/dist/main/services/views/placement-test-driver.js +3 -0
  56. package/dist/main/services/views/view-manager-devtools-host-test-fixtures.js +3 -1
  57. package/dist/main/services/views/view-manager.d.ts +10 -78
  58. package/dist/main/services/views/view-manager.js +34 -3
  59. package/dist/main/utils/paths.d.ts +4 -0
  60. package/dist/main/utils/paths.js +4 -0
  61. package/dist/main/utils/sender-policy.js +10 -0
  62. package/dist/native-host/common/common.js +65 -62
  63. package/dist/native-host/render/render.js +2230 -2052
  64. package/dist/native-host/service/service.js +2 -2
  65. package/dist/preload/runtime/host-dialog-advertiser.d.ts +22 -0
  66. package/dist/preload/runtime/host-dialog-advertiser.js +37 -0
  67. package/dist/preload/runtime/host-dialog-port.d.ts +44 -0
  68. package/dist/preload/runtime/host-dialog-port.js +53 -0
  69. package/dist/preload/runtime/host-dialog-runtime.d.ts +28 -0
  70. package/dist/preload/runtime/host-dialog-runtime.js +36 -0
  71. package/dist/preload/runtime/host-sidebar-advertiser.d.ts +19 -0
  72. package/dist/preload/runtime/host-sidebar-advertiser.js +35 -0
  73. package/dist/preload/runtime/host-sidebar-port.d.ts +44 -0
  74. package/dist/preload/runtime/host-sidebar-port.js +53 -0
  75. package/dist/preload/runtime/host-sidebar-runtime.d.ts +28 -0
  76. package/dist/preload/runtime/host-sidebar-runtime.js +36 -0
  77. package/dist/preload/runtime/host-slot-port-bridge.d.ts +18 -0
  78. package/dist/preload/runtime/host-slot-port-bridge.js +112 -0
  79. package/dist/preload/runtime/host-toolbar-advertiser.d.ts +5 -13
  80. package/dist/preload/runtime/host-toolbar-advertiser.js +18 -38
  81. package/dist/preload/runtime/host-toolbar-port.d.ts +9 -0
  82. package/dist/preload/runtime/host-toolbar-port.js +11 -95
  83. package/dist/preload/runtime/wait-for-slot-root.d.ts +20 -0
  84. package/dist/preload/runtime/wait-for-slot-root.js +52 -0
  85. package/dist/preload/windows/host-dialog-runtime.cjs +353 -0
  86. package/dist/preload/windows/host-dialog-runtime.cjs.map +7 -0
  87. package/dist/preload/windows/host-dialog-runtime.d.ts +2 -0
  88. package/dist/preload/windows/host-dialog-runtime.js +13 -0
  89. package/dist/preload/windows/host-sidebar-runtime.cjs +354 -0
  90. package/dist/preload/windows/host-sidebar-runtime.cjs.map +7 -0
  91. package/dist/preload/windows/host-sidebar-runtime.d.ts +2 -0
  92. package/dist/preload/windows/host-sidebar-runtime.js +13 -0
  93. package/dist/preload/windows/host-toolbar-runtime.cjs +134 -51
  94. package/dist/preload/windows/host-toolbar-runtime.cjs.map +3 -3
  95. package/dist/preload/windows/simulator.cjs.map +1 -1
  96. package/dist/renderer/assets/button-DsNqZm7o.js +2 -0
  97. package/dist/renderer/assets/constants-D7DfVqMp.js +2 -0
  98. package/dist/renderer/assets/constants-D8zsejFq.js +2 -0
  99. package/dist/renderer/assets/createLucideIcon-CYJeT1ji.js +2 -0
  100. package/dist/renderer/assets/dialog-pT4qWmka.js +46 -0
  101. package/dist/renderer/assets/dist-o70xmpc1.js +2 -0
  102. package/dist/renderer/assets/hostSidebarDefault-NvGZlE9O.js +2 -0
  103. package/dist/renderer/assets/index-zErhG3ta.js +5 -0
  104. package/dist/renderer/assets/input-4QxTXMZN.js +2 -0
  105. package/dist/renderer/assets/ipc-channels-overlays-Blr5TNoB.js +2 -0
  106. package/dist/renderer/assets/jsx-runtime-B-3rq1y5.css +1 -0
  107. package/dist/renderer/assets/jsx-runtime-CqVdW3eg.js +9 -0
  108. package/dist/renderer/assets/popover-CJiSWwuE.js +2 -0
  109. package/dist/renderer/assets/project-api-DZ1Zi6EC.js +2 -0
  110. package/dist/renderer/assets/projectCreateDialog-DMWNhw_1.js +2 -0
  111. package/dist/renderer/assets/select-C86pNcsu.js +2 -0
  112. package/dist/renderer/assets/settings-BoI8mLT1.js +2 -0
  113. package/dist/renderer/assets/settings-api-Bs_g4NHs.js +2 -0
  114. package/dist/renderer/assets/settings-tab-bar-DHLLygqv.js +2 -0
  115. package/dist/renderer/assets/tooltip-BhAyY0T4.js +2 -0
  116. package/dist/renderer/assets/types-YYjkvane.js +2 -0
  117. package/dist/renderer/assets/updateDialog-DPum3wx1.js +2 -0
  118. package/dist/renderer/assets/utils-kfQov6Pa.js +2 -0
  119. package/dist/renderer/assets/view-api-DFhb2R8-.js +2 -0
  120. package/dist/renderer/assets/{workbenchSettings-oq_EyfKf.js → workbenchSettings-DrajRQUk.js} +3 -3
  121. package/dist/renderer/entries/host-sidebar-default/index.html +21 -0
  122. package/dist/renderer/entries/main/index.html +15 -8
  123. package/dist/renderer/entries/popover/index.html +10 -7
  124. package/dist/renderer/entries/project-create-dialog/index.html +27 -0
  125. package/dist/renderer/entries/settings/index.html +10 -7
  126. package/dist/renderer/entries/tooltip/index.html +5 -4
  127. package/dist/renderer/entries/update-dialog/index.html +25 -0
  128. package/dist/renderer/entries/workbench-settings/index.html +11 -7
  129. package/dist/service-host/preload.cjs +10 -0
  130. package/dist/shared/constants.d.ts +22 -0
  131. package/dist/shared/constants.js +22 -0
  132. package/dist/shared/ipc-channels-overlays.d.ts +156 -0
  133. package/dist/shared/ipc-channels-overlays.js +186 -0
  134. package/dist/shared/ipc-channels.d.ts +0 -77
  135. package/dist/shared/ipc-channels.js +0 -98
  136. package/dist/shared/ipc-schemas.d.ts +33 -0
  137. package/dist/shared/ipc-schemas.js +41 -0
  138. package/dist/shared/types.d.ts +10 -0
  139. package/dist/shared/view-ids.d.ts +7 -0
  140. package/dist/shared/view-ids.js +10 -0
  141. package/package.json +8 -8
  142. package/dist/renderer/assets/constants-Bdz0LHdy.js +0 -2
  143. package/dist/renderer/assets/index-CtznrmHR.js +0 -49
  144. package/dist/renderer/assets/input-Dmm6nob5.js +0 -2
  145. package/dist/renderer/assets/ipc-transport-BOg0gR9W.js +0 -9
  146. package/dist/renderer/assets/ipc-transport-DgQhZaE9.css +0 -1
  147. package/dist/renderer/assets/popover-Cd1FEGOX.js +0 -2
  148. package/dist/renderer/assets/select-BaYox7If.js +0 -2
  149. package/dist/renderer/assets/settings-BXvwuFaz.js +0 -2
  150. package/dist/renderer/assets/settings-api-Bo9GbYqs.js +0 -2
  151. package/dist/renderer/assets/settings-tab-bar-Beia6lMr.js +0 -2
  152. package/dist/renderer/assets/tooltip-DfQqK3Sk.js +0 -2
  153. package/dist/renderer/assets/view-api-nn69je7A.js +0 -2
@@ -0,0 +1,44 @@
1
+ import { HOST_SIDEBAR_RUNTIME_MARKER } from '../../../shared/constants.js';
2
+ import { VIEW_ID, VIEW_LAYER } from '../../../shared/view-ids.js';
3
+ import { acquireHostSidebarSessionRuntime, releaseHostSidebarSessionRuntime, } from './host-sidebar-session-runtime.js';
4
+ import { createHostSidebarPortChannel } from './host-sidebar-port-channel.js';
5
+ import { createHostSlotView, deriveSlotControl } from './host-slot-view.js';
6
+ /**
7
+ * Sidebar-specific instantiation of `createHostSlotView` (see that module for
8
+ * the full lazy-create / port-channel / extent-mode contract). Symmetric to
9
+ * `createHostToolbarView` on the inline (width) axis instead of block
10
+ * (height) — the slot itself is axis-agnostic (a single advertised extent
11
+ * number); which CSS dimension it resizes is decided entirely by the host's
12
+ * renderer placeholder layout, not here. The sidebar never coexists with the
13
+ * settings/popover/tooltip overlays, so unlike the toolbar there is no
14
+ * `reapplyPresentOverlays` dependency to thread through.
15
+ */
16
+ export function createHostSidebarView(ctx, reconciler) {
17
+ const port = createHostSidebarPortChannel({
18
+ isCurrent: (wc) => slot.getWebContentsId() === wc.id,
19
+ });
20
+ const slot = createHostSlotView(reconciler, {
21
+ viewId: VIEW_ID.hostSidebar,
22
+ layer: VIEW_LAYER.hostSidebar,
23
+ marker: HOST_SIDEBAR_RUNTIME_MARKER,
24
+ sessionRuntime: {
25
+ acquire: acquireHostSidebarSessionRuntime,
26
+ release: releaseHostSidebarSessionRuntime,
27
+ },
28
+ portChannel: port,
29
+ setExtentModeErrorLabel: 'hostSidebar.setWidthMode',
30
+ }, {
31
+ onExtentChanged: (width) => ctx.notify.hostSidebarWidthChanged(width),
32
+ });
33
+ const control = deriveSlotControl(slot.control, {
34
+ setWidthMode: (mode) => slot.control.setExtentMode(mode),
35
+ });
36
+ return {
37
+ control,
38
+ setHostSidebarWidth: (extent) => slot.setExtent(extent),
39
+ getHostSidebarWidth: () => slot.getExtent(),
40
+ getHostSidebarWebContentsId: () => slot.getWebContentsId(),
41
+ dispose: () => slot.dispose(),
42
+ };
43
+ }
44
+ //# sourceMappingURL=host-sidebar-view.js.map
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Gated narrow channel between the main process and a downstream host-slot
3
+ * WebContentsView's page (host-toolbar / host-sidebar / host-dialog), over a
4
+ * per-load transferred MessagePort.
5
+ *
6
+ * Why a MessagePort and not plain IPC: a slot's page is HOST-ARBITRARY
7
+ * content. A dedicated port pair gives it exactly one pipe whose main-side end
8
+ * lives in this module — the page never gains a generic `ipcRenderer` reach
9
+ * into the internal devtools channels, and main-side inbound traffic is
10
+ * validated at this single waist.
11
+ *
12
+ * Lifecycle (per-load handshake):
13
+ * - On every slot-wc `did-finish-load` main builds a fresh
14
+ * `MessageChannelMain`, transfers port2 to the new document via
15
+ * `wc.postMessage(opts.channel, null, [port2])`, keeps port1 and
16
+ * `start()`s it. The previous port1 (if any) is closed first — its
17
+ * document is gone.
18
+ * - `onMessage` registrations are CONTROL-level (this module's registry), not
19
+ * per-port: they may be made before any view exists and survive page
20
+ * reloads / wc rebuilds, because each new handshake dispatches into the
21
+ * same registry.
22
+ * - `send` is gated and non-queueing: `false` means "not delivered, ever" —
23
+ * no view is conjured, nothing is buffered for a later handshake.
24
+ * - A port1 `'close'` event (the renderer end died without a re-handshake
25
+ * yet) drops the live-port reference so `send` reports `false` instead of
26
+ * posting into a dead pipe.
27
+ * - NAVIGATION INVALIDATES THE PORT. The port pair belongs to one DOCUMENT,
28
+ * so starting a navigation that replaces that document closes port1 and
29
+ * `send` returns `false` until the new document's `did-finish-load`
30
+ * handshake. Two paths:
31
+ * (a) host-initiated `loadURL/loadFile` calls `invalidate()`
32
+ * synchronously AT INITIATION (the slot view does this before the
33
+ * `webContents.loadURL/loadFile` await), so a same-tick `send` can
34
+ * never report `true` for an envelope posted into the document the
35
+ * load is about to replace;
36
+ * (b) page-initiated navigation (location.href / reload) is caught by a
37
+ * `did-start-navigation` listener — guarded to the ACTIVE wc,
38
+ * main-frame, cross-document only. Same-document navigations
39
+ * (anchors, pushState, history) and subframe navigations do NOT
40
+ * invalidate: the document the port lives in survives those, and no
41
+ * main-frame `did-finish-load` would follow to ever restore the
42
+ * channel.
43
+ *
44
+ * Envelope both directions: `{ channel: string, payload: unknown }`. Inbound
45
+ * data that is not an object with a string `channel` is DROPPED without
46
+ * throwing (the counterpart is arbitrary host content).
47
+ */
48
+ import type { WebContents } from 'electron';
49
+ /** Handle returned by `onMessage`; `dispose()` detaches (idempotent). */
50
+ export interface HostSlotMessageSubscription {
51
+ dispose(): void;
52
+ }
53
+ export interface HostSlotPortChannel {
54
+ /**
55
+ * Hook a freshly created slot webContents: registers the `did-finish-load`
56
+ * (per-load handshake) and `destroyed` (drop the live port) listeners via
57
+ * `wc.on(...)`. Call exactly once per wc, right after the view is created.
58
+ */
59
+ attach(wc: WebContents): void;
60
+ /**
61
+ * Drop the live port NOW (close + clear): the document it belongs to is
62
+ * being replaced. Called synchronously when the HOST initiates a
63
+ * `loadURL`/`loadFile` — the host-initiated half of the
64
+ * navigation-invalidates contract (the page-initiated half is the
65
+ * `did-start-navigation` listener `attach` installs). `send` returns false
66
+ * from this call until the next `did-finish-load` handshake. Idempotent.
67
+ */
68
+ invalidate(): void;
69
+ /** Register a control-level inbound handler for `channel`. */
70
+ onMessage(channel: string, handler: (payload: unknown) => void): HostSlotMessageSubscription;
71
+ /**
72
+ * Observe handshake readiness. Fires `handler` once per load GENERATION,
73
+ * at the moment that generation's handshake completes (`send` flips true).
74
+ * Registering while already `ready` schedules a one-shot catch-up fire on a
75
+ * microtask — never synchronously — and the catch-up RE-CHECKS at fire time
76
+ * that (a) the subscription is still registered and (b) the generation is
77
+ * unchanged / the port is still live, so a same-frame `dispose()` or
78
+ * host-initiated load suppresses it. Inert (never fires) after `dispose()`.
79
+ */
80
+ onReady(handler: () => void): HostSlotMessageSubscription;
81
+ /**
82
+ * Post `{ channel, payload }` to the slot page over the live port. Returns
83
+ * false (delivering NOTHING, creating NOTHING) when there is no live slot
84
+ * wc, the current load's handshake hasn't completed, or a
85
+ * document-replacing navigation is in flight (see `invalidate`).
86
+ */
87
+ send(channel: string, payload: unknown): boolean;
88
+ /**
89
+ * Teardown (manager `disposeAll`): close the live port, sweep the handler
90
+ * registry, refuse further handshakes. Subsequent `send` returns false;
91
+ * late `dispose()` of an old subscription stays a no-op.
92
+ */
93
+ dispose(): void;
94
+ }
95
+ export declare function createHostSlotPortChannel(opts: {
96
+ /**
97
+ * Is `wc` still the manager's CURRENT live slot webContents? Guards a
98
+ * stale wc's late `did-finish-load` from hijacking the channel after a
99
+ * rebuild swapped the view out underneath it.
100
+ */
101
+ isCurrent: (wc: WebContents) => boolean;
102
+ /** The IPC channel name used for the per-load port-transfer postMessage. */
103
+ channel: string;
104
+ /** Log-line prefix identifying which slot this instance belongs to. */
105
+ logPrefix: string;
106
+ }): HostSlotPortChannel;
107
+ //# sourceMappingURL=host-slot-port-channel.d.ts.map
@@ -0,0 +1,261 @@
1
+ /**
2
+ * Gated narrow channel between the main process and a downstream host-slot
3
+ * WebContentsView's page (host-toolbar / host-sidebar / host-dialog), over a
4
+ * per-load transferred MessagePort.
5
+ *
6
+ * Why a MessagePort and not plain IPC: a slot's page is HOST-ARBITRARY
7
+ * content. A dedicated port pair gives it exactly one pipe whose main-side end
8
+ * lives in this module — the page never gains a generic `ipcRenderer` reach
9
+ * into the internal devtools channels, and main-side inbound traffic is
10
+ * validated at this single waist.
11
+ *
12
+ * Lifecycle (per-load handshake):
13
+ * - On every slot-wc `did-finish-load` main builds a fresh
14
+ * `MessageChannelMain`, transfers port2 to the new document via
15
+ * `wc.postMessage(opts.channel, null, [port2])`, keeps port1 and
16
+ * `start()`s it. The previous port1 (if any) is closed first — its
17
+ * document is gone.
18
+ * - `onMessage` registrations are CONTROL-level (this module's registry), not
19
+ * per-port: they may be made before any view exists and survive page
20
+ * reloads / wc rebuilds, because each new handshake dispatches into the
21
+ * same registry.
22
+ * - `send` is gated and non-queueing: `false` means "not delivered, ever" —
23
+ * no view is conjured, nothing is buffered for a later handshake.
24
+ * - A port1 `'close'` event (the renderer end died without a re-handshake
25
+ * yet) drops the live-port reference so `send` reports `false` instead of
26
+ * posting into a dead pipe.
27
+ * - NAVIGATION INVALIDATES THE PORT. The port pair belongs to one DOCUMENT,
28
+ * so starting a navigation that replaces that document closes port1 and
29
+ * `send` returns `false` until the new document's `did-finish-load`
30
+ * handshake. Two paths:
31
+ * (a) host-initiated `loadURL/loadFile` calls `invalidate()`
32
+ * synchronously AT INITIATION (the slot view does this before the
33
+ * `webContents.loadURL/loadFile` await), so a same-tick `send` can
34
+ * never report `true` for an envelope posted into the document the
35
+ * load is about to replace;
36
+ * (b) page-initiated navigation (location.href / reload) is caught by a
37
+ * `did-start-navigation` listener — guarded to the ACTIVE wc,
38
+ * main-frame, cross-document only. Same-document navigations
39
+ * (anchors, pushState, history) and subframe navigations do NOT
40
+ * invalidate: the document the port lives in survives those, and no
41
+ * main-frame `did-finish-load` would follow to ever restore the
42
+ * channel.
43
+ *
44
+ * Envelope both directions: `{ channel: string, payload: unknown }`. Inbound
45
+ * data that is not an object with a string `channel` is DROPPED without
46
+ * throwing (the counterpart is arbitrary host content).
47
+ */
48
+ import { MessageChannelMain } from 'electron';
49
+ export function createHostSlotPortChannel(opts) {
50
+ let activePort = null;
51
+ /** The wc that owns `activePort` (so a stale wc's `destroyed` can't drop a successor's port). */
52
+ let activeWc = null;
53
+ /** Named lifecycle state. INVARIANT: `state === 'ready'` ⟺ `activePort !== null`. */
54
+ let state = 'absent';
55
+ /**
56
+ * Monotonic per-handshake counter. Each completed handshake is one load
57
+ * GENERATION; `onReady` catch-up fires capture it at registration and
58
+ * re-check it at fire time so a fire scheduled for generation N can never
59
+ * deliver after a navigation/handshake moved the channel past N.
60
+ */
61
+ let generation = 0;
62
+ // Array (not Map<channel, Set>) so the same handler function may be
63
+ // registered twice and each registration disposes independently.
64
+ const handlers = [];
65
+ // onReady registrations. Same array-of-entries discipline as `handlers`.
66
+ const readyHandlers = [];
67
+ function dispatch(data) {
68
+ // Inbound waist: the slot page is host-arbitrary content — anything that
69
+ // is not an object envelope with a string channel is dropped.
70
+ if (typeof data !== 'object' || data === null)
71
+ return;
72
+ const { channel, payload } = data;
73
+ if (typeof channel !== 'string')
74
+ return;
75
+ // Snapshot so a handler that (un)subscribes mid-dispatch can't skew iteration.
76
+ for (const entry of [...handlers]) {
77
+ // Exception isolation, same posture as `invokeReadyHandler`: these are
78
+ // arbitrary host-registered handlers running inside a `MessagePortMain`
79
+ // 'message' listener — an uncaught throw here escapes straight to
80
+ // process-level `uncaughtException`, and would also starve every
81
+ // sibling handler still due to run for this message. Report-and-continue.
82
+ if (entry.channel === channel)
83
+ invokeMessageHandler(entry.handler, payload);
84
+ }
85
+ }
86
+ function invokeMessageHandler(handler, payload) {
87
+ try {
88
+ handler(payload);
89
+ }
90
+ catch (err) {
91
+ console.error(`${opts.logPrefix} onMessage handler threw:`, err);
92
+ }
93
+ }
94
+ /**
95
+ * Drop the live port and transition to `next` (`'absent'` for death paths,
96
+ * `'awaitingHandshake'` for navigation paths). Never demotes `disposed`.
97
+ */
98
+ function dropActivePort(close, next) {
99
+ const port = activePort;
100
+ activePort = null;
101
+ activeWc = null;
102
+ if (state !== 'disposed')
103
+ state = next;
104
+ if (close && port) {
105
+ try {
106
+ port.close();
107
+ }
108
+ catch {
109
+ /* already closed */
110
+ }
111
+ }
112
+ }
113
+ /**
114
+ * Invoke one onReady handler with exception ISOLATION: subscribers are
115
+ * arbitrary control-level code — one throwing must neither starve sibling
116
+ * registrations nor escape the surrounding event/microtask callback (a
117
+ * throw out of a `did-finish-load` listener or a `queueMicrotask` body is
118
+ * process-level `uncaughtException` territory). Report-and-continue.
119
+ */
120
+ function invokeReadyHandler(handler) {
121
+ try {
122
+ handler();
123
+ }
124
+ catch (err) {
125
+ console.error(`${opts.logPrefix} onReady handler threw:`, err);
126
+ }
127
+ }
128
+ /** Fire every still-registered onReady handler exactly once (snapshot iteration). */
129
+ function fireReadyHandlers() {
130
+ for (const entry of [...readyHandlers]) {
131
+ // A handler may dispose a sibling registration mid-fire: re-check
132
+ // membership so a disposed entry never fires. Per-handler isolation
133
+ // (NOT around the loop): a throwing handler must not abort the fire
134
+ // for later-registered siblings.
135
+ if (readyHandlers.includes(entry))
136
+ invokeReadyHandler(entry.handler);
137
+ }
138
+ }
139
+ function handshake(wc) {
140
+ if (state === 'disposed')
141
+ return;
142
+ if (!opts.isCurrent(wc))
143
+ return;
144
+ // The previous load's document is gone; its port goes with it.
145
+ dropActivePort(true, 'awaitingHandshake');
146
+ const { port1, port2 } = new MessageChannelMain();
147
+ port1.on('message', (event) => {
148
+ dispatch(event.data);
149
+ });
150
+ // Renderer end died without a re-handshake (page crash / wc close): drop
151
+ // the reference so send() reports false instead of posting into the void.
152
+ port1.on('close', () => {
153
+ if (activePort === port1)
154
+ dropActivePort(false, 'absent');
155
+ });
156
+ wc.postMessage(opts.channel, null, [port2]);
157
+ port1.start();
158
+ activePort = port1;
159
+ activeWc = wc;
160
+ state = 'ready';
161
+ generation++;
162
+ // Readiness signal AFTER the port is live: a handler calling send() from
163
+ // inside its onReady fire must observe `true`.
164
+ fireReadyHandlers();
165
+ }
166
+ return {
167
+ attach(wc) {
168
+ wc.on('did-finish-load', () => handshake(wc));
169
+ // Page-initiated navigation (location.href / reload): the document the
170
+ // port lives in is being replaced — invalidate so send() reports false
171
+ // through the navigation window instead of confirming delivery into a
172
+ // document being torn down. TRIPLE guard, because over-invalidating
173
+ // mutes the channel FOREVER (no main-frame did-finish-load follows to
174
+ // re-handshake):
175
+ // - activeWc: a stale wc's late event must not drop a successor's port
176
+ // (same discipline as the 'destroyed' handler below);
177
+ // - cross-document only: anchors/pushState/history keep the document;
178
+ // - main frame only: an <iframe> navigating keeps the main document.
179
+ // Reads the details object (electron >= 12 shape); falls back to the
180
+ // deprecated positional args (`isInPlace` === details.isSameDocument).
181
+ wc.on('did-start-navigation', (details, _url, isInPlace, isMainFramePositional) => {
182
+ if (activeWc !== wc)
183
+ return;
184
+ const isSameDocument = typeof details?.isSameDocument === 'boolean'
185
+ ? details.isSameDocument
186
+ : isInPlace;
187
+ const isMainFrame = typeof details?.isMainFrame === 'boolean'
188
+ ? details.isMainFrame
189
+ : isMainFramePositional;
190
+ if (isSameDocument || !isMainFrame)
191
+ return;
192
+ dropActivePort(true, 'awaitingHandshake');
193
+ });
194
+ wc.on('destroyed', () => {
195
+ if (activeWc === wc)
196
+ dropActivePort(true, 'absent');
197
+ });
198
+ },
199
+ invalidate() {
200
+ dropActivePort(true, 'awaitingHandshake');
201
+ },
202
+ onMessage(channel, handler) {
203
+ if (typeof channel !== 'string' || channel === '') {
204
+ throw new TypeError(`${opts.logPrefix} onMessage: channel must be a non-empty string`);
205
+ }
206
+ const entry = { channel, handler };
207
+ handlers.push(entry);
208
+ return {
209
+ dispose() {
210
+ const i = handlers.indexOf(entry);
211
+ if (i >= 0)
212
+ handlers.splice(i, 1);
213
+ },
214
+ };
215
+ },
216
+ onReady(handler) {
217
+ // Torn-down control: inert registration — never fires, never throws.
218
+ if (state === 'disposed') {
219
+ return { dispose() { } };
220
+ }
221
+ const entry = { handler };
222
+ readyHandlers.push(entry);
223
+ if (state === 'ready') {
224
+ // Missed-signal catch-up: the handshake already happened, so the
225
+ // subscriber would otherwise wait forever. Asynchronous on a
226
+ // microtask (never re-enter host code synchronously inside
227
+ // onReady()), and RE-CHECKED at fire time — both the subscription's
228
+ // liveness and the load generation can change between scheduling and
229
+ // the microtask (same-frame dispose() / same-frame loadFile).
230
+ const scheduledGeneration = generation;
231
+ queueMicrotask(() => {
232
+ if (state !== 'ready' || generation !== scheduledGeneration || !activePort)
233
+ return;
234
+ if (!readyHandlers.includes(entry))
235
+ return;
236
+ invokeReadyHandler(entry.handler);
237
+ });
238
+ }
239
+ return {
240
+ dispose() {
241
+ const i = readyHandlers.indexOf(entry);
242
+ if (i >= 0)
243
+ readyHandlers.splice(i, 1);
244
+ },
245
+ };
246
+ },
247
+ send(channel, payload) {
248
+ if (!activePort)
249
+ return false;
250
+ activePort.postMessage({ channel, payload });
251
+ return true;
252
+ },
253
+ dispose() {
254
+ dropActivePort(true, 'absent');
255
+ state = 'disposed';
256
+ handlers.length = 0;
257
+ readyHandlers.length = 0;
258
+ },
259
+ };
260
+ }
261
+ //# sourceMappingURL=host-slot-port-channel.js.map
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Ref-counted registration of a downstream-host-slot framework runtime on
3
+ * `session.defaultSession`.
4
+ *
5
+ * A slot's size advertiser does not ride the slot WCV's `webPreferences.preload`:
6
+ * a host's `setPreloadPath` would replace it and the reserved strip/pane would
7
+ * collapse to 0. Instead the runtime bundle is registered once per process as a
8
+ * session frame preload; its own guard (an `additionalArguments` marker +
9
+ * `isMainFrame`) keeps it a zero-footprint no-op in every other defaultSession
10
+ * renderer.
11
+ *
12
+ * Ref-counting: multiple ViewManagers can coexist in one process and share the
13
+ * ONE defaultSession. Each manager acquires at most one reference (on first
14
+ * slot need) and releases it in `disposeAll`. Only the first acquire registers;
15
+ * only the last release unregisters (with the id `registerPreloadScript`
16
+ * returned). After a full release a new acquire re-registers — there is
17
+ * deliberately no "registered once ever" latch, so dispose-everything-then-
18
+ * relaunch flows keep their slot runtime.
19
+ *
20
+ * Each `createHostSlotSessionRuntime(preloadPath)` call owns its own
21
+ * independent ref-count closure (one instance per slot TYPE, e.g. toolbar vs.
22
+ * sidebar), never a cross-slot shared counter. Module-level state per call site
23
+ * that binds one; tests reset it via `vi.resetModules()` + re-import.
24
+ */
25
+ export interface HostSlotSessionRuntime {
26
+ /**
27
+ * Take a reference on the shared session registration. Registers the runtime
28
+ * preload on `session.defaultSession` when the count rises from zero. Call at
29
+ * most once per ViewManager (the manager tracks its own acquired-flag).
30
+ */
31
+ acquire(): void;
32
+ /**
33
+ * Release a reference. Unregisters (with the stored registration id) only when
34
+ * the LAST reference is released; a still-alive ViewManager's slot keeps its
35
+ * session runtime. Safe to call only by managers that actually acquired.
36
+ */
37
+ release(): void;
38
+ }
39
+ export declare function createHostSlotSessionRuntime(preloadPath: string): HostSlotSessionRuntime;
40
+ //# sourceMappingURL=host-slot-session-runtime.d.ts.map
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Ref-counted registration of a downstream-host-slot framework runtime on
3
+ * `session.defaultSession`.
4
+ *
5
+ * A slot's size advertiser does not ride the slot WCV's `webPreferences.preload`:
6
+ * a host's `setPreloadPath` would replace it and the reserved strip/pane would
7
+ * collapse to 0. Instead the runtime bundle is registered once per process as a
8
+ * session frame preload; its own guard (an `additionalArguments` marker +
9
+ * `isMainFrame`) keeps it a zero-footprint no-op in every other defaultSession
10
+ * renderer.
11
+ *
12
+ * Ref-counting: multiple ViewManagers can coexist in one process and share the
13
+ * ONE defaultSession. Each manager acquires at most one reference (on first
14
+ * slot need) and releases it in `disposeAll`. Only the first acquire registers;
15
+ * only the last release unregisters (with the id `registerPreloadScript`
16
+ * returned). After a full release a new acquire re-registers — there is
17
+ * deliberately no "registered once ever" latch, so dispose-everything-then-
18
+ * relaunch flows keep their slot runtime.
19
+ *
20
+ * Each `createHostSlotSessionRuntime(preloadPath)` call owns its own
21
+ * independent ref-count closure (one instance per slot TYPE, e.g. toolbar vs.
22
+ * sidebar), never a cross-slot shared counter. Module-level state per call site
23
+ * that binds one; tests reset it via `vi.resetModules()` + re-import.
24
+ */
25
+ import { session } from 'electron';
26
+ export function createHostSlotSessionRuntime(preloadPath) {
27
+ let refCount = 0;
28
+ let registrationId = null;
29
+ return {
30
+ acquire() {
31
+ if (refCount === 0) {
32
+ registrationId = session.defaultSession.registerPreloadScript({
33
+ type: 'frame',
34
+ filePath: preloadPath,
35
+ });
36
+ }
37
+ refCount++;
38
+ },
39
+ release() {
40
+ if (refCount === 0)
41
+ return;
42
+ refCount--;
43
+ if (refCount === 0 && registrationId !== null) {
44
+ session.defaultSession.unregisterPreloadScript(registrationId);
45
+ registrationId = null;
46
+ }
47
+ },
48
+ };
49
+ }
50
+ //# sourceMappingURL=host-slot-session-runtime.js.map
@@ -0,0 +1,109 @@
1
+ import type { WebContents } from 'electron';
2
+ import type { PlacementReconciler } from './placement-reconciler.js';
3
+ import type { HostSlotMessageSubscription, HostSlotPortChannel } from './host-slot-port-channel.js';
4
+ export type HostSlotExtentMode = 'auto' | {
5
+ fixed: number;
6
+ };
7
+ /**
8
+ * The control surface a downstream host uses to own a persistent, single-axis
9
+ * slot's WebContentsView (host-toolbar, host-sidebar). Axis-neutral base for
10
+ * `HostToolbarControl`/`HostSidebarControl`, which each derive from it via
11
+ * `Omit<HostSlotControl, 'setExtentMode'>` and rename that one member to the
12
+ * axis-specific `setHeightMode`/`setWidthMode` — see either for the frozen,
13
+ * per-slot public surface these members ship under.
14
+ */
15
+ export interface HostSlotControl {
16
+ /** Load a URL into the slot's view (lazy-creates the view). */
17
+ loadURL(url: string): Promise<void>;
18
+ /** Load a local file into the slot's view (lazy-creates the view). */
19
+ loadFile(filePath: string): Promise<void>;
20
+ /** The slot's live WebContents, or null if not yet created/destroyed. */
21
+ readonly webContents: WebContents | null;
22
+ /** Remove the slot's view from the contentView and reset it (kept alive). */
23
+ hide(): void;
24
+ /**
25
+ * The HOST's own `webPreferences.preload` for the slot's view (purely
26
+ * additive). The framework's advertiser runtime does NOT ride
27
+ * `webPreferences.preload` — it is session-resident (registered on
28
+ * `session.defaultSession`, self-guarded by the slot's own marker +
29
+ * `isMainFrame`), so a host preload set here coexists with it and never
30
+ * replaces it. Must be set before the view is (re)created (first
31
+ * `loadURL`/`loadFile`, or the next one after the host closed the
32
+ * webContents); `null` (default) means "no host preload" — it does not and
33
+ * cannot restore any built-in preload.
34
+ */
35
+ setPreloadPath(path: string | null): void;
36
+ setExtentMode(mode: HostSlotExtentMode): void;
37
+ /**
38
+ * Register a host-side handler for messages the slot PAGE sends over its
39
+ * gated port channel. May be called before the view exists and survives
40
+ * page reloads / wc rebuilds (each per-load MessagePort handshake
41
+ * re-attaches the registry to the new port). Throws on an empty /
42
+ * non-string channel. `dispose()` detaches (idempotent).
43
+ */
44
+ onMessage(channel: string, handler: (payload: unknown) => void): HostSlotMessageSubscription;
45
+ /**
46
+ * Observe handshake readiness — the push counterpart to polling `send()`
47
+ * for `true`. Fires the handler once per load generation, exactly when
48
+ * that load's MessagePort handshake completes; registering while the
49
+ * channel is ALREADY ready fires once asynchronously on a microtask
50
+ * (missed-signal race guard, re-validated at fire time). A reload /
51
+ * re-handshake fires registered handlers again; a host-initiated
52
+ * `loadURL`/`loadFile` invalidates readiness at initiation, so handlers
53
+ * registered in that window wait for the NEW document's handshake.
54
+ * `dispose()` detaches (idempotent); `disposeAll` sweeps everything.
55
+ */
56
+ onReady(handler: () => void): HostSlotMessageSubscription;
57
+ /**
58
+ * Post `{ channel, payload }` to the slot page (received via the page's
59
+ * `onMessage(channel, handler)`). Gated and non-queueing: returns false —
60
+ * delivering nothing, creating no view — while there is no live slot
61
+ * webContents, the current load's MessagePort handshake hasn't completed,
62
+ * or a document-replacing navigation is in flight (`loadURL`/`loadFile`
63
+ * was issued, or the page itself started a main-frame cross-document
64
+ * navigation, and the new document hasn't handshaked yet); true once the
65
+ * envelope went out. No manual webContentsId gating needed: the false/true
66
+ * result IS the readiness signal.
67
+ */
68
+ send(channel: string, payload: unknown): boolean;
69
+ }
70
+ /**
71
+ * Build an axis-specific control surface (`HostToolbarControl`/
72
+ * `HostSidebarControl`) from `createHostSlotView`'s underlying `slot.control`
73
+ * plus that one axis's renamed extent-mode setter. Forwards every shared
74
+ * member explicitly (not a shallow spread) so the `webContents` accessor
75
+ * stays a LIVE getter into `base` rather than a one-time snapshot.
76
+ */
77
+ export declare function deriveSlotControl<Extra extends Record<string, unknown>>(base: HostSlotControl, extra: Extra): Omit<HostSlotControl, 'setExtentMode'> & Extra;
78
+ export interface HostSlotView {
79
+ readonly control: HostSlotControl;
80
+ setExtent(extent: number): void;
81
+ getExtent(): number;
82
+ getWebContentsId(): number | null;
83
+ dispose(): void;
84
+ }
85
+ export interface HostSlotConfig {
86
+ viewId: string;
87
+ layer: number;
88
+ marker: string;
89
+ sessionRuntime: {
90
+ acquire(): void;
91
+ release(): void;
92
+ };
93
+ portChannel: HostSlotPortChannel;
94
+ /** Prefix for the `setExtentMode` fixed-value validation error, e.g. `'hostToolbar.setHeightMode'`. */
95
+ setExtentModeErrorLabel: string;
96
+ }
97
+ /**
98
+ * Shared implementation behind the persistent, single-axis host slots
99
+ * (host-toolbar, host-sidebar): lazily-created `WebContentsView` + ref-counted
100
+ * session runtime + per-load port-channel handshake + `setBaseDesired`
101
+ * placement, with an auto/fixed extent mode reported back through
102
+ * `deps.onExtentChanged`. See `host-toolbar-view.ts` for the frozen
103
+ * toolbar-shaped public wrapper.
104
+ */
105
+ export declare function createHostSlotView(reconciler: PlacementReconciler, config: HostSlotConfig, deps: {
106
+ onExtentChanged(extent: number): void;
107
+ reapplyPresentOverlays?(): void;
108
+ }): HostSlotView;
109
+ //# sourceMappingURL=host-slot-view.d.ts.map