Files
rea/src/browser/execution/CdpExecutionCollection.ts
T
morluto 5891bfb302 fix(browser): retain precise observation termination facts
Distinguish CDP session detachment, observed crashes, and transport loss. Preserve the observed window alongside the original typed coverage-stop failure and unconfirmed cleanup.

Validated with 40 focused boundary tests, check:ci, docs:check, and the real Chrome public CLI/MCP verifier on macOS. Browser timing comparison remains incomplete because unchanged-main fixture readiness was intermittent.

Fixes #5756
2026-10-21 14:50:32 +08:00

65 lines
2.4 KiB
TypeScript

import { AnalysisError } from "../../domain/analysisErrorBase.js";
import { AnalysisOutputError } from "../../domain/analysisErrorCore.js";
import { cdpTargetEventMatches } from "../CdpTargetEvents.js";
import { CdpExecutionWindow } from "./CdpExecutionWindow.js";
import { CdpRuntimeRequests } from "./CdpRuntimeRequests.js";
import type { CdpRuntimeSources } from "./CdpRuntimeSources.js";
/** Own source/request collection and failure subscriptions through a precise-sample cutoff. */
export class CdpExecutionCollection {
readonly window: CdpExecutionWindow;
readonly requests: CdpRuntimeRequests;
readonly #unsubscribers: readonly (() => void)[];
#metadataOpen = true;
constructor(readonly sources: CdpRuntimeSources) {
const session = sources.session;
this.window = new CdpExecutionWindow(session);
this.requests = new CdpRuntimeRequests(sources);
this.#unsubscribers = [
session.transport.connection.onEvent((event) => {
if (!cdpTargetEventMatches(event, session.transport.sessionId)) return;
try {
if (
this.#metadataOpen &&
(this.window.reason === undefined ||
this.window.reason === "window_elapsed")
)
sources.ingest(event);
else if (this.window.reason === "window_elapsed")
sources.verifyKnownIdentity(event);
if (this.window.active) this.requests.ingest(event);
sources.check();
this.requests.check();
} catch (cause: unknown) {
this.window.fail(
cause instanceof AnalysisError
? cause
: new AnalysisOutputError(session.operation, String(cause)),
);
}
this.window.ingest(event);
}),
session.transport.connection.onDisconnect((error) =>
this.window.fail(error),
),
session.transport.connection.onProtocolFailure((error) =>
this.window.fail(error),
),
];
}
/** Freeze metadata after receiving the final sample so source joins use one fixed inventory. */
freezeMetadata(): void {
this.#metadataOpen = false;
this.sources.check();
}
/** Remove every owned subscription and local timer on success, cancellation or failure. */
close(): void {
this.#metadataOpen = false;
this.window.dispose();
for (const unsubscribe of this.#unsubscribers) unsubscribe();
}
}