[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).
This commit is contained in:
Jonas Jenwald 2022-06-20 18:08:41 +02:00
parent 6ee538e0ba
commit 35a6a508ee
6 changed files with 102 additions and 73 deletions

View File

@ -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,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,
@ -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;
@ -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) {

View File

@ -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: {

View File

@ -219,8 +219,6 @@ class BaseViewer {
#annotationMode = AnnotationMode.ENABLE_FORMS;
#previousAnnotationMode = null;
#enablePermissions = false;
#previousContainerHeight = 0;
@ -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;
@ -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 &&
@ -354,13 +351,6 @@ class BaseViewer {
return this.#annotationMode === AnnotationMode.ENABLE_FORMS;
}
/**
* @type {boolean}
*/
get enableAnnotationEditor() {
return !!this.#annotationEditorUIManager;
}
/**
* @type {boolean}
*/
@ -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() {
@ -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;
@ -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({
@ -734,9 +749,9 @@ class BaseViewer {
optionalContentConfigPromise,
renderingQueue: this.renderingQueue,
textLayerFactory,
textLayerMode: this.textLayerMode,
textLayerMode,
annotationLayerFactory,
annotationMode: this.#annotationMode,
annotationMode,
xfaLayerFactory,
annotationEditorLayerFactory,
textHighlighterFactory: this,
@ -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;
@ -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() {
@ -2125,6 +2139,9 @@ class BaseViewer {
}
}
/**
* @type {number | null}
*/
get annotationEditorMode() {
return this.#annotationEditorMode;
}
@ -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,

View File

@ -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.

View File

@ -263,41 +263,44 @@ See https://github.com/adobe-type-tools/cmap-resources
<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>

View File

@ -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"),