Merge pull request #18284 from calixteman/editor_signal

[Editor] Remove the various listeners when destroying the editor manager
This commit is contained in:
calixteman 2024-06-19 12:36:38 +02:00 committed by GitHub
commit c53f71a7d2
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 244 additions and 129 deletions

View File

@ -49,20 +49,27 @@ class AltText {
altText.textContent = msg; altText.textContent = msg;
altText.setAttribute("aria-label", msg); altText.setAttribute("aria-label", msg);
altText.tabIndex = "0"; altText.tabIndex = "0";
altText.addEventListener("contextmenu", noContextMenu); const signal = this.#editor._uiManager._signal;
altText.addEventListener("pointerdown", event => event.stopPropagation()); altText.addEventListener("contextmenu", noContextMenu, { signal });
altText.addEventListener("pointerdown", event => event.stopPropagation(), {
signal,
});
const onClick = event => { const onClick = event => {
event.preventDefault(); event.preventDefault();
this.#editor._uiManager.editAltText(this.#editor); this.#editor._uiManager.editAltText(this.#editor);
}; };
altText.addEventListener("click", onClick, { capture: true }); altText.addEventListener("click", onClick, { capture: true, signal });
altText.addEventListener("keydown", event => { altText.addEventListener(
if (event.target === altText && event.key === "Enter") { "keydown",
this.#altTextWasFromKeyBoard = true; event => {
onClick(event); if (event.target === altText && event.key === "Enter") {
} this.#altTextWasFromKeyBoard = true;
}); onClick(event);
}
},
{ signal }
);
await this.#setState(); await this.#setState();
return altText; return altText;
@ -142,22 +149,39 @@ class AltText {
button.setAttribute("aria-describedby", id); button.setAttribute("aria-describedby", id);
const DELAY_TO_SHOW_TOOLTIP = 100; const DELAY_TO_SHOW_TOOLTIP = 100;
button.addEventListener("mouseenter", () => { const signal = this.#editor._uiManager._signal;
this.#altTextTooltipTimeout = setTimeout(() => { signal.addEventListener(
this.#altTextTooltipTimeout = null; "abort",
this.#altTextTooltip.classList.add("show"); () => {
this.#editor._reportTelemetry({
action: "alt_text_tooltip",
});
}, DELAY_TO_SHOW_TOOLTIP);
});
button.addEventListener("mouseleave", () => {
if (this.#altTextTooltipTimeout) {
clearTimeout(this.#altTextTooltipTimeout); clearTimeout(this.#altTextTooltipTimeout);
this.#altTextTooltipTimeout = null; this.#altTextTooltipTimeout = null;
} },
this.#altTextTooltip?.classList.remove("show"); { once: true }
}); );
button.addEventListener(
"mouseenter",
() => {
this.#altTextTooltipTimeout = setTimeout(() => {
this.#altTextTooltipTimeout = null;
this.#altTextTooltip.classList.add("show");
this.#editor._reportTelemetry({
action: "alt_text_tooltip",
});
}, DELAY_TO_SHOW_TOOLTIP);
},
{ signal }
);
button.addEventListener(
"mouseleave",
() => {
if (this.#altTextTooltipTimeout) {
clearTimeout(this.#altTextTooltipTimeout);
this.#altTextTooltipTimeout = null;
}
this.#altTextTooltip?.classList.remove("show");
},
{ signal }
);
} }
tooltip.innerText = this.#altTextDecorative tooltip.innerText = this.#altTextDecorative
? await AltText._l10nPromise.get( ? await AltText._l10nPromise.get(

View File

@ -365,7 +365,8 @@ class AnnotationEditorLayer {
this.#boundTextLayerPointerDown = this.#textLayerPointerDown.bind(this); this.#boundTextLayerPointerDown = this.#textLayerPointerDown.bind(this);
this.#textLayer.div.addEventListener( this.#textLayer.div.addEventListener(
"pointerdown", "pointerdown",
this.#boundTextLayerPointerDown this.#boundTextLayerPointerDown,
{ signal: this.#uiManager._signal }
); );
this.#textLayer.div.classList.add("highlighting"); this.#textLayer.div.classList.add("highlighting");
} }
@ -409,7 +410,7 @@ class AnnotationEditorLayer {
() => { () => {
this.#textLayer.div.classList.remove("free"); this.#textLayer.div.classList.remove("free");
}, },
{ once: true } { once: true, signal: this.#uiManager._signal }
); );
event.preventDefault(); event.preventDefault();
} }
@ -419,10 +420,13 @@ class AnnotationEditorLayer {
if (this.#boundPointerdown) { if (this.#boundPointerdown) {
return; return;
} }
const signal = this.#uiManager._signal;
this.#boundPointerdown = this.pointerdown.bind(this); this.#boundPointerdown = this.pointerdown.bind(this);
this.#boundPointerup = this.pointerup.bind(this); this.#boundPointerup = this.pointerup.bind(this);
this.div.addEventListener("pointerdown", this.#boundPointerdown); this.div.addEventListener("pointerdown", this.#boundPointerdown, {
this.div.addEventListener("pointerup", this.#boundPointerup); signal,
});
this.div.addEventListener("pointerup", this.#boundPointerup, { signal });
} }
disableClick() { disableClick() {
@ -540,7 +544,7 @@ class AnnotationEditorLayer {
() => { () => {
editor._focusEventsAllowed = true; editor._focusEventsAllowed = true;
}, },
{ once: true } { once: true, signal: this.#uiManager._signal }
); );
activeElement.focus(); activeElement.focus();
} else { } else {
@ -596,6 +600,10 @@ class AnnotationEditorLayer {
return AnnotationEditorLayer.#editorTypes.get(this.#uiManager.getMode()); return AnnotationEditorLayer.#editorTypes.get(this.#uiManager.getMode());
} }
get _signal() {
return this.#uiManager._signal;
}
/** /**
* Create a new editor * Create a new editor
* @param {Object} params * @param {Object} params

View File

@ -89,8 +89,9 @@ class ColorPicker {
button.tabIndex = "0"; button.tabIndex = "0";
button.setAttribute("data-l10n-id", "pdfjs-editor-colorpicker-button"); button.setAttribute("data-l10n-id", "pdfjs-editor-colorpicker-button");
button.setAttribute("aria-haspopup", true); button.setAttribute("aria-haspopup", true);
button.addEventListener("click", this.#openDropdown.bind(this)); const signal = this.#uiManager._signal;
button.addEventListener("keydown", this.#boundKeyDown); button.addEventListener("click", this.#openDropdown.bind(this), { signal });
button.addEventListener("keydown", this.#boundKeyDown, { signal });
const swatch = (this.#buttonSwatch = document.createElement("span")); const swatch = (this.#buttonSwatch = document.createElement("span"));
swatch.className = "swatch"; swatch.className = "swatch";
swatch.setAttribute("aria-hidden", true); swatch.setAttribute("aria-hidden", true);
@ -109,7 +110,8 @@ class ColorPicker {
#getDropdownRoot() { #getDropdownRoot() {
const div = document.createElement("div"); const div = document.createElement("div");
div.addEventListener("contextmenu", noContextMenu); const signal = this.#uiManager._signal;
div.addEventListener("contextmenu", noContextMenu, { signal });
div.className = "dropdown"; div.className = "dropdown";
div.role = "listbox"; div.role = "listbox";
div.setAttribute("aria-multiselectable", false); div.setAttribute("aria-multiselectable", false);
@ -127,11 +129,13 @@ class ColorPicker {
swatch.className = "swatch"; swatch.className = "swatch";
swatch.style.backgroundColor = color; swatch.style.backgroundColor = color;
button.setAttribute("aria-selected", color === this.#defaultColor); button.setAttribute("aria-selected", color === this.#defaultColor);
button.addEventListener("click", this.#colorSelect.bind(this, color)); button.addEventListener("click", this.#colorSelect.bind(this, color), {
signal,
});
div.append(button); div.append(button);
} }
div.addEventListener("keydown", this.#boundKeyDown); div.addEventListener("keydown", this.#boundKeyDown, { signal });
return div; return div;
} }
@ -211,7 +215,9 @@ class ColorPicker {
return; return;
} }
this.#dropdownWasFromKeyboard = event.detail === 0; this.#dropdownWasFromKeyboard = event.detail === 0;
window.addEventListener("pointerdown", this.#boundPointerDown); window.addEventListener("pointerdown", this.#boundPointerDown, {
signal: this.#uiManager._signal,
});
if (this.#dropdown) { if (this.#dropdown) {
this.#dropdown.classList.remove("hidden"); this.#dropdown.classList.remove("hidden");
return; return;

View File

@ -715,6 +715,7 @@ class AnnotationEditor {
"bottomLeft", "bottomLeft",
"middleLeft", "middleLeft",
]; ];
const signal = this._uiManager._signal;
for (const name of classes) { for (const name of classes) {
const div = document.createElement("div"); const div = document.createElement("div");
this.#resizersDiv.append(div); this.#resizersDiv.append(div);
@ -722,9 +723,10 @@ class AnnotationEditor {
div.setAttribute("data-resizer-name", name); div.setAttribute("data-resizer-name", name);
div.addEventListener( div.addEventListener(
"pointerdown", "pointerdown",
this.#resizerPointerdown.bind(this, name) this.#resizerPointerdown.bind(this, name),
{ signal }
); );
div.addEventListener("contextmenu", noContextMenu); div.addEventListener("contextmenu", noContextMenu, { signal });
div.tabIndex = -1; div.tabIndex = -1;
} }
this.div.prepend(this.#resizersDiv); this.div.prepend(this.#resizersDiv);
@ -742,14 +744,15 @@ class AnnotationEditor {
const boundResizerPointermove = this.#resizerPointermove.bind(this, name); const boundResizerPointermove = this.#resizerPointermove.bind(this, name);
const savedDraggable = this._isDraggable; const savedDraggable = this._isDraggable;
this._isDraggable = false; this._isDraggable = false;
const pointerMoveOptions = { passive: true, capture: true }; const signal = this._uiManager._signal;
const pointerMoveOptions = { passive: true, capture: true, signal };
this.parent.togglePointerEvents(false); this.parent.togglePointerEvents(false);
window.addEventListener( window.addEventListener(
"pointermove", "pointermove",
boundResizerPointermove, boundResizerPointermove,
pointerMoveOptions pointerMoveOptions
); );
window.addEventListener("contextmenu", noContextMenu); window.addEventListener("contextmenu", noContextMenu, { signal });
const savedX = this.x; const savedX = this.x;
const savedY = this.y; const savedY = this.y;
const savedWidth = this.width; const savedWidth = this.width;
@ -776,10 +779,10 @@ class AnnotationEditor {
this.#addResizeToUndoStack(savedX, savedY, savedWidth, savedHeight); this.#addResizeToUndoStack(savedX, savedY, savedWidth, savedHeight);
}; };
window.addEventListener("pointerup", pointerUpCallback); window.addEventListener("pointerup", pointerUpCallback, { signal });
// If the user switches to another window (with alt+tab), then we end the // If the user switches to another window (with alt+tab), then we end the
// resize session. // resize session.
window.addEventListener("blur", pointerUpCallback); window.addEventListener("blur", pointerUpCallback, { signal });
} }
#addResizeToUndoStack(savedX, savedY, savedWidth, savedHeight) { #addResizeToUndoStack(savedX, savedY, savedWidth, savedHeight) {
@ -1027,8 +1030,9 @@ class AnnotationEditor {
this.setInForeground(); this.setInForeground();
this.div.addEventListener("focusin", this.#boundFocusin); const signal = this._uiManager._signal;
this.div.addEventListener("focusout", this.#boundFocusout); this.div.addEventListener("focusin", this.#boundFocusin, { signal });
this.div.addEventListener("focusout", this.#boundFocusout, { signal });
const [parentWidth, parentHeight] = this.parentDimensions; const [parentWidth, parentHeight] = this.parentDimensions;
if (this.parentRotation % 180 !== 0) { if (this.parentRotation % 180 !== 0) {
@ -1089,9 +1093,10 @@ class AnnotationEditor {
this._uiManager.setUpDragSession(); this._uiManager.setUpDragSession();
let pointerMoveOptions, pointerMoveCallback; let pointerMoveOptions, pointerMoveCallback;
const signal = this._uiManager._signal;
if (isSelected) { if (isSelected) {
this.div.classList.add("moving"); this.div.classList.add("moving");
pointerMoveOptions = { passive: true, capture: true }; pointerMoveOptions = { passive: true, capture: true, signal };
this.#prevDragX = event.clientX; this.#prevDragX = event.clientX;
this.#prevDragY = event.clientY; this.#prevDragY = event.clientY;
pointerMoveCallback = e => { pointerMoveCallback = e => {
@ -1128,11 +1133,11 @@ class AnnotationEditor {
this.#selectOnPointerEvent(event); this.#selectOnPointerEvent(event);
} }
}; };
window.addEventListener("pointerup", pointerUpCallback); window.addEventListener("pointerup", pointerUpCallback, { signal });
// If the user is using alt+tab during the dragging session, the pointerup // If the user is using alt+tab during the dragging session, the pointerup
// event could be not fired, but a blur event is fired so we can use it in // event could be not fired, but a blur event is fired so we can use it in
// order to interrupt the dragging session. // order to interrupt the dragging session.
window.addEventListener("blur", pointerUpCallback); window.addEventListener("blur", pointerUpCallback, { signal });
} }
moveInDOM() { moveInDOM() {
@ -1284,8 +1289,9 @@ class AnnotationEditor {
* To implement in subclasses. * To implement in subclasses.
*/ */
rebuild() { rebuild() {
this.div?.addEventListener("focusin", this.#boundFocusin); const signal = this._uiManager._signal;
this.div?.addEventListener("focusout", this.#boundFocusout); this.div?.addEventListener("focusin", this.#boundFocusin, { signal });
this.div?.addEventListener("focusout", this.#boundFocusout, { signal });
} }
/** /**
@ -1429,12 +1435,15 @@ class AnnotationEditor {
this.#allResizerDivs = Array.from(children); this.#allResizerDivs = Array.from(children);
const boundResizerKeydown = this.#resizerKeydown.bind(this); const boundResizerKeydown = this.#resizerKeydown.bind(this);
const boundResizerBlur = this.#resizerBlur.bind(this); const boundResizerBlur = this.#resizerBlur.bind(this);
const signal = this._uiManager._signal;
for (const div of this.#allResizerDivs) { for (const div of this.#allResizerDivs) {
const name = div.getAttribute("data-resizer-name"); const name = div.getAttribute("data-resizer-name");
div.setAttribute("role", "spinbutton"); div.setAttribute("role", "spinbutton");
div.addEventListener("keydown", boundResizerKeydown); div.addEventListener("keydown", boundResizerKeydown, { signal });
div.addEventListener("blur", boundResizerBlur); div.addEventListener("blur", boundResizerBlur, { signal });
div.addEventListener("focus", this.#resizerFocus.bind(this, name)); div.addEventListener("focus", this.#resizerFocus.bind(this, name), {
signal,
});
AnnotationEditor._l10nPromise AnnotationEditor._l10nPromise
.get(`pdfjs-editor-resizer-label-${name}`) .get(`pdfjs-editor-resizer-label-${name}`)
.then(msg => div.setAttribute("aria-label", msg)); .then(msg => div.setAttribute("aria-label", msg));

View File

@ -307,11 +307,22 @@ class FreeTextEditor extends AnnotationEditor {
this.editorDiv.contentEditable = true; this.editorDiv.contentEditable = true;
this._isDraggable = false; this._isDraggable = false;
this.div.removeAttribute("aria-activedescendant"); this.div.removeAttribute("aria-activedescendant");
this.editorDiv.addEventListener("keydown", this.#boundEditorDivKeydown); const signal = this._uiManager._signal;
this.editorDiv.addEventListener("focus", this.#boundEditorDivFocus); this.editorDiv.addEventListener("keydown", this.#boundEditorDivKeydown, {
this.editorDiv.addEventListener("blur", this.#boundEditorDivBlur); signal,
this.editorDiv.addEventListener("input", this.#boundEditorDivInput); });
this.editorDiv.addEventListener("paste", this.#boundEditorDivPaste); this.editorDiv.addEventListener("focus", this.#boundEditorDivFocus, {
signal,
});
this.editorDiv.addEventListener("blur", this.#boundEditorDivBlur, {
signal,
});
this.editorDiv.addEventListener("input", this.#boundEditorDivInput, {
signal,
});
this.editorDiv.addEventListener("paste", this.#boundEditorDivPaste, {
signal,
});
} }
/** @inheritdoc */ /** @inheritdoc */

View File

@ -568,7 +568,9 @@ class HighlightEditor extends AnnotationEditor {
if (this.#isFreeHighlight) { if (this.#isFreeHighlight) {
div.classList.add("free"); div.classList.add("free");
} else { } else {
this.div.addEventListener("keydown", this.#boundKeydown); this.div.addEventListener("keydown", this.#boundKeydown, {
signal: this._uiManager._signal,
});
} }
const highlightDiv = (this.#highlightDiv = document.createElement("div")); const highlightDiv = (this.#highlightDiv = document.createElement("div"));
div.append(highlightDiv); div.append(highlightDiv);
@ -702,7 +704,8 @@ class HighlightEditor extends AnnotationEditor {
const pointerMove = e => { const pointerMove = e => {
this.#highlightMove(parent, e); this.#highlightMove(parent, e);
}; };
const pointerDownOptions = { capture: true, passive: false }; const signal = parent._signal;
const pointerDownOptions = { capture: true, passive: false, signal };
const pointerDown = e => { const pointerDown = e => {
// Avoid to have undesired clicks during the drawing. // Avoid to have undesired clicks during the drawing.
e.preventDefault(); e.preventDefault();
@ -720,12 +723,12 @@ class HighlightEditor extends AnnotationEditor {
window.removeEventListener("contextmenu", noContextMenu); window.removeEventListener("contextmenu", noContextMenu);
this.#endHighlight(parent, e); this.#endHighlight(parent, e);
}; };
window.addEventListener("blur", pointerUpCallback); window.addEventListener("blur", pointerUpCallback, { signal });
window.addEventListener("pointerup", pointerUpCallback); window.addEventListener("pointerup", pointerUpCallback, { signal });
window.addEventListener("pointerdown", pointerDown, pointerDownOptions); window.addEventListener("pointerdown", pointerDown, pointerDownOptions);
window.addEventListener("contextmenu", noContextMenu); window.addEventListener("contextmenu", noContextMenu, { signal });
textLayer.addEventListener("pointermove", pointerMove); textLayer.addEventListener("pointermove", pointerMove, { signal });
this._freeHighlight = new FreeOutliner( this._freeHighlight = new FreeOutliner(
{ x, y }, { x, y },
[layerX, layerY, parentWidth, parentHeight], [layerX, layerY, parentWidth, parentHeight],

View File

@ -261,7 +261,7 @@ class InkEditor extends AnnotationEditor {
this.#canvasContextMenuTimeoutId = null; this.#canvasContextMenuTimeoutId = null;
} }
this.#observer.disconnect(); this.#observer?.disconnect();
this.#observer = null; this.#observer = null;
super.remove(); super.remove();
@ -296,7 +296,9 @@ class InkEditor extends AnnotationEditor {
super.enableEditMode(); super.enableEditMode();
this._isDraggable = false; this._isDraggable = false;
this.canvas.addEventListener("pointerdown", this.#boundCanvasPointerdown); this.canvas.addEventListener("pointerdown", this.#boundCanvasPointerdown, {
signal: this._uiManager._signal,
});
} }
/** @inheritdoc */ /** @inheritdoc */
@ -363,10 +365,19 @@ class InkEditor extends AnnotationEditor {
* @param {number} y * @param {number} y
*/ */
#startDrawing(x, y) { #startDrawing(x, y) {
this.canvas.addEventListener("contextmenu", noContextMenu); const signal = this._uiManager._signal;
this.canvas.addEventListener("pointerleave", this.#boundCanvasPointerleave); this.canvas.addEventListener("contextmenu", noContextMenu, { signal });
this.canvas.addEventListener("pointermove", this.#boundCanvasPointermove); this.canvas.addEventListener(
this.canvas.addEventListener("pointerup", this.#boundCanvasPointerup); "pointerleave",
this.#boundCanvasPointerleave,
{ signal }
);
this.canvas.addEventListener("pointermove", this.#boundCanvasPointermove, {
signal,
});
this.canvas.addEventListener("pointerup", this.#boundCanvasPointerup, {
signal,
});
this.canvas.removeEventListener( this.canvas.removeEventListener(
"pointerdown", "pointerdown",
this.#boundCanvasPointerdown this.#boundCanvasPointerdown
@ -706,7 +717,9 @@ class InkEditor extends AnnotationEditor {
this.#boundCanvasPointermove this.#boundCanvasPointermove
); );
this.canvas.removeEventListener("pointerup", this.#boundCanvasPointerup); this.canvas.removeEventListener("pointerup", this.#boundCanvasPointerup);
this.canvas.addEventListener("pointerdown", this.#boundCanvasPointerdown); this.canvas.addEventListener("pointerdown", this.#boundCanvasPointerdown, {
signal: this._uiManager._signal,
});
// Slight delay to avoid the context menu to appear (it can happen on a long // Slight delay to avoid the context menu to appear (it can happen on a long
// tap with a pen). // tap with a pen).
@ -751,6 +764,14 @@ class InkEditor extends AnnotationEditor {
} }
}); });
this.#observer.observe(this.div); this.#observer.observe(this.div);
this._uiManager._signal.addEventListener(
"abort",
() => {
this.#observer?.disconnect();
this.#observer = null;
},
{ once: true }
);
} }
/** @inheritdoc */ /** @inheritdoc */

View File

@ -160,26 +160,35 @@ class StampEditor extends AnnotationEditor {
} }
input.type = "file"; input.type = "file";
input.accept = StampEditor.supportedTypesStr; input.accept = StampEditor.supportedTypesStr;
const signal = this._uiManager._signal;
this.#bitmapPromise = new Promise(resolve => { this.#bitmapPromise = new Promise(resolve => {
input.addEventListener("change", async () => { input.addEventListener(
if (!input.files || input.files.length === 0) { "change",
async () => {
if (!input.files || input.files.length === 0) {
this.remove();
} else {
this._uiManager.enableWaiting(true);
const data = await this._uiManager.imageManager.getFromFile(
input.files[0]
);
this.#getBitmapFetched(data);
}
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("TESTING")) {
input.remove();
}
resolve();
},
{ signal }
);
input.addEventListener(
"cancel",
() => {
this.remove(); this.remove();
} else { resolve();
this._uiManager.enableWaiting(true); },
const data = await this._uiManager.imageManager.getFromFile( { signal }
input.files[0] );
);
this.#getBitmapFetched(data);
}
if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("TESTING")) {
input.remove();
}
resolve();
});
input.addEventListener("cancel", () => {
this.remove();
resolve();
});
}).finally(() => this.#getBitmapDone()); }).finally(() => this.#getBitmapDone());
if (typeof PDFJSDev === "undefined" || !PDFJSDev.test("TESTING")) { if (typeof PDFJSDev === "undefined" || !PDFJSDev.test("TESTING")) {
input.click(); input.click();
@ -536,6 +545,14 @@ class StampEditor extends AnnotationEditor {
} }
}); });
this.#observer.observe(this.div); this.#observer.observe(this.div);
this._uiManager._signal.addEventListener(
"abort",
() => {
this.#observer?.disconnect();
this.#observer = null;
},
{ once: true }
);
} }
/** @inheritdoc */ /** @inheritdoc */

View File

@ -32,8 +32,11 @@ class EditorToolbar {
const editToolbar = (this.#toolbar = document.createElement("div")); const editToolbar = (this.#toolbar = document.createElement("div"));
editToolbar.className = "editToolbar"; editToolbar.className = "editToolbar";
editToolbar.setAttribute("role", "toolbar"); editToolbar.setAttribute("role", "toolbar");
editToolbar.addEventListener("contextmenu", noContextMenu); const signal = this.#editor._uiManager._signal;
editToolbar.addEventListener("pointerdown", EditorToolbar.#pointerDown); editToolbar.addEventListener("contextmenu", noContextMenu, { signal });
editToolbar.addEventListener("pointerdown", EditorToolbar.#pointerDown, {
signal,
});
const buttons = (this.#buttons = document.createElement("div")); const buttons = (this.#buttons = document.createElement("div"));
buttons.className = "buttons"; buttons.className = "buttons";
@ -77,13 +80,16 @@ class EditorToolbar {
// If we're clicking on a button with the keyboard or with // If we're clicking on a button with the keyboard or with
// the mouse, we don't want to trigger any focus events on // the mouse, we don't want to trigger any focus events on
// the editor. // the editor.
const signal = this.#editor._uiManager._signal;
element.addEventListener("focusin", this.#focusIn.bind(this), { element.addEventListener("focusin", this.#focusIn.bind(this), {
capture: true, capture: true,
signal,
}); });
element.addEventListener("focusout", this.#focusOut.bind(this), { element.addEventListener("focusout", this.#focusOut.bind(this), {
capture: true, capture: true,
signal,
}); });
element.addEventListener("contextmenu", noContextMenu); element.addEventListener("contextmenu", noContextMenu, { signal });
} }
hide() { hide() {
@ -104,9 +110,13 @@ class EditorToolbar {
`pdfjs-editor-remove-${this.#editor.editorType}-button` `pdfjs-editor-remove-${this.#editor.editorType}-button`
); );
this.#addListenersToElement(button); this.#addListenersToElement(button);
button.addEventListener("click", e => { button.addEventListener(
this.#editor._uiManager.delete(); "click",
}); e => {
this.#editor._uiManager.delete();
},
{ signal: this.#editor._uiManager._signal }
);
this.#buttons.append(button); this.#buttons.append(button);
} }
@ -150,7 +160,9 @@ class HighlightToolbar {
const editToolbar = (this.#toolbar = document.createElement("div")); const editToolbar = (this.#toolbar = document.createElement("div"));
editToolbar.className = "editToolbar"; editToolbar.className = "editToolbar";
editToolbar.setAttribute("role", "toolbar"); editToolbar.setAttribute("role", "toolbar");
editToolbar.addEventListener("contextmenu", noContextMenu); editToolbar.addEventListener("contextmenu", noContextMenu, {
signal: this.#uiManager._signal,
});
const buttons = (this.#buttons = document.createElement("div")); const buttons = (this.#buttons = document.createElement("div"));
buttons.className = "buttons"; buttons.className = "buttons";
@ -207,10 +219,15 @@ class HighlightToolbar {
button.append(span); button.append(span);
span.className = "visuallyHidden"; span.className = "visuallyHidden";
span.setAttribute("data-l10n-id", "pdfjs-highlight-floating-button-label"); span.setAttribute("data-l10n-id", "pdfjs-highlight-floating-button-label");
button.addEventListener("contextmenu", noContextMenu); const signal = this.#uiManager._signal;
button.addEventListener("click", () => { button.addEventListener("contextmenu", noContextMenu, { signal });
this.#uiManager.highlightSelection("floating_button"); button.addEventListener(
}); "click",
() => {
this.#uiManager.highlightSelection("floating_button");
},
{ signal }
);
this.#buttons.append(button); this.#buttons.append(button);
} }
} }

View File

@ -534,6 +534,8 @@ class ColorManager {
* some action like copy/paste, undo/redo, ... * some action like copy/paste, undo/redo, ...
*/ */
class AnnotationEditorUIManager { class AnnotationEditorUIManager {
#abortController = new AbortController();
#activeEditor = null; #activeEditor = null;
#allEditors = new Map(); #allEditors = new Map();
@ -600,10 +602,6 @@ class AnnotationEditorUIManager {
#boundCut = this.cut.bind(this); #boundCut = this.cut.bind(this);
#boundDragOver = this.dragOver.bind(this);
#boundDrop = this.drop.bind(this);
#boundPaste = this.paste.bind(this); #boundPaste = this.paste.bind(this);
#boundKeydown = this.keydown.bind(this); #boundKeydown = this.keydown.bind(this);
@ -616,8 +614,6 @@ class AnnotationEditorUIManager {
#boundOnScaleChanging = this.onScaleChanging.bind(this); #boundOnScaleChanging = this.onScaleChanging.bind(this);
#boundSelectionChange = this.#selectionChange.bind(this);
#boundOnRotationChanging = this.onRotationChanging.bind(this); #boundOnRotationChanging = this.onRotationChanging.bind(this);
#previousStates = { #previousStates = {
@ -785,6 +781,7 @@ class AnnotationEditorUIManager {
enableHighlightFloatingButton, enableHighlightFloatingButton,
mlManager mlManager
) { ) {
this._signal = this.#abortController.signal;
this.#container = container; this.#container = container;
this.#viewer = viewer; this.#viewer = viewer;
this.#altTextManager = altTextManager; this.#altTextManager = altTextManager;
@ -820,9 +817,10 @@ class AnnotationEditorUIManager {
} }
destroy() { destroy() {
this.#removeDragAndDropListeners(); this.#abortController?.abort();
this.#removeKeyboardManager(); this.#abortController = null;
this.#removeFocusManager(); this._signal = null;
this._eventBus._off("editingaction", this.#boundOnEditingAction); this._eventBus._off("editingaction", this.#boundOnEditingAction);
this._eventBus._off("pagechanging", this.#boundOnPageChanging); this._eventBus._off("pagechanging", this.#boundOnPageChanging);
this._eventBus._off("scalechanging", this.#boundOnScaleChanging); this._eventBus._off("scalechanging", this.#boundOnScaleChanging);
@ -847,7 +845,6 @@ class AnnotationEditorUIManager {
clearTimeout(this.#translationTimeoutId); clearTimeout(this.#translationTimeoutId);
this.#translationTimeoutId = null; this.#translationTimeoutId = null;
} }
this.#removeSelectionListener();
} }
async mlGuess(data) { async mlGuess(data) {
@ -1084,6 +1081,7 @@ class AnnotationEditorUIManager {
this.#highlightWhenShiftUp = this.isShiftKeyDown; this.#highlightWhenShiftUp = this.isShiftKeyDown;
if (!this.isShiftKeyDown) { if (!this.isShiftKeyDown) {
const signal = this._signal;
const pointerup = e => { const pointerup = e => {
if (e.type === "pointerup" && e.button !== 0) { if (e.type === "pointerup" && e.button !== 0) {
// Do nothing on right click. // Do nothing on right click.
@ -1095,8 +1093,8 @@ class AnnotationEditorUIManager {
this.#onSelectEnd("main_toolbar"); this.#onSelectEnd("main_toolbar");
} }
}; };
window.addEventListener("pointerup", pointerup); window.addEventListener("pointerup", pointerup, { signal });
window.addEventListener("blur", pointerup); window.addEventListener("blur", pointerup, { signal });
} }
} }
@ -1109,16 +1107,19 @@ class AnnotationEditorUIManager {
} }
#addSelectionListener() { #addSelectionListener() {
document.addEventListener("selectionchange", this.#boundSelectionChange); document.addEventListener(
} "selectionchange",
this.#selectionChange.bind(this),
#removeSelectionListener() { {
document.removeEventListener("selectionchange", this.#boundSelectionChange); signal: this._signal,
}
);
} }
#addFocusManager() { #addFocusManager() {
window.addEventListener("focus", this.#boundFocus); const signal = this._signal;
window.addEventListener("blur", this.#boundBlur); window.addEventListener("focus", this.#boundFocus, { signal });
window.addEventListener("blur", this.#boundBlur, { signal });
} }
#removeFocusManager() { #removeFocusManager() {
@ -1160,16 +1161,17 @@ class AnnotationEditorUIManager {
() => { () => {
lastEditor._focusEventsAllowed = true; lastEditor._focusEventsAllowed = true;
}, },
{ once: true } { once: true, signal: this._signal }
); );
lastActiveElement.focus(); lastActiveElement.focus();
} }
#addKeyboardManager() { #addKeyboardManager() {
const signal = this._signal;
// The keyboard events are caught at the container level in order to be able // The keyboard events are caught at the container level in order to be able
// to execute some callbacks even if the current page doesn't have focus. // to execute some callbacks even if the current page doesn't have focus.
window.addEventListener("keydown", this.#boundKeydown); window.addEventListener("keydown", this.#boundKeydown, { signal });
window.addEventListener("keyup", this.#boundKeyup); window.addEventListener("keyup", this.#boundKeyup, { signal });
} }
#removeKeyboardManager() { #removeKeyboardManager() {
@ -1178,9 +1180,10 @@ class AnnotationEditorUIManager {
} }
#addCopyPasteListeners() { #addCopyPasteListeners() {
document.addEventListener("copy", this.#boundCopy); const signal = this._signal;
document.addEventListener("cut", this.#boundCut); document.addEventListener("copy", this.#boundCopy, { signal });
document.addEventListener("paste", this.#boundPaste); document.addEventListener("cut", this.#boundCut, { signal });
document.addEventListener("paste", this.#boundPaste, { signal });
} }
#removeCopyPasteListeners() { #removeCopyPasteListeners() {
@ -1190,13 +1193,9 @@ class AnnotationEditorUIManager {
} }
#addDragAndDropListeners() { #addDragAndDropListeners() {
document.addEventListener("dragover", this.#boundDragOver); const signal = this._signal;
document.addEventListener("drop", this.#boundDrop); document.addEventListener("dragover", this.dragOver.bind(this), { signal });
} document.addEventListener("drop", this.drop.bind(this), { signal });
#removeDragAndDropListeners() {
document.removeEventListener("dragover", this.#boundDragOver);
document.removeEventListener("drop", this.#boundDrop);
} }
addEditListeners() { addEditListeners() {