From 8dd16259287f58f9273002717ec4d27e97127719 Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Wed, 12 Jun 2024 07:43:14 +0200 Subject: Merging upstream version 127.0. Signed-off-by: Daniel Baumann --- .../components/translations/content/Translator.mjs | 137 +++++++++++++++++++-- .../content/translations-document.sys.mjs | 43 ++++++- .../translations/content/translations.mjs | 5 +- 3 files changed, 169 insertions(+), 16 deletions(-) (limited to 'toolkit/components/translations/content') diff --git a/toolkit/components/translations/content/Translator.mjs b/toolkit/components/translations/content/Translator.mjs index 9a0de6a2c2..bef82a5fc1 100644 --- a/toolkit/components/translations/content/Translator.mjs +++ b/toolkit/components/translations/content/Translator.mjs @@ -79,7 +79,6 @@ export class Translator { this.#fromLanguage = fromLanguage; this.#toLanguage = toLanguage; this.#requestTranslationsPort = requestTranslationsPort; - this.#createNewPortIfClosed(); } /** @@ -113,13 +112,41 @@ export class Translator { /** * Opens up a port and creates a new translator. * - * @param {string} fromLanguage - * @param {string} toLanguage - * @returns {Promise} + * @param {string} fromLanguage - The BCP-47 language tag of the from-language. + * @param {string} toLanguage - The BCP-47 language tag of the to-language. + * @param {object} data - Data for creating a translator. + * @param {Function} [data.requestTranslationsPort] + * - A function to request a translations port for communication with the Translations engine. + * This is required in all cases except if allowSameLanguage is true and the fromLanguage + * is the same as the toLanguage. + * @param {boolean} [data.allowSameLanguage] + * - Whether to allow or disallow the creation of a PassthroughTranslator in the event + * that the fromLanguage and the toLanguage are the same language. + * + * @returns {Promise} */ - static async create(fromLanguage, toLanguage, requestTranslationsPort) { - if (!fromLanguage || !toLanguage || !requestTranslationsPort) { - return undefined; + static async create( + fromLanguage, + toLanguage, + { requestTranslationsPort, allowSameLanguage } + ) { + if (!fromLanguage || !toLanguage) { + throw new Error( + "Attempt to create Translator with missing language tags." + ); + } + + if (fromLanguage === toLanguage) { + if (!allowSameLanguage) { + throw new Error("Attempt to create disallowed PassthroughTranslator"); + } + return new PassthroughTranslator(fromLanguage, toLanguage); + } + + if (!requestTranslationsPort) { + throw new Error( + "Attempt to create Translator without a requestTranslationsPort function" + ); } const translator = new Translator( @@ -127,7 +154,7 @@ export class Translator { toLanguage, requestTranslationsPort ); - await translator.ready; + await translator.#createNewPortIfClosed(); return translator; } @@ -139,13 +166,14 @@ export class Translator { */ async #createNewPortIfClosed() { if (!this.#portClosed) { - return this.#ready; + return; } this.#port = await this.#requestTranslationsPort( this.#fromLanguage, this.#toLanguage ); + this.#portClosed = false; // Create a promise that will be resolved when the engine is ready. const { promise, resolve, reject } = Promise.withResolvers(); @@ -162,7 +190,6 @@ export class Translator { } case "TranslationsPort:GetEngineStatusResponse": { if (data.status === "ready") { - this.#portClosed = false; resolve(); } else { this.#portClosed = true; @@ -181,8 +208,6 @@ export class Translator { this.#ready = promise; this.#port.postMessage({ type: "TranslationsPort:GetEngineStatusRequest" }); - - return this.#ready; } /** @@ -195,6 +220,8 @@ export class Translator { */ async translate(sourceText, isHTML = false) { await this.#createNewPortIfClosed(); + await this.#ready; + const { promise, resolve, reject } = Promise.withResolvers(); const messageId = this.#nextMessageId++; @@ -225,3 +252,89 @@ export class Translator { this.#ready = Promise.reject; } } + +/** + * The PassthroughTranslator class mimics the same API as the Translator class, + * but it does not create any message ports for actual translation. This class + * may only be constructed with the same fromLanguage and toLanguage value, and + * instead of translating, it just passes through the source text as the translated + * text. + * + * The Translator class may return a PassthroughTranslator instance if the fromLanguage + * and toLanguage passed to the create() method are the same. + * + * @see Translator.create + */ +class PassthroughTranslator { + /** + * The BCP-47 language tag for the from-language and the to-language. + * + * @type {string} + */ + #language; + + /** + * @returns {Promise} A promise that indicates if the Translator is ready to translate. + */ + get ready() { + return Promise.resolve; + } + + /** + * @returns {boolean} Always false for PassthroughTranslator because there is no port. + */ + get portClosed() { + return false; + } + + /** + * @returns {string} The BCP-47 language tag of the from-language. + */ + get fromLanguage() { + return this.#language; + } + + /** + * @returns {string} The BCP-47 language tag of the to-language. + */ + get toLanguage() { + return this.#language; + } + + /** + * Initializes a new PassthroughTranslator. + * + * Prefer using the Translator.create() function. + * + * @see Translator.create + * + * @param {string} fromLanguage - The BCP-47 from-language tag. + * @param {string} toLanguage - The BCP-47 to-language tag. + */ + constructor(fromLanguage, toLanguage) { + if (fromLanguage !== toLanguage) { + throw new Error( + "Attempt to create PassthroughTranslator with different fromLanguage and toLanguage." + ); + } + this.#language = fromLanguage; + } + + /** + * Passes through the source text as if it was translated. + * + * @returns {Promise} + */ + async translate(sourceText) { + return Promise.resolve(sourceText); + } + + /** + * There is nothing to destroy in the PassthroughTranslator class. + * This function is implemented to maintain the same API surface as + * the Translator class. + * + * @see Translator + */ + destroy() {} +} diff --git a/toolkit/components/translations/content/translations-document.sys.mjs b/toolkit/components/translations/content/translations-document.sys.mjs index ed75fe9ec6..dd64e5a829 100644 --- a/toolkit/components/translations/content/translations-document.sys.mjs +++ b/toolkit/components/translations/content/translations-document.sys.mjs @@ -331,6 +331,13 @@ export class TranslationsDocument { isDestroyed = false; + /** + * This boolean indicates whether the first visible DOM translation change is about to occur. + * + * @type {boolean} + */ + hasFirstVisibleChange = false; + /** * Construct a new TranslationsDocument. It is tied to a specific Document and cannot * be re-used. The translation functions are injected since this class shouldn't @@ -343,6 +350,8 @@ export class TranslationsDocument { * @param {MessagePort} port - The port to the translations engine. * @param {() => void} requestNewPort - Used when an engine times out and a new * translation request comes in. + * @param {() => void} reportVisibleChange - Used to report to the actor that the first visible change + * for a translation is about to occur. * @param {number} translationsStart * @param {() => number} now * @param {LRUCache} translationsCache @@ -354,6 +363,7 @@ export class TranslationsDocument { innerWindowId, port, requestNewPort, + reportVisibleChange, translationsStart, now, translationsCache @@ -379,7 +389,11 @@ export class TranslationsDocument { } /** @type {QueuedTranslator} */ - this.translator = new QueuedTranslator(port, requestNewPort); + this.translator = new QueuedTranslator( + port, + requestNewPort, + reportVisibleChange + ); /** @type {number} */ this.innerWindowId = innerWindowId; @@ -393,6 +407,9 @@ export class TranslationsDocument { /** @type {LRUCache} */ this.translationsCache = translationsCache; + /** @type {() => void} */ + this.actorReportFirstVisibleChange = reportVisibleChange; + /** * This selector runs to find child nodes that should be excluded. It should be * basically the same implementation of `isExcludedNode`, but as a selector. @@ -1143,8 +1160,10 @@ export class TranslationsDocument { if (translation === undefined) { translation = await this.translator.translate(node, text, isHTML); this.translationsCache.set(text, translation, isHTML); + } else if (!this.hasFirstVisibleChange) { + this.hasFirstVisibleChange = true; + this.actorReportFirstVisibleChange(); } - return translation; } catch (error) { lazy.console.log("Translation failed", error); @@ -1573,6 +1592,11 @@ function updateElement(translationsDocument, element) { ([, element]) => !element.parentNode ); + for (node of liveTree.querySelectorAll("*")) { + // Clean-up the translation ids. + delete node.dataset.mozTranslationsId; + } + if (unhandledElements.length) { lazy.console.warn( `${createNodePath( @@ -1835,6 +1859,13 @@ class QueuedTranslator { */ #actorRequestNewPort; + /** + * Send a message to the actor that the first visible DOM translation change is about to occur. + * + * @type {() => void} + */ + #actorReportFirstVisibleChange; + /** * An id for each message sent. This is used to match up the request and response. */ @@ -1863,9 +1894,11 @@ class QueuedTranslator { /** * @param {MessagePort} port * @param {() => void} actorRequestNewPort + * @param {() => void} actorReportFirstVisibleChange */ - constructor(port, actorRequestNewPort) { + constructor(port, actorRequestNewPort, actorReportFirstVisibleChange) { this.#actorRequestNewPort = actorRequestNewPort; + this.#actorReportFirstVisibleChange = actorReportFirstVisibleChange; this.acquirePort(port); } @@ -2087,6 +2120,10 @@ class QueuedTranslator { port.onmessage = ({ data }) => { switch (data.type) { case "TranslationsPort:TranslationResponse": { + if (!this.hasFirstVisibleChange) { + this.hasFirstVisibleChange = true; + this.#actorReportFirstVisibleChange(); + } const { targetText, messageId } = data; // A request may not match match a messageId if there is a race during the pausing // and discarding of the queue. diff --git a/toolkit/components/translations/content/translations.mjs b/toolkit/components/translations/content/translations.mjs index d5541408d4..c944ee75ea 100644 --- a/toolkit/components/translations/content/translations.mjs +++ b/toolkit/components/translations/content/translations.mjs @@ -250,7 +250,10 @@ class TranslationsState { const translatorPromise = Translator.create( this.fromLanguage, this.toLanguage, - translationPortPromise + { + allowSameLanguage: false, + requestTranslationsPort: translationPortPromise, + } ); const duration = performance.now() - start; -- cgit v1.2.3