From 5093d51c057ea7dfc89f919a01af3cb4cc482a79 Mon Sep 17 00:00:00 2001 From: morluto <76467478+morluto@users.noreply.github.com> Date: Tue, 27 Oct 2026 10:30:57 +0800 Subject: [PATCH] refactor(browser): share CDP script source capture --- src/browser/CdpElectronScripts.ts | 68 ++++++++------------------- src/browser/CdpPageCaptureScripts.ts | 51 +++++--------------- src/browser/CdpScriptSourceCapture.ts | 49 +++++++++++++++++++ 3 files changed, 80 insertions(+), 88 deletions(-) create mode 100644 src/browser/CdpScriptSourceCapture.ts diff --git a/src/browser/CdpElectronScripts.ts b/src/browser/CdpElectronScripts.ts index 5ed027cf62..eb2e5ec023 100644 --- a/src/browser/CdpElectronScripts.ts +++ b/src/browser/CdpElectronScripts.ts @@ -4,10 +4,9 @@ import type { 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 { capturePassiveCdpScriptSource } from "./CdpScriptSourceCapture.js"; import { jsonValueByteLength } from "../domain/jsonValueByteLength.js"; import { CDP_CAPTURE_SCRIPT_METADATA_LIMITS } from "./CdpCaptureEventTypes.js"; import type { @@ -19,7 +18,6 @@ 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"; @@ -104,15 +102,16 @@ export const captureElectronScripts = async ( seen.add(scriptKey); metadataBytes += retainedBytes; total += 1; - let source: ElectronScriptSource = input.request.include_script_sources - ? { - included: false, - reason: deadlineOmission, - } - : { - included: false, - reason: "source capture was not selected", - }; + let source: ElectronPageInspection["scripts"]["items"][number]["source"] = + 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) @@ -123,7 +122,15 @@ export const captureElectronScripts = async ( sourceBudget !== undefined ) { try { - source = await captureScriptSource(input, script, sourceBudget); + source = await capturePassiveCdpScriptSource({ + connection: input.connection, + sessionId: input.sessionId, + signal: input.signal, + operation: "inspect_electron_page", + scriptId: script.scriptId, + sourceBudget, + completeness: input.completeness, + }); } catch (cause: unknown) { throwIfCallerCancelled(input.signal); if (isSourceDeadlineExpired(sourceBudget)) { @@ -148,41 +155,6 @@ export const captureElectronScripts = async ( }; }; -type ElectronScriptSource = - ElectronPageInspection["scripts"]["items"][number]["source"]; - -const captureScriptSource = async ( - input: ElectronScriptCaptureInput, - script: ElectronScriptDraft, - sourceBudget: CdpScriptSourceBudget, -): Promise => { - 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"); }; diff --git a/src/browser/CdpPageCaptureScripts.ts b/src/browser/CdpPageCaptureScripts.ts index 14a8531775..7db3a3cac4 100644 --- a/src/browser/CdpPageCaptureScripts.ts +++ b/src/browser/CdpPageCaptureScripts.ts @@ -6,11 +6,8 @@ import { import type { CapturedResource } from "./CdpCaptureDocuments.js"; import type { CdpCaptureEvents } from "./CdpCaptureEvents.js"; import type { CapturedScript } from "./CdpCaptureEventTypes.js"; -import { - requiredScriptSource, - sourceExcluded, - sourceResult, -} from "./CdpCaptureValues.js"; +import { sourceExcluded } from "./CdpCaptureValues.js"; +import { capturePassiveCdpScriptSource } from "./CdpScriptSourceCapture.js"; import type { CaptureContext } from "./CdpPageCapture.js"; import type { WebSourceMapRequest } from "./WebSourceMapFetcher.js"; import { compareUnicodeCodePoints } from "../domain/unicodeCodePointOrder.js"; @@ -18,7 +15,6 @@ import { AnalysisCancelledError } from "../domain/analysisErrorCore.js"; import type { BrowserObservationOperation } from "../domain/browserObservationErrors.js"; import { selectedCdpCommandTimeoutMs } from "../domain/browserDeadline.js"; import { - CDP_SCRIPT_SOURCE_BYTES_OMISSION_REASON, cdpScriptSourceDeadlineOmissionReason, CdpScriptSourceBudget, } from "./CdpScriptSourceBudget.js"; @@ -101,7 +97,15 @@ export const captureScripts = async ( sourceBudget !== undefined ) { try { - source = await captureScriptSource(script, state, sourceBudget); + source = await capturePassiveCdpScriptSource({ + connection: context.connection, + sessionId: context.sessionId, + signal: context.signal, + operation: context.operation, + scriptId: script.scriptId, + sourceBudget, + completeness: events.completeness, + }); } catch (cause: unknown) { throwIfCallerCancelled(context.signal, context.operation); if (isSourceDeadlineExpired(sourceBudget)) @@ -168,39 +172,6 @@ const buildScriptDraft = ( }; }; -const captureScriptSource = async ( - script: CapturedScript, - state: ScriptDraftState, - sourceBudget: CdpScriptSourceBudget, -): Promise => { - const { context } = state; - const response = await context.connection.send( - "Debugger.getScriptSource", - { scriptId: script.scriptId }, - context.sessionId, - sourceBudget.signal, - ); - throwIfCallerCancelled(context.signal, context.operation); - const content = requiredScriptSource(response, context.operation); - if (sourceBudget.timedOut) { - state.events.completeness.exclude( - "script_sources", - "resource_budget_exhausted", - ); - return sourceExcluded( - cdpScriptSourceDeadlineOmissionReason(sourceBudget.commandTimeoutMs), - ); - } - if (!sourceBudget.admit(content)) { - state.events.completeness.exclude( - "script_sources", - "resource_budget_exhausted", - ); - return sourceExcluded(CDP_SCRIPT_SOURCE_BYTES_OMISSION_REASON); - } - return sourceResult(content); -}; - const throwIfCallerCancelled = ( signal: AbortSignal | undefined, operation: BrowserObservationOperation, diff --git a/src/browser/CdpScriptSourceCapture.ts b/src/browser/CdpScriptSourceCapture.ts new file mode 100644 index 0000000000..87058bffe5 --- /dev/null +++ b/src/browser/CdpScriptSourceCapture.ts @@ -0,0 +1,49 @@ +import { AnalysisCancelledError } from "../domain/analysisErrorCore.js"; +import type { BrowserObservationOperation } from "../domain/browserObservationErrors.js"; +import type { CdpConnection } from "./CdpConnection.js"; +import type { CdpCaptureCompleteness } from "./CdpCaptureCompleteness.js"; +import { + requiredScriptSource, + sourceExcluded, + sourceResult, +} from "./CdpCaptureValues.js"; +import { + CDP_SCRIPT_SOURCE_BYTES_OMISSION_REASON, + cdpScriptSourceDeadlineOmissionReason, + type CdpScriptSourceBudget, +} from "./CdpScriptSourceBudget.js"; + +/** One owner for passive page/Electron source admission and artifact creation. */ +export const capturePassiveCdpScriptSource = async (input: { + readonly connection: CdpConnection; + readonly sessionId: string | undefined; + readonly signal: AbortSignal | undefined; + readonly operation: BrowserObservationOperation; + readonly scriptId: string; + readonly sourceBudget: CdpScriptSourceBudget; + readonly completeness: CdpCaptureCompleteness; +}): Promise< + ReturnType | ReturnType +> => { + const response = await input.connection.send( + "Debugger.getScriptSource", + { scriptId: input.scriptId }, + input.sessionId, + input.sourceBudget.signal, + ); + if (input.signal?.aborted === true) + throw new AnalysisCancelledError(input.operation, { + cause: input.signal.reason, + }); + const text = requiredScriptSource(response, input.operation); + const reason = input.sourceBudget.timedOut + ? cdpScriptSourceDeadlineOmissionReason(input.sourceBudget.commandTimeoutMs) + : !input.sourceBudget.admit(text) + ? CDP_SCRIPT_SOURCE_BYTES_OMISSION_REASON + : undefined; + if (reason !== undefined) { + input.completeness.exclude("script_sources", "resource_budget_exhausted"); + return sourceExcluded(reason); + } + return sourceResult(text); +};