@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.
- package/dist/main/api.d.ts +1 -1
- package/dist/main/app/app.js +55 -2
- package/dist/main/index.bundle.js +887 -234
- package/dist/main/ipc/index.d.ts +2 -0
- package/dist/main/ipc/index.js +2 -0
- package/dist/main/ipc/popover.js +1 -1
- package/dist/main/ipc/project-create.d.ts +15 -0
- package/dist/main/ipc/project-create.js +26 -0
- package/dist/main/ipc/projects.d.ts +1 -1
- package/dist/main/ipc/projects.js +11 -0
- package/dist/main/ipc/settings.js +2 -1
- package/dist/main/ipc/simulator-module.d.ts +4 -3
- package/dist/main/ipc/simulator-module.js +4 -5
- package/dist/main/ipc/tooltip.js +1 -1
- package/dist/main/ipc/views.d.ts +15 -1
- package/dist/main/ipc/views.js +61 -4
- package/dist/main/runtime/miniapp-runtime.d.ts +87 -19
- package/dist/main/services/notifications/renderer-notifier.d.ts +30 -1
- package/dist/main/services/notifications/renderer-notifier.js +23 -1
- package/dist/main/services/projects/project-repository.d.ts +4 -1
- package/dist/main/services/projects/project-repository.js +63 -5
- package/dist/main/services/update/update-manager.d.ts +17 -3
- package/dist/main/services/update/update-manager.js +25 -6
- package/dist/main/services/views/host-dialog-port-channel.d.ts +18 -0
- package/dist/main/services/views/host-dialog-port-channel.js +15 -0
- package/dist/main/services/views/host-dialog-session-runtime.d.ts +10 -0
- package/dist/main/services/views/host-dialog-session-runtime.js +17 -0
- package/dist/main/services/views/host-dialog-view.d.ts +95 -0
- package/dist/main/services/views/host-dialog-view.js +147 -0
- package/dist/main/services/views/host-sidebar-port-channel.d.ts +18 -0
- package/dist/main/services/views/host-sidebar-port-channel.js +15 -0
- package/dist/main/services/views/host-sidebar-session-runtime.d.ts +10 -0
- package/dist/main/services/views/host-sidebar-session-runtime.js +17 -0
- package/dist/main/services/views/host-sidebar-view.d.ts +59 -0
- package/dist/main/services/views/host-sidebar-view.js +44 -0
- package/dist/main/services/views/host-slot-port-channel.d.ts +107 -0
- package/dist/main/services/views/host-slot-port-channel.js +261 -0
- package/dist/main/services/views/host-slot-session-runtime.d.ts +40 -0
- package/dist/main/services/views/host-slot-session-runtime.js +50 -0
- package/dist/main/services/views/host-slot-view.d.ts +109 -0
- package/dist/main/services/views/host-slot-view.js +147 -0
- package/dist/main/services/views/host-toolbar-port-channel.d.ts +6 -91
- package/dist/main/services/views/host-toolbar-port-channel.js +10 -243
- package/dist/main/services/views/host-toolbar-session-runtime.d.ts +5 -30
- package/dist/main/services/views/host-toolbar-session-runtime.js +9 -47
- package/dist/main/services/views/host-toolbar-view.d.ts +34 -7
- package/dist/main/services/views/host-toolbar-view.js +30 -224
- package/dist/main/services/views/managed-web-contents-view.d.ts +48 -0
- package/dist/main/services/views/managed-web-contents-view.js +123 -0
- package/dist/main/services/views/overlay-panels-view.d.ts +23 -0
- package/dist/main/services/views/overlay-panels-view.js +93 -4
- package/dist/main/services/views/placement-reconciler.d.ts +14 -2
- package/dist/main/services/views/placement-reconciler.js +27 -0
- package/dist/main/services/views/placement-test-driver.d.ts +1 -0
- package/dist/main/services/views/placement-test-driver.js +3 -0
- package/dist/main/services/views/view-manager-devtools-host-test-fixtures.js +3 -1
- package/dist/main/services/views/view-manager.d.ts +10 -78
- package/dist/main/services/views/view-manager.js +34 -3
- package/dist/main/utils/paths.d.ts +4 -0
- package/dist/main/utils/paths.js +4 -0
- package/dist/main/utils/sender-policy.js +10 -0
- package/dist/native-host/common/common.js +65 -62
- package/dist/native-host/render/render.js +2230 -2052
- package/dist/native-host/service/service.js +2 -2
- package/dist/preload/runtime/host-dialog-advertiser.d.ts +22 -0
- package/dist/preload/runtime/host-dialog-advertiser.js +37 -0
- package/dist/preload/runtime/host-dialog-port.d.ts +44 -0
- package/dist/preload/runtime/host-dialog-port.js +53 -0
- package/dist/preload/runtime/host-dialog-runtime.d.ts +28 -0
- package/dist/preload/runtime/host-dialog-runtime.js +36 -0
- package/dist/preload/runtime/host-sidebar-advertiser.d.ts +19 -0
- package/dist/preload/runtime/host-sidebar-advertiser.js +35 -0
- package/dist/preload/runtime/host-sidebar-port.d.ts +44 -0
- package/dist/preload/runtime/host-sidebar-port.js +53 -0
- package/dist/preload/runtime/host-sidebar-runtime.d.ts +28 -0
- package/dist/preload/runtime/host-sidebar-runtime.js +36 -0
- package/dist/preload/runtime/host-slot-port-bridge.d.ts +18 -0
- package/dist/preload/runtime/host-slot-port-bridge.js +112 -0
- package/dist/preload/runtime/host-toolbar-advertiser.d.ts +5 -13
- package/dist/preload/runtime/host-toolbar-advertiser.js +18 -38
- package/dist/preload/runtime/host-toolbar-port.d.ts +9 -0
- package/dist/preload/runtime/host-toolbar-port.js +11 -95
- package/dist/preload/runtime/wait-for-slot-root.d.ts +20 -0
- package/dist/preload/runtime/wait-for-slot-root.js +52 -0
- package/dist/preload/windows/host-dialog-runtime.cjs +353 -0
- package/dist/preload/windows/host-dialog-runtime.cjs.map +7 -0
- package/dist/preload/windows/host-dialog-runtime.d.ts +2 -0
- package/dist/preload/windows/host-dialog-runtime.js +13 -0
- package/dist/preload/windows/host-sidebar-runtime.cjs +354 -0
- package/dist/preload/windows/host-sidebar-runtime.cjs.map +7 -0
- package/dist/preload/windows/host-sidebar-runtime.d.ts +2 -0
- package/dist/preload/windows/host-sidebar-runtime.js +13 -0
- package/dist/preload/windows/host-toolbar-runtime.cjs +134 -51
- package/dist/preload/windows/host-toolbar-runtime.cjs.map +3 -3
- package/dist/preload/windows/simulator.cjs.map +1 -1
- package/dist/renderer/assets/button-DsNqZm7o.js +2 -0
- package/dist/renderer/assets/constants-D7DfVqMp.js +2 -0
- package/dist/renderer/assets/constants-D8zsejFq.js +2 -0
- package/dist/renderer/assets/createLucideIcon-CYJeT1ji.js +2 -0
- package/dist/renderer/assets/dialog-pT4qWmka.js +46 -0
- package/dist/renderer/assets/dist-o70xmpc1.js +2 -0
- package/dist/renderer/assets/hostSidebarDefault-NvGZlE9O.js +2 -0
- package/dist/renderer/assets/index-zErhG3ta.js +5 -0
- package/dist/renderer/assets/input-4QxTXMZN.js +2 -0
- package/dist/renderer/assets/ipc-channels-overlays-Blr5TNoB.js +2 -0
- package/dist/renderer/assets/jsx-runtime-B-3rq1y5.css +1 -0
- package/dist/renderer/assets/jsx-runtime-CqVdW3eg.js +9 -0
- package/dist/renderer/assets/popover-CJiSWwuE.js +2 -0
- package/dist/renderer/assets/project-api-DZ1Zi6EC.js +2 -0
- package/dist/renderer/assets/projectCreateDialog-DMWNhw_1.js +2 -0
- package/dist/renderer/assets/select-C86pNcsu.js +2 -0
- package/dist/renderer/assets/settings-BoI8mLT1.js +2 -0
- package/dist/renderer/assets/settings-api-Bs_g4NHs.js +2 -0
- package/dist/renderer/assets/settings-tab-bar-DHLLygqv.js +2 -0
- package/dist/renderer/assets/tooltip-BhAyY0T4.js +2 -0
- package/dist/renderer/assets/types-YYjkvane.js +2 -0
- package/dist/renderer/assets/updateDialog-DPum3wx1.js +2 -0
- package/dist/renderer/assets/utils-kfQov6Pa.js +2 -0
- package/dist/renderer/assets/view-api-DFhb2R8-.js +2 -0
- package/dist/renderer/assets/{workbenchSettings-oq_EyfKf.js → workbenchSettings-DrajRQUk.js} +3 -3
- package/dist/renderer/entries/host-sidebar-default/index.html +21 -0
- package/dist/renderer/entries/main/index.html +15 -8
- package/dist/renderer/entries/popover/index.html +10 -7
- package/dist/renderer/entries/project-create-dialog/index.html +27 -0
- package/dist/renderer/entries/settings/index.html +10 -7
- package/dist/renderer/entries/tooltip/index.html +5 -4
- package/dist/renderer/entries/update-dialog/index.html +25 -0
- package/dist/renderer/entries/workbench-settings/index.html +11 -7
- package/dist/service-host/preload.cjs +10 -0
- package/dist/shared/constants.d.ts +22 -0
- package/dist/shared/constants.js +22 -0
- package/dist/shared/ipc-channels-overlays.d.ts +156 -0
- package/dist/shared/ipc-channels-overlays.js +186 -0
- package/dist/shared/ipc-channels.d.ts +0 -77
- package/dist/shared/ipc-channels.js +0 -98
- package/dist/shared/ipc-schemas.d.ts +33 -0
- package/dist/shared/ipc-schemas.js +41 -0
- package/dist/shared/types.d.ts +10 -0
- package/dist/shared/view-ids.d.ts +7 -0
- package/dist/shared/view-ids.js +10 -0
- package/package.json +8 -8
- package/dist/renderer/assets/constants-Bdz0LHdy.js +0 -2
- package/dist/renderer/assets/index-CtznrmHR.js +0 -49
- package/dist/renderer/assets/input-Dmm6nob5.js +0 -2
- package/dist/renderer/assets/ipc-transport-BOg0gR9W.js +0 -9
- package/dist/renderer/assets/ipc-transport-DgQhZaE9.css +0 -1
- package/dist/renderer/assets/popover-Cd1FEGOX.js +0 -2
- package/dist/renderer/assets/select-BaYox7If.js +0 -2
- package/dist/renderer/assets/settings-BXvwuFaz.js +0 -2
- package/dist/renderer/assets/settings-api-Bo9GbYqs.js +0 -2
- package/dist/renderer/assets/settings-tab-bar-Beia6lMr.js +0 -2
- package/dist/renderer/assets/tooltip-DfQqK3Sk.js +0 -2
- package/dist/renderer/assets/view-api-nn69je7A.js +0 -2
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
import { createManagedWebContentsView } from './managed-web-contents-view.js';
|
|
2
|
+
/**
|
|
3
|
+
* Build an axis-specific control surface (`HostToolbarControl`/
|
|
4
|
+
* `HostSidebarControl`) from `createHostSlotView`'s underlying `slot.control`
|
|
5
|
+
* plus that one axis's renamed extent-mode setter. Forwards every shared
|
|
6
|
+
* member explicitly (not a shallow spread) so the `webContents` accessor
|
|
7
|
+
* stays a LIVE getter into `base` rather than a one-time snapshot.
|
|
8
|
+
*/
|
|
9
|
+
export function deriveSlotControl(base, extra) {
|
|
10
|
+
return {
|
|
11
|
+
loadURL: (url) => base.loadURL(url),
|
|
12
|
+
loadFile: (filePath) => base.loadFile(filePath),
|
|
13
|
+
get webContents() {
|
|
14
|
+
return base.webContents;
|
|
15
|
+
},
|
|
16
|
+
hide: () => base.hide(),
|
|
17
|
+
setPreloadPath: (path) => base.setPreloadPath(path),
|
|
18
|
+
onMessage: (channel, handler) => base.onMessage(channel, handler),
|
|
19
|
+
onReady: (handler) => base.onReady(handler),
|
|
20
|
+
send: (channel, payload) => base.send(channel, payload),
|
|
21
|
+
...extra,
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Shared implementation behind the persistent, single-axis host slots
|
|
26
|
+
* (host-toolbar, host-sidebar): lazily-created `WebContentsView` + ref-counted
|
|
27
|
+
* session runtime + per-load port-channel handshake + `setBaseDesired`
|
|
28
|
+
* placement, with an auto/fixed extent mode reported back through
|
|
29
|
+
* `deps.onExtentChanged`. See `host-toolbar-view.ts` for the frozen
|
|
30
|
+
* toolbar-shaped public wrapper.
|
|
31
|
+
*/
|
|
32
|
+
export function createHostSlotView(reconciler, config, deps) {
|
|
33
|
+
let extentMode = 'auto';
|
|
34
|
+
let lastExtent = 0;
|
|
35
|
+
// The real current size the advertiser last reported, independent of
|
|
36
|
+
// `extentMode` — tracked separately from `lastExtent` (the last value we
|
|
37
|
+
// actually NOTIFIED) because `setExtent` still no-ops while fixed. Needed
|
|
38
|
+
// to reapply the true current size immediately when switching back to
|
|
39
|
+
// 'auto': the advertiser (measure-loop.ts) dedupes against its own last-
|
|
40
|
+
// EMITTED value, so if content stayed exactly the size it was before the
|
|
41
|
+
// switch to fixed, it will never re-report that value on its own.
|
|
42
|
+
let lastAdvertisedExtent = null;
|
|
43
|
+
const port = config.portChannel;
|
|
44
|
+
const managed = createManagedWebContentsView({
|
|
45
|
+
reconciler,
|
|
46
|
+
viewId: config.viewId,
|
|
47
|
+
marker: config.marker,
|
|
48
|
+
sessionRuntime: config.sessionRuntime,
|
|
49
|
+
port,
|
|
50
|
+
});
|
|
51
|
+
function notifyExtent(extent) {
|
|
52
|
+
lastExtent = extent;
|
|
53
|
+
deps.onExtentChanged(extent);
|
|
54
|
+
deps.reapplyPresentOverlays?.();
|
|
55
|
+
}
|
|
56
|
+
function setExtent(extent) {
|
|
57
|
+
lastAdvertisedExtent = extent;
|
|
58
|
+
if (extentMode !== 'auto')
|
|
59
|
+
return;
|
|
60
|
+
// Push the reserved extent back to the main-window renderer so its
|
|
61
|
+
// placeholder div resizes (closing the dynamic-size loop). The notified
|
|
62
|
+
// extent IS retained in main (`getExtent`) so a renderer that mounts
|
|
63
|
+
// later can pull/replay it; the renderer placeholder remains the
|
|
64
|
+
// geometry authority — the forward anchor re-reports bounds from it.
|
|
65
|
+
notifyExtent(extent);
|
|
66
|
+
}
|
|
67
|
+
function hideView() {
|
|
68
|
+
reconciler.setBaseDesired(config.viewId, {
|
|
69
|
+
viewId: config.viewId,
|
|
70
|
+
placement: { visible: false },
|
|
71
|
+
layer: config.layer,
|
|
72
|
+
});
|
|
73
|
+
reconciler.reconcileNow();
|
|
74
|
+
// Collapse the renderer placeholder to 0 too. Otherwise its anchor keeps a
|
|
75
|
+
// non-zero reserved extent and re-publishes bounds on the next window
|
|
76
|
+
// resize, silently re-adding the view we just hid (unstable hide). Zeroing
|
|
77
|
+
// the extent flips the anchor to `present:false` so it stops re-publishing.
|
|
78
|
+
// Through the funnel so the retained value follows to 0 — a renderer
|
|
79
|
+
// mounting after the hide must replay 0, not the stale pre-hide extent.
|
|
80
|
+
notifyExtent(0);
|
|
81
|
+
// Also clear the advertised-size memory: a hidden strip's pre-hide size is
|
|
82
|
+
// stale content state, not a value a later `setExtentMode('auto')` should
|
|
83
|
+
// resurrect (see the `lastAdvertisedExtent`-reapply branch below) — the
|
|
84
|
+
// advertiser (still mounted, unaware of the hide) reports the real size
|
|
85
|
+
// again on its own once content changes, or immediately on next mount.
|
|
86
|
+
lastAdvertisedExtent = null;
|
|
87
|
+
}
|
|
88
|
+
const control = {
|
|
89
|
+
loadURL: (url) => managed.loadURL(url),
|
|
90
|
+
loadFile: (filePath) => managed.loadFile(filePath),
|
|
91
|
+
get webContents() {
|
|
92
|
+
return managed.liveWebContents();
|
|
93
|
+
},
|
|
94
|
+
hide() {
|
|
95
|
+
hideView();
|
|
96
|
+
},
|
|
97
|
+
setPreloadPath: (path) => managed.setPreloadPath(path),
|
|
98
|
+
setExtentMode(mode) {
|
|
99
|
+
// Validate BEFORE touching any state: a poisoned `{ fixed }` (NaN /
|
|
100
|
+
// ±Infinity / negative) must neither reach the renderer placeholder
|
|
101
|
+
// (`extent: NaNpx` corrupts the strip with no error anywhere) nor
|
|
102
|
+
// clobber the standing mode — fail-closed, not fail-corrupt.
|
|
103
|
+
if (mode !== 'auto' && !(Number.isFinite(mode.fixed) && mode.fixed >= 0)) {
|
|
104
|
+
throw new TypeError(`${config.setExtentModeErrorLabel}: fixed extent must be a finite, non-negative number (got ${mode.fixed})`);
|
|
105
|
+
}
|
|
106
|
+
extentMode = mode;
|
|
107
|
+
if (mode !== 'auto') {
|
|
108
|
+
// Pin immediately: a preload-less/static slot never advertises, so
|
|
109
|
+
// waiting for the next report would leave the strip at extent 0.
|
|
110
|
+
notifyExtent(mode.fixed);
|
|
111
|
+
}
|
|
112
|
+
else if (lastAdvertisedExtent !== null && lastAdvertisedExtent !== lastExtent) {
|
|
113
|
+
// Reapply the real current advertised size immediately rather than
|
|
114
|
+
// waiting for the next report — see `lastAdvertisedExtent`'s
|
|
115
|
+
// doc-comment above for why that report may never come. No-op when
|
|
116
|
+
// nothing has ever been advertised, or the advertised value already
|
|
117
|
+
// matches what's notified (avoids a spurious flash/duplicate push).
|
|
118
|
+
notifyExtent(lastAdvertisedExtent);
|
|
119
|
+
}
|
|
120
|
+
},
|
|
121
|
+
onMessage(channel, handler) {
|
|
122
|
+
return port.onMessage(channel, handler);
|
|
123
|
+
},
|
|
124
|
+
onReady(handler) {
|
|
125
|
+
return port.onReady(handler);
|
|
126
|
+
},
|
|
127
|
+
send(channel, payload) {
|
|
128
|
+
return port.send(channel, payload);
|
|
129
|
+
},
|
|
130
|
+
};
|
|
131
|
+
reconciler.registerView(config.viewId, {
|
|
132
|
+
getView: () => managed.getView(),
|
|
133
|
+
ensureView: () => managed.ensureView(),
|
|
134
|
+
ensureLazy: (desired) => {
|
|
135
|
+
if (desired?.placement.visible && !managed.liveWebContents())
|
|
136
|
+
managed.ensureView();
|
|
137
|
+
},
|
|
138
|
+
});
|
|
139
|
+
return {
|
|
140
|
+
control,
|
|
141
|
+
setExtent,
|
|
142
|
+
getExtent: () => lastExtent,
|
|
143
|
+
getWebContentsId: () => managed.liveWebContents()?.id ?? null,
|
|
144
|
+
dispose: () => managed.dispose(),
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
//# sourceMappingURL=host-slot-view.js.map
|
|
@@ -1,97 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* Why a MessagePort and not plain IPC: the toolbar page is HOST-ARBITRARY
|
|
6
|
-
* content. A dedicated port pair gives it exactly one pipe whose main-side end
|
|
7
|
-
* lives in this module — the page never gains a generic `ipcRenderer` reach
|
|
8
|
-
* into the ~72 internal devtools channels, and main-side inbound traffic is
|
|
9
|
-
* validated at this single waist.
|
|
10
|
-
*
|
|
11
|
-
* Lifecycle (per-load handshake):
|
|
12
|
-
* - On every toolbar-wc `did-finish-load` main builds a fresh
|
|
13
|
-
* `MessageChannelMain`, transfers port2 to the new document via
|
|
14
|
-
* `wc.postMessage(ViewChannel.HostToolbarPort, null, [port2])`, keeps
|
|
15
|
-
* port1 and `start()`s it. The previous port1 (if any) is closed first —
|
|
16
|
-
* its document is gone.
|
|
17
|
-
* - `onMessage` registrations are CONTROL-level (this module's registry), not
|
|
18
|
-
* per-port: they may be made before any view exists and survive page
|
|
19
|
-
* reloads / wc rebuilds, because each new handshake dispatches into the
|
|
20
|
-
* same registry.
|
|
21
|
-
* - `send` is gated and non-queueing: `false` means "not delivered, ever" —
|
|
22
|
-
* no toolbar view is conjured, nothing is buffered for a later handshake.
|
|
23
|
-
* - A port1 `'close'` event (the renderer end died without a re-handshake
|
|
24
|
-
* yet) drops the live-port reference so `send` reports `false` instead of
|
|
25
|
-
* posting into a dead pipe.
|
|
26
|
-
* - NAVIGATION INVALIDATES THE PORT. The port pair belongs to one DOCUMENT,
|
|
27
|
-
* so starting a navigation that replaces that document closes port1 and
|
|
28
|
-
* `send` returns `false` until the new document's `did-finish-load`
|
|
29
|
-
* handshake. Two paths:
|
|
30
|
-
* (a) host-initiated `hostToolbar.loadURL/loadFile` calls `invalidate()`
|
|
31
|
-
* synchronously AT INITIATION (view-manager does this before the
|
|
32
|
-
* `webContents.loadURL/loadFile` await), so a same-tick `send` can
|
|
33
|
-
* never report `true` for an envelope posted into the document the
|
|
34
|
-
* load is about to replace;
|
|
35
|
-
* (b) page-initiated navigation (location.href / reload) is caught by a
|
|
36
|
-
* `did-start-navigation` listener — guarded to the ACTIVE wc,
|
|
37
|
-
* main-frame, cross-document only. Same-document navigations
|
|
38
|
-
* (anchors, pushState, history) and subframe navigations do NOT
|
|
39
|
-
* invalidate: the document the port lives in survives those, and no
|
|
40
|
-
* main-frame `did-finish-load` would follow to ever restore the
|
|
41
|
-
* channel.
|
|
42
|
-
*
|
|
43
|
-
* Envelope both directions: `{ channel: string, payload: unknown }`. Inbound
|
|
44
|
-
* data that is not an object with a string `channel` is DROPPED without
|
|
45
|
-
* throwing (the counterpart is arbitrary host content).
|
|
2
|
+
* Toolbar-specific instantiation of `createHostSlotPortChannel` (see that
|
|
3
|
+
* module for the full per-load handshake / navigation-invalidation
|
|
4
|
+
* contract).
|
|
46
5
|
*/
|
|
47
6
|
import type { WebContents } from 'electron';
|
|
48
|
-
|
|
49
|
-
export
|
|
50
|
-
|
|
51
|
-
}
|
|
52
|
-
export interface HostToolbarPortChannel {
|
|
53
|
-
/**
|
|
54
|
-
* Hook a freshly created toolbar webContents: registers the
|
|
55
|
-
* `did-finish-load` (per-load handshake) and `destroyed` (drop the live
|
|
56
|
-
* port) listeners via `wc.on(...)`. Call exactly once per wc, right after
|
|
57
|
-
* 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 by view-manager synchronously when the HOST
|
|
63
|
-
* initiates a `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): HostToolbarMessageSubscription;
|
|
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): HostToolbarMessageSubscription;
|
|
81
|
-
/**
|
|
82
|
-
* Post `{ channel, payload }` to the toolbar page over the live port.
|
|
83
|
-
* Returns false (delivering NOTHING, creating NOTHING) when there is no
|
|
84
|
-
* live toolbar 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 (view-manager `disposeAll`): close the live port, sweep the
|
|
90
|
-
* handler registry, refuse further handshakes. Subsequent `send` returns
|
|
91
|
-
* false; late `dispose()` of an old subscription stays a no-op.
|
|
92
|
-
*/
|
|
93
|
-
dispose(): void;
|
|
94
|
-
}
|
|
7
|
+
import { type HostSlotMessageSubscription, type HostSlotPortChannel } from './host-slot-port-channel.js';
|
|
8
|
+
export type HostToolbarMessageSubscription = HostSlotMessageSubscription;
|
|
9
|
+
export type HostToolbarPortChannel = HostSlotPortChannel;
|
|
95
10
|
export declare function createHostToolbarPortChannel(opts: {
|
|
96
11
|
/**
|
|
97
12
|
* Is `wc` still the manager's CURRENT live toolbar webContents? Guards a
|
|
@@ -1,248 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* Why a MessagePort and not plain IPC: the toolbar page is HOST-ARBITRARY
|
|
6
|
-
* content. A dedicated port pair gives it exactly one pipe whose main-side end
|
|
7
|
-
* lives in this module — the page never gains a generic `ipcRenderer` reach
|
|
8
|
-
* into the ~72 internal devtools channels, and main-side inbound traffic is
|
|
9
|
-
* validated at this single waist.
|
|
10
|
-
*
|
|
11
|
-
* Lifecycle (per-load handshake):
|
|
12
|
-
* - On every toolbar-wc `did-finish-load` main builds a fresh
|
|
13
|
-
* `MessageChannelMain`, transfers port2 to the new document via
|
|
14
|
-
* `wc.postMessage(ViewChannel.HostToolbarPort, null, [port2])`, keeps
|
|
15
|
-
* port1 and `start()`s it. The previous port1 (if any) is closed first —
|
|
16
|
-
* its document is gone.
|
|
17
|
-
* - `onMessage` registrations are CONTROL-level (this module's registry), not
|
|
18
|
-
* per-port: they may be made before any view exists and survive page
|
|
19
|
-
* reloads / wc rebuilds, because each new handshake dispatches into the
|
|
20
|
-
* same registry.
|
|
21
|
-
* - `send` is gated and non-queueing: `false` means "not delivered, ever" —
|
|
22
|
-
* no toolbar view is conjured, nothing is buffered for a later handshake.
|
|
23
|
-
* - A port1 `'close'` event (the renderer end died without a re-handshake
|
|
24
|
-
* yet) drops the live-port reference so `send` reports `false` instead of
|
|
25
|
-
* posting into a dead pipe.
|
|
26
|
-
* - NAVIGATION INVALIDATES THE PORT. The port pair belongs to one DOCUMENT,
|
|
27
|
-
* so starting a navigation that replaces that document closes port1 and
|
|
28
|
-
* `send` returns `false` until the new document's `did-finish-load`
|
|
29
|
-
* handshake. Two paths:
|
|
30
|
-
* (a) host-initiated `hostToolbar.loadURL/loadFile` calls `invalidate()`
|
|
31
|
-
* synchronously AT INITIATION (view-manager does this before the
|
|
32
|
-
* `webContents.loadURL/loadFile` await), so a same-tick `send` can
|
|
33
|
-
* never report `true` for an envelope posted into the document the
|
|
34
|
-
* load is about to replace;
|
|
35
|
-
* (b) page-initiated navigation (location.href / reload) is caught by a
|
|
36
|
-
* `did-start-navigation` listener — guarded to the ACTIVE wc,
|
|
37
|
-
* main-frame, cross-document only. Same-document navigations
|
|
38
|
-
* (anchors, pushState, history) and subframe navigations do NOT
|
|
39
|
-
* invalidate: the document the port lives in survives those, and no
|
|
40
|
-
* main-frame `did-finish-load` would follow to ever restore the
|
|
41
|
-
* channel.
|
|
42
|
-
*
|
|
43
|
-
* Envelope both directions: `{ channel: string, payload: unknown }`. Inbound
|
|
44
|
-
* data that is not an object with a string `channel` is DROPPED without
|
|
45
|
-
* throwing (the counterpart is arbitrary host content).
|
|
2
|
+
* Toolbar-specific instantiation of `createHostSlotPortChannel` (see that
|
|
3
|
+
* module for the full per-load handshake / navigation-invalidation
|
|
4
|
+
* contract).
|
|
46
5
|
*/
|
|
47
|
-
import {
|
|
48
|
-
import {
|
|
6
|
+
import { ViewChannel } from '../../../shared/ipc-channels-overlays.js';
|
|
7
|
+
import { createHostSlotPortChannel, } from './host-slot-port-channel.js';
|
|
49
8
|
export function createHostToolbarPortChannel(opts) {
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
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 toolbar page is host-arbitrary content — anything
|
|
69
|
-
// that 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
|
-
if (entry.channel === channel)
|
|
78
|
-
entry.handler(payload);
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
/**
|
|
82
|
-
* Drop the live port and transition to `next` (`'absent'` for death paths,
|
|
83
|
-
* `'awaitingHandshake'` for navigation paths). Never demotes `disposed`.
|
|
84
|
-
*/
|
|
85
|
-
function dropActivePort(close, next) {
|
|
86
|
-
const port = activePort;
|
|
87
|
-
activePort = null;
|
|
88
|
-
activeWc = null;
|
|
89
|
-
if (state !== 'disposed')
|
|
90
|
-
state = next;
|
|
91
|
-
if (close && port) {
|
|
92
|
-
try {
|
|
93
|
-
port.close();
|
|
94
|
-
}
|
|
95
|
-
catch {
|
|
96
|
-
/* already closed */
|
|
97
|
-
}
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
/**
|
|
101
|
-
* Invoke one onReady handler with exception ISOLATION: subscribers are
|
|
102
|
-
* arbitrary control-level code — one throwing must neither starve sibling
|
|
103
|
-
* registrations nor escape the surrounding event/microtask callback (a
|
|
104
|
-
* throw out of a `did-finish-load` listener or a `queueMicrotask` body is
|
|
105
|
-
* process-level `uncaughtException` territory). Report-and-continue.
|
|
106
|
-
*/
|
|
107
|
-
function invokeReadyHandler(handler) {
|
|
108
|
-
try {
|
|
109
|
-
handler();
|
|
110
|
-
}
|
|
111
|
-
catch (err) {
|
|
112
|
-
console.error('[host-toolbar] onReady handler threw:', err);
|
|
113
|
-
}
|
|
114
|
-
}
|
|
115
|
-
/** Fire every still-registered onReady handler exactly once (snapshot iteration). */
|
|
116
|
-
function fireReadyHandlers() {
|
|
117
|
-
for (const entry of [...readyHandlers]) {
|
|
118
|
-
// A handler may dispose a sibling registration mid-fire: re-check
|
|
119
|
-
// membership so a disposed entry never fires. Per-handler isolation
|
|
120
|
-
// (NOT around the loop): a throwing handler must not abort the fire
|
|
121
|
-
// for later-registered siblings.
|
|
122
|
-
if (readyHandlers.includes(entry))
|
|
123
|
-
invokeReadyHandler(entry.handler);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
function handshake(wc) {
|
|
127
|
-
if (state === 'disposed')
|
|
128
|
-
return;
|
|
129
|
-
if (!opts.isCurrent(wc))
|
|
130
|
-
return;
|
|
131
|
-
// The previous load's document is gone; its port goes with it.
|
|
132
|
-
dropActivePort(true, 'awaitingHandshake');
|
|
133
|
-
const { port1, port2 } = new MessageChannelMain();
|
|
134
|
-
port1.on('message', (event) => {
|
|
135
|
-
dispatch(event.data);
|
|
136
|
-
});
|
|
137
|
-
// Renderer end died without a re-handshake (page crash / wc close): drop
|
|
138
|
-
// the reference so send() reports false instead of posting into the void.
|
|
139
|
-
port1.on('close', () => {
|
|
140
|
-
if (activePort === port1)
|
|
141
|
-
dropActivePort(false, 'absent');
|
|
142
|
-
});
|
|
143
|
-
wc.postMessage(ViewChannel.HostToolbarPort, null, [port2]);
|
|
144
|
-
port1.start();
|
|
145
|
-
activePort = port1;
|
|
146
|
-
activeWc = wc;
|
|
147
|
-
state = 'ready';
|
|
148
|
-
generation++;
|
|
149
|
-
// Readiness signal AFTER the port is live: a handler calling send() from
|
|
150
|
-
// inside its onReady fire must observe `true`.
|
|
151
|
-
fireReadyHandlers();
|
|
152
|
-
}
|
|
153
|
-
return {
|
|
154
|
-
attach(wc) {
|
|
155
|
-
wc.on('did-finish-load', () => handshake(wc));
|
|
156
|
-
// Page-initiated navigation (location.href / reload): the document the
|
|
157
|
-
// port lives in is being replaced — invalidate so send() reports false
|
|
158
|
-
// through the navigation window instead of confirming delivery into a
|
|
159
|
-
// document being torn down. TRIPLE guard, because over-invalidating
|
|
160
|
-
// mutes the channel FOREVER (no main-frame did-finish-load follows to
|
|
161
|
-
// re-handshake):
|
|
162
|
-
// - activeWc: a stale wc's late event must not drop a successor's port
|
|
163
|
-
// (same discipline as the 'destroyed' handler below);
|
|
164
|
-
// - cross-document only: anchors/pushState/history keep the document;
|
|
165
|
-
// - main frame only: an <iframe> navigating keeps the main document.
|
|
166
|
-
// Reads the details object (electron >= 12 shape); falls back to the
|
|
167
|
-
// deprecated positional args (`isInPlace` === details.isSameDocument).
|
|
168
|
-
wc.on('did-start-navigation', (details, _url, isInPlace, isMainFramePositional) => {
|
|
169
|
-
if (activeWc !== wc)
|
|
170
|
-
return;
|
|
171
|
-
const isSameDocument = typeof details?.isSameDocument === 'boolean'
|
|
172
|
-
? details.isSameDocument
|
|
173
|
-
: isInPlace;
|
|
174
|
-
const isMainFrame = typeof details?.isMainFrame === 'boolean'
|
|
175
|
-
? details.isMainFrame
|
|
176
|
-
: isMainFramePositional;
|
|
177
|
-
if (isSameDocument || !isMainFrame)
|
|
178
|
-
return;
|
|
179
|
-
dropActivePort(true, 'awaitingHandshake');
|
|
180
|
-
});
|
|
181
|
-
wc.on('destroyed', () => {
|
|
182
|
-
if (activeWc === wc)
|
|
183
|
-
dropActivePort(true, 'absent');
|
|
184
|
-
});
|
|
185
|
-
},
|
|
186
|
-
invalidate() {
|
|
187
|
-
dropActivePort(true, 'awaitingHandshake');
|
|
188
|
-
},
|
|
189
|
-
onMessage(channel, handler) {
|
|
190
|
-
if (typeof channel !== 'string' || channel === '') {
|
|
191
|
-
throw new TypeError('hostToolbar.onMessage: channel must be a non-empty string');
|
|
192
|
-
}
|
|
193
|
-
const entry = { channel, handler };
|
|
194
|
-
handlers.push(entry);
|
|
195
|
-
return {
|
|
196
|
-
dispose() {
|
|
197
|
-
const i = handlers.indexOf(entry);
|
|
198
|
-
if (i >= 0)
|
|
199
|
-
handlers.splice(i, 1);
|
|
200
|
-
},
|
|
201
|
-
};
|
|
202
|
-
},
|
|
203
|
-
onReady(handler) {
|
|
204
|
-
// Torn-down control: inert registration — never fires, never throws.
|
|
205
|
-
if (state === 'disposed') {
|
|
206
|
-
return { dispose() { } };
|
|
207
|
-
}
|
|
208
|
-
const entry = { handler };
|
|
209
|
-
readyHandlers.push(entry);
|
|
210
|
-
if (state === 'ready') {
|
|
211
|
-
// Missed-signal catch-up: the handshake already happened, so the
|
|
212
|
-
// subscriber would otherwise wait forever. Asynchronous on a
|
|
213
|
-
// microtask (never re-enter host code synchronously inside
|
|
214
|
-
// onReady()), and RE-CHECKED at fire time — both the subscription's
|
|
215
|
-
// liveness and the load generation can change between scheduling and
|
|
216
|
-
// the microtask (same-frame dispose() / same-frame loadFile).
|
|
217
|
-
const scheduledGeneration = generation;
|
|
218
|
-
queueMicrotask(() => {
|
|
219
|
-
if (state !== 'ready' || generation !== scheduledGeneration || !activePort)
|
|
220
|
-
return;
|
|
221
|
-
if (!readyHandlers.includes(entry))
|
|
222
|
-
return;
|
|
223
|
-
invokeReadyHandler(entry.handler);
|
|
224
|
-
});
|
|
225
|
-
}
|
|
226
|
-
return {
|
|
227
|
-
dispose() {
|
|
228
|
-
const i = readyHandlers.indexOf(entry);
|
|
229
|
-
if (i >= 0)
|
|
230
|
-
readyHandlers.splice(i, 1);
|
|
231
|
-
},
|
|
232
|
-
};
|
|
233
|
-
},
|
|
234
|
-
send(channel, payload) {
|
|
235
|
-
if (!activePort)
|
|
236
|
-
return false;
|
|
237
|
-
activePort.postMessage({ channel, payload });
|
|
238
|
-
return true;
|
|
239
|
-
},
|
|
240
|
-
dispose() {
|
|
241
|
-
dropActivePort(true, 'absent');
|
|
242
|
-
state = 'disposed';
|
|
243
|
-
handlers.length = 0;
|
|
244
|
-
readyHandlers.length = 0;
|
|
245
|
-
},
|
|
246
|
-
};
|
|
9
|
+
return createHostSlotPortChannel({
|
|
10
|
+
isCurrent: opts.isCurrent,
|
|
11
|
+
channel: ViewChannel.HostToolbarPort,
|
|
12
|
+
logPrefix: '[host-toolbar]',
|
|
13
|
+
});
|
|
247
14
|
}
|
|
248
15
|
//# sourceMappingURL=host-toolbar-port-channel.js.map
|
|
@@ -1,35 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* `
|
|
7
|
-
* strip height would collapse to 0. Instead the runtime bundle
|
|
8
|
-
* (`hostToolbarRuntimePreloadPath`) is registered once per process as a
|
|
9
|
-
* session frame preload; its own guard (`--dimina-host-toolbar` marker +
|
|
10
|
-
* `isMainFrame`) keeps it a zero-footprint no-op in every other defaultSession
|
|
11
|
-
* renderer.
|
|
12
|
-
*
|
|
13
|
-
* Ref-counting: multiple ViewManagers can coexist in one
|
|
14
|
-
* process and share the ONE defaultSession. Each manager acquires at most one
|
|
15
|
-
* reference (on first toolbar need) and releases it in `disposeAll`. Only the
|
|
16
|
-
* first acquire registers; only the last release unregisters (with the id
|
|
17
|
-
* `registerPreloadScript` returned). After a full release a new acquire
|
|
18
|
-
* re-registers — there is deliberately no "registered once ever" latch, so
|
|
19
|
-
* dispose-everything-then-relaunch flows keep their toolbar runtime.
|
|
20
|
-
*
|
|
21
|
-
* Module-level state; tests reset it via `vi.resetModules()` + re-import.
|
|
22
|
-
*/
|
|
23
|
-
/**
|
|
24
|
-
* Take a reference on the shared session registration. Registers the runtime
|
|
25
|
-
* preload on `session.defaultSession` when the count rises from zero. Call at
|
|
26
|
-
* most once per ViewManager (the manager tracks its own acquired-flag).
|
|
2
|
+
* Toolbar-specific instantiation of `createHostSlotSessionRuntime` (see that
|
|
3
|
+
* module for the full ref-counting contract). Kept as a thin, independently
|
|
4
|
+
* named wrapper rather than inlined into `host-toolbar-view.ts` because
|
|
5
|
+
* `host-toolbar-session-preload.test.ts` re-imports this module's state via
|
|
6
|
+
* `vi.resetModules()` per test.
|
|
27
7
|
*/
|
|
28
8
|
export declare function acquireHostToolbarSessionRuntime(): void;
|
|
29
|
-
/**
|
|
30
|
-
* Release a reference. Unregisters (with the stored registration id) only when
|
|
31
|
-
* the LAST reference is released; a still-alive ViewManager's toolbar keeps
|
|
32
|
-
* its session runtime. Safe to call only by managers that actually acquired.
|
|
33
|
-
*/
|
|
34
9
|
export declare function releaseHostToolbarSessionRuntime(): void;
|
|
35
10
|
//# sourceMappingURL=host-toolbar-session-runtime.d.ts.map
|
|
@@ -1,55 +1,17 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* `
|
|
7
|
-
* strip height would collapse to 0. Instead the runtime bundle
|
|
8
|
-
* (`hostToolbarRuntimePreloadPath`) is registered once per process as a
|
|
9
|
-
* session frame preload; its own guard (`--dimina-host-toolbar` marker +
|
|
10
|
-
* `isMainFrame`) keeps it a zero-footprint no-op in every other defaultSession
|
|
11
|
-
* renderer.
|
|
12
|
-
*
|
|
13
|
-
* Ref-counting: multiple ViewManagers can coexist in one
|
|
14
|
-
* process and share the ONE defaultSession. Each manager acquires at most one
|
|
15
|
-
* reference (on first toolbar need) and releases it in `disposeAll`. Only the
|
|
16
|
-
* first acquire registers; only the last release unregisters (with the id
|
|
17
|
-
* `registerPreloadScript` returned). After a full release a new acquire
|
|
18
|
-
* re-registers — there is deliberately no "registered once ever" latch, so
|
|
19
|
-
* dispose-everything-then-relaunch flows keep their toolbar runtime.
|
|
20
|
-
*
|
|
21
|
-
* Module-level state; tests reset it via `vi.resetModules()` + re-import.
|
|
2
|
+
* Toolbar-specific instantiation of `createHostSlotSessionRuntime` (see that
|
|
3
|
+
* module for the full ref-counting contract). Kept as a thin, independently
|
|
4
|
+
* named wrapper rather than inlined into `host-toolbar-view.ts` because
|
|
5
|
+
* `host-toolbar-session-preload.test.ts` re-imports this module's state via
|
|
6
|
+
* `vi.resetModules()` per test.
|
|
22
7
|
*/
|
|
23
|
-
import {
|
|
8
|
+
import { createHostSlotSessionRuntime } from './host-slot-session-runtime.js';
|
|
24
9
|
import { hostToolbarRuntimePreloadPath } from '../../utils/paths.js';
|
|
25
|
-
|
|
26
|
-
let registrationId = null;
|
|
27
|
-
/**
|
|
28
|
-
* Take a reference on the shared session registration. Registers the runtime
|
|
29
|
-
* preload on `session.defaultSession` when the count rises from zero. Call at
|
|
30
|
-
* most once per ViewManager (the manager tracks its own acquired-flag).
|
|
31
|
-
*/
|
|
10
|
+
const runtime = createHostSlotSessionRuntime(hostToolbarRuntimePreloadPath);
|
|
32
11
|
export function acquireHostToolbarSessionRuntime() {
|
|
33
|
-
|
|
34
|
-
registrationId = session.defaultSession.registerPreloadScript({
|
|
35
|
-
type: 'frame',
|
|
36
|
-
filePath: hostToolbarRuntimePreloadPath,
|
|
37
|
-
});
|
|
38
|
-
}
|
|
39
|
-
refCount++;
|
|
12
|
+
runtime.acquire();
|
|
40
13
|
}
|
|
41
|
-
/**
|
|
42
|
-
* Release a reference. Unregisters (with the stored registration id) only when
|
|
43
|
-
* the LAST reference is released; a still-alive ViewManager's toolbar keeps
|
|
44
|
-
* its session runtime. Safe to call only by managers that actually acquired.
|
|
45
|
-
*/
|
|
46
14
|
export function releaseHostToolbarSessionRuntime() {
|
|
47
|
-
|
|
48
|
-
return;
|
|
49
|
-
refCount--;
|
|
50
|
-
if (refCount === 0 && registrationId !== null) {
|
|
51
|
-
session.defaultSession.unregisterPreloadScript(registrationId);
|
|
52
|
-
registrationId = null;
|
|
53
|
-
}
|
|
15
|
+
runtime.release();
|
|
54
16
|
}
|
|
55
17
|
//# sourceMappingURL=host-toolbar-session-runtime.js.map
|