summaryrefslogtreecommitdiffstats
path: root/toolkit/components/translations/content
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-06-12 05:43:14 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-06-12 05:43:14 +0000
commit8dd16259287f58f9273002717ec4d27e97127719 (patch)
tree3863e62a53829a84037444beab3abd4ed9dfc7d0 /toolkit/components/translations/content
parentReleasing progress-linux version 126.0.1-1~progress7.99u1. (diff)
downloadfirefox-8dd16259287f58f9273002717ec4d27e97127719.tar.xz
firefox-8dd16259287f58f9273002717ec4d27e97127719.zip
Merging upstream version 127.0.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/components/translations/content')
-rw-r--r--toolkit/components/translations/content/Translator.mjs137
-rw-r--r--toolkit/components/translations/content/translations-document.sys.mjs43
-rw-r--r--toolkit/components/translations/content/translations.mjs5
3 files changed, 169 insertions, 16 deletions
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<Translator>}
+ * @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<Translator | PassthroughTranslator>}
*/
- 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<void>} 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<string>}
+ */
+ 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
@@ -332,6 +332,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
* manage the life cycle of the translations engines.
@@ -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(
@@ -1836,6 +1860,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.
*/
#nextMessageId = 0;
@@ -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;