[api-minor] Move the viewer scripting initialization/handling into a new PDFScriptingManager class

The *main* purpose of this patch is to allow scripting to be used together with the viewer components, note the updated "simpleviewer"/"singlepageviewer" examples, in addition to the full default viewer.
Given how the scripting functionality is currently implemented in the default viewer, trying to re-use this with the standalone viewer components would be *very* hard and ideally you'd want it to work out-of-the-box.

For an initial implementation, in the default viewer, of the scripting functionality it probably made sense to simply dump all of the code in the `app.js` file, however that cannot be used with the viewer components.
To address this, the functionality is moved into a new `PDFScriptingManager` class which can thus be handled in the same way as all other viewer components (and e.g. be passed to the `BaseViewer`-implementations).

Obviously the scripting functionality needs quite a lot of data, during its initialization, and for the default viewer we want to maintain the current way of doing the lookups since that helps avoid a number of redundant API-calls.
To that end, the `PDFScriptingManager` implementation accepts (optional) factories/functions such that we can maintain the current behaviour for the default viewer. For the viewer components specifically, fallback code-paths are provided to ensure that scripting will "just work"[1].

Besides moving the viewer handling of the scripting code to its own file/class, this patch also takes the opportunity to re-factor the functionality into a number of helper methods to improve overall readability[2].
Note that it's definitely possible that the `PDFScriptingManager` class could be improved even further (e.g. for general re-use), since it's still heavily tailored to the default viewer use-case, however I believe that this patch is still a good step forward overall.

---

[1] Obviously *all* the relevant document properties might not be available in the viewer components use-case (e.g. the various URLs), but most things should work just fine.

[2] The old `PDFViewerApplication._initializeJavaScript` method, where everything was simply inlined, have over time (in my opinion) become quite large and somewhat difficult to *easily* reason about.
This commit is contained in:
Jonas Jenwald 2021-03-05 00:15:18 +01:00
parent 5b9638329c
commit a6d1cba38c
8 changed files with 525 additions and 261 deletions

View file

@ -55,6 +55,8 @@ const DEFAULT_CACHE_SIZE = 10;
* component.
* @property {PDFFindController} [findController] - The find controller
* component.
* @property {PDFScriptingManager} [scriptingManager] - The scripting manager
* component.
* @property {PDFRenderingQueue} [renderingQueue] - The rendering queue object.
* @property {boolean} [removePageBorders] - Removes the border shadow around
* the pages. The default value is `false`.
@ -77,10 +79,8 @@ const DEFAULT_CACHE_SIZE = 10;
* total pixels, i.e. width * height. Use -1 for no limit. The default value
* is 4096 * 4096 (16 mega-pixels).
* @property {IL10n} l10n - Localization service.
* @property {boolean} [enableScripting] - Enable embedded script execution.
* The default value is `false`.
* @property {Object} [mouseState] - The mouse button state. The default value
* is `null`.
* @property {boolean} [enableScripting] - Enable embedded script execution
* (also requires {scriptingManager} being set). The default value is `false`.
*/
function PDFPageViewBuffer(size) {
@ -183,6 +183,7 @@ class BaseViewer {
this.linkService = options.linkService || new SimpleLinkService();
this.downloadManager = options.downloadManager || null;
this.findController = options.findController || null;
this._scriptingManager = options.scriptingManager || null;
this.removePageBorders = options.removePageBorders || false;
this.textLayerMode = Number.isInteger(options.textLayerMode)
? options.textLayerMode
@ -195,8 +196,8 @@ class BaseViewer {
this.useOnlyCssZoom = options.useOnlyCssZoom || false;
this.maxCanvasPixels = options.maxCanvasPixels;
this.l10n = options.l10n || NullL10n;
this.enableScripting = options.enableScripting || false;
this._mouseState = options.mouseState || null;
this.enableScripting =
options.enableScripting === true && !!this._scriptingManager;
this.defaultRenderingQueue = !options.renderingQueue;
if (this.defaultRenderingQueue) {
@ -468,6 +469,12 @@ class BaseViewer {
if (this.findController) {
this.findController.setDocument(null);
}
if (this._scriptingManager) {
// Defer this slightly, to allow the "pageclose" event to be handled.
Promise.resolve().then(() => {
this._scriptingManager.setDocument(null);
});
}
}
this.pdfDocument = pdfDocument;
@ -562,6 +569,9 @@ class BaseViewer {
if (this.findController) {
this.findController.setDocument(pdfDocument); // Enable searching.
}
if (this.enableScripting) {
this._scriptingManager.setDocument(pdfDocument);
}
// In addition to 'disableAutoFetch' being set, also attempt to reduce
// resource usage when loading *very* long/large documents.
@ -1299,7 +1309,7 @@ class BaseViewer {
enableScripting,
hasJSActionsPromise:
hasJSActionsPromise || this.pdfDocument?.hasJSActions(),
mouseState: mouseState || this._mouseState,
mouseState: mouseState || this._scriptingManager?.mouseState,
});
}
@ -1645,7 +1655,7 @@ class BaseViewer {
}
const eventBus = this.eventBus,
pageOpenPendingSet = (this._pageOpenPendingSet = new Set()),
scriptingEvents = (this._scriptingEvents ||= Object.create(null));
scriptingEvents = (this._scriptingEvents = Object.create(null));
const dispatchPageClose = pageNumber => {
if (pageOpenPendingSet.has(pageNumber)) {
@ -1709,15 +1719,11 @@ class BaseViewer {
// Remove the event listeners.
eventBus._off("pagechanging", scriptingEvents.onPageChanging);
scriptingEvents.onPageChanging = null;
eventBus._off("pagerendered", scriptingEvents.onPageRendered);
scriptingEvents.onPageRendered = null;
eventBus._off("pagesdestroy", scriptingEvents.onPagesDestroy);
scriptingEvents.onPagesDestroy = null;
this._pageOpenPendingSet = null;
this._scriptingEvents = null;
}
}