mirror of
https://github.com/morluto/rea
synced 2026-10-28 15:07:26 -04:00
b5afa5f91e
* feat(browser): attribute native execution and listener sources Integrate finite native precise-coverage windows and direct DOM listener inspection through shared CLI/MCP workflows, exact session script identities, inline source evidence and owned instrumentation cleanup. Refs #881, #774 Assisted-by: Yukari (gensokyo-skills:yukari-boundary-analysis) Assisted-by: Kogasa (gensokyo-skills:kogasa-surprise-testing) * fix(browser): preserve runtime identity and settle fixture cleanup Retain complete reported initiator objects, reject changing loader availability, propagate the selected operation through main-frame authorization and complete effect/help inventories. Close only the synthetic owned Chrome via its native endpoint, await process-group settlement and preserve primary verification errors during cleanup. Refs #881 Assisted-by: Yukari (gensokyo-skills:yukari-boundary-analysis) Assisted-by: Kogasa (gensokyo-skills:kogasa-surprise-testing) * fix(browser): settle invalid events and preserve document evidence Accept same-document URL changes with proven stable loader identity, retain metadata without inventing ownership, terminate malformed event collection immediately, and sanitize known transport URLs without erasing sourceURL declarations. Assisted-by: Yukari (gensokyo-skills:yukari-boundary-analysis) Assisted-by: Kogasa (gensokyo-skills:kogasa-surprise-testing) * fix(browser): end detached target windows and sanitize coverage identities Reuse the existing flat-session event matcher, retain prearming termination, and apply the source identity policy to coverage URLs. Add real MCP target-close verification and producer regressions for detachment orderings. Assisted-by: Yukari (gensokyo-skills:yukari-boundary-analysis) Assisted-by: Kogasa (gensokyo-skills:kogasa-surprise-testing) * fix(browser): retain final sample metadata and settle fatal protocol failures Keep source inventory open through the resetting sample, propagate fatal wire failures without outstanding commands, and check listener collection failures after final document validation. Assisted-by: Yukari (gensokyo-skills:yukari-boundary-analysis) Assisted-by: Kogasa (gensokyo-skills:kogasa-surprise-testing) * fix(browser): require default execution worlds for website attribution Preserve native context and world metadata without treating same-frame isolated or unknown contexts as website sources. Verify actual Chrome isolated function execution before excluding its coverage, and cover ambiguous world metadata with producer regressions. Assisted-by: Yukari (gensokyo-skills:yukari-boundary-analysis) Assisted-by: Kogasa (gensokyo-skills:kogasa-surprise-testing) * fix(browser): revalidate runtime source joins and selector failures Assisted-by: Yukari (gensokyo-skills:yukari-boundary-analysis) Assisted-by: Kogasa (gensokyo-skills:kogasa-surprise-testing) --------- Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
111 lines
4.2 KiB
TypeScript
111 lines
4.2 KiB
TypeScript
import { AnalysisCancelledError } from "../../domain/analysisErrorCore.js";
|
|
import type { AnalysisError } from "../../domain/analysisErrorBase.js";
|
|
import type { WebExecution } from "../../domain/webExecution.js";
|
|
import type { CdpEvent } from "../CdpConnection.js";
|
|
import type { CdpRuntimeSession } from "./CdpRuntimeSession.js";
|
|
|
|
type EndReason = WebExecution["window"]["end_reason"];
|
|
/** One finite document-scoped window, with an actual armed point and event-driven termination. */
|
|
export class CdpExecutionWindow {
|
|
armedAt: string | undefined;
|
|
endedAt: string | undefined;
|
|
reason: EndReason | undefined;
|
|
#timer: NodeJS.Timeout | undefined;
|
|
#resolve: ((reason: EndReason) => void) | undefined;
|
|
#reject: ((error: AnalysisError) => void) | undefined;
|
|
#failure: AnalysisError | undefined;
|
|
#removeAbort: (() => void) | undefined;
|
|
constructor(
|
|
readonly session: Pick<CdpRuntimeSession, "operation" | "options"> & {
|
|
readonly target: { readonly frame_id: string };
|
|
},
|
|
) {}
|
|
|
|
/** Collection ends on a main-document replacement, never follows source URLs into another context. */
|
|
ingest(event: CdpEvent): void {
|
|
if (
|
|
event.method === "Target.detachedFromTarget" ||
|
|
event.method === "Inspector.targetCrashed"
|
|
) {
|
|
this.end("target_terminated");
|
|
return;
|
|
}
|
|
if (!this.active) return;
|
|
if (event.method === "Runtime.executionContextsCleared")
|
|
this.end("document_changed");
|
|
if (
|
|
event.method === "Page.frameNavigated" &&
|
|
typeof event.params === "object" &&
|
|
event.params !== null &&
|
|
"frame" in event.params
|
|
) {
|
|
const frame = event.params.frame;
|
|
if (typeof frame === "object" && frame !== null && !("parentId" in frame))
|
|
this.end("document_changed");
|
|
}
|
|
if (
|
|
event.method === "Page.frameDetached" &&
|
|
typeof event.params === "object" &&
|
|
event.params !== null &&
|
|
"frameId" in event.params &&
|
|
event.params.frameId === this.session.target.frame_id
|
|
)
|
|
this.end("document_changed");
|
|
}
|
|
/** Metadata before arming is inventory; request events outside the window are excluded. */
|
|
get active(): boolean {
|
|
return (
|
|
this.armedAt !== undefined &&
|
|
this.reason === undefined &&
|
|
this.endedAt === undefined
|
|
);
|
|
}
|
|
|
|
/** The timer starts at arming, before progress delivery can delay the external action. */
|
|
start(duration: number): Promise<EndReason> {
|
|
this.armedAt = new Date().toISOString();
|
|
if (this.#failure !== undefined) return Promise.reject(this.#failure);
|
|
if (this.reason !== undefined) return Promise.resolve(this.reason);
|
|
return new Promise((resolve, reject) => {
|
|
this.#resolve = resolve;
|
|
this.#reject = reject;
|
|
this.#timer = setTimeout(() => this.end("window_elapsed"), duration);
|
|
const signal = this.session.options.signal;
|
|
const abort = (): void => {
|
|
this.endedAt = new Date().toISOString();
|
|
this.dispose();
|
|
this.#reject?.(new AnalysisCancelledError(this.session.operation));
|
|
};
|
|
this.#removeAbort = () => signal?.removeEventListener("abort", abort);
|
|
signal?.addEventListener("abort", abort, { once: true });
|
|
if (signal?.aborted) abort();
|
|
});
|
|
}
|
|
|
|
/** Transport loss ends the window; instrumentation cleanup still requires confirmation. */
|
|
end(reason: EndReason): void {
|
|
if (this.reason !== undefined || this.#failure !== undefined) return;
|
|
this.reason = reason;
|
|
this.endedAt = new Date().toISOString();
|
|
this.dispose();
|
|
this.#resolve?.(reason);
|
|
}
|
|
/** Invalid producer data ends instrumentation immediately with its original failure. */
|
|
fail(error: AnalysisError): void {
|
|
if (this.reason !== undefined || this.#failure !== undefined) return;
|
|
this.#failure = error;
|
|
this.endedAt = new Date().toISOString();
|
|
this.dispose();
|
|
this.#reject?.(error);
|
|
}
|
|
/** Surface a failure recorded before local arming without emitting misleading progress. */
|
|
check(): void {
|
|
if (this.#failure !== undefined) throw this.#failure;
|
|
}
|
|
/** Release local timer and abort listener on every setup/failure path. */
|
|
dispose(): void {
|
|
if (this.#timer !== undefined) clearTimeout(this.#timer);
|
|
this.#removeAbort?.();
|
|
}
|
|
}
|