@dimina-kit/devtools 0.3.2-dev.20260611063505 → 0.4.0-dev.20260612025610
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +56 -41
- package/dist/main/api.d.ts +4 -2
- package/dist/main/api.js +5 -1
- package/dist/main/app/app.d.ts +1 -5
- package/dist/main/app/app.js +25 -30
- package/dist/main/app/launch.d.ts +4 -4
- package/dist/main/app/launch.js +5 -17
- package/dist/main/index.bundle.js +535 -593
- package/dist/main/ipc/app.d.ts +1 -1
- package/dist/main/ipc/app.js +0 -7
- package/dist/main/ipc/index.d.ts +0 -2
- package/dist/main/ipc/index.js +0 -2
- package/dist/main/ipc/settings.d.ts +1 -1
- package/dist/main/ipc/settings.js +1 -13
- package/dist/main/ipc/simulator-module.d.ts +3 -3
- package/dist/main/ipc/simulator-module.js +3 -7
- package/dist/main/ipc/simulator.js +1 -12
- package/dist/main/menu/index.d.ts +2 -2
- package/dist/main/menu/index.js +4 -2
- package/dist/main/runtime/miniapp-runtime.d.ts +206 -28
- package/dist/main/runtime/miniapp-runtime.js +6 -11
- package/dist/main/services/layout/index.d.ts +0 -11
- package/dist/main/services/layout/index.js +0 -23
- package/dist/main/services/notifications/renderer-notifier.d.ts +0 -6
- package/dist/main/services/notifications/renderer-notifier.js +1 -10
- package/dist/main/services/service-host-pool/pool.d.ts +5 -4
- package/dist/main/services/views/host-toolbar-port-channel.d.ts +103 -0
- package/dist/main/services/views/host-toolbar-port-channel.js +248 -0
- package/dist/main/services/views/host-toolbar-session-runtime.d.ts +35 -0
- package/dist/main/services/views/host-toolbar-session-runtime.js +55 -0
- package/dist/main/services/views/view-manager.d.ts +80 -39
- package/dist/main/services/views/view-manager.js +153 -182
- package/dist/main/services/workbench-context.d.ts +13 -26
- package/dist/main/services/workbench-context.js +5 -14
- package/dist/main/services/workspace/workspace-service.d.ts +3 -7
- package/dist/main/services/workspace/workspace-service.js +34 -13
- package/dist/main/utils/paths.d.ts +11 -2
- package/dist/main/utils/paths.js +11 -2
- package/dist/main/windows/settings-window/index.d.ts +33 -0
- package/dist/main/windows/settings-window/index.js +59 -0
- package/dist/preload/index.d.ts +15 -0
- package/dist/preload/index.js +15 -0
- package/dist/preload/runtime/host-toolbar-port.d.ts +49 -0
- package/dist/preload/runtime/host-toolbar-port.js +151 -0
- package/dist/preload/runtime/host-toolbar-runtime.d.ts +41 -0
- package/dist/preload/runtime/host-toolbar-runtime.js +49 -0
- package/dist/preload/windows/{host-toolbar.cjs → host-toolbar-runtime.cjs} +103 -3
- package/dist/preload/windows/host-toolbar-runtime.cjs.map +7 -0
- package/dist/preload/windows/host-toolbar-runtime.d.ts +2 -0
- package/dist/preload/windows/host-toolbar-runtime.js +13 -0
- package/dist/preload/windows/simulator.cjs +0 -2
- package/dist/preload/windows/simulator.cjs.map +2 -2
- package/dist/preload/windows/simulator.js +0 -2
- package/dist/renderer/assets/constants-DI382mTP.js +2 -0
- package/dist/renderer/assets/index-DLzjz1wv.js +50 -0
- package/dist/renderer/assets/{input-CFHmbOgz.js → input-6fpCXV-n.js} +2 -2
- package/dist/renderer/assets/ipc-transport-Bs8Sf1B2.css +1 -0
- package/dist/renderer/assets/{ipc-transport-BkKKww3u.js → ipc-transport-D22e4dv7.js} +6 -6
- package/dist/renderer/assets/popover-CdGdKkkc.js +2 -0
- package/dist/renderer/assets/select-X0sbjwdP.js +2 -0
- package/dist/renderer/assets/{settings-Cl4IcWnG.js → settings-CS4jYaCw.js} +2 -2
- package/dist/renderer/assets/settings-api-CCWTAao_.js +2 -0
- package/dist/renderer/assets/workbenchSettings-Bpzaj3bm.js +8 -0
- package/dist/renderer/entries/main/index.html +7 -6
- package/dist/renderer/entries/popover/index.html +6 -6
- package/dist/renderer/entries/settings/index.html +5 -5
- package/dist/renderer/entries/workbench-settings/index.html +5 -5
- package/dist/shared/constants.d.ts +18 -0
- package/dist/shared/constants.js +18 -0
- package/dist/shared/ipc-channels.d.ts +10 -23
- package/dist/shared/ipc-channels.js +13 -34
- package/dist/shared/ipc-schemas.d.ts +0 -12
- package/dist/shared/ipc-schemas.js +0 -12
- package/dist/shared/types.d.ts +60 -76
- package/package.json +6 -6
- package/dist/main/ipc/panels.d.ts +0 -4
- package/dist/main/ipc/panels.js +0 -46
- package/dist/main/ipc/toolbar.d.ts +0 -4
- package/dist/main/ipc/toolbar.js +0 -21
- package/dist/main/services/toolbar/toolbar-store.d.ts +0 -23
- package/dist/main/services/toolbar/toolbar-store.js +0 -40
- package/dist/preload/windows/host-toolbar.cjs.map +0 -7
- package/dist/preload/windows/host-toolbar.d.ts +0 -2
- package/dist/preload/windows/host-toolbar.js +0 -12
- package/dist/renderer/assets/constants-CLAQX6VZ.js +0 -2
- package/dist/renderer/assets/index-CZQOpCvg.js +0 -50
- package/dist/renderer/assets/ipc-transport-Bv_BVu4T.css +0 -1
- package/dist/renderer/assets/popover-h1M7n3tt.js +0 -2
- package/dist/renderer/assets/select-kj0n1oYf.js +0 -2
- package/dist/renderer/assets/settings-api-CCwo2eOi.js +0 -2
- package/dist/renderer/assets/workbenchSettings-BzlpQnve.js +0 -8
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gated narrow channel between the main process and the host-toolbar
|
|
3
|
+
* WebContentsView's page, over a per-load transferred MessagePort.
|
|
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).
|
|
46
|
+
*/
|
|
47
|
+
import type { WebContents } from 'electron';
|
|
48
|
+
/** Handle returned by `onMessage`; `dispose()` detaches (idempotent). */
|
|
49
|
+
export interface HostToolbarMessageSubscription {
|
|
50
|
+
dispose(): void;
|
|
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
|
+
}
|
|
95
|
+
export declare function createHostToolbarPortChannel(opts: {
|
|
96
|
+
/**
|
|
97
|
+
* Is `wc` still the manager's CURRENT live toolbar 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
|
+
}): HostToolbarPortChannel;
|
|
103
|
+
//# sourceMappingURL=host-toolbar-port-channel.d.ts.map
|
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gated narrow channel between the main process and the host-toolbar
|
|
3
|
+
* WebContentsView's page, over a per-load transferred MessagePort.
|
|
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).
|
|
46
|
+
*/
|
|
47
|
+
import { MessageChannelMain } from 'electron';
|
|
48
|
+
import { ViewChannel } from '../../../shared/ipc-channels.js';
|
|
49
|
+
export function createHostToolbarPortChannel(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 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
|
+
};
|
|
247
|
+
}
|
|
248
|
+
//# sourceMappingURL=host-toolbar-port-channel.js.map
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ref-counted registration of the host-toolbar framework runtime on
|
|
3
|
+
* `session.defaultSession`.
|
|
4
|
+
*
|
|
5
|
+
* The toolbar's height advertiser no longer rides the toolbar WCV's
|
|
6
|
+
* `webPreferences.preload` (a host's `setPreloadPath` used to replace it and
|
|
7
|
+
* the strip height collapsed 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 (codex condition 3): 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).
|
|
27
|
+
*/
|
|
28
|
+
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
|
+
export declare function releaseHostToolbarSessionRuntime(): void;
|
|
35
|
+
//# sourceMappingURL=host-toolbar-session-runtime.d.ts.map
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ref-counted registration of the host-toolbar framework runtime on
|
|
3
|
+
* `session.defaultSession`.
|
|
4
|
+
*
|
|
5
|
+
* The toolbar's height advertiser no longer rides the toolbar WCV's
|
|
6
|
+
* `webPreferences.preload` (a host's `setPreloadPath` used to replace it and
|
|
7
|
+
* the strip height collapsed 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 (codex condition 3): 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
|
+
import { session } from 'electron';
|
|
24
|
+
import { hostToolbarRuntimePreloadPath } from '../../utils/paths.js';
|
|
25
|
+
let refCount = 0;
|
|
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
|
+
*/
|
|
32
|
+
export function acquireHostToolbarSessionRuntime() {
|
|
33
|
+
if (refCount === 0) {
|
|
34
|
+
registrationId = session.defaultSession.registerPreloadScript({
|
|
35
|
+
type: 'frame',
|
|
36
|
+
filePath: hostToolbarRuntimePreloadPath,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
refCount++;
|
|
40
|
+
}
|
|
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
|
+
export function releaseHostToolbarSessionRuntime() {
|
|
47
|
+
if (refCount === 0)
|
|
48
|
+
return;
|
|
49
|
+
refCount--;
|
|
50
|
+
if (refCount === 0 && registrationId !== null) {
|
|
51
|
+
session.defaultSession.unregisterPreloadScript(registrationId);
|
|
52
|
+
registrationId = null;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=host-toolbar-session-runtime.js.map
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { WebContents } from 'electron';
|
|
2
2
|
import type { NativeDeviceInfo } from '../../../shared/ipc-channels.js';
|
|
3
3
|
import { type WorkbenchContext } from '../workbench-context.js';
|
|
4
|
+
import { type HostToolbarMessageSubscription } from './host-toolbar-port-channel.js';
|
|
4
5
|
/**
|
|
5
6
|
* Context surface used by the ViewManager. We only need a small slice of the
|
|
6
7
|
* full WorkbenchContext here; typing it this way documents the actual dependency.
|
|
@@ -23,12 +24,11 @@ export interface ViewManagerContext {
|
|
|
23
24
|
* frame preload instead. Optional so partial test contexts compile.
|
|
24
25
|
*/
|
|
25
26
|
preloadPath?: string;
|
|
26
|
-
panels: string[];
|
|
27
27
|
notify: WorkbenchContext['notify'];
|
|
28
28
|
bridge?: WorkbenchContext['bridge'];
|
|
29
29
|
/**
|
|
30
30
|
* Native-host network forwarder. `attachNativeSimulator` hands it the freshly
|
|
31
|
-
* created simulator WebContentsView (`attachSimulator`) so it can attach the
|
|
31
|
+
* created simulator WebContentsView (`networkForward.attachSimulator`) so it can attach the
|
|
32
32
|
* CDP debugger, and the DevTools front-end host wc (`setDevtoolsHost`) so it can
|
|
33
33
|
* inject that WCV's Network.* events into the native Network tab (service-host
|
|
34
34
|
* console line is the fallback). Optional so partial test contexts compile.
|
|
@@ -43,12 +43,6 @@ export interface ViewManagerContext {
|
|
|
43
43
|
* Optional so partial test contexts compile.
|
|
44
44
|
*/
|
|
45
45
|
simulatorApis?: WorkbenchContext['simulatorApis'];
|
|
46
|
-
/**
|
|
47
|
-
* Header bar height in px, used to position overlay views below the header.
|
|
48
|
-
* Optional here so partial test contexts compile; `createWorkbenchContext`
|
|
49
|
-
* always supplies it (default 40).
|
|
50
|
-
*/
|
|
51
|
-
headerHeight?: number;
|
|
52
46
|
}
|
|
53
47
|
/**
|
|
54
48
|
* Unified lifecycle manager for Electron WebContentsView overlays.
|
|
@@ -64,23 +58,22 @@ export interface ViewManagerContext {
|
|
|
64
58
|
* window's own renderer is the content root and is not managed here.
|
|
65
59
|
*/
|
|
66
60
|
export interface ViewManager {
|
|
67
|
-
/**
|
|
68
|
-
* Create a DevTools view for the given simulator webContents and add it to
|
|
69
|
-
* the main window contentView (only when the `devtools` tab is the default
|
|
70
|
-
* tab; otherwise the view is created but not added yet).
|
|
71
|
-
*/
|
|
72
|
-
attachSimulator(simWcId: number, simWidth: number): void;
|
|
73
61
|
/**
|
|
74
62
|
* NATIVE-HOST ONLY. Create the simulator itself as a top-level
|
|
75
63
|
* WebContentsView (not a renderer `<webview>` guest) loading `simulatorUrl`,
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
80
|
-
*
|
|
64
|
+
* and treat its webContents as THE simulator webContents (so
|
|
65
|
+
* `getSimulatorWebContents` resolves it and the spawn/SIMULATOR_EVENTS
|
|
66
|
+
* pipeline flows through it). This is required because Electron
|
|
67
|
+
* force-disables the `<webview>` tag inside a webview guest, so a
|
|
68
|
+
* `<webview>`-in-`<webview>` topology can never host DeviceShell's
|
|
81
69
|
* per-page render-host `<webview>`s. A top-level WebContentsView's webContents
|
|
82
70
|
* is NOT a guest and CAN host them. Then wires the DevTools/console view on
|
|
83
|
-
* top of it via `
|
|
71
|
+
* top of it via `attachNativeSimulatorDevtoolsHost`. Neither view is added
|
|
72
|
+
* to the contentView here — both mount only when the renderer's view anchors
|
|
73
|
+
* publish a non-zero rect (`setNativeSimulatorViewBounds` /
|
|
74
|
+
* `setSimulatorDevtoolsBounds`). No-op (logs) when `preloadPath` is unset.
|
|
75
|
+
* `simWidth` rides the wire for schema compatibility but is unused: all
|
|
76
|
+
* geometry is anchor-published.
|
|
84
77
|
*/
|
|
85
78
|
attachNativeSimulator(simulatorUrl: string, simWidth: number): void;
|
|
86
79
|
/**
|
|
@@ -90,10 +83,6 @@ export interface ViewManager {
|
|
|
90
83
|
* `windows/views.ts` module, which every detach call relied on.
|
|
91
84
|
*/
|
|
92
85
|
detachSimulator(): void;
|
|
93
|
-
/** Reveal the existing DevTools view (idempotent). */
|
|
94
|
-
showSimulator(simWidth: number): void;
|
|
95
|
-
/** Remove (but do not destroy) the simulator view from the contentView. */
|
|
96
|
-
hideSimulator(): void;
|
|
97
86
|
/** Lazy-create and show the settings overlay view. */
|
|
98
87
|
showSettings(): Promise<void>;
|
|
99
88
|
/** Remove the settings overlay view (kept around for next open). */
|
|
@@ -110,12 +99,6 @@ export interface ViewManager {
|
|
|
110
99
|
getSimulatorWebContentsId(): number | null;
|
|
111
100
|
/** Return the live webContents of the currently attached simulator, or null. */
|
|
112
101
|
getSimulatorWebContents(): WebContents | null;
|
|
113
|
-
/** Return the last known simulator width. */
|
|
114
|
-
getLastSimWidth(): number;
|
|
115
|
-
/** Whether the simulator overlay is currently added to the contentView. */
|
|
116
|
-
isSimulatorAdded(): boolean;
|
|
117
|
-
/** Whether a DevTools view exists (created but maybe not added). */
|
|
118
|
-
hasSimulatorView(): boolean;
|
|
119
102
|
/** Return the settings overlay's WebContents (for renderer-notifier). */
|
|
120
103
|
getSettingsWebContents(): WebContents | null;
|
|
121
104
|
/** Return the webContents ID of the settings overlay if alive, else null. */
|
|
@@ -151,10 +134,12 @@ export interface ViewManager {
|
|
|
151
134
|
* pick it up automatically when they attach (`did-attach-webview`).
|
|
152
135
|
*/
|
|
153
136
|
reapplySafeArea(device: NativeDeviceInfo | null): void;
|
|
154
|
-
/**
|
|
137
|
+
/**
|
|
138
|
+
* Window-resize entry point. Re-applies the settings overlay's bounds only.
|
|
139
|
+
* Simulator + DevTools overlay geometry is anchor-published (the renderer's
|
|
140
|
+
* ResizeObserver re-measures on the same resize), so no static re-apply here.
|
|
141
|
+
*/
|
|
155
142
|
resize(simWidth: number): void;
|
|
156
|
-
/** Show or hide the simulator overlay based on visibility flag. */
|
|
157
|
-
setVisible(visible: boolean, simWidth: number): void;
|
|
158
143
|
/**
|
|
159
144
|
* Apply a renderer-measured rectangle to the simulator's Chromium
|
|
160
145
|
* DevTools overlay view. `{ width: 0, height: 0 }` is treated as "hide" —
|
|
@@ -185,7 +170,9 @@ export interface ViewManager {
|
|
|
185
170
|
* Reverse size-advertiser sink: the toolbar WCV's own renderer advertises
|
|
186
171
|
* its intrinsic content height (block-axis extent); we store it and push it
|
|
187
172
|
* to the main-window renderer so the placeholder div resizes (closing the
|
|
188
|
-
* dynamic-height loop).
|
|
173
|
+
* dynamic-height loop). Ignored while a `{ fixed }` height mode is pinned
|
|
174
|
+
* via `hostToolbar.setHeightMode` (the session-resident advertiser always
|
|
175
|
+
* runs, so its reports must not fight a host-pinned height).
|
|
189
176
|
*/
|
|
190
177
|
setHostToolbarHeight(extent: number): void;
|
|
191
178
|
/**
|
|
@@ -195,6 +182,14 @@ export interface ViewManager {
|
|
|
195
182
|
*/
|
|
196
183
|
readonly hostToolbar: HostToolbarControl;
|
|
197
184
|
}
|
|
185
|
+
/**
|
|
186
|
+
* Height mode for the host-toolbar placeholder strip. `'auto'` (default): the
|
|
187
|
+
* session-resident advertiser's reports drive the height. `{ fixed }`: the
|
|
188
|
+
* host pins the height; advertiser reports are ignored until `'auto'` again.
|
|
189
|
+
*/
|
|
190
|
+
export type HostToolbarHeightMode = 'auto' | {
|
|
191
|
+
fixed: number;
|
|
192
|
+
};
|
|
198
193
|
/**
|
|
199
194
|
* The control object the downstream host uses to own the toolbar
|
|
200
195
|
* WebContentsView. Lazily backed by the view-manager's `hostToolbarView`.
|
|
@@ -209,13 +204,59 @@ export interface HostToolbarControl {
|
|
|
209
204
|
/** Remove the toolbar view from the contentView and reset it (kept alive). */
|
|
210
205
|
hide(): void;
|
|
211
206
|
/**
|
|
212
|
-
*
|
|
213
|
-
*
|
|
214
|
-
* `
|
|
215
|
-
* `
|
|
216
|
-
*
|
|
207
|
+
* The HOST's own `webPreferences.preload` for the toolbar view (purely
|
|
208
|
+
* additive). The framework's height-advertiser runtime does NOT ride
|
|
209
|
+
* `webPreferences.preload` — it is session-resident (registered on
|
|
210
|
+
* `session.defaultSession`, self-guarded by the `--dimina-host-toolbar`
|
|
211
|
+
* marker + `isMainFrame`), so a host preload set here coexists with it and
|
|
212
|
+
* never replaces it. Must be set before the view is (re)created (first
|
|
213
|
+
* `loadURL`/`loadFile`, or the next one after the host closed the
|
|
214
|
+
* webContents); `null` (default) means "no host preload" — it does not and
|
|
215
|
+
* cannot restore any built-in preload.
|
|
217
216
|
*/
|
|
218
217
|
setPreloadPath(path: string | null): void;
|
|
218
|
+
/**
|
|
219
|
+
* Register a host-side handler for messages the toolbar PAGE sends via
|
|
220
|
+
* `window.diminaHostToolbar.send(channel, payload)`. Control-level: may be
|
|
221
|
+
* called before the view exists and survives page reloads / wc rebuilds
|
|
222
|
+
* (each per-load MessagePort handshake re-attaches the registry to the new
|
|
223
|
+
* port). Throws on an empty / non-string channel. `dispose()` detaches
|
|
224
|
+
* (idempotent).
|
|
225
|
+
*/
|
|
226
|
+
onMessage(channel: string, handler: (payload: unknown) => void): HostToolbarMessageSubscription;
|
|
227
|
+
/**
|
|
228
|
+
* Observe handshake readiness — the push counterpart to polling `send()`
|
|
229
|
+
* for `true`. Fires the handler once per load generation, exactly when
|
|
230
|
+
* that load's MessagePort handshake completes; registering while the
|
|
231
|
+
* channel is ALREADY ready fires once asynchronously on a microtask
|
|
232
|
+
* (missed-signal race guard, re-validated at fire time). A reload /
|
|
233
|
+
* re-handshake fires registered handlers again; a host-initiated
|
|
234
|
+
* `loadURL`/`loadFile` invalidates readiness at initiation, so handlers
|
|
235
|
+
* registered in that window wait for the NEW document's handshake.
|
|
236
|
+
* `dispose()` detaches (idempotent); `disposeAll` sweeps everything.
|
|
237
|
+
*/
|
|
238
|
+
onReady(handler: () => void): HostToolbarMessageSubscription;
|
|
239
|
+
/**
|
|
240
|
+
* Post `{ channel, payload }` to the toolbar page (received via
|
|
241
|
+
* `window.diminaHostToolbar.onMessage(channel, handler)`). Gated and
|
|
242
|
+
* non-queueing: returns false — delivering nothing, creating no view —
|
|
243
|
+
* while there is no live toolbar webContents, the current load's
|
|
244
|
+
* MessagePort handshake hasn't completed, or a document-replacing
|
|
245
|
+
* navigation is in flight (`loadURL`/`loadFile` was issued, or the page
|
|
246
|
+
* itself started a main-frame cross-document navigation, and the new
|
|
247
|
+
* document hasn't handshaked yet); true once the envelope went out.
|
|
248
|
+
* No manual `getHostToolbarWebContentsId` gating needed: the false/true
|
|
249
|
+
* result IS the readiness signal.
|
|
250
|
+
*/
|
|
251
|
+
send(channel: string, payload: unknown): boolean;
|
|
252
|
+
/**
|
|
253
|
+
* Pin or unpin the toolbar strip height. `{ fixed }` notifies the renderer
|
|
254
|
+
* placeholder with that height immediately (so a preload-less/static toolbar
|
|
255
|
+
* is visible without any advertiser report) and ignores subsequent advertiser
|
|
256
|
+
* reports. `'auto'` (default) re-enables advertiser-driven height starting
|
|
257
|
+
* from the NEXT report — it does not synthesize/replay a stale height.
|
|
258
|
+
*/
|
|
259
|
+
setHeightMode(mode: HostToolbarHeightMode): void;
|
|
219
260
|
}
|
|
220
261
|
/**
|
|
221
262
|
* Build a ViewManager bound to the given context. The returned object is the
|