summaryrefslogtreecommitdiffstats
path: root/toolkit/components/translations
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/translations')
-rw-r--r--toolkit/components/translations/TranslationsTelemetry.sys.mjs3
-rw-r--r--toolkit/components/translations/actors/AboutTranslationsChild.sys.mjs12
-rw-r--r--toolkit/components/translations/actors/AboutTranslationsParent.sys.mjs6
-rw-r--r--toolkit/components/translations/actors/TranslationsChild.sys.mjs1
-rw-r--r--toolkit/components/translations/actors/TranslationsEngineChild.sys.mjs6
-rw-r--r--toolkit/components/translations/actors/TranslationsEngineParent.sys.mjs13
-rw-r--r--toolkit/components/translations/actors/TranslationsParent.sys.mjs219
-rw-r--r--toolkit/components/translations/content/translations-document.sys.mjs74
-rw-r--r--toolkit/components/translations/content/translations-engine.sys.mjs8
-rw-r--r--toolkit/components/translations/content/translations.mjs7
-rw-r--r--toolkit/components/translations/tests/browser/browser_translations_translation_document.js100
-rw-r--r--toolkit/components/translations/tests/browser/shared-head.js142
-rw-r--r--toolkit/components/translations/tests/browser/translations-test.mjs2
-rw-r--r--toolkit/components/translations/translations.d.ts2
14 files changed, 331 insertions, 264 deletions
diff --git a/toolkit/components/translations/TranslationsTelemetry.sys.mjs b/toolkit/components/translations/TranslationsTelemetry.sys.mjs
index f01ee1d144..8550a1c1af 100644
--- a/toolkit/components/translations/TranslationsTelemetry.sys.mjs
+++ b/toolkit/components/translations/TranslationsTelemetry.sys.mjs
@@ -41,6 +41,7 @@ export class TranslationsTelemetry {
/**
* Telemetry functions for the Translations panel.
+ *
* @returns {Panel}
*/
static panel() {
@@ -49,6 +50,7 @@ export class TranslationsTelemetry {
/**
* Forces the creation of a new Translations telemetry flowId and returns it.
+ *
* @returns {string}
*/
static createFlowId() {
@@ -60,6 +62,7 @@ export class TranslationsTelemetry {
/**
* Returns a Translations telemetry flowId by retrieving the cached value
* if available, or creating a new one otherwise.
+ *
* @returns {string}
*/
static getOrCreateFlowId() {
diff --git a/toolkit/components/translations/actors/AboutTranslationsChild.sys.mjs b/toolkit/components/translations/actors/AboutTranslationsChild.sys.mjs
index c501c1b0cd..9d0b27a6a1 100644
--- a/toolkit/components/translations/actors/AboutTranslationsChild.sys.mjs
+++ b/toolkit/components/translations/actors/AboutTranslationsChild.sys.mjs
@@ -84,17 +84,6 @@ export class AboutTranslationsChild extends JSWindowActorChild {
}
/**
- * @returns {TranslationsChild}
- */
- #getTranslationsChild() {
- const child = this.contentWindow.windowGlobalChild.getActor("Translations");
- if (!child) {
- throw new Error("Unable to find the TranslationsChild");
- }
- return child;
- }
-
- /**
* A privileged promise can't be used in the content page, so convert a privileged
* promise into a content one.
*
@@ -194,6 +183,7 @@ export class AboutTranslationsChild extends JSWindowActorChild {
/**
* Does this device support the translation engine?
+ *
* @returns {Promise<boolean>}
*/
AT_isTranslationEngineSupported() {
diff --git a/toolkit/components/translations/actors/AboutTranslationsParent.sys.mjs b/toolkit/components/translations/actors/AboutTranslationsParent.sys.mjs
index 4680dbeef5..236e17bbc3 100644
--- a/toolkit/components/translations/actors/AboutTranslationsParent.sys.mjs
+++ b/toolkit/components/translations/actors/AboutTranslationsParent.sys.mjs
@@ -5,6 +5,7 @@
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
TranslationsParent: "resource://gre/actors/TranslationsParent.sys.mjs",
+ EngineProcess: "chrome://global/content/ml/EngineProcess.sys.mjs",
});
/**
@@ -22,12 +23,13 @@ export class AboutTranslationsParent extends JSWindowActorParent {
switch (name) {
case "AboutTranslations:GetTranslationsPort": {
const { fromLanguage, toLanguage } = data;
- const engineProcess = await lazy.TranslationsParent.getEngineProcess();
+ const translationsEngineParent =
+ await lazy.EngineProcess.getTranslationsEngineParent();
if (this.#isDestroyed) {
return undefined;
}
const { port1, port2 } = new MessageChannel();
- engineProcess.actor.startTranslation(
+ translationsEngineParent.startTranslation(
fromLanguage,
toLanguage,
port1,
diff --git a/toolkit/components/translations/actors/TranslationsChild.sys.mjs b/toolkit/components/translations/actors/TranslationsChild.sys.mjs
index a3f8d15c85..d318b7284f 100644
--- a/toolkit/components/translations/actors/TranslationsChild.sys.mjs
+++ b/toolkit/components/translations/actors/TranslationsChild.sys.mjs
@@ -24,6 +24,7 @@ export class TranslationsChild extends JSWindowActorChild {
/**
* This cache is shared across TranslationsChild instances. This means
* that it will be shared across multiple page loads in the same origin.
+ *
* @type {LRUCache | null}
*/
static #translationsCache = null;
diff --git a/toolkit/components/translations/actors/TranslationsEngineChild.sys.mjs b/toolkit/components/translations/actors/TranslationsEngineChild.sys.mjs
index a4ab8e2640..d638858e52 100644
--- a/toolkit/components/translations/actors/TranslationsEngineChild.sys.mjs
+++ b/toolkit/components/translations/actors/TranslationsEngineChild.sys.mjs
@@ -19,6 +19,7 @@ export class TranslationsEngineChild extends JSWindowActorChild {
/**
* The resolve function for the Promise returned by the
* "TranslationsEngine:ForceShutdown" message.
+ *
* @type {null | () => {}}
*/
#resolveForceShutdown = null;
@@ -130,9 +131,10 @@ export class TranslationsEngineChild extends JSWindowActorChild {
}
/**
- * @param {Object} options
+ * @param {object} options
* @param {number?} options.startTime
* @param {string} options.message
+ * @param {number} options.innerWindowId
*/
TE_addProfilerMarker({ startTime, message, innerWindowId }) {
ChromeUtils.addProfilerMarker(
@@ -199,7 +201,7 @@ export class TranslationsEngineChild extends JSWindowActorChild {
}
/**
- * No engines are still alive, destroy the process.
+ * No engines are still alive, signal that the process can be destroyed.
*/
TE_destroyEngineProcess() {
this.sendAsyncMessage("TranslationsEngine:DestroyEngineProcess");
diff --git a/toolkit/components/translations/actors/TranslationsEngineParent.sys.mjs b/toolkit/components/translations/actors/TranslationsEngineParent.sys.mjs
index 77b16d7ae9..0b35117d7a 100644
--- a/toolkit/components/translations/actors/TranslationsEngineParent.sys.mjs
+++ b/toolkit/components/translations/actors/TranslationsEngineParent.sys.mjs
@@ -5,6 +5,7 @@
const lazy = {};
ChromeUtils.defineESModuleGetters(lazy, {
TranslationsParent: "resource://gre/actors/TranslationsParent.sys.mjs",
+ EngineProcess: "chrome://global/content/ml/EngineProcess.sys.mjs",
});
/**
@@ -22,12 +23,12 @@ export class TranslationsEngineParent extends JSWindowActorParent {
async receiveMessage({ name, data }) {
switch (name) {
case "TranslationsEngine:Ready":
- if (!lazy.TranslationsParent.resolveEngine) {
+ if (!lazy.EngineProcess.resolveTranslationsEngineParent) {
throw new Error(
"Unable to find the resolve function for when the translations engine is ready."
);
}
- lazy.TranslationsParent.resolveEngine(this);
+ lazy.EngineProcess.resolveTranslationsEngineParent(this);
return undefined;
case "TranslationsEngine:RequestEnginePayload": {
const { fromLanguage, toLanguage } = data;
@@ -62,12 +63,7 @@ export class TranslationsEngineParent extends JSWindowActorParent {
return undefined;
}
case "TranslationsEngine:DestroyEngineProcess":
- ChromeUtils.addProfilerMarker(
- "TranslationsEngine",
- {},
- "Loading bergamot wasm array buffer"
- );
- lazy.TranslationsParent.destroyEngineProcess().catch(error =>
+ lazy.EngineProcess.destroyTranslationsEngine().catch(error =>
console.error(error)
);
return undefined;
@@ -79,7 +75,6 @@ export class TranslationsEngineParent extends JSWindowActorParent {
/**
* @param {string} fromLanguage
* @param {string} toLanguage
- * @param {number} innerWindowId
* @param {MessagePort} port
* @param {number} innerWindowId
* @param {TranslationsParent} [translationsParent]
diff --git a/toolkit/components/translations/actors/TranslationsParent.sys.mjs b/toolkit/components/translations/actors/TranslationsParent.sys.mjs
index 44b761e6b0..70754d95c4 100644
--- a/toolkit/components/translations/actors/TranslationsParent.sys.mjs
+++ b/toolkit/components/translations/actors/TranslationsParent.sys.mjs
@@ -58,7 +58,7 @@ ChromeUtils.defineESModuleGetters(lazy, {
setTimeout: "resource://gre/modules/Timer.sys.mjs",
TranslationsTelemetry:
"chrome://global/content/translations/TranslationsTelemetry.sys.mjs",
- HiddenFrame: "resource://gre/modules/HiddenFrame.sys.mjs",
+ EngineProcess: "chrome://global/content/ml/EngineProcess.sys.mjs",
});
ChromeUtils.defineLazyGetter(lazy, "console", () => {
@@ -142,11 +142,11 @@ const VERIFY_SIGNATURES_FROM_FS = false;
*/
/**
- * @typedef {Object} TranslationPair
- * @prop {string} fromLanguage
- * @prop {string} toLanguage
- * @prop {string} [fromDisplayLanguage]
- * @prop {string} [toDisplayLanguage]
+ * @typedef {object} TranslationPair
+ * @property {string} fromLanguage
+ * @property {string} toLanguage
+ * @property {string} [fromDisplayLanguage]
+ * @property {string} [toDisplayLanguage]
*/
/**
@@ -332,6 +332,7 @@ export class TranslationsParent extends JSWindowActorParent {
/**
* Telemetry functions for Translations
+ *
* @returns {TranslationsTelemetry}
*/
static telemetry() {
@@ -349,108 +350,9 @@ export class TranslationsParent extends JSWindowActorParent {
}
/**
- * @type {Promise<{ hiddenFrame: HiddenFrame, actor: TranslationsEngineParent }> | null}
- */
- static #engine = null;
-
- static async getEngineProcess() {
- if (!TranslationsParent.#engine) {
- TranslationsParent.#engine = TranslationsParent.#getEngineProcessImpl();
- }
- const enginePromise = TranslationsParent.#engine;
-
- // Determine if the actor was destroyed, or if there was an error. In this case
- // attempt to rebuild the process.
- let needsRebuilding = true;
- try {
- const { actor } = await enginePromise;
- needsRebuilding = actor.isDestroyed;
- } catch {}
-
- if (
- TranslationsParent.#engine &&
- enginePromise !== TranslationsParent.#engine
- ) {
- // This call lost the race, something else updated the engine promise, return that.
- return TranslationsParent.#engine;
- }
-
- if (needsRebuilding) {
- // The engine was destroyed, attempt to re-create the engine process.
- const rebuild = TranslationsParent.destroyEngineProcess().then(() =>
- TranslationsParent.#getEngineProcessImpl()
- );
- TranslationsParent.#engine = rebuild;
- return rebuild;
- }
-
- return enginePromise;
- }
-
- static destroyEngineProcess() {
- const enginePromise = this.#engine;
- this.#engine = null;
- if (enginePromise) {
- ChromeUtils.addProfilerMarker(
- "TranslationsParent",
- {},
- "Destroying the translations engine process"
- );
- return enginePromise.then(({ actor, hiddenFrame }) =>
- actor
- .forceShutdown()
- .catch(error => {
- lazy.console.error(
- "There was an error shutting down the engine.",
- error
- );
- })
- .then(() => {
- hiddenFrame.destroy();
- })
- );
- }
- return Promise.resolve();
- }
-
- /**
- * @type {Promise<{ hiddenFrame: HiddenFrame, actor: TranslationsEngineParent }> | null}
- */
- static async #getEngineProcessImpl() {
- ChromeUtils.addProfilerMarker(
- "TranslationsParent",
- {},
- "Creating the translations engine process"
- );
-
- // Manages the hidden ChromeWindow.
- const hiddenFrame = new lazy.HiddenFrame();
- const chromeWindow = await hiddenFrame.get();
- const doc = chromeWindow.document;
-
- const actorPromise = new Promise(resolve => {
- this.resolveEngine = resolve;
- });
-
- const browser = doc.createXULElement("browser");
- browser.setAttribute("remote", "true");
- browser.setAttribute("remoteType", "web");
- browser.setAttribute("disableglobalhistory", "true");
- browser.setAttribute("type", "content");
- browser.setAttribute(
- "src",
- "chrome://global/content/translations/translations-engine.html"
- );
- doc.documentElement.appendChild(browser);
-
- const actor = await actorPromise;
- this.resolveEngine = null;
- return { hiddenFrame, browser, actor };
- }
-
- /**
* Offer translations (for instance by automatically opening the popup panel) whenever
* languages are detected, but only do it once per host per session.
+ *
* @param {LangTags} detectedLanguages
*/
maybeOfferTranslations(detectedLanguages) {
@@ -559,10 +461,6 @@ export class TranslationsParent extends JSWindowActorParent {
detectedLanguages
);
- TranslationsParent.getEngineProcess().catch(error =>
- console.error(error)
- );
-
browser.dispatchEvent(
new CustomEvent("TranslationsParent:OfferTranslation", {
bubbles: true,
@@ -616,7 +514,7 @@ export class TranslationsParent extends JSWindowActorParent {
* about:* pages will not be translated. Keep this logic up to date with the "matches"
* array in the `toolkit/modules/ActorManagerParent.sys.mjs` definition.
*
- * @param {string} scheme - The URI spec
+ * @param {object} gBrowser
* @returns {boolean}
*/
static isRestrictedPage(gBrowser) {
@@ -657,6 +555,7 @@ export class TranslationsParent extends JSWindowActorParent {
/**
* Provide a way for tests to override the system locales.
+ *
* @type {null | string[]}
*/
static mockedSystemLocales = null;
@@ -814,9 +713,9 @@ export class TranslationsParent extends JSWindowActorParent {
return undefined;
}
- let engineProcess;
+ let actor;
try {
- engineProcess = await TranslationsParent.getEngineProcess();
+ actor = await lazy.EngineProcess.getTranslationsEngineParent();
} catch (error) {
console.error("Failed to get the translation engine process", error);
return undefined;
@@ -836,7 +735,7 @@ export class TranslationsParent extends JSWindowActorParent {
// The MessageChannel will be used for communicating directly between the content
// process and the engine's process.
const { port1, port2 } = new MessageChannel();
- engineProcess.actor.startTranslation(
+ actor.startTranslation(
requestedTranslationPair.fromLanguage,
requestedTranslationPair.toLanguage,
port1,
@@ -952,6 +851,7 @@ export class TranslationsParent extends JSWindowActorParent {
/**
* The cached language pairs.
+ *
* @type {Promise<Array<LanguagePair>> | null}
*/
static #languagePairs = null;
@@ -1041,8 +941,8 @@ export class TranslationsParent extends JSWindowActorParent {
/**
* Create a unique list of languages, sorted by the display name.
*
- * @param {Object} supportedLanguages
- * @returns {Array<{ langTag: string, displayName: string}}
+ * @param {object} supportedLanguages
+ * @returns {Array<{ langTag: string, displayName: string}>}
*/
static getLanguageList(supportedLanguages) {
const displayNames = new Map();
@@ -1070,8 +970,8 @@ export class TranslationsParent extends JSWindowActorParent {
}
/**
- * @param {Object} event
- * @param {Object} event.data
+ * @param {object} event
+ * @param {object} event.data
* @param {TranslationModelRecord[]} event.data.created
* @param {TranslationModelRecord[]} event.data.updated
* @param {TranslationModelRecord[]} event.data.deleted
@@ -1147,12 +1047,13 @@ export class TranslationsParent extends JSWindowActorParent {
* then only the 1.1-version record will be returned in the resulting collection.
*
* @param {RemoteSettingsClient} remoteSettingsClient
- * @param {Object} [options]
- * @param {Object} [options.filters={}]
+ * @param {object} [options]
+ * @param {object} [options.filters={}]
* The filters to apply when retrieving the records from RemoteSettings.
* Filters should correspond to properties on the RemoteSettings records themselves.
* For example, A filter to retrieve only records with a `fromLang` value of "en" and a `toLang` value of "es":
* { filters: { fromLang: "en", toLang: "es" } }
+ * @param {number} options.majorVersion
* @param {Function} [options.lookupKey=(record => record.name)]
* The function to use to extract a lookup key from each record.
* This function should take a record as input and return a string that represents the lookup key for the record.
@@ -1537,7 +1438,7 @@ export class TranslationsParent extends JSWindowActorParent {
/**
* Deletes language files that match a language.
*
- * @param {string} requestedLanguage The BCP 47 language tag.
+ * @param {string} language The BCP 47 language tag.
*/
static async deleteLanguageFiles(language) {
const client = TranslationsParent.#getTranslationModelsRemoteClient();
@@ -1558,7 +1459,7 @@ export class TranslationsParent extends JSWindowActorParent {
/**
* Download language files that match a language.
*
- * @param {string} requestedLanguage The BCP 47 language tag.
+ * @param {string} language The BCP 47 language tag.
*/
static async downloadLanguageFiles(language) {
const client = TranslationsParent.#getTranslationModelsRemoteClient();
@@ -1607,6 +1508,7 @@ export class TranslationsParent extends JSWindowActorParent {
/**
* Delete all language model files.
+ *
* @returns {Promise<string[]>} A list of record IDs.
*/
static async deleteAllLanguageFiles() {
@@ -1867,8 +1769,10 @@ export class TranslationsParent extends JSWindowActorParent {
* @param {string} fromLanguage
* @param {string} toLanguage
* @param {boolean} withQualityEstimation
- * @returns {Promise<{downloadSize: long, modelFound: boolean}> Download size is the size in bytes of the estimated download for display purposes. Model found indicates a model was found.
- * e.g., a result of {size: 0, modelFound: false} indicates no bytes to download, because a model wasn't located.
+ * @returns {Promise<{downloadSize: long, modelFound: boolean}>} Download size is the
+ * size in bytes of the estimated download for display purposes. Model found indicates
+ * a model was found. e.g., a result of {size: 0, modelFound: false} indicates no
+ * bytes to download, because a model wasn't located.
*/
static async #getModelDownloadSize(
fromLanguage,
@@ -1964,6 +1868,7 @@ export class TranslationsParent extends JSWindowActorParent {
/**
* Report an error. Having this as a method allows tests to check that an error
* was properly reported.
+ *
* @param {Error} error - Providing an Error object makes sure the stack is properly
* reported.
* @param {any[]} args - Any args to pass on to console.error.
@@ -2001,9 +1906,9 @@ export class TranslationsParent extends JSWindowActorParent {
} else {
const { docLangTag } = this.languageState.detectedLanguages;
- let engineProcess;
+ let actor;
try {
- engineProcess = await TranslationsParent.getEngineProcess();
+ actor = await lazy.EngineProcess.getTranslationsEngineParent();
} catch (error) {
console.error("Failed to get the translation engine process", error);
return;
@@ -2018,7 +1923,7 @@ export class TranslationsParent extends JSWindowActorParent {
// The MessageChannel will be used for communicating directly between the content
// process and the engine's process.
const { port1, port2 } = new MessageChannel();
- engineProcess.actor.startTranslation(
+ actor.startTranslation(
fromLanguage,
toLanguage,
port1,
@@ -2152,6 +2057,46 @@ export class TranslationsParent extends JSWindowActorParent {
}
/**
+ * Checks if a given language tag is supported for translation
+ * when translating from this language into other languages.
+ *
+ * @param {string} langTag - A BCP-47 language tag.
+ * @returns {Promise<boolean>}
+ */
+ static async isSupportedAsFromLang(langTag) {
+ if (!langTag) {
+ return false;
+ }
+ let languagePairs = await TranslationsParent.getLanguagePairs();
+ return Boolean(languagePairs.find(({ fromLang }) => fromLang === langTag));
+ }
+
+ /**
+ * Checks if a given language tag is supported for translation
+ * when translating from other languages into this language.
+ *
+ * @param {string} langTag - A BCP-47 language tag.
+ * @returns {Promise<boolean>}
+ */
+ static async isSupportedAsToLang(langTag) {
+ if (!langTag) {
+ return false;
+ }
+ let languagePairs = await TranslationsParent.getLanguagePairs();
+ return Boolean(languagePairs.find(({ fromLang }) => fromLang === langTag));
+ }
+
+ /**
+ * Retrieves the top preferred user language for which translation
+ * is supported when translating to that language.
+ */
+ static async getTopPreferredSupportedToLang() {
+ return TranslationsParent.getPreferredLanguages().find(
+ async langTag => await TranslationsParent.isSupportedAsToLang(langTag)
+ );
+ }
+
+ /**
* Returns the lang tags that should be offered for translation. This is in the parent
* rather than the child to remove the per-content process memory allocation amount.
*
@@ -2584,15 +2529,15 @@ export class TranslationsParent extends JSWindowActorParent {
* are misbehaving.
*/
#ensureTranslationsDiscarded() {
- if (!TranslationsParent.#engine) {
+ if (!lazy.EngineProcess.translationsEngineParent) {
return;
}
- TranslationsParent.#engine
+ lazy.EngineProcess.translationsEngineParent
// If the engine fails to load, ignore it since we are ending translations.
.catch(() => null)
- .then(engineProcess => {
- if (engineProcess && this.languageState.requestedTranslationPair) {
- engineProcess.actor.discardTranslations(this.innerWindowId);
+ .then(actor => {
+ if (actor && this.languageState.requestedTranslationPair) {
+ actor.discardTranslations(this.innerWindowId);
}
})
// This error will be one from the endTranslation code, which we need to
@@ -2804,11 +2749,11 @@ class TranslationsLanguageState {
}
/**
- * @typedef {Object} QueueItem
- * @prop {Function} download
- * @prop {Function} [onSuccess]
- * @prop {Function} [onFailure]
- * @prop {number} [retriesLeft]
+ * @typedef {object} QueueItem
+ * @property {Function} download
+ * @property {Function} [onSuccess]
+ * @property {Function} [onFailure]
+ * @property {number} [retriesLeft]
*/
/**
diff --git a/toolkit/components/translations/content/translations-document.sys.mjs b/toolkit/components/translations/content/translations-document.sys.mjs
index 7f436575d8..ed75fe9ec6 100644
--- a/toolkit/components/translations/content/translations-document.sys.mjs
+++ b/toolkit/components/translations/content/translations-document.sys.mjs
@@ -254,7 +254,7 @@ export class TranslationsDocument {
/**
* The BCP 47 language tag that is used on the page.
*
- * @type {string} */
+ @type {string} */
documentLanguage;
/**
@@ -292,7 +292,7 @@ export class TranslationsDocument {
* The list of nodes that need updating with the translated HTML. These are batched
* into an update.
*
- * @type {Set<{ node: Node, translatedHTML: string }}
+ * @type {Set<{ node: Node, translatedHTML: string }>}
*/
#nodesWithTranslatedHTML = new Set();
@@ -300,7 +300,7 @@ export class TranslationsDocument {
* The list of nodes that need updating with the translated Attribute HTML. These are batched
* into an update.
*
- * @type {Set<{ node: Node, translation: string, attribute: string }}
+ * @type {Set<{ node: Node, translation: string, attribute: string }>}
*/
#nodesWithTranslatedAttributes = new Set();
@@ -475,8 +475,9 @@ export class TranslationsDocument {
/**
* Queue a node for translation of attributes.
+ *
* @param {Node} node
- * @param {Array<String>}
+ * @param {Array<string>} attributeList
*/
queueAttributeNodeForTranslation(node, attributeList) {
/** @type {NodeVisibility} */
@@ -522,6 +523,7 @@ export class TranslationsDocument {
/**
* Helper function for adding a new root to the mutation
* observer.
+ *
* @param {Node} root
*/
observeNewRoot(root) {
@@ -689,6 +691,7 @@ export class TranslationsDocument {
* Get all the nodes which have selected attributes
* from the node/document and queue them.
* Call the translate function on these nodes
+ *
* @param {Node} node
* @returns {Array<Promise<void>> | null}
*/
@@ -773,7 +776,7 @@ export class TranslationsDocument {
* Runs `determineTranslationStatus`, but only on unprocessed nodes.
*
* @param {Node} node
- * @return {number} - One of the NodeStatus values.
+ * @returns {number} - One of the NodeStatus values.
*/
determineTranslationStatusForUnprocessedNodes = node => {
if (this.#processedNodes.has(node)) {
@@ -840,6 +843,7 @@ export class TranslationsDocument {
/**
* Queue a node for translation.
+ *
* @param {Node} node
*/
queueNodeForTranslation(node) {
@@ -856,6 +860,7 @@ export class TranslationsDocument {
/**
* Submit the translations giving priority to nodes in the viewport.
+ *
* @returns {Array<Promise<void>> | null}
*/
dispatchQueuedTranslations() {
@@ -905,6 +910,7 @@ export class TranslationsDocument {
/**
* Submit the Attribute translations giving priority to nodes in the viewport.
+ *
* @returns {Array<Promise<void>> | null}
*/
dispatchQueuedAttributeTranslations() {
@@ -1124,9 +1130,10 @@ export class TranslationsDocument {
/**
* A single function to update pendingTranslationsCount while
* calling the translate function
+ *
* @param {Node} node
* @param {string} text
- * @prop {boolean} isHTML
+ * @property {boolean} isHTML
* @returns {Promise<string | null>}
*/
async maybeTranslate(node, text, isHTML) {
@@ -1223,6 +1230,7 @@ export class TranslationsDocument {
/**
* Stop the mutations so that the updates of the translations
* in the nodes won't trigger observations.
+ *
* @param {Function} run The function to update translations
*/
pauseMutationObserverAndRun(run) {
@@ -1286,7 +1294,8 @@ export class TranslationsDocument {
/**
* Get the list of attributes that need to be translated
* in a given node.
- * @returns Array<string>
+ *
+ * @returns {Array<string>}
*/
function getTranslatableAttributes(node) {
if (node.nodeType !== Node.ELEMENT_NODE) {
@@ -1342,7 +1351,7 @@ function langTagsMatch(knownLanguage, otherLanguage) {
* style of node.
*
* @param {Node} node
- * @returns {HTMLElement} */
+ @returns {HTMLElement} */
function getElementForStyle(node) {
if (node.nodeType != Node.TEXT_NODE) {
return node;
@@ -1424,6 +1433,7 @@ function updateElement(translationsDocument, element) {
/**
* The Set of translation IDs for nodes that have been cloned.
+ *
* @type {Set<number>}
*/
const clonedNodes = new Set();
@@ -1649,6 +1659,7 @@ function removeTextNodes(node) {
* - `<p>test</p>`: yes
* - `<p> </p>`: no
* - `<p><b>test</b></p>`: no
+ *
* @param {Node} node
* @returns {boolean}
*/
@@ -1723,18 +1734,21 @@ function isNodeQueued(node, queuedNodes) {
}
/**
- * Reads the elements computed style and determines if the element is inline or not.
+ * Reads the elements computed style and determines if the element is a block-like
+ * element or not. Every element that lays out like a block should be sent in as one
+ * cohesive unit to be translated.
*
* @param {Element} element
*/
-function getIsInline(element) {
+function getIsBlockLike(element) {
const win = element.ownerGlobal;
if (element.namespaceURI === "http://www.w3.org/2000/svg") {
// SVG elements will report as inline, but there is no block layout in SVG.
// Treat every SVG element as being block so that every node will be subdivided.
- return false;
+ return true;
}
- return win.getComputedStyle(element).display === "inline";
+ const { display } = win.getComputedStyle(element);
+ return display !== "inline" && display !== "none";
}
/**
@@ -1751,7 +1765,8 @@ function nodeNeedsSubdividing(node) {
return false;
}
- if (getIsInline(node)) {
+ if (!getIsBlockLike(node)) {
+ // This element is inline, or not displayed.
return false;
}
@@ -1761,12 +1776,12 @@ function nodeNeedsSubdividing(node) {
// Keep checking for more inline or text nodes.
continue;
case Node.ELEMENT_NODE: {
- if (getIsInline(child)) {
- // Keep checking for more inline or text nodes.
- continue;
+ if (getIsBlockLike(child)) {
+ // This node is a block node, so it needs further subdividing.
+ return true;
}
- // A child element is not inline, so subdivide this node further.
- return true;
+ // Keep checking for more inline or text nodes.
+ continue;
}
default:
return true;
@@ -1795,12 +1810,12 @@ function* getAncestorsIterator(node) {
/**
* This contains all of the information needed to perform a translation request.
*
- * @typedef {Object} TranslationRequest
- * @prop {Node} node
- * @prop {string} sourceText
- * @prop {boolean} isHTML
- * @prop {Function} resolve
- * @prop {Function} reject
+ * @typedef {object} TranslationRequest
+ * @property {Node} node
+ * @property {string} sourceText
+ * @property {boolean} isHTML
+ * @property {Function} resolve
+ * @property {Function} reject
*/
/**
@@ -1827,13 +1842,15 @@ class QueuedTranslator {
/**
* Tie together a message id to a resolved response.
- * @type {Map<number, TranslationRequest}
+ *
+ * @type {Map<number, TranslationRequest>}
*/
#requests = new Map();
/**
* If the translations are paused, they are queued here. This Map is ordered by
* from oldest to newest requests with stale requests being removed.
+ *
* @type {Map<Node, TranslationRequest>}
*/
#queue = new Map();
@@ -1845,7 +1862,6 @@ class QueuedTranslator {
/**
* @param {MessagePort} port
- * @param {Document} document
* @param {() => void} actorRequestNewPort
*/
constructor(port, actorRequestNewPort) {
@@ -1902,6 +1918,7 @@ class QueuedTranslator {
/**
* Request a new port. The port will come in via `acquirePort`, and then resolved
* through the `this.#portRequest.resolve`.
+ *
* @returns {Promise<void>}
*/
#requestNewPort() {
@@ -1956,7 +1973,7 @@ class QueuedTranslator {
* then the request is stale. A rejection means there was an error in the translation.
* This request may be queued.
*
- * @param {node} Node
+ * @param {Node} node
* @param {string} sourceText
* @param {boolean} isHTML
*/
@@ -1997,7 +2014,7 @@ class QueuedTranslator {
* @param {Node} node
* @param {string} sourceText
* @param {boolean} isHTML
- * @return {{ translateText: TranslationFunction, translateHTML: TranslationFunction}}
+ * @returns {{ translateText: TranslationFunction, translateHTML: TranslationFunction}}
*/
#postTranslationRequest(node, sourceText, isHTML) {
return new Promise((resolve, reject) => {
@@ -2049,6 +2066,7 @@ class QueuedTranslator {
/**
* Acquires a port, checks on the engine status, and then starts or resumes
* translations.
+ *
* @param {MessagePort} port
*/
acquirePort(port) {
diff --git a/toolkit/components/translations/content/translations-engine.sys.mjs b/toolkit/components/translations/content/translations-engine.sys.mjs
index e9aeb8076b..72b5757e21 100644
--- a/toolkit/components/translations/content/translations-engine.sys.mjs
+++ b/toolkit/components/translations/content/translations-engine.sys.mjs
@@ -150,6 +150,7 @@ export class TranslationsEngine {
/**
* Removes the engine, and if it's the last, call the process to destroy itself.
+ *
* @param {string} languagePairKey
* @param {boolean} force - On forced shutdowns, it's not necessary to notify the
* parent process.
@@ -207,6 +208,7 @@ export class TranslationsEngine {
/**
* Terminates the engine and its worker after a timeout.
+ *
* @param {boolean} force
*/
terminate = (force = false) => {
@@ -419,7 +421,8 @@ function getLanguagePairKey(fromLanguage, toLanguage) {
/**
* Maps the innerWindowId to the port.
- * @type {Map<number, { fromLanguage: string, toLanguage: string, port: MessagePort }}
+ *
+ * @type {Map<number, { fromLanguage: string, toLanguage: string, port: MessagePort }>}
*/
const ports = new Map();
@@ -427,6 +430,7 @@ const ports = new Map();
* Listen to the port to the content process for incoming messages, and pass
* them to the TranslationsEngine manager. The other end of the port is held
* in the content process by the TranslationsDocument.
+ *
* @param {string} fromLanguage
* @param {string} toLanguage
* @param {number} innerWindowId
@@ -511,7 +515,7 @@ function listenForPortMessages(fromLanguage, toLanguage, innerWindowId, port) {
/**
* Discards the queue and removes the port.
*
- * @param {innerWindowId} number
+ * @param {number} innerWindowId
*/
function discardTranslations(innerWindowId) {
TE_log("Discarding translations, innerWindowId:", innerWindowId);
diff --git a/toolkit/components/translations/content/translations.mjs b/toolkit/components/translations/content/translations.mjs
index 0ec8b2d475..478f854bb5 100644
--- a/toolkit/components/translations/content/translations.mjs
+++ b/toolkit/components/translations/content/translations.mjs
@@ -57,6 +57,7 @@ class TranslationsState {
/**
* Only send one translation in at a time to the worker.
+ *
* @type {Promise<string[]>}
*/
translationRequest = Promise.resolve([]);
@@ -75,6 +76,7 @@ class TranslationsState {
constructor(isSupported) {
/**
* Is the engine supported by the device?
+ *
* @type {boolean}
*/
this.isTranslationEngineSupported = isSupported;
@@ -607,7 +609,7 @@ window.addEventListener("AboutTranslationsChromeToContent", ({ detail }) => {
* Debounce a function so that it is only called after some wait time with no activity.
* This is good for grouping text entry via keyboard.
*
- * @param {Object} settings
+ * @param {object} settings
* @param {Function} settings.onDebounce
* @param {Function} settings.doEveryTime
* @returns {Function}
@@ -671,7 +673,8 @@ class Translator {
/**
* Tie together a message id to a resolved response.
- * @type {Map<number, TranslationRequest}
+ *
+ * @type {Map<number, TranslationRequest>}
*/
#requests = new Map();
diff --git a/toolkit/components/translations/tests/browser/browser_translations_translation_document.js b/toolkit/components/translations/tests/browser/browser_translations_translation_document.js
index 9a00da9ccf..d3d56fd387 100644
--- a/toolkit/components/translations/tests/browser/browser_translations_translation_document.js
+++ b/toolkit/components/translations/tests/browser/browser_translations_translation_document.js
@@ -51,6 +51,7 @@ async function createDoc(html, options) {
/**
* Test utility to check that the document matches the expected markup
*
+ * @param {string} message
* @param {string} html
*/
async function htmlMatches(message, html) {
@@ -720,6 +721,105 @@ add_task(async function test_presumed_inlines3() {
cleanup();
});
+/**
+ * Test the display "none" properties properly subdivide in block elements.
+ */
+add_task(async function test_display_none() {
+ const { translate, htmlMatches, cleanup } = await createDoc(
+ /* html */ `
+ <p>
+ This is some text.
+ <span>It has inline elements</span>
+ <style></style>
+ </p>
+ `,
+ { mockedTranslatorPort: createBatchedMockedTranslatorPort() }
+ );
+
+ translate();
+
+ // Note: The bergamot translator does not translate style elements, while our fake
+ // translator does translate the inside of style elements. That is why in the assertion
+ // here the style element is blank rather than containing style.
+ await htmlMatches(
+ "Display none",
+ /* html */ `
+ <p>
+ aaaa aa aaaa aaaa.
+ <span data-moz-translations-id="0">
+ aa aaa aaaaaa aaaaaaaa
+ </span>
+ <style data-moz-translations-id="1">
+ </style>
+ </p>
+ `
+ );
+
+ cleanup();
+});
+
+/**
+ * Test the display "none" properties properly subdivide in block elements.
+ *
+ * TODO - See Bug 1885235
+ *
+ * This assertion is wrong, as our test suite doesn't properly compute the style for
+ * elements. The div with "display; none;" is still block, not "none".
+ */
+add_task(async function test_display_none_div() {
+ const { translate, htmlMatches, cleanup } = await createDoc(
+ /* html */ `
+ <div>
+ <span>
+ Start of inline text
+ </span>
+ <div style="display: none;">
+ hidden portion of
+ </div>
+ <span>
+ rest of inline text.
+ </span>
+ </div>
+ `,
+ { mockedTranslatorPort: createBatchedMockedTranslatorPort() }
+ );
+
+ translate();
+
+ // eslint-disable-next-line no-unused-vars
+ const _realExpectedResults = /* html */ `
+ <div>
+ <span>
+ aaaaa aa aaaaaa aaaa
+ </span>
+ <div style="display: none;">
+ aaaaaa aaaaaaa aa
+ </div>
+ <span>
+ aaaa aa aaaaaa aaaa.
+ </span>
+ </div>
+ `;
+
+ const currentResults = /* html */ `
+ <div>
+ <span>
+ aaaaa aa aaaaaa aaaa
+ </span>
+ <div style="display: none;">
+ bbbbbb bbbbbbb bb
+ </div>
+ <span>
+ cccc cc cccccc cccc.
+ </span>
+ </div>
+ `;
+
+ await htmlMatches("Display none", currentResults);
+
+ cleanup();
+});
+
add_task(async function test_chunking_large_text() {
const { translate, htmlMatches, cleanup } = await createDoc(
/* html */ `
diff --git a/toolkit/components/translations/tests/browser/shared-head.js b/toolkit/components/translations/tests/browser/shared-head.js
index bad8e48a1b..82b3e783a7 100644
--- a/toolkit/components/translations/tests/browser/shared-head.js
+++ b/toolkit/components/translations/tests/browser/shared-head.js
@@ -3,6 +3,13 @@
"use strict";
+/**
+ * @type {import("../../../ml/content/EngineProcess.sys.mjs")}
+ */
+const { EngineProcess } = ChromeUtils.importESModule(
+ "chrome://global/content/ml/EngineProcess.sys.mjs"
+);
+
// Avoid about:blank's non-standard behavior.
const BLANK_PAGE =
"data:text/html;charset=utf-8,<!DOCTYPE html><title>Blank</title>Blank page";
@@ -134,7 +141,7 @@ async function openAboutTranslations({
BrowserTestUtils.removeTab(tab);
await removeMocks();
- await TranslationsParent.destroyEngineProcess();
+ await EngineProcess.destroyTranslationsEngine();
await SpecialPowers.popPrefEnv();
}
@@ -142,6 +149,7 @@ async function openAboutTranslations({
/**
* Naively prettify's html based on the opening and closing tags. This is not robust
* for general usage, but should be adequate for these tests.
+ *
* @param {string} html
* @returns {string}
*/
@@ -340,11 +348,23 @@ function getTranslationsParent() {
}
/**
- * Closes the context menu if it is open.
+ * Closes all open panels and menu popups related to Translations.
*/
-function closeContextMenuIfOpen() {
- return waitForCondition(async () => {
- const contextMenu = document.getElementById("contentAreaContextMenu");
+async function closeAllOpenPanelsAndMenus() {
+ await closeSettingsMenuIfOpen();
+ await closeFullPageTranslationsPanelIfOpen();
+ await closeSelectTranslationsPanelIfOpen();
+ await closeContextMenuIfOpen();
+}
+
+/**
+ * Closes the popup element with the given Id if it is open.
+ *
+ * @param {string} popupElementId
+ */
+async function closePopupIfOpen(popupElementId) {
+ await waitForCondition(async () => {
+ const contextMenu = document.getElementById(popupElementId);
if (!contextMenu) {
return true;
}
@@ -362,50 +382,31 @@ function closeContextMenuIfOpen() {
}
/**
+ * Closes the context menu if it is open.
+ */
+async function closeContextMenuIfOpen() {
+ await closePopupIfOpen("contentAreaContextMenu");
+}
+
+/**
* Closes the translations panel settings menu if it is open.
*/
-function closeSettingsMenuIfOpen() {
- return waitForCondition(async () => {
- const settings = document.getElementById(
- "translations-panel-settings-menupopup"
- );
- if (!settings) {
- return true;
- }
- if (settings.state === "closed") {
- return true;
- }
- let popuphiddenPromise = BrowserTestUtils.waitForEvent(
- settings,
- "popuphidden"
- );
- PanelMultiView.hidePopup(settings);
- await popuphiddenPromise;
- return false;
- });
+async function closeSettingsMenuIfOpen() {
+ await closePopupIfOpen("full-page-translations-panel-settings-menupopup");
}
/**
* Closes the translations panel if it is open.
*/
-async function closeTranslationsPanelIfOpen() {
- await closeSettingsMenuIfOpen();
- return waitForCondition(async () => {
- const panel = document.getElementById("translations-panel");
- if (!panel) {
- return true;
- }
- if (panel.state === "closed") {
- return true;
- }
- let popuphiddenPromise = BrowserTestUtils.waitForEvent(
- panel,
- "popuphidden"
- );
- PanelMultiView.hidePopup(panel);
- await popuphiddenPromise;
- return false;
- });
+async function closeFullPageTranslationsPanelIfOpen() {
+ await closePopupIfOpen("full-page-translations-panel");
+}
+
+/**
+ * Closes the translations panel if it is open.
+ */
+async function closeSelectTranslationsPanelIfOpen() {
+ await closePopupIfOpen("select-translations-panel");
}
/**
@@ -442,10 +443,9 @@ async function setupActorTest({
actor,
remoteClients,
async cleanup() {
+ await closeAllOpenPanelsAndMenus();
await loadBlankPage();
- await TranslationsParent.destroyEngineProcess();
- await closeTranslationsPanelIfOpen();
- await closeContextMenuIfOpen();
+ await EngineProcess.destroyTranslationsEngine();
BrowserTestUtils.removeTab(tab);
await removeMocks();
TestTranslationsTelemetry.reset();
@@ -500,7 +500,7 @@ async function loadTestPage({
}) {
info(`Loading test page starting at url: ${page}`);
// Ensure no engine is being carried over from a previous test.
- await TranslationsParent.destroyEngineProcess();
+ await EngineProcess.destroyTranslationsEngine();
Services.fog.testResetFOG();
await SpecialPowers.pushPrefEnv({
set: [
@@ -552,7 +552,7 @@ async function loadTestPage({
if (autoOffer && TranslationsParent.shouldAlwaysOfferTranslations()) {
info("Waiting for the popup to be automatically shown.");
await waitForCondition(() => {
- const panel = document.getElementById("translations-panel");
+ const panel = document.getElementById("full-page-translations-panel");
return panel && panel.state === "open";
});
}
@@ -585,10 +585,9 @@ async function loadTestPage({
* @returns {Promise<void>}
*/
async cleanup() {
+ await closeAllOpenPanelsAndMenus();
await loadBlankPage();
- await TranslationsParent.destroyEngineProcess();
- await closeTranslationsPanelIfOpen();
- await closeContextMenuIfOpen();
+ await EngineProcess.destroyTranslationsEngine();
await removeMocks();
Services.fog.testResetFOG();
TranslationsParent.testAutomaticPopup = false;
@@ -658,7 +657,8 @@ async function captureTranslationsError(callback) {
/**
* Load a test page and run
- * @param {Object} options - The options for `loadTestPage` plus a `runInPage` function.
+ *
+ * @param {object} options - The options for `loadTestPage` plus a `runInPage` function.
*/
async function autoTranslatePage(options) {
const { prefs, languagePairs, ...otherOptions } = options;
@@ -676,6 +676,10 @@ async function autoTranslatePage(options) {
}
/**
+ * @typedef {ReturnType<createAttachmentMock>} AttachmentMock
+ */
+
+/**
* @param {RemoteSettingsClient} client
* @param {string} mockedCollectionName - The name of the mocked collection without
* the incrementing "id" part. This is provided so that attachments can be asserted
@@ -826,7 +830,7 @@ let _remoteSettingsMockId = 0;
* Creates a local RemoteSettingsClient for use within tests.
*
* @param {boolean} autoDownloadFromRemoteSettings
- * @param {Object[]} langPairs
+ * @param {object[]} langPairs
* @returns {RemoteSettingsClient}
*/
async function createTranslationModelsRemoteClient(
@@ -980,7 +984,7 @@ function hitEnterKey(button, message) {
*
* @see assertVisibility
*
- * @param {Object} options
+ * @param {object} options
* @param {string} options.message
* @param {Record<string, Element[]>} options.visible
* @param {Record<string, Element[]>} options.hidden
@@ -1011,7 +1015,7 @@ async function ensureVisibility({ message = null, visible = {}, hidden = {} }) {
/**
* Asserts that the provided elements are either visible or hidden.
*
- * @param {Object} options
+ * @param {object} options
* @param {string} options.message
* @param {Record<string, Element[]>} options.visible
* @param {Record<string, Element[]>} options.hidden
@@ -1066,10 +1070,9 @@ async function setupAboutPreferences(
const elements = await selectAboutPreferencesElements();
async function cleanup() {
+ await closeAllOpenPanelsAndMenus();
await loadBlankPage();
- await TranslationsParent.destroyEngineProcess();
- await closeTranslationsPanelIfOpen();
- await closeContextMenuIfOpen();
+ await EngineProcess.destroyTranslationsEngine();
BrowserTestUtils.removeTab(tab);
await removeMocks();
await SpecialPowers.popPrefEnv();
@@ -1137,8 +1140,8 @@ class TestTranslationsTelemetry {
* Asserts qualities about a counter telemetry metric.
*
* @param {string} name - The name of the metric.
- * @param {Object} counter - The Glean counter object.
- * @param {Object} expectedCount - The expected value of the counter.
+ * @param {object} counter - The Glean counter object.
+ * @param {object} expectedCount - The expected value of the counter.
*/
static async assertCounter(name, counter, expectedCount) {
// Ensures that glean metrics are collected from all child processes
@@ -1155,16 +1158,16 @@ class TestTranslationsTelemetry {
/**
* Asserts qualities about an event telemetry metric.
*
- * @param {string} name - The name of the metric.
- * @param {Object} event - The Glean event object.
- * @param {Object} expectations - The test expectations.
+ * @param {object} event - The Glean event object.
+ * @param {object} expectations - The test expectations.
* @param {number} expectations.expectedEventCount - The expected count of events.
* @param {boolean} expectations.expectNewFlowId
+ * @param {boolean} [expectations.expectFirstInteraction]
* - Expects the flowId to be different than the previous flowId if true,
* and expects it to be the same if false.
- * @param {Array<function>} [expectations.allValuePredicates=[]]
+ * @param {Array<Function>} [expectations.allValuePredicates=[]]
* - An array of function predicates to assert for all event values.
- * @param {Array<function>} [expectations.finalValuePredicates=[]]
+ * @param {Array<Function>} [expectations.finalValuePredicates=[]]
* - An array of function predicates to assert for only the final event value.
*/
static async assertEvent(
@@ -1264,8 +1267,8 @@ class TestTranslationsTelemetry {
* Asserts qualities about a rate telemetry metric.
*
* @param {string} name - The name of the metric.
- * @param {Object} rate - The Glean rate object.
- * @param {Object} expectations - The test expectations.
+ * @param {object} rate - The Glean rate object.
+ * @param {object} expectations - The test expectations.
* @param {number} expectations.expectedNumerator - The expected value of the numerator.
* @param {number} expectations.expectedDenominator - The expected value of the denominator.
*/
@@ -1295,7 +1298,7 @@ class TestTranslationsTelemetry {
* Provide longer defaults for the waitForCondition.
*
* @param {Function} callback
- * @param {string} messages
+ * @param {string} message
*/
function waitForCondition(callback, message) {
const interval = 100;
@@ -1346,9 +1349,10 @@ function getNeverTranslateSitesFromPerms() {
/**
* Opens a dialog window for about:preferences
+ *
* @param {string} dialogUrl - The URL of the dialog window
* @param {Function} callback - The function to open the dialog via UI
- * @returns {Object} The dialog window object
+ * @returns {object} The dialog window object
*/
async function waitForOpenDialogWindow(dialogUrl, callback) {
const dialogLoaded = promiseLoadSubDialog(dialogUrl);
@@ -1360,7 +1364,7 @@ async function waitForOpenDialogWindow(dialogUrl, callback) {
/**
* Closes an open dialog window and waits for it to close.
*
- * @param {Object} dialogWindow
+ * @param {object} dialogWindow
*/
async function waitForCloseDialogWindow(dialogWindow) {
const closePromise = BrowserTestUtils.waitForEvent(
diff --git a/toolkit/components/translations/tests/browser/translations-test.mjs b/toolkit/components/translations/tests/browser/translations-test.mjs
index 3e16be57e9..a740a2d1cc 100644
--- a/toolkit/components/translations/tests/browser/translations-test.mjs
+++ b/toolkit/components/translations/tests/browser/translations-test.mjs
@@ -60,7 +60,7 @@ export function getSelectors() {
* Provide longer defaults for the waitForCondition.
*
* @param {Function} callback
- * @param {string} messages
+ * @param {string} message
*/
function waitForCondition(callback, message) {
const interval = 100;
diff --git a/toolkit/components/translations/translations.d.ts b/toolkit/components/translations/translations.d.ts
index ec1e899af4..cc8d462a9c 100644
--- a/toolkit/components/translations/translations.d.ts
+++ b/toolkit/components/translations/translations.d.ts
@@ -187,7 +187,7 @@ export namespace Bergamot {
/**
* The client to interact with RemoteSettings.
- * See services/settings/RemoteSettingsClient.jsm
+ * See services/settings/RemoteSettingsClient.sys.mjs
*/
interface RemoteSettingsClient {
on: Function,