@dimina-kit/devtools 0.4.0-dev.20260701155140 → 0.4.0-dev.20260702145840
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/app/max-listeners-diagnostic.js +5 -1
- package/dist/main/index.bundle.js +1532 -1236
- package/dist/main/ipc/bridge-router.d.ts +28 -0
- package/dist/main/ipc/bridge-router.js +175 -50
- package/dist/main/ipc/session-listener-bag.d.ts +41 -0
- package/dist/main/ipc/session-listener-bag.js +40 -0
- package/dist/main/ipc/simulator.js +5 -1
- package/dist/main/services/elements-forward/index.js +19 -0
- package/dist/main/services/network-forward/index.js +13 -0
- package/dist/main/services/views/destroy-child-view.d.ts +9 -0
- package/dist/main/services/views/destroy-child-view.js +23 -0
- package/dist/main/services/views/host-toolbar-view.d.ts +21 -0
- package/dist/main/services/views/host-toolbar-view.js +249 -0
- package/dist/main/services/views/inject-when-ready.d.ts +41 -0
- package/dist/main/services/views/inject-when-ready.js +62 -0
- package/dist/main/services/views/native-simulator-devtools-host.d.ts +26 -0
- package/dist/main/services/views/native-simulator-devtools-host.js +484 -0
- package/dist/main/services/views/native-simulator-view.d.ts +28 -0
- package/dist/main/services/views/native-simulator-view.js +428 -0
- package/dist/main/services/views/overlay-panels-view.d.ts +29 -0
- package/dist/main/services/views/overlay-panels-view.js +150 -0
- package/dist/main/services/views/placement-reconciler.d.ts +73 -0
- package/dist/main/services/views/placement-reconciler.js +120 -0
- package/dist/main/services/views/resolve-project-editor-target.d.ts +13 -0
- package/dist/main/services/views/resolve-project-editor-target.js +33 -0
- package/dist/main/services/views/view-manager.d.ts +20 -16
- package/dist/main/services/views/view-manager.js +66 -1545
- package/dist/main/services/views/workbench-view.d.ts +15 -0
- package/dist/main/services/views/workbench-view.js +170 -0
- package/dist/preload/windows/host-toolbar-runtime.cjs.map +2 -2
- package/dist/preload/windows/main.cjs +10 -1
- package/dist/preload/windows/main.cjs.map +2 -2
- package/dist/preload/windows/simulator.cjs +6 -0
- package/dist/preload/windows/simulator.cjs.map +2 -2
- package/dist/preload/windows/simulator.js +6 -0
- package/dist/renderer/assets/index-ChF9V4r-.js +49 -0
- package/dist/renderer/assets/{input-Djg2NR4U.js → input-Cjk0wSEh.js} +2 -2
- package/dist/renderer/assets/ipc-transport-D5dKIti1.css +1 -0
- package/dist/renderer/assets/{ipc-transport-Be-AzCub.js → ipc-transport-OZS-KmOW.js} +2 -2
- package/dist/renderer/assets/{popover-eDUh4NYe.js → popover-CQVvOe90.js} +2 -2
- package/dist/renderer/assets/{select-DesFfkVV.js → select-BQDXi5ih.js} +2 -2
- package/dist/renderer/assets/{settings-api-HQ7ZFPWi.js → settings-api-B6x2mhaD.js} +2 -2
- package/dist/renderer/assets/{settings-CRmO7zUG.js → settings-tP3Px2KR.js} +2 -2
- package/dist/renderer/assets/{workbenchSettings-CZBWsliT.js → workbenchSettings-Bim9ol9R.js} +2 -2
- package/dist/renderer/entries/main/index.html +6 -6
- package/dist/renderer/entries/popover/index.html +5 -5
- package/dist/renderer/entries/settings/index.html +5 -5
- package/dist/renderer/entries/workbench-settings/index.html +4 -4
- package/dist/shared/bridge-channels.d.ts +17 -0
- package/dist/shared/bridge-channels.js +9 -0
- package/dist/shared/ipc-channels.d.ts +1 -0
- package/dist/shared/ipc-channels.js +6 -0
- package/dist/shared/ipc-schemas.d.ts +6 -0
- package/dist/shared/ipc-schemas.js +10 -0
- package/dist/simulator/assets/{bridge-channels-BUQ5AbvJ.js → bridge-channels-zhsumfky.js} +2 -2
- package/dist/simulator/assets/{device-shell-CT3LRnTB.js → device-shell-CUl0ILfn.js} +2 -2
- package/dist/simulator/assets/{simulator-BizhGGE3.js → simulator-Dz8iGcgy.js} +5 -5
- package/dist/simulator/assets/{simulator-mini-app-BfNuDF3R.js → simulator-mini-app-Bt639XL_.js} +2 -2
- package/dist/simulator/simulator.html +2 -1
- package/package.json +5 -5
- package/dist/renderer/assets/index-jDrNHghH.js +0 -49
- package/dist/renderer/assets/ipc-transport-C6-KPMqw.css +0 -1
|
@@ -0,0 +1,484 @@
|
|
|
1
|
+
import { WebContentsView } from 'electron';
|
|
2
|
+
import { buildDevtoolsProjectSourceLinksScript, decodeOpenInEditorUrl, projectSourceContextFromServiceHostUrl, } from '../../../shared/open-in-editor.js';
|
|
3
|
+
import { buildCustomizeTabsScript } from './devtools-tabs.js';
|
|
4
|
+
import { installElementsForward } from '../elements-forward/index.js';
|
|
5
|
+
import { installServiceConsoleForward } from '../service-console/index.js';
|
|
6
|
+
import { VIEW_ID } from '../../../shared/view-ids.js';
|
|
7
|
+
import { resolveProjectEditorTarget } from './resolve-project-editor-target.js';
|
|
8
|
+
import { destroyChildView } from './destroy-child-view.js';
|
|
9
|
+
import { createLoadDeferredInjector } from './inject-when-ready.js';
|
|
10
|
+
// `webContents.executeJavaScript()` registers a transient `did-stop-loading`
|
|
11
|
+
// listener on the target wc whenever that wc is still loading (Electron's
|
|
12
|
+
// `waitTillCanExecuteJavaScript` defers the eval until load finishes, then
|
|
13
|
+
// removes the listener). The DevTools front-end host wc takes a burst of injects
|
|
14
|
+
// during its boot window — tab customization, default-panel selection, the 150ms
|
|
15
|
+
// Elements-forward reconcile, and Network-forward probing all call
|
|
16
|
+
// `executeJavaScript()` on it before the front-end has finished loading — so the
|
|
17
|
+
// pending listeners can transiently exceed Node's default EventEmitter ceiling
|
|
18
|
+
// of 10 and print a spurious `MaxListenersExceededWarning: 11 did-stop-loading
|
|
19
|
+
// listeners`. They drain once the wc stops loading; this is a boot-time
|
|
20
|
+
// concurrency spike, not a per-event leak. Raise the ceiling on the DevTools
|
|
21
|
+
// host wc, which we knowingly inject into repeatedly during its boot window, so
|
|
22
|
+
// the benign spike stays quiet. (Confirmed at runtime: the tripped emitter is
|
|
23
|
+
// this host wc's `did-stop-loading`, count 11 — see the `process.on('warning')`
|
|
24
|
+
// diagnostic in app bootstrap.)
|
|
25
|
+
const DEVTOOLS_HOST_EXEC_JS_MAX_LISTENERS = 50;
|
|
26
|
+
function raiseExecuteJavaScriptListenerCeiling(wc) {
|
|
27
|
+
try {
|
|
28
|
+
if (wc.isDestroyed())
|
|
29
|
+
return;
|
|
30
|
+
if (wc.getMaxListeners() >= DEVTOOLS_HOST_EXEC_JS_MAX_LISTENERS)
|
|
31
|
+
return;
|
|
32
|
+
wc.setMaxListeners(DEVTOOLS_HOST_EXEC_JS_MAX_LISTENERS);
|
|
33
|
+
}
|
|
34
|
+
catch { /* torn-down / stub wc — best effort, warning is non-fatal */ }
|
|
35
|
+
}
|
|
36
|
+
export function createDevtoolsHost(ctx, reconciler, deps) {
|
|
37
|
+
// The right-panel Chrome DevTools front-end HOST WebContentsView. Created by
|
|
38
|
+
// rebuildDevtoolsHostView; positioned by the renderer's simulatorDevtools anchor.
|
|
39
|
+
let simulatorView = null;
|
|
40
|
+
let simulatorViewAdded = false;
|
|
41
|
+
// The webContents the right-panel Chrome DevTools front-end currently inspects.
|
|
42
|
+
// We point it at the SERVICE HOST (logic layer) — the hidden BrowserWindow
|
|
43
|
+
// where the mini-app's page code runs (`console.log`, `wx.request`,
|
|
44
|
+
// Sources/Network(fetch) all live there). The UI/view layer (Elements/Styles/
|
|
45
|
+
// WXML tree) is served separately by the native WXML panel + render-guest
|
|
46
|
+
// highlight chain, so a single DevTools front-end is enough. The service window
|
|
47
|
+
// can be swapped on respawn (pre-warm pool recycles it), so this is re-resolved
|
|
48
|
+
// fresh via `ctx.bridge.getServiceWc()` and re-pointed on every render-side event.
|
|
49
|
+
let nativeDevtoolsSourceWc = null;
|
|
50
|
+
// `setDevToolsWebContents` requires its host argument to have never navigated
|
|
51
|
+
// (Electron contract). The front-end host wc navigates the moment it is first
|
|
52
|
+
// pointed at a service wc, so it is a one-shot resource: once used, re-pointing
|
|
53
|
+
// to a different (pool-swapped) service wc must rebuild the host
|
|
54
|
+
// (`rebuildDevtoolsHostView`) rather than target it again.
|
|
55
|
+
let devtoolsHostUsed = false;
|
|
56
|
+
let unsubscribeNativeRenderEvents = null;
|
|
57
|
+
// Disposer for the Elements-forward feature (routes the front-end's Elements
|
|
58
|
+
// CDP traffic at the active render guest). Installed in
|
|
59
|
+
// `attach`, stopped on detach / host destroyed.
|
|
60
|
+
let stopElementsForward = null;
|
|
61
|
+
// Disposer for the service-layer console capture (CDP `consoleAPICalled` on the
|
|
62
|
+
// service host wc → console fan-out). Installed when the DevTools is pointed at
|
|
63
|
+
// a service host, stopped when that source is closed / swapped.
|
|
64
|
+
let stopServiceConsole = null;
|
|
65
|
+
let nativeDevtoolsRetryTimer = null;
|
|
66
|
+
let nativeDevtoolsRetryToken = 0;
|
|
67
|
+
// Front-end injects re-fire on EVERY re-point; while the host is loading each
|
|
68
|
+
// raw executeJavaScript would queue one did-stop-loading waiter per call.
|
|
69
|
+
// The deferred injector keeps one hook per (wc, kind) with latest-wins.
|
|
70
|
+
const injectWhenReady = createLoadDeferredInjector();
|
|
71
|
+
// The open-in-editor: click a console file link → workbench. The right-panel
|
|
72
|
+
// console is the embedded Chromium DevTools front-end. Once a sourcemap maps a
|
|
73
|
+
// console frame back to source, we redirect a source-link click to OUR
|
|
74
|
+
// workbench editor instead of the DevTools Sources panel.
|
|
75
|
+
const openInEditorWiredWcIds = new Set();
|
|
76
|
+
function clearNativeDevtoolsRetry() {
|
|
77
|
+
nativeDevtoolsRetryToken++;
|
|
78
|
+
if (nativeDevtoolsRetryTimer) {
|
|
79
|
+
clearTimeout(nativeDevtoolsRetryTimer);
|
|
80
|
+
nativeDevtoolsRetryTimer = null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
function closeNativeDevtoolsSource() {
|
|
84
|
+
const source = nativeDevtoolsSourceWc;
|
|
85
|
+
nativeDevtoolsSourceWc = null;
|
|
86
|
+
try {
|
|
87
|
+
stopServiceConsole?.();
|
|
88
|
+
}
|
|
89
|
+
catch { /* already stopped */ }
|
|
90
|
+
stopServiceConsole = null;
|
|
91
|
+
if (!source || source.isDestroyed())
|
|
92
|
+
return;
|
|
93
|
+
try {
|
|
94
|
+
if (source.isDevToolsOpened()) {
|
|
95
|
+
source.closeDevTools();
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
catch { /* source may be mid-destroy */ }
|
|
99
|
+
}
|
|
100
|
+
function stopFollowingNativeServiceHost() {
|
|
101
|
+
clearNativeDevtoolsRetry();
|
|
102
|
+
if (unsubscribeNativeRenderEvents) {
|
|
103
|
+
try {
|
|
104
|
+
unsubscribeNativeRenderEvents();
|
|
105
|
+
}
|
|
106
|
+
catch { /* ignore */ }
|
|
107
|
+
unsubscribeNativeRenderEvents = null;
|
|
108
|
+
}
|
|
109
|
+
closeNativeDevtoolsSource();
|
|
110
|
+
}
|
|
111
|
+
function injectOpenResourceHandler(serviceWc, devtoolsWc) {
|
|
112
|
+
// Inject the front-end glue that routes a project source-link click to our
|
|
113
|
+
// Monaco editor instead of the DevTools Sources panel: a capture-phase click
|
|
114
|
+
// interceptor re-emits an encoded sentinel via
|
|
115
|
+
// `InspectorFrontendHost.openInNewTab` → Electron `devtools-open-url`. (The
|
|
116
|
+
// legacy `setOpenResourceHandler` hook this used to rely on is gone in
|
|
117
|
+
// current Chromium, so the script keeps it only as a fallback.)
|
|
118
|
+
//
|
|
119
|
+
// Destroy-race chokepoint: this runs from a deferred `dom-ready` callback
|
|
120
|
+
// (see wireOpenInEditor), by which time the inspected service-host wc may
|
|
121
|
+
// already be torn down (pool swap / project close / relaunch). Reading
|
|
122
|
+
// `serviceWc.getURL()` on a destroyed wc throws "Object has been destroyed"
|
|
123
|
+
// synchronously, which would escape the event listener. Guard both wc and
|
|
124
|
+
// wrap the body so a teardown mid-call degrades silently (mirrors
|
|
125
|
+
// customizeDevtoolsTabs).
|
|
126
|
+
if (serviceWc.isDestroyed() || devtoolsWc.isDestroyed())
|
|
127
|
+
return;
|
|
128
|
+
try {
|
|
129
|
+
const sourceContext = projectSourceContextFromServiceHostUrl(serviceWc.getURL(), ctx.workspace?.getProjectPath?.());
|
|
130
|
+
if (!sourceContext)
|
|
131
|
+
return;
|
|
132
|
+
devtoolsWc.executeJavaScript(buildDevtoolsProjectSourceLinksScript(sourceContext)).catch(() => { });
|
|
133
|
+
}
|
|
134
|
+
catch { /* wc torn down mid-call — degrade silently */ }
|
|
135
|
+
}
|
|
136
|
+
// ── DevTools tab customization: keep only Elements/Console/Network ──────────
|
|
137
|
+
// Inject into the same DevTools front-end host wc that the console/network
|
|
138
|
+
// injectors target. Reorders Elements/Console/Network to the front and closes
|
|
139
|
+
// every other panel tab by driving the front-end's UI.ViewManager /
|
|
140
|
+
// InspectorView.tabbedLocation (see ./devtools-tabs.ts). Best-effort: the
|
|
141
|
+
// injected script bounded-polls for the lazily-registered panels, wraps
|
|
142
|
+
// everything in try/catch, and silently no-ops if the API never appears —
|
|
143
|
+
// DevTools default tab behaviour is preserved on any failure. Re-injected on
|
|
144
|
+
// every (re)point so a re-`openDevTools` (service-host pool swap) re-applies.
|
|
145
|
+
function customizeDevtoolsTabs(devtoolsWc) {
|
|
146
|
+
try {
|
|
147
|
+
if (devtoolsWc.isDestroyed())
|
|
148
|
+
return;
|
|
149
|
+
const inject = () => {
|
|
150
|
+
if (devtoolsWc.isDestroyed())
|
|
151
|
+
return;
|
|
152
|
+
try {
|
|
153
|
+
void devtoolsWc.executeJavaScript(buildCustomizeTabsScript()).catch(() => { });
|
|
154
|
+
}
|
|
155
|
+
catch { /* wc torn down mid-call */ }
|
|
156
|
+
};
|
|
157
|
+
injectWhenReady(devtoolsWc, 'customize-tabs', inject);
|
|
158
|
+
}
|
|
159
|
+
catch { /* wc surface incomplete / torn down; degrade silently */ }
|
|
160
|
+
}
|
|
161
|
+
function wireOpenInEditor(serviceWc, devtoolsWc) {
|
|
162
|
+
// Inject the front-end handler on every (re)attach — the DevTools front-end
|
|
163
|
+
// wc is recreated per simulatorView, so a fresh host needs the handler set.
|
|
164
|
+
if (!devtoolsWc.isDestroyed()) {
|
|
165
|
+
injectWhenReady(devtoolsWc, 'open-in-editor', () => injectOpenResourceHandler(serviceWc, devtoolsWc));
|
|
166
|
+
}
|
|
167
|
+
// Attach the `devtools-open-url` decoder to the inspected service wc once
|
|
168
|
+
// (the listener is keyed off the encoded sentinel scheme, so it only acts on
|
|
169
|
+
// OUR redirected links; any other devtools "open in new tab" falls through).
|
|
170
|
+
if (openInEditorWiredWcIds.has(serviceWc.id))
|
|
171
|
+
return;
|
|
172
|
+
openInEditorWiredWcIds.add(serviceWc.id);
|
|
173
|
+
const onOpenUrl = (_event, url) => {
|
|
174
|
+
const req = decodeOpenInEditorUrl(url);
|
|
175
|
+
if (!req)
|
|
176
|
+
return; // not our sentinel — leave it to Electron's default path
|
|
177
|
+
const target = resolveProjectEditorTarget(serviceWc.getURL(), ctx.workspace?.getProjectPath?.(), req);
|
|
178
|
+
if (!target)
|
|
179
|
+
return;
|
|
180
|
+
// Drive the workbench WCV (the sole editor) when it is attached, AND
|
|
181
|
+
// always emit `editor:openFile` — it carries the editor-agnostic mapping
|
|
182
|
+
// (project-relative path + 1-based line) that downstream/consumers (and the
|
|
183
|
+
// open-in-editor contract test) observe; with Monaco gone it has no
|
|
184
|
+
// renderer subscriber, so emitting it is harmless when the workbench
|
|
185
|
+
// handles the actual reveal.
|
|
186
|
+
deps.openFileInWorkbench(target.path, target.line ?? 1, target.column ?? 1);
|
|
187
|
+
ctx.notify.editorOpenFile(target);
|
|
188
|
+
};
|
|
189
|
+
serviceWc.on('devtools-open-url', onOpenUrl);
|
|
190
|
+
// Consolidate teardown onto the connection layer,
|
|
191
|
+
// but as a wc-LIFETIME resource: the open-in-editor wiring inspects this
|
|
192
|
+
// service-host wc and must SURVIVE pool reuse (`reset`) — the dedup set
|
|
193
|
+
// entry + listener stay valid across sessions reusing the same wc, and the
|
|
194
|
+
// early-return dedup above correctly skips re-wiring after a reset. So we
|
|
195
|
+
// register on `'closed'` (fires only on real wc destroy), NOT `own()`
|
|
196
|
+
// (which also fires on `reset` and would leave the wc un-wired after reuse
|
|
197
|
+
// because re-pointing the same wc.id early-returns). acquire() is
|
|
198
|
+
// idempotent — bridge-router owning session-scoped resources on the same
|
|
199
|
+
// serviceWc connection coexists cleanly.
|
|
200
|
+
const conn = ctx.connections.acquire(serviceWc);
|
|
201
|
+
conn.on('closed', () => {
|
|
202
|
+
openInEditorWiredWcIds.delete(serviceWc.id);
|
|
203
|
+
try {
|
|
204
|
+
serviceWc.removeListener('devtools-open-url', onOpenUrl);
|
|
205
|
+
}
|
|
206
|
+
catch { /* wc gone */ }
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
// Point the right-panel Chrome DevTools front-end at `next` — the SERVICE HOST
|
|
210
|
+
// webContents (logic layer). Idempotent: if we already inspect this wc, no-op.
|
|
211
|
+
function pointNativeDevtoolsAtServiceWc(next) {
|
|
212
|
+
if (!simulatorView || simulatorView.webContents.isDestroyed())
|
|
213
|
+
return true;
|
|
214
|
+
if (nativeDevtoolsSourceWc?.id === next.id && !nativeDevtoolsSourceWc.isDestroyed()) {
|
|
215
|
+
return true;
|
|
216
|
+
}
|
|
217
|
+
closeNativeDevtoolsSource();
|
|
218
|
+
nativeDevtoolsSourceWc = next;
|
|
219
|
+
// The host wc is a one-shot `setDevToolsWebContents` target (Electron
|
|
220
|
+
// forbids re-pointing at an already-navigated host): once used, a
|
|
221
|
+
// service-wc swap must repoint onto a freshly rebuilt host instead.
|
|
222
|
+
if (devtoolsHostUsed) {
|
|
223
|
+
rebuildDevtoolsHostView();
|
|
224
|
+
}
|
|
225
|
+
if (!simulatorView || simulatorView.webContents.isDestroyed())
|
|
226
|
+
return true;
|
|
227
|
+
try {
|
|
228
|
+
next.setDevToolsWebContents(simulatorView.webContents);
|
|
229
|
+
// DevTools renders into the right-panel host view (simulatorView); with a
|
|
230
|
+
// custom host the `mode` is overridden, and `activate:false` prevents it
|
|
231
|
+
// stealing focus — this re-points whenever the service window is swapped,
|
|
232
|
+
// so a focusing window would yank focus repeatedly (disrupting the user /
|
|
233
|
+
// e2e).
|
|
234
|
+
next.openDevTools({ mode: 'detach', activate: false });
|
|
235
|
+
// The host is factually USED the moment it navigates (openDevTools) —
|
|
236
|
+
// NOT when the whole wiring below succeeds. Flagging late lies to the
|
|
237
|
+
// retry path: a mid-wiring throw (service wc torn down mid-point) left
|
|
238
|
+
// the flag false, so the 50ms follow-retry re-pointed the SAME navigated
|
|
239
|
+
// host over and over, re-running the front-end injects each attempt
|
|
240
|
+
// until the host wc's pending executeJavaScript waiters tripped the
|
|
241
|
+
// MaxListeners ceiling. Flag first: a failed attempt rebuilds fresh.
|
|
242
|
+
devtoolsHostUsed = true;
|
|
243
|
+
// Redirect console source-link clicks to the workbench editor.
|
|
244
|
+
wireOpenInEditor(next, simulatorView.webContents);
|
|
245
|
+
// Keep only Elements/Console/Network tabs (front-most, in that order).
|
|
246
|
+
// Re-applied on every re-point so a service-host pool swap (fresh
|
|
247
|
+
// openDevTools) re-asserts the custom tab bar.
|
|
248
|
+
customizeDevtoolsTabs(simulatorView.webContents);
|
|
249
|
+
// Capture service-layer console via CDP (NOT a preload monkeypatch, which
|
|
250
|
+
// would clobber native source attribution) and feed it to the console
|
|
251
|
+
// fan-out (automation `App.logAdded`). Bound to THIS service wc; replaced
|
|
252
|
+
// on the next re-point via closeNativeDevtoolsSource.
|
|
253
|
+
try {
|
|
254
|
+
stopServiceConsole?.();
|
|
255
|
+
}
|
|
256
|
+
catch { /* already stopped */ }
|
|
257
|
+
stopServiceConsole = installServiceConsoleForward({
|
|
258
|
+
serviceWc: next,
|
|
259
|
+
connections: ctx.connections,
|
|
260
|
+
emit: (entry) => ctx.consoleForwarder?.emit(entry),
|
|
261
|
+
}).stop;
|
|
262
|
+
return true;
|
|
263
|
+
}
|
|
264
|
+
catch (err) {
|
|
265
|
+
// Not silent: a throw here (service wc torn down mid-point is the common
|
|
266
|
+
// one) makes the follow-retry loop spin — leave a trace so the spin has
|
|
267
|
+
// a name in the log instead of hiding for weeks.
|
|
268
|
+
console.warn('[devtools-host] point-at-service-wc failed; retry will rebuild:', err);
|
|
269
|
+
if (nativeDevtoolsSourceWc?.id === next.id) {
|
|
270
|
+
nativeDevtoolsSourceWc = null;
|
|
271
|
+
}
|
|
272
|
+
return false;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
// Resolve the SERVICE HOST webContents for the active (or named) app. This is
|
|
276
|
+
// the hidden service BrowserWindow's wc — a top-level wc that CAN host a
|
|
277
|
+
// Chrome DevTools front-end (unlike a `<webview>` guest's). Re-resolved fresh
|
|
278
|
+
// so a pre-warm-pool swap on respawn is tolerated.
|
|
279
|
+
function pointNativeDevtoolsAtActiveServiceHost(appId) {
|
|
280
|
+
if (!ctx.bridge?.isNativeHost())
|
|
281
|
+
return true;
|
|
282
|
+
if (!simulatorView || simulatorView.webContents.isDestroyed())
|
|
283
|
+
return true;
|
|
284
|
+
const wc = ctx.bridge.getServiceWc(appId);
|
|
285
|
+
if (!wc || wc.isDestroyed())
|
|
286
|
+
return false;
|
|
287
|
+
return pointNativeDevtoolsAtServiceWc(wc);
|
|
288
|
+
}
|
|
289
|
+
function scheduleNativeDevtoolsFollow(appId, attempt = 0) {
|
|
290
|
+
if (attempt >= 20)
|
|
291
|
+
return;
|
|
292
|
+
if (!ctx.bridge?.isNativeHost())
|
|
293
|
+
return;
|
|
294
|
+
const token = nativeDevtoolsRetryToken;
|
|
295
|
+
if (nativeDevtoolsRetryTimer)
|
|
296
|
+
clearTimeout(nativeDevtoolsRetryTimer);
|
|
297
|
+
nativeDevtoolsRetryTimer = setTimeout(() => {
|
|
298
|
+
nativeDevtoolsRetryTimer = null;
|
|
299
|
+
if (token !== nativeDevtoolsRetryToken)
|
|
300
|
+
return;
|
|
301
|
+
if (pointNativeDevtoolsAtActiveServiceHost(appId))
|
|
302
|
+
return;
|
|
303
|
+
scheduleNativeDevtoolsFollow(appId, attempt + 1);
|
|
304
|
+
}, 50);
|
|
305
|
+
}
|
|
306
|
+
function followNativeDevtoolsServiceHost(appId) {
|
|
307
|
+
clearNativeDevtoolsRetry();
|
|
308
|
+
if (pointNativeDevtoolsAtActiveServiceHost(appId))
|
|
309
|
+
return;
|
|
310
|
+
scheduleNativeDevtoolsFollow(appId);
|
|
311
|
+
}
|
|
312
|
+
// Render-side activity (a page DOM mounting / the visible page changing)
|
|
313
|
+
// always follows a spawn or respawn, by which point the service window exists
|
|
314
|
+
// (and may have been swapped by the pool). Re-resolve + re-point the DevTools
|
|
315
|
+
// at the now-current service host on every such event.
|
|
316
|
+
function onNativeRenderEvent(event) {
|
|
317
|
+
if (event.kind !== 'activePage' && event.kind !== 'domReady')
|
|
318
|
+
return;
|
|
319
|
+
followNativeDevtoolsServiceHost(event.appId);
|
|
320
|
+
}
|
|
321
|
+
// (Re)build the right-panel DevTools front-end host — a fresh, never-navigated
|
|
322
|
+
// `simulatorView` WebContentsView. `setDevToolsWebContents` may only target a
|
|
323
|
+
// host that has never navigated, so this is the sole path that produces a
|
|
324
|
+
// usable host: called once on attach, and again by `pointNativeDevtoolsAtServiceWc`
|
|
325
|
+
// whenever the current host has already been pointed at a service wc (a
|
|
326
|
+
// service-host pool swap must repoint onto a fresh host, not reuse the
|
|
327
|
+
// already-navigated one).
|
|
328
|
+
function rebuildDevtoolsHostView() {
|
|
329
|
+
// Destroy old simulatorView to prevent WebContentsView leak
|
|
330
|
+
if (simulatorView) {
|
|
331
|
+
removeSimulatorDevtoolsView();
|
|
332
|
+
try {
|
|
333
|
+
if (!simulatorView.webContents.isDestroyed()) {
|
|
334
|
+
simulatorView.webContents.close();
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
catch { /* ignore */ }
|
|
338
|
+
simulatorView = null;
|
|
339
|
+
}
|
|
340
|
+
simulatorView = new WebContentsView();
|
|
341
|
+
// Default DevTools to Console panel (Chrome DevTools defaults to Elements).
|
|
342
|
+
// The DevTools UI lives inside closed shadow roots, so a light-DOM
|
|
343
|
+
// querySelector('[role="tab"]') cannot reach the tab bar to click it.
|
|
344
|
+
// Instead drive the front-end's own view manager: the bundled DevTools
|
|
345
|
+
// exposes `UI.ViewManager.instance().showView(id)` on `globalThis.UI`
|
|
346
|
+
// once the front-end has finished bootstrapping. We poll for it and
|
|
347
|
+
// request the `console` view, and also persist the choice via the
|
|
348
|
+
// `panel-selectedTab` localStorage key so subsequent reloads honor it.
|
|
349
|
+
const devtoolsWc = simulatorView.webContents;
|
|
350
|
+
// The boot-time burst of `executeJavaScript()` injects on this host wc (tab
|
|
351
|
+
// customization / console default / Elements+Network forwarding below) each
|
|
352
|
+
// queue a pending `did-stop-loading` waiter while the front-end is still
|
|
353
|
+
// loading (see raiseExecuteJavaScriptListenerCeiling).
|
|
354
|
+
raiseExecuteJavaScriptListenerCeiling(devtoolsWc);
|
|
355
|
+
// Hand the network forwarder THIS front-end host wc — it injects the
|
|
356
|
+
// simulator WCV's Network.* CDP events into `window.DevToolsAPI.dispatchMessage`
|
|
357
|
+
// here so the native Network tab renders them (falls back to the service-host
|
|
358
|
+
// console line when null / the API never appears). Cleared on detach.
|
|
359
|
+
ctx.networkForward?.setDevtoolsHost(devtoolsWc);
|
|
360
|
+
// Elements forwarding (production, always on for the native simulator): route
|
|
361
|
+
// the front-end's Elements-panel CDP traffic (DOM/CSS/Overlay/DOMSnapshot/
|
|
362
|
+
// DOMDebugger) onto the ACTIVE RENDER GUEST so the panel reflects the page's
|
|
363
|
+
// live DOM tree instead of the service host it natively inspects. Reuses the
|
|
364
|
+
// safe-area-attached debugger session (never detaches one it doesn't own);
|
|
365
|
+
// Emulation.* and every other domain stay on the service-host path. Degrades
|
|
366
|
+
// to the native service-host DOM if the front-end hook is unavailable. The
|
|
367
|
+
// disposer is stopped on detach / host destroyed.
|
|
368
|
+
if (ctx.bridge) {
|
|
369
|
+
try {
|
|
370
|
+
stopElementsForward?.();
|
|
371
|
+
}
|
|
372
|
+
catch { /* prior disposer already gone */ }
|
|
373
|
+
// Capture THIS instance's disposer in the destroy handler — NOT the mutable
|
|
374
|
+
// `stopElementsForward`. A respawn creates a new simulatorView whose old
|
|
375
|
+
// devtools host wc is destroyed AFTER the new instance is already installed;
|
|
376
|
+
// a handler closing over `stopElementsForward` would then dispose the CURRENT
|
|
377
|
+
// instance (its reconcile loop never gets to install the hook, so Elements
|
|
378
|
+
// falls back to the natively-inspected service host). Stop only this wc's own
|
|
379
|
+
// forward, and clear the module pointer only while it still points here.
|
|
380
|
+
const thisForward = installElementsForward({ devtoolsWc, bridge: ctx.bridge, connections: ctx.connections });
|
|
381
|
+
stopElementsForward = thisForward;
|
|
382
|
+
devtoolsWc.once('destroyed', () => {
|
|
383
|
+
try {
|
|
384
|
+
thisForward();
|
|
385
|
+
}
|
|
386
|
+
catch { /* already stopped */ }
|
|
387
|
+
if (stopElementsForward === thisForward)
|
|
388
|
+
stopElementsForward = null;
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
injectWhenReady(devtoolsWc, 'console-default', () => {
|
|
392
|
+
devtoolsWc.executeJavaScript(`
|
|
393
|
+
(function() {
|
|
394
|
+
try { localStorage.setItem('panel-selectedTab', '"console"') } catch {}
|
|
395
|
+
let tries = 0
|
|
396
|
+
const timer = setInterval(() => {
|
|
397
|
+
tries++
|
|
398
|
+
try {
|
|
399
|
+
const UI = globalThis.UI
|
|
400
|
+
const vm = UI && UI.ViewManager && typeof UI.ViewManager.instance === 'function'
|
|
401
|
+
? UI.ViewManager.instance()
|
|
402
|
+
: null
|
|
403
|
+
if (vm && typeof vm.showView === 'function') {
|
|
404
|
+
vm.showView('console')
|
|
405
|
+
clearInterval(timer)
|
|
406
|
+
return
|
|
407
|
+
}
|
|
408
|
+
} catch {}
|
|
409
|
+
if (tries > 80) clearInterval(timer)
|
|
410
|
+
}, 50)
|
|
411
|
+
})()
|
|
412
|
+
`).catch(() => { });
|
|
413
|
+
});
|
|
414
|
+
// Anchor-only mount: the renderer's published rect is the SOLE authority.
|
|
415
|
+
// If a non-zero rect was already published (it can land before this attach
|
|
416
|
+
// on the project-open ordering), replay it; otherwise the view stays
|
|
417
|
+
// unadded and unsized until the first publish arrives. No static-layout
|
|
418
|
+
// fallback — an attach-time computed rect raced the precise anchor rect
|
|
419
|
+
// and flashed the overlay at the wrong rectangle.
|
|
420
|
+
// The view now exists: re-run the reconciler so a placement published before
|
|
421
|
+
// this attach (project-open ordering) attaches it once the gate re-opens.
|
|
422
|
+
reconciler.reconcileNow();
|
|
423
|
+
// A freshly built host has never navigated — it may be targeted by
|
|
424
|
+
// `setDevToolsWebContents` again.
|
|
425
|
+
devtoolsHostUsed = false;
|
|
426
|
+
}
|
|
427
|
+
function attachNativeSimulatorDevtoolsHost() {
|
|
428
|
+
stopFollowingNativeServiceHost();
|
|
429
|
+
rebuildDevtoolsHostView();
|
|
430
|
+
if (ctx.bridge?.isNativeHost()) {
|
|
431
|
+
unsubscribeNativeRenderEvents = ctx.bridge.onRenderEvent(onNativeRenderEvent);
|
|
432
|
+
followNativeDevtoolsServiceHost();
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
// Remove (but do not destroy) the DevTools overlay from the contentView.
|
|
436
|
+
// Internal teardown helper for re-attach; user-facing visibility is the
|
|
437
|
+
// anchor 0×0 single path (`setSimulatorDevtoolsBounds`).
|
|
438
|
+
function removeSimulatorDevtoolsView() {
|
|
439
|
+
if (simulatorView && simulatorViewAdded) {
|
|
440
|
+
try {
|
|
441
|
+
ctx.windows.mainWindow.contentView.removeChildView(simulatorView);
|
|
442
|
+
}
|
|
443
|
+
catch (e) {
|
|
444
|
+
console.error('[workbench] removeSimulatorDevtoolsView error', e);
|
|
445
|
+
}
|
|
446
|
+
simulatorViewAdded = false;
|
|
447
|
+
}
|
|
448
|
+
// The instance is being replaced/destroyed — forget its reconciled mount
|
|
449
|
+
// state so the next rebuilt host is treated as a fresh attach. Without this,
|
|
450
|
+
// the level-triggered reconciler still records `simulatorDevtools` as
|
|
451
|
+
// `attached` (the manual removeChildView above bypasses it), so it never
|
|
452
|
+
// emits the `attach` op for the rebuilt view and the new host is never
|
|
453
|
+
// addChildView'd — embedded but invisible. Mirrors the hostToolbar /
|
|
454
|
+
// simulator instance-replacement handling (ensureHostToolbarView /
|
|
455
|
+
// tearDownNativeSimulatorView).
|
|
456
|
+
reconciler.forgetActual(VIEW_ID.simulatorDevtools);
|
|
457
|
+
}
|
|
458
|
+
reconciler.registerView(VIEW_ID.simulatorDevtools, {
|
|
459
|
+
getView: () => simulatorView,
|
|
460
|
+
setAdded: (added) => { simulatorViewAdded = added; },
|
|
461
|
+
gateHidden: () => !simulatorView,
|
|
462
|
+
});
|
|
463
|
+
return {
|
|
464
|
+
attach: attachNativeSimulatorDevtoolsHost,
|
|
465
|
+
followServiceHost: followNativeDevtoolsServiceHost,
|
|
466
|
+
stopFollowing: stopFollowingNativeServiceHost,
|
|
467
|
+
stopElementsForwarding: () => {
|
|
468
|
+
// Stop Elements forwarding (detaches only the debugger sessions IT attached;
|
|
469
|
+
// safe-area's sessions are untouched). Idempotent — the host 'destroyed'
|
|
470
|
+
// handler may have already run.
|
|
471
|
+
try {
|
|
472
|
+
stopElementsForward?.();
|
|
473
|
+
}
|
|
474
|
+
catch { /* already stopped */ }
|
|
475
|
+
stopElementsForward = null;
|
|
476
|
+
},
|
|
477
|
+
destroyHostView: () => {
|
|
478
|
+
destroyChildView(ctx.windows.mainWindow, simulatorView);
|
|
479
|
+
simulatorView = null;
|
|
480
|
+
simulatorViewAdded = false;
|
|
481
|
+
},
|
|
482
|
+
};
|
|
483
|
+
}
|
|
484
|
+
//# sourceMappingURL=native-simulator-devtools-host.js.map
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import type { WebContents } from 'electron';
|
|
2
|
+
import type { SafeAreaController } from '../safe-area/index.js';
|
|
3
|
+
import type { DevtoolsHost } from './native-simulator-devtools-host.js';
|
|
4
|
+
import type { OverlayPanelsView } from './overlay-panels-view.js';
|
|
5
|
+
import type { PlacementReconciler } from './placement-reconciler.js';
|
|
6
|
+
import type { ViewManagerContext } from './view-manager.js';
|
|
7
|
+
/**
|
|
8
|
+
* NATIVE-HOST ONLY. The native simulator is a top-level WebContentsView (the
|
|
9
|
+
* DeviceShell host) that IS the simulator webContents, so bridge-router resolves
|
|
10
|
+
* `ap.simulatorWc = event.sender = this wc` and SIMULATOR_EVENTS flow through
|
|
11
|
+
* it. It hosts per-page render-host `<webview>`s (impossible under a `<webview>`
|
|
12
|
+
* guest — the whole point of Option A). Its DevTools live in the right panel
|
|
13
|
+
* (the separate DevtoolsHost).
|
|
14
|
+
*/
|
|
15
|
+
export interface NativeSimulatorView {
|
|
16
|
+
attachNativeSimulator(simulatorUrl: string, simWidth: number): Promise<void>;
|
|
17
|
+
softReloadNativeSimulator(simulatorUrl: string): boolean;
|
|
18
|
+
detachSimulator(): void;
|
|
19
|
+
getSimulatorWebContentsId(): number | null;
|
|
20
|
+
getSimulatorWebContents(): WebContents | null;
|
|
21
|
+
getSimulatorProjectPath(): string | null;
|
|
22
|
+
}
|
|
23
|
+
export declare function createNativeSimulatorView(ctx: ViewManagerContext, reconciler: PlacementReconciler, deps: {
|
|
24
|
+
safeArea: SafeAreaController;
|
|
25
|
+
devtoolsHost: DevtoolsHost;
|
|
26
|
+
overlayPanels: OverlayPanelsView;
|
|
27
|
+
}): NativeSimulatorView;
|
|
28
|
+
//# sourceMappingURL=native-simulator-view.d.ts.map
|