@dimina-kit/devtools 0.4.0-dev.20260618090552 → 0.4.0-dev.20260624084417
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 +5 -1
- package/dist/main/app/app.js +14 -2
- package/dist/main/index.bundle.js +596 -112
- package/dist/main/ipc/bridge-router.js +13 -8
- package/dist/main/ipc/projects.js +1 -1
- package/dist/main/ipc/simulator.js +1 -1
- package/dist/main/runtime/miniapp-runtime.d.ts +3 -3
- package/dist/main/services/elements-forward/index.js +42 -8
- package/dist/main/services/network-forward/index.d.ts +5 -6
- package/dist/main/services/network-forward/index.js +9 -10
- package/dist/main/services/notifications/renderer-notifier.d.ts +3 -4
- package/dist/main/services/projects/index.js +1 -1
- package/dist/main/services/render-inspect/index.js +182 -6
- package/dist/main/services/service-console/console-api.d.ts +5 -6
- package/dist/main/services/service-console/console-api.js +5 -6
- package/dist/main/services/service-host-pool/pool.d.ts +2 -3
- package/dist/main/services/simulator-storage/index.d.ts +2 -4
- package/dist/main/services/simulator-storage/index.js +2 -4
- package/dist/main/services/simulator-temp-files/index.js +2 -2
- package/dist/main/services/views/devtools-tabs.d.ts +4 -1
- package/dist/main/services/views/devtools-tabs.js +21 -3
- package/dist/main/services/views/host-toolbar-session-runtime.d.ts +4 -4
- package/dist/main/services/views/host-toolbar-session-runtime.js +4 -4
- package/dist/main/services/views/view-manager.d.ts +17 -1
- package/dist/main/services/views/view-manager.js +74 -47
- package/dist/main/services/workspace/workspace-service.js +11 -0
- package/dist/main/windows/service-host-window/create.d.ts +1 -0
- package/dist/main/windows/service-host-window/create.js +1 -0
- package/dist/preload/instrumentation/wxml-extract.js +53 -7
- package/dist/preload/runtime/host-toolbar-port.d.ts +1 -1
- package/dist/preload/runtime/host-toolbar-port.js +1 -1
- package/dist/preload/runtime/host-toolbar-runtime.d.ts +8 -7
- package/dist/preload/runtime/host-toolbar-runtime.js +8 -7
- package/dist/preload/windows/host-toolbar-runtime.cjs.map +2 -2
- package/dist/preload/windows/main.cjs.map +2 -2
- package/dist/preload/windows/simulator.cjs +1 -1
- package/dist/preload/windows/simulator.cjs.map +1 -1
- package/dist/preload/windows/simulator.js +1 -1
- package/dist/render-host/render-inspect.js +24 -23
- package/dist/renderer/assets/index-BMqriQNr.js +50 -0
- package/dist/renderer/entries/main/index.html +1 -1
- package/dist/shared/appdata-accumulator.d.ts +2 -2
- package/dist/shared/appdata-accumulator.js +3 -3
- package/dist/shared/constants.d.ts +1 -1
- package/dist/shared/constants.js +1 -1
- package/dist/shared/ipc-channels.d.ts +3 -3
- package/dist/shared/ipc-channels.js +3 -3
- package/dist/shared/open-in-editor.d.ts +27 -1
- package/dist/shared/open-in-editor.js +369 -3
- package/dist/shared/vpath.d.ts +2 -2
- package/dist/shared/vpath.js +2 -2
- package/dist/simulator/assets/device-shell-CPCnCp1L.js +2 -0
- package/dist/simulator/assets/{simulator-sf-D0mhw.js → simulator-gMBWKDeO.js} +3 -3
- package/dist/simulator/simulator.html +1 -1
- package/package.json +6 -4
- package/dist/renderer/assets/index-DmgWoK8N.js +0 -50
- package/dist/simulator/assets/device-shell-BWX7Yopg.js +0 -2
|
@@ -453,7 +453,10 @@ async function handleSpawn(state, ctx, event, opts) {
|
|
|
453
453
|
const appSessionId = bridgeId;
|
|
454
454
|
const simulatorWc = resolveSimulatorWebContents(ctx, opts.simulatorWcId, event.sender);
|
|
455
455
|
const pagePath = normalizePagePath(opts.pagePath || 'pages/index/index');
|
|
456
|
-
const
|
|
456
|
+
const workspaceProjectPath = typeof ctx.workspace.getProjectPath === 'function'
|
|
457
|
+
? ctx.workspace.getProjectPath()
|
|
458
|
+
: '';
|
|
459
|
+
const pkgRoot = path.resolve(opts.pkgRoot || workspaceProjectPath || process.cwd());
|
|
457
460
|
const root = opts.root || 'main';
|
|
458
461
|
// Resource base resolution. Preferred: the simulator-supplied dev-server
|
|
459
462
|
// origin, which statically serves the compiled `<appId>/<root>/…` tree (same
|
|
@@ -505,6 +508,7 @@ async function handleSpawn(state, ctx, event, opts) {
|
|
|
505
508
|
appId,
|
|
506
509
|
pagePath,
|
|
507
510
|
pkgRoot,
|
|
511
|
+
root,
|
|
508
512
|
resourceBaseUrl,
|
|
509
513
|
hostEnvSnapshot: hostEnv,
|
|
510
514
|
});
|
|
@@ -608,6 +612,7 @@ async function handleSpawn(state, ctx, event, opts) {
|
|
|
608
612
|
appId,
|
|
609
613
|
pagePath,
|
|
610
614
|
pkgRoot,
|
|
615
|
+
root,
|
|
611
616
|
resourceBaseUrl,
|
|
612
617
|
hostEnvSnapshot: hostEnv,
|
|
613
618
|
}));
|
|
@@ -1099,13 +1104,13 @@ function makeLoadResource(ap, page, target) {
|
|
|
1099
1104
|
// dimina's service runtime reads hostEnv as `{ systemInfo, menuRect }`
|
|
1100
1105
|
// (core/host-env.js init → getSystemInfo/getMenuRect; invokeAPI resolves
|
|
1101
1106
|
// getSystemInfoSync/getWindowInfo/getDeviceInfo from hostEnv.systemInfo).
|
|
1102
|
-
//
|
|
1103
|
-
// `wx.getSystemInfoSync()`
|
|
1104
|
-
// `.screenWidth`
|
|
1105
|
-
//
|
|
1106
|
-
//
|
|
1107
|
-
// menuRect stays null
|
|
1108
|
-
//
|
|
1107
|
+
// The snapshot must be nested under `systemInfo`: a FLAT HostEnvSnapshot
|
|
1108
|
+
// leaves `systemInfo` null → `wx.getSystemInfoSync()` returns null and
|
|
1109
|
+
// pages reading `.screenWidth` throw. (render does NOT read hostEnv, so
|
|
1110
|
+
// this is service-only; the devtools sync-api-patch reads the separate
|
|
1111
|
+
// __diminaSpawnContext.hostEnvSnapshot and is unaffected.)
|
|
1112
|
+
// menuRect stays null — getMenuButtonBoundingClientRect is served by the
|
|
1113
|
+
// sync-api-patch / DeviceShell capsule, not this path.
|
|
1109
1114
|
hostEnv: { systemInfo: ap.hostEnv, menuRect: null },
|
|
1110
1115
|
},
|
|
1111
1116
|
};
|
|
@@ -49,7 +49,7 @@ export function registerProjectsIpc(ctx) {
|
|
|
49
49
|
const [dirPath] = validate(ProjectsChannel.Remove, ProjectsRemoveSchema, args);
|
|
50
50
|
return ctx.workspace.removeProject(dirPath);
|
|
51
51
|
})
|
|
52
|
-
// ──
|
|
52
|
+
// ── template catalog + create flow ──
|
|
53
53
|
.handle(ProjectsChannel.ListTemplates, () => {
|
|
54
54
|
// Sanitize at the IPC boundary: `generate` is a function and the
|
|
55
55
|
// structured-clone algorithm Electron uses for invoke would otherwise
|
|
@@ -7,7 +7,7 @@ export function registerSimulatorIpc(ctx) {
|
|
|
7
7
|
return new IpcRegistry(ctx.senderPolicy)
|
|
8
8
|
.handle(SimulatorChannel.AttachNative, (_, ...args) => {
|
|
9
9
|
const [simulatorUrl, simWidth] = validate(SimulatorChannel.AttachNative, SimulatorAttachNativeSchema, args);
|
|
10
|
-
ctx.views.attachNativeSimulator(simulatorUrl, simWidth);
|
|
10
|
+
return ctx.views.attachNativeSimulator(simulatorUrl, simWidth);
|
|
11
11
|
})
|
|
12
12
|
.handle(SimulatorChannel.Detach, () => {
|
|
13
13
|
ctx.views.detachSimulator();
|
|
@@ -83,9 +83,9 @@ declare global {
|
|
|
83
83
|
}
|
|
84
84
|
}
|
|
85
85
|
/**
|
|
86
|
-
* Host-facing control surface for the toolbar WebContentsView —
|
|
87
|
-
*
|
|
88
|
-
*
|
|
86
|
+
* Host-facing control surface for the toolbar WebContentsView — a
|
|
87
|
+
* message-channel surface (`send`/`onMessage`), with no `webContents` escape
|
|
88
|
+
* hatch (that would put Electron types on the contract).
|
|
89
89
|
*/
|
|
90
90
|
export interface MiniappHostToolbar {
|
|
91
91
|
/**
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
* no longer active is dropped (its in-flight command id is settled with an error
|
|
37
37
|
* so the front-end never leaks a pending request, and stale nodes never bleed into
|
|
38
38
|
* the new tree); and switching away and BACK to a previously-wired guest RESUMES
|
|
39
|
-
* its forwarding (a snapshot would strand it
|
|
39
|
+
* its forwarding (a snapshot would strand it).
|
|
40
40
|
*
|
|
41
41
|
* ── Degradation ──────────────────────────────────────────────────────────────
|
|
42
42
|
* Hook unavailable / `DevToolsAPI` missing / no active guest → routing is simply
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
*
|
|
47
47
|
* This is a production feature (no env gate, default on for the native simulator).
|
|
48
48
|
* It deliberately re-implements the small pure helpers it needs (routing table,
|
|
49
|
-
* hook + dispatch scripts)
|
|
49
|
+
* hook + dispatch scripts) as self-contained code.
|
|
50
50
|
*/
|
|
51
51
|
import { webContents as electronWebContents } from 'electron';
|
|
52
52
|
/**
|
|
@@ -198,8 +198,8 @@ export function installElementsForward(deps) {
|
|
|
198
198
|
// bridge per check — NOT a generation snapshot. An event/response is honoured
|
|
199
199
|
// only while its originating guest is still the active one, so switching away
|
|
200
200
|
// and BACK to a previously-wired guest RESUMES its forwarding (a snapshot would
|
|
201
|
-
// strand it forever
|
|
202
|
-
//
|
|
201
|
+
// strand it forever). It also means destroying some OTHER (non-active) guest
|
|
202
|
+
// never stales the active guest's in-flight commands.
|
|
203
203
|
const isActiveWcId = (id) => {
|
|
204
204
|
const a = activeRenderWc();
|
|
205
205
|
return a != null && a.id === id;
|
|
@@ -395,7 +395,7 @@ export function installElementsForward(deps) {
|
|
|
395
395
|
}
|
|
396
396
|
// A destroyed guest is no longer the active one, so its in-flight commands
|
|
397
397
|
// fail `isActiveWcId` and settle as errors on their own — no global bump
|
|
398
|
-
// (which would wrongly stale OTHER, still-active guests' commands
|
|
398
|
+
// (which would wrongly stale OTHER, still-active guests' commands).
|
|
399
399
|
// If we own this wc's session there is nothing left to detach; drop it.
|
|
400
400
|
selfAttached.delete(wc.id);
|
|
401
401
|
};
|
|
@@ -430,14 +430,40 @@ export function installElementsForward(deps) {
|
|
|
430
430
|
catch { /* gone */ }
|
|
431
431
|
});
|
|
432
432
|
}
|
|
433
|
-
/**
|
|
433
|
+
/**
|
|
434
|
+
* enable DOM/CSS/Overlay + wire events on a guest. Best-effort (never throws).
|
|
435
|
+
*
|
|
436
|
+
* `Overlay.enable` is sent ONLY AFTER `DOM.enable` has resolved: Chromium rejects
|
|
437
|
+
* `Overlay.enable` with "DOM should be enabled first" when it arrives before the
|
|
438
|
+
* DOM domain is enabled, and a silently-dropped rejection leaves Overlay disabled
|
|
439
|
+
* so every later `Overlay.highlightNode` fails with "Overlay must be enabled" and
|
|
440
|
+
* the Elements-panel hover highlight never paints. Awaiting DOM.enable first
|
|
441
|
+
* guarantees the correct enable order. CSS.enable has no such dependency and is
|
|
442
|
+
* fire-and-forget alongside.
|
|
443
|
+
*/
|
|
434
444
|
function primeGuest(wc) {
|
|
435
445
|
if (!ensureGuestDebugger(wc))
|
|
436
446
|
return;
|
|
437
447
|
wireGuestEvents(wc);
|
|
438
|
-
|
|
439
|
-
|
|
448
|
+
void enableGuestDomains(wc);
|
|
449
|
+
}
|
|
450
|
+
/** Enable the render domains in dependency order. Resolves; never rejects. */
|
|
451
|
+
async function enableGuestDomains(wc) {
|
|
452
|
+
wc.debugger.sendCommand('CSS.enable').catch(() => { });
|
|
453
|
+
try {
|
|
454
|
+
await wc.debugger.sendCommand('DOM.enable');
|
|
440
455
|
}
|
|
456
|
+
catch {
|
|
457
|
+
// Guest mid-destroy or DOM domain unavailable; Overlay.enable would only
|
|
458
|
+
// re-reject, so stop here rather than firing it out of order.
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
// DOM.enable can settle after the guest was destroyed or the active page
|
|
462
|
+
// switched away (priming always targets the active guest). Re-check before
|
|
463
|
+
// arming Overlay so a stale/dead guest is left untouched.
|
|
464
|
+
if (disposed || !isActiveWcId(wc.id))
|
|
465
|
+
return;
|
|
466
|
+
wc.debugger.sendCommand('Overlay.enable').catch(() => { });
|
|
441
467
|
}
|
|
442
468
|
// ── front-end → render command routing ─────────────────────────────────────
|
|
443
469
|
function routeCommand(cmd) {
|
|
@@ -465,6 +491,14 @@ export function installElementsForward(deps) {
|
|
|
465
491
|
replyError(cmd, 'stale render generation');
|
|
466
492
|
return;
|
|
467
493
|
}
|
|
494
|
+
// The front-end emits `Overlay.disable` on Elements-panel state
|
|
495
|
+
// transitions. Forwarded verbatim it disables the guest's Overlay agent,
|
|
496
|
+
// after which every `Overlay.highlightNode` fails with "Overlay must be
|
|
497
|
+
// enabled" and the hover highlight stops painting. Re-arm it so the next
|
|
498
|
+
// hover paints — only while this guest is still the active one.
|
|
499
|
+
if (cmd.method === 'Overlay.disable' && isActiveWcId(cmdWcId)) {
|
|
500
|
+
wc.debugger.sendCommand('Overlay.enable').catch(() => { });
|
|
501
|
+
}
|
|
468
502
|
replyResult(cmd, result);
|
|
469
503
|
})
|
|
470
504
|
.catch((err) => {
|
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
* `direct-request.ts`/`simulator-api-network.ts`, which call `fetch()` /
|
|
15
15
|
* `XMLHttpRequest`). Those requests therefore go through the simulator WCV's
|
|
16
16
|
* network stack — a DIFFERENT webContents than the one the DevTools front-end
|
|
17
|
-
* inspects — so they
|
|
18
|
-
*
|
|
17
|
+
* inspects — so they are otherwise invisible in the Network panel. This service
|
|
18
|
+
* surfaces them.
|
|
19
19
|
*
|
|
20
20
|
* ── How it surfaces them (primary sink: native Network tab) ─────────────────
|
|
21
21
|
* We attach the CDP `webContents.debugger` to the simulator WCV, `Network.enable`,
|
|
@@ -32,9 +32,8 @@
|
|
|
32
32
|
* into a namespaced virtual id (`dimina:sim:<epoch>:<rawId>`) before dispatch,
|
|
33
33
|
* keeping a raw→virtual map so redirects / extra-info / completion events on the
|
|
34
34
|
* same request stay correlated. We inject on the MAIN session (no `sessionId`),
|
|
35
|
-
* i.e. the events appear as activity on the currently-inspected target.
|
|
36
|
-
*
|
|
37
|
-
* (`Target.attachedToTarget` + hooking outbound CDP) is explicitly deferred.
|
|
35
|
+
* i.e. the events appear as activity on the currently-inspected target. Child-
|
|
36
|
+
* target routing (`Target.attachedToTarget` + hooking outbound CDP) is deferred.
|
|
38
37
|
*
|
|
39
38
|
* ── Fallback sink (console line) ────────────────────────────────────────────
|
|
40
39
|
* If the DevTools host wc is unavailable, or `window.DevToolsAPI.dispatchMessage`
|
|
@@ -127,7 +126,7 @@ export interface NetworkForwarder extends Disposable {
|
|
|
127
126
|
* Anything carrying a requestId must be namespaced consistently — even methods
|
|
128
127
|
* we don't (yet) forward — so the raw→virtual map stays coherent if forwarding
|
|
129
128
|
* is widened later. `requestServedFromCache` and `resourceChangedPriority` are
|
|
130
|
-
* included for that reason (
|
|
129
|
+
* included for that reason (rewrite-only today, not forwarded).
|
|
131
130
|
*/
|
|
132
131
|
export declare const REWRITE_REQUEST_ID_METHODS: ReadonlySet<string>;
|
|
133
132
|
/** The Network.* methods this one-shot pass forwards to the front-end. */
|
|
@@ -5,7 +5,7 @@ import { DisposableRegistry, toDisposable } from '@dimina-kit/electron-deck/main
|
|
|
5
5
|
* Anything carrying a requestId must be namespaced consistently — even methods
|
|
6
6
|
* we don't (yet) forward — so the raw→virtual map stays coherent if forwarding
|
|
7
7
|
* is widened later. `requestServedFromCache` and `resourceChangedPriority` are
|
|
8
|
-
* included for that reason (
|
|
8
|
+
* included for that reason (rewrite-only today, not forwarded).
|
|
9
9
|
*/
|
|
10
10
|
export const REWRITE_REQUEST_ID_METHODS = new Set([
|
|
11
11
|
'Network.requestWillBeSent',
|
|
@@ -243,10 +243,9 @@ export function createNetworkForwarder(bridge) {
|
|
|
243
243
|
let attachDisposables = null;
|
|
244
244
|
// The DevTools front-end host wc (primary sink), set by the ViewManager.
|
|
245
245
|
let devtoolsWc = null;
|
|
246
|
-
// Teardown for the wc 'destroyed' watcher on the current host (clears the host
|
|
247
|
-
// here, in this file — view-manager is owned by another change and untouched).
|
|
246
|
+
// Teardown for the wc 'destroyed' watcher on the current host (clears the host).
|
|
248
247
|
let devtoolsHostDisposable = null;
|
|
249
|
-
// ── Native-sink state machine
|
|
248
|
+
// ── Native-sink state machine ─────────────────────────────────────────────
|
|
250
249
|
let sink = 'idle';
|
|
251
250
|
// Buffered completed-request records while 'probing' — flushed to console if we
|
|
252
251
|
// degrade, dropped if we go ready (so a request shows in exactly one sink).
|
|
@@ -321,7 +320,7 @@ export function createNetworkForwarder(bridge) {
|
|
|
321
320
|
/** Trim the native queue to its cap, preferring to keep request-opening events.
|
|
322
321
|
* Active requests' first events (requestWillBeSent / ...ExtraInfo) are retained
|
|
323
322
|
* so later responseReceived/loadingFinished never become orphans in the panel;
|
|
324
|
-
* we drop the oldest NON-opening (low-value / completion) events first.
|
|
323
|
+
* we drop the oldest NON-opening (low-value / completion) events first. */
|
|
325
324
|
function trimQueue() {
|
|
326
325
|
if (dispatchQueue.length <= MAX_DISPATCH_QUEUE)
|
|
327
326
|
return;
|
|
@@ -355,15 +354,15 @@ export function createNetworkForwarder(bridge) {
|
|
|
355
354
|
}
|
|
356
355
|
const wc = resolveDevtoolsWc();
|
|
357
356
|
if (!wc) {
|
|
358
|
-
// Host went away mid-flight. Keep the queue bounded (
|
|
357
|
+
// Host went away mid-flight. Keep the queue bounded (the cap applies on
|
|
359
358
|
// EVERY path, not just no-host) and wait — setDevtoolsHost re-arms probing.
|
|
360
359
|
trimQueue();
|
|
361
360
|
return;
|
|
362
361
|
}
|
|
363
362
|
if (sink === 'idle')
|
|
364
363
|
beginProbing();
|
|
365
|
-
// Pack greedily up to MAX_BATCH_CHARS so one executeJavaScript stays sized
|
|
366
|
-
//
|
|
364
|
+
// Pack greedily up to MAX_BATCH_CHARS so one executeJavaScript stays sized;
|
|
365
|
+
// oversized single messages go via the chunked transport.
|
|
367
366
|
const batch = [];
|
|
368
367
|
let batchChars = 0;
|
|
369
368
|
let i = 0;
|
|
@@ -421,7 +420,7 @@ export function createNetworkForwarder(bridge) {
|
|
|
421
420
|
}).catch(() => {
|
|
422
421
|
// wc navigated / torn down mid-call, OR the script overflowed IPC. Re-queue
|
|
423
422
|
// (bounded) and let the next flush re-resolve the host. Best-effort; the
|
|
424
|
-
// ready-timeout still governs giving up.
|
|
423
|
+
// ready-timeout still governs giving up. Backoff is via the retry timer.
|
|
425
424
|
if (sink === 'degraded')
|
|
426
425
|
return;
|
|
427
426
|
dispatchQueue = batch.concat(dispatchQueue);
|
|
@@ -654,7 +653,7 @@ export function createNetworkForwarder(bridge) {
|
|
|
654
653
|
}
|
|
655
654
|
/**
|
|
656
655
|
* Route a completed request to EXACTLY ONE sink, per the native-sink state
|
|
657
|
-
* machine (
|
|
656
|
+
* machine (no double-display):
|
|
658
657
|
* - 'ready' : native path already rendered it → suppress console.
|
|
659
658
|
* - 'degraded' : native abandoned → console.
|
|
660
659
|
* - 'idle' : no host configured → console (the native queue never flushes).
|
|
@@ -39,10 +39,9 @@ export interface WorkbenchSettingsInitPayload {
|
|
|
39
39
|
}
|
|
40
40
|
/**
|
|
41
41
|
* Unified entry point for every main → renderer event sent by the devtools
|
|
42
|
-
* main process.
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
* in exactly one place.
|
|
42
|
+
* main process. Instead of calling `someWebContents.send(channel, payload)`
|
|
43
|
+
* directly, every site goes through a typed method here so that channel names,
|
|
44
|
+
* payload shapes and `isDestroyed()` guards live in exactly one place.
|
|
46
45
|
*
|
|
47
46
|
* Methods resolve their target `WebContents` lazily from references provided
|
|
48
47
|
* at call time (or read from the owning context) and silently no-op when the
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// higher-level coordination (session + renderer notifications) lives in
|
|
4
4
|
// ../workspace/workspace-service.ts.
|
|
5
5
|
//
|
|
6
|
-
//
|
|
6
|
+
// The extensibility surface lives in `types.ts` (ProjectsProvider,
|
|
7
7
|
// ProjectTemplate, CreateProjectInput) and `local-provider.ts` (the default
|
|
8
8
|
// implementation injected when the host omits `projectsProvider`).
|
|
9
9
|
export * from './project-repository.js';
|
|
@@ -2,11 +2,47 @@ import { readFileSync } from 'node:fs';
|
|
|
2
2
|
import path from 'node:path';
|
|
3
3
|
import { devtoolsPackageRoot } from '../../utils/paths.js';
|
|
4
4
|
const DEFAULT_SOURCE_PATH = 'dist/render-host/render-inspect.js';
|
|
5
|
+
/**
|
|
6
|
+
* Chrome DevTools' default Elements-panel highlight palette (content / padding /
|
|
7
|
+
* border / margin tints + the size tooltip). Mirroring it makes the WXML panel's
|
|
8
|
+
* hover box visually identical to the embedded Elements panel's.
|
|
9
|
+
*/
|
|
10
|
+
const HIGHLIGHT_CONFIG = {
|
|
11
|
+
showInfo: true,
|
|
12
|
+
showRulers: false,
|
|
13
|
+
showExtensionLines: false,
|
|
14
|
+
contentColor: { r: 111, g: 168, b: 220, a: 0.66 },
|
|
15
|
+
paddingColor: { r: 147, g: 196, b: 125, a: 0.55 },
|
|
16
|
+
borderColor: { r: 255, g: 229, b: 153, a: 0.66 },
|
|
17
|
+
marginColor: { r: 246, g: 178, b: 107, a: 0.66 },
|
|
18
|
+
};
|
|
19
|
+
/** Object group for the per-hover `Runtime.evaluate` reference, released after the draw. */
|
|
20
|
+
const HOVER_OBJECT_GROUP = 'render-inspect-hover';
|
|
21
|
+
/**
|
|
22
|
+
* Upper bound on awaiting the `DOM.enable → Overlay.enable` handshake before
|
|
23
|
+
* highlighting. A hung `DOM.enable` then degrades to a missed paint instead of a
|
|
24
|
+
* stuck hover.
|
|
25
|
+
*/
|
|
26
|
+
const ENABLE_HANDSHAKE_TIMEOUT_MS = 500;
|
|
27
|
+
/** Resolve when `p` settles or `ms` elapses, whichever comes first (never rejects). */
|
|
28
|
+
function withTimeout(p, ms) {
|
|
29
|
+
return new Promise((resolve) => {
|
|
30
|
+
const timer = setTimeout(resolve, ms);
|
|
31
|
+
p.then(() => { clearTimeout(timer); resolve(); }, () => { clearTimeout(timer); resolve(); });
|
|
32
|
+
});
|
|
33
|
+
}
|
|
5
34
|
export function createRenderInspector(options = {}) {
|
|
6
35
|
const loadSource = options.loadSource ??
|
|
7
36
|
(() => readFileSync(path.join(devtoolsPackageRoot, DEFAULT_SOURCE_PATH), 'utf8'));
|
|
8
37
|
let cachedSource = null;
|
|
9
38
|
const injected = new Set();
|
|
39
|
+
// Debugger sessions THIS service attached itself (nobody else owned one). Only
|
|
40
|
+
// these are detached on guest-destroy; sessions safe-area / Elements-forward
|
|
41
|
+
// own are never touched (single-owner per wc).
|
|
42
|
+
const selfAttached = new Set();
|
|
43
|
+
// Per-wc `DOM.enable → Overlay.enable` handshake, started once and reused so a
|
|
44
|
+
// burst of hovers shares one enable. Cleared when the guest is destroyed.
|
|
45
|
+
const enablePromises = new Map();
|
|
10
46
|
function source() {
|
|
11
47
|
if (cachedSource === null)
|
|
12
48
|
cachedSource = loadSource();
|
|
@@ -37,11 +73,15 @@ export function createRenderInspector(options = {}) {
|
|
|
37
73
|
// Consolidate the per-wc bookkeeping teardown onto the connection layer
|
|
38
74
|
// (foundation.md §4 / P2) when a registry is available; fall back to the
|
|
39
75
|
// bespoke `once('destroyed')` only when omitted (focused unit tests).
|
|
76
|
+
const forget = () => {
|
|
77
|
+
injected.delete(wc.id);
|
|
78
|
+
enablePromises.delete(wc.id);
|
|
79
|
+
};
|
|
40
80
|
if (options.connections) {
|
|
41
|
-
options.connections.acquire(wc).own(
|
|
81
|
+
options.connections.acquire(wc).own(forget);
|
|
42
82
|
}
|
|
43
83
|
else {
|
|
44
|
-
wc.once('destroyed',
|
|
84
|
+
wc.once('destroyed', forget);
|
|
45
85
|
}
|
|
46
86
|
return true;
|
|
47
87
|
}
|
|
@@ -63,21 +103,157 @@ export function createRenderInspector(options = {}) {
|
|
|
63
103
|
return null;
|
|
64
104
|
if (!(await ensureInjected(wc)))
|
|
65
105
|
return null;
|
|
106
|
+
let inspection;
|
|
66
107
|
try {
|
|
67
108
|
const result = await wc.executeJavaScript(`window.__diminaRenderInspect ? window.__diminaRenderInspect.highlightElement(${JSON.stringify(sid)}) : null`);
|
|
68
|
-
|
|
109
|
+
inspection = result ?? null;
|
|
69
110
|
}
|
|
70
111
|
catch {
|
|
71
112
|
return null;
|
|
72
113
|
}
|
|
114
|
+
// No geometry → nothing to draw; never touch the debugger.
|
|
115
|
+
if (!inspection)
|
|
116
|
+
return null;
|
|
117
|
+
// Draw the native overlay best-effort: the inspection data must survive even
|
|
118
|
+
// if the CDP draw fails, so a rejected debugger step never propagates.
|
|
119
|
+
await drawNativeHighlight(wc, sid).catch(() => { });
|
|
120
|
+
return inspection;
|
|
73
121
|
}
|
|
74
|
-
|
|
122
|
+
/**
|
|
123
|
+
* Paint the Chrome-style native highlight over the guest via CDP — the same
|
|
124
|
+
* `Overlay.highlightNode` the embedded Elements panel uses, so the WXML panel's
|
|
125
|
+
* hover box matches it exactly. Reuses the guest's existing debugger session
|
|
126
|
+
* (single-owner per wc; safe-area / Elements-forward has usually already
|
|
127
|
+
* attached it) and only attaches itself when nobody has.
|
|
128
|
+
*
|
|
129
|
+
* `Overlay.highlightNode` paints only while the Overlay domain is enabled, and
|
|
130
|
+
* Chromium rejects `Overlay.enable` with "DOM should be enabled first" unless
|
|
131
|
+
* DOM is enabled first. On a cold/self-attached session (WXML hovered without
|
|
132
|
+
* the Elements panel ever enabling Overlay) the highlight would silently no-op
|
|
133
|
+
* if the command raced ahead of the enable, so we AWAIT the `DOM.enable →
|
|
134
|
+
* Overlay.enable` handshake before highlighting — but only up to
|
|
135
|
+
* `ENABLE_HANDSHAKE_TIMEOUT_MS`, so a hung `DOM.enable` degrades to a missed
|
|
136
|
+
* paint rather than a stuck hover.
|
|
137
|
+
*
|
|
138
|
+
* The `Runtime.evaluate` runs in a named object group whose remote references
|
|
139
|
+
* are released in `finally`; otherwise every hover would leak a live DOM
|
|
140
|
+
* wrapper into the guest's execution context.
|
|
141
|
+
*/
|
|
142
|
+
async function drawNativeHighlight(wc, sid) {
|
|
75
143
|
if (wc.isDestroyed())
|
|
76
144
|
return;
|
|
77
|
-
if (!(
|
|
145
|
+
if (!ensureGuestDebugger(wc))
|
|
146
|
+
return;
|
|
147
|
+
await withTimeout(ensureDomainsEnabled(wc), ENABLE_HANDSHAKE_TIMEOUT_MS);
|
|
148
|
+
if (wc.isDestroyed())
|
|
149
|
+
return;
|
|
150
|
+
const expression = `window.__diminaRenderInspect && window.__diminaRenderInspect.elementFor(${JSON.stringify(sid)})`;
|
|
151
|
+
try {
|
|
152
|
+
const evaluated = await wc.debugger.sendCommand('Runtime.evaluate', {
|
|
153
|
+
expression,
|
|
154
|
+
returnByValue: false,
|
|
155
|
+
objectGroup: HOVER_OBJECT_GROUP,
|
|
156
|
+
});
|
|
157
|
+
const objectId = evaluated?.result?.objectId;
|
|
158
|
+
if (!objectId)
|
|
159
|
+
return;
|
|
160
|
+
await wc.debugger.sendCommand('Overlay.highlightNode', {
|
|
161
|
+
objectId,
|
|
162
|
+
highlightConfig: HIGHLIGHT_CONFIG,
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
finally {
|
|
166
|
+
// Drop the hover's remote DOM reference so repeated hovers don't accumulate
|
|
167
|
+
// live wrappers in the guest context.
|
|
168
|
+
wc.debugger
|
|
169
|
+
.sendCommand('Runtime.releaseObjectGroup', { objectGroup: HOVER_OBJECT_GROUP })
|
|
170
|
+
.catch(() => { });
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Ensure the guest's debugger is usable WITHOUT opening a second session: a
|
|
175
|
+
* webContents debugger is single-owner, so reuse the already-attached session
|
|
176
|
+
* (Elements-forward / safe-area) and only `attach('1.3')` when nobody has. The
|
|
177
|
+
* sessions we open ourselves are tracked so guest-destroy detaches only ours.
|
|
178
|
+
* Returns false when the debugger can't be made usable.
|
|
179
|
+
*/
|
|
180
|
+
function ensureGuestDebugger(wc) {
|
|
181
|
+
try {
|
|
182
|
+
if (wc.debugger.isAttached())
|
|
183
|
+
return true;
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
return false;
|
|
187
|
+
}
|
|
188
|
+
try {
|
|
189
|
+
wc.debugger.attach('1.3');
|
|
190
|
+
trackSelfAttached(wc);
|
|
191
|
+
return true;
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
// A concurrent attach (race) leaves it usable; anything else is a failure.
|
|
195
|
+
try {
|
|
196
|
+
return wc.debugger.isAttached();
|
|
197
|
+
}
|
|
198
|
+
catch {
|
|
199
|
+
return false;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
/** Record a self-attached session and detach it when the guest is destroyed. */
|
|
204
|
+
function trackSelfAttached(wc) {
|
|
205
|
+
if (selfAttached.has(wc.id))
|
|
206
|
+
return;
|
|
207
|
+
selfAttached.add(wc.id);
|
|
208
|
+
const detach = () => {
|
|
209
|
+
selfAttached.delete(wc.id);
|
|
210
|
+
try {
|
|
211
|
+
if (!wc.isDestroyed() && wc.debugger.isAttached())
|
|
212
|
+
wc.debugger.detach();
|
|
213
|
+
}
|
|
214
|
+
catch { /* already gone */ }
|
|
215
|
+
};
|
|
216
|
+
if (options.connections)
|
|
217
|
+
options.connections.acquire(wc).own(detach);
|
|
218
|
+
else {
|
|
219
|
+
try {
|
|
220
|
+
wc.once('destroyed', () => selfAttached.delete(wc.id));
|
|
221
|
+
}
|
|
222
|
+
catch { /* fake wc */ }
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
/**
|
|
226
|
+
* Enable the DOM + Overlay render domains in dependency order, ONCE per guest
|
|
227
|
+
* (the promise is cached + reused across hovers). `Overlay.enable` is sent ONLY
|
|
228
|
+
* AFTER `DOM.enable` resolves (Chromium rejects it otherwise). Resolves when
|
|
229
|
+
* Overlay is enabled; a rejection at any step is swallowed (the guest may be
|
|
230
|
+
* mid-teardown) and the cache entry is dropped so a later hover can retry.
|
|
231
|
+
*/
|
|
232
|
+
function ensureDomainsEnabled(wc) {
|
|
233
|
+
const cached = enablePromises.get(wc.id);
|
|
234
|
+
if (cached)
|
|
235
|
+
return cached;
|
|
236
|
+
wc.debugger.sendCommand('CSS.enable').catch(() => { });
|
|
237
|
+
const handshake = wc.debugger
|
|
238
|
+
.sendCommand('DOM.enable')
|
|
239
|
+
.then(() => {
|
|
240
|
+
if (wc.isDestroyed())
|
|
241
|
+
return;
|
|
242
|
+
return wc.debugger.sendCommand('Overlay.enable');
|
|
243
|
+
})
|
|
244
|
+
.then(() => undefined)
|
|
245
|
+
.catch(() => {
|
|
246
|
+
// Allow a retry on the next hover (e.g. the guest was mid-destroy).
|
|
247
|
+
enablePromises.delete(wc.id);
|
|
248
|
+
});
|
|
249
|
+
enablePromises.set(wc.id, handshake);
|
|
250
|
+
return handshake;
|
|
251
|
+
}
|
|
252
|
+
async function unhighlight(wc) {
|
|
253
|
+
if (wc.isDestroyed())
|
|
78
254
|
return;
|
|
79
255
|
try {
|
|
80
|
-
await wc.
|
|
256
|
+
await wc.debugger.sendCommand('Overlay.hideHighlight');
|
|
81
257
|
}
|
|
82
258
|
catch {
|
|
83
259
|
// best-effort
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Pure helpers for the native-host service-console forwarder.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* stack frame, so Chrome DevTools (attached
|
|
7
|
-
*
|
|
8
|
-
* instead of the developer's source.
|
|
9
|
-
* `Runtime.consoleAPICalled` instead, which preserves native source attribution.
|
|
4
|
+
* The service-host's console is captured via CDP `Runtime.consoleAPICalled`,
|
|
5
|
+
* which preserves native source attribution. A `console.*` monkeypatch in
|
|
6
|
+
* `service-host/preload.cjs` would add a stack frame, so Chrome DevTools (attached
|
|
7
|
+
* natively to the service host) would attribute EVERY service-layer log to the
|
|
8
|
+
* wrapper line instead of the developer's source.
|
|
10
9
|
*
|
|
11
10
|
* These functions turn the CDP event shape into the `GuestConsoleEntry` shape
|
|
12
11
|
* the existing console fan-out (automation `App.logAdded`) expects, WITHOUT any
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Pure helpers for the native-host service-console forwarder.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* stack frame, so Chrome DevTools (attached
|
|
7
|
-
*
|
|
8
|
-
* instead of the developer's source.
|
|
9
|
-
* `Runtime.consoleAPICalled` instead, which preserves native source attribution.
|
|
4
|
+
* The service-host's console is captured via CDP `Runtime.consoleAPICalled`,
|
|
5
|
+
* which preserves native source attribution. A `console.*` monkeypatch in
|
|
6
|
+
* `service-host/preload.cjs` would add a stack frame, so Chrome DevTools (attached
|
|
7
|
+
* natively to the service host) would attribute EVERY service-layer log to the
|
|
8
|
+
* wrapper line instead of the developer's source.
|
|
10
9
|
*
|
|
11
10
|
* These functions turn the CDP event shape into the `GuestConsoleEntry` shape
|
|
12
11
|
* the existing console fan-out (automation `App.logAdded`) expects, WITHOUT any
|
|
@@ -7,9 +7,8 @@ import { BrowserWindow } from 'electron';
|
|
|
7
7
|
* (release / releaseDestroyed), opt-in behind `DIMINA_PREWARM_POOL_SIZE`
|
|
8
8
|
* (default OFF). Only the SERVICE-HOST window is pooled; the simulator content
|
|
9
9
|
* WebContentsView (`view-manager.attachNativeSimulator`) is created fresh per
|
|
10
|
-
* attach. (
|
|
11
|
-
*
|
|
12
|
-
* Phase 4 — and that route has since been deleted outright.)
|
|
10
|
+
* attach. (A `<webview>`-tag arch is not poolable — Electron can't reparent a
|
|
11
|
+
* pre-warmed WebContents into a `<webview>`.)
|
|
13
12
|
*
|
|
14
13
|
* Lifecycle of a pool entry:
|
|
15
14
|
* warming — BrowserWindow constructed, page loading
|
|
@@ -2,11 +2,9 @@
|
|
|
2
2
|
* SimulatorStorageWatcher
|
|
3
3
|
*
|
|
4
4
|
* Attaches the Chrome DevTools Protocol debugger to the simulator <webview>
|
|
5
|
-
* and forwards `DOMStorage.*` events to the renderer host.
|
|
6
|
-
* preload-side localStorage.setItem hook that used to push storage changes
|
|
7
|
-
* via SimulatorChannel.Storage / StorageAll.
|
|
5
|
+
* and forwards `DOMStorage.*` events to the renderer host.
|
|
8
6
|
*
|
|
9
|
-
* Trade-offs vs
|
|
7
|
+
* Trade-offs vs a preload-side localStorage.setItem hook:
|
|
10
8
|
* + Uses standard browser protocol; no preload injection
|
|
11
9
|
* + Captures every change including ones bypassing wx (api-compat fallback,
|
|
12
10
|
* direct localStorage.setItem from devtools, etc.)
|
|
@@ -2,11 +2,9 @@
|
|
|
2
2
|
* SimulatorStorageWatcher
|
|
3
3
|
*
|
|
4
4
|
* Attaches the Chrome DevTools Protocol debugger to the simulator <webview>
|
|
5
|
-
* and forwards `DOMStorage.*` events to the renderer host.
|
|
6
|
-
* preload-side localStorage.setItem hook that used to push storage changes
|
|
7
|
-
* via SimulatorChannel.Storage / StorageAll.
|
|
5
|
+
* and forwards `DOMStorage.*` events to the renderer host.
|
|
8
6
|
*
|
|
9
|
-
* Trade-offs vs
|
|
7
|
+
* Trade-offs vs a preload-side localStorage.setItem hook:
|
|
10
8
|
* + Uses standard browser protocol; no preload injection
|
|
11
9
|
* + Captures every change including ones bypassing wx (api-compat fallback,
|
|
12
10
|
* direct localStorage.setItem from devtools, etc.)
|
|
@@ -155,8 +155,8 @@ export function setupSimulatorTempFiles(simSession) {
|
|
|
155
155
|
installOnSession(simSession);
|
|
156
156
|
// Apply to every per-project miniapp partition session (current + future).
|
|
157
157
|
const unregisterConfigurator = registerMiniappSessionConfigurator((sess) => installOnSession(sess));
|
|
158
|
-
//
|
|
159
|
-
//
|
|
158
|
+
// Renderer FSM → main fs operations bridge. The same simulator-only sender
|
|
159
|
+
// policy applies — registry instance is shared.
|
|
160
160
|
registry.handle('simulator:fs:read', (_event, payload) => handleFsRead(payload));
|
|
161
161
|
registry.handle('simulator:fs:write', (_event, payload) => handleFsWrite(payload));
|
|
162
162
|
registry.handle('simulator:fs:stat', (_event, payload) => handleFsStat(payload));
|
|
@@ -35,7 +35,10 @@
|
|
|
35
35
|
*/
|
|
36
36
|
/**
|
|
37
37
|
* Canonical DevTools panel view ids kept in the DEFAULT tab bar (stable front-end
|
|
38
|
-
* ids, unchanged for many Chromium releases): `elements`, `console`, `network
|
|
38
|
+
* ids, unchanged for many Chromium releases): `elements`, `console`, `network`,
|
|
39
|
+
* `sources`. Sources stays so a source-link click that isn't routed to Monaco
|
|
40
|
+
* (build/runtime chunks, framework frames) still has a panel to reveal in instead
|
|
41
|
+
* of silently no-op'ing.
|
|
39
42
|
*/
|
|
40
43
|
export declare const DEVTOOLS_KEPT_VIEW_IDS: readonly string[];
|
|
41
44
|
/**
|