diff --git a/src/display/editor/editor.js b/src/display/editor/editor.js index 7ea180ad5..959d33ed4 100644 --- a/src/display/editor/editor.js +++ b/src/display/editor/editor.js @@ -119,6 +119,10 @@ class AnnotationEditor { this.deleted = false; } + get editorType() { + return Object.getPrototypeOf(this).constructor._type; + } + static get _defaultLineColor() { return shadow( this, @@ -843,18 +847,6 @@ class AnnotationEditor { this._uiManager.editAltText(this); } }); - const DELAY_TO_SHOW_TOOLTIP = 500; - altText.addEventListener("mouseenter", () => { - this.#altTextTooltipTimeout = setTimeout(() => { - this.#altTextTooltipTimeout = null; - this.#altTextTooltip?.classList.add("show"); - }, DELAY_TO_SHOW_TOOLTIP); - }); - altText.addEventListener("mouseleave", () => { - clearTimeout(this.#altTextTooltipTimeout); - this.#altTextTooltipTimeout = null; - this.#altTextTooltip?.classList.remove("show"); - }); this.#setAltTextButtonState(); this.div.append(altText); if (!AnnotationEditor.SMALL_EDITOR_SIZE) { @@ -881,6 +873,29 @@ class AnnotationEditor { const id = (tooltip.id = `alt-text-tooltip-${this.id}`); button.append(tooltip); button.setAttribute("aria-describedby", id); + + const DELAY_TO_SHOW_TOOLTIP = 500; + button.addEventListener("mouseenter", () => { + this.#altTextTooltipTimeout = setTimeout(() => { + this.#altTextTooltipTimeout = null; + this.#altTextTooltip.classList.add("show"); + this._uiManager._eventBus.dispatch("reporttelemetry", { + source: this, + details: { + type: "editing", + subtype: this.editorType, + data: { + action: "alt_text_tooltip", + }, + }, + }); + }, DELAY_TO_SHOW_TOOLTIP); + }); + button.addEventListener("mouseleave", () => { + clearTimeout(this.#altTextTooltipTimeout); + this.#altTextTooltipTimeout = null; + this.#altTextTooltip?.classList.remove("show"); + }); } button.classList.add("done"); if (this.#altTextDecorative) { diff --git a/src/display/editor/stamp.js b/src/display/editor/stamp.js index 678ac9546..54835317f 100644 --- a/src/display/editor/stamp.js +++ b/src/display/editor/stamp.js @@ -311,6 +311,20 @@ class StampEditor extends AnnotationEditor { this.parent.addUndoableEditor(this); this.#hasBeenAddedInUndoStack = true; } + + // There are multiple ways to add an image to the page, so here we just + // count the number of times an image is added to the page whatever the way + // is. + this._uiManager._eventBus.dispatch("reporttelemetry", { + source: this, + details: { + type: "editing", + subtype: this.editorType, + data: { + action: "inserted_image", + }, + }, + }); this.addAltTextButton(); } diff --git a/src/display/editor/tools.js b/src/display/editor/tools.js index bca94e7e6..ed8b2e0bd 100644 --- a/src/display/editor/tools.js +++ b/src/display/editor/tools.js @@ -541,8 +541,6 @@ class AnnotationEditorUIManager { #editorsToRescale = new Set(); - #eventBus = null; - #filterFactory = null; #idManager = new IdManager(); @@ -706,11 +704,11 @@ class AnnotationEditorUIManager { this.#container = container; this.#viewer = viewer; this.#altTextManager = altTextManager; - this.#eventBus = eventBus; - this.#eventBus._on("editingaction", this.#boundOnEditingAction); - this.#eventBus._on("pagechanging", this.#boundOnPageChanging); - this.#eventBus._on("scalechanging", this.#boundOnScaleChanging); - this.#eventBus._on("rotationchanging", this.#boundOnRotationChanging); + this._eventBus = eventBus; + this._eventBus._on("editingaction", this.#boundOnEditingAction); + this._eventBus._on("pagechanging", this.#boundOnPageChanging); + this._eventBus._on("scalechanging", this.#boundOnScaleChanging); + this._eventBus._on("rotationchanging", this.#boundOnRotationChanging); this.#annotationStorage = pdfDocument.annotationStorage; this.#filterFactory = pdfDocument.filterFactory; this.#pageColors = pageColors; @@ -723,10 +721,10 @@ class AnnotationEditorUIManager { destroy() { this.#removeKeyboardManager(); this.#removeFocusManager(); - this.#eventBus._off("editingaction", this.#boundOnEditingAction); - this.#eventBus._off("pagechanging", this.#boundOnPageChanging); - this.#eventBus._off("scalechanging", this.#boundOnScaleChanging); - this.#eventBus._off("rotationchanging", this.#boundOnRotationChanging); + this._eventBus._off("editingaction", this.#boundOnEditingAction); + this._eventBus._off("pagechanging", this.#boundOnPageChanging); + this._eventBus._off("scalechanging", this.#boundOnScaleChanging); + this._eventBus._off("rotationchanging", this.#boundOnRotationChanging); for (const layer of this.#allLayers.values()) { layer.destroy(); } @@ -1041,7 +1039,7 @@ class AnnotationEditorUIManager { ); if (hasChanged) { - this.#eventBus.dispatch("annotationeditorstateschanged", { + this._eventBus.dispatch("annotationeditorstateschanged", { source: this, details: Object.assign(this.#previousStates, details), }); @@ -1049,7 +1047,7 @@ class AnnotationEditorUIManager { } #dispatchUpdateUI(details) { - this.#eventBus.dispatch("annotationeditorparamschanged", { + this._eventBus.dispatch("annotationeditorparamschanged", { source: this, details, }); @@ -1177,7 +1175,7 @@ class AnnotationEditorUIManager { if (mode === this.#mode) { return; } - this.#eventBus.dispatch("switchannotationeditormode", { + this._eventBus.dispatch("switchannotationeditormode", { source: this, mode, }); diff --git a/web/alt_text_manager.js b/web/alt_text_manager.js index fc3b020ee..5c2734a70 100644 --- a/web/alt_text_manager.js +++ b/web/alt_text_manager.js @@ -13,16 +13,24 @@ * limitations under the License. */ +import { shadow } from "pdfjs-lib"; + class AltTextManager { #boundUpdateUIState = this.#updateUIState.bind(this); #boundSetPosition = this.#setPosition.bind(this); + #boundPointerDown = this.#pointerDown.bind(this); + #currentEditor = null; + #cancelButton; + #dialog; - #eventBus = null; + #eventBus; + + #hasUsedPointer = false; #optionDescription; @@ -36,6 +44,8 @@ class AltTextManager { #uiManager; + #previousAltText = null; + constructor( { dialog, @@ -52,12 +62,13 @@ class AltTextManager { this.#optionDescription = optionDescription; this.#optionDecorative = optionDecorative; this.#textarea = textarea; + this.#cancelButton = cancelButton; this.#saveButton = saveButton; this.#overlayManager = overlayManager; this.#eventBus = eventBus; dialog.addEventListener("close", this.#close.bind(this)); - cancelButton.addEventListener("click", this.#finish.bind(this)); + cancelButton.addEventListener("click", this.#cancel.bind(this)); saveButton.addEventListener("click", this.#save.bind(this)); optionDescription.addEventListener("change", this.#boundUpdateUIState); optionDecorative.addEventListener("change", this.#boundUpdateUIState); @@ -66,11 +77,26 @@ class AltTextManager { this.#overlayManager.register(dialog); } + get _elements() { + return shadow(this, "_elements", [ + this.#optionDescription, + this.#optionDecorative, + this.#textarea, + this.#saveButton, + this.#cancelButton, + ]); + } + async editAltText(uiManager, editor) { if (this.#currentEditor || !editor) { return; } + this.#hasUsedPointer = false; + for (const element of this._elements) { + element.addEventListener("pointerdown", this.#boundPointerDown); + } + const { altText, decorative } = editor.altTextData; if (decorative === true) { this.#optionDecorative.checked = true; @@ -79,7 +105,7 @@ class AltTextManager { this.#optionDecorative.checked = false; this.#optionDescription.checked = true; } - this.#textarea.value = altText?.trim() || ""; + this.#previousAltText = this.#textarea.value = altText?.trim() || ""; this.#updateUIState(); this.#currentEditor = editor; @@ -157,7 +183,23 @@ class AltTextManager { } } + #cancel() { + this.#eventBus.dispatch("reporttelemetry", { + source: this, + details: { + type: "editing", + subtype: this.#currentEditor.editorType, + data: { + action: "alt_text_cancel", + alt_text_keyboard: !this.#hasUsedPointer, + }, + }, + }); + this.#finish(); + } + #close() { + this.#removePointerDownListeners(); this.#uiManager?.addEditListeners(); this.#eventBus._off("resize", this.#boundSetPosition); this.#currentEditor = null; @@ -173,13 +215,41 @@ class AltTextManager { } #save() { + const altText = this.#textarea.value.trim(); + const decorative = this.#optionDecorative.checked; this.#currentEditor.altTextData = { - altText: this.#textarea.value.trim(), - decorative: this.#optionDecorative.checked, + altText, + decorative, }; + this.#eventBus.dispatch("reporttelemetry", { + source: this, + details: { + type: "editing", + subtype: this.#currentEditor.editorType, + data: { + action: "alt_text_save", + alt_text_description: !!altText, + alt_text_edit: + this.#previousAltText && this.#previousAltText !== altText, + alt_text_decorative: decorative, + alt_text_keyboard: !this.#hasUsedPointer, + }, + }, + }); this.#finish(); } + #pointerDown() { + this.#hasUsedPointer = true; + this.#removePointerDownListeners(); + } + + #removePointerDownListeners() { + for (const element of this._elements) { + element.removeEventListener("pointerdown", this.#boundPointerDown); + } + } + destroy() { this.#currentEditor = null; this.#uiManager = null; diff --git a/web/app.js b/web/app.js index 6e9a9b9d2..b9e1e9fd1 100644 --- a/web/app.js +++ b/web/app.js @@ -2012,6 +2012,7 @@ const PDFViewerApplication = { "annotationeditorstateschanged", webViewerAnnotationEditorStatesChanged ); + eventBus._on("reporttelemetry", webViewerReportTelemetry); } }, @@ -2141,6 +2142,10 @@ const PDFViewerApplication = { eventBus._off("openfile", webViewerOpenFile); } + if (typeof PDFJSDev !== "undefined" && PDFJSDev.test("MOZCENTRAL")) { + eventBus._off("reporttelemetry", webViewerReportTelemetry); + } + _boundEvents.beforePrint = null; _boundEvents.afterPrint = null; }, @@ -3289,6 +3294,10 @@ function webViewerAnnotationEditorStatesChanged(data) { PDFViewerApplication.externalServices.updateEditorStates(data); } +function webViewerReportTelemetry({ details }) { + PDFViewerApplication.externalServices.reportTelemetry(details); +} + /* Abstract factory for the print service. */ const PDFPrintServiceFactory = { instance: {