fix(canvas): prevent AbortSignal cloning in render worker

This commit is contained in:
2026-04-11 11:23:02 +02:00
parent f499aea691
commit 61728f9e52
3 changed files with 49 additions and 5 deletions

View File

@@ -21,7 +21,7 @@ type PreviewWorkerPayload = {
featureFlags?: BackendFeatureFlags;
};
type FullWorkerPayload = RenderFullOptions & {
type FullWorkerPayload = Omit<RenderFullOptions, "signal"> & {
featureFlags?: BackendFeatureFlags;
};

View File

@@ -32,7 +32,7 @@ type PreviewWorkerPayload = {
featureFlags?: BackendFeatureFlags;
};
type FullWorkerPayload = RenderFullOptions & {
type FullWorkerPayload = Omit<RenderFullOptions, "signal"> & {
featureFlags?: BackendFeatureFlags;
};
@@ -323,7 +323,7 @@ function runWorkerRequest<TResponse extends PreviewRenderResult | RenderFullResu
worker.postMessage({
kind: "full",
requestId,
payload: args.payload as RenderFullOptions,
payload: args.payload as FullWorkerPayload,
} satisfies WorkerRequestMessage);
});
}
@@ -501,14 +501,16 @@ export async function renderPreviewWithWorkerFallback(options: {
export async function renderFullWithWorkerFallback(
options: RenderFullOptions,
): Promise<RenderFullResult> {
const { signal, ...serializableOptions } = options;
try {
return await runWorkerRequest<RenderFullResult>({
kind: "full",
payload: {
...options,
...serializableOptions,
featureFlags: getWorkerFeatureFlagsSnapshot(),
},
signal: options.signal,
signal,
});
} catch (error: unknown) {
if (isAbortError(error)) {

View File

@@ -199,6 +199,48 @@ describe("worker-client fallbacks", () => {
expect(bridgeMocks.renderFull).not.toHaveBeenCalled();
});
it("does not include AbortSignal in full worker payload serialization", async () => {
const workerMessages: WorkerMessage[] = [];
FakeWorker.behavior = (worker, message) => {
workerMessages.push(message);
if (message.kind !== "full") {
return;
}
queueMicrotask(() => {
worker.onmessage?.({
data: {
kind: "full-result",
requestId: message.requestId,
payload: createFullResult(),
},
} as MessageEvent);
});
};
vi.stubGlobal("Worker", FakeWorker as unknown as typeof Worker);
const { renderFullWithWorkerFallback } = await import("@/lib/image-pipeline/worker-client");
await renderFullWithWorkerFallback({
sourceUrl: "https://cdn.example.com/source.png",
steps: [],
render: {
resolution: "original",
format: "png",
},
signal: new AbortController().signal,
});
const fullMessage = workerMessages.find((message) => message.kind === "full") as
| (WorkerMessage & {
payload?: Record<string, unknown>;
})
| undefined;
expect(fullMessage).toBeDefined();
expect(fullMessage?.payload).not.toHaveProperty("signal");
});
it("still falls back to the main thread when the Worker API is unavailable", async () => {
vi.stubGlobal("Worker", undefined);