Prevent PdfTextExtractor.prototype.extractTextContent from failing intermittently

Given that extracting all text can take a while to complete, especially in long PDF documents[1], it's technically possible for the user to "select all", copy, and finally abort copying (with the `Esc` key) while `PdfTextExtractor.prototype.extractTextContent` is still running.
If that happens the `PDFViewer.prototype.getAllText` code would be interrupted, and text-extraction would thus fail in an intermittent and (likely) hard to debug way. To avoid this we replace the current "global" interrupt handling with an optional `AbortSignal` instead.

---

[1] See e.g. `pdf.pdf` in the test-suite.
This commit is contained in:
Jonas Jenwald 2026-03-03 11:10:45 +01:00
parent d859d78bb5
commit 54a8c049fd

View File

@ -272,8 +272,6 @@ class PDFViewer {
#hiddenCopyElement = null; #hiddenCopyElement = null;
#interruptCopyCondition = false;
#previousContainerHeight = 0; #previousContainerHeight = 0;
#resizeObserver = new ResizeObserver(this.#resizeObserverCallback.bind(this)); #resizeObserver = new ResizeObserver(this.#resizeObserverCallback.bind(this));
@ -771,7 +769,7 @@ class PDFViewer {
ac.abort(); // Remove the "visibilitychange" listener immediately. ac.abort(); // Remove the "visibilitychange" listener immediately.
} }
async getAllText() { async getAllText(interruptSignal = null) {
const texts = []; const texts = [];
const buffer = []; const buffer = [];
for ( for (
@ -779,7 +777,7 @@ class PDFViewer {
pageNum <= pagesCount; pageNum <= pagesCount;
++pageNum ++pageNum
) { ) {
if (this.#interruptCopyCondition) { if (interruptSignal?.aborted) {
return null; return null;
} }
buffer.length = 0; buffer.length = 0;
@ -833,14 +831,19 @@ class PDFViewer {
const { classList } = this.viewer; const { classList } = this.viewer;
classList.add("copyAll"); classList.add("copyAll");
const ac = new AbortController(); const keydownAC = new AbortController(),
interruptAC = new AbortController();
window.addEventListener( window.addEventListener(
"keydown", "keydown",
ev => (this.#interruptCopyCondition = ev.key === "Escape"), ev => {
{ signal: ac.signal } if (ev.key === "Escape") {
interruptAC.abort();
}
},
{ signal: keydownAC.signal }
); );
this.getAllText() this.getAllText(interruptAC.signal)
.then(async text => { .then(async text => {
if (text !== null) { if (text !== null) {
await navigator.clipboard.writeText(text); await navigator.clipboard.writeText(text);
@ -853,8 +856,7 @@ class PDFViewer {
}) })
.finally(() => { .finally(() => {
this.#getAllTextInProgress = false; this.#getAllTextInProgress = false;
this.#interruptCopyCondition = false; keydownAC.abort();
ac.abort();
classList.remove("copyAll"); classList.remove("copyAll");
}); });