Merge pull request #17967 from Snuffleupagus/eventBus-signal

Add `signal`-support in the `EventBus`, and utilize it in the viewer (PR 17964 follow-up)
This commit is contained in:
Tim van der Meij 2024-04-23 15:55:59 +02:00 committed by GitHub
commit bda98b91cb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 246 additions and 223 deletions

View File

@ -143,6 +143,60 @@ describe("event_utils", function () {
expect(onceCount).toEqual(1); expect(onceCount).toEqual(1);
}); });
it("dispatch event to handlers with/without 'signal' option, aborted *before* dispatch", function () {
const eventBus = new EventBus();
const ac = new AbortController();
let multipleCount = 0,
noneCount = 0;
eventBus.on("test", function () {
multipleCount++;
});
eventBus.on(
"test",
function () {
noneCount++;
},
{ signal: ac.signal }
);
ac.abort();
eventBus.dispatch("test");
eventBus.dispatch("test");
eventBus.dispatch("test");
expect(multipleCount).toEqual(3);
expect(noneCount).toEqual(0);
});
it("dispatch event to handlers with/without 'signal' option, aborted *after* dispatch", function () {
const eventBus = new EventBus();
const ac = new AbortController();
let multipleCount = 0,
onceCount = 0;
eventBus.on("test", function () {
multipleCount++;
});
eventBus.on(
"test",
function () {
onceCount++;
},
{ signal: ac.signal }
);
eventBus.dispatch("test");
ac.abort();
eventBus.dispatch("test");
eventBus.dispatch("test");
expect(multipleCount).toEqual(3);
expect(onceCount).toEqual(1);
});
it("should not re-dispatch to DOM", async function () { it("should not re-dispatch to DOM", async function () {
if (isNodeJS) { if (isNodeJS) {
pending("Document is not supported in Node.js."); pending("Document is not supported in Node.js.");

View File

@ -50,7 +50,7 @@ import { PresentationModeState } from "./ui_utils.js";
class AnnotationLayerBuilder { class AnnotationLayerBuilder {
#onAppend = null; #onAppend = null;
#onPresentationModeChanged = null; #eventAbortController = null;
/** /**
* @param {AnnotationLayerBuilderOptions} options * @param {AnnotationLayerBuilderOptions} options
@ -155,13 +155,15 @@ class AnnotationLayerBuilder {
if (this.linkService.isInPresentationMode) { if (this.linkService.isInPresentationMode) {
this.#updatePresentationModeState(PresentationModeState.FULLSCREEN); this.#updatePresentationModeState(PresentationModeState.FULLSCREEN);
} }
if (!this.#onPresentationModeChanged) { if (!this.#eventAbortController) {
this.#onPresentationModeChanged = evt => { this.#eventAbortController = new AbortController();
this.#updatePresentationModeState(evt.state);
};
this._eventBus?._on( this._eventBus?._on(
"presentationmodechanged", "presentationmodechanged",
this.#onPresentationModeChanged evt => {
this.#updatePresentationModeState(evt.state);
},
{ signal: this.#eventAbortController.signal }
); );
} }
} }
@ -169,13 +171,8 @@ class AnnotationLayerBuilder {
cancel() { cancel() {
this._cancelled = true; this._cancelled = true;
if (this.#onPresentationModeChanged) { this.#eventAbortController?.abort();
this._eventBus?._off( this.#eventAbortController = null;
"presentationmodechanged",
this.#onPresentationModeChanged
);
this.#onPresentationModeChanged = null;
}
} }
hide() { hide() {

View File

@ -157,7 +157,7 @@ const PDFViewerApplication = {
url: "", url: "",
baseUrl: "", baseUrl: "",
_downloadUrl: "", _downloadUrl: "",
_boundEvents: Object.create(null), _eventBusAbortController: null,
_windowAbortController: null, _windowAbortController: null,
documentInfo: null, documentInfo: null,
metadata: null, metadata: null,
@ -1832,75 +1832,87 @@ const PDFViewerApplication = {
}, },
bindEvents() { bindEvents() {
const { eventBus, _boundEvents } = this; if (this._eventBusAbortController) {
return;
}
this._eventBusAbortController = new AbortController();
_boundEvents.beforePrint = this.beforePrint.bind(this); const {
_boundEvents.afterPrint = this.afterPrint.bind(this); eventBus,
_eventBusAbortController: { signal },
} = this;
eventBus._on("resize", webViewerResize); eventBus._on("resize", webViewerResize, { signal });
eventBus._on("hashchange", webViewerHashchange); eventBus._on("hashchange", webViewerHashchange, { signal });
eventBus._on("beforeprint", _boundEvents.beforePrint); eventBus._on("beforeprint", this.beforePrint.bind(this), { signal });
eventBus._on("afterprint", _boundEvents.afterPrint); eventBus._on("afterprint", this.afterPrint.bind(this), { signal });
eventBus._on("pagerender", webViewerPageRender); eventBus._on("pagerender", webViewerPageRender, { signal });
eventBus._on("pagerendered", webViewerPageRendered); eventBus._on("pagerendered", webViewerPageRendered, { signal });
eventBus._on("updateviewarea", webViewerUpdateViewarea); eventBus._on("updateviewarea", webViewerUpdateViewarea, { signal });
eventBus._on("pagechanging", webViewerPageChanging); eventBus._on("pagechanging", webViewerPageChanging, { signal });
eventBus._on("scalechanging", webViewerScaleChanging); eventBus._on("scalechanging", webViewerScaleChanging, { signal });
eventBus._on("rotationchanging", webViewerRotationChanging); eventBus._on("rotationchanging", webViewerRotationChanging, { signal });
eventBus._on("sidebarviewchanged", webViewerSidebarViewChanged); eventBus._on("sidebarviewchanged", webViewerSidebarViewChanged, { signal });
eventBus._on("pagemode", webViewerPageMode); eventBus._on("pagemode", webViewerPageMode, { signal });
eventBus._on("namedaction", webViewerNamedAction); eventBus._on("namedaction", webViewerNamedAction, { signal });
eventBus._on("presentationmodechanged", webViewerPresentationModeChanged); eventBus._on("presentationmodechanged", webViewerPresentationModeChanged, {
eventBus._on("presentationmode", webViewerPresentationMode); signal,
});
eventBus._on("presentationmode", webViewerPresentationMode, { signal });
eventBus._on( eventBus._on(
"switchannotationeditormode", "switchannotationeditormode",
webViewerSwitchAnnotationEditorMode webViewerSwitchAnnotationEditorMode,
{ signal }
); );
eventBus._on( eventBus._on(
"switchannotationeditorparams", "switchannotationeditorparams",
webViewerSwitchAnnotationEditorParams webViewerSwitchAnnotationEditorParams,
{ signal }
); );
eventBus._on("print", webViewerPrint); eventBus._on("print", webViewerPrint, { signal });
eventBus._on("download", webViewerDownload); eventBus._on("download", webViewerDownload, { signal });
eventBus._on("firstpage", webViewerFirstPage); eventBus._on("firstpage", webViewerFirstPage, { signal });
eventBus._on("lastpage", webViewerLastPage); eventBus._on("lastpage", webViewerLastPage, { signal });
eventBus._on("nextpage", webViewerNextPage); eventBus._on("nextpage", webViewerNextPage, { signal });
eventBus._on("previouspage", webViewerPreviousPage); eventBus._on("previouspage", webViewerPreviousPage, { signal });
eventBus._on("zoomin", webViewerZoomIn); eventBus._on("zoomin", webViewerZoomIn, { signal });
eventBus._on("zoomout", webViewerZoomOut); eventBus._on("zoomout", webViewerZoomOut, { signal });
eventBus._on("zoomreset", webViewerZoomReset); eventBus._on("zoomreset", webViewerZoomReset, { signal });
eventBus._on("pagenumberchanged", webViewerPageNumberChanged); eventBus._on("pagenumberchanged", webViewerPageNumberChanged, { signal });
eventBus._on("scalechanged", webViewerScaleChanged); eventBus._on("scalechanged", webViewerScaleChanged, { signal });
eventBus._on("rotatecw", webViewerRotateCw); eventBus._on("rotatecw", webViewerRotateCw, { signal });
eventBus._on("rotateccw", webViewerRotateCcw); eventBus._on("rotateccw", webViewerRotateCcw, { signal });
eventBus._on("optionalcontentconfig", webViewerOptionalContentConfig); eventBus._on("optionalcontentconfig", webViewerOptionalContentConfig, {
eventBus._on("switchscrollmode", webViewerSwitchScrollMode); signal,
eventBus._on("scrollmodechanged", webViewerScrollModeChanged); });
eventBus._on("switchspreadmode", webViewerSwitchSpreadMode); eventBus._on("switchscrollmode", webViewerSwitchScrollMode, { signal });
eventBus._on("spreadmodechanged", webViewerSpreadModeChanged); eventBus._on("scrollmodechanged", webViewerScrollModeChanged, { signal });
eventBus._on("documentproperties", webViewerDocumentProperties); eventBus._on("switchspreadmode", webViewerSwitchSpreadMode, { signal });
eventBus._on("findfromurlhash", webViewerFindFromUrlHash); eventBus._on("spreadmodechanged", webViewerSpreadModeChanged, { signal });
eventBus._on("updatefindmatchescount", webViewerUpdateFindMatchesCount); eventBus._on("documentproperties", webViewerDocumentProperties, { signal });
eventBus._on("updatefindcontrolstate", webViewerUpdateFindControlState); eventBus._on("findfromurlhash", webViewerFindFromUrlHash, { signal });
eventBus._on("updatefindmatchescount", webViewerUpdateFindMatchesCount, {
signal,
});
eventBus._on("updatefindcontrolstate", webViewerUpdateFindControlState, {
signal,
});
if (AppOptions.get("pdfBug")) { if (AppOptions.get("pdfBug")) {
_boundEvents.reportPageStatsPDFBug = reportPageStatsPDFBug; eventBus._on("pagerendered", reportPageStatsPDFBug, { signal });
eventBus._on("pagechanging", reportPageStatsPDFBug, { signal });
eventBus._on("pagerendered", _boundEvents.reportPageStatsPDFBug);
eventBus._on("pagechanging", _boundEvents.reportPageStatsPDFBug);
} }
if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) { if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) {
eventBus._on("fileinputchange", webViewerFileInputChange); eventBus._on("fileinputchange", webViewerFileInputChange, { signal });
eventBus._on("openfile", webViewerOpenFile); eventBus._on("openfile", webViewerOpenFile, { signal });
} }
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) { if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) {
// The `unbindEvents` method is unused in MOZCENTRAL builds,
// hence we don't need to unregister these event listeners.
eventBus._on( eventBus._on(
"annotationeditorstateschanged", "annotationeditorstateschanged",
webViewerAnnotationEditorStatesChanged webViewerAnnotationEditorStatesChanged,
{ signal }
); );
eventBus._on("reporttelemetry", webViewerReportTelemetry); eventBus._on("reporttelemetry", webViewerReportTelemetry, { signal });
} }
}, },
@ -2049,62 +2061,8 @@ const PDFViewerApplication = {
}, },
unbindEvents() { unbindEvents() {
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) { this._eventBusAbortController?.abort();
throw new Error("Not implemented: unbindEvents"); this._eventBusAbortController = null;
}
const { eventBus, _boundEvents } = this;
eventBus._off("resize", webViewerResize);
eventBus._off("hashchange", webViewerHashchange);
eventBus._off("beforeprint", _boundEvents.beforePrint);
eventBus._off("afterprint", _boundEvents.afterPrint);
eventBus._off("pagerender", webViewerPageRender);
eventBus._off("pagerendered", webViewerPageRendered);
eventBus._off("updateviewarea", webViewerUpdateViewarea);
eventBus._off("pagechanging", webViewerPageChanging);
eventBus._off("scalechanging", webViewerScaleChanging);
eventBus._off("rotationchanging", webViewerRotationChanging);
eventBus._off("sidebarviewchanged", webViewerSidebarViewChanged);
eventBus._off("pagemode", webViewerPageMode);
eventBus._off("namedaction", webViewerNamedAction);
eventBus._off("presentationmodechanged", webViewerPresentationModeChanged);
eventBus._off("presentationmode", webViewerPresentationMode);
eventBus._off("print", webViewerPrint);
eventBus._off("download", webViewerDownload);
eventBus._off("firstpage", webViewerFirstPage);
eventBus._off("lastpage", webViewerLastPage);
eventBus._off("nextpage", webViewerNextPage);
eventBus._off("previouspage", webViewerPreviousPage);
eventBus._off("zoomin", webViewerZoomIn);
eventBus._off("zoomout", webViewerZoomOut);
eventBus._off("zoomreset", webViewerZoomReset);
eventBus._off("pagenumberchanged", webViewerPageNumberChanged);
eventBus._off("scalechanged", webViewerScaleChanged);
eventBus._off("rotatecw", webViewerRotateCw);
eventBus._off("rotateccw", webViewerRotateCcw);
eventBus._off("optionalcontentconfig", webViewerOptionalContentConfig);
eventBus._off("switchscrollmode", webViewerSwitchScrollMode);
eventBus._off("scrollmodechanged", webViewerScrollModeChanged);
eventBus._off("switchspreadmode", webViewerSwitchSpreadMode);
eventBus._off("spreadmodechanged", webViewerSpreadModeChanged);
eventBus._off("documentproperties", webViewerDocumentProperties);
eventBus._off("findfromurlhash", webViewerFindFromUrlHash);
eventBus._off("updatefindmatchescount", webViewerUpdateFindMatchesCount);
eventBus._off("updatefindcontrolstate", webViewerUpdateFindControlState);
if (_boundEvents.reportPageStatsPDFBug) {
eventBus._off("pagerendered", _boundEvents.reportPageStatsPDFBug);
eventBus._off("pagechanging", _boundEvents.reportPageStatsPDFBug);
_boundEvents.reportPageStatsPDFBug = null;
}
if (typeof PDFJSDev === "undefined" || PDFJSDev.test("GENERIC")) {
eventBus._off("fileinputchange", webViewerFileInputChange);
eventBus._off("openfile", webViewerOpenFile);
}
_boundEvents.beforePrint = null;
_boundEvents.afterPrint = null;
}, },
unbindWindowEvents() { unbindWindowEvents() {

View File

@ -44,29 +44,21 @@ async function waitOnEventOrTimeout({ target, name, delay = 0 }) {
throw new Error("waitOnEventOrTimeout - invalid parameters."); throw new Error("waitOnEventOrTimeout - invalid parameters.");
} }
const { promise, resolve } = Promise.withResolvers(); const { promise, resolve } = Promise.withResolvers();
const ac = new AbortController();
function handler(type) { function handler(type) {
if (target instanceof EventBus) { ac.abort(); // Remove event listener.
target._off(name, eventHandler); clearTimeout(timeout);
} else {
target.removeEventListener(name, eventHandler);
}
if (timeout) {
clearTimeout(timeout);
}
resolve(type); resolve(type);
} }
const eventHandler = handler.bind(null, WaitOnType.EVENT); const evtMethod = target instanceof EventBus ? "_on" : "addEventListener";
if (target instanceof EventBus) { target[evtMethod](name, handler.bind(null, WaitOnType.EVENT), {
target._on(name, eventHandler); signal: ac.signal,
} else { });
target.addEventListener(name, eventHandler);
}
const timeoutHandler = handler.bind(null, WaitOnType.TIMEOUT); const timeout = setTimeout(handler.bind(null, WaitOnType.TIMEOUT), delay);
const timeout = setTimeout(timeoutHandler, delay);
return promise; return promise;
} }
@ -87,6 +79,7 @@ class EventBus {
this._on(eventName, listener, { this._on(eventName, listener, {
external: true, external: true,
once: options?.once, once: options?.once,
signal: options?.signal,
}); });
} }
@ -96,10 +89,7 @@ class EventBus {
* @param {Object} [options] * @param {Object} [options]
*/ */
off(eventName, listener, options = null) { off(eventName, listener, options = null) {
this._off(eventName, listener, { this._off(eventName, listener);
external: true,
once: options?.once,
});
} }
/** /**
@ -138,11 +128,25 @@ class EventBus {
* @ignore * @ignore
*/ */
_on(eventName, listener, options = null) { _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] ||= []); const eventListeners = (this.#listeners[eventName] ||= []);
eventListeners.push({ eventListeners.push({
listener, listener,
external: options?.external === true, external: options?.external === true,
once: options?.once === true, once: options?.once === true,
rmAbort,
}); });
} }
@ -155,7 +159,9 @@ class EventBus {
return; return;
} }
for (let i = 0, ii = eventListeners.length; i < ii; i++) { for (let i = 0, ii = eventListeners.length; i < ii; i++) {
if (eventListeners[i].listener === listener) { const evt = eventListeners[i];
if (evt.listener === listener) {
evt.rmAbort?.(); // Ensure that the `AbortSignal` listener is removed.
eventListeners.splice(i, 1); eventListeners.splice(i, 1);
return; return;
} }

View File

@ -53,6 +53,8 @@ function getCurrentHash() {
} }
class PDFHistory { class PDFHistory {
#eventAbortController = null;
/** /**
* @param {PDFHistoryOptions} options * @param {PDFHistoryOptions} options
*/ */
@ -64,7 +66,6 @@ class PDFHistory {
this._fingerprint = ""; this._fingerprint = "";
this.reset(); this.reset();
this._boundEvents = null;
// Ensure that we don't miss a "pagesinit" event, // Ensure that we don't miss a "pagesinit" event,
// by registering the listener immediately. // by registering the listener immediately.
this.eventBus._on("pagesinit", () => { this.eventBus._on("pagesinit", () => {
@ -679,29 +680,22 @@ class PDFHistory {
} }
#bindEvents() { #bindEvents() {
if (this._boundEvents) { if (this.#eventAbortController) {
return; // The event listeners were already added. return; // The event listeners were already added.
} }
this._boundEvents = { this.#eventAbortController = new AbortController();
updateViewarea: this.#updateViewarea.bind(this), const { signal } = this.#eventAbortController;
popState: this.#popState.bind(this),
pageHide: this.#pageHide.bind(this),
};
this.eventBus._on("updateviewarea", this._boundEvents.updateViewarea); this.eventBus._on("updateviewarea", this.#updateViewarea.bind(this), {
window.addEventListener("popstate", this._boundEvents.popState); signal,
window.addEventListener("pagehide", this._boundEvents.pageHide); });
window.addEventListener("popstate", this.#popState.bind(this), { signal });
window.addEventListener("pagehide", this.#pageHide.bind(this), { signal });
} }
#unbindEvents() { #unbindEvents() {
if (!this._boundEvents) { this.#eventAbortController?.abort();
return; // The event listeners were already removed. this.#eventAbortController = null;
}
this.eventBus._off("updateviewarea", this._boundEvents.updateViewarea);
window.removeEventListener("popstate", this._boundEvents.popState);
window.removeEventListener("pagehide", this._boundEvents.pageHide);
this._boundEvents = null;
} }
} }

View File

@ -37,6 +37,8 @@ class PDFScriptingManager {
#docProperties = null; #docProperties = null;
#eventAbortController = null;
#eventBus = null; #eventBus = null;
#externalServices = null; #externalServices = null;
@ -108,46 +110,66 @@ class PDFScriptingManager {
await this.#destroyScripting(); await this.#destroyScripting();
return; return;
} }
const eventBus = this.#eventBus;
this._internalEvents.set("updatefromsandbox", event => { this.#eventAbortController = new AbortController();
if (event?.source === window) { const { signal } = this.#eventAbortController;
this.#updateFromSandbox(event.detail);
}
});
this._internalEvents.set("dispatcheventinsandbox", event => {
this.#scripting?.dispatchEventInSandbox(event.detail);
});
this._internalEvents.set("pagechanging", ({ pageNumber, previous }) => { eventBus._on(
if (pageNumber === previous) { "updatefromsandbox",
return; // The current page didn't change. event => {
} if (event?.source === window) {
this.#dispatchPageClose(previous); this.#updateFromSandbox(event.detail);
this.#dispatchPageOpen(pageNumber); }
}); },
this._internalEvents.set("pagerendered", ({ pageNumber }) => { { signal }
if (!this._pageOpenPending.has(pageNumber)) { );
return; // No pending "PageOpen" event for the newly rendered page. eventBus._on(
} "dispatcheventinsandbox",
if (pageNumber !== this.#pdfViewer.currentPageNumber) { event => {
return; // The newly rendered page is no longer the current one. this.#scripting?.dispatchEventInSandbox(event.detail);
} },
this.#dispatchPageOpen(pageNumber); { signal }
}); );
this._internalEvents.set("pagesdestroy", async () => {
await this.#dispatchPageClose(this.#pdfViewer.currentPageNumber);
await this.#scripting?.dispatchEventInSandbox({ eventBus._on(
id: "doc", "pagechanging",
name: "WillClose", ({ pageNumber, previous }) => {
}); if (pageNumber === previous) {
return; // The current page didn't change.
}
this.#dispatchPageClose(previous);
this.#dispatchPageOpen(pageNumber);
},
{ signal }
);
eventBus._on(
"pagerendered",
({ pageNumber }) => {
if (!this._pageOpenPending.has(pageNumber)) {
return; // No pending "PageOpen" event for the newly rendered page.
}
if (pageNumber !== this.#pdfViewer.currentPageNumber) {
return; // The newly rendered page is no longer the current one.
}
this.#dispatchPageOpen(pageNumber);
},
{ signal }
);
eventBus._on(
"pagesdestroy",
async () => {
await this.#dispatchPageClose(this.#pdfViewer.currentPageNumber);
this.#closeCapability?.resolve(); await this.#scripting?.dispatchEventInSandbox({
}); id: "doc",
name: "WillClose",
});
for (const [name, listener] of this._internalEvents) { this.#closeCapability?.resolve();
this.#eventBus._on(name, listener); },
} { signal }
);
try { try {
const docProperties = await this.#docProperties(pdfDocument); const docProperties = await this.#docProperties(pdfDocument);
@ -168,7 +190,7 @@ class PDFScriptingManager {
}, },
}); });
this.#eventBus.dispatch("sandboxcreated", { source: this }); eventBus.dispatch("sandboxcreated", { source: this });
} catch (error) { } catch (error) {
console.error(`setDocument: "${error.message}".`); console.error(`setDocument: "${error.message}".`);
@ -242,13 +264,6 @@ class PDFScriptingManager {
return this.#ready; return this.#ready;
} }
/**
* @private
*/
get _internalEvents() {
return shadow(this, "_internalEvents", new Map());
}
/** /**
* @private * @private
*/ */
@ -466,10 +481,8 @@ class PDFScriptingManager {
this.#willPrintCapability?.reject(new Error("Scripting destroyed.")); this.#willPrintCapability?.reject(new Error("Scripting destroyed."));
this.#willPrintCapability = null; this.#willPrintCapability = null;
for (const [name, listener] of this._internalEvents) { this.#eventAbortController?.abort();
this.#eventBus._off(name, listener); this.#eventAbortController = null;
}
this._internalEvents.clear();
this._pageOpenPending.clear(); this._pageOpenPending.clear();
this._visitedPages.clear(); this._visitedPages.clear();

View File

@ -29,6 +29,8 @@
* either the text layer or XFA layer depending on the type of document. * either the text layer or XFA layer depending on the type of document.
*/ */
class TextHighlighter { class TextHighlighter {
#eventAbortController = null;
/** /**
* @param {TextHighlighterOptions} options * @param {TextHighlighterOptions} options
*/ */
@ -37,7 +39,6 @@ class TextHighlighter {
this.matches = []; this.matches = [];
this.eventBus = eventBus; this.eventBus = eventBus;
this.pageIdx = pageIndex; this.pageIdx = pageIndex;
this._onUpdateTextLayerMatches = null;
this.textDivs = null; this.textDivs = null;
this.textContentItemsStr = null; this.textContentItemsStr = null;
this.enabled = false; this.enabled = false;
@ -69,15 +70,18 @@ class TextHighlighter {
throw new Error("TextHighlighter is already enabled."); throw new Error("TextHighlighter is already enabled.");
} }
this.enabled = true; this.enabled = true;
if (!this._onUpdateTextLayerMatches) {
this._onUpdateTextLayerMatches = evt => { if (!this.#eventAbortController) {
if (evt.pageIndex === this.pageIdx || evt.pageIndex === -1) { this.#eventAbortController = new AbortController();
this._updateMatches();
}
};
this.eventBus._on( this.eventBus._on(
"updatetextlayermatches", "updatetextlayermatches",
this._onUpdateTextLayerMatches evt => {
if (evt.pageIndex === this.pageIdx || evt.pageIndex === -1) {
this._updateMatches();
}
},
{ signal: this.#eventAbortController.signal }
); );
} }
this._updateMatches(); this._updateMatches();
@ -88,13 +92,10 @@ class TextHighlighter {
return; return;
} }
this.enabled = false; this.enabled = false;
if (this._onUpdateTextLayerMatches) {
this.eventBus._off( this.#eventAbortController?.abort();
"updatetextlayermatches", this.#eventAbortController = null;
this._onUpdateTextLayerMatches
);
this._onUpdateTextLayerMatches = null;
}
this._updateMatches(/* reset = */ true); this._updateMatches(/* reset = */ true);
} }