mirror of
https://github.com/morluto/rea
synced 2026-10-26 22:28:27 -04:00
0155b89953
* fix(browser): name web-bundle budgets and recover web deadlines Name the existing heap, per-script, and total web-bundle phases, and reject a per-script deadline above the total. Promote capture, CDP command, source-map decode, and source-map fetch deadlines at today's defaults. Name DevTools timeoutMs, and state that discovery, cleanup grace, byte budgets, and Electron hook retention have no runtime override. Refs #5910 Co-authored-by: morluto <morluto@users.noreply.github.com> * fix(browser): apply selected web deadlines to the owning phase Keep HTTP and browser-socket discovery on the fixed discovery deadline so a short command_timeout_ms starts at the handshake. Apply scenario command_timeout_ms to launch startup and the primary connect session, and keep service-worker restoration on the fixed 5000 ms grace. Omit command_timeout_ms from recovery text when the operation does not accept it. Co-authored-by: morluto <morluto@users.noreply.github.com> * fix(browser): retain scenario commands that finish after their deadline A timed-out scenario command stays owned so cleanup can close a late browser or context and restore a mutation that lands after the deadline. Child storage replies use that deadline, scenario recovery names command_timeout_ms in the capture JSON, and the session test connection implements beginCleanupGrace. Co-authored-by: morluto <morluto@users.noreply.github.com> * fix(browser): keep child command timeouts as the public failure A nested command such as Page.enable that is delivered but never answered was stored as protocol_error, hiding the command name, selected deadline, and recovery. Store the typed timeout and assert that public error. Co-authored-by: morluto <morluto@users.noreply.github.com> * fix(browser): keep runtime cleanup on the fixed grace Switch runtime restoration to the cleanup grace before the first send so a short command_timeout_ms cannot fail an otherwise successful observation. Stop advertising command_timeout_ms on list_browser_targets, which never bounds a post-discovery command. Co-authored-by: morluto <morluto@users.noreply.github.com> * test(browser): prove a short command deadline cannot fail runtime cleanup Drive restoration through the runtime fixture. A cleanup command that outlives command_timeout_ms still succeeds, and a stalled restoration names the fixed 5000 ms grace. Co-authored-by: morluto <morluto@users.noreply.github.com> * fix(browser): name the effective source-map decode deadline codecBudgetLimitation reported a fixed 20-second deadline after a caller selected decode_timeout_ms. Result and Evidence limitations now name that effective millisecond deadline. Co-authored-by: morluto <morluto@users.noreply.github.com> * fix(browser): separate handshake and target-attach deadlines A browser-level Target.attachToTarget no longer shares the WebSocket handshake budget. Blocked service-worker attachment keeps discovery on its fixed deadline and applies command_timeout_ms to the later handshake and attach. Co-authored-by: morluto <morluto@users.noreply.github.com> * fix(browser): name the web-bundle resource each runtime exhausts Bun ignores the V8 old-space flag. A Windows Job reports the process commit limit, and other Bun hosts no longer suggest raising max_heap_mb. Co-authored-by: morluto <morluto@users.noreply.github.com> * fix(browser): cite the browser-socket discovery message budget WebSocket discovery enforces the 64 MiB protocol budget. Timeout recovery no longer advertises the 8 MiB HTTP document budget. Co-authored-by: morluto <morluto@users.noreply.github.com> * fix(browser): bound the blocked service-worker check before discovery The first registration check has its own fixed 10s deadline. Discovery, handshake, and Target.attachToTarget stay on their separate deadlines. Co-authored-by: morluto <morluto@users.noreply.github.com> * fix(browser): name the operand that stopped web-bundle admission Host heap pressure no longer claims the fixed result budget is exhausted. A Windows Bun allocation before the commit limit still names that limit. Co-authored-by: morluto <morluto@users.noreply.github.com> --------- Co-authored-by: Cursor Agent <cursoragent@cursor.com> Co-authored-by: morluto <morluto@users.noreply.github.com>
167 lines
5.6 KiB
TypeScript
167 lines
5.6 KiB
TypeScript
import type {
|
|
ElectronPageInspection,
|
|
InspectElectronPageInput,
|
|
} from "../domain/javascript/electronObservation.js";
|
|
import { digestCanonicalValue } from "../domain/canonicalDigest.js";
|
|
import { createWebTextArtifact } from "../domain/webContentArtifact.js";
|
|
import type { CdpConnection } from "./CdpConnection.js";
|
|
import { CdpCaptureCompleteness } from "./CdpCaptureCompleteness.js";
|
|
import { requiredScriptSource } from "./CdpCaptureValues.js";
|
|
import type { ElectronScriptDraft } from "./CdpElectronScriptEvents.js";
|
|
import { authorizedElectronFile } from "./ElectronFileScope.js";
|
|
import { compareUnicodeCodePoints } from "../domain/unicodeCodePointOrder.js";
|
|
import { AnalysisCancelledError } from "../domain/analysisErrorCore.js";
|
|
import { selectedCdpCommandTimeoutMs } from "../domain/browserDeadline.js";
|
|
import {
|
|
CDP_SCRIPT_SOURCE_BYTES_OMISSION_REASON,
|
|
cdpScriptSourceDeadlineOmissionReason,
|
|
CdpScriptSourceBudget,
|
|
} from "./CdpScriptSourceBudget.js";
|
|
|
|
interface ElectronScriptCaptureInput {
|
|
readonly connection: CdpConnection;
|
|
readonly sessionId: string | undefined;
|
|
readonly signal?: AbortSignal;
|
|
readonly request: InspectElectronPageInput;
|
|
readonly scripts: readonly ElectronScriptDraft[];
|
|
readonly frameIds: ReadonlySet<string>;
|
|
readonly completeness: CdpCaptureCompleteness;
|
|
}
|
|
|
|
/** Normalize local script metadata and optionally approved source. */
|
|
export const captureElectronScripts = async (
|
|
input: ElectronScriptCaptureInput,
|
|
): Promise<ElectronPageInspection["scripts"]> => {
|
|
let total = 0;
|
|
const items: ElectronPageInspection["scripts"]["items"] = [];
|
|
const seen = new Set<string>();
|
|
const commandTimeoutMs = selectedCdpCommandTimeoutMs(
|
|
input.request.command_timeout_ms,
|
|
);
|
|
const deadlineOmission =
|
|
cdpScriptSourceDeadlineOmissionReason(commandTimeoutMs);
|
|
const sourceBudget =
|
|
input.request.include_script_sources && input.scripts.length > 0
|
|
? new CdpScriptSourceBudget(input.signal, commandTimeoutMs)
|
|
: undefined;
|
|
try {
|
|
for (const script of input.scripts) {
|
|
throwIfCallerCancelled(input.signal);
|
|
const path = await authorizedElectronFile(
|
|
script.rawUrl,
|
|
input.signal,
|
|
"inspect_electron_page",
|
|
);
|
|
if (path === undefined) {
|
|
input.completeness.exclude("scripts", "out_of_target_scope");
|
|
continue;
|
|
}
|
|
const identity = {
|
|
frame_id:
|
|
script.frameId !== null && input.frameIds.has(script.frameId)
|
|
? script.frameId
|
|
: null,
|
|
file_path: path,
|
|
cdp_hash: script.hash,
|
|
length: script.length,
|
|
is_module: script.isModule,
|
|
language: script.language,
|
|
};
|
|
const scriptKey = `electron_script_${digestCanonicalValue(identity, "CDP capture")}`;
|
|
if (seen.has(scriptKey)) continue;
|
|
seen.add(scriptKey);
|
|
total += 1;
|
|
let source: ElectronScriptSource = input.request.include_script_sources
|
|
? {
|
|
included: false,
|
|
reason: deadlineOmission,
|
|
}
|
|
: {
|
|
included: false,
|
|
reason: "source capture was not selected",
|
|
};
|
|
if (
|
|
input.request.include_script_sources &&
|
|
isSourceDeadlineExpired(sourceBudget)
|
|
) {
|
|
excludeScriptSource(input.completeness);
|
|
} else if (
|
|
input.request.include_script_sources &&
|
|
sourceBudget !== undefined
|
|
) {
|
|
try {
|
|
source = await captureScriptSource(input, script, sourceBudget);
|
|
} catch (cause: unknown) {
|
|
throwIfCallerCancelled(input.signal);
|
|
if (isSourceDeadlineExpired(sourceBudget)) {
|
|
excludeScriptSource(input.completeness);
|
|
} else {
|
|
throw cause;
|
|
}
|
|
}
|
|
}
|
|
items.push({ script_key: scriptKey, ...identity, source });
|
|
}
|
|
} finally {
|
|
sourceBudget?.dispose();
|
|
}
|
|
if (!input.request.include_script_sources)
|
|
input.completeness.exclude("script_sources", "not_approved", total);
|
|
return {
|
|
total,
|
|
items: items.sort((left, right) =>
|
|
compareUnicodeCodePoints(left.script_key, right.script_key),
|
|
),
|
|
};
|
|
};
|
|
|
|
type ElectronScriptSource =
|
|
ElectronPageInspection["scripts"]["items"][number]["source"];
|
|
|
|
const captureScriptSource = async (
|
|
input: ElectronScriptCaptureInput,
|
|
script: ElectronScriptDraft,
|
|
sourceBudget: CdpScriptSourceBudget,
|
|
): Promise<ElectronScriptSource> => {
|
|
const response = await input.connection.send(
|
|
"Debugger.getScriptSource",
|
|
{ scriptId: script.scriptId },
|
|
input.sessionId,
|
|
sourceBudget.signal,
|
|
);
|
|
throwIfCallerCancelled(input.signal);
|
|
const text = requiredScriptSource(response, "inspect_electron_page");
|
|
if (sourceBudget.timedOut) {
|
|
excludeScriptSource(input.completeness);
|
|
return {
|
|
included: false,
|
|
reason: cdpScriptSourceDeadlineOmissionReason(
|
|
sourceBudget.commandTimeoutMs,
|
|
),
|
|
};
|
|
}
|
|
if (!sourceBudget.admit(text)) {
|
|
excludeScriptSource(input.completeness);
|
|
return { included: false, reason: CDP_SCRIPT_SOURCE_BYTES_OMISSION_REASON };
|
|
}
|
|
return {
|
|
included: true,
|
|
artifact: createWebTextArtifact(text, "text/javascript"),
|
|
};
|
|
};
|
|
|
|
const excludeScriptSource = (completeness: CdpCaptureCompleteness): void => {
|
|
completeness.exclude("script_sources", "resource_budget_exhausted");
|
|
};
|
|
|
|
const throwIfCallerCancelled = (signal: AbortSignal | undefined): void => {
|
|
if (signal?.aborted === true)
|
|
throw new AnalysisCancelledError("inspect_electron_page", {
|
|
cause: signal.reason,
|
|
});
|
|
};
|
|
|
|
const isSourceDeadlineExpired = (
|
|
sourceBudget: CdpScriptSourceBudget | undefined,
|
|
): boolean => sourceBudget?.timedOut === true;
|