diff --git a/test/integration/document_properties_spec.mjs b/test/integration/document_properties_spec.mjs index cb3d08db6..e4192dd64 100644 --- a/test/integration/document_properties_spec.mjs +++ b/test/integration/document_properties_spec.mjs @@ -33,21 +33,50 @@ const FIELDS = [ "linearized", ]; -describe("PDFDocumentProperties", () => { - async function getFieldProperties(page) { - const promises = []; +async function openDocumentProperties(page) { + await page.click("#secondaryToolbarToggleButton"); + await page.waitForSelector("#secondaryToolbar", { hidden: false }); - for (const name of FIELDS) { - promises.push( - page.evaluate( - n => [n, document.getElementById(`${n}Field`).textContent], - name - ) - ); - } - return Object.fromEntries(await Promise.all(promises)); + 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); +} +function getFieldDataLastUpdated(page) { + return page.evaluate( + () => + document.getElementById("documentPropertiesDialog").dataset + .fieldDataLastUpdated + ); +} + +describe("PDFDocumentProperties", () => { describe("Document with both /Info and /Metadata", () => { let pages; @@ -59,23 +88,12 @@ 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 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: "basicapi.pdf", fileSize: `${FSI}103${PDI} KB (${FSI}105,779${PDI} bytes)`, title: "Basic API Test", @@ -92,10 +110,7 @@ describe("PDFDocumentProperties", () => { linearized: "No", }); - await page.click("#documentPropertiesClose"); - await page.waitForSelector("#documentPropertiesDialog", { - hidden: true, - }); + await closeDocumentProperties(page); }) ); }); @@ -115,23 +130,12 @@ 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 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: "arial_unicode_en_cidfont.pdf", fileSize: `${FSI}15.4${PDI} KB (${FSI}15,779${PDI} bytes)`, title: "-", @@ -148,10 +152,7 @@ describe("PDFDocumentProperties", () => { linearized: "No", }); - await page.click("#documentPropertiesClose"); - await page.waitForSelector("#documentPropertiesDialog", { - hidden: true, - }); + await closeDocumentProperties(page); }) ); }); @@ -168,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. @@ -182,20 +183,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 +202,226 @@ describe("PDFDocumentProperties", () => { linearized: "No", }); - await page.click("#documentPropertiesClose"); - await page.waitForSelector("#documentPropertiesDialog", { - hidden: true, + await closeDocumentProperties(page); + }) + ); + }); + }); + + 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("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("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); + }) + ); + }); + }); + + 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 98de92bf7..98327e8bf 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 [ @@ -118,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 [ @@ -135,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), ]); @@ -220,6 +229,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 : "-"; @@ -239,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 = {