From 92ade5495b8dd900afa5751e8993d0efe5c35da2 Mon Sep 17 00:00:00 2001 From: Calixte Denizet Date: Wed, 7 Aug 2024 15:37:55 +0200 Subject: [PATCH] [Editor] Dispatch changes in prefs enableAltTextModelDownload and enableGuessAltText to the viewer (bug 1912024) The user can change a setting in a tab and it must have an effect in the current viewer. --- src/display/editor/stamp.js | 26 +++++++--- web/app.js | 4 +- web/app_options.js | 4 +- web/firefoxcom.js | 94 ++++++++++++++++++++++++++++++++----- web/genericcom.js | 4 ++ web/new_alt_text_manager.js | 81 +++++++++++++++++++++----------- 6 files changed, 162 insertions(+), 51 deletions(-) diff --git a/src/display/editor/stamp.js b/src/display/editor/stamp.js index 0381f475651ed..fc15ea7270b79 100644 --- a/src/display/editor/stamp.js +++ b/src/display/editor/stamp.js @@ -141,9 +141,11 @@ class StampEditor extends AnnotationEditor { this._uiManager.useNewAltTextFlow && this.#bitmap ) { - // The alt-text dialog isn't opened but we still want to guess the alt - // text. - this.mlGuessAltText(); + try { + // The alt-text dialog isn't opened but we still want to guess the alt + // text. + this.mlGuessAltText(); + } catch {} } this.div.focus(); @@ -155,8 +157,11 @@ class StampEditor extends AnnotationEditor { } const { mlManager } = this._uiManager; - if (!mlManager || !(await mlManager.isEnabledFor("altText"))) { - return null; + if (!mlManager) { + throw new Error("No ML."); + } + if (!(await mlManager.isEnabledFor("altText"))) { + throw new Error("ML isn't enabled for alt text."); } const { data, width, height } = imageData || @@ -170,9 +175,18 @@ class StampEditor extends AnnotationEditor { channels: data.length / (width * height), }, }); - if (!response || response.error || !response.output) { + if (!response) { + throw new Error("No response from the AI service."); + } + if (response.error) { + throw new Error("Error from the AI service."); + } + if (response.cancel) { return null; } + if (!response.output) { + throw new Error("No valid response from the AI service."); + } const altText = response.output; await this.setGuessedAltText(altText); if (updateAltTextData && !this.hasAltTextData()) { diff --git a/web/app.js b/web/app.js index 93b07735b3943..684f543334fce 100644 --- a/web/app.js +++ b/web/app.js @@ -404,9 +404,7 @@ const PDFViewerApplication = { } else { eventBus = new EventBus(); } - if (this.mlManager) { - this.mlManager.eventBus = eventBus; - } + this.mlManager?.setEventBus(eventBus, this._globalAbortController.signal); this.eventBus = eventBus; this.overlayManager = new OverlayManager(); diff --git a/web/app_options.js b/web/app_options.js index 040a08221be7c..53c4b5325dcc9 100644 --- a/web/app_options.js +++ b/web/app_options.js @@ -193,12 +193,12 @@ const defaultOptions = { enableAltTextModelDownload: { /** @type {boolean} */ value: true, - kind: OptionKind.VIEWER + OptionKind.PREFERENCE, + kind: OptionKind.VIEWER + OptionKind.PREFERENCE + OptionKind.EVENT_DISPATCH, }, enableGuessAltText: { /** @type {boolean} */ value: true, - kind: OptionKind.VIEWER + OptionKind.PREFERENCE, + kind: OptionKind.VIEWER + OptionKind.PREFERENCE + OptionKind.EVENT_DISPATCH, }, enableHighlightEditor: { // We'll probably want to make some experiments before enabling this diff --git a/web/firefoxcom.js b/web/firefoxcom.js index 0993e7070e4ff..5329886adc879 100644 --- a/web/firefoxcom.js +++ b/web/firefoxcom.js @@ -307,11 +307,15 @@ class FirefoxScripting { } class MLManager { + #abortSignal = null; + #enabled = null; + #eventBus = null; + #ready = null; - eventBus = null; + #requestResolvers = null; hasProgress = false; @@ -330,6 +334,32 @@ class MLManager { this.enableGuessAltText = enableGuessAltText; } + setEventBus(eventBus, abortSignal) { + this.#eventBus = eventBus; + this.#abortSignal = abortSignal; + eventBus._on( + "enablealttextmodeldownload", + ({ value }) => { + if (this.enableAltTextModelDownload === value) { + return; + } + if (value) { + this.downloadModel("altText"); + } else { + this.deleteModel("altText"); + } + }, + abortSignal + ); + eventBus._on( + "enableguessalttext", + ({ value }) => { + this.toggleService("altText", value); + }, + abortSignal + ); + } + async isEnabledFor(name) { return this.enableGuessAltText && !!(await this.#enabled?.get(name)); } @@ -339,16 +369,17 @@ class MLManager { } async deleteModel(name) { - if (name !== "altText") { + if (name !== "altText" || !this.enableAltTextModelDownload) { return; } this.enableAltTextModelDownload = false; this.#ready?.delete(name); this.#enabled?.delete(name); - await Promise.all([ - this.toggleService("altText", false), - FirefoxCom.requestAsync("mlDelete", MLManager.#AI_ALT_TEXT_MODEL_NAME), - ]); + await this.toggleService("altText", false); + await FirefoxCom.requestAsync( + "mlDelete", + MLManager.#AI_ALT_TEXT_MODEL_NAME + ); } async loadModel(name) { @@ -358,7 +389,7 @@ class MLManager { } async downloadModel(name) { - if (name !== "altText") { + if (name !== "altText" || this.enableAltTextModelDownload) { return null; } this.enableAltTextModelDownload = true; @@ -369,18 +400,53 @@ class MLManager { if (data?.name !== "altText") { return null; } + const resolvers = (this.#requestResolvers ||= new Set()); + const resolver = Promise.withResolvers(); + resolvers.add(resolver); + data.service = MLManager.#AI_ALT_TEXT_MODEL_NAME; - return FirefoxCom.requestAsync("mlGuess", data); + const requestPromise = FirefoxCom.requestAsync("mlGuess", data); + + requestPromise + .then(response => { + if (resolvers.has(resolver)) { + resolver.resolve(response); + resolvers.delete(resolver); + } + }) + .catch(reason => { + if (resolvers.has(resolver)) { + resolver.reject(reason); + resolvers.delete(resolver); + } + }); + + return resolver.promise; + } + + async #cancelAllRequests() { + if (!this.#requestResolvers) { + return; + } + for (const resolver of this.#requestResolvers) { + resolver.resolve({ cancel: true }); + } + this.#requestResolvers.clear(); + this.#requestResolvers = null; } async toggleService(name, enabled) { - if (name !== "altText") { + if (name !== "altText" || this.enableGuessAltText === enabled) { return; } this.enableGuessAltText = enabled; - if (enabled && this.enableAltTextModelDownload) { - await this.#loadAltTextEngine(false); + if (enabled) { + if (this.enableAltTextModelDownload) { + await this.#loadAltTextEngine(false); + } + } else { + this.#cancelAllRequests(); } } @@ -403,7 +469,7 @@ class MLManager { if (listenToProgress) { this.hasProgress = true; const callback = ({ detail }) => { - this.eventBus.dispatch("loadaiengineprogress", { + this.#eventBus.dispatch("loadaiengineprogress", { source: this, detail, }); @@ -412,7 +478,9 @@ class MLManager { window.removeEventListener("loadAIEngineProgress", callback); } }; - window.addEventListener("loadAIEngineProgress", callback); + window.addEventListener("loadAIEngineProgress", callback, { + signal: this.#abortSignal, + }); promise.then(ok => { if (!ok) { this.hasProgress = false; diff --git a/web/genericcom.js b/web/genericcom.js index 4abde7e8ad980..9bddcbc456eb5 100644 --- a/web/genericcom.js +++ b/web/genericcom.js @@ -79,6 +79,10 @@ class FakeMLManager { this.enableAltTextModelDownload = enableAltTextModelDownload; } + setEventBus(eventBus, abortSignal) { + this.eventBus = eventBus; + } + async isEnabledFor(_name) { return this.enableGuessAltText; } diff --git a/web/new_alt_text_manager.js b/web/new_alt_text_manager.js index a89ac4575b054..58d522b3bc8a3 100644 --- a/web/new_alt_text_manager.js +++ b/web/new_alt_text_manager.js @@ -137,6 +137,10 @@ class NewAltTextManager { this.#toggleDisclaimer(); }); + eventBus._on("enableguessalttext", ({ value }) => { + this.#toggleGuessAltText(value, /* isInitial = */ false); + }); + this.#overlayManager.register(dialog); } @@ -247,13 +251,12 @@ class NewAltTextManager { this.#imageData, /* updateAltTextData = */ false ); - if (altText === null) { - throw new Error("No valid response from the AI service."); - } - this.#guessedAltText = altText; - this.#wasAILoading = this.#isAILoading; - if (this.#isAILoading) { - this.#addAltText(altText); + if (altText) { + this.#guessedAltText = altText; + this.#wasAILoading = this.#isAILoading; + if (this.#isAILoading) { + this.#addAltText(altText); + } } } catch (e) { console.error(e); @@ -458,6 +461,8 @@ class ImageAltTextSettings { #createModelButton; + #downloadModelButton; + #dialog; #eventBus; @@ -486,6 +491,7 @@ class ImageAltTextSettings { this.#dialog = dialog; this.#aiModelSettings = aiModelSettings; this.#createModelButton = createModelButton; + this.#downloadModelButton = downloadModelButton; this.#showAltTextDialogButton = showAltTextDialogButton; this.#overlayManager = overlayManager; this.#eventBus = eventBus; @@ -508,40 +514,61 @@ class ImageAltTextSettings { this.#togglePref.bind(this, "enableNewAltTextWhenAddingImage") ); - deleteModelButton.addEventListener("click", async () => { - await mlManager.deleteModel("altText"); + deleteModelButton.addEventListener("click", this.#delete.bind(this, true)); + downloadModelButton.addEventListener( + "click", + this.#download.bind(this, true) + ); + + closeButton.addEventListener("click", this.#finish.bind(this)); - aiModelSettings.classList.toggle("download", true); - createModelButton.disabled = true; - createModelButton.setAttribute("aria-pressed", false); - this.#setPref("enableGuessAltText", false); - this.#setPref("enableAltTextModelDownload", false); + eventBus._on("enablealttextmodeldownload", ({ value }) => { + if (value) { + this.#download(false); + } else { + this.#delete(false); + } }); - downloadModelButton.addEventListener("click", async () => { - downloadModelButton.disabled = true; - downloadModelButton.firstChild.setAttribute( + this.#overlayManager.register(dialog); + } + + async #download(isFromUI = false) { + if (isFromUI) { + this.#downloadModelButton.disabled = true; + this.#downloadModelButton.firstChild.setAttribute( "data-l10n-id", "pdfjs-editor-alt-text-settings-downloading-model-button" ); - await mlManager.downloadModel("altText"); + await this.#mlManager.downloadModel("altText"); - aiModelSettings.classList.toggle("download", false); - downloadModelButton.firstChild.setAttribute( + this.#downloadModelButton.firstChild.setAttribute( "data-l10n-id", "pdfjs-editor-alt-text-settings-download-model-button" ); - createModelButton.disabled = false; - createModelButton.setAttribute("aria-pressed", true); + + this.#createModelButton.disabled = false; this.#setPref("enableGuessAltText", true); - mlManager.toggleService("altText", true); + this.#mlManager.toggleService("altText", true); this.#setPref("enableAltTextModelDownload", true); - downloadModelButton.disabled = false; - }); + this.#downloadModelButton.disabled = false; + } - closeButton.addEventListener("click", this.#finish.bind(this)); - this.#overlayManager.register(dialog); + this.#aiModelSettings.classList.toggle("download", false); + this.#createModelButton.setAttribute("aria-pressed", true); + } + + async #delete(isFromUI = false) { + if (isFromUI) { + await this.#mlManager.deleteModel("altText"); + this.#setPref("enableGuessAltText", false); + this.#setPref("enableAltTextModelDownload", false); + } + + this.#aiModelSettings.classList.toggle("download", true); + this.#createModelButton.disabled = true; + this.#createModelButton.setAttribute("aria-pressed", false); } async open({ enableGuessAltText, enableNewAltTextWhenAddingImage }) {