diff --git a/gulpfile.mjs b/gulpfile.mjs index 6b518f345..28d78e451 100644 --- a/gulpfile.mjs +++ b/gulpfile.mjs @@ -116,6 +116,7 @@ const DEFINES = Object.freeze({ WORKER_THREAD: false, TESTING: undefined, COVERAGE: undefined, + INTERNAL_EVT: crypto.randomUUID(), // The main build targets: GENERIC: false, MOZCENTRAL: false, diff --git a/src/display/editor/tools.js b/src/display/editor/tools.js index 52ffdc832..08baf05a1 100644 --- a/src/display/editor/tools.js +++ b/src/display/editor/tools.js @@ -36,6 +36,7 @@ import { stopEvent, } from "../display_utils.js"; import { FloatingToolbar } from "./toolbar.js"; +import { internalOpt } from "../../shared/internal_evt.js"; function bindEvents(obj, element, names) { for (const name of names) { @@ -939,17 +940,21 @@ class AnnotationEditorUIManager { this.#signatureManager = signatureManager; this.#pdfDocument = pdfDocument; this._eventBus = eventBus; - eventBus._on("editingaction", this.onEditingAction.bind(this), { signal }); - eventBus._on("pagechanging", this.onPageChanging.bind(this), { signal }); - eventBus._on("scalechanging", this.onScaleChanging.bind(this), { signal }); - eventBus._on("rotationchanging", this.onRotationChanging.bind(this), { - signal, - }); - eventBus._on("setpreference", this.onSetPreference.bind(this), { signal }); - eventBus._on( + + const evtOpts = { signal, ...internalOpt }; + eventBus.on("editingaction", this.onEditingAction.bind(this), evtOpts); + eventBus.on("pagechanging", this.onPageChanging.bind(this), evtOpts); + eventBus.on("scalechanging", this.onScaleChanging.bind(this), evtOpts); + eventBus.on( + "rotationchanging", + this.onRotationChanging.bind(this), + evtOpts + ); + eventBus.on("setpreference", this.onSetPreference.bind(this), evtOpts); + eventBus.on( "switchannotationeditorparams", evt => this.updateParams(evt.type, evt.value), - { signal } + evtOpts ); window.addEventListener( "pointerdown", @@ -1222,7 +1227,7 @@ class AnnotationEditorUIManager { const { resolve, promise } = Promise.withResolvers(); const onEditorsRendered = evt => { if (evt.pageNumber === pageNumber) { - this._eventBus._off("editorsrendered", onEditorsRendered); + this._eventBus.off("editorsrendered", onEditorsRendered); resolve(); } }; diff --git a/src/shared/internal_evt.js b/src/shared/internal_evt.js new file mode 100644 index 000000000..09e49de6b --- /dev/null +++ b/src/shared/internal_evt.js @@ -0,0 +1,27 @@ +/* Copyright 2026 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Keep this file in sync with `web/internal_evt.js`. + */ + +const INTERNAL_EVT = + typeof PDFJSDev !== "undefined" && !PDFJSDev.test("TESTING") + ? PDFJSDev.eval("INTERNAL_EVT") + : "internalEvent"; + +const internalOpt = Object.freeze({ internal: INTERNAL_EVT }); + +export { INTERNAL_EVT, internalOpt }; diff --git a/test/integration/reorganize_pages_spec.mjs b/test/integration/reorganize_pages_spec.mjs index bdf634f72..e1a7f20f3 100644 --- a/test/integration/reorganize_pages_spec.mjs +++ b/test/integration/reorganize_pages_spec.mjs @@ -3556,7 +3556,7 @@ describe("Reorganize Pages View", () => { await waitAndClick(page, getThumbnailSelector(2)); const handleMerged = await createPromise(page, resolve => { - window.PDFViewerApplication.eventBus._on( + window.PDFViewerApplication.eventBus.on( "thumbnailsloaded", resolve, { once: true } @@ -3628,7 +3628,7 @@ describe("Reorganize Pages View", () => { await waitForThumbnailVisible(page, 1); const handleMerged = await createPromise(page, resolve => { - window.PDFViewerApplication.eventBus._on( + window.PDFViewerApplication.eventBus.on( "thumbnailsloaded", resolve, { once: true } diff --git a/web/alt_text_manager.js b/web/alt_text_manager.js index d3ddac8e7..8aca10ac3 100644 --- a/web/alt_text_manager.js +++ b/web/alt_text_manager.js @@ -14,6 +14,7 @@ */ import { DOMSVGFactory } from "pdfjs-lib"; +import { internalOpt } from "./internal_evt.js"; class AltTextManager { #clickAC = null; @@ -170,8 +171,9 @@ class AltTextManager { this.#uiManager.removeEditListeners(); this.#resizeAC = new AbortController(); - this.#eventBus._on("resize", this.#setPosition.bind(this), { + this.#eventBus.on("resize", this.#setPosition.bind(this), { signal: this.#resizeAC.signal, + ...internalOpt, }); try { diff --git a/web/annotation_editor_params.js b/web/annotation_editor_params.js index 1b29eb8d4..8535d52d9 100644 --- a/web/annotation_editor_params.js +++ b/web/annotation_editor_params.js @@ -21,6 +21,7 @@ import { getRGBA, Util, } from "pdfjs-lib"; +import { internalOpt } from "./internal_evt.js"; /** * @typedef {Object} AnnotationEditorParamsOptions @@ -155,42 +156,46 @@ class AnnotationEditorParams { dispatchEvent("CREATE"); }); - eventBus._on("annotationeditorparamschanged", evt => { - for (const [type, value] of evt.details) { - switch (type) { - case AnnotationEditorParamsType.FREETEXT_SIZE: - editorFreeTextFontSize.value = value; - break; - case AnnotationEditorParamsType.FREETEXT_COLOR: - editorFreeTextColor.value = value; - break; - case AnnotationEditorParamsType.INK_COLOR: - updateInkColor(value); - break; - case AnnotationEditorParamsType.INK_THICKNESS: - editorInkThickness.value = value; - break; - case AnnotationEditorParamsType.INK_OPACITY: - updateInkOpacity(value); - break; - case AnnotationEditorParamsType.HIGHLIGHT_COLOR: - eventBus.dispatch("mainhighlightcolorpickerupdatecolor", { - source: this, - value, - }); - break; - case AnnotationEditorParamsType.HIGHLIGHT_THICKNESS: - editorFreeHighlightThickness.value = value; - break; - case AnnotationEditorParamsType.HIGHLIGHT_FREE: - editorFreeHighlightThickness.disabled = !value; - break; - case AnnotationEditorParamsType.HIGHLIGHT_SHOW_ALL: - editorHighlightShowAll.setAttribute("aria-pressed", value); - break; + eventBus.on( + "annotationeditorparamschanged", + evt => { + for (const [type, value] of evt.details) { + switch (type) { + case AnnotationEditorParamsType.FREETEXT_SIZE: + editorFreeTextFontSize.value = value; + break; + case AnnotationEditorParamsType.FREETEXT_COLOR: + editorFreeTextColor.value = value; + break; + case AnnotationEditorParamsType.INK_COLOR: + updateInkColor(value); + break; + case AnnotationEditorParamsType.INK_THICKNESS: + editorInkThickness.value = value; + break; + case AnnotationEditorParamsType.INK_OPACITY: + updateInkOpacity(value); + break; + case AnnotationEditorParamsType.HIGHLIGHT_COLOR: + eventBus.dispatch("mainhighlightcolorpickerupdatecolor", { + source: this, + value, + }); + break; + case AnnotationEditorParamsType.HIGHLIGHT_THICKNESS: + editorFreeHighlightThickness.value = value; + break; + case AnnotationEditorParamsType.HIGHLIGHT_FREE: + editorFreeHighlightThickness.disabled = !value; + break; + case AnnotationEditorParamsType.HIGHLIGHT_SHOW_ALL: + editorHighlightShowAll.setAttribute("aria-pressed", value); + break; + } } - } - }); + }, + internalOpt + ); } } diff --git a/web/annotation_layer_builder.js b/web/annotation_layer_builder.js index 2a30043ee..963d006a9 100644 --- a/web/annotation_layer_builder.js +++ b/web/annotation_layer_builder.js @@ -35,6 +35,7 @@ import { setLayerDimensions, Util, } from "pdfjs-lib"; +import { internalOpt } from "./internal_evt.js"; import { PresentationModeState } from "./ui_utils.js"; /** @@ -75,7 +76,7 @@ class AnnotationLayerBuilder { #onAppend = null; - #eventAbortController = null; + #eventAC = null; #linksInjected = false; @@ -190,15 +191,15 @@ class AnnotationLayerBuilder { if (this.linkService.isInPresentationMode) { this.#updatePresentationModeState(PresentationModeState.FULLSCREEN); } - if (!this.#eventAbortController) { - this.#eventAbortController = new AbortController(); + if (!this.#eventAC) { + this.#eventAC = new AbortController(); - this._eventBus?._on( + this._eventBus?.on( "presentationmodechanged", evt => { this.#updatePresentationModeState(evt.state); }, - { signal: this.#eventAbortController.signal } + { signal: this.#eventAC.signal, ...internalOpt } ); } } @@ -221,8 +222,8 @@ class AnnotationLayerBuilder { cancel() { this._cancelled = true; - this.#eventAbortController?.abort(); - this.#eventAbortController = null; + this.#eventAC?.abort(); + this.#eventAC = null; } hide(internal = false) { diff --git a/web/app.js b/web/app.js index f45bb5c57..b883ed0da 100644 --- a/web/app.js +++ b/web/app.js @@ -72,6 +72,7 @@ import { CaretBrowsingMode } from "./caret_browsing.js"; import { CommentManager } from "./comment_manager.js"; import { DownloadManager } from "web-download_manager"; import { EditorUndoBar } from "./editor_undo_bar.js"; +import { internalOpt } from "./internal_evt.js"; import { OverlayManager } from "./overlay_manager.js"; import { PasswordPrompt } from "./password_prompt.js"; import { PDFAttachmentViewer } from "web-pdf_attachment_viewer"; @@ -171,8 +172,8 @@ const PDFViewerApplication = { baseUrl: "", mlManager: null, _downloadUrl: "", - _eventBusAbortController: null, - _windowAbortController: null, + _eventBusAC: null, + _windowAC: null, _globalAbortController: new AbortController(), documentInfo: null, metadata: null, @@ -1681,7 +1682,10 @@ const PDFViewerApplication = { // It should be *extremely* rare for metadata to not have been resolved // when this code runs, but ensure that we handle that case here. await new Promise(resolve => { - this.eventBus._on("metadataloaded", resolve, { once: true }); + this.eventBus.on("metadataloaded", resolve, { + once: true, + ...internalOpt, + }); }); if (pdfDocument !== this.pdfDocument) { return null; // The document was closed while the metadata resolved. @@ -1694,7 +1698,10 @@ const PDFViewerApplication = { // Hence we'll simply have to trust that the `contentLength` (as provided // by the server), when it exists, is accurate enough here. await new Promise(resolve => { - this.eventBus._on("documentloaded", resolve, { once: true }); + this.eventBus.on("documentloaded", resolve, { + once: true, + ...internalOpt, + }); }); if (pdfDocument !== this.pdfDocument) { return null; // The document was closed while the downloadInfo resolved. @@ -2095,11 +2102,11 @@ const PDFViewerApplication = { }, bindEvents() { - if (this._eventBusAbortController) { + if (this._eventBusAC) { return; } - const ac = (this._eventBusAbortController = new AbortController()); - const opts = { signal: ac.signal }; + const ac = (this._eventBusAC = new AbortController()); + const opts = { signal: ac.signal, ...internalOpt }; const { eventBus, @@ -2109,109 +2116,109 @@ const PDFViewerApplication = { preferences, } = this; - eventBus._on("resize", onResize.bind(this), opts); - eventBus._on("hashchange", onHashchange.bind(this), opts); - eventBus._on("beforeprint", this.beforePrint.bind(this), opts); - eventBus._on("afterprint", this.afterPrint.bind(this), opts); - eventBus._on("pagerender", onPageRender.bind(this), opts); - eventBus._on("pagerendered", onPageRendered.bind(this), opts); - eventBus._on("updateviewarea", onUpdateViewarea.bind(this), opts); - eventBus._on("pagechanging", onPageChanging.bind(this), opts); - eventBus._on("scalechanging", onScaleChanging.bind(this), opts); - eventBus._on("rotationchanging", onRotationChanging.bind(this), opts); - eventBus._on("sidebarviewchanged", onSidebarViewChanged.bind(this), opts); - eventBus._on("pagemode", onPageMode.bind(this), opts); - eventBus._on("namedaction", onNamedAction.bind(this), opts); - eventBus._on( + eventBus.on("resize", onResize.bind(this), opts); + eventBus.on("hashchange", onHashchange.bind(this), opts); + eventBus.on("beforeprint", this.beforePrint.bind(this), opts); + eventBus.on("afterprint", this.afterPrint.bind(this), opts); + eventBus.on("pagerender", onPageRender.bind(this), opts); + eventBus.on("pagerendered", onPageRendered.bind(this), opts); + eventBus.on("updateviewarea", onUpdateViewarea.bind(this), opts); + eventBus.on("pagechanging", onPageChanging.bind(this), opts); + eventBus.on("scalechanging", onScaleChanging.bind(this), opts); + eventBus.on("rotationchanging", onRotationChanging.bind(this), opts); + eventBus.on("sidebarviewchanged", onSidebarViewChanged.bind(this), opts); + eventBus.on("pagemode", onPageMode.bind(this), opts); + eventBus.on("namedaction", onNamedAction.bind(this), opts); + eventBus.on( "presentationmodechanged", evt => (pdfViewer.presentationModeState = evt.state), opts ); - eventBus._on( + eventBus.on( "presentationmode", this.requestPresentationMode.bind(this), opts ); - eventBus._on( + eventBus.on( "switchannotationeditormode", evt => (pdfViewer.annotationEditorMode = evt), opts ); - eventBus._on("print", this.triggerPrinting.bind(this), opts); - eventBus._on("download", this.downloadOrSave.bind(this), opts); - eventBus._on("firstpage", () => (this.page = 1), opts); - eventBus._on("lastpage", () => (this.page = this.pagesCount), opts); - eventBus._on("nextpage", () => pdfViewer.nextPage(), opts); - eventBus._on("previouspage", () => pdfViewer.previousPage(), opts); - eventBus._on("zoomin", this.zoomIn.bind(this), opts); - eventBus._on("zoomout", this.zoomOut.bind(this), opts); - eventBus._on("zoomreset", this.zoomReset.bind(this), opts); - eventBus._on("pagenumberchanged", onPageNumberChanged.bind(this), opts); - eventBus._on( + eventBus.on("print", this.triggerPrinting.bind(this), opts); + eventBus.on("download", this.downloadOrSave.bind(this), opts); + eventBus.on("firstpage", () => (this.page = 1), opts); + eventBus.on("lastpage", () => (this.page = this.pagesCount), opts); + eventBus.on("nextpage", () => pdfViewer.nextPage(), opts); + eventBus.on("previouspage", () => pdfViewer.previousPage(), opts); + eventBus.on("zoomin", this.zoomIn.bind(this), opts); + eventBus.on("zoomout", this.zoomOut.bind(this), opts); + eventBus.on("zoomreset", this.zoomReset.bind(this), opts); + eventBus.on("pagenumberchanged", onPageNumberChanged.bind(this), opts); + eventBus.on( "scalechanged", evt => (pdfViewer.currentScaleValue = evt.value), opts ); - eventBus._on("rotatecw", this.rotatePages.bind(this, 90), opts); - eventBus._on("rotateccw", this.rotatePages.bind(this, -90), opts); - eventBus._on( + eventBus.on("rotatecw", this.rotatePages.bind(this, 90), opts); + eventBus.on("rotateccw", this.rotatePages.bind(this, -90), opts); + eventBus.on( "optionalcontentconfig", evt => (pdfViewer.optionalContentConfigPromise = evt.promise), opts ); - eventBus._on( + eventBus.on( "switchscrollmode", evt => (pdfViewer.scrollMode = evt.mode), opts ); - eventBus._on( + eventBus.on( "scrollmodechanged", onViewerModesChanged.bind(this, "scrollMode"), opts ); - eventBus._on( + eventBus.on( "switchspreadmode", evt => (pdfViewer.spreadMode = evt.mode), opts ); - eventBus._on( + eventBus.on( "spreadmodechanged", onViewerModesChanged.bind(this, "spreadMode"), opts ); - eventBus._on( + eventBus.on( "imagealttextsettings", onImageAltTextSettings.bind(this), opts ); - eventBus._on( + eventBus.on( "documentproperties", () => pdfDocumentProperties?.open(), opts ); - eventBus._on("findfromurlhash", onFindFromUrlHash.bind(this), opts); - eventBus._on( + eventBus.on("findfromurlhash", onFindFromUrlHash.bind(this), opts); + eventBus.on( "updatefindmatchescount", onUpdateFindMatchesCount.bind(this), opts ); - eventBus._on( + eventBus.on( "updatefindcontrolstate", onUpdateFindControlState.bind(this), opts ); if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) { - eventBus._on("fileinputchange", onFileInputChange.bind(this), opts); - eventBus._on("openfile", onOpenFile.bind(this), opts); + eventBus.on("fileinputchange", onFileInputChange.bind(this), opts); + eventBus.on("openfile", onOpenFile.bind(this), opts); } if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) { - eventBus._on( + eventBus.on( "editingstateschanged", evt => externalServices.updateEditorStates(evt), opts ); - eventBus._on( + eventBus.on( "reporttelemetry", evt => externalServices.reportTelemetry(evt.details), opts @@ -2221,28 +2228,28 @@ const PDFViewerApplication = { typeof PDFJSDev === "undefined" || PDFJSDev.test("TESTING || MOZCENTRAL") ) { - eventBus._on( + eventBus.on( "setpreference", evt => preferences.set(evt.name, evt.value), opts ); } - eventBus._on("pagesedited", this.onPagesEdited.bind(this), opts); - eventBus._on("saveextractedpages", this.onSavePages.bind(this), opts); - eventBus._on("saveandload", this.onSaveAndLoad.bind(this), opts); + eventBus.on("pagesedited", this.onPagesEdited.bind(this), opts); + eventBus.on("saveextractedpages", this.onSavePages.bind(this), opts); + eventBus.on("saveandload", this.onSaveAndLoad.bind(this), opts); }, bindWindowEvents() { - if (this._windowAbortController) { + if (this._windowAC) { return; } - this._windowAbortController = new AbortController(); + this._windowAC = new AbortController(); const { eventBus, appConfig: { mainContainer }, pdfViewer, - _windowAbortController: { signal }, + _windowAC: { signal }, } = this; this._touchManager = new TouchManager({ @@ -2382,13 +2389,13 @@ const PDFViewerApplication = { }, unbindEvents() { - this._eventBusAbortController?.abort(); - this._eventBusAbortController = null; + this._eventBusAC?.abort(); + this._eventBusAC = null; }, unbindWindowEvents() { - this._windowAbortController?.abort(); - this._windowAbortController = null; + this._windowAC?.abort(); + this._windowAC = null; this._touchManager = null; }, diff --git a/web/editor_undo_bar.js b/web/editor_undo_bar.js index c2d44e9e5..17beb6d1f 100644 --- a/web/editor_undo_bar.js +++ b/web/editor_undo_bar.js @@ -13,6 +13,7 @@ * limitations under the License. */ +import { internalOpt } from "./internal_evt.js"; import { noContextMenu } from "pdfjs-lib"; class EditorUndoBar { @@ -62,13 +63,14 @@ class EditorUndoBar { show(undoAction, messageData) { if (!this.#initController) { this.#initController = new AbortController(); - const opts = { signal: this.#initController.signal }; + const domOpts = { signal: this.#initController.signal }; + const evtOpts = { signal: this.#initController.signal, ...internalOpt }; const boundHide = this.hide.bind(this); - this.#container.addEventListener("contextmenu", noContextMenu, opts); - this.#closeButton.addEventListener("click", boundHide, opts); - this.#eventBus._on("beforeprint", boundHide, opts); - this.#eventBus._on("download", boundHide, opts); + this.#container.addEventListener("contextmenu", noContextMenu, domOpts); + this.#closeButton.addEventListener("click", boundHide, domOpts); + this.#eventBus.on("beforeprint", boundHide, evtOpts); + this.#eventBus.on("download", boundHide, evtOpts); } this.hide(); diff --git a/web/event_utils.js b/web/event_utils.js index ee237d377..3c4f501c5 100644 --- a/web/event_utils.js +++ b/web/event_utils.js @@ -13,6 +13,8 @@ * limitations under the License. */ +import { INTERNAL_EVT, internalOpt } from "./internal_evt.js"; + const WaitOnType = { EVENT: "event", TIMEOUT: "timeout", @@ -53,10 +55,12 @@ async function waitOnEventOrTimeout({ target, name, delay = 0 }) { resolve(type); } - const evtMethod = target instanceof EventBus ? "_on" : "addEventListener"; - target[evtMethod](name, handler.bind(null, WaitOnType.EVENT), { - signal: ac.signal, - }); + const evtMethod = target instanceof EventBus ? "on" : "addEventListener"; + const evtOpts = + target instanceof EventBus + ? { signal: ac.signal, ...internalOpt } + : { signal: ac.signal }; + target[evtMethod](name, handler.bind(null, WaitOnType.EVENT), evtOpts); const timeout = setTimeout(handler.bind(null, WaitOnType.TIMEOUT), delay); @@ -70,16 +74,39 @@ async function waitOnEventOrTimeout({ target, name, delay = 0 }) { class EventBus { #listeners = Object.create(null); + constructor() { + if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) { + // Prevent the class methods from being overridden by third-party users, + // to ensure that `INTERNAL_EVT` cannot be accessed from the outside. + Object.seal(this); + } + } + /** * @param {string} eventName * @param {function} listener * @param {Object} [options] */ on(eventName, listener, options = null) { - this._on(eventName, listener, { - external: true, - once: options?.once, - signal: options?.signal, + let rmAbort = null; + if (options?.signal instanceof AbortSignal) { + const { signal } = options; + if (signal.aborted) { + console.error("Cannot use an `aborted` signal."); + return; + } + const onAbort = () => this.off(eventName, listener); + rmAbort = () => signal.removeEventListener("abort", onAbort); + + signal.addEventListener("abort", onAbort); + } + + const eventListeners = (this.#listeners[eventName] ??= []); + eventListeners.push({ + listener, + internal: options?.internal === INTERNAL_EVT, + once: options?.once === true, + rmAbort, }); } @@ -89,71 +116,6 @@ class EventBus { * @param {Object} [options] */ off(eventName, listener, options = null) { - this._off(eventName, listener); - } - - /** - * @param {string} eventName - * @param {Object} data - */ - dispatch(eventName, data) { - const eventListeners = this.#listeners[eventName]; - if (!eventListeners || eventListeners.length === 0) { - return; - } - let externalListeners; - // Making copy of the listeners array in case if it will be modified - // during dispatch. - for (const { listener, external, once } of eventListeners.slice(0)) { - if (once) { - this._off(eventName, listener); - } - if (external) { - (externalListeners ||= []).push(listener); - continue; - } - listener(data); - } - // Dispatch any "external" listeners *after* the internal ones, to give the - // viewer components time to handle events and update their state first. - if (externalListeners) { - for (const listener of externalListeners) { - listener(data); - } - externalListeners = null; - } - } - - /** - * @ignore - */ - _on(eventName, listener, options = null) { - let rmAbort = null; - if (options?.signal instanceof AbortSignal) { - const { signal } = options; - if (signal.aborted) { - console.error("Cannot use an `aborted` signal."); - return; - } - const onAbort = () => this._off(eventName, listener); - rmAbort = () => signal.removeEventListener("abort", onAbort); - - signal.addEventListener("abort", onAbort); - } - - const eventListeners = (this.#listeners[eventName] ||= []); - eventListeners.push({ - listener, - external: options?.external === true, - once: options?.once === true, - rmAbort, - }); - } - - /** - * @ignore - */ - _off(eventName, listener, options = null) { const eventListeners = this.#listeners[eventName]; if (!eventListeners) { return; @@ -167,6 +129,37 @@ class EventBus { } } } + + /** + * @param {string} eventName + * @param {Object} data + */ + dispatch(eventName, data) { + const eventListeners = this.#listeners[eventName]; + if (!eventListeners?.length) { + return; + } + let extListeners; + // Making copy of the listeners array in case if it will be modified + // during dispatch. + for (const { listener, internal, once } of eventListeners.slice(0)) { + if (once) { + this.off(eventName, listener); + } + if (!internal) { + (extListeners ??= []).push(listener); + continue; + } + listener(data); + } + // Dispatch any "external" listeners *after* the internal ones, to give the + // viewer components time to handle events and update their state first. + if (extListeners) { + for (const listener of extListeners) { + listener(data); + } + } + } } /** diff --git a/web/firefoxcom.js b/web/firefoxcom.js index b12645574..ced482f81 100644 --- a/web/firefoxcom.js +++ b/web/firefoxcom.js @@ -19,6 +19,7 @@ import { BaseDownloadManager } from "./base_download_manager.js"; import { BaseExternalServices } from "./external_services.js"; import { BasePreferences } from "./preferences.js"; import { DEFAULT_SCALE_VALUE } from "./ui_utils.js"; +import { internalOpt } from "./internal_evt.js"; import { L10n } from "./l10n.js"; if (typeof PDFJSDev === "undefined" || !PDFJSDev.test("MOZCENTRAL")) { @@ -294,7 +295,9 @@ class MLManager { setEventBus(eventBus, abortSignal) { this.#eventBus = eventBus; this.#abortSignal = abortSignal; - eventBus._on( + + const evtOpts = { signal: abortSignal, ...internalOpt }; + eventBus.on( "enablealttextmodeldownload", ({ value }) => { if (this.enableAltTextModelDownload === value) { @@ -306,14 +309,14 @@ class MLManager { this.deleteModel("altText"); } }, - { signal: abortSignal } + evtOpts ); - eventBus._on( + eventBus.on( "enableguessalttext", ({ value }) => { this.toggleService("altText", value); }, - { signal: abortSignal } + evtOpts ); } diff --git a/web/internal_evt.js b/web/internal_evt.js new file mode 100644 index 000000000..d8f54bcce --- /dev/null +++ b/web/internal_evt.js @@ -0,0 +1,27 @@ +/* Copyright 2026 Mozilla Foundation + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Keep this file in sync with `src/shared/internal_evt.js`. + */ + +const INTERNAL_EVT = + typeof PDFJSDev !== "undefined" && !PDFJSDev.test("TESTING") + ? PDFJSDev.eval("INTERNAL_EVT") + : "internalEvent"; + +const internalOpt = Object.freeze({ internal: INTERNAL_EVT }); + +export { INTERNAL_EVT, internalOpt }; diff --git a/web/new_alt_text_manager.js b/web/new_alt_text_manager.js index ce67d8284..2b39e018b 100644 --- a/web/new_alt_text_manager.js +++ b/web/new_alt_text_manager.js @@ -13,6 +13,7 @@ * limitations under the License. */ +import { internalOpt } from "./internal_evt.js"; import { noContextMenu } from "pdfjs-lib"; class NewAltTextManager { @@ -165,9 +166,13 @@ class NewAltTextManager { } }); - eventBus._on("enableguessalttext", ({ value }) => { - this.#toggleGuessAltText(value, /* isInitial = */ false); - }); + eventBus.on( + "enableguessalttext", + ({ value }) => { + this.#toggleGuessAltText(value, /* isInitial = */ false); + }, + internalOpt + ); this.#overlayManager.register(dialog); @@ -345,7 +350,7 @@ class NewAltTextManager { } // We're done, remove the listener and hide the download model progress. - this.#eventBus._off("loadaiengineprogress", callback); + this.#eventBus.off("loadaiengineprogress", callback); this.#downloadModel.classList.toggle("hidden", true); this.#toggleAI(true); @@ -361,7 +366,7 @@ class NewAltTextManager { /* isInitial = */ true ); }; - this.#eventBus._on("loadaiengineprogress", callback); + this.#eventBus.on("loadaiengineprogress", callback, internalOpt); } async editAltText(uiManager, editor, firstTime) { @@ -611,13 +616,17 @@ class ImageAltTextSettings { }); }); - eventBus._on("enablealttextmodeldownload", ({ value }) => { - if (value) { - this.#download(false); - } else { - this.#delete(false); - } - }); + eventBus.on( + "enablealttextmodeldownload", + ({ value }) => { + if (value) { + this.#download(false); + } else { + this.#delete(false); + } + }, + internalOpt + ); this.#overlayManager.register(dialog); } diff --git a/web/pdf_attachment_viewer.js b/web/pdf_attachment_viewer.js index 6d36e4a15..f515b75c2 100644 --- a/web/pdf_attachment_viewer.js +++ b/web/pdf_attachment_viewer.js @@ -18,6 +18,7 @@ /** @typedef {import("./download_manager.js").DownloadManager} DownloadManager */ import { BaseTreeViewer } from "./base_tree_viewer.js"; +import { internalOpt } from "./internal_evt.js"; import { waitOnEventOrTimeout } from "./event_utils.js"; /** @@ -41,9 +42,10 @@ class PDFAttachmentViewer extends BaseTreeViewer { super(options); this.downloadManager = options.downloadManager; - this.eventBus._on( + this.eventBus.on( "fileattachmentannotation", - this.#appendAttachment.bind(this) + this.#appendAttachment.bind(this), + internalOpt ); } diff --git a/web/pdf_cursor_tools.js b/web/pdf_cursor_tools.js index ad91c0719..b3f76ab1d 100644 --- a/web/pdf_cursor_tools.js +++ b/web/pdf_cursor_tools.js @@ -18,6 +18,7 @@ import { AnnotationEditorType, shadow } from "pdfjs-lib"; import { CursorTool, PresentationModeState } from "./ui_utils.js"; import { GrabToPan } from "./grab_to_pan.js"; +import { internalOpt } from "./internal_evt.js"; /** * @typedef {Object} PDFCursorToolsOptions @@ -120,16 +121,22 @@ class PDFCursorTools { } #addEventListeners() { - this.eventBus._on("switchcursortool", evt => { - if (!evt.reset) { - this.switchTool(evt.tool); - } else if (this.#prevActive !== null) { - annotationEditorMode = AnnotationEditorType.NONE; - presentationModeState = PresentationModeState.NORMAL; + const { eventBus } = this; - enableActive(); - } - }); + eventBus.on( + "switchcursortool", + evt => { + if (!evt.reset) { + this.switchTool(evt.tool); + } else if (this.#prevActive !== null) { + annotationEditorMode = AnnotationEditorType.NONE; + presentationModeState = PresentationModeState.NORMAL; + + enableActive(); + } + }, + internalOpt + ); let annotationEditorMode = AnnotationEditorType.NONE, presentationModeState = PresentationModeState.NORMAL; @@ -149,25 +156,33 @@ class PDFCursorTools { } }; - this.eventBus._on("annotationeditormodechanged", ({ mode }) => { - annotationEditorMode = mode; + eventBus.on( + "annotationeditormodechanged", + ({ mode }) => { + annotationEditorMode = mode; - if (mode === AnnotationEditorType.NONE) { - enableActive(); - } else { - disableActive(); - } - }); + if (mode === AnnotationEditorType.NONE) { + enableActive(); + } else { + disableActive(); + } + }, + internalOpt + ); - this.eventBus._on("presentationmodechanged", ({ state }) => { - presentationModeState = state; + eventBus.on( + "presentationmodechanged", + ({ state }) => { + presentationModeState = state; - if (state === PresentationModeState.NORMAL) { - enableActive(); - } else if (state === PresentationModeState.FULLSCREEN) { - disableActive(); - } - }); + if (state === PresentationModeState.NORMAL) { + enableActive(); + } else if (state === PresentationModeState.FULLSCREEN) { + disableActive(); + } + }, + internalOpt + ); } /** diff --git a/web/pdf_document_properties.js b/web/pdf_document_properties.js index 98327e8bf..1914646d9 100644 --- a/web/pdf_document_properties.js +++ b/web/pdf_document_properties.js @@ -19,6 +19,7 @@ /** @typedef {import("../src/display/api.js").PDFDocumentProxy} PDFDocumentProxy */ import { getPageSizeInches, isPortraitOrientation } from "./ui_utils.js"; +import { internalOpt } from "./internal_evt.js"; import { PDFDateString } from "pdfjs-lib"; // See https://en.wikibooks.org/wiki/Lentis/Conversion_to_the_Metric_Standard_in_the_United_States @@ -82,12 +83,20 @@ class PDFDocumentProperties { this.overlayManager.register(this.dialog); - eventBus._on("pagechanging", evt => { - this._currentPageNumber = evt.pageNumber; - }); - eventBus._on("rotationchanging", evt => { - this._pagesRotation = evt.pagesRotation; - }); + eventBus.on( + "pagechanging", + evt => { + this._currentPageNumber = evt.pageNumber; + }, + internalOpt + ); + eventBus.on( + "rotationchanging", + evt => { + this._pagesRotation = evt.pagesRotation; + }, + internalOpt + ); } /** diff --git a/web/pdf_find_controller.js b/web/pdf_find_controller.js index 8582d7a84..5670cbdc6 100644 --- a/web/pdf_find_controller.js +++ b/web/pdf_find_controller.js @@ -19,6 +19,7 @@ import { getCharacterType, getNormalizeWithNFKC } from "./pdf_find_utils.js"; import { binarySearchFirstItem } from "./ui_utils.js"; +import { internalOpt } from "./internal_evt.js"; const FindState = { FOUND: 0, @@ -450,9 +451,9 @@ class PDFFindController { this.onIsPageVisible = null; this.#reset(); - eventBus._on("find", this.#onFind.bind(this)); - eventBus._on("findbarclose", this.#onFindBarClose.bind(this)); - eventBus._on("pagesedited", this.#onPagesEdited.bind(this)); + eventBus.on("find", this.#onFind.bind(this), internalOpt); + eventBus.on("findbarclose", this.#onFindBarClose.bind(this), internalOpt); + eventBus.on("pagesedited", this.#onPagesEdited.bind(this), internalOpt); } get highlightMatches() { diff --git a/web/pdf_history.js b/web/pdf_history.js index c4eaab841..f9ffac6fc 100644 --- a/web/pdf_history.js +++ b/web/pdf_history.js @@ -17,6 +17,7 @@ /** @typedef {import("./pdf_link_service.js").PDFLinkService} PDFLinkService */ import { isValidRotation, parseQueryString } from "./ui_utils.js"; +import { internalOpt } from "./internal_evt.js"; import { updateUrlHash } from "pdfjs-lib"; import { waitOnEventOrTimeout } from "./event_utils.js"; @@ -54,7 +55,7 @@ function getCurrentHash() { } class PDFHistory { - #eventAbortController = null; + #eventAC = null; /** * @param {PDFHistoryOptions} options @@ -69,17 +70,21 @@ class PDFHistory { // Ensure that we don't miss a "pagesinit" event, // by registering the listener immediately. - this.eventBus._on("pagesinit", () => { - this._isPagesLoaded = false; + this.eventBus.on( + "pagesinit", + () => { + this._isPagesLoaded = false; - this.eventBus._on( - "pagesloaded", - evt => { - this._isPagesLoaded = !!evt.pagesCount; - }, - { once: true } - ); - }); + this.eventBus.on( + "pagesloaded", + evt => { + this._isPagesLoaded = !!evt.pagesCount; + }, + { once: true, ...internalOpt } + ); + }, + internalOpt + ); } /** @@ -671,22 +676,23 @@ class PDFHistory { } #bindEvents() { - if (this.#eventAbortController) { + if (this.#eventAC) { return; // The event listeners were already added. } - this.#eventAbortController = new AbortController(); - const { signal } = this.#eventAbortController; + this.#eventAC = new AbortController(); + const { signal } = this.#eventAC; - this.eventBus._on("updateviewarea", this.#updateViewarea.bind(this), { + this.eventBus.on("updateviewarea", this.#updateViewarea.bind(this), { signal, + ...internalOpt, }); window.addEventListener("popstate", this.#popState.bind(this), { signal }); window.addEventListener("pagehide", this.#pageHide.bind(this), { signal }); } #unbindEvents() { - this.#eventAbortController?.abort(); - this.#eventAbortController = null; + this.#eventAC?.abort(); + this.#eventAC = null; } } diff --git a/web/pdf_layer_viewer.js b/web/pdf_layer_viewer.js index e8290b4c0..badcdf072 100644 --- a/web/pdf_layer_viewer.js +++ b/web/pdf_layer_viewer.js @@ -20,6 +20,7 @@ /** @typedef {import("../src/display/api.js").PDFDocumentProxy} PDFDocumentProxy */ import { BaseTreeViewer } from "./base_tree_viewer.js"; +import { internalOpt } from "./internal_evt.js"; /** * @typedef {Object} PDFLayerViewerOptions @@ -38,13 +39,26 @@ class PDFLayerViewer extends BaseTreeViewer { constructor(options) { super(options); - this.eventBus._on("optionalcontentconfigchanged", evt => { - this.#updateLayers(evt.promise); - }); - this.eventBus._on("resetlayers", () => { - this.#updateLayers(); - }); - this.eventBus._on("togglelayerstree", this._toggleAllTreeItems.bind(this)); + const { eventBus } = this; + eventBus.on( + "optionalcontentconfigchanged", + evt => { + this.#updateLayers(evt.promise); + }, + internalOpt + ); + eventBus.on( + "resetlayers", + () => { + this.#updateLayers(); + }, + internalOpt + ); + eventBus.on( + "togglelayerstree", + this._toggleAllTreeItems.bind(this), + internalOpt + ); } reset() { diff --git a/web/pdf_link_service.js b/web/pdf_link_service.js index 2b592d62b..4db85534b 100644 --- a/web/pdf_link_service.js +++ b/web/pdf_link_service.js @@ -15,6 +15,7 @@ /** @typedef {import("./event_utils").EventBus} EventBus */ +import { internalOpt } from "./internal_evt.js"; import { isValidExplicitDest } from "pdfjs-lib"; import { parseQueryString } from "./ui_utils.js"; @@ -192,7 +193,7 @@ class PDFLinkService { }); const ac = new AbortController(); - this.eventBus._on( + this.eventBus.on( "textlayerrendered", evt => { if (evt.pageNumber === pageNumber) { @@ -200,7 +201,7 @@ class PDFLinkService { ac.abort(); } }, - { signal: ac.signal } + { signal: ac.signal, ...internalOpt } ); } diff --git a/web/pdf_outline_viewer.js b/web/pdf_outline_viewer.js index 011e2c8d8..9b2b6c8bb 100644 --- a/web/pdf_outline_viewer.js +++ b/web/pdf_outline_viewer.js @@ -20,6 +20,7 @@ /** @typedef {import("../src/display/api.js").PDFDocumentProxy} PDFDocumentProxy */ import { BaseTreeViewer } from "./base_tree_viewer.js"; +import { internalOpt } from "./internal_evt.js"; import { SidebarView } from "./ui_utils.js"; /** @@ -45,27 +46,45 @@ class PDFOutlineViewer extends BaseTreeViewer { this.linkService = options.linkService; this.downloadManager = options.downloadManager; - this.eventBus._on("toggleoutlinetree", this._toggleAllTreeItems.bind(this)); - this.eventBus._on( + const { eventBus } = this; + eventBus.on( + "toggleoutlinetree", + this._toggleAllTreeItems.bind(this), + internalOpt + ); + eventBus.on( "currentoutlineitem", - this._currentOutlineItem.bind(this) + this._currentOutlineItem.bind(this), + internalOpt ); - this.eventBus._on("pagechanging", evt => { - this._currentPageNumber = evt.pageNumber; - }); - this.eventBus._on("pagesloaded", evt => { - this._isPagesLoaded = !!evt.pagesCount; + eventBus.on( + "pagechanging", + evt => { + this._currentPageNumber = evt.pageNumber; + }, + internalOpt + ); + eventBus.on( + "pagesloaded", + evt => { + this._isPagesLoaded = !!evt.pagesCount; - // If the capability is still pending, see the `_dispatchEvent`-method, - // we know that the `currentOutlineItem`-button can be enabled here. - this._currentOutlineItemCapability?.resolve( - /* enabled = */ this._isPagesLoaded - ); - }); - this.eventBus._on("sidebarviewchanged", evt => { - this._sidebarView = evt.view; - }); + // If the capability is still pending, see the `_dispatchEvent`-method, + // we know that the `currentOutlineItem`-button can be enabled here. + this._currentOutlineItemCapability?.resolve( + /* enabled = */ this._isPagesLoaded + ); + }, + internalOpt + ); + eventBus.on( + "sidebarviewchanged", + evt => { + this._sidebarView = evt.view; + }, + internalOpt + ); } reset() { diff --git a/web/pdf_presentation_mode.js b/web/pdf_presentation_mode.js index 004af1af1..4ee40ffa2 100644 --- a/web/pdf_presentation_mode.js +++ b/web/pdf_presentation_mode.js @@ -49,9 +49,9 @@ class PDFPresentationMode { #args = null; - #fullscreenChangeAbortController = null; + #fullscreenChangeAC = null; - #windowAbortController = null; + #windowAC = null; /** * @param {PDFPresentationModeOptions} options @@ -350,11 +350,11 @@ class PDFPresentationMode { } #addWindowListeners() { - if (this.#windowAbortController) { + if (this.#windowAC) { return; } - this.#windowAbortController = new AbortController(); - const { signal } = this.#windowAbortController; + this.#windowAC = new AbortController(); + const { signal } = this.#windowAC; const touchSwipeBind = this.#touchSwipe.bind(this); @@ -380,15 +380,15 @@ class PDFPresentationMode { } #removeWindowListeners() { - this.#windowAbortController?.abort(); - this.#windowAbortController = null; + this.#windowAC?.abort(); + this.#windowAC = null; } #addFullscreenChangeListeners() { - if (this.#fullscreenChangeAbortController) { + if (this.#fullscreenChangeAC) { return; } - this.#fullscreenChangeAbortController = new AbortController(); + this.#fullscreenChangeAC = new AbortController(); window.addEventListener( "fullscreenchange", @@ -399,13 +399,13 @@ class PDFPresentationMode { this.#exit(); } }, - { signal: this.#fullscreenChangeAbortController.signal } + { signal: this.#fullscreenChangeAC.signal } ); } #removeFullscreenChangeListeners() { - this.#fullscreenChangeAbortController?.abort(); - this.#fullscreenChangeAbortController = null; + this.#fullscreenChangeAC?.abort(); + this.#fullscreenChangeAC = null; } } diff --git a/web/pdf_scripting_manager.js b/web/pdf_scripting_manager.js index 4583285b5..9da66880d 100644 --- a/web/pdf_scripting_manager.js +++ b/web/pdf_scripting_manager.js @@ -16,6 +16,7 @@ /** @typedef {import("./event_utils").EventBus} EventBus */ import { apiPageLayoutToViewerModes } from "./ui_utils.js"; +import { internalOpt } from "./internal_evt.js"; import { RenderingStates } from "./renderable_view.js"; import { shadow } from "pdfjs-lib"; @@ -38,7 +39,7 @@ class PDFScriptingManager { #docProperties = null; - #eventAbortController = null; + #eventAC = null; #eventBus = null; @@ -113,27 +114,27 @@ class PDFScriptingManager { } const eventBus = this.#eventBus; - this.#eventAbortController = new AbortController(); - const { signal } = this.#eventAbortController; + this.#eventAC = new AbortController(); + const evtOpts = { signal: this.#eventAC.signal, ...internalOpt }; - eventBus._on( + eventBus.on( "updatefromsandbox", event => { if (event?.source === window) { this.#updateFromSandbox(event.detail); } }, - { signal } + evtOpts ); - eventBus._on( + eventBus.on( "dispatcheventinsandbox", event => { this.#scripting?.dispatchEventInSandbox(event.detail); }, - { signal } + evtOpts ); - eventBus._on( + eventBus.on( "pagechanging", ({ pageNumber, previous }) => { if (pageNumber === previous) { @@ -142,9 +143,9 @@ class PDFScriptingManager { this.#dispatchPageClose(previous); this.#dispatchPageOpen(pageNumber); }, - { signal } + evtOpts ); - eventBus._on( + eventBus.on( "pagerendered", ({ pageNumber }) => { if (!this._pageOpenPending.has(pageNumber)) { @@ -155,9 +156,9 @@ class PDFScriptingManager { } this.#dispatchPageOpen(pageNumber); }, - { signal } + evtOpts ); - eventBus._on( + eventBus.on( "pagesdestroy", async () => { await this.#dispatchPageClose(this.#pdfViewer.currentPageNumber); @@ -169,7 +170,7 @@ class PDFScriptingManager { this.#closeCapability?.resolve(); }, - { signal } + evtOpts ); try { @@ -482,8 +483,8 @@ class PDFScriptingManager { this.#willPrintCapability?.reject(new Error("Scripting destroyed.")); this.#willPrintCapability = null; - this.#eventAbortController?.abort(); - this.#eventAbortController = null; + this.#eventAC?.abort(); + this.#eventAC = null; this._pageOpenPending.clear(); this._visitedPages.clear(); diff --git a/web/pdf_text_extractor.js b/web/pdf_text_extractor.js index 4df7bf184..a9b987fb0 100644 --- a/web/pdf_text_extractor.js +++ b/web/pdf_text_extractor.js @@ -13,6 +13,8 @@ * limitations under the License. */ +import { internalOpt } from "./internal_evt.js"; + /** * This class manages the interaction of extracting the text content of the page * and passing it back to the external service. @@ -29,15 +31,23 @@ class PdfTextExtractor { constructor(externalServices, pdfViewer, eventBus) { this.#externalServices = externalServices; - eventBus._on("pagesinit", () => { - this.#capability.resolve(pdfViewer); - }); - eventBus._on("pagesdestroy", () => { - this.#capability.reject(new Error("pagesdestroy")); - this.#textPromise = null; + eventBus.on( + "pagesinit", + () => { + this.#capability.resolve(pdfViewer); + }, + internalOpt + ); + eventBus.on( + "pagesdestroy", + () => { + this.#capability.reject(new Error("pagesdestroy")); + this.#textPromise = null; - this.#capability = Promise.withResolvers(); - }); + this.#capability = Promise.withResolvers(); + }, + internalOpt + ); window.addEventListener("requestTextContent", ({ detail }) => { this.extractTextContent(detail.requestId); diff --git a/web/pdf_thumbnail_viewer.js b/web/pdf_thumbnail_viewer.js index 1332b6233..d4315266d 100644 --- a/web/pdf_thumbnail_viewer.js +++ b/web/pdf_thumbnail_viewer.js @@ -27,6 +27,7 @@ import { watchScroll, } from "./ui_utils.js"; import { MathClamp, noContextMenu, stopEvent } from "pdfjs-lib"; +import { internalOpt } from "./internal_evt.js"; import { Menu } from "./menu.js"; import { PDFThumbnailView } from "./pdf_thumbnail_view.js"; import { RenderingStates } from "./renderable_view.js"; @@ -383,7 +384,7 @@ class PDFThumbnailViewer { ? this.getStructuralChanges() : [{ document: null }]; data.push(...entries); - this.eventBus._on( + this.eventBus.on( "pagesloaded", () => { // Clear any pre-merge selection: thumbnails are rebuilt fresh @@ -406,7 +407,7 @@ class PDFThumbnailViewer { this.#updateCurrentPage(insertAfter + 2, /* force = */ true); } }, - { once: true } + { once: true, ...internalOpt } ); this.#reportTelemetry({ action: "merge" }); this.eventBus.dispatch("saveandload", { diff --git a/web/pdf_viewer.js b/web/pdf_viewer.js index 11aacbb61..7e557f019 100644 --- a/web/pdf_viewer.js +++ b/web/pdf_viewer.js @@ -66,6 +66,7 @@ import { watchScroll, } from "./ui_utils.js"; import { GenericL10n } from "web-null_l10n"; +import { internalOpt } from "./internal_evt.js"; import { PDFPageView } from "./pdf_page_view.js"; import { PDFRenderingQueue } from "./pdf_rendering_queue.js"; import { RenderingStates } from "./renderable_view.js"; @@ -258,7 +259,7 @@ class PDFViewer { #abortSignal = null; - #eventAbortController = null; + #eventAC = null; #minDurationToUpdateCanvas = 0; @@ -420,12 +421,16 @@ class PDFViewer { // Trigger API-cleanup, once thumbnail rendering has finished, // if the relevant pageView is *not* cached in the buffer. - this.eventBus._on("thumbnailrendered", ({ pageNumber, pdfPage }) => { - const pageView = this._pages[pageNumber - 1]; - if (!this.#buffer.has(pageView)) { - pdfPage?.cleanup(); - } - }); + this.eventBus.on( + "thumbnailrendered", + ({ pageNumber, pdfPage }) => { + const pageView = this._pages[pageNumber - 1]; + if (!this.#buffer.has(pageView)) { + pdfPage?.cleanup(); + } + }, + internalOpt + ); if ( (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) && @@ -937,8 +942,9 @@ class PDFViewer { const { eventBus, pageColors, viewer } = this; - this.#eventAbortController = new AbortController(); - const { signal } = this.#eventAbortController; + this.#eventAC = new AbortController(); + const { signal } = this.#eventAC; + const evtOpts = { signal, ...internalOpt }; // Given that browsers don't handle huge amounts of DOM-elements very well, // enforce usage of PAGE-scrolling when loading *very* long/large documents. @@ -968,7 +974,7 @@ class PDFViewer { // evicted from the buffer and destroyed even if we pause its rendering. this.#buffer.push(pageView); }; - eventBus._on("pagerender", onBeforeDraw, { signal }); + eventBus.on("pagerender", onBeforeDraw, evtOpts); const onAfterDraw = evt => { if (evt.cssTransform || evt.isDetailView) { @@ -976,9 +982,9 @@ class PDFViewer { } this._onePageRenderedCapability.resolve({ timestamp: evt.timestamp }); - eventBus._off("pagerendered", onAfterDraw); // Remove immediately. + eventBus.off("pagerendered", onAfterDraw); // Remove immediately. }; - eventBus._on("pagerendered", onAfterDraw, { signal }); + eventBus.on("pagerendered", onAfterDraw, evtOpts); // Fetch a single page so we can get a viewport that will be the default // viewport for all pages @@ -1120,7 +1126,7 @@ class PDFViewer { this._updateSpreadMode(); } - eventBus._on( + eventBus.on( "annotationeditorlayerrendered", evt => { if (this.#annotationEditorUIManager) { @@ -1131,7 +1137,7 @@ class PDFViewer { }); } }, - { once: true, signal } + { once: true, signal, ...internalOpt } ); // Fetch all the pages since the viewport is needed before printing @@ -1368,8 +1374,8 @@ class PDFViewer { pages: [], }; - this.#eventAbortController?.abort(); - this.#eventAbortController = null; + this.#eventAC?.abort(); + this.#eventAC = null; // Remove the pages from the DOM... this.viewer.textContent = ""; @@ -2688,11 +2694,11 @@ class PDFViewer { this.#cleanupSwitchAnnotationEditorMode(); this.#switchAnnotationEditorModeAC = new AbortController(); const signal = AbortSignal.any([ - this.#eventAbortController.signal, + this.#eventAC.signal, this.#switchAnnotationEditorModeAC.signal, ]); - eventBus._on( + eventBus.on( "pagerendered", ({ pageNumber }) => { idsToRefresh.delete(pageNumber); @@ -2703,7 +2709,7 @@ class PDFViewer { ); } }, - { signal } + { signal, ...internalOpt } ); return; } diff --git a/web/secondary_toolbar.js b/web/secondary_toolbar.js index 919b17b21..c2048f7ea 100644 --- a/web/secondary_toolbar.js +++ b/web/secondary_toolbar.js @@ -22,6 +22,7 @@ import { toggleCheckedBtn, toggleExpandedBtn, } from "./ui_utils.js"; +import { internalOpt } from "./internal_evt.js"; import { PagesCountLimit } from "./pdf_viewer.js"; /** @@ -235,9 +236,21 @@ class SecondaryToolbar { }); } - eventBus._on("cursortoolchanged", this.#cursorToolChanged.bind(this)); - eventBus._on("scrollmodechanged", this.#scrollModeChanged.bind(this)); - eventBus._on("spreadmodechanged", this.#spreadModeChanged.bind(this)); + eventBus.on( + "cursortoolchanged", + this.#cursorToolChanged.bind(this), + internalOpt + ); + eventBus.on( + "scrollmodechanged", + this.#scrollModeChanged.bind(this), + internalOpt + ); + eventBus.on( + "spreadmodechanged", + this.#spreadModeChanged.bind(this), + internalOpt + ); } #cursorToolChanged({ tool, disabled }) { diff --git a/web/signature_manager.js b/web/signature_manager.js index 749d1a48d..7f903e63f 100644 --- a/web/signature_manager.js +++ b/web/signature_manager.js @@ -21,6 +21,7 @@ import { stopEvent, SupportedImageMimeTypes, } from "pdfjs-lib"; +import { internalOpt } from "./internal_evt.js"; // Default height of the added signature in page coordinates. const DEFAULT_HEIGHT_IN_PAGE = 40; @@ -229,7 +230,11 @@ class SignatureManager { this.#initTabButtons(typeButton, drawButton, imageButton, panels); imagePicker.accept = SupportedImageMimeTypes.join(","); - eventBus._on("storedsignatureschanged", this.#signaturesChanged.bind(this)); + eventBus.on( + "storedsignatureschanged", + this.#signaturesChanged.bind(this), + internalOpt + ); overlayManager.register(dialog); } diff --git a/web/text_highlighter.js b/web/text_highlighter.js index 90a9e898f..9de2eeb06 100644 --- a/web/text_highlighter.js +++ b/web/text_highlighter.js @@ -13,6 +13,8 @@ * limitations under the License. */ +import { internalOpt } from "./internal_evt.js"; + /** @typedef {import("./event_utils").EventBus} EventBus */ // eslint-disable-next-line max-len /** @typedef {import("./pdf_find_controller").PDFFindController} PDFFindController */ @@ -29,7 +31,7 @@ * either the text layer or XFA layer depending on the type of document. */ class TextHighlighter { - #eventAbortController = null; + #eventAC = null; /** * @param {TextHighlighterOptions} options @@ -71,17 +73,17 @@ class TextHighlighter { } this.enabled = true; - if (!this.#eventAbortController) { - this.#eventAbortController = new AbortController(); + if (!this.#eventAC) { + this.#eventAC = new AbortController(); - this.eventBus._on( + this.eventBus.on( "updatetextlayermatches", evt => { if (evt.pageIndex === this.pageIdx || evt.pageIndex === -1) { this._updateMatches(); } }, - { signal: this.#eventAbortController.signal } + { signal: this.#eventAC.signal, ...internalOpt } ); } this._updateMatches(); @@ -93,8 +95,8 @@ class TextHighlighter { } this.enabled = false; - this.#eventAbortController?.abort(); - this.#eventAbortController = null; + this.#eventAC?.abort(); + this.#eventAC = null; this._updateMatches(/* reset = */ true); } diff --git a/web/text_layer_builder.js b/web/text_layer_builder.js index 53541763a..62c8e5fd3 100644 --- a/web/text_layer_builder.js +++ b/web/text_layer_builder.js @@ -61,7 +61,7 @@ class TextLayerBuilder { static #textLayers = new Map(); - static #selectionChangeAbortController = null; + static #selectionChangeAC = null; /** * @param {TextLayerBuilderOptions} options @@ -204,23 +204,20 @@ class TextLayerBuilder { this.#textLayers.delete(textLayerDiv); if (this.#textLayers.size === 0) { - this.#selectionChangeAbortController?.abort(); - this.#selectionChangeAbortController = null; + this.#selectionChangeAC?.abort(); + this.#selectionChangeAC = null; } } static #enableGlobalSelectionListener(globalAbortSignal) { - if (this.#selectionChangeAbortController) { + if (this.#selectionChangeAC) { // document-level event listeners already installed return; } - this.#selectionChangeAbortController = new AbortController(); + this.#selectionChangeAC = new AbortController(); const signal = globalAbortSignal - ? AbortSignal.any([ - this.#selectionChangeAbortController.signal, - globalAbortSignal, - ]) - : this.#selectionChangeAbortController.signal; + ? AbortSignal.any([this.#selectionChangeAC.signal, globalAbortSignal]) + : this.#selectionChangeAC.signal; const reset = (end, textLayer) => { if (typeof PDFJSDev === "undefined" || !PDFJSDev.test("MOZCENTRAL")) { diff --git a/web/toolbar.js b/web/toolbar.js index 382ad189b..66b453d28 100644 --- a/web/toolbar.js +++ b/web/toolbar.js @@ -23,6 +23,7 @@ import { MIN_SCALE, toggleExpandedBtn, } from "./ui_utils.js"; +import { internalOpt } from "./internal_evt.js"; /** * @typedef {Object} ToolbarOptions @@ -237,12 +238,16 @@ class Toolbar { value: this.value, }); }); - eventBus._on("pagesedited", ({ pagesMapper }) => { - const pagesCount = pagesMapper.pagesNumber; - if (pagesCount !== this.pagesCount) { - this.setPagesCount(pagesCount, this.hasPageLabels); - } - }); + eventBus.on( + "pagesedited", + ({ pagesMapper }) => { + const pagesCount = pagesMapper.pagesNumber; + if (pagesCount !== this.pagesCount) { + this.setPagesCount(pagesCount, this.hasPageLabels); + } + }, + internalOpt + ); scaleSelect.addEventListener("change", function () { if (this.value === "custom") { @@ -268,29 +273,46 @@ class Toolbar { // Suppress context menus for some controls. scaleSelect.oncontextmenu = noContextMenu; - eventBus._on( + eventBus.on( "annotationeditormodechanged", - this.#editorModeChanged.bind(this) + this.#editorModeChanged.bind(this), + internalOpt + ); + eventBus.on( + "showannotationeditorui", + ({ mode }) => { + switch (mode) { + case AnnotationEditorType.HIGHLIGHT: + editorHighlightButton.click(); + break; + } + }, + internalOpt + ); + eventBus.on( + "toolbardensity", + this.#updateToolbarDensity.bind(this), + internalOpt ); - eventBus._on("showannotationeditorui", ({ mode }) => { - switch (mode) { - case AnnotationEditorType.HIGHLIGHT: - editorHighlightButton.click(); - break; - } - }); - eventBus._on("toolbardensity", this.#updateToolbarDensity.bind(this)); if (editorHighlightColorPicker) { - eventBus._on("annotationeditoruimanager", ({ uiManager }) => { - const cp = (this.#colorPicker = new ColorPicker({ uiManager })); - uiManager.setMainHighlightColorPicker(cp); - editorHighlightColorPicker.append(cp.renderMainDropdown()); - }); + eventBus.on( + "annotationeditoruimanager", + ({ uiManager }) => { + const cp = (this.#colorPicker = new ColorPicker({ uiManager })); + uiManager.setMainHighlightColorPicker(cp); + editorHighlightColorPicker.append(cp.renderMainDropdown()); + }, + internalOpt + ); - eventBus._on("mainhighlightcolorpickerupdatecolor", ({ value }) => { - this.#colorPicker?.updateColor(value); - }); + eventBus.on( + "mainhighlightcolorpickerupdatecolor", + ({ value }) => { + this.#colorPicker?.updateColor(value); + }, + internalOpt + ); } } diff --git a/web/views_manager.js b/web/views_manager.js index ea9427ca5..a7cd294fe 100644 --- a/web/views_manager.js +++ b/web/views_manager.js @@ -22,6 +22,7 @@ import { toggleExpandedBtn, toggleSelectedBtn, } from "./ui_utils.js"; +import { internalOpt } from "./internal_evt.js"; import { Menu } from "./menu.js"; import { Sidebar } from "./sidebar.js"; @@ -474,38 +475,54 @@ class ViewsManager extends Sidebar { } }; - eventBus._on("outlineloaded", evt => { - onTreeLoaded(evt.outlineCount, this.outlineButton, SidebarView.OUTLINE); + eventBus.on( + "outlineloaded", + evt => { + onTreeLoaded(evt.outlineCount, this.outlineButton, SidebarView.OUTLINE); - evt.currentOutlineItemPromise.then(enabled => { - if (!this.isInitialViewSet) { - return; - } - this.viewsManagerCurrentOutlineButton.disabled = !enabled; - }); - }); + evt.currentOutlineItemPromise.then(enabled => { + if (!this.isInitialViewSet) { + return; + } + this.viewsManagerCurrentOutlineButton.disabled = !enabled; + }); + }, + internalOpt + ); - eventBus._on("attachmentsloaded", evt => { - onTreeLoaded( - evt.attachmentsCount, - this.attachmentsButton, - SidebarView.ATTACHMENTS - ); - }); + eventBus.on( + "attachmentsloaded", + evt => { + onTreeLoaded( + evt.attachmentsCount, + this.attachmentsButton, + SidebarView.ATTACHMENTS + ); + }, + internalOpt + ); - eventBus._on("layersloaded", evt => { - onTreeLoaded(evt.layersCount, this.layersButton, SidebarView.LAYERS); - }); + eventBus.on( + "layersloaded", + evt => { + onTreeLoaded(evt.layersCount, this.layersButton, SidebarView.LAYERS); + }, + internalOpt + ); // Update the thumbnailViewer, if visible, when exiting presentation mode. - eventBus._on("presentationmodechanged", evt => { - if ( - evt.state === PresentationModeState.NORMAL && - this.visibleView === SidebarView.THUMBS - ) { - this.onUpdateThumbnails(); - } - }); + eventBus.on( + "presentationmodechanged", + evt => { + if ( + evt.state === PresentationModeState.NORMAL && + this.visibleView === SidebarView.THUMBS + ) { + this.onUpdateThumbnails(); + } + }, + internalOpt + ); } onStartResizing() {