books reader: fix the error when leaving a PDF (back button)
Some checks failed
Deploy production / deploy (push) Has been cancelled
Some checks failed
Deploy production / deploy (push) Has been cancelled
pdf.js 6 removed PDFDocumentProxy.destroy(), so the PdfReader teardown threw "destroy is not a function" on every unmount, breaking the client-side navigation back to the book page (and sometimes showing the error page). Close the document through its loading task instead (closePdf), which also cancels a load still in flight. Test covers the pdf.js API and closePdf.
This commit is contained in:
@@ -14,6 +14,7 @@
|
|||||||
type Rect
|
type Rect
|
||||||
} from '#lib/books/shared.js';
|
} from '#lib/books/shared.js';
|
||||||
import { clampZoom, pdfRows, rowIndexOf, type Spread } from './layout.js';
|
import { clampZoom, pdfRows, rowIndexOf, type Spread } from './layout.js';
|
||||||
|
import { closePdf } from './pdf.js';
|
||||||
import { type EngineProps, type TocItem } from './types.js';
|
import { type EngineProps, type TocItem } from './types.js';
|
||||||
|
|
||||||
let {
|
let {
|
||||||
@@ -370,6 +371,7 @@
|
|||||||
|
|
||||||
onMount(() => {
|
onMount(() => {
|
||||||
let disposed = false;
|
let disposed = false;
|
||||||
|
let loadingTask: ReturnType<typeof import('pdfjs-dist').getDocument> | undefined;
|
||||||
const measure = () => {
|
const measure = () => {
|
||||||
width = scroller.clientWidth;
|
width = scroller.clientWidth;
|
||||||
height = scroller.clientHeight;
|
height = scroller.clientHeight;
|
||||||
@@ -450,15 +452,15 @@
|
|||||||
pdfjs = await import('pdfjs-dist');
|
pdfjs = await import('pdfjs-dist');
|
||||||
pdfjs.GlobalWorkerOptions.workerSrc = workerUrl;
|
pdfjs.GlobalWorkerOptions.workerSrc = workerUrl;
|
||||||
onloading(0);
|
onloading(0);
|
||||||
const task = pdfjs.getDocument({
|
const task = (loadingTask = pdfjs.getDocument({
|
||||||
url,
|
url,
|
||||||
withCredentials: true,
|
withCredentials: true,
|
||||||
rangeChunkSize: 1 << 20
|
rangeChunkSize: 1 << 20
|
||||||
});
|
}));
|
||||||
task.onProgress = ({ loaded, total }: { loaded: number; total: number }) =>
|
task.onProgress = ({ loaded, total }: { loaded: number; total: number }) =>
|
||||||
onloading(total ? Math.min(1, loaded / total) : null);
|
onloading(total ? Math.min(1, loaded / total) : null);
|
||||||
const doc = await task.promise;
|
const doc = await task.promise;
|
||||||
if (disposed) return void doc.destroy();
|
if (disposed) return void closePdf(task);
|
||||||
pdf = doc;
|
pdf = doc;
|
||||||
onloading(null);
|
onloading(null);
|
||||||
// Use the first page's size for every page up front; real sizes fill in quickly.
|
// Use the first page's size for every page up front; real sizes fill in quickly.
|
||||||
@@ -499,7 +501,8 @@
|
|||||||
resize.disconnect();
|
resize.disconnect();
|
||||||
observer?.disconnect();
|
observer?.disconnect();
|
||||||
for (const num of [...live.keys()]) unrender(num);
|
for (const num of [...live.keys()]) unrender(num);
|
||||||
void pdf?.destroy();
|
// pdf.js 6 has no PDFDocumentProxy.destroy(); calling it threw on every unmount.
|
||||||
|
void closePdf(loadingTask);
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
39
src/lib/books/reader/pdf.test.ts
Normal file
39
src/lib/books/reader/pdf.test.ts
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
import { describe, expect, it } from 'vitest';
|
||||||
|
import { closePdf } from './pdf.js';
|
||||||
|
|
||||||
|
// A tiny one-page PDF (pdf.js tolerates the missing xref table).
|
||||||
|
const PDF =
|
||||||
|
'%PDF-1.4\n1 0 obj<</Type/Catalog/Pages 2 0 R>>endobj\n' +
|
||||||
|
'2 0 obj<</Type/Pages/Kids[3 0 R]/Count 1>>endobj\n' +
|
||||||
|
'3 0 obj<</Type/Page/Parent 2 0 R/MediaBox[0 0 200 300]>>endobj\ntrailer<</Root 1 0 R>>\n%%EOF';
|
||||||
|
|
||||||
|
const open = async () => {
|
||||||
|
const pdfjs = await import('pdfjs-dist/legacy/build/pdf.mjs');
|
||||||
|
return pdfjs.getDocument({ data: new TextEncoder().encode(PDF) });
|
||||||
|
};
|
||||||
|
|
||||||
|
describe('closePdf', () => {
|
||||||
|
it('the document itself has no destroy() in pdf.js 6 (the old teardown threw)', async () => {
|
||||||
|
const task = await open();
|
||||||
|
const doc = await task.promise;
|
||||||
|
expect(doc.numPages).toBe(1);
|
||||||
|
expect((doc as unknown as { destroy?: unknown }).destroy).toBeUndefined();
|
||||||
|
await closePdf(task);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('destroys a loaded document via its loading task, idempotently', async () => {
|
||||||
|
const task = await open();
|
||||||
|
await task.promise;
|
||||||
|
await closePdf(task);
|
||||||
|
expect(task.destroyed).toBe(true);
|
||||||
|
await expect(closePdf(task)).resolves.toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can close while still loading, or with nothing open', async () => {
|
||||||
|
const task = await open();
|
||||||
|
task.promise.catch(() => {}); // rejected by the destroy
|
||||||
|
await closePdf(task);
|
||||||
|
expect(task.destroyed).toBe(true);
|
||||||
|
await expect(closePdf(undefined)).resolves.toBeUndefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
18
src/lib/books/reader/pdf.ts
Normal file
18
src/lib/books/reader/pdf.ts
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
// pdf.js lifecycle helpers for the reader.
|
||||||
|
|
||||||
|
type LoadingTask = { destroyed: boolean; destroy(): Promise<void> };
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Closes a PDF opened with pdfjs.getDocument(). pdf.js 6 removed PDFDocumentProxy.destroy();
|
||||||
|
* the loading task owns the document, its network requests and the worker, so tearing it
|
||||||
|
* down works both mid-load and after. Never throws: it runs while the reader unmounts
|
||||||
|
* (e.g. the back button), where an exception would break the navigation.
|
||||||
|
*/
|
||||||
|
export async function closePdf(task: LoadingTask | null | undefined): Promise<void> {
|
||||||
|
if (!task || task.destroyed) return;
|
||||||
|
try {
|
||||||
|
await task.destroy();
|
||||||
|
} catch {
|
||||||
|
// already torn down
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user