From c0c18a51913ab35e4d737f90de69a55288b8e45a Mon Sep 17 00:00:00 2001 From: JB Date: Mon, 21 Sep 2026 13:11:16 -0400 Subject: [PATCH] perf(fast-browser): compute scoped origin policy without worker dispatch --- extensions/fast-browser/runtime.test.ts | 111 +++++++++++++++++++++--- extensions/fast-browser/runtime.ts | 34 ++++++-- 2 files changed, 128 insertions(+), 17 deletions(-) diff --git a/extensions/fast-browser/runtime.test.ts b/extensions/fast-browser/runtime.test.ts index 6946736..7d4228e 100644 --- a/extensions/fast-browser/runtime.test.ts +++ b/extensions/fast-browser/runtime.test.ts @@ -1,6 +1,7 @@ import assert from "node:assert/strict"; import test from "node:test"; -import { createBrowserRuntimeFactory, withRuntimeOriginPolicy } from "./runtime.ts"; +import { createBrowserRuntimeFactory } from "./runtime.ts"; +import { BrowserOperationError } from "./contracts.ts"; import type { BrowserOperation, BrowserRuntimeOptions, @@ -9,7 +10,6 @@ import type { BrowserTransportResultFor, PrivateArtifactMetadata, PrivateArtifactStore, - RuntimeOperationContext, } from "./contracts.ts"; const tab = (id: string, url = "https://example.test/form") => ({ @@ -153,18 +153,105 @@ test("factory forwards pinned Python selection to the transport", async () => { await runtime.close(); }); -test("wildcard narrowing is scoped while restricted bases cannot broaden", async () => { - const first = artifacts(); +test("narrowing is local, lazy, and propagates only its effective context", async () => { + const store = artifacts(); const fixture = transportFixture(); - const runtime = createBrowserRuntimeFactory(options(first.store, fixture.transport).transportFactory).create(options(first.store, fixture.transport)); - await withRuntimeOriginPolicy(runtime, ["https://example.test"], (context) => runtime.read({}, new AbortController().signal, context)); - const reads = fixture.calls.filter((call) => call.operation.op === "read"); - assert.deepEqual((reads[0]?.context as RuntimeOperationContext).allowedOrigins, ["https://example.test"]); + let created = 0; + const runtime = createBrowserRuntimeFactory({ create() { created++; return fixture.transport; } }).create(options(store.store, fixture.transport)); + + const context = await runtime.narrowOriginPolicy({ allowedOrigins: ["https://example.test"] }, new AbortController().signal); + assert.deepEqual(context, { allowedOrigins: ["https://example.test"] }); + assert.equal(created, 0); + assert.equal(fixture.calls.length, 0); + + await runtime.read({}, new AbortController().signal, context); await runtime.read({}, new AbortController().signal); - assert.equal(fixture.calls.at(-1)?.context, undefined); - const restricted = createBrowserRuntimeFactory({ create() { return fixture.transport; } }).create({ ...options(first.store, fixture.transport), allowedOrigins: ["https://example.test"] }); - await assert.rejects(restricted.narrowOriginPolicy({ allowedOrigins: "*" }, new AbortController().signal), /broadened/); - await runtime.close(); await restricted.close(); + assert.equal(created, 1); + assert.deepEqual(fixture.calls[0]?.context, { allowedOrigins: ["https://example.test"] }); + assert.equal(fixture.calls[1]?.context, undefined); + assert.equal(fixture.calls.some((call) => call.operation.op === "narrowOriginPolicy"), false); + await runtime.close(); +}); + +test("narrowing validates invalid and broadened policies without dispatch", async () => { + const store = artifacts(); + const fixture = transportFixture(); + let created = 0; + const runtime = createBrowserRuntimeFactory({ create() { created++; return fixture.transport; } }).create({ + ...options(store.store, fixture.transport), allowedOrigins: ["https://example.test"], + }); + + await assert.rejects(runtime.narrowOriginPolicy({ allowedOrigins: [] }, new AbortController().signal), /must not be empty/); + await assert.rejects(runtime.narrowOriginPolicy({ allowedOrigins: "*" }, new AbortController().signal), (error: unknown) => { + assert.ok(error instanceof BrowserOperationError); + assert.equal(error.details.code, "policy_denied"); + assert.equal(error.details.mutationState, "not_attempted"); + return true; + }); + assert.equal(created, 0); + assert.equal(fixture.calls.length, 0); + await runtime.close(); +}); + +test("narrowing preserves closed and aborted lifecycle errors", async () => { + const store = artifacts(); + const fixture = transportFixture(); + const runtime = createBrowserRuntimeFactory({ create() { return fixture.transport; } }).create(options(store.store, fixture.transport)); + await runtime.close(); + await assert.rejects(runtime.narrowOriginPolicy({ allowedOrigins: ["https://example.test"] }, new AbortController().signal), (error: unknown) => { + assert.ok(error instanceof BrowserOperationError); + assert.equal(error.details.code, "browser_closed"); + return true; + }); + + const parent = new AbortController(); + parent.abort(new Error("browser session closed")); + const abortedRuntime = createBrowserRuntimeFactory({ create() { return fixture.transport; } }).create(options(store.store, fixture.transport), parent.signal); + await assert.rejects(abortedRuntime.narrowOriginPolicy({ allowedOrigins: ["https://example.test"] }, new AbortController().signal), (error: unknown) => { + assert.ok(error instanceof BrowserOperationError); + assert.equal(error.details.code, "browser_closed"); + return true; + }); + const operation = new AbortController(); + operation.abort(); + const liveRuntime = createBrowserRuntimeFactory({ create() { return fixture.transport; } }).create(options(store.store, fixture.transport)); + await assert.rejects(liveRuntime.narrowOriginPolicy({ allowedOrigins: ["https://example.test"] }, operation.signal), (error: unknown) => { + assert.ok(error instanceof Error); + assert.equal(error.name, "AbortError"); + return true; + }); + await abortedRuntime.close(); + await liveRuntime.close(); +}); + +test("narrowing remains serialized behind an in-flight transport operation", async () => { + const store = artifacts(); + const fixture = transportFixture(); + let releaseRead!: () => void; + let readStarted!: () => void; + const readGate = new Promise((resolve) => { releaseRead = resolve; }); + const started = new Promise((resolve) => { readStarted = resolve; }); + const transport: BrowserTransport = { + async dispatch(request: BrowserTransportRequest, signal: AbortSignal): Promise> { + if (request.operation.op === "read") { + readStarted(); + await readGate; + } + return fixture.transport.dispatch(request, signal); + }, + async close() { return fixture.transport.close(); }, + }; + const runtime = createBrowserRuntimeFactory({ create() { return transport; } }).create(options(store.store, transport)); + const read = runtime.read({}, new AbortController().signal); + await started; + let narrowed = false; + const narrow = runtime.narrowOriginPolicy({ allowedOrigins: ["https://example.test"] }, new AbortController().signal).then(() => { narrowed = true; }); + await Promise.resolve(); + assert.equal(narrowed, false); + releaseRead(); + await Promise.all([read, narrow]); + assert.equal(narrowed, true); + await runtime.close(); }); test("policy is enforced before navigation and tabs are origin filtered", async () => { diff --git a/extensions/fast-browser/runtime.ts b/extensions/fast-browser/runtime.ts index e27fc3b..694c5ae 100644 --- a/extensions/fast-browser/runtime.ts +++ b/extensions/fast-browser/runtime.ts @@ -449,6 +449,30 @@ export function createBrowserRuntime( return run; }; + // Policy narrowing is a runtime concern, not a worker operation. Keep it + // on the same lane so it observes the same lifecycle and serialization + // boundaries without starting a transport or emitting JSONL. + const invokeLocal = ( + operation: BrowserRuntimeOperationName, + signal: AbortSignal, + action: () => T, + ): Promise => { + const run = lane.then(() => { + if (closed && operation !== "close") throw operationError(operation, "lifecycle", "browser_closed", "browser session is closed", "not_attempted"); + if (parentSignal.aborted && operation !== "close") throw operationError(operation, "lifecycle", "browser_closed", "browser session is unavailable", "not_attempted"); + // Match invoke(): a pre-aborted operation signal is rejected before + // dispatch/error shaping, while lifecycle failures remain typed. + throwIfAborted(signal); + try { + return action(); + } catch (error) { + throw asOperationError(error, operation, "dispatch", false); + } + }); + lane = run.catch(() => undefined); + return run; + }; + const captureAfterFailure = async (operation: BrowserRuntimeOperationName, context?: RuntimeOperationContext): Promise => { if (parentSignal.aborted) return { status: "unavailable", reason: "browser session is unavailable" }; if (operation === "captureEvidence" || operation === "close" || operation === "narrowOriginPolicy") { @@ -500,6 +524,10 @@ export function createBrowserRuntime( const controller = new AbortController(); const timed = timedSignal(controller.signal, OPERATION_BUDGETS.close); try { + // Wait for local-only operations too. A narrow policy must not race + // session teardown, and waiting here still keeps an unused transport + // unstarted. + await lane; if (transport) { await invoke("close", { op: "close" }, timed.signal, undefined, () => undefined).catch(() => undefined); await transport.close().catch(() => {}); @@ -612,11 +640,7 @@ export function createBrowserRuntime( const allowedOrigins = normalizePolicy(request.allowedOrigins); if (basePolicy !== "*" && (allowedOrigins === "*" || allowedOrigins.some((origin) => !basePolicy.includes(origin)))) throw operationError("narrowOriginPolicy", "policy", "policy_denied", "origin policy cannot be broadened", "not_attempted"); const context = { allowedOrigins }; - const result = await invoke("narrowOriginPolicy", { op: "narrowOriginPolicy", request: { allowedOrigins } }, signal, { allowedOrigins: basePolicy }, (raw) => { - if (!raw || typeof raw !== "object" || !("allowedOrigins" in raw)) throw new Error("worker returned invalid origin policy"); - return context; - }); - return result; + return invokeLocal("narrowOriginPolicy", signal, () => context); }, close, };