@dimina-kit/devtools 0.4.0-dev.20260718143821 → 0.4.0-dev.20260728065133
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/app.js +67 -1
- package/dist/main/app/lifecycle.js +9 -0
- package/dist/main/index.bundle.js +928 -162
- package/dist/main/ipc/bridge-router.d.ts +36 -0
- package/dist/main/ipc/bridge-router.js +56 -0
- package/dist/main/ipc/index.d.ts +1 -0
- package/dist/main/ipc/index.js +1 -0
- package/dist/main/ipc/internal-devtools.d.ts +15 -0
- package/dist/main/ipc/internal-devtools.js +9 -0
- package/dist/main/services/compile-standby.js +4 -0
- package/dist/main/services/console-forward/cdp-inject.d.ts +18 -0
- package/dist/main/services/console-forward/cdp-inject.js +30 -0
- package/dist/main/services/console-forward/global-console-mirror.d.ts +33 -0
- package/dist/main/services/console-forward/global-console-mirror.js +71 -0
- package/dist/main/services/console-forward/global-diagnostics-mirror.d.ts +33 -0
- package/dist/main/services/console-forward/global-diagnostics-mirror.js +73 -0
- package/dist/main/services/console-forward/index.d.ts +15 -1
- package/dist/main/services/console-forward/index.js +42 -2
- package/dist/main/services/console-forward/internal-log.d.ts +11 -0
- package/dist/main/services/console-forward/internal-log.js +23 -0
- package/dist/main/services/console-forward/open-gated-relay.d.ts +67 -0
- package/dist/main/services/console-forward/open-gated-relay.js +55 -0
- package/dist/main/services/diagnostics/index.d.ts +10 -0
- package/dist/main/services/elements-forward/index.js +14 -96
- package/dist/main/services/network-forward/frontend-dispatch.d.ts +64 -0
- package/dist/main/services/network-forward/frontend-dispatch.js +113 -8
- package/dist/main/services/network-forward/global-body-gate.d.ts +46 -0
- package/dist/main/services/network-forward/global-body-gate.js +93 -0
- package/dist/main/services/network-forward/global-mirror-test-fixtures.d.ts +48 -0
- package/dist/main/services/network-forward/global-mirror-test-fixtures.js +96 -0
- package/dist/main/services/network-forward/index.d.ts +31 -0
- package/dist/main/services/network-forward/index.js +256 -8
- package/dist/main/services/network-forward/user-facing.d.ts +21 -0
- package/dist/main/services/network-forward/user-facing.js +50 -0
- package/dist/main/services/views/console-filter.d.ts +145 -0
- package/dist/main/services/views/console-filter.js +233 -0
- package/dist/main/services/views/frontend-bootstrap-gate.d.ts +59 -0
- package/dist/main/services/views/frontend-bootstrap-gate.js +117 -0
- package/dist/main/services/views/native-simulator-devtools-host.js +138 -30
- package/dist/main/services/views/view-manager-devtools-host-test-fixtures.d.ts +76 -0
- package/dist/main/services/views/view-manager-devtools-host-test-fixtures.js +182 -0
- package/dist/main/services/workbench-context.d.ts +10 -0
- package/dist/main/utils/theme.d.ts +10 -6
- package/dist/main/utils/theme.js +10 -6
- package/dist/main/windows/internal-devtools-window/index.d.ts +66 -0
- package/dist/main/windows/internal-devtools-window/index.js +160 -0
- package/dist/native-host/render/render.js +89 -73
- package/dist/native-host/service/service.js +2 -2
- package/dist/preload/windows/host-toolbar-runtime.cjs.map +1 -1
- package/dist/preload/windows/simulator.cjs.map +1 -1
- package/dist/renderer/assets/index-M5NDq_vi.js +49 -0
- package/dist/renderer/assets/{input-c2OcrrZy.js → input-dsgeTmVX.js} +2 -2
- package/dist/renderer/assets/ipc-transport-BgFdT9bT.js +9 -0
- package/dist/renderer/assets/{popover-kv2qspWz.js → popover-Disfu1cx.js} +2 -2
- package/dist/renderer/assets/{select-DKqzMtTh.js → select-DAw1hzkl.js} +2 -2
- package/dist/renderer/assets/{settings-dJNKTGr8.js → settings-M_8GFW2M.js} +2 -2
- package/dist/renderer/assets/settings-api-DFR5eDl_.js +2 -0
- package/dist/renderer/assets/{workbenchSettings-DQ_VP5-W.js → workbenchSettings-CxdXeoOu.js} +2 -2
- package/dist/renderer/entries/main/index.html +5 -5
- package/dist/renderer/entries/popover/index.html +4 -4
- package/dist/renderer/entries/settings/index.html +4 -4
- package/dist/renderer/entries/workbench-settings/index.html +3 -3
- package/dist/shared/ipc-channels.d.ts +3 -0
- package/dist/shared/ipc-channels.js +6 -0
- package/package.json +7 -7
- package/dist/renderer/assets/index-DfDIvgvK.js +0 -49
- package/dist/renderer/assets/ipc-transport-CuDJ07aE.js +0 -9
- package/dist/renderer/assets/settings-api-qpXKDRXt.js +0 -2
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared test scaffolding for the Phase 2 "global mirror" test suites
|
|
3
|
+
* (`global-mirror.test.ts` + `global-mirror-bugfixes.test.ts`), split out so
|
|
4
|
+
* neither test file needs to duplicate these fixtures. Pure test scaffolding,
|
|
5
|
+
* not the code under test — see network-forward/index.test.ts for the
|
|
6
|
+
* canonical source these were originally copied from.
|
|
7
|
+
*/
|
|
8
|
+
import { vi } from 'vitest';
|
|
9
|
+
export declare function flushMicrotasks(): Promise<void>;
|
|
10
|
+
export declare function makeSimWc(): {
|
|
11
|
+
wc: Electron.WebContents;
|
|
12
|
+
dbg: {
|
|
13
|
+
isAttached: () => boolean;
|
|
14
|
+
attach: import("vitest").Mock<() => void>;
|
|
15
|
+
detach: import("vitest").Mock<() => void>;
|
|
16
|
+
sendCommand: import("vitest").Mock<(_method: string, _params?: object) => Promise<unknown>>;
|
|
17
|
+
on: (ev: string, fn: (...args: unknown[]) => void) => void;
|
|
18
|
+
removeListener: (ev: string, fn: (...args: unknown[]) => void) => void;
|
|
19
|
+
};
|
|
20
|
+
sendCommand: import("vitest").Mock<(_method: string, _params?: object) => Promise<unknown>>;
|
|
21
|
+
emitMessage: (method: string, params: unknown) => void;
|
|
22
|
+
};
|
|
23
|
+
export declare function makeServiceWc(): {
|
|
24
|
+
wc: Electron.WebContents;
|
|
25
|
+
exec: import("vitest").Mock<(_script: string, _userGesture?: boolean) => Promise<undefined>>;
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* A DevTools FRONT-END host wc. The injected dispatch script returns `true` when
|
|
29
|
+
* `window.DevToolsAPI.dispatchMessage` is "present", so the fake resolves the
|
|
30
|
+
* configured value to simulate the API being ready (true) or still booting
|
|
31
|
+
* (false → forwarder retries / falls back).
|
|
32
|
+
*/
|
|
33
|
+
export declare function makeDevtoolsWc(apiReady?: boolean | (() => boolean), isLoading?: () => boolean): {
|
|
34
|
+
wc: Electron.WebContents;
|
|
35
|
+
exec: import("vitest").Mock<(_script: string, _userGesture?: boolean) => Promise<boolean>>;
|
|
36
|
+
emitDestroyed: () => void;
|
|
37
|
+
};
|
|
38
|
+
/** Decode the messages array embedded in a buildDispatchScript() source. */
|
|
39
|
+
export declare function decodeDispatched(script: string): Array<{
|
|
40
|
+
method: string;
|
|
41
|
+
params: unknown;
|
|
42
|
+
}>;
|
|
43
|
+
/** All events mirrored into a fake devtools host wc across every exec() call. */
|
|
44
|
+
export declare function allDispatched(exec: ReturnType<typeof vi.fn>): Array<{
|
|
45
|
+
method: string;
|
|
46
|
+
params: unknown;
|
|
47
|
+
}>;
|
|
48
|
+
//# sourceMappingURL=global-mirror-test-fixtures.d.ts.map
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shared test scaffolding for the Phase 2 "global mirror" test suites
|
|
3
|
+
* (`global-mirror.test.ts` + `global-mirror-bugfixes.test.ts`), split out so
|
|
4
|
+
* neither test file needs to duplicate these fixtures. Pure test scaffolding,
|
|
5
|
+
* not the code under test — see network-forward/index.test.ts for the
|
|
6
|
+
* canonical source these were originally copied from.
|
|
7
|
+
*/
|
|
8
|
+
import { vi } from 'vitest';
|
|
9
|
+
export async function flushMicrotasks() {
|
|
10
|
+
await Promise.resolve();
|
|
11
|
+
await Promise.resolve();
|
|
12
|
+
}
|
|
13
|
+
export function makeSimWc() {
|
|
14
|
+
let attached = false;
|
|
15
|
+
const listeners = new Map();
|
|
16
|
+
const sendCommand = vi.fn((_method, _params) => Promise.resolve({}));
|
|
17
|
+
const dbg = {
|
|
18
|
+
isAttached: () => attached,
|
|
19
|
+
attach: vi.fn(() => { attached = true; }),
|
|
20
|
+
detach: vi.fn(() => { attached = false; }),
|
|
21
|
+
sendCommand,
|
|
22
|
+
on: (ev, fn) => {
|
|
23
|
+
if (!listeners.has(ev))
|
|
24
|
+
listeners.set(ev, new Set());
|
|
25
|
+
listeners.get(ev).add(fn);
|
|
26
|
+
},
|
|
27
|
+
removeListener: (ev, fn) => {
|
|
28
|
+
listeners.get(ev)?.delete(fn);
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
const wc = {
|
|
32
|
+
isDestroyed: () => false,
|
|
33
|
+
debugger: dbg,
|
|
34
|
+
once: () => { },
|
|
35
|
+
removeListener: () => { },
|
|
36
|
+
};
|
|
37
|
+
const emitMessage = (method, params) => {
|
|
38
|
+
for (const fn of listeners.get('message') ?? [])
|
|
39
|
+
fn({}, method, params);
|
|
40
|
+
};
|
|
41
|
+
return { wc, dbg, sendCommand, emitMessage };
|
|
42
|
+
}
|
|
43
|
+
export function makeServiceWc() {
|
|
44
|
+
const exec = vi.fn((_script, _userGesture) => Promise.resolve(undefined));
|
|
45
|
+
const wc = { isDestroyed: () => false, executeJavaScript: exec };
|
|
46
|
+
return { wc, exec };
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* A DevTools FRONT-END host wc. The injected dispatch script returns `true` when
|
|
50
|
+
* `window.DevToolsAPI.dispatchMessage` is "present", so the fake resolves the
|
|
51
|
+
* configured value to simulate the API being ready (true) or still booting
|
|
52
|
+
* (false → forwarder retries / falls back).
|
|
53
|
+
*/
|
|
54
|
+
export function makeDevtoolsWc(apiReady = true, isLoading = () => false) {
|
|
55
|
+
const exec = vi.fn((_script, _userGesture) => Promise.resolve(typeof apiReady === 'function' ? apiReady() : apiReady));
|
|
56
|
+
const destroyedListeners = new Set();
|
|
57
|
+
const wc = {
|
|
58
|
+
isDestroyed: () => false,
|
|
59
|
+
isLoading,
|
|
60
|
+
getURL: () => 'devtools://devtools/bundled/devtools_app.html',
|
|
61
|
+
executeJavaScript: exec,
|
|
62
|
+
once: (ev, fn) => { if (ev === 'destroyed')
|
|
63
|
+
destroyedListeners.add(fn); },
|
|
64
|
+
removeListener: (ev, fn) => { if (ev === 'destroyed')
|
|
65
|
+
destroyedListeners.delete(fn); },
|
|
66
|
+
};
|
|
67
|
+
const emitDestroyed = () => { for (const fn of [...destroyedListeners])
|
|
68
|
+
fn(); };
|
|
69
|
+
return { wc, exec, emitDestroyed };
|
|
70
|
+
}
|
|
71
|
+
/** Decode the messages array embedded in a buildDispatchScript() source. */
|
|
72
|
+
export function decodeDispatched(script) {
|
|
73
|
+
const start = script.indexOf('JSON.parse("');
|
|
74
|
+
if (start < 0)
|
|
75
|
+
return [];
|
|
76
|
+
let i = start + 'JSON.parse('.length;
|
|
77
|
+
const open = i;
|
|
78
|
+
i++;
|
|
79
|
+
for (; i < script.length; i++) {
|
|
80
|
+
if (script[i] === '\\') {
|
|
81
|
+
i++;
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (script[i] === '"')
|
|
85
|
+
break;
|
|
86
|
+
}
|
|
87
|
+
const literal = script.slice(open, i + 1);
|
|
88
|
+
const inner = JSON.parse(literal);
|
|
89
|
+
const arr = JSON.parse(inner);
|
|
90
|
+
return arr.map((s) => JSON.parse(s));
|
|
91
|
+
}
|
|
92
|
+
/** All events mirrored into a fake devtools host wc across every exec() call. */
|
|
93
|
+
export function allDispatched(exec) {
|
|
94
|
+
return exec.mock.calls.flatMap((c) => decodeDispatched(String(c[0])));
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=global-mirror-test-fixtures.js.map
|
|
@@ -128,6 +128,26 @@ export interface NetworkForwarderBridge {
|
|
|
128
128
|
* `setDevtoolsHost`. Null until the DevTools host view exists.
|
|
129
129
|
*/
|
|
130
130
|
getDevtoolsWc?(): WebContents | null;
|
|
131
|
+
/**
|
|
132
|
+
* The dimina resource server's baseUrl (e.g. `'http://127.0.0.1:54321/'`),
|
|
133
|
+
* when running — consulted by `isUserFacingRequest` so the framework
|
|
134
|
+
* bundle it serves is classified as host-internal, not business traffic.
|
|
135
|
+
* Absent → that origin check is simply skipped (the scheme rule alone
|
|
136
|
+
* still filters out file:// / difile:// / devtools:// framework loads).
|
|
137
|
+
*/
|
|
138
|
+
getResourceServerBaseUrl?(): string | null;
|
|
139
|
+
/**
|
|
140
|
+
* The simulator's own static-asset server baseUrl (serves `simulator.html`
|
|
141
|
+
* + its JS/CSS — a devkit-owned server, independent from the resource
|
|
142
|
+
* server above, on its own dynamically-assigned port), when a project is
|
|
143
|
+
* open — consulted by `isUserFacingRequest` alongside
|
|
144
|
+
* `getResourceServerBaseUrl` so the simulator SHELL's own asset loading
|
|
145
|
+
* (captured because `attachSimulator` CDP-observes that whole
|
|
146
|
+
* WebContentsView, not just the mini-app's own `wx.request` calls) isn't
|
|
147
|
+
* misclassified as the developer's business traffic. Absent → that origin
|
|
148
|
+
* check is simply skipped.
|
|
149
|
+
*/
|
|
150
|
+
getSimulatorServerBaseUrl?(): string | null;
|
|
131
151
|
/**
|
|
132
152
|
* Optional connection-layer registry (`@dimina-kit/electron-deck/main`). When
|
|
133
153
|
* present, per-webContents teardowns route through `acquire(wc).own(d)` so the
|
|
@@ -179,6 +199,17 @@ export interface NetworkForwarder extends Disposable {
|
|
|
179
199
|
* we fall back to the console line. Re-callable across DevTools re-creates.
|
|
180
200
|
*/
|
|
181
201
|
setDevtoolsHost(wc: WebContents | null): void;
|
|
202
|
+
/**
|
|
203
|
+
* Point the forwarder at the WebContents hosting the standalone internal
|
|
204
|
+
* (app-wide) DevTools window's front-end — the global mirror sink.
|
|
205
|
+
* Unlike `setDevtoolsHost` (the user-facing sink), this has NO probing/
|
|
206
|
+
* ready/degraded state machine and NO console fallback: every
|
|
207
|
+
* FORWARDED_METHODS event is mirrored here raw and UNFILTERED (no
|
|
208
|
+
* `isUserFacingRequest` gating) whenever a host is set, best-effort (a
|
|
209
|
+
* failed dispatch is silently dropped — nobody depends on this path having
|
|
210
|
+
* a fallback). Pass null when the window closes.
|
|
211
|
+
*/
|
|
212
|
+
setGlobalDevtoolsHost(wc: WebContents | null): void;
|
|
182
213
|
/**
|
|
183
214
|
* Manually surface a request that no `webContents.debugger` can observe
|
|
184
215
|
* (e.g. a main-process direct send). Uses the console fallback sink.
|
|
@@ -4,6 +4,8 @@ import { packDispatchBatch } from './dispatch-batch.js';
|
|
|
4
4
|
import { PrefetchCache, DEFAULT_PER_ENTRY_MAX_CHARS } from './body-cache.js';
|
|
5
5
|
import { PROBE_DEVTOOLS_API, MAX_SINGLE_DISPATCH_CHARS, CHUNK_CHARS, buildChunkedDispatchScript } from './frontend-dispatch.js';
|
|
6
6
|
import { createCdpSessionBroker } from '../cdp-session/index.js';
|
|
7
|
+
import { isUserFacingRequest } from './user-facing.js';
|
|
8
|
+
import { installGlobalNetworkBodyGate } from './global-body-gate.js';
|
|
7
9
|
/**
|
|
8
10
|
* Namespace prefix of every virtual requestId this forwarder injects into the
|
|
9
11
|
* DevTools front-end. SINGLE SOURCE for the literal: the front-end outbound
|
|
@@ -302,6 +304,169 @@ export function createNetworkForwarder(bridge) {
|
|
|
302
304
|
let devtoolsWc = null;
|
|
303
305
|
// Teardown for the wc 'destroyed' watcher on the current host (clears the host).
|
|
304
306
|
let devtoolsHostDisposable = null;
|
|
307
|
+
// ── Global mirror sink ──────────────────────────────────────────────────────
|
|
308
|
+
// The standalone internal (app-wide) DevTools window's front-end host wc.
|
|
309
|
+
// Deliberately NO probing/ready/degraded state machine and NO console
|
|
310
|
+
// fallback — see setGlobalDevtoolsHost's doc comment.
|
|
311
|
+
let globalDevtoolsWc = null;
|
|
312
|
+
let globalDevtoolsHostDisposable = null;
|
|
313
|
+
// Dedicated network-only outbound gate (body/post-data lookups) for the
|
|
314
|
+
// CURRENT global host — never elements-forward's gate, see
|
|
315
|
+
// global-body-gate.ts's header for why. Stopped whenever the host changes
|
|
316
|
+
// or clears.
|
|
317
|
+
let globalBodyGateStop = null;
|
|
318
|
+
// Bounded in-order hold for global-mirror messages arriving while the
|
|
319
|
+
// global host's front-end has not settled yet (its boot window) — without
|
|
320
|
+
// it those events are silently unrecoverable, breaking the window's
|
|
321
|
+
// full-stream promise. Scoped strictly to the CURRENT host: cleared when
|
|
322
|
+
// the host changes/clears, never replayed into a different host.
|
|
323
|
+
const MAX_GLOBAL_PENDING_QUEUE = 2000;
|
|
324
|
+
let globalPendingQueue = [];
|
|
325
|
+
let globalPendingOverflowWarned = false;
|
|
326
|
+
let globalFlushTimer = null;
|
|
327
|
+
// Wall-clock bound on the settle-poll (not on the queue itself): a
|
|
328
|
+
// front-end that never settles stops being polled after this, and the
|
|
329
|
+
// still-held queue flushes via the next incoming event instead.
|
|
330
|
+
const GLOBAL_FLUSH_POLL_MAX_MS = 60_000;
|
|
331
|
+
function clearGlobalPending() {
|
|
332
|
+
globalPendingQueue = [];
|
|
333
|
+
globalPendingOverflowWarned = false;
|
|
334
|
+
if (globalFlushTimer) {
|
|
335
|
+
clearTimeout(globalFlushTimer);
|
|
336
|
+
globalFlushTimer = null;
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
function scheduleGlobalFlush(startedAt = Date.now()) {
|
|
340
|
+
if (globalFlushTimer)
|
|
341
|
+
return;
|
|
342
|
+
if (Date.now() - startedAt >= GLOBAL_FLUSH_POLL_MAX_MS)
|
|
343
|
+
return;
|
|
344
|
+
// unref'd like scheduleRenderGuestRetry's timer: a settle-poll must
|
|
345
|
+
// never be what keeps the process (or a test file) alive.
|
|
346
|
+
globalFlushTimer = setTimeout(() => {
|
|
347
|
+
globalFlushTimer = null;
|
|
348
|
+
if (globalPendingQueue.length === 0)
|
|
349
|
+
return;
|
|
350
|
+
if (!globalDevtoolsWc || globalDevtoolsWc.isDestroyed()) {
|
|
351
|
+
clearGlobalPending();
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
if (!isFrontendSettled(globalDevtoolsWc)) {
|
|
355
|
+
scheduleGlobalFlush(startedAt);
|
|
356
|
+
return;
|
|
357
|
+
}
|
|
358
|
+
flushGlobalPending();
|
|
359
|
+
}, READY_RETRY_MS);
|
|
360
|
+
globalFlushTimer.unref?.();
|
|
361
|
+
}
|
|
362
|
+
/** Drain the pending queue into the (settled) global host, oldest first,
|
|
363
|
+
* packed greedily under the same per-script size bound as the native sink —
|
|
364
|
+
* and through the SAME chunked transport for messages past
|
|
365
|
+
* MAX_SINGLE_DISPATCH_CHARS: a single oversized message stitched into one
|
|
366
|
+
* giant executeJavaScript overflows the IPC/script limit and the whole
|
|
367
|
+
* call rejects into a silent drop. */
|
|
368
|
+
function flushGlobalPending() {
|
|
369
|
+
if (globalPendingQueue.length === 0)
|
|
370
|
+
return;
|
|
371
|
+
const target = globalDevtoolsWc;
|
|
372
|
+
if (!target || target.isDestroyed()) {
|
|
373
|
+
clearGlobalPending();
|
|
374
|
+
return;
|
|
375
|
+
}
|
|
376
|
+
while (globalPendingQueue.length > 0) {
|
|
377
|
+
const { batch, chunked, remaining } = packDispatchBatch(globalPendingQueue, MAX_SINGLE_DISPATCH_CHARS, MAX_BATCH_CHARS);
|
|
378
|
+
globalPendingQueue = remaining;
|
|
379
|
+
for (const msg of chunked)
|
|
380
|
+
dispatchChunked(target, msg);
|
|
381
|
+
if (batch.length > 0) {
|
|
382
|
+
target.executeJavaScript(buildDispatchScript(batch), true).catch(() => { });
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
globalPendingOverflowWarned = false;
|
|
386
|
+
}
|
|
387
|
+
function enqueueGlobalPending(json) {
|
|
388
|
+
if (globalPendingQueue.length >= MAX_GLOBAL_PENDING_QUEUE) {
|
|
389
|
+
globalPendingQueue.shift();
|
|
390
|
+
// Not silent: dropping past the cap means the window's full-stream
|
|
391
|
+
// promise is degraded — say so once per overflow episode, not per event.
|
|
392
|
+
if (!globalPendingOverflowWarned) {
|
|
393
|
+
globalPendingOverflowWarned = true;
|
|
394
|
+
console.warn(`[network-forward] global mirror queue overflow (cap ${MAX_GLOBAL_PENDING_QUEUE}): dropping oldest events while the debug window front-end is still loading`);
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
globalPendingQueue.push(json);
|
|
398
|
+
scheduleGlobalFlush();
|
|
399
|
+
}
|
|
400
|
+
function applyGlobalDevtoolsHost(host) {
|
|
401
|
+
globalDevtoolsHostDisposable?.dispose();
|
|
402
|
+
globalDevtoolsHostDisposable = null;
|
|
403
|
+
globalBodyGateStop?.();
|
|
404
|
+
globalBodyGateStop = null;
|
|
405
|
+
// Queued events belong to the PREVIOUS host's boot window — never carry
|
|
406
|
+
// them across a host change (or into "no host").
|
|
407
|
+
clearGlobalPending();
|
|
408
|
+
globalDevtoolsWc = host && !host.isDestroyed() ? host : null;
|
|
409
|
+
if (!globalDevtoolsWc)
|
|
410
|
+
return;
|
|
411
|
+
const target = globalDevtoolsWc;
|
|
412
|
+
const onHostDestroyed = () => {
|
|
413
|
+
if (globalDevtoolsWc === target)
|
|
414
|
+
globalDevtoolsWc = null;
|
|
415
|
+
globalBodyGateStop?.();
|
|
416
|
+
globalBodyGateStop = null;
|
|
417
|
+
clearGlobalPending();
|
|
418
|
+
};
|
|
419
|
+
const reg = bridge.connections;
|
|
420
|
+
if (reg && typeof target.once === 'function') {
|
|
421
|
+
const owned = reg.acquire(target).own(onHostDestroyed);
|
|
422
|
+
globalDevtoolsHostDisposable = toDisposable(() => owned.dispose());
|
|
423
|
+
}
|
|
424
|
+
else {
|
|
425
|
+
if (typeof target.once === 'function')
|
|
426
|
+
target.once('destroyed', onHostDestroyed);
|
|
427
|
+
globalDevtoolsHostDisposable = toDisposable(() => {
|
|
428
|
+
try {
|
|
429
|
+
target.removeListener?.('destroyed', onHostDestroyed);
|
|
430
|
+
}
|
|
431
|
+
catch { /* gone */ }
|
|
432
|
+
});
|
|
433
|
+
}
|
|
434
|
+
globalBodyGateStop = installGlobalNetworkBodyGate(target, {
|
|
435
|
+
getResponseBody: (requestId) => bodyCache.lookup(requestId),
|
|
436
|
+
getRequestPostData: (requestId) => postDataCache.lookup(requestId),
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
/** Best-effort mirror of one raw CDP message into the global host. */
|
|
440
|
+
function dispatchToGlobal(method, params) {
|
|
441
|
+
if (!globalDevtoolsWc || globalDevtoolsWc.isDestroyed())
|
|
442
|
+
return;
|
|
443
|
+
let json;
|
|
444
|
+
try {
|
|
445
|
+
json = JSON.stringify({ method, params });
|
|
446
|
+
}
|
|
447
|
+
catch {
|
|
448
|
+
return;
|
|
449
|
+
}
|
|
450
|
+
// Same settled gate every other injection point in this file uses: an
|
|
451
|
+
// unsettled front-end wipes its state on load anyway, and executeJavaScript
|
|
452
|
+
// against it queues one did-stop-loading waiter per call — piling toward
|
|
453
|
+
// the MaxListeners ceiling during the global host's boot window. Unlike
|
|
454
|
+
// those injection points, the events here are NOT re-derivable later, so
|
|
455
|
+
// they queue for a post-settle flush instead of dropping.
|
|
456
|
+
if (!isFrontendSettled(globalDevtoolsWc)) {
|
|
457
|
+
enqueueGlobalPending(json);
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
// Anything still queued flushes first so delivery order matches arrival.
|
|
461
|
+
flushGlobalPending();
|
|
462
|
+
// Oversized single messages take the chunked transport, mirroring the
|
|
463
|
+
// native sink's flushDispatch — see flushGlobalPending's doc.
|
|
464
|
+
if (json.length > MAX_SINGLE_DISPATCH_CHARS) {
|
|
465
|
+
dispatchChunked(globalDevtoolsWc, json);
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
globalDevtoolsWc.executeJavaScript(buildDispatchScript([json]), true).catch(() => { });
|
|
469
|
+
}
|
|
305
470
|
// ── Native-sink state machine ─────────────────────────────────────────────
|
|
306
471
|
let sink = 'idle';
|
|
307
472
|
// Buffered completed-request records while 'probing' — flushed to console if we
|
|
@@ -625,6 +790,12 @@ export function createNetworkForwarder(bridge) {
|
|
|
625
790
|
// Fallback bookkeeping: requestId → in-flight request, for the console line
|
|
626
791
|
// when the native dispatch path is unusable.
|
|
627
792
|
const pending = new Map();
|
|
793
|
+
// User-facing classification, decided ONCE at
|
|
794
|
+
// requestWillBeSent (the only event carrying a url) and consulted by every
|
|
795
|
+
// later event on the same rawId — later events (responseReceived/
|
|
796
|
+
// loadingFinished/loadingFailed) carry no url, so they cannot re-derive
|
|
797
|
+
// the verdict; they must remember it.
|
|
798
|
+
const userFacingByRawId = new Map();
|
|
628
799
|
// Raw ids whose requestWillBeSent announced a post body that was NOT
|
|
629
800
|
// inlined (`hasPostData` without `postData`) — the only case the front-end
|
|
630
801
|
// round-trips `Network.getRequestPostData`. Consumed at loadingFinished.
|
|
@@ -638,9 +809,15 @@ export function createNetworkForwarder(bridge) {
|
|
|
638
809
|
* bodies would only burn memory.
|
|
639
810
|
*/
|
|
640
811
|
const prefetchBodies = (rawId, encodedDataLength) => {
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
812
|
+
// Skip only when NEITHER consumer can use the body: the user-facing
|
|
813
|
+
// sink is unusable (degraded, or idle with no host) AND the global
|
|
814
|
+
// mirror has no host either. Either alone is enough reason to prefetch
|
|
815
|
+
// — global-body-gate answers Network.getResponseBody/getRequestPostData
|
|
816
|
+
// from this same cache, and it must not 404 just because the
|
|
817
|
+
// user-facing sink happens to be closed/degraded.
|
|
818
|
+
const userSinkUsable = sink !== 'degraded' && !(sink === 'idle' && !resolveDevtoolsWc());
|
|
819
|
+
const globalUsable = globalDevtoolsWc !== null && !globalDevtoolsWc.isDestroyed();
|
|
820
|
+
if (!userSinkUsable && !globalUsable)
|
|
644
821
|
return;
|
|
645
822
|
const virtualId = ns.resolve(rawId);
|
|
646
823
|
// Skip the CDP round-trip entirely for a response already known (from
|
|
@@ -693,23 +870,32 @@ export function createNetworkForwarder(bridge) {
|
|
|
693
870
|
if (req)
|
|
694
871
|
req.status = p.response?.status ?? 0;
|
|
695
872
|
}
|
|
873
|
+
/** Shared "request terminated" bookkeeping for both loadingFinished and
|
|
874
|
+
* loadingFailed: drop the classification + pending record, returning the
|
|
875
|
+
* pending entry (or undefined if it was already gone/never seen). */
|
|
876
|
+
function retirePending(rawId) {
|
|
877
|
+
userFacingByRawId.delete(rawId);
|
|
878
|
+
const req = pending.get(rawId);
|
|
879
|
+
if (!req)
|
|
880
|
+
return undefined;
|
|
881
|
+
pending.delete(rawId);
|
|
882
|
+
return req;
|
|
883
|
+
}
|
|
696
884
|
function onLoadingFinished(params) {
|
|
697
885
|
const p = params;
|
|
698
886
|
if (typeof p?.requestId === 'string')
|
|
699
887
|
prefetchBodies(p.requestId, p.encodedDataLength);
|
|
700
|
-
const req =
|
|
888
|
+
const req = retirePending(p?.requestId);
|
|
701
889
|
if (!req)
|
|
702
890
|
return;
|
|
703
|
-
pending.delete(p.requestId);
|
|
704
891
|
maybeFallback({ source, url: req.url, method: req.method, status: req.status });
|
|
705
892
|
}
|
|
706
893
|
function onLoadingFailed(params) {
|
|
707
894
|
const p = params;
|
|
708
895
|
postDataWanted.delete(p?.requestId);
|
|
709
|
-
const req =
|
|
896
|
+
const req = retirePending(p?.requestId);
|
|
710
897
|
if (!req)
|
|
711
898
|
return;
|
|
712
|
-
pending.delete(p.requestId);
|
|
713
899
|
maybeFallback({
|
|
714
900
|
source,
|
|
715
901
|
url: req.url,
|
|
@@ -724,11 +910,61 @@ export function createNetworkForwarder(bridge) {
|
|
|
724
910
|
'Network.loadingFinished': onLoadingFinished,
|
|
725
911
|
'Network.loadingFailed': onLoadingFailed,
|
|
726
912
|
};
|
|
913
|
+
/**
|
|
914
|
+
* Resolve (and, on `requestWillBeSent`, record) whether `rawId` is
|
|
915
|
+
* user-facing. MUST be computed eagerly here — not deferred to
|
|
916
|
+
* `onRequestWillBeSent` below, which runs AFTER this in `onMessage` — a
|
|
917
|
+
* `requestWillBeSent` is itself a FORWARDED_METHODS event whose own
|
|
918
|
+
* routing decision depends on the very verdict it establishes. Every
|
|
919
|
+
* later event for the same rawId (no url of its own) just looks up the
|
|
920
|
+
* recorded verdict; an unknown rawId fails open (user-facing) rather
|
|
921
|
+
* than silently hiding it.
|
|
922
|
+
*/
|
|
923
|
+
function resolveUserFacing(method, params, rawId) {
|
|
924
|
+
if (method === 'Network.requestWillBeSent') {
|
|
925
|
+
const url = params?.request?.url;
|
|
926
|
+
const verdict = typeof url === 'string'
|
|
927
|
+
? isUserFacingRequest(url, [bridge.getResourceServerBaseUrl?.(), bridge.getSimulatorServerBaseUrl?.()])
|
|
928
|
+
: true;
|
|
929
|
+
if (rawId) {
|
|
930
|
+
if (userFacingByRawId.size >= MAX_PENDING)
|
|
931
|
+
userFacingByRawId.clear();
|
|
932
|
+
userFacingByRawId.set(rawId, verdict);
|
|
933
|
+
}
|
|
934
|
+
return verdict;
|
|
935
|
+
}
|
|
936
|
+
if (rawId && userFacingByRawId.has(rawId))
|
|
937
|
+
return userFacingByRawId.get(rawId);
|
|
938
|
+
// `Network.requestWillBeSentExtraInfo` can arrive BEFORE its own
|
|
939
|
+
// `requestWillBeSent` (real CDP ordering) — it carries no url, so an
|
|
940
|
+
// unrecorded rawId here means "classification not yet known", not
|
|
941
|
+
// "capture attached mid-flight". Fail CLOSED for this one case: the
|
|
942
|
+
// very next event for the same rawId is (almost always) the real
|
|
943
|
+
// requestWillBeSent, which establishes the true verdict immediately —
|
|
944
|
+
// dropping this one early line is a far smaller cost than leaking an
|
|
945
|
+
// internal resource's ExtraInfo into the user-facing panel before its
|
|
946
|
+
// classification is even known. Every OTHER "rawId never seen" case
|
|
947
|
+
// (e.g. capture attached mid-flight, no requestWillBeSent ever coming)
|
|
948
|
+
// keeps failing open — there is no better signal available there.
|
|
949
|
+
if (method === 'Network.requestWillBeSentExtraInfo')
|
|
950
|
+
return false;
|
|
951
|
+
return true;
|
|
952
|
+
}
|
|
727
953
|
const onMessage = (_event, method, params) => {
|
|
728
954
|
// ── Primary sink: forward the raw CDP event into the DevTools front-end ──
|
|
729
955
|
if (FORWARDED_METHODS.has(method)) {
|
|
730
956
|
const rewritten = rewriteRequestId(method, params, ns);
|
|
731
|
-
|
|
957
|
+
// Global mirror: full, unfiltered — independent of the
|
|
958
|
+
// user-facing sink's classification and state machine.
|
|
959
|
+
dispatchToGlobal(rewritten.method, rewritten.params);
|
|
960
|
+
// User-facing sink: only requests isUserFacingRequest() judged as the
|
|
961
|
+
// developer's own business traffic — internal/framework resource
|
|
962
|
+
// loads are mirrored to the global window ONLY.
|
|
963
|
+
const rawId = params?.requestId;
|
|
964
|
+
const userFacing = resolveUserFacing(method, params, typeof rawId === 'string' ? rawId : undefined);
|
|
965
|
+
if (userFacing) {
|
|
966
|
+
enqueueNative(rewritten.method, rewritten.params);
|
|
967
|
+
}
|
|
732
968
|
}
|
|
733
969
|
else if (REWRITE_REQUEST_ID_METHODS.has(method)) {
|
|
734
970
|
// Methods we namespace but don't forward (dataReceived): still resolve
|
|
@@ -1024,6 +1260,17 @@ export function createNetworkForwarder(bridge) {
|
|
|
1024
1260
|
readyRetryTimer = null;
|
|
1025
1261
|
}
|
|
1026
1262
|
});
|
|
1263
|
+
registry.add(() => {
|
|
1264
|
+
globalDevtoolsHostDisposable?.dispose();
|
|
1265
|
+
globalDevtoolsHostDisposable = null;
|
|
1266
|
+
globalBodyGateStop?.();
|
|
1267
|
+
globalBodyGateStop = null;
|
|
1268
|
+
globalDevtoolsWc = null;
|
|
1269
|
+
// A live settle-poll timer at dispose time is a real-timer leak (this
|
|
1270
|
+
// suite's flaky-test history: undisposed timers adopted by later tests'
|
|
1271
|
+
// fake clocks).
|
|
1272
|
+
clearGlobalPending();
|
|
1273
|
+
});
|
|
1027
1274
|
registry.add(() => {
|
|
1028
1275
|
bodyCache.clear();
|
|
1029
1276
|
postDataCache.clear();
|
|
@@ -1051,6 +1298,7 @@ export function createNetworkForwarder(bridge) {
|
|
|
1051
1298
|
detachSimulator,
|
|
1052
1299
|
attachRenderGuest,
|
|
1053
1300
|
setDevtoolsHost: (wc) => applyDevtoolsHost(wc),
|
|
1301
|
+
setGlobalDevtoolsHost: (wc) => applyGlobalDevtoolsHost(wc),
|
|
1054
1302
|
// report() has no observing debugger, so there's no live CDP event to push
|
|
1055
1303
|
// natively — surface it via the console fallback line.
|
|
1056
1304
|
report: (record) => forwardToConsole(record),
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Judges whether a captured network request belongs on the user-facing
|
|
3
|
+
* (right-panel) DevTools Network tab, or should be hidden there — a
|
|
4
|
+
* framework/host-internal resource load, visible only in the standalone
|
|
5
|
+
* internal DevTools window's unfiltered global mirror.
|
|
6
|
+
*
|
|
7
|
+
* Single source of truth for the split (network-forward's user-facing sink
|
|
8
|
+
* and the global mirror both key off this): scheme must be one of
|
|
9
|
+
* http/https/ws/wss, AND the request's origin must not match ANY of
|
|
10
|
+
* `internalOrigins` — each is a base URL of a server this app itself runs
|
|
11
|
+
* whose own traffic is host-internal, not the developer's business traffic.
|
|
12
|
+
* There are currently two: the resource server (serves the compiled
|
|
13
|
+
* mini-app bundle) and the simulator's own static-asset server (serves
|
|
14
|
+
* `simulator.html` + its JS/CSS — a THIRD-PARTY-to-the-user-code origin the
|
|
15
|
+
* `attachSimulator` CDP session also observes, since it captures the whole
|
|
16
|
+
* simulator WebContentsView's network activity, not just the mini-app's own
|
|
17
|
+
* `wx.request` calls). `null`/`undefined` entries (a server not currently
|
|
18
|
+
* running, e.g. no project open) are skipped, not treated as a match.
|
|
19
|
+
*/
|
|
20
|
+
export declare function isUserFacingRequest(url: string, internalOrigins?: ReadonlyArray<string | null | undefined>): boolean;
|
|
21
|
+
//# sourceMappingURL=user-facing.d.ts.map
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/** URL schemes that can carry the mini-app author's own business traffic. */
|
|
2
|
+
const USER_FACING_SCHEMES = new Set(['http:', 'https:', 'ws:', 'wss:']);
|
|
3
|
+
/**
|
|
4
|
+
* Judges whether a captured network request belongs on the user-facing
|
|
5
|
+
* (right-panel) DevTools Network tab, or should be hidden there — a
|
|
6
|
+
* framework/host-internal resource load, visible only in the standalone
|
|
7
|
+
* internal DevTools window's unfiltered global mirror.
|
|
8
|
+
*
|
|
9
|
+
* Single source of truth for the split (network-forward's user-facing sink
|
|
10
|
+
* and the global mirror both key off this): scheme must be one of
|
|
11
|
+
* http/https/ws/wss, AND the request's origin must not match ANY of
|
|
12
|
+
* `internalOrigins` — each is a base URL of a server this app itself runs
|
|
13
|
+
* whose own traffic is host-internal, not the developer's business traffic.
|
|
14
|
+
* There are currently two: the resource server (serves the compiled
|
|
15
|
+
* mini-app bundle) and the simulator's own static-asset server (serves
|
|
16
|
+
* `simulator.html` + its JS/CSS — a THIRD-PARTY-to-the-user-code origin the
|
|
17
|
+
* `attachSimulator` CDP session also observes, since it captures the whole
|
|
18
|
+
* simulator WebContentsView's network activity, not just the mini-app's own
|
|
19
|
+
* `wx.request` calls). `null`/`undefined` entries (a server not currently
|
|
20
|
+
* running, e.g. no project open) are skipped, not treated as a match.
|
|
21
|
+
*/
|
|
22
|
+
export function isUserFacingRequest(url, internalOrigins) {
|
|
23
|
+
let parsed;
|
|
24
|
+
try {
|
|
25
|
+
parsed = new URL(url);
|
|
26
|
+
}
|
|
27
|
+
catch {
|
|
28
|
+
// Real CDP `Network.requestWillBeSent.request.url` is always a fully
|
|
29
|
+
// qualified URL — this only happens for hand-authored test/placeholder
|
|
30
|
+
// input. Fail OPEN (treat as user-facing): hiding a request the caller
|
|
31
|
+
// couldn't even classify risks silently swallowing real business
|
|
32
|
+
// traffic, a worse outcome than occasionally over-showing something.
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
if (!USER_FACING_SCHEMES.has(parsed.protocol))
|
|
36
|
+
return false;
|
|
37
|
+
for (const baseUrl of internalOrigins ?? []) {
|
|
38
|
+
if (!baseUrl)
|
|
39
|
+
continue;
|
|
40
|
+
try {
|
|
41
|
+
if (parsed.origin === new URL(baseUrl).origin)
|
|
42
|
+
return false;
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
// Malformed baseUrl — can't compare, so don't let it hide anything.
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=user-facing.js.map
|