@dimina-kit/devtools 0.4.0-dev.20260907055341 → 0.5.0-dev.20260908120530
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 +1 -1
- package/dist/main/app/window-runtime-services.d.ts +12 -12
- package/dist/main/app/window-runtime-services.js +34 -15
- package/dist/main/index.bundle.js +6307 -6050
- package/dist/main/services/elements-forward/index.d.ts +2 -2
- package/dist/main/services/elements-forward/index.js +6 -6
- package/dist/main/services/network-forward/body-cache.d.ts +2 -0
- package/dist/main/services/network-forward/body-cache.js +4 -0
- package/dist/main/services/network-forward/global-body-gate.d.ts +1 -1
- package/dist/main/services/network-forward/global-body-gate.js +4 -4
- package/dist/main/services/network-forward/http.d.ts +47 -0
- package/dist/main/services/network-forward/http.js +162 -0
- package/dist/main/services/network-forward/index.d.ts +19 -13
- package/dist/main/services/network-forward/index.js +220 -134
- package/dist/main/services/network-forward/request-ids.d.ts +5 -0
- package/dist/main/services/network-forward/request-ids.js +5 -0
- package/dist/main/services/safe-area/index.d.ts +7 -9
- package/dist/main/services/safe-area/index.js +7 -14
- package/dist/main/services/views/native-simulator-view.js +7 -8
- package/dist/main/services/workbench-context.d.ts +3 -3
- package/dist/main/services/workbench-context.js +7 -1
- package/dist/native-host/render/render.js +525 -482
- package/dist/native-host/service/service.js +2 -2
- package/dist/preload/shared/api-compat.js +63 -41
- package/dist/preload/windows/main.cjs +15 -1
- package/dist/preload/windows/main.cjs.map +2 -2
- package/dist/preload/windows/simulator.cjs +55 -134
- package/dist/preload/windows/simulator.cjs.map +4 -4
- package/dist/preload/windows/simulator.js +55 -134
- package/dist/renderer/assets/workbench-C0Jkdlps.js +5 -0
- package/dist/renderer/entries/workbench/index.html +1 -1
- package/dist/shared/types.d.ts +2 -0
- package/dist/simulator/assets/device-shell-DucLepkm.js +431 -0
- package/dist/simulator/assets/simulator-B4rbcOVt.js +10 -0
- package/dist/simulator/assets/simulator-ui-i3GCc7YJ.js +2 -0
- package/dist/simulator/simulator.html +2 -2
- package/package.json +12 -12
- package/dist/renderer/assets/workbench-BLJ0wKsg.js +0 -5
- package/dist/shared/request-core.d.ts +0 -2
- package/dist/shared/request-core.js +0 -2
- package/dist/simulator/assets/device-shell-pxnZZp10.js +0 -431
- package/dist/simulator/assets/simulator-Dwp7lnvu.js +0 -10
- package/dist/simulator/assets/simulator-ui-B2ddJB1R.js +0 -2
|
@@ -1,20 +1,15 @@
|
|
|
1
|
-
import { SyncDisposableRegistry, toDisposable } from
|
|
2
|
-
import { isFrontendSettled } from
|
|
3
|
-
import { packDispatchBatch } from
|
|
4
|
-
import { PrefetchCache, DEFAULT_PER_ENTRY_MAX_CHARS } from
|
|
5
|
-
import { PROBE_DEVTOOLS_API, MAX_SINGLE_DISPATCH_CHARS, CHUNK_CHARS, buildChunkedDispatchScript } from
|
|
6
|
-
import { createCdpSessionBroker } from
|
|
7
|
-
import { isUserFacingRequest } from
|
|
8
|
-
import { installGlobalNetworkBodyGate } from
|
|
9
|
-
import { WebSocketTraceSynthesizer } from
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
* gate (elements-forward) keys its `Network.getResponseBody` /
|
|
14
|
-
* `Network.getRequestPostData` interception on this exact prefix, so the two
|
|
15
|
-
* modules can never drift apart on what counts as "one of ours".
|
|
16
|
-
*/
|
|
17
|
-
export const VIRTUAL_REQUEST_ID_PREFIX = 'dimina:sim:';
|
|
1
|
+
import { SyncDisposableRegistry, toDisposable, } from "@dimina-kit/electron-deck/main";
|
|
2
|
+
import { isFrontendSettled } from "../views/inject-when-ready.js";
|
|
3
|
+
import { packDispatchBatch } from "./dispatch-batch.js";
|
|
4
|
+
import { PrefetchCache, DEFAULT_PER_ENTRY_MAX_CHARS } from "./body-cache.js";
|
|
5
|
+
import { PROBE_DEVTOOLS_API, MAX_SINGLE_DISPATCH_CHARS, CHUNK_CHARS, buildChunkedDispatchScript, } from "./frontend-dispatch.js";
|
|
6
|
+
import { createCdpSessionBroker, } from "../cdp-session/index.js";
|
|
7
|
+
import { isUserFacingRequest } from "./user-facing.js";
|
|
8
|
+
import { installGlobalNetworkBodyGate } from "./global-body-gate.js";
|
|
9
|
+
import { WebSocketTraceSynthesizer } from "./websocket.js";
|
|
10
|
+
import { RequestTraceSynthesizer } from "./http.js";
|
|
11
|
+
import { VIRTUAL_REQUEST_ID_PREFIX } from "./request-ids.js";
|
|
12
|
+
export { VIRTUAL_REQUEST_ID_PREFIX } from "./request-ids.js";
|
|
18
13
|
// ── requestId namespacing (pure, testable) ──────────────────────────────────
|
|
19
14
|
/**
|
|
20
15
|
* CDP events whose `params.requestId` we rewrite into the virtual namespace.
|
|
@@ -24,32 +19,32 @@ export const VIRTUAL_REQUEST_ID_PREFIX = 'dimina:sim:';
|
|
|
24
19
|
* included for that reason (rewrite-only today, not forwarded).
|
|
25
20
|
*/
|
|
26
21
|
export const REWRITE_REQUEST_ID_METHODS = new Set([
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
22
|
+
"Network.requestWillBeSent",
|
|
23
|
+
"Network.requestWillBeSentExtraInfo",
|
|
24
|
+
"Network.responseReceived",
|
|
25
|
+
"Network.responseReceivedExtraInfo",
|
|
26
|
+
"Network.dataReceived",
|
|
27
|
+
"Network.loadingFinished",
|
|
28
|
+
"Network.loadingFailed",
|
|
29
|
+
"Network.requestServedFromCache",
|
|
30
|
+
"Network.resourceChangedPriority",
|
|
36
31
|
// 二期 (when WebSocket/EventSource forwarding lands): Network.webSocket*,
|
|
37
32
|
// Network.eventSourceMessageReceived — keep ids namespaced once added here.
|
|
38
33
|
]);
|
|
39
34
|
/** The Network.* methods this one-shot pass forwards to the front-end. */
|
|
40
35
|
export const FORWARDED_METHODS = new Set([
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
36
|
+
"Network.requestWillBeSent",
|
|
37
|
+
"Network.requestWillBeSentExtraInfo",
|
|
38
|
+
"Network.responseReceived",
|
|
39
|
+
"Network.responseReceivedExtraInfo",
|
|
40
|
+
"Network.loadingFinished",
|
|
41
|
+
"Network.loadingFailed",
|
|
47
42
|
// `dataReceived` deliberately omitted (二期) — see header.
|
|
48
43
|
]);
|
|
49
44
|
/** Methods that mark a request finished, so its id mapping can age out. */
|
|
50
45
|
const TERMINAL_METHODS = new Set([
|
|
51
|
-
|
|
52
|
-
|
|
46
|
+
"Network.loadingFinished",
|
|
47
|
+
"Network.loadingFailed",
|
|
53
48
|
]);
|
|
54
49
|
/**
|
|
55
50
|
* Bounded, TTL'd raw→virtual requestId map with an active/retired split.
|
|
@@ -157,7 +152,7 @@ export function rewriteRequestId(method, params, ns) {
|
|
|
157
152
|
if (!REWRITE_REQUEST_ID_METHODS.has(method))
|
|
158
153
|
return { method, params };
|
|
159
154
|
const p = params;
|
|
160
|
-
if (!p || typeof p.requestId !==
|
|
155
|
+
if (!p || typeof p.requestId !== "string")
|
|
161
156
|
return { method, params };
|
|
162
157
|
const virtual = ns.resolve(p.requestId);
|
|
163
158
|
if (TERMINAL_METHODS.has(method))
|
|
@@ -174,12 +169,12 @@ export function rewriteRequestId(method, params, ns) {
|
|
|
174
169
|
*/
|
|
175
170
|
function buildDispatchScript(messages) {
|
|
176
171
|
const arr = JSON.stringify(messages);
|
|
177
|
-
return `(()=>{try{`
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
172
|
+
return (`(()=>{try{` +
|
|
173
|
+
`if(!${PROBE_DEVTOOLS_API})return false;` +
|
|
174
|
+
`const ms=JSON.parse(${JSON.stringify(arr)});` +
|
|
175
|
+
`for(const m of ms){try{window.DevToolsAPI.dispatchMessage(m)}catch(_){}}` +
|
|
176
|
+
`return true;` +
|
|
177
|
+
`}catch(_){return false}})()`);
|
|
183
178
|
}
|
|
184
179
|
/**
|
|
185
180
|
* Upper bound on the COMBINED size (in UTF-16 chars) of the messages packed into
|
|
@@ -197,13 +192,13 @@ const MAX_BATCH_CHARS = 512 * 1024;
|
|
|
197
192
|
*/
|
|
198
193
|
function buildForwardScript(record) {
|
|
199
194
|
const json = JSON.stringify(record);
|
|
200
|
-
return `(()=>{try{const r=JSON.parse(${JSON.stringify(json)});`
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
195
|
+
return (`(()=>{try{const r=JSON.parse(${JSON.stringify(json)});` +
|
|
196
|
+
`const tag='[网络]['+r.source+']';` +
|
|
197
|
+
`const head=r.method+' '+(r.status||'-')+' '+r.url;` +
|
|
198
|
+
`if(r.errorText){console.warn(tag,head,r.errorText)}` +
|
|
199
|
+
`else if(r.status>=400){console.warn(tag,head)}` +
|
|
200
|
+
`else{console.log(tag,head)}` +
|
|
201
|
+
`}catch(_){}})()`);
|
|
207
202
|
}
|
|
208
203
|
/** Cap so a long session can't grow the fallback pending map unboundedly. */
|
|
209
204
|
const MAX_PENDING = 1000;
|
|
@@ -261,11 +256,19 @@ export function createNetworkForwarder(bridge) {
|
|
|
261
256
|
return;
|
|
262
257
|
pendingPrefetchCount++;
|
|
263
258
|
let released = false;
|
|
264
|
-
const release = () => {
|
|
265
|
-
released
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
259
|
+
const release = () => {
|
|
260
|
+
if (!released) {
|
|
261
|
+
released = true;
|
|
262
|
+
pendingPrefetchCount--;
|
|
263
|
+
}
|
|
264
|
+
};
|
|
265
|
+
const started = cache.prime(id, () => fetch().then((v) => {
|
|
266
|
+
release();
|
|
267
|
+
return v;
|
|
268
|
+
}, (err) => {
|
|
269
|
+
release();
|
|
270
|
+
throw err;
|
|
271
|
+
}));
|
|
269
272
|
if (!started)
|
|
270
273
|
release();
|
|
271
274
|
}
|
|
@@ -300,7 +303,8 @@ export function createNetworkForwarder(bridge) {
|
|
|
300
303
|
// Own (and dispose on this forwarder's own dispose()) a private broker only
|
|
301
304
|
// when the caller didn't supply a shared one.
|
|
302
305
|
const ownsBroker = !bridge.broker;
|
|
303
|
-
const broker = bridge.broker ??
|
|
306
|
+
const broker = bridge.broker ??
|
|
307
|
+
createCdpSessionBroker({ connections: bridge.connections });
|
|
304
308
|
// The DevTools front-end host wc (primary sink), set by the ViewManager.
|
|
305
309
|
let devtoolsWc = null;
|
|
306
310
|
// Teardown for the wc 'destroyed' watcher on the current host (clears the host).
|
|
@@ -380,7 +384,9 @@ export function createNetworkForwarder(bridge) {
|
|
|
380
384
|
for (const msg of chunked)
|
|
381
385
|
dispatchChunked(target, msg);
|
|
382
386
|
if (batch.length > 0) {
|
|
383
|
-
target.executeJavaScript(buildDispatchScript(batch), true).catch(() => {
|
|
387
|
+
target.executeJavaScript(buildDispatchScript(batch), true).catch(() => {
|
|
388
|
+
/* best-effort */
|
|
389
|
+
});
|
|
384
390
|
}
|
|
385
391
|
}
|
|
386
392
|
globalPendingOverflowWarned = false;
|
|
@@ -418,18 +424,20 @@ export function createNetworkForwarder(bridge) {
|
|
|
418
424
|
clearGlobalPending();
|
|
419
425
|
};
|
|
420
426
|
const reg = bridge.connections;
|
|
421
|
-
if (reg && typeof target.once ===
|
|
427
|
+
if (reg && typeof target.once === "function") {
|
|
422
428
|
const owned = reg.acquire(target).own(onHostDestroyed);
|
|
423
429
|
globalDevtoolsHostDisposable = toDisposable(() => owned.dispose());
|
|
424
430
|
}
|
|
425
431
|
else {
|
|
426
|
-
if (typeof target.once ===
|
|
427
|
-
target.once(
|
|
432
|
+
if (typeof target.once === "function")
|
|
433
|
+
target.once("destroyed", onHostDestroyed);
|
|
428
434
|
globalDevtoolsHostDisposable = toDisposable(() => {
|
|
429
435
|
try {
|
|
430
|
-
target.removeListener?.(
|
|
436
|
+
target.removeListener?.("destroyed", onHostDestroyed);
|
|
437
|
+
}
|
|
438
|
+
catch {
|
|
439
|
+
/* gone */
|
|
431
440
|
}
|
|
432
|
-
catch { /* gone */ }
|
|
433
441
|
});
|
|
434
442
|
}
|
|
435
443
|
globalBodyGateStop = installGlobalNetworkBodyGate(target, {
|
|
@@ -466,10 +474,14 @@ export function createNetworkForwarder(bridge) {
|
|
|
466
474
|
dispatchChunked(globalDevtoolsWc, json);
|
|
467
475
|
return;
|
|
468
476
|
}
|
|
469
|
-
globalDevtoolsWc
|
|
477
|
+
globalDevtoolsWc
|
|
478
|
+
.executeJavaScript(buildDispatchScript([json]), true)
|
|
479
|
+
.catch(() => {
|
|
480
|
+
/* best-effort */
|
|
481
|
+
});
|
|
470
482
|
}
|
|
471
483
|
// ── Native-sink state machine ─────────────────────────────────────────────
|
|
472
|
-
let sink =
|
|
484
|
+
let sink = "idle";
|
|
473
485
|
// Buffered completed-request records while 'probing' — flushed to console if we
|
|
474
486
|
// degrade, dropped if we go ready (so a request shows in exactly one sink).
|
|
475
487
|
let probeConsoleBuffer = [];
|
|
@@ -497,22 +509,22 @@ export function createNetworkForwarder(bridge) {
|
|
|
497
509
|
}
|
|
498
510
|
/** Enter the 'probing' state and arm the ready-timeout (idempotent). */
|
|
499
511
|
function beginProbing() {
|
|
500
|
-
if (sink ===
|
|
512
|
+
if (sink === "probing")
|
|
501
513
|
return;
|
|
502
|
-
sink =
|
|
514
|
+
sink = "probing";
|
|
503
515
|
probeDeadline = Date.now() + DEVTOOLS_READY_TIMEOUT_MS;
|
|
504
516
|
if (readyTimeoutTimer)
|
|
505
517
|
clearTimeout(readyTimeoutTimer);
|
|
506
518
|
readyTimeoutTimer = setTimeout(() => {
|
|
507
519
|
readyTimeoutTimer = null;
|
|
508
520
|
// Still not ready after the grace period → abandon native for this host.
|
|
509
|
-
if (sink ===
|
|
521
|
+
if (sink === "probing")
|
|
510
522
|
degradeToConsole();
|
|
511
523
|
}, DEVTOOLS_READY_TIMEOUT_MS);
|
|
512
524
|
}
|
|
513
525
|
/** Native path confirmed live: console buffer is moot, drop it. */
|
|
514
526
|
function markReady() {
|
|
515
|
-
sink =
|
|
527
|
+
sink = "ready";
|
|
516
528
|
probeDeadline = null;
|
|
517
529
|
if (readyTimeoutTimer) {
|
|
518
530
|
clearTimeout(readyTimeoutTimer);
|
|
@@ -531,7 +543,7 @@ export function createNetworkForwarder(bridge) {
|
|
|
531
543
|
* and flush the buffered completed-request records to the console sink.
|
|
532
544
|
*/
|
|
533
545
|
function degradeToConsole() {
|
|
534
|
-
sink =
|
|
546
|
+
sink = "degraded";
|
|
535
547
|
probeDeadline = null;
|
|
536
548
|
dispatchQueue = [];
|
|
537
549
|
if (readyTimeoutTimer) {
|
|
@@ -562,9 +574,9 @@ export function createNetworkForwarder(bridge) {
|
|
|
562
574
|
function trimQueue() {
|
|
563
575
|
if (dispatchQueue.length <= MAX_DISPATCH_QUEUE)
|
|
564
576
|
return;
|
|
565
|
-
const isOpener = (json) => json.includes('"Network.requestWillBeSent"')
|
|
566
|
-
|
|
567
|
-
|
|
577
|
+
const isOpener = (json) => json.includes('"Network.requestWillBeSent"') ||
|
|
578
|
+
json.includes('"Network.requestWillBeSentExtraInfo"') ||
|
|
579
|
+
json.includes('"Network.webSocketCreated"');
|
|
568
580
|
// First pass: drop oldest non-opener events.
|
|
569
581
|
const kept = [];
|
|
570
582
|
let over = dispatchQueue.length - MAX_DISPATCH_QUEUE;
|
|
@@ -587,7 +599,7 @@ export function createNetworkForwarder(bridge) {
|
|
|
587
599
|
flushScheduled = false;
|
|
588
600
|
if (dispatchQueue.length === 0)
|
|
589
601
|
return;
|
|
590
|
-
if (sink ===
|
|
602
|
+
if (sink === "degraded") {
|
|
591
603
|
dispatchQueue = [];
|
|
592
604
|
return;
|
|
593
605
|
}
|
|
@@ -610,7 +622,7 @@ export function createNetworkForwarder(bridge) {
|
|
|
610
622
|
scheduleReadyRetry();
|
|
611
623
|
return;
|
|
612
624
|
}
|
|
613
|
-
if (sink ===
|
|
625
|
+
if (sink === "idle")
|
|
614
626
|
beginProbing();
|
|
615
627
|
// Pack greedily up to MAX_BATCH_CHARS so one executeJavaScript stays sized;
|
|
616
628
|
// oversized single messages go via the chunked transport (see packDispatchBatch).
|
|
@@ -639,24 +651,26 @@ export function createNetworkForwarder(bridge) {
|
|
|
639
651
|
dispatchQueue = remaining;
|
|
640
652
|
if (remaining.length > 0)
|
|
641
653
|
scheduleFlush();
|
|
642
|
-
wc.executeJavaScript(script, true)
|
|
654
|
+
wc.executeJavaScript(script, true)
|
|
655
|
+
.then((ok) => {
|
|
643
656
|
if (ok === true) {
|
|
644
|
-
if (sink !==
|
|
657
|
+
if (sink !== "ready")
|
|
645
658
|
markReady();
|
|
646
659
|
return;
|
|
647
660
|
}
|
|
648
661
|
// API not present yet — front-end still booting. Re-queue this batch ahead
|
|
649
662
|
// of any newer events and poll until ready (or the timeout degrades us).
|
|
650
|
-
if (sink ===
|
|
663
|
+
if (sink === "degraded")
|
|
651
664
|
return;
|
|
652
665
|
dispatchQueue = batch.concat(dispatchQueue);
|
|
653
666
|
trimQueue();
|
|
654
667
|
scheduleReadyRetry();
|
|
655
|
-
})
|
|
668
|
+
})
|
|
669
|
+
.catch(() => {
|
|
656
670
|
// wc navigated / torn down mid-call, OR the script overflowed IPC. Re-queue
|
|
657
671
|
// (bounded) and let the next flush re-resolve the host. Best-effort; the
|
|
658
672
|
// ready-timeout still governs giving up. Backoff is via the retry timer.
|
|
659
|
-
if (sink ===
|
|
673
|
+
if (sink === "degraded")
|
|
660
674
|
return;
|
|
661
675
|
dispatchQueue = batch.concat(dispatchQueue);
|
|
662
676
|
trimQueue();
|
|
@@ -676,10 +690,12 @@ export function createNetworkForwarder(bridge) {
|
|
|
676
690
|
catch {
|
|
677
691
|
return;
|
|
678
692
|
}
|
|
679
|
-
wc.executeJavaScript(script, true).catch(() => {
|
|
693
|
+
wc.executeJavaScript(script, true).catch(() => {
|
|
694
|
+
/* best-effort */
|
|
695
|
+
});
|
|
680
696
|
}
|
|
681
697
|
function scheduleReadyRetry() {
|
|
682
|
-
if (readyRetryTimer || sink ===
|
|
698
|
+
if (readyRetryTimer || sink === "ready" || sink === "degraded")
|
|
683
699
|
return;
|
|
684
700
|
// Self-terminate on the same deadline readyTimeoutTimer enforces, instead of
|
|
685
701
|
// trusting that timer to win a same-instant race against this one (see the
|
|
@@ -690,7 +706,7 @@ export function createNetworkForwarder(bridge) {
|
|
|
690
706
|
}
|
|
691
707
|
readyRetryTimer = setTimeout(() => {
|
|
692
708
|
readyRetryTimer = null;
|
|
693
|
-
if (sink ===
|
|
709
|
+
if (sink === "degraded")
|
|
694
710
|
return;
|
|
695
711
|
if (probeDeadline !== null && Date.now() >= probeDeadline) {
|
|
696
712
|
degradeToConsole();
|
|
@@ -707,9 +723,9 @@ export function createNetworkForwarder(bridge) {
|
|
|
707
723
|
// requests — queueing them would let them double-render if a host later
|
|
708
724
|
// arrives / swaps in. resolveDevtoolsWc() guards the idle case where a host
|
|
709
725
|
// is set on the bridge but applyDevtoolsHost hasn't run yet.
|
|
710
|
-
if (sink ===
|
|
726
|
+
if (sink === "degraded")
|
|
711
727
|
return;
|
|
712
|
-
if (sink ===
|
|
728
|
+
if (sink === "idle" && !resolveDevtoolsWc())
|
|
713
729
|
return;
|
|
714
730
|
let json;
|
|
715
731
|
try {
|
|
@@ -759,7 +775,9 @@ export function createNetworkForwarder(bridge) {
|
|
|
759
775
|
try {
|
|
760
776
|
ad.disposeAll();
|
|
761
777
|
}
|
|
762
|
-
catch {
|
|
778
|
+
catch {
|
|
779
|
+
/* best-effort teardown */
|
|
780
|
+
}
|
|
763
781
|
}
|
|
764
782
|
lease?.dispose();
|
|
765
783
|
// Drop any queued-but-unflushed messages so a new attach starts clean, and
|
|
@@ -768,7 +786,7 @@ export function createNetworkForwarder(bridge) {
|
|
|
768
786
|
probeConsoleBuffer = [];
|
|
769
787
|
// A new simulator attach restarts the native sink: 'idle' until the next
|
|
770
788
|
// event re-probes the (possibly already-set) host.
|
|
771
|
-
sink =
|
|
789
|
+
sink = "idle";
|
|
772
790
|
probeDeadline = null;
|
|
773
791
|
if (readyRetryTimer) {
|
|
774
792
|
clearTimeout(readyRetryTimer);
|
|
@@ -819,7 +837,7 @@ export function createNetworkForwarder(bridge) {
|
|
|
819
837
|
// — global-body-gate answers Network.getResponseBody/getRequestPostData
|
|
820
838
|
// from this same cache, and it must not 404 just because the
|
|
821
839
|
// user-facing sink happens to be closed/degraded.
|
|
822
|
-
const userSinkUsable = sink !==
|
|
840
|
+
const userSinkUsable = sink !== "degraded" && !(sink === "idle" && !resolveDevtoolsWc());
|
|
823
841
|
const globalUsable = globalDevtoolsWc !== null && !globalDevtoolsWc.isDestroyed();
|
|
824
842
|
if (!userSinkUsable && !globalUsable)
|
|
825
843
|
return;
|
|
@@ -831,23 +849,24 @@ export function createNetworkForwarder(bridge) {
|
|
|
831
849
|
// is a best-effort heuristic (encodedDataLength is the ON-THE-WIRE size;
|
|
832
850
|
// a decompressed body can be larger), not a hard guarantee — the
|
|
833
851
|
// concurrency cap below is what actually bounds worst-case memory.
|
|
834
|
-
const knownOversized = typeof encodedDataLength ===
|
|
852
|
+
const knownOversized = typeof encodedDataLength === "number" &&
|
|
853
|
+
encodedDataLength > DEFAULT_PER_ENTRY_MAX_CHARS;
|
|
835
854
|
if (!knownOversized) {
|
|
836
855
|
primeWithAdmission(bodyCache, virtualId, async () => {
|
|
837
|
-
const raw = await wc.debugger.sendCommand(
|
|
856
|
+
const raw = await wc.debugger.sendCommand("Network.getResponseBody", { requestId: rawId });
|
|
838
857
|
const r = raw;
|
|
839
|
-
if (!r || typeof r.body !==
|
|
840
|
-
throw new Error(
|
|
858
|
+
if (!r || typeof r.body !== "string")
|
|
859
|
+
throw new Error("response body unavailable");
|
|
841
860
|
return { body: r.body, base64Encoded: r.base64Encoded === true };
|
|
842
861
|
});
|
|
843
862
|
}
|
|
844
863
|
if (!postDataWanted.delete(rawId))
|
|
845
864
|
return;
|
|
846
865
|
primeWithAdmission(postDataCache, virtualId, async () => {
|
|
847
|
-
const raw = await wc.debugger.sendCommand(
|
|
866
|
+
const raw = await wc.debugger.sendCommand("Network.getRequestPostData", { requestId: rawId });
|
|
848
867
|
const r = raw;
|
|
849
|
-
if (!r || typeof r.postData !==
|
|
850
|
-
throw new Error(
|
|
868
|
+
if (!r || typeof r.postData !== "string")
|
|
869
|
+
throw new Error("post data unavailable");
|
|
851
870
|
return { postData: r.postData };
|
|
852
871
|
});
|
|
853
872
|
};
|
|
@@ -859,10 +878,15 @@ export function createNetworkForwarder(bridge) {
|
|
|
859
878
|
return;
|
|
860
879
|
if (pending.size >= MAX_PENDING)
|
|
861
880
|
pending.clear();
|
|
862
|
-
pending.set(p.requestId, {
|
|
881
|
+
pending.set(p.requestId, {
|
|
882
|
+
url: p.request.url,
|
|
883
|
+
method: p.request.method,
|
|
884
|
+
status: 0,
|
|
885
|
+
});
|
|
863
886
|
// A body announced but not inlined is the one case the panel will
|
|
864
887
|
// round-trip `Network.getRequestPostData` — flag it for prefetch.
|
|
865
|
-
if (p.request.hasPostData === true &&
|
|
888
|
+
if (p.request.hasPostData === true &&
|
|
889
|
+
typeof p.request.postData !== "string") {
|
|
866
890
|
if (postDataWanted.size >= MAX_PENDING)
|
|
867
891
|
postDataWanted.clear();
|
|
868
892
|
postDataWanted.add(p.requestId);
|
|
@@ -887,12 +911,17 @@ export function createNetworkForwarder(bridge) {
|
|
|
887
911
|
}
|
|
888
912
|
function onLoadingFinished(params) {
|
|
889
913
|
const p = params;
|
|
890
|
-
if (typeof p?.requestId ===
|
|
914
|
+
if (typeof p?.requestId === "string")
|
|
891
915
|
prefetchBodies(p.requestId, p.encodedDataLength);
|
|
892
916
|
const req = retirePending(p?.requestId);
|
|
893
917
|
if (!req)
|
|
894
918
|
return;
|
|
895
|
-
maybeFallback({
|
|
919
|
+
maybeFallback({
|
|
920
|
+
source,
|
|
921
|
+
url: req.url,
|
|
922
|
+
method: req.method,
|
|
923
|
+
status: req.status,
|
|
924
|
+
});
|
|
896
925
|
}
|
|
897
926
|
function onLoadingFailed(params) {
|
|
898
927
|
const p = params;
|
|
@@ -905,14 +934,14 @@ export function createNetworkForwarder(bridge) {
|
|
|
905
934
|
url: req.url,
|
|
906
935
|
method: req.method,
|
|
907
936
|
status: req.status,
|
|
908
|
-
errorText: p.canceled ?
|
|
937
|
+
errorText: p.canceled ? "canceled" : p.errorText || "failed",
|
|
909
938
|
});
|
|
910
939
|
}
|
|
911
940
|
const FALLBACK_HANDLERS = {
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
941
|
+
"Network.requestWillBeSent": onRequestWillBeSent,
|
|
942
|
+
"Network.responseReceived": onResponseReceived,
|
|
943
|
+
"Network.loadingFinished": onLoadingFinished,
|
|
944
|
+
"Network.loadingFailed": onLoadingFailed,
|
|
916
945
|
};
|
|
917
946
|
/**
|
|
918
947
|
* Resolve (and, on `requestWillBeSent`, record) whether `rawId` is
|
|
@@ -925,10 +954,14 @@ export function createNetworkForwarder(bridge) {
|
|
|
925
954
|
* than silently hiding it.
|
|
926
955
|
*/
|
|
927
956
|
function resolveUserFacing(method, params, rawId) {
|
|
928
|
-
if (method ===
|
|
929
|
-
const url = params?.request
|
|
930
|
-
|
|
931
|
-
|
|
957
|
+
if (method === "Network.requestWillBeSent") {
|
|
958
|
+
const url = params?.request
|
|
959
|
+
?.url;
|
|
960
|
+
const verdict = typeof url === "string"
|
|
961
|
+
? isUserFacingRequest(url, [
|
|
962
|
+
bridge.getResourceServerBaseUrl?.(),
|
|
963
|
+
bridge.getSimulatorServerBaseUrl?.(),
|
|
964
|
+
])
|
|
932
965
|
: true;
|
|
933
966
|
if (rawId) {
|
|
934
967
|
if (userFacingByRawId.size >= MAX_PENDING)
|
|
@@ -950,7 +983,7 @@ export function createNetworkForwarder(bridge) {
|
|
|
950
983
|
// classification is even known. Every OTHER "rawId never seen" case
|
|
951
984
|
// (e.g. capture attached mid-flight, no requestWillBeSent ever coming)
|
|
952
985
|
// keeps failing open — there is no better signal available there.
|
|
953
|
-
if (method ===
|
|
986
|
+
if (method === "Network.requestWillBeSentExtraInfo")
|
|
954
987
|
return false;
|
|
955
988
|
return true;
|
|
956
989
|
}
|
|
@@ -964,8 +997,9 @@ export function createNetworkForwarder(bridge) {
|
|
|
964
997
|
// User-facing sink: only requests isUserFacingRequest() judged as the
|
|
965
998
|
// developer's own business traffic — internal/framework resource
|
|
966
999
|
// loads are mirrored to the global window ONLY.
|
|
967
|
-
const rawId = params
|
|
968
|
-
|
|
1000
|
+
const rawId = params
|
|
1001
|
+
?.requestId;
|
|
1002
|
+
const userFacing = resolveUserFacing(method, params, typeof rawId === "string" ? rawId : undefined);
|
|
969
1003
|
if (userFacing) {
|
|
970
1004
|
enqueueNative(rewritten.method, rewritten.params);
|
|
971
1005
|
}
|
|
@@ -978,12 +1012,14 @@ export function createNetworkForwarder(bridge) {
|
|
|
978
1012
|
// ── Fallback bookkeeping (used only when native dispatch is unusable) ──
|
|
979
1013
|
FALLBACK_HANDLERS[method]?.(params);
|
|
980
1014
|
};
|
|
981
|
-
wc.debugger.on(
|
|
1015
|
+
wc.debugger.on("message", onMessage);
|
|
982
1016
|
attach.add(() => {
|
|
983
1017
|
try {
|
|
984
|
-
wc.debugger.removeListener(
|
|
1018
|
+
wc.debugger.removeListener("message", onMessage);
|
|
1019
|
+
}
|
|
1020
|
+
catch {
|
|
1021
|
+
/* wc gone */
|
|
985
1022
|
}
|
|
986
|
-
catch { /* wc gone */ }
|
|
987
1023
|
});
|
|
988
1024
|
}
|
|
989
1025
|
/** Drop one guest's wiring (message listener + broker lease). Attach/detach
|
|
@@ -1093,7 +1129,7 @@ export function createNetworkForwarder(bridge) {
|
|
|
1093
1129
|
guestRetryGeneration.delete(wc.id);
|
|
1094
1130
|
const teardown = new SyncDisposableRegistry();
|
|
1095
1131
|
guestWired.set(wc.id, teardown);
|
|
1096
|
-
wireNetworkCapture(wc,
|
|
1132
|
+
wireNetworkCapture(wc, "render", teardown, `g${wc.id}-${Date.now()}`);
|
|
1097
1133
|
const detachSub = lease.onDetach(() => {
|
|
1098
1134
|
cleanupGuest(wc.id);
|
|
1099
1135
|
scheduleRenderGuestRetry(wc);
|
|
@@ -1102,8 +1138,8 @@ export function createNetworkForwarder(bridge) {
|
|
|
1102
1138
|
detachSub.dispose();
|
|
1103
1139
|
lease.dispose();
|
|
1104
1140
|
});
|
|
1105
|
-
void lease.send(
|
|
1106
|
-
console.warn(
|
|
1141
|
+
void lease.send("Network.enable").catch((err) => {
|
|
1142
|
+
console.warn("[network-forward] guest Network.enable failed:", err instanceof Error ? err.message : err);
|
|
1107
1143
|
});
|
|
1108
1144
|
}
|
|
1109
1145
|
function attachRenderGuest(wc) {
|
|
@@ -1127,14 +1163,14 @@ export function createNetworkForwarder(bridge) {
|
|
|
1127
1163
|
detachSimulator();
|
|
1128
1164
|
const lease = broker.acquire(wc);
|
|
1129
1165
|
if (!lease) {
|
|
1130
|
-
console.warn(
|
|
1166
|
+
console.warn("[network-forward] debugger session unavailable; simulator network not captured");
|
|
1131
1167
|
return;
|
|
1132
1168
|
}
|
|
1133
1169
|
simWc = wc;
|
|
1134
1170
|
simLease = lease;
|
|
1135
1171
|
const attach = new SyncDisposableRegistry();
|
|
1136
1172
|
attachDisposables = attach;
|
|
1137
|
-
wireNetworkCapture(wc,
|
|
1173
|
+
wireNetworkCapture(wc, "service", attach, String(Date.now()));
|
|
1138
1174
|
const detachSub = lease.onDetach(() => {
|
|
1139
1175
|
if (simWc !== wc)
|
|
1140
1176
|
return;
|
|
@@ -1149,12 +1185,14 @@ export function createNetworkForwarder(bridge) {
|
|
|
1149
1185
|
try {
|
|
1150
1186
|
ad.disposeAll();
|
|
1151
1187
|
}
|
|
1152
|
-
catch {
|
|
1188
|
+
catch {
|
|
1189
|
+
/* best-effort teardown */
|
|
1190
|
+
}
|
|
1153
1191
|
}
|
|
1154
1192
|
});
|
|
1155
1193
|
attach.add(() => detachSub.dispose());
|
|
1156
|
-
void lease.send(
|
|
1157
|
-
console.warn(
|
|
1194
|
+
void lease.send("Network.enable").catch((err) => {
|
|
1195
|
+
console.warn("[network-forward] Network.enable failed:", err instanceof Error ? err.message : err);
|
|
1158
1196
|
});
|
|
1159
1197
|
}
|
|
1160
1198
|
/**
|
|
@@ -1170,18 +1208,18 @@ export function createNetworkForwarder(bridge) {
|
|
|
1170
1208
|
// 'idle' but a host is resolvable (set via the bridge, applyDevtoolsHost not
|
|
1171
1209
|
// run): the native path is in play, so promote to 'probing' instead of
|
|
1172
1210
|
// console — otherwise this completion would later double-render natively.
|
|
1173
|
-
if (sink ===
|
|
1211
|
+
if (sink === "idle" && resolveDevtoolsWc())
|
|
1174
1212
|
beginProbing();
|
|
1175
1213
|
switch (sink) {
|
|
1176
|
-
case
|
|
1214
|
+
case "ready":
|
|
1177
1215
|
return;
|
|
1178
|
-
case
|
|
1216
|
+
case "probing":
|
|
1179
1217
|
if (probeConsoleBuffer.length >= MAX_PENDING)
|
|
1180
1218
|
probeConsoleBuffer.shift();
|
|
1181
1219
|
probeConsoleBuffer.push(record);
|
|
1182
1220
|
return;
|
|
1183
|
-
case
|
|
1184
|
-
case
|
|
1221
|
+
case "degraded":
|
|
1222
|
+
case "idle":
|
|
1185
1223
|
default:
|
|
1186
1224
|
forwardToConsole(record);
|
|
1187
1225
|
}
|
|
@@ -1207,7 +1245,7 @@ export function createNetworkForwarder(bridge) {
|
|
|
1207
1245
|
if (!devtoolsWc) {
|
|
1208
1246
|
// No host → 'idle': completions go straight to console; native queue is
|
|
1209
1247
|
// moot, drop it so it can't double-render if a host later appears.
|
|
1210
|
-
sink =
|
|
1248
|
+
sink = "idle";
|
|
1211
1249
|
dispatchQueue = [];
|
|
1212
1250
|
return;
|
|
1213
1251
|
}
|
|
@@ -1215,7 +1253,9 @@ export function createNetworkForwarder(bridge) {
|
|
|
1215
1253
|
// WITHOUT touching view-manager (host-destroyed cleanup lives here). Begin
|
|
1216
1254
|
// probing and flush anything already queued.
|
|
1217
1255
|
const host = devtoolsWc;
|
|
1218
|
-
const onHostDestroyed = () => {
|
|
1256
|
+
const onHostDestroyed = () => {
|
|
1257
|
+
applyDevtoolsHost(null);
|
|
1258
|
+
};
|
|
1219
1259
|
// Route host-destroyed teardown through the connection registry when present
|
|
1220
1260
|
// (the Connection fires onHostDestroyed on wc destroy / reset, and the
|
|
1221
1261
|
// returned Disposable releases the ownership early on host swap/clear);
|
|
@@ -1227,18 +1267,20 @@ export function createNetworkForwarder(bridge) {
|
|
|
1227
1267
|
// gated by the SAME `typeof host.once === 'function'` guard the fallback
|
|
1228
1268
|
// uses — otherwise a minimal/fake DevTools host (no emitter) throws on the
|
|
1229
1269
|
// connection path where the fallback would safely no-op.
|
|
1230
|
-
if (reg && typeof host.once ===
|
|
1270
|
+
if (reg && typeof host.once === "function") {
|
|
1231
1271
|
const owned = reg.acquire(host).own(onHostDestroyed);
|
|
1232
1272
|
devtoolsHostDisposable = toDisposable(() => owned.dispose());
|
|
1233
1273
|
}
|
|
1234
1274
|
else {
|
|
1235
|
-
if (typeof host.once ===
|
|
1236
|
-
host.once(
|
|
1275
|
+
if (typeof host.once === "function")
|
|
1276
|
+
host.once("destroyed", onHostDestroyed);
|
|
1237
1277
|
devtoolsHostDisposable = toDisposable(() => {
|
|
1238
1278
|
try {
|
|
1239
|
-
host.removeListener?.(
|
|
1279
|
+
host.removeListener?.("destroyed", onHostDestroyed);
|
|
1280
|
+
}
|
|
1281
|
+
catch {
|
|
1282
|
+
/* gone */
|
|
1240
1283
|
}
|
|
1241
|
-
catch { /* gone */ }
|
|
1242
1284
|
});
|
|
1243
1285
|
}
|
|
1244
1286
|
// A host swap while still 'probing' the OLD host must not inherit its
|
|
@@ -1246,12 +1288,14 @@ export function createNetworkForwarder(bridge) {
|
|
|
1246
1288
|
// (re-)arming readyTimeoutTimer/probeDeadline for the NEW host, leaving it
|
|
1247
1289
|
// probing forever with no timeout. Force through 'idle' so beginProbing()
|
|
1248
1290
|
// always arms a fresh window for whichever host is now current.
|
|
1249
|
-
sink =
|
|
1291
|
+
sink = "idle";
|
|
1250
1292
|
beginProbing();
|
|
1251
1293
|
if (dispatchQueue.length > 0)
|
|
1252
1294
|
scheduleFlush();
|
|
1253
1295
|
}
|
|
1254
|
-
registry.add(() => {
|
|
1296
|
+
registry.add(() => {
|
|
1297
|
+
disposed = true;
|
|
1298
|
+
});
|
|
1255
1299
|
registry.add(() => {
|
|
1256
1300
|
devtoolsHostDisposable?.dispose();
|
|
1257
1301
|
devtoolsHostDisposable = null;
|
|
@@ -1303,7 +1347,10 @@ export function createNetworkForwarder(bridge) {
|
|
|
1303
1347
|
// per-socket verdict cache mirrors resolveUserFacing's decide-once rule.
|
|
1304
1348
|
const wsSynthesizer = new WebSocketTraceSynthesizer({
|
|
1305
1349
|
epoch: String(Date.now()),
|
|
1306
|
-
internalOrigins: () => [
|
|
1350
|
+
internalOrigins: () => [
|
|
1351
|
+
bridge.getResourceServerBaseUrl?.(),
|
|
1352
|
+
bridge.getSimulatorServerBaseUrl?.(),
|
|
1353
|
+
],
|
|
1307
1354
|
});
|
|
1308
1355
|
function reportWebSocketTrace(sessionId, event) {
|
|
1309
1356
|
if (disposed)
|
|
@@ -1318,6 +1365,44 @@ export function createNetworkForwarder(bridge) {
|
|
|
1318
1365
|
if (message.userFacing)
|
|
1319
1366
|
enqueueNative(message.method, message.params);
|
|
1320
1367
|
}
|
|
1368
|
+
// ── Main-process HTTP request trace → synthesized Network.request*/
|
|
1369
|
+
// Network.loading* CDP ──────────────────────────────────────────────────
|
|
1370
|
+
// Same shape as the WebSocket trace bridge above, for the same reason:
|
|
1371
|
+
// wx.request now runs on Node http/https in this process (that's what
|
|
1372
|
+
// stops Chromium's Fetch/CORS algorithm from attaching a spurious OPTIONS
|
|
1373
|
+
// preflight to it), so no webContents.debugger can observe it either.
|
|
1374
|
+
const httpSynthesizer = new RequestTraceSynthesizer({
|
|
1375
|
+
epoch: String(Date.now()),
|
|
1376
|
+
internalOrigins: () => [
|
|
1377
|
+
bridge.getResourceServerBaseUrl?.(),
|
|
1378
|
+
bridge.getSimulatorServerBaseUrl?.(),
|
|
1379
|
+
],
|
|
1380
|
+
});
|
|
1381
|
+
function reportNativeRequestTrace(sessionId, event) {
|
|
1382
|
+
if (disposed)
|
|
1383
|
+
return;
|
|
1384
|
+
const message = httpSynthesizer.synthesize(sessionId, event);
|
|
1385
|
+
if (!message)
|
|
1386
|
+
return;
|
|
1387
|
+
const params = message.params;
|
|
1388
|
+
if (event.type === "redirect") {
|
|
1389
|
+
bodyCache.delete(params.requestId);
|
|
1390
|
+
postDataCache.delete(params.requestId);
|
|
1391
|
+
}
|
|
1392
|
+
// The response is already fully buffered in-process at `finished` — prime
|
|
1393
|
+
// the SAME cache a simulator-CDP prefetch would populate, so the
|
|
1394
|
+
// front-end's Get Response Body click resolves without a debugger
|
|
1395
|
+
// round-trip that (for this requestId) has nowhere to go.
|
|
1396
|
+
if (message.body) {
|
|
1397
|
+
bodyCache.prime(params.requestId, () => Promise.resolve(message.body));
|
|
1398
|
+
}
|
|
1399
|
+
if (message.postData !== undefined) {
|
|
1400
|
+
postDataCache.prime(params.requestId, () => Promise.resolve({ postData: message.postData }));
|
|
1401
|
+
}
|
|
1402
|
+
dispatchToGlobal(message.method, message.params);
|
|
1403
|
+
if (message.userFacing)
|
|
1404
|
+
enqueueNative(message.method, message.params);
|
|
1405
|
+
}
|
|
1321
1406
|
return {
|
|
1322
1407
|
attachSimulator,
|
|
1323
1408
|
detachSimulator,
|
|
@@ -1328,6 +1413,7 @@ export function createNetworkForwarder(bridge) {
|
|
|
1328
1413
|
// natively — surface it via the console fallback line.
|
|
1329
1414
|
report: (record) => forwardToConsole(record),
|
|
1330
1415
|
reportWebSocketTrace,
|
|
1416
|
+
reportNativeRequestTrace,
|
|
1331
1417
|
bodies: {
|
|
1332
1418
|
getResponseBody: (requestId) => bodyCache.lookup(requestId),
|
|
1333
1419
|
getRequestPostData: (requestId) => postDataCache.lookup(requestId),
|