fix(image-pipeline): skip pre-aborted source bitmap loads

This commit is contained in:
Matthias
2026-04-04 11:32:59 +02:00
parent c41dde871f
commit 77f8736579
2 changed files with 32 additions and 0 deletions

View File

@@ -90,6 +90,8 @@ export async function loadSourceBitmap(
throw new Error("ImageBitmap is not available in this environment."); throw new Error("ImageBitmap is not available in this environment.");
} }
throwIfAborted(options.signal);
const promise = getOrCreateSourceBitmapPromise(sourceUrl); const promise = getOrCreateSourceBitmapPromise(sourceUrl);
return await awaitWithLocalAbort(promise, options.signal); return await awaitWithLocalAbort(promise, options.signal);
} }

View File

@@ -64,6 +64,36 @@ describe("loadSourceBitmap", () => {
expect(createImageBitmap).toHaveBeenCalledTimes(1); expect(createImageBitmap).toHaveBeenCalledTimes(1);
}); });
it("does not start fetch/decode work or cache when the signal is already aborted", async () => {
const response = {
ok: true,
status: 200,
blob: vi.fn().mockResolvedValue(blob),
};
vi.stubGlobal("fetch", vi.fn().mockResolvedValue(response));
const { loadSourceBitmap } = await importSubject();
const controller = new AbortController();
controller.abort();
await expect(
loadSourceBitmap("https://cdn.example.com/source.png", {
signal: controller.signal,
}),
).rejects.toMatchObject({ name: "AbortError" });
expect(fetch).not.toHaveBeenCalled();
expect(response.blob).not.toHaveBeenCalled();
expect(createImageBitmap).not.toHaveBeenCalled();
await expect(loadSourceBitmap("https://cdn.example.com/source.png")).resolves.toBe(bitmap);
expect(fetch).toHaveBeenCalledTimes(1);
expect(response.blob).toHaveBeenCalledTimes(1);
expect(createImageBitmap).toHaveBeenCalledTimes(1);
});
it("lets a later consumer succeed after an earlier caller aborts", async () => { it("lets a later consumer succeed after an earlier caller aborts", async () => {
const responseDeferred = createDeferred<{ const responseDeferred = createDeferred<{
ok: boolean; ok: boolean;