From 806f9c1cee624f9257bbd4e62442a144be767368 Mon Sep 17 00:00:00 2001 From: Calixte Denizet Date: Mon, 28 Jul 2025 17:42:13 +0200 Subject: [PATCH] Disable printing when enablePermission is true and the pdf isn't allowed to be printed (bug 1978985) --- test/integration/viewer_spec.mjs | 53 +++++++++++++++++++++++++++++++ test/pdfs/.gitignore | 1 + test/pdfs/print_protection.pdf | Bin 0 -> 10832 bytes web/app.js | 42 +++++++++++++++++++++--- web/pdf_print_service.js | 4 +++ web/pdf_viewer.js | 22 +++++++++++++ 6 files changed, 117 insertions(+), 5 deletions(-) create mode 100755 test/pdfs/print_protection.pdf diff --git a/test/integration/viewer_spec.mjs b/test/integration/viewer_spec.mjs index 3d7f87192..e0c6937bb 100644 --- a/test/integration/viewer_spec.mjs +++ b/test/integration/viewer_spec.mjs @@ -1302,4 +1302,57 @@ describe("PDF viewer", () => { ); }); }); + + describe("Printing can be disallowed for some pdfs (bug 1978985)", () => { + let pages; + + beforeEach(async () => { + pages = await loadAndWait( + "print_protection.pdf", + "#passwordDialog", + null, + null, + { enablePermissions: true } + ); + }); + + afterEach(async () => { + await closePages(pages); + }); + + it("must check that printing is disallowed", async () => { + await Promise.all( + pages.map(async ([browserName, page]) => { + await page.waitForSelector("#printButton", { + visible: true, + }); + + const selector = "#passwordDialog input#password"; + await page.waitForSelector(selector, { visible: true }); + await page.type(selector, "1234"); + await page.click("#passwordDialog button#passwordSubmit"); + + await page.waitForSelector(".textLayer .endOfContent"); + + // The print button should be hidden. + await page.waitForSelector("#printButton", { + hidden: true, + }); + await page.waitForSelector("#secondaryPrint", { + hidden: true, + }); + + const hasThrown = await page.evaluate(() => { + try { + window.print(); + } catch { + return true; + } + return false; + }); + expect(hasThrown).withContext(`In ${browserName}`).toBeTrue(); + }) + ); + }); + }); }); diff --git a/test/pdfs/.gitignore b/test/pdfs/.gitignore index ff077a83b..233649a1f 100644 --- a/test/pdfs/.gitignore +++ b/test/pdfs/.gitignore @@ -738,3 +738,4 @@ !bug1978317.pdf !dates.pdf !dates_save.pdf +!print_protection.pdf diff --git a/test/pdfs/print_protection.pdf b/test/pdfs/print_protection.pdf new file mode 100755 index 0000000000000000000000000000000000000000..e40bd77d7a91beff93d2a867c7495339b0a7121b GIT binary patch literal 10832 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 x === "true", enableFakeMLManager: x => x === "true", enableGuessAltText: x => x === "true", + enablePermissions: x => x === "true", enableUpdatedAddImage: x => x === "true", highlightEditorColors: x => x, maxCanvasPixels: x => parseInt(x), @@ -407,6 +409,7 @@ const PDFViewerApplication = { ) : new EventBus(); this.eventBus = AppOptions.eventBus = eventBus; + mlManager?.setEventBus(eventBus, abortSignal); const overlayManager = (this.overlayManager = new OverlayManager()); @@ -798,9 +801,19 @@ const PDFViewerApplication = { }); } + const togglePrintingButtons = visible => { + appConfig.toolbar?.print?.classList.toggle("hidden", !visible); + appConfig.secondaryToolbar?.printButton.classList.toggle( + "hidden", + !visible + ); + }; if (!this.supportsPrinting) { - appConfig.toolbar?.print?.classList.add("hidden"); - appConfig.secondaryToolbar?.printButton.classList.add("hidden"); + togglePrintingButtons(false); + } else { + eventBus.on("printingallowed", ({ isAllowed }) => + togglePrintingButtons(isAllowed) + ); } if (!this.supportsFullscreen) { @@ -1335,6 +1348,25 @@ const PDFViewerApplication = { load(pdfDocument) { this.pdfDocument = pdfDocument; + this._printPermissionPromise = new Promise(resolve => { + this.eventBus.on( + "printingallowed", + ({ isAllowed }) => { + if ( + typeof PDFJSDev !== "undefined" && + PDFJSDev.test("MOZCENTRAL") && + !isAllowed + ) { + window.print = () => { + console.warn("Printing is not allowed."); + }; + } + resolve(isAllowed); + }, + { once: true } + ); + }); + pdfDocument.getDownloadInfo().then(({ length }) => { this._contentLength = length; // Ensure that the correct length is used. this.loadingBar?.hide(); @@ -1893,7 +1925,7 @@ const PDFViewerApplication = { return; } - if (!this.supportsPrinting) { + if (!this.supportsPrinting || !this.pdfViewer.printingAllowed) { this._otherError("pdfjs-printing-not-supported"); return; } @@ -1961,8 +1993,8 @@ const PDFViewerApplication = { this.pdfPresentationMode?.request(); }, - triggerPrinting() { - if (this.supportsPrinting) { + async triggerPrinting() { + if (this.supportsPrinting && (await this._printPermissionPromise)) { window.print(); } }, diff --git a/web/pdf_print_service.js b/web/pdf_print_service.js index b4cc80e01..c20f928a9 100644 --- a/web/pdf_print_service.js +++ b/web/pdf_print_service.js @@ -256,6 +256,10 @@ window.print = function () { dispatchEvent("beforeprint"); } finally { if (!activeService) { + if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("TESTING")) { + // eslint-disable-next-line no-unsafe-finally + throw new Error("window.print() is not supported"); + } console.error("Expected print service to be initialized."); ensureOverlay().then(function () { overlayManager.closeIfActive(dialog); diff --git a/web/pdf_viewer.js b/web/pdf_viewer.js index 896617cc3..99a4c49fc 100644 --- a/web/pdf_viewer.js +++ b/web/pdf_viewer.js @@ -254,6 +254,8 @@ class PDFViewer { #mlManager = null; + #printingAllowed = true; + #scrollTimeoutId = null; #switchAnnotationEditorModeAC = null; @@ -415,6 +417,10 @@ class PDFViewer { } } + get printingAllowed() { + return this.#printingAllowed; + } + get pagesCount() { return this._pages.length; } @@ -672,9 +678,23 @@ class PDFViewer { textLayerMode: this.#textLayerMode, }; if (!permissions) { + this.#printingAllowed = true; + this.eventBus.dispatch("printingallowed", { + source: this, + isAllowed: this.#printingAllowed, + }); + return params; } + this.#printingAllowed = + permissions.includes(PermissionFlag.PRINT_HIGH_QUALITY) || + permissions.includes(PermissionFlag.PRINT); + this.eventBus.dispatch("printingallowed", { + source: this, + isAllowed: this.#printingAllowed, + }); + if ( !permissions.includes(PermissionFlag.COPY) && this.#textLayerMode === TextLayerMode.ENABLE @@ -843,6 +863,8 @@ class PDFViewer { this.#annotationEditorUIManager?.destroy(); this.#annotationEditorUIManager = null; + + this.#printingAllowed = true; } this.pdfDocument = pdfDocument;