Passive page capture retained console entries and websocket frames with no aggregate ceiling. Every sibling retention surface already enforces WEB_RUNTIME_LIMITS.retainedEventBytes (8 MiB): the observation session's pre-arm buffer and document-request identities, the browser runtime sources, requests, and metadata, the V8 inspector's retained metadata, and this same capture's script metadata, which has its own count-and-bytes budget. A page that logs a few hundred megabytes of console text, or sends text frames whose JSON shapes carry hundreds of thousands of keys, grew the capture without limit until the process ran out of memory; the MCP response budget runs after the handler, so the growth happened before any typed refusal could exist. Admit each console entry and websocket frame against the same shared budget before retaining it. A refused entry is counted through the completeness tracker's dropped-events vocabulary, which already carries console_events and websocket_frames kinds and was so far used only for late scripts, so the inspection reports a truncated inventory with the dropped count instead of silently overflowing. Console entry bytes measure the retained representation: the entry's type, url, argument-type strings, and the text capture's retained bytes, which the handler already computed per entry. A websocket frame retains its shape summary rather than the payload, and the shape grows with the payload's own keys, so the frame is charged the serialized size of the shape it retains. beginAuthorizedFrame resets the counter together with the rest of the per-frame state. Out of scope: response metadata and agent hints, whose rows are fixed-shape facts already bounded per response, and any new setting. The shared 8 MiB constant is reused as-is. Validation: RED then green in src/browser/CdpCaptureEvents.eventBudget.test.ts, where the console case previously retained every oversized entry and the websocket case retained the over-budget frame; 26 tests across the neighboring console, websocket, coverage, and completeness suites pass. On this Windows host the wider browser project has 26 pre-existing failures in three Electron/WebBundle worker-spawn files, reproduced identically with this change stashed (26 failed, 644 passed on the pristine base; 26 failed, 647 passed with it on the same file set). tsc --noEmit over the changed set exits 0, oxlint reports 0 errors, oxfmt --check is clean, and verify:module-boundaries passes. No real browser session was driven on this host; the browser CI lane owns that coverage. Each retained entry now carries a conservative fixed overhead, and each captured console text value a wrapper cost, so a stream of minimal entries cannot admit millions of objects into the budget when shapes are disabled; a mixed-kind test locks that console entries and websocket frames spend one shared allowance.
42 lines
1.9 KiB
TypeScript
42 lines
1.9 KiB
TypeScript
import type { InspectWebPageInput } from "../domain/browserObservation.js";
|
|
import type { WebPageInspection } from "../domain/browserObservationSchemas.js";
|
|
import type { CdpCaptureCompleteness } from "./CdpCaptureCompleteness.js";
|
|
import type { CapturedScript, NetworkState } from "./CdpCaptureEventTypes.js";
|
|
|
|
export interface CdpCaptureEventsState {
|
|
readonly input: InspectWebPageInput;
|
|
readonly allowedOrigins: ReadonlySet<string>;
|
|
readonly scripts: Map<string, CapturedScript>;
|
|
readonly scriptMetadataBudgetOmissionsById: Map<string, true>;
|
|
rejectedScriptMetadataBudgetCount: number;
|
|
rejectedSourceMapMetadataBudgetCount: number;
|
|
readonly scriptMetadataBytesById: Map<string, number>;
|
|
retainedScriptMetadataBytes: number;
|
|
/** Console-entry and websocket-frame bytes admitted against the shared retained-event budget. */
|
|
retainedEventBytes: number;
|
|
readonly executionContextFrames: Map<string, string>;
|
|
readonly network: Map<string, NetworkState>;
|
|
readonly networkRequestTimestamps: Map<string, number>;
|
|
readonly malformedRedirectRequestIds: Set<string>;
|
|
/** Requests whose selected JSON request-body shape was expected but not obtained. */
|
|
readonly missingRequestBodyShapeIds: Set<string>;
|
|
readonly webSocketConnectionsById: Map<
|
|
string,
|
|
WebPageInspection["network"]["websocket_connections"][number]
|
|
>;
|
|
console: WebPageInspection["console"]["events"];
|
|
responseMetadata: WebPageInspection["metadata"]["responses"];
|
|
agentHints: WebPageInspection["metadata"]["agent_hints"];
|
|
readonly completeness: CdpCaptureCompleteness;
|
|
originViolation: boolean;
|
|
navigationDuringCapture: boolean;
|
|
unknownRootIdentity: boolean;
|
|
mainFrameId: string | undefined;
|
|
/**
|
|
* Identity of the document last observed in the main frame. `frameNavigated`
|
|
* re-fires for the same document, so this is what distinguishes a real
|
|
* document change from a duplicate event.
|
|
*/
|
|
committedDocument: string | undefined;
|
|
}
|