From fee1d4f56d4a72bde171e2e479361566a6aedcfe Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Mon, 20 Jun 2022 18:08:41 +0200 Subject: [PATCH] [editor] Support disabling of editing when `pdfjs.enablePermissions` is set (issue 15049) For encrypted PDF documents without the required permissions set, this patch adds support for disabling of Annotation-editing. However, please note that it also requires that the `pdfjs.enablePermissions` preference is set to `true` (since PDF document permissions could be seen as user hostile).[1] As I started looking at the issue, it soon became clear that *only* trying to fix the issue without slightly re-factor the surrounding code would be somewhat difficult. The following is an overview of the changes in this patch; sorry about the size/scope of this! - Use a new `AnnotationEditorUIManager`-instance *for each* PDF document opened in the GENERIC viewer, to prevent user-added Annotations from "leaking" from one document into the next. - Re-factor the `BaseViewer.#initializePermissions`-method, to simplify handling of temporarily disabled modes (e.g. for both Annotation-rendering and Annotation-editing). - When editing is enabled, let the Editor-buttons be `disabled` until the document has loaded. This way we avoid the buttons becoming clickable temporarily, for PDF documents that use permissions. - Slightly re-factor how the Editor-buttons are shown/hidden in the viewer, and reset the toolbar-state when a new PDF document is opened. - Flip the order of the Editor-buttons and the pre-exising toolbarButtons in the "toolbarViewerRight"-div. (To help reduce the size, a little bit, for the PR that adds new Editor-toolbars.) - Enable editing by default in the development viewer, i.e. `gulp server`, since having to (repeatedly) do that manually becomes annoying after a while. - Finally, support disabling of editing when `pdfjs.enablePermissions` is set; fixes issue 15049. --- [1] Either manually with `about:config`, or using e.g. a [Group Policy](https://github.com/mozilla/policy-templates). --- web/app.js | 20 +++++------ web/app_options.js | 4 ++- web/base_viewer.js | 90 ++++++++++++++++++++++++++++------------------ web/toolbar.js | 26 ++++++++------ web/viewer.html | 36 ++++++++++--------- web/viewer.js | 1 - 6 files changed, 103 insertions(+), 74 deletions(-) diff --git a/web/app.js b/web/app.js index 390ebd426776db..4e4d0800902f45 100644 --- a/web/app.js +++ b/web/app.js @@ -506,6 +506,7 @@ const PDFViewerApplication = { const container = appConfig.mainContainer, viewer = appConfig.viewerContainer; + const annotationEditorEnabled = AppOptions.get("annotationEditorEnabled"); const pageColors = { background: AppOptions.get("pageColorsBackground"), foreground: AppOptions.get("pageColorsForeground"), @@ -529,7 +530,7 @@ const PDFViewerApplication = { l10n: this.l10n, textLayerMode: AppOptions.get("textLayerMode"), annotationMode: AppOptions.get("annotationMode"), - annotationEditorEnabled: AppOptions.get("annotationEditorEnabled"), + annotationEditorEnabled, imageResourcesPath: AppOptions.get("imageResourcesPath"), enablePrintAutoRotate: AppOptions.get("enablePrintAutoRotate"), useOnlyCssZoom: AppOptions.get("useOnlyCssZoom"), @@ -565,6 +566,14 @@ const PDFViewerApplication = { this.findBar = new PDFFindBar(appConfig.findBar, eventBus, this.l10n); } + if (annotationEditorEnabled) { + const editorModeButtons = appConfig.toolbar.editorNoneButton.parentNode; + editorModeButtons.classList.remove("hidden"); + + const editorModeSeparator = editorModeButtons.nextElementSibling; + editorModeSeparator.classList.remove("hidden"); + } + this.pdfDocumentProperties = new PDFDocumentProperties( appConfig.documentProperties, this.overlayManager, @@ -1196,11 +1205,6 @@ const PDFViewerApplication = { this.toolbar.setPagesCount(pdfDocument.numPages, false); this.secondaryToolbar.setPagesCount(pdfDocument.numPages); - if (pdfDocument.isPureXfa) { - console.warn("Warning: XFA-editing is not implemented."); - this.toolbar.updateEditorModeButtonsState(/* disabled = */ true); - } - let baseDocumentUrl; if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) { baseDocumentUrl = null; @@ -2242,10 +2246,6 @@ function webViewerInitialized() { appConfig.toolbar.viewFind.classList.add("hidden"); } - if (PDFViewerApplication.pdfViewer.enableAnnotationEditor) { - appConfig.toolbar.editorModeButtons.classList.remove("hidden"); - } - appConfig.mainContainer.addEventListener( "transitionend", function (evt) { diff --git a/web/app_options.js b/web/app_options.js index 5334c850c4a597..220975859a7538 100644 --- a/web/app_options.js +++ b/web/app_options.js @@ -61,7 +61,9 @@ const OptionKind = { const defaultOptions = { annotationEditorEnabled: { /** @type {boolean} */ - value: typeof PDFJSDev !== "undefined" && PDFJSDev.test("TESTING"), + value: + typeof PDFJSDev === "undefined" || + PDFJSDev.test("!PRODUCTION || TESTING"), kind: OptionKind.VIEWER + OptionKind.PREFERENCE, }, annotationMode: { diff --git a/web/base_viewer.js b/web/base_viewer.js index 12837f1d210e08..1eaf016a8db956 100644 --- a/web/base_viewer.js +++ b/web/base_viewer.js @@ -86,7 +86,9 @@ const PagesCountLimit = { }; function isValidAnnotationEditorMode(mode) { - return Object.values(AnnotationEditorType).includes(mode); + return ( + Number.isInteger(mode) && Object.values(AnnotationEditorType).includes(mode) + ); } /** @@ -219,8 +221,6 @@ class BaseViewer { #annotationMode = AnnotationMode.ENABLE_FORMS; - #previousAnnotationMode = null; - #enablePermissions = false; #previousContainerHeight = 0; @@ -275,6 +275,9 @@ class BaseViewer { this.textLayerMode = options.textLayerMode ?? TextLayerMode.ENABLE; this.#annotationMode = options.annotationMode ?? AnnotationMode.ENABLE_FORMS; + this.#annotationEditorMode = options.annotationEditorEnabled + ? AnnotationEditorType.NONE + : null; this.imageResourcesPath = options.imageResourcesPath || ""; this.enablePrintAutoRotate = options.enablePrintAutoRotate || false; this.renderer = options.renderer || RendererType.CANVAS; @@ -284,10 +287,6 @@ class BaseViewer { this.#enablePermissions = options.enablePermissions || false; this.pageColors = options.pageColors || null; - if (options.annotationEditorEnabled === true) { - this.#annotationEditorUIManager = new AnnotationEditorUIManager(); - } - if (typeof PDFJSDev === "undefined" || !PDFJSDev.test("MOZCENTRAL")) { if ( this.pageColors && @@ -354,13 +353,6 @@ class BaseViewer { return this.#annotationMode === AnnotationMode.ENABLE_FORMS; } - /** - * @type {boolean} - */ - get enableAnnotationEditor() { - return !!this.#annotationEditorUIManager; - } - /** * @type {boolean} */ @@ -553,25 +545,35 @@ class BaseViewer { /** * Currently only *some* permissions are supported. + * @returns {Object} */ #initializePermissions(permissions) { + const params = { + annotationEditorMode: this.#annotationEditorMode, + annotationMode: this.#annotationMode, + textLayerMode: this.textLayerMode, + }; if (!permissions) { - return; + return params; } if (!permissions.includes(PermissionFlag.COPY)) { this.viewer.classList.add(ENABLE_PERMISSIONS_CLASS); } + if (!permissions.includes(PermissionFlag.MODIFY_CONTENTS)) { + params.annotationEditorMode = null; + } + if ( !permissions.includes(PermissionFlag.MODIFY_ANNOTATIONS) && - !permissions.includes(PermissionFlag.FILL_INTERACTIVE_FORMS) + !permissions.includes(PermissionFlag.FILL_INTERACTIVE_FORMS) && + this.#annotationMode === AnnotationMode.ENABLE_FORMS ) { - if (this.#annotationMode === AnnotationMode.ENABLE_FORMS) { - this.#previousAnnotationMode = this.#annotationMode; // Allow resetting. - this.#annotationMode = AnnotationMode.ENABLE; - } + params.annotationMode = AnnotationMode.ENABLE; } + + return params; } #onePageRenderedOrForceFetch() { @@ -706,7 +708,23 @@ class BaseViewer { } this._firstPageCapability.resolve(firstPdfPage); this._optionalContentConfigPromise = optionalContentConfigPromise; - this.#initializePermissions(permissions); + + const { annotationEditorMode, annotationMode, textLayerMode } = + this.#initializePermissions(permissions); + + if (annotationEditorMode !== null) { + if (isPureXfa) { + console.warn("Warning: XFA-editing is not implemented."); + } else { + // Ensure that the Editor buttons, in the toolbar, are updated. + this.eventBus.dispatch("annotationeditormodechanged", { + source: this, + mode: annotationEditorMode, + }); + + this.#annotationEditorUIManager = new AnnotationEditorUIManager(); + } + } const viewerElement = this._scrollMode === ScrollMode.PAGE ? null : this.viewer; @@ -715,14 +733,13 @@ class BaseViewer { scale: scale * PixelsPerInch.PDF_TO_CSS_UNITS, }); const textLayerFactory = - this.textLayerMode !== TextLayerMode.DISABLE && !isPureXfa - ? this - : null; + textLayerMode !== TextLayerMode.DISABLE && !isPureXfa ? this : null; const annotationLayerFactory = - this.#annotationMode !== AnnotationMode.DISABLE ? this : null; + annotationMode !== AnnotationMode.DISABLE ? this : null; const xfaLayerFactory = isPureXfa ? this : null; - const annotationEditorLayerFactory = - this.#annotationEditorUIManager && !isPureXfa ? this : null; + const annotationEditorLayerFactory = this.#annotationEditorUIManager + ? this + : null; for (let pageNum = 1; pageNum <= pagesCount; ++pageNum) { const pageView = new PDFPageView({ @@ -734,9 +751,9 @@ class BaseViewer { optionalContentConfigPromise, renderingQueue: this.renderingQueue, textLayerFactory, - textLayerMode: this.textLayerMode, + textLayerMode, annotationLayerFactory, - annotationMode: this.#annotationMode, + annotationMode, xfaLayerFactory, annotationEditorLayerFactory, textHighlighterFactory: this, @@ -868,6 +885,10 @@ class BaseViewer { } _resetView() { + if (this.#annotationEditorMode !== null) { + this.#annotationEditorMode = AnnotationEditorType.NONE; + } + this.#annotationEditorUIManager = null; this._pages = []; this._currentPageNumber = 1; this._currentScale = UNKNOWN_SCALE; @@ -913,11 +934,6 @@ class BaseViewer { this.viewer.removeAttribute("lang"); // Reset all PDF document permissions. this.viewer.classList.remove(ENABLE_PERMISSIONS_CLASS); - - if (this.#previousAnnotationMode !== null) { - this.#annotationMode = this.#previousAnnotationMode; - this.#previousAnnotationMode = null; - } } #ensurePageViewVisible() { @@ -2125,6 +2141,9 @@ class BaseViewer { } } + /** + * @type {number | null} + */ get annotationEditorMode() { return this.#annotationEditorMode; } @@ -2142,6 +2161,9 @@ class BaseViewer { if (!isValidAnnotationEditorMode(mode)) { throw new Error(`Invalid AnnotationEditor mode: ${mode}`); } + if (!this.pdfDocument) { + return; + } this.#annotationEditorMode = mode; this.eventBus.dispatch("annotationeditormodechanged", { source: this, diff --git a/web/toolbar.js b/web/toolbar.js index 5da90c8f8d97e5..b78e9476d9862d 100644 --- a/web/toolbar.js +++ b/web/toolbar.js @@ -141,7 +141,9 @@ class Toolbar { this.pageScale = DEFAULT_SCALE; this._updateUIState(true); this.updateLoadingIndicatorState(); - this.updateEditorModeButtonsState(); + + // Reset the Editor buttons too, since they're document specific. + this.eventBus.dispatch("toolbarreset", { source: this }); } _bindListeners(options) { @@ -212,7 +214,7 @@ class Toolbar { editorFreeTextButton, editorInkButton, }) { - this.eventBus._on("annotationeditormodechanged", evt => { + const editorModeChanged = (evt, disableButtons = false) => { const editorButtons = [ [AnnotationEditorType.NONE, editorNoneButton], [AnnotationEditorType.FREETEXT, editorFreeTextButton], @@ -223,6 +225,17 @@ class Toolbar { const checked = mode === evt.mode; button.classList.toggle("toggled", checked); button.setAttribute("aria-checked", checked); + button.disabled = disableButtons; + } + }; + this.eventBus._on("annotationeditormodechanged", editorModeChanged); + + this.eventBus._on("toolbarreset", evt => { + if (evt.source === this) { + editorModeChanged( + { mode: AnnotationEditorType.NONE }, + /* disableButtons = */ true + ); } }); } @@ -286,15 +299,6 @@ class Toolbar { pageNumber.classList.toggle(PAGE_NUMBER_LOADING_INDICATOR, loading); } - updateEditorModeButtonsState(disabled = false) { - const { editorNoneButton, editorFreeTextButton, editorInkButton } = - this.items; - - editorNoneButton.disabled = disabled; - editorFreeTextButton.disabled = disabled; - editorInkButton.disabled = disabled; - } - /** * Increase the width of the zoom dropdown DOM element if, and only if, it's * too narrow to fit the *longest* of the localized strings. diff --git a/web/viewer.html b/web/viewer.html index 86e979455f3406..871b53cf29e535 100644 --- a/web/viewer.html +++ b/web/viewer.html @@ -263,41 +263,43 @@
- - - - - - - + Current View
+ + + + diff --git a/web/viewer.js b/web/viewer.js index 72616c65bdb365..3cce5f3aaed4e8 100644 --- a/web/viewer.js +++ b/web/viewer.js @@ -93,7 +93,6 @@ function getViewerConfiguration() { ? document.getElementById("openFile") : null, print: document.getElementById("print"), - editorModeButtons: document.getElementById("editorModeButtons"), editorNoneButton: document.getElementById("editorNone"), editorFreeTextButton: document.getElementById("editorFreeText"), editorInkButton: document.getElementById("editorInk"),