From 2a83c965e877154260882a8271a3b015db960f18 Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Wed, 9 Dec 2020 20:08:49 +0100 Subject: [PATCH 1/2] Add new "pageopen"/"pageclose" events for usage with JavaScript actions Having looked at the Acrobat JavaScript specification, see https://www.adobe.com/content/dam/acom/en/devnet/acrobat/pdfs/AcrobatDC_js_api_reference.pdf#G5.1963437, I suppose that introducing these two new events is probably the easiest solution overall. However there's a number of things that, as far as I'm concerned, will help the overall implementation: - Only dispatch these new events when `enableScripting = true` is set. - Handle them *separately* from the existing "pagechanging" event dispatching, to avoid too much clutter. - Don't dispatch either of the events if the page didn't actually change. - When waiting for pages to render, don't dispatch "pageopen" if the page is no longer active when rendering finishes. - Ensure that we only use *one* "pagerendered" event listener. - Ensure that "pageopen" is actually dispatched when the document loads. I suppose that we *could* avoid adding the "pageclose" event, and use the existing "pagechanging" event instead, however having a separate event might allow more flexibility in the future. (E.g. I don't know if we'll possibly want to dispatch "pageclose" on document close, as mentioned briefly in the specification.) --- web/base_viewer.js | 56 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/web/base_viewer.js b/web/base_viewer.js index f0a172b374d01..1bbff87ebfff9 100644 --- a/web/base_viewer.js +++ b/web/base_viewer.js @@ -214,6 +214,7 @@ class BaseViewer { if (this.removePageBorders) { this.viewer.classList.add("removePageBorders"); } + this._initializeScriptingEvents(); // Defer the dispatching of this event, to give other viewer components // time to initialize *and* register 'baseviewerinit' event listeners. Promise.resolve().then(() => { @@ -283,12 +284,14 @@ class BaseViewer { if (!(0 < val && val <= this.pagesCount)) { return false; } + const previous = this._currentPageNumber; this._currentPageNumber = val; this.eventBus.dispatch("pagechanging", { source: this, pageNumber: val, pageLabel: this._pageLabels && this._pageLabels[val - 1], + previous, }); if (resetCurrentPageView) { @@ -649,6 +652,7 @@ class BaseViewer { this._pagesCapability = createPromiseCapability(); this._scrollMode = ScrollMode.VERTICAL; this._spreadMode = SpreadMode.NONE; + this._pageOpenPendingSet = null; if (this._onBeforeDraw) { this.eventBus._off("pagerender", this._onBeforeDraw); @@ -1500,6 +1504,58 @@ class BaseViewer { this._setCurrentPageNumber(pageNumber, /* resetCurrentPageView = */ true); this.update(); } + + /** + * @private + */ + _initializeScriptingEvents() { + if (!this.enableScripting) { + return; + } + const { eventBus } = this; + + const dispatchPageClose = pageNumber => { + eventBus.dispatch("pageclose", { source: this, pageNumber }); + }; + const dispatchPageOpen = (pageNumber, force = false) => { + const pageView = this._pages[pageNumber - 1]; + if (force || pageView?.renderingState === RenderingStates.FINISHED) { + this._pageOpenPendingSet?.delete(pageNumber); + + eventBus.dispatch("pageopen", { source: this, pageNumber }); + } else { + if (!this._pageOpenPendingSet) { + this._pageOpenPendingSet = new Set(); + } + this._pageOpenPendingSet.add(pageNumber); + } + }; + + eventBus._on("pagechanging", ({ pageNumber, previous }) => { + if (pageNumber === previous) { + return; // The active page didn't change. + } + dispatchPageClose(previous); + dispatchPageOpen(pageNumber); + }); + + eventBus._on("pagerendered", ({ pageNumber }) => { + if (!this._pageOpenPendingSet) { + return; // No pending "pageopen" events. + } + if (!this._pageOpenPendingSet.has(pageNumber)) { + return; // No pending "pageopen" event for the newly rendered page. + } + if (pageNumber !== this._currentPageNumber) { + return; // The newly rendered page is no longer the current one. + } + dispatchPageOpen(pageNumber, /* force = */ true); + }); + + eventBus._on("pagesinit", () => { + dispatchPageOpen(this._currentPageNumber); + }); + } } export { BaseViewer }; From 0e69973d710f80ad0ced5048837f774de3635a9d Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Tue, 15 Dec 2020 17:20:15 +0100 Subject: [PATCH 2/2] Add a new "pagesdestroy" event, dispatched *before* the `BaseViewer` removes an existing document This new event essentially mirrors the existing "pagesinit" event, and will allow e.g. a custom implementation of the viewer to be notified before the current PDF document is removed from the viewer. By using this new event, we're thus able to dispatch a "pageclose" event for JavaScript actions when closing the existing document. --- web/base_viewer.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/web/base_viewer.js b/web/base_viewer.js index 1bbff87ebfff9..df50b37611ca7 100644 --- a/web/base_viewer.js +++ b/web/base_viewer.js @@ -460,6 +460,8 @@ class BaseViewer { */ setDocument(pdfDocument) { if (this.pdfDocument) { + this.eventBus.dispatch("pagesdestroy", { source: this }); + this._cancelRendering(); this._resetView(); @@ -1555,6 +1557,10 @@ class BaseViewer { eventBus._on("pagesinit", () => { dispatchPageOpen(this._currentPageNumber); }); + + eventBus._on("pagesdestroy", () => { + dispatchPageClose(this._currentPageNumber); + }); } }