fix(canvas): prevent AbortSignal cloning in render worker
This commit is contained in:
@@ -21,7 +21,7 @@ type PreviewWorkerPayload = {
|
||||
featureFlags?: BackendFeatureFlags;
|
||||
};
|
||||
|
||||
type FullWorkerPayload = RenderFullOptions & {
|
||||
type FullWorkerPayload = Omit<RenderFullOptions, "signal"> & {
|
||||
featureFlags?: BackendFeatureFlags;
|
||||
};
|
||||
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user