Skip to content

Commit

Permalink
[editor] Support disabling of editing when pdfjs.enablePermissions
Browse files Browse the repository at this point in the history
…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).
  • Loading branch information
Snuffleupagus committed Jun 21, 2022
1 parent 6ee538e commit 35a6a50
Show file tree
Hide file tree
Showing 6 changed files with 102 additions and 73 deletions.
21 changes: 11 additions & 10 deletions web/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand All @@ -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"),
Expand Down Expand Up @@ -565,6 +566,15 @@ const PDFViewerApplication = {
this.findBar = new PDFFindBar(appConfig.findBar, eventBus, this.l10n);
}

if (annotationEditorEnabled) {
for (const element of [
document.getElementById("editorModeButtons"),
document.getElementById("editorModeSeparator"),
]) {
element.classList.remove("hidden");
}
}

this.pdfDocumentProperties = new PDFDocumentProperties(
appConfig.documentProperties,
this.overlayManager,
Expand Down Expand Up @@ -1196,11 +1206,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;
Expand Down Expand Up @@ -2242,10 +2247,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) {
Expand Down
4 changes: 3 additions & 1 deletion web/app_options.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
86 changes: 53 additions & 33 deletions web/base_viewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -219,8 +219,6 @@ class BaseViewer {

#annotationMode = AnnotationMode.ENABLE_FORMS;

#previousAnnotationMode = null;

#enablePermissions = false;

#previousContainerHeight = 0;
Expand Down Expand Up @@ -275,6 +273,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;
Expand All @@ -284,10 +285,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 &&
Expand Down Expand Up @@ -354,13 +351,6 @@ class BaseViewer {
return this.#annotationMode === AnnotationMode.ENABLE_FORMS;
}

/**
* @type {boolean}
*/
get enableAnnotationEditor() {
return !!this.#annotationEditorUIManager;
}

/**
* @type {boolean}
*/
Expand Down Expand Up @@ -553,25 +543,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() {
Expand Down Expand Up @@ -706,7 +706,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;
Expand All @@ -715,14 +731,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({
Expand All @@ -734,9 +749,9 @@ class BaseViewer {
optionalContentConfigPromise,
renderingQueue: this.renderingQueue,
textLayerFactory,
textLayerMode: this.textLayerMode,
textLayerMode,
annotationLayerFactory,
annotationMode: this.#annotationMode,
annotationMode,
xfaLayerFactory,
annotationEditorLayerFactory,
textHighlighterFactory: this,
Expand Down Expand Up @@ -868,6 +883,10 @@ class BaseViewer {
}

_resetView() {
if (this.#annotationEditorMode !== null) {
this.#annotationEditorMode = AnnotationEditorType.NONE;
}
this.#annotationEditorUIManager = null;
this._pages = [];
this._currentPageNumber = 1;
this._currentScale = UNKNOWN_SCALE;
Expand Down Expand Up @@ -913,11 +932,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() {
Expand Down Expand Up @@ -2125,6 +2139,9 @@ class BaseViewer {
}
}

/**
* @type {number | null}
*/
get annotationEditorMode() {
return this.#annotationEditorMode;
}
Expand All @@ -2142,6 +2159,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,
Expand Down
26 changes: 15 additions & 11 deletions web/toolbar.js
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -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],
Expand All @@ -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
);
}
});
}
Expand Down Expand Up @@ -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.
Expand Down
37 changes: 20 additions & 17 deletions web/viewer.html
Original file line number Diff line number Diff line change
Expand Up @@ -263,41 +263,44 @@
<span id="numPages" class="toolbarLabel"></span>
</div>
<div id="toolbarViewerRight">
<div id="editorModeButtons" class="splitToolbarButton toggled hidden" role="radiogroup">
<button id="editorNone" class="toolbarButton toggled" title="Disable Annotation Editing" role="radio" aria-checked="true" tabindex="31" data-l10n-id="editor_none">
<span data-l10n-id="editor_none_label">Disable Editing</span>
</button>
<button id="editorFreeText" class="toolbarButton" title="Add FreeText Annotation" role="radio" aria-checked="false" tabindex="32" data-l10n-id="editor_free_text">
<span data-l10n-id="editor_free_text_label">FreeText Annotation</span>
</button>
<button id="editorInk" class="toolbarButton" title="Add Ink Annotation" role="radio" aria-checked="false" tabindex="33" data-l10n-id="editor_ink">
<span data-l10n-id="editor_ink_label">Ink Annotation</span>
</button>
</div>

<button id="presentationMode" class="toolbarButton hiddenLargeView" title="Switch to Presentation Mode" tabindex="43" data-l10n-id="presentation_mode">
<button id="presentationMode" class="toolbarButton hiddenLargeView" title="Switch to Presentation Mode" tabindex="31" data-l10n-id="presentation_mode">
<span data-l10n-id="presentation_mode_label">Presentation Mode</span>
</button>

<!--#if GENERIC-->
<button id="openFile" class="toolbarButton hiddenLargeView" title="Open File" tabindex="44" data-l10n-id="open_file">
<button id="openFile" class="toolbarButton hiddenLargeView" title="Open File" tabindex="32" data-l10n-id="open_file">
<span data-l10n-id="open_file_label">Open</span>
</button>
<!--#endif-->

<button id="print" class="toolbarButton hiddenMediumView" title="Print" tabindex="45" data-l10n-id="print">
<button id="print" class="toolbarButton hiddenMediumView" title="Print" tabindex="33" data-l10n-id="print">
<span data-l10n-id="print_label">Print</span>
</button>

<button id="download" class="toolbarButton hiddenMediumView" title="Download" tabindex="46" data-l10n-id="download">
<button id="download" class="toolbarButton hiddenMediumView" title="Download" tabindex="34" data-l10n-id="download">
<span data-l10n-id="download_label">Download</span>
</button>
<a href="#" id="viewBookmark" class="toolbarButton hiddenSmallView" title="Current view (copy or open in new window)" tabindex="47" data-l10n-id="bookmark">
<a href="#" id="viewBookmark" class="toolbarButton hiddenSmallView" title="Current view (copy or open in new window)" tabindex="35" data-l10n-id="bookmark">
<span data-l10n-id="bookmark_label">Current View</span>
</a>

<div class="verticalToolbarSeparator hiddenSmallView"></div>

<div id="editorModeButtons" class="splitToolbarButton toggled hidden" role="radiogroup">
<button id="editorNone" class="toolbarButton toggled" disabled="disabled" title="Disable Annotation Editing" role="radio" aria-checked="true" tabindex="36" data-l10n-id="editor_none">
<span data-l10n-id="editor_none_label">Disable Editing</span>
</button>
<button id="editorFreeText" class="toolbarButton" disabled="disabled" title="Add FreeText Annotation" role="radio" aria-checked="false" tabindex="37" data-l10n-id="editor_free_text">
<span data-l10n-id="editor_free_text_label">FreeText Annotation</span>
</button>
<button id="editorInk" class="toolbarButton" disabled="disabled" title="Add Ink Annotation" role="radio" aria-checked="false" tabindex="38" data-l10n-id="editor_ink">
<span data-l10n-id="editor_ink_label">Ink Annotation</span>
</button>
</div>

<!-- Should be visible when the "editorModeButtons" are visible. -->
<div id="editorModeSeparator" class="verticalToolbarSeparator hidden"></div>

<button id="secondaryToolbarToggle" class="toolbarButton" title="Tools" tabindex="48" data-l10n-id="tools" aria-expanded="false" aria-controls="secondaryToolbar">
<span data-l10n-id="tools_label">Tools</span>
</button>
Expand Down
1 change: 0 additions & 1 deletion web/viewer.js
Original file line number Diff line number Diff line change
Expand Up @@ -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"),
Expand Down

0 comments on commit 35a6a50

Please sign in to comment.