From dcca4d075ac40d12272585578eae841896a069a3 Mon Sep 17 00:00:00 2001 From: Jonas Jenwald Date: Thu, 12 Dec 2024 11:46:09 +0100 Subject: [PATCH] Polyfill `AbortSignal.any` in PDF.js `legacy` builds With recent changes, in particular PR 19216, we're now relying on `AbortSignal.any` in a way that cannot just be skipped with pre-processor statements as done previously. This is problematical since `AbortSignal.any` is fairly new functionality, see https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal/any_static#browser_compatibility, that's not available in all browsers that we support. Instead it seems that we need to add a polyfill, however I unfortunately wasn't able to find one on NPM. In fact the only one I could find was https://gist.github.com/CNSeniorious000/9fc1a72e45358dd7c9e2f16e5d26df5c, which doesn't contain any licensing information. *Note:* Given the use of pre-processor statements, this polfill will *not* be included in the Firefox PDF Viewer. --- src/shared/util.js | 64 ++++++++++++++++++++++++++++++++++++++++++++++ web/app.js | 8 ++---- web/pdf_viewer.js | 14 ++-------- 3 files changed, 68 insertions(+), 18 deletions(-) diff --git a/src/shared/util.js b/src/shared/util.js index e0f66716ab9ca..58b4bf9443abf 100644 --- a/src/shared/util.js +++ b/src/shared/util.js @@ -1125,6 +1125,70 @@ if ( }; } +if ( + typeof PDFJSDev !== "undefined" && + !PDFJSDev.test("SKIP_BABEL") && + typeof AbortSignal.any !== "function" +) { + /** + * This polyfill is based on https://gist.github.com/CNSeniorious000/9fc1a72e45358dd7c9e2f16e5d26df5c + * which has no licensing information available. + * + * The following changes have been made: + * - Removal of commented out code. + * - Removal of test-only `count` variable and related code/logging. + * - Renaming of variable in `clear` function, to avoid variable shadowing. + */ + const registry = new FinalizationRegistry(callback => void callback()); + + AbortSignal.any = function polyfillAbortSignalAny(signals) { + const controller = new AbortController(); + for (const signal of signals) { + if (signal.aborted) { + controller.abort(signal.reason); + return controller.signal; + } + } + const controllerRef = new WeakRef(controller); + /** @type {[WeakRef, (() => void)][]} */ + const eventListenerPairs = []; + let followingCount = signals.length; + + signals.forEach(signal => { + const signalRef = new WeakRef(signal); + function abort() { + controllerRef.deref()?.abort(signalRef.deref()?.reason); + } + signal.addEventListener("abort", abort); + eventListenerPairs.push([signalRef, abort]); + registry.register(signal, () => !--followingCount && clear(), signal); + }); + function clear() { + eventListenerPairs.forEach(([signalRef, abort]) => { + const signal = signalRef.deref(); + if (signal) { + signal.removeEventListener("abort", abort); + registry.unregister(signal); + } + const cntlr = controllerRef.deref(); + if (cntlr) { + registry.unregister(cntlr.signal); + delete cntlr.signal.__controller; + } + }); + } + + const { signal } = controller; + + registry.register(signal, clear, signal); + signal.addEventListener("abort", clear); + + signal.__controller = controller; + + return signal; + }; +} + export { AbortException, AnnotationActionEventType, diff --git a/web/app.js b/web/app.js index d23a04f2cc5a2..2dc9e4503a39f 100644 --- a/web/app.js +++ b/web/app.js @@ -531,11 +531,7 @@ const PDFViewerApplication = { } if (appConfig.annotationEditorParams) { - if ( - ((typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) || - typeof AbortSignal.any === "function") && - annotationEditorMode !== AnnotationEditorType.DISABLE - ) { + if (annotationEditorMode !== AnnotationEditorType.DISABLE) { this.annotationEditorParams = new AnnotationEditorParams( appConfig.annotationEditorParams, eventBus @@ -2046,7 +2042,7 @@ const PDFViewerApplication = { this._touchManager = new TouchManager({ container: window, - isPinchingDisabled: () => this.pdfViewer.isInPresentationMode, + isPinchingDisabled: () => pdfViewer.isInPresentationMode, isPinchingStopped: () => this.overlayManager?.active, onPinching: this.touchPinchCallback.bind(this), onPinchEnd: this.touchPinchEndCallback.bind(this), diff --git a/web/pdf_viewer.js b/web/pdf_viewer.js index e968db3f14810..2b7f4323fa542 100644 --- a/web/pdf_viewer.js +++ b/web/pdf_viewer.js @@ -686,13 +686,7 @@ class PDFViewer { hiddenCapability.resolve(); } }, - { - signal: - (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) || - typeof AbortSignal.any === "function" - ? AbortSignal.any([signal, ac.signal]) - : signal, - } + { signal: AbortSignal.any([signal, ac.signal]) } ); await Promise.race([ @@ -889,11 +883,7 @@ class PDFViewer { viewer.before(element); } - if ( - ((typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) || - typeof AbortSignal.any === "function") && - annotationEditorMode !== AnnotationEditorType.DISABLE - ) { + if (annotationEditorMode !== AnnotationEditorType.DISABLE) { const mode = annotationEditorMode; if (pdfDocument.isPureXfa) {