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 000000000..e40bd77d7 Binary files /dev/null and b/test/pdfs/print_protection.pdf differ diff --git a/web/app.js b/web/app.js index 8dd61389b..f48643138 100644 --- a/web/app.js +++ b/web/app.js @@ -190,6 +190,7 @@ const PDFViewerApplication = { _caretBrowsing: null, _isScrolling: false, editorUndoBar: null, + _printPermissionPromise: null, // Called once when the document is loaded. async initialize(appConfig) { @@ -369,6 +370,7 @@ const PDFViewerApplication = { enableAutoLinking: x => 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;