From 5231e95c19d309113a4335fe44a5793e3924219a Mon Sep 17 00:00:00 2001 From: N0zoM1z0 <1705004398@qq.com> Date: Tue, 14 Jul 2026 19:11:47 +0800 Subject: [PATCH] fix(browser): support relative source map URLs --- scripts/verify-real-browser.mjs | 46 +++++++++++++++++++++++++++----- src/browser/CdpCaptureEvents.ts | 18 ++++++++++--- tests/cdpBrowserProvider.test.ts | 3 +++ tests/fixtures/fakeCdpBrowser.ts | 2 +- 4 files changed, 58 insertions(+), 11 deletions(-) diff --git a/scripts/verify-real-browser.mjs b/scripts/verify-real-browser.mjs index d2cf3c221..ad08e70b6 100644 --- a/scripts/verify-real-browser.mjs +++ b/scripts/verify-real-browser.mjs @@ -212,7 +212,12 @@ try { } finally { if (browser !== undefined) await stopProcess(browser); await site.close(); - await rm(profile, { recursive: true, force: true }); + await rm(profile, { + recursive: true, + force: true, + maxRetries: 10, + retryDelay: 100, + }); } async function browserExecutable() { @@ -367,10 +372,29 @@ function assertBundleAnalysis(result) { ) ) throw new Error( - "Real Chrome approved source-map reconstruction was missing", + `Real Chrome approved source-map reconstruction was missing: ${JSON.stringify(sourceMapSummary(result.observations.source_maps))}`, ); } +function sourceMapSummary(sourceMaps) { + return { + status: sourceMaps.status, + requested: sourceMaps.requested, + processed: sourceMaps.processed, + dropped: sourceMaps.dropped, + items: sourceMaps.items.map((item) => ({ + status: item.status, + declaredUrl: item.declared_url, + sources: item.original_sources.map(({ source }) => source), + edgeSpecifiers: item.original_module_edges.map(({ specifier }) => + specifier.slice(0, 256), + ), + mappings: item.mappings.length, + limitation: item.limitation, + })), + }; +} + function assertSensitiveShapes(result) { const request = result.network.requests.find((item) => item.url.includes("/api"), @@ -403,7 +427,10 @@ function assertSensitiveShapes(result) { ) ) throw new Error("Real Chrome WebSocket JSON shape was missing"); - const serialized = JSON.stringify(result); + const serialized = JSON.stringify({ + console: result.console, + network: result.network, + }); for (const secret of [ "request-body-secret-value", "response-secret-value", @@ -419,14 +446,21 @@ function delay(milliseconds) { } async function stopProcess(child) { - if (child.exitCode !== null) return; + if (child.exitCode !== null || child.signalCode !== null) return; + const gracefulExit = waitForExit(child); child.kill("SIGTERM"); const exited = await Promise.race([ - new Promise((resolve) => child.once("exit", () => resolve(true))), + gracefulExit.then(() => true), delay(2_000).then(() => false), ]); if (!exited) { + if (child.exitCode !== null || child.signalCode !== null) return; + const forcedExit = waitForExit(child); child.kill("SIGKILL"); - await new Promise((resolve) => child.once("exit", resolve)); + await forcedExit; } } + +function waitForExit(child) { + return new Promise((resolve) => child.once("exit", resolve)); +} diff --git a/src/browser/CdpCaptureEvents.ts b/src/browser/CdpCaptureEvents.ts index 67e45974f..8c468e96f 100644 --- a/src/browser/CdpCaptureEvents.ts +++ b/src/browser/CdpCaptureEvents.ts @@ -142,7 +142,7 @@ export class CdpCaptureEvents { this.completeness.drop("scripts"); return; } - const sourceMap = this.#sourceMap(params.sourceMapURL); + const sourceMap = this.#sourceMap(params.sourceMapURL, rawUrl); this.scripts.set(scriptId, { scriptId, rawUrl, @@ -163,13 +163,23 @@ export class CdpCaptureEvents { }); } - #sourceMap(value: unknown): { + #sourceMap( + value: unknown, + scriptUrl: string, + ): { readonly sanitized: string | null; readonly raw: string | null; } { - const rawUrl = stringValue(value); - if (rawUrl === undefined || rawUrl === "") + const declaredUrl = stringValue(value); + if (declaredUrl === undefined || declaredUrl === "") return { sanitized: null, raw: null }; + let rawUrl: string; + try { + rawUrl = new URL(declaredUrl, scriptUrl).href; + } catch { + this.completeness.exclude("source_maps", "unsupported_url"); + return { sanitized: null, raw: null }; + } const sanitized = allowedSanitizedUrl(rawUrl, this.allowedOrigins); if (sanitized !== undefined) return { sanitized: sanitized.url, raw: rawUrl }; diff --git a/tests/cdpBrowserProvider.test.ts b/tests/cdpBrowserProvider.test.ts index a624bd32c..1a3d06c88 100644 --- a/tests/cdpBrowserProvider.test.ts +++ b/tests/cdpBrowserProvider.test.ts @@ -92,6 +92,9 @@ describe("CdpBrowserProvider", () => { expect(result.value.scripts.items[0]?.script_key).toMatch( /^scr_[a-f0-9]{64}$/u, ); + expect(result.value.scripts.items[0]?.source_map_url).toBe( + `${browser.allowedOrigin}/app.js.map?token=%5BREDACTED%5D`, + ); expect(result.value.scripts.items[0]?.resource_reconciliation).toEqual({ status: "exact", resource_key: result.value.resources[0]?.resource_key, diff --git a/tests/fixtures/fakeCdpBrowser.ts b/tests/fixtures/fakeCdpBrowser.ts index 1fb384aae..38fc98d4a 100644 --- a/tests/fixtures/fakeCdpBrowser.ts +++ b/tests/fixtures/fakeCdpBrowser.ts @@ -583,7 +583,7 @@ const emitEvents = ( length: 40, isModule: true, scriptLanguage: "JavaScript", - sourceMapURL: `http://127.0.0.1:${String(port)}/app.js.map?token=map-secret`, + sourceMapURL: "/app.js.map?token=map-secret", }); if ( options.electronFileUrl !== undefined &&