From 94de952b65c4efae90a8e627872f20e7fc1b5848 Mon Sep 17 00:00:00 2001 From: Calixte Denizet Date: Tue, 12 May 2026 12:14:30 +0200 Subject: [PATCH 1/5] Add a Content-Security-Policy to pdf.js' viewer.html (bug 1960363) --- gulpfile.mjs | 4 + test/integration/viewer_spec.mjs | 106 ++++++++++++++++++++++++ web/viewer-geckoview.html | 14 ++++ web/viewer-snippet-chrome-overlays.html | 14 +--- web/viewer.css | 19 +++++ web/viewer.html | 31 ++++++- 6 files changed, 174 insertions(+), 14 deletions(-) diff --git a/gulpfile.mjs b/gulpfile.mjs index 1b948115258fd..cebe49e5c2783 100644 --- a/gulpfile.mjs +++ b/gulpfile.mjs @@ -1209,6 +1209,10 @@ function discardCommentsCSS() { } function preprocessHTML(source, defines) { + defines = { + ...defines, + TESTING: defines.TESTING ?? process.env.TESTING === "true", + }; const outName = getTempFile("~preprocess", ".html"); preprocess(source, outName, defines); const out = fs.readFileSync(outName).toString(); diff --git a/test/integration/viewer_spec.mjs b/test/integration/viewer_spec.mjs index deb059ef139f4..1309029a99c83 100644 --- a/test/integration/viewer_spec.mjs +++ b/test/integration/viewer_spec.mjs @@ -26,8 +26,11 @@ import { waitForPageChanging, waitForPageRendered, } from "./test_utils.mjs"; +import path from "path"; import { PNG } from "pngjs"; +const __dirname = import.meta.dirname; + describe("PDF viewer", () => { describe("Zoom origin", () => { let pages; @@ -1892,5 +1895,108 @@ describe("PDF viewer", () => { ); }); }); + + describe("@page size stylesheet under CSP", () => { + let pages; + + beforeEach(async () => { + pages = await loadAndWait( + "basicapi.pdf", + ".textLayer .endOfContent", + null, + { + earlySetup: () => { + // Capture state while window.print() runs — the print service's + // destroy() removes the @page stylesheet right after, on the + // afterprint event. + window._pageRuleApplied = null; + window.print = () => { + window._pageRuleApplied = [ + ...document.querySelectorAll("style"), + ].some( + s => + s.sheet?.cssRules.length > 0 && + [...s.sheet.cssRules].some(r => r.cssText.includes("@page")) + ); + }; + }, + appSetup: app => { + app._testPrintResolver = Promise.withResolvers(); + }, + eventBusSetup: eventBus => { + eventBus.on( + "afterprint", + () => { + window.PDFViewerApplication._testPrintResolver.resolve(); + }, + { once: true } + ); + }, + } + ); + }); + + afterEach(async () => { + await closePages(pages); + }); + + // The print service injects an inline + // to match the PDF's page + // dimensions. If the CSP `style-src-elem` directive blocks inline + // + at print time (web/pdf_print_service.js, web/firefox_print_service.js) + to match the PDF's page dimensions. Since the size varies per PDF the + content can't be pre-hashed, so style-src-elem allows 'unsafe-inline'. + Inline style="…" attributes stay blocked via style-src (no fallback). + --> + + + + diff --git a/web/viewer-snippet-chrome-overlays.html b/web/viewer-snippet-chrome-overlays.html index fff0eb2f32786..357b8510786b0 100644 --- a/web/viewer-snippet-chrome-overlays.html +++ b/web/viewer-snippet-chrome-overlays.html @@ -4,19 +4,7 @@ users with recognizing which checkbox they have to click when they visit chrome://extensions. --> -

+

Click on "Allow access to file URLs" at chrome://extensions
diff --git a/web/viewer.css b/web/viewer.css index 832439af42344..4defccd10f6d5 100644 --- a/web/viewer.css +++ b/web/viewer.css @@ -713,6 +713,25 @@ dialog :link { margin-top: 10px; } +/*#if !MOZCENTRAL*/ +#printServiceDialog { + min-width: 200px; +} +/*#endif*/ + +/*#if CHROME*/ +#chrome-pdfjs-logo-bg { + display: block; + padding-left: 60px; + min-height: 48px; + background-size: 48px; + background-repeat: no-repeat; + font-size: 14px; + line-height: 1.8em; + word-break: break-all; +} +/*#endif*/ + .grab-to-pan-grab { cursor: grab !important; } diff --git a/web/viewer.html b/web/viewer.html index 0d5d90c7d61ca..e4d24d7e4e45f 100644 --- a/web/viewer.html +++ b/web/viewer.html @@ -29,6 +29,35 @@ PDF.js viewer + + + + + + + + + + + @@ -1237,7 +1266,7 @@ -

+
From d1da73931abca676fa6d62231365757dc0aad20a Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Mon, 18 May 2026 11:23:08 +0200 Subject: [PATCH 2/5] Move the `Util.scaleMinMax` helper into `src/display/canvas_dependency_tracker.js` This method is completely unused in the worker-thread, and it only has a single call-site in the main-thread. By moving this helper into the `src/display/canvas_dependency_tracker.js` file, the size of the `gulp mozcentral` bundle is reduced by `1220` bytes. --- src/display/canvas_dependency_tracker.js | 52 +++++++++++++++++++++++- src/shared/util.js | 51 ----------------------- 2 files changed, 51 insertions(+), 52 deletions(-) diff --git a/src/display/canvas_dependency_tracker.js b/src/display/canvas_dependency_tracker.js index 11b6a564f6505..d448fa8c80f93 100644 --- a/src/display/canvas_dependency_tracker.js +++ b/src/display/canvas_dependency_tracker.js @@ -28,6 +28,56 @@ function expandBBox(array, index, minX, minY, maxX, maxY) { array[index * 4 + 3] = Math.max(array[index * 4 + 3], maxY); } +// Apply a scaling matrix to some min/max values. +// If a scaling factor is negative then min and max must be swapped. +function scaleMinMax(transform, minMax) { + let temp; + if (transform[0]) { + if (transform[0] < 0) { + temp = minMax[0]; + minMax[0] = minMax[2]; + minMax[2] = temp; + } + minMax[0] *= transform[0]; + minMax[2] *= transform[0]; + + if (transform[3] < 0) { + temp = minMax[1]; + minMax[1] = minMax[3]; + minMax[3] = temp; + } + minMax[1] *= transform[3]; + minMax[3] *= transform[3]; + } else { + temp = minMax[0]; + minMax[0] = minMax[1]; + minMax[1] = temp; + temp = minMax[2]; + minMax[2] = minMax[3]; + minMax[3] = temp; + + if (transform[1] < 0) { + temp = minMax[1]; + minMax[1] = minMax[3]; + minMax[3] = temp; + } + minMax[1] *= transform[1]; + minMax[3] *= transform[1]; + + if (transform[2] < 0) { + temp = minMax[0]; + minMax[0] = minMax[2]; + minMax[2] = temp; + } + minMax[0] *= transform[2]; + minMax[2] *= transform[2]; + } + minMax[0] += transform[4]; + minMax[1] += transform[5]; + minMax[2] += transform[4]; + minMax[3] += transform[5]; +} + // This is computed rathter than hard-coded to keep into // account the platform's endianess. const EMPTY_BBOX = new Uint32Array(new Uint8Array([255, 255, 0, 0]).buffer)[0]; @@ -613,7 +663,7 @@ class CanvasDependencyTracker { computedBBox = [0, 0, 0, 0]; Util.axialAlignedBoundingBox(fontBBox, font.fontMatrix, computedBBox); if (scale !== 1 || x !== 0 || y !== 0) { - Util.scaleMinMax([scale, 0, 0, -scale, x, y], computedBBox); + scaleMinMax([scale, 0, 0, -scale, x, y], computedBBox); } if (isBBoxTrustworthy) { diff --git a/src/shared/util.js b/src/shared/util.js index 88fda0439ffa8..6c36f758a4665 100644 --- a/src/shared/util.js +++ b/src/shared/util.js @@ -711,57 +711,6 @@ class Util { return `#${this.hexNums[r]}${this.hexNums[g]}${this.hexNums[b]}`; } - // Apply a scaling matrix to some min/max values. - // If a scaling factor is negative then min and max must be - // swapped. - static scaleMinMax(transform, minMax) { - let temp; - if (transform[0]) { - if (transform[0] < 0) { - temp = minMax[0]; - minMax[0] = minMax[2]; - minMax[2] = temp; - } - minMax[0] *= transform[0]; - minMax[2] *= transform[0]; - - if (transform[3] < 0) { - temp = minMax[1]; - minMax[1] = minMax[3]; - minMax[3] = temp; - } - minMax[1] *= transform[3]; - minMax[3] *= transform[3]; - } else { - temp = minMax[0]; - minMax[0] = minMax[1]; - minMax[1] = temp; - temp = minMax[2]; - minMax[2] = minMax[3]; - minMax[3] = temp; - - if (transform[1] < 0) { - temp = minMax[1]; - minMax[1] = minMax[3]; - minMax[3] = temp; - } - minMax[1] *= transform[1]; - minMax[3] *= transform[1]; - - if (transform[2] < 0) { - temp = minMax[0]; - minMax[0] = minMax[2]; - minMax[2] = temp; - } - minMax[0] *= transform[2]; - minMax[2] *= transform[2]; - } - minMax[0] += transform[4]; - minMax[1] += transform[5]; - minMax[2] += transform[4]; - minMax[3] += transform[5]; - } - // Concatenates two transformation matrices together and returns the result. static transform(m1, m2) { return [ From 1e9e8fad7eb760dc1d15664d195aca591aa90188 Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Mon, 18 May 2026 13:31:26 +0200 Subject: [PATCH 3/5] Add helper functions for opening/closing the document properties dialog during testing This reduces duplication in the various integration-tests. --- test/integration/document_properties_spec.mjs | 107 +++++++----------- 1 file changed, 43 insertions(+), 64 deletions(-) diff --git a/test/integration/document_properties_spec.mjs b/test/integration/document_properties_spec.mjs index cb3d08db6cf68..dcb9bc6c1536b 100644 --- a/test/integration/document_properties_spec.mjs +++ b/test/integration/document_properties_spec.mjs @@ -33,21 +33,42 @@ const FIELDS = [ "linearized", ]; -describe("PDFDocumentProperties", () => { - async function getFieldProperties(page) { - const promises = []; - - for (const name of FIELDS) { - promises.push( - page.evaluate( - n => [n, document.getElementById(`${n}Field`).textContent], - name - ) - ); - } - return Object.fromEntries(await Promise.all(promises)); +async function openDocumentProperties(page) { + await page.click("#secondaryToolbarToggleButton"); + await page.waitForSelector("#secondaryToolbar", { hidden: false }); + + await page.click("#documentProperties"); + await page.waitForSelector("#documentPropertiesDialog", { + hidden: false, + }); +} + +async function closeDocumentProperties(page) { + await page.click("#documentPropertiesClose"); + await page.waitForSelector("#documentPropertiesDialog", { + hidden: true, + }); +} + +async function checkFieldProperties(page, expectedProps) { + await page.waitForFunction( + `document.getElementById("fileSizeField").textContent !== "-"` + ); + const promises = []; + + for (const name of FIELDS) { + promises.push( + page.evaluate( + n => [n, document.getElementById(`${n}Field`).textContent], + name + ) + ); } + const props = Object.fromEntries(await Promise.all(promises)); + expect(props).toEqual(expectedProps); +} +describe("PDFDocumentProperties", () => { describe("Document with both /Info and /Metadata", () => { let pages; @@ -62,20 +83,9 @@ describe("PDFDocumentProperties", () => { it("must check that the document properties dialog has the correct information", async () => { await Promise.all( pages.map(async ([browserName, page]) => { - await page.click("#secondaryToolbarToggleButton"); - await page.waitForSelector("#secondaryToolbar", { hidden: false }); - - await page.click("#documentProperties"); - await page.waitForSelector("#documentPropertiesDialog", { - hidden: false, - }); - - await page.waitForFunction( - `document.getElementById("fileSizeField").textContent !== "-"` - ); - const props = await getFieldProperties(page); + await openDocumentProperties(page); - expect(props).toEqual({ + await checkFieldProperties(page, { fileName: "basicapi.pdf", fileSize: `${FSI}103${PDI} KB (${FSI}105,779${PDI} bytes)`, title: "Basic API Test", @@ -92,10 +102,7 @@ describe("PDFDocumentProperties", () => { linearized: "No", }); - await page.click("#documentPropertiesClose"); - await page.waitForSelector("#documentPropertiesDialog", { - hidden: true, - }); + await closeDocumentProperties(page); }) ); }); @@ -118,20 +125,9 @@ describe("PDFDocumentProperties", () => { it("must check that the document properties dialog has the correct information", async () => { await Promise.all( pages.map(async ([browserName, page]) => { - await page.click("#secondaryToolbarToggleButton"); - await page.waitForSelector("#secondaryToolbar", { hidden: false }); - - await page.click("#documentProperties"); - await page.waitForSelector("#documentPropertiesDialog", { - hidden: false, - }); + await openDocumentProperties(page); - await page.waitForFunction( - `document.getElementById("fileSizeField").textContent !== "-"` - ); - const props = await getFieldProperties(page); - - expect(props).toEqual({ + await checkFieldProperties(page, { fileName: "arial_unicode_en_cidfont.pdf", fileSize: `${FSI}15.4${PDI} KB (${FSI}15,779${PDI} bytes)`, title: "-", @@ -148,10 +144,7 @@ describe("PDFDocumentProperties", () => { linearized: "No", }); - await page.click("#documentPropertiesClose"); - await page.waitForSelector("#documentPropertiesDialog", { - hidden: true, - }); + await closeDocumentProperties(page); }) ); }); @@ -182,20 +175,9 @@ describe("PDFDocumentProperties", () => { }); }, base64); - await page.click("#secondaryToolbarToggleButton"); - await page.waitForSelector("#secondaryToolbar", { hidden: false }); + await openDocumentProperties(page); - await page.click("#documentProperties"); - await page.waitForSelector("#documentPropertiesDialog", { - hidden: false, - }); - - await page.waitForFunction( - `document.getElementById("fileSizeField").textContent !== "-"` - ); - const props = await getFieldProperties(page); - - expect(props).toEqual({ + await checkFieldProperties(page, { fileName: "document.pdf", fileSize: `${FSI}0.448${PDI} KB (${FSI}459${PDI} bytes)`, title: "-", @@ -212,10 +194,7 @@ describe("PDFDocumentProperties", () => { linearized: "No", }); - await page.click("#documentPropertiesClose"); - await page.waitForSelector("#documentPropertiesDialog", { - hidden: true, - }); + await closeDocumentProperties(page); }) ); }); From e05b6d6f59adcc87da2441af3d131198f53c31e8 Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Mon, 18 May 2026 13:33:33 +0200 Subject: [PATCH 4/5] Add more integration-tests, with multi-page documents, for the `PDFDocumentProperties` dialog Improve test coverage for multi-page documents, to ensure that: - Unnecessary re-parsing is avoided where possible. - Rotation, in the viewer, is handled correctly. - Different page sizes are handled correctly. --- test/integration/document_properties_spec.mjs | 155 ++++++++++++++++++ web/pdf_document_properties.js | 6 + 2 files changed, 161 insertions(+) diff --git a/test/integration/document_properties_spec.mjs b/test/integration/document_properties_spec.mjs index dcb9bc6c1536b..6afb20f1a084a 100644 --- a/test/integration/document_properties_spec.mjs +++ b/test/integration/document_properties_spec.mjs @@ -68,6 +68,14 @@ async function checkFieldProperties(page, expectedProps) { expect(props).toEqual(expectedProps); } +function getFieldDataLastUpdated(page) { + return page.evaluate( + () => + document.getElementById("documentPropertiesDialog").dataset + .fieldDataLastUpdated + ); +} + describe("PDFDocumentProperties", () => { describe("Document with both /Info and /Metadata", () => { let pages; @@ -199,4 +207,151 @@ describe("PDFDocumentProperties", () => { ); }); }); + + describe("Document with multiple pages, and changed viewer page/rotation", () => { + let pages; + + beforeEach(async () => { + pages = await loadAndWait("basicapi.pdf", ".textLayer .endOfContent"); + }); + + afterEach(async () => { + await closePages(pages); + }); + + it("must check that the document properties dialog has the correct information", async () => { + await Promise.all( + pages.map(async ([browserName, page]) => { + await openDocumentProperties(page); + + await checkFieldProperties(page, { + fileName: "basicapi.pdf", + fileSize: `${FSI}103${PDI} KB (${FSI}105,779${PDI} bytes)`, + title: "Basic API Test", + author: "Brendan Dahl", + subject: "-", + keywords: "TCPDF", + creationDate: "4/10/12, 7:30:26 AM", + modificationDate: "4/10/12, 7:30:26 AM", + creator: "TCPDF", + producer: "TCPDF 5.9.133 (http://www.tcpdf.org)", + version: "1.7", + pageCount: "3", + pageSize: `${FSI}8.27${PDI} × ${FSI}11.69${PDI} ${FSI}in${PDI} (${FSI}A4${PDI}, ${FSI}portrait${PDI})`, + linearized: "No", + }); + const fieldDataLastUpdated = await getFieldDataLastUpdated(page); + + await closeDocumentProperties(page); + + // Ensure that immediately re-opening the dialog doesn't cause + // the field-data to be fetched and parsed again. + await openDocumentProperties(page); + + expect(await getFieldDataLastUpdated(page)).toEqual( + fieldDataLastUpdated + ); + + await closeDocumentProperties(page); + + // Goto the second page, and rotate the document. + await page.click("#next"); + await page.waitForFunction( + () => window.PDFViewerApplication.page === 2 + ); + await page.keyboard.press("r"); + await page.waitForFunction( + () => window.PDFViewerApplication.pdfViewer.pagesRotation === 90 + ); + + await openDocumentProperties(page); + + await checkFieldProperties(page, { + fileName: "basicapi.pdf", + fileSize: `${FSI}103${PDI} KB (${FSI}105,779${PDI} bytes)`, + title: "Basic API Test", + author: "Brendan Dahl", + subject: "-", + keywords: "TCPDF", + creationDate: "4/10/12, 7:30:26 AM", + modificationDate: "4/10/12, 7:30:26 AM", + creator: "TCPDF", + producer: "TCPDF 5.9.133 (http://www.tcpdf.org)", + version: "1.7", + pageCount: "3", + pageSize: `${FSI}11.69${PDI} × ${FSI}8.27${PDI} ${FSI}in${PDI} (${FSI}A4${PDI}, ${FSI}landscape${PDI})`, + linearized: "No", + }); + + await closeDocumentProperties(page); + }) + ); + }); + }); + + describe("Document with different page sizes", () => { + let pages; + + beforeEach(async () => { + pages = await loadAndWait("sizes.pdf", ".textLayer .endOfContent"); + }); + + afterEach(async () => { + await closePages(pages); + }); + + it("must check that the document properties dialog has the correct information", async () => { + await Promise.all( + pages.map(async ([browserName, page]) => { + await openDocumentProperties(page); + + await checkFieldProperties(page, { + fileName: "sizes.pdf", + fileSize: `${FSI}13.4${PDI} KB (${FSI}13,739${PDI} bytes)`, + title: "-", + author: "Yury ", + subject: "-", + keywords: "-", + creationDate: "6/26/11, 1:26:03 PM", + modificationDate: "-", + creator: "Writer", + producer: "OpenOffice.org 3.3", + version: "1.4", + pageCount: "3", + pageSize: `${FSI}8.5${PDI} × ${FSI}11${PDI} ${FSI}in${PDI} (${FSI}Letter${PDI}, ${FSI}portrait${PDI})`, + linearized: "No", + }); + + await closeDocumentProperties(page); + + // Goto the second page. + await page.click("#next"); + await page.waitForFunction( + () => window.PDFViewerApplication.page === 2 + ); + + await openDocumentProperties(page); + + await checkFieldProperties(page, { + fileName: "sizes.pdf", + fileSize: `${FSI}13.4${PDI} KB (${FSI}13,739${PDI} bytes)`, + title: "-", + author: "Yury ", + subject: "-", + keywords: "-", + creationDate: "6/26/11, 1:26:03 PM", + modificationDate: "-", + creator: "Writer", + producer: "OpenOffice.org 3.3", + version: "1.4", + pageCount: "3", + pageSize: `${FSI}9.01${PDI} × ${FSI}4.49${PDI} ${FSI}in${PDI} (${FSI}landscape${PDI})`, + linearized: "No", + }); + + await closeDocumentProperties(page); + }) + ); + }); + }); }); diff --git a/web/pdf_document_properties.js b/web/pdf_document_properties.js index 98de92bf71548..f7150b72c902c 100644 --- a/web/pdf_document_properties.js +++ b/web/pdf_document_properties.js @@ -111,6 +111,9 @@ class PDFDocumentProperties { this.#updateUI(); return; } + if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) { + this._fieldDataLastUpdated = Date.now(); + } // Get the document properties. const [ @@ -220,6 +223,9 @@ class PDFDocumentProperties { // since it will be updated the next time `this.open` is called. return; } + if (typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING")) { + this.dialog.dataset.fieldDataLastUpdated = this._fieldDataLastUpdated; + } for (const id in this.fields) { const content = this.#fieldData?.[id]; this.fields[id].textContent = content || content === 0 ? content : "-"; From 1e62f01773da4755535c466e6c2eefa4e2172098 Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Mon, 18 May 2026 17:26:39 +0200 Subject: [PATCH 5/5] Improve handling of corrupt pages in the `PDFDocumentProperties` dialog If the active page is corrupt that currently results in the entire dialog being "blank", thus providing no information, which seems unfortunate and it's easy enough to only skip `pageSizeField` in that rare case. --- test/integration/document_properties_spec.mjs | 82 +++++++++++++++++-- web/pdf_document_properties.js | 15 +++- 2 files changed, 88 insertions(+), 9 deletions(-) diff --git a/test/integration/document_properties_spec.mjs b/test/integration/document_properties_spec.mjs index 6afb20f1a084a..e4192dd640c9b 100644 --- a/test/integration/document_properties_spec.mjs +++ b/test/integration/document_properties_spec.mjs @@ -88,7 +88,7 @@ describe("PDFDocumentProperties", () => { await closePages(pages); }); - it("must check that the document properties dialog has the correct information", async () => { + it("check that the document properties dialog has the correct information", async () => { await Promise.all( pages.map(async ([browserName, page]) => { await openDocumentProperties(page); @@ -130,7 +130,7 @@ describe("PDFDocumentProperties", () => { await closePages(pages); }); - it("must check that the document properties dialog has the correct information", async () => { + it("check that the document properties dialog has the correct information", async () => { await Promise.all( pages.map(async ([browserName, page]) => { await openDocumentProperties(page); @@ -169,7 +169,7 @@ describe("PDFDocumentProperties", () => { await closePages(pages); }); - it("must check that the document properties dialog has the correct information", async () => { + it("check that the document properties dialog has the correct information", async () => { await Promise.all( pages.map(async ([browserName, page]) => { // Open a binary PDF document, such that `contentLength` is undefined. @@ -219,7 +219,7 @@ describe("PDFDocumentProperties", () => { await closePages(pages); }); - it("must check that the document properties dialog has the correct information", async () => { + it("check that the document properties dialog has the correct information", async () => { await Promise.all( pages.map(async ([browserName, page]) => { await openDocumentProperties(page); @@ -300,7 +300,7 @@ describe("PDFDocumentProperties", () => { await closePages(pages); }); - it("must check that the document properties dialog has the correct information", async () => { + it("check that the document properties dialog has the correct information", async () => { await Promise.all( pages.map(async ([browserName, page]) => { await openDocumentProperties(page); @@ -354,4 +354,76 @@ describe("PDFDocumentProperties", () => { ); }); }); + + describe("Document with corrupt page", () => { + let pages; + + beforeEach(async () => { + pages = await loadAndWait( + "Pages-tree-refs.pdf", + ".textLayer .endOfContent", + null, + null, + { page: 2 } + ); + }); + + afterEach(async () => { + await closePages(pages); + }); + + it("check that the document properties dialog has the correct information", async () => { + await Promise.all( + pages.map(async ([browserName, page]) => { + await openDocumentProperties(page); + + await checkFieldProperties(page, { + fileName: "Pages-tree-refs.pdf", + fileSize: `${FSI}1.07${PDI} KB (${FSI}1,098${PDI} bytes)`, + title: "-", + author: "-", + subject: "-", + keywords: "-", + creationDate: "-", + modificationDate: "-", + creator: "-", + producer: "-", + version: "1.7", + pageCount: "2", + pageSize: "-", + linearized: "No", + }); + + await closeDocumentProperties(page); + + // Goto the first page (which is *not* corrupt). + await page.click("#previous"); + await page.waitForFunction( + () => window.PDFViewerApplication.page === 1 + ); + + await openDocumentProperties(page); + + await checkFieldProperties(page, { + fileName: "Pages-tree-refs.pdf", + fileSize: `${FSI}1.07${PDI} KB (${FSI}1,098${PDI} bytes)`, + title: "-", + author: "-", + subject: "-", + keywords: "-", + creationDate: "-", + modificationDate: "-", + creator: "-", + producer: "-", + version: "1.7", + pageCount: "2", + pageSize: `${FSI}8.27${PDI} × ${FSI}11.69${PDI} ${FSI}in${PDI} (${FSI}A4${PDI}, ${FSI}portrait${PDI})`, + linearized: "No", + }); + + await closeDocumentProperties(page); + }) + ); + }); + }); }); diff --git a/web/pdf_document_properties.js b/web/pdf_document_properties.js index f7150b72c902c..98327e8bf625b 100644 --- a/web/pdf_document_properties.js +++ b/web/pdf_document_properties.js @@ -121,7 +121,13 @@ class PDFDocumentProperties { pdfPage, ] = await Promise.all([ this.pdfDocument.getMetadata(), - this.pdfDocument.getPage(currentPageNumber), + this.pdfDocument.getPage(currentPageNumber).catch(reason => { + console.error( + `PDFDocumentProperties - unable to get page ${currentPageNumber}.`, + reason + ); + return null; + }), ]); const [ @@ -138,7 +144,7 @@ class PDFDocumentProperties { this._titleLookup(), this.#parseDate(metadata?.get("xmp:createdate"), info.CreationDate), this.#parseDate(metadata?.get("xmp:modifydate"), info.ModDate), - this.#parsePageSize(getPageSizeInches(pdfPage), pagesRotation), + this.#parsePageSize(pdfPage, pagesRotation), this.#parseLinearization(info.IsLinearized), ]); @@ -245,10 +251,11 @@ class PDFDocumentProperties { : undefined; } - async #parsePageSize(pageSizeInches, pagesRotation) { - if (!pageSizeInches) { + async #parsePageSize(pdfPage, pagesRotation) { + if (!pdfPage) { return undefined; } + let pageSizeInches = getPageSizeInches(pdfPage); // Take the viewer rotation into account as well; compare with Adobe Reader. if (pagesRotation % 180 !== 0) { pageSizeInches = {