summaryrefslogtreecommitdiffstats
path: root/toolkit/components/translations
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/components/translations')
-rw-r--r--toolkit/components/translations/actors/TranslationsChild.sys.mjs1
-rw-r--r--toolkit/components/translations/actors/TranslationsParent.sys.mjs54
-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
-rw-r--r--toolkit/components/translations/tests/browser/browser.toml2
-rw-r--r--toolkit/components/translations/tests/browser/browser_translations_actor.js26
-rw-r--r--toolkit/components/translations/tests/browser/browser_translations_pdf_is_disabled.js2
-rw-r--r--toolkit/components/translations/tests/browser/browser_translations_translation_document.js90
-rw-r--r--toolkit/components/translations/tests/browser/shared-head.js30
-rw-r--r--toolkit/components/translations/tests/browser/translations-test.mjs13
-rw-r--r--toolkit/components/translations/tests/browser/translations-tester-empty-pdf-file.pdf0
-rw-r--r--toolkit/components/translations/tests/browser/translations-tester-pdf-file.pdfbin0 -> 150611 bytes
-rw-r--r--toolkit/components/translations/translations.d.ts19
14 files changed, 349 insertions, 73 deletions
diff --git a/toolkit/components/translations/actors/TranslationsChild.sys.mjs b/toolkit/components/translations/actors/TranslationsChild.sys.mjs
index d318b7284f..ad33dc0119 100644
--- a/toolkit/components/translations/actors/TranslationsChild.sys.mjs
+++ b/toolkit/components/translations/actors/TranslationsChild.sys.mjs
@@ -81,6 +81,7 @@ export class TranslationsChild extends JSWindowActorChild {
this.contentWindow.windowGlobalChild.innerWindowId,
port,
() => this.sendAsyncMessage("Translations:RequestPort"),
+ () => this.sendAsyncMessage("Translations:ReportFirstVisibleChange"),
translationsStart,
() => this.docShell.now(),
TranslationsChild.#translationsCache
diff --git a/toolkit/components/translations/actors/TranslationsParent.sys.mjs b/toolkit/components/translations/actors/TranslationsParent.sys.mjs
index f262cbeab2..a033bb02db 100644
--- a/toolkit/components/translations/actors/TranslationsParent.sys.mjs
+++ b/toolkit/components/translations/actors/TranslationsParent.sys.mjs
@@ -443,6 +443,15 @@ export class TranslationsParent extends JSWindowActorParent {
}
/**
+ * Returns whether the Translations Engine is mocked for testing.
+ *
+ * @returns {boolean}
+ */
+ static isTranslationsEngineMocked() {
+ return TranslationsParent.#isTranslationsEngineMocked;
+ }
+
+ /**
* Offer translations (for instance by automatically opening the popup panel) whenever
* languages are detected, but only do it once per host per session.
*
@@ -455,7 +464,10 @@ export class TranslationsParent extends JSWindowActorParent {
if (!lazy.automaticallyPopupPref) {
return;
}
- if (lazy.BrowserHandler?.kiosk) {
+
+ // On Android the BrowserHandler is intermittently not available (for unknown reasons).
+ // Check that the component is available before de-lazifying lazy.BrowserHandler.
+ if (Cc["@mozilla.org/browser/clh;1"] && lazy.BrowserHandler?.kiosk) {
// Pop-ups should not be shown in kiosk mode.
return;
}
@@ -628,7 +640,7 @@ export class TranslationsParent extends JSWindowActorParent {
* @param {object} gBrowser
* @returns {boolean}
*/
- static isRestrictedPage(gBrowser) {
+ static isFullPageTranslationsRestrictedForPage(gBrowser) {
const contentType = gBrowser.selectedBrowser.documentContentType;
const scheme = gBrowser.currentURI.scheme;
@@ -636,7 +648,8 @@ export class TranslationsParent extends JSWindowActorParent {
return true;
}
- // Keep this logic up to date with TranslationsChild.prototype.#isRestrictedPage.
+ // Keep this logic up to date with the "matches" array in the
+ // `toolkit/modules/ActorManagerParent.sys.mjs` definition.
switch (scheme) {
case "https":
case "http":
@@ -898,6 +911,9 @@ export class TranslationsParent extends JSWindowActorParent {
return undefined;
}
+ case "Translations:ReportFirstVisibleChange": {
+ this.languageState.hasVisibleChange = true;
+ }
}
return undefined;
}
@@ -1041,6 +1057,7 @@ export class TranslationsParent extends JSWindowActorParent {
* @returns {Promise<SupportedLanguages>}
*/
static async getSupportedLanguages() {
+ await chaosMode(1 / 4);
const languagePairs = await TranslationsParent.getLanguagePairs();
/** @type {Set<string>} */
@@ -2125,6 +2142,7 @@ export class TranslationsParent extends JSWindowActorParent {
// Skip auto-translate for one page load.
const windowState = this.getWindowState();
windowState.isPageRestored = true;
+ this.languageState.hasVisibleChange = false;
this.languageState.requestedTranslationPair = null;
windowState.previousDetectedLanguages =
this.languageState.detectedLanguages;
@@ -2314,7 +2332,7 @@ export class TranslationsParent extends JSWindowActorParent {
if (!langTags.docLangTag) {
const message = "No valid language detected.";
ChromeUtils.addProfilerMarker(
- "TranslationsChild",
+ "TranslationsParent",
{ innerWindowId: this.innerWindowId },
message
);
@@ -2339,7 +2357,7 @@ export class TranslationsParent extends JSWindowActorParent {
const message =
"The app and document languages match, so not translating.";
ChromeUtils.addProfilerMarker(
- "TranslationsChild",
+ "TranslationsParent",
{ innerWindowId: this.innerWindowId },
message
);
@@ -2392,7 +2410,7 @@ export class TranslationsParent extends JSWindowActorParent {
// No language pairs match.
const message = `No matching translation pairs were found for translating from "${langTags.docLangTag}".`;
ChromeUtils.addProfilerMarker(
- "TranslationsChild",
+ "TranslationsParent",
{ innerWindowId: this.innerWindowId },
message
);
@@ -2750,6 +2768,9 @@ class TranslationsLanguageState {
/** @type {LangTags | null} */
#detectedLanguages = null;
+ /** @type {boolean} */
+ #hasVisibleChange = false;
+
/** @type {null | TranslationErrors} */
#error = null;
@@ -2797,8 +2818,7 @@ class TranslationsLanguageState {
}
/**
- * The TranslationsChild will detect languages and offer them up for translation.
- * The results are stored here.
+ * The stored results for the detected languages.
*
* @returns {LangTags | null}
*/
@@ -2816,6 +2836,24 @@ class TranslationsLanguageState {
}
/**
+ * A visual translation change occurred on the DOM.
+ *
+ * @returns {boolean}
+ */
+ get hasVisibleChange() {
+ return this.#hasVisibleChange;
+ }
+
+ set hasVisibleChange(hasVisibleChange) {
+ if (this.#hasVisibleChange === hasVisibleChange) {
+ return;
+ }
+
+ this.#hasVisibleChange = hasVisibleChange;
+ this.dispatch();
+ }
+
+ /**
* When the location changes remove the previous error and dispatch a change event
* so that any browser chrome UI that needs to be updated can get the latest state.
*/
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;
diff --git a/toolkit/components/translations/tests/browser/browser.toml b/toolkit/components/translations/tests/browser/browser.toml
index c940bea9a0..acfd83c138 100644
--- a/toolkit/components/translations/tests/browser/browser.toml
+++ b/toolkit/components/translations/tests/browser/browser.toml
@@ -3,7 +3,7 @@ support-files = [
"head.js",
"shared-head.js",
"translations-test.mjs",
- "translations-tester-empty-pdf-file.pdf",
+ "translations-tester-pdf-file.pdf",
"translations-tester-en.html",
"translations-tester-es.html",
"translations-tester-es-2.html",
diff --git a/toolkit/components/translations/tests/browser/browser_translations_actor.js b/toolkit/components/translations/tests/browser/browser_translations_actor.js
index 457d032ea9..03d3e40c91 100644
--- a/toolkit/components/translations/tests/browser/browser_translations_actor.js
+++ b/toolkit/components/translations/tests/browser/browser_translations_actor.js
@@ -232,3 +232,29 @@ add_task(async function test_translating_to_and_from_app_language() {
return cleanup();
});
+
+add_task(async function test_firstVisualChange() {
+ const { cleanup } = await setupActorTest({
+ languagePairs: [{ fromLang: "en", toLang: "es" }],
+ });
+
+ const parent = getTranslationsParent();
+
+ Assert.equal(
+ parent.languageState.hasVisibleChange,
+ false,
+ "No visual translation change has occurred yet"
+ );
+
+ parent.receiveMessage({
+ name: "Translations:ReportFirstVisibleChange",
+ });
+
+ Assert.equal(
+ parent.languageState.hasVisibleChange,
+ true,
+ "A change occurred."
+ );
+
+ return cleanup();
+});
diff --git a/toolkit/components/translations/tests/browser/browser_translations_pdf_is_disabled.js b/toolkit/components/translations/tests/browser/browser_translations_pdf_is_disabled.js
index b3ac09169f..902a5ad248 100644
--- a/toolkit/components/translations/tests/browser/browser_translations_pdf_is_disabled.js
+++ b/toolkit/components/translations/tests/browser/browser_translations_pdf_is_disabled.js
@@ -8,7 +8,7 @@
*/
add_task(async function test_translations_button_disabled_in_pdf() {
const { cleanup } = await loadTestPage({
- page: EMPTY_PDF_URL,
+ page: PDF_TEST_PAGE_URL,
});
const appMenuButton = document.getElementById("PanelUI-menu-button");
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 d3d56fd387..9119b0ddae 100644
--- a/toolkit/components/translations/tests/browser/browser_translations_translation_document.js
+++ b/toolkit/components/translations/tests/browser/browser_translations_translation_document.js
@@ -12,7 +12,8 @@ const { TranslationsDocument, LRUCache } = ChromeUtils.importESModule(
/**
* @param {string} html
* @param {{
- * mockedTranslatorPort?: (message: string) => Promise<string>
+ * mockedTranslatorPort?: (message: string) => Promise<string>,
+ * mockedReportVisibleChange?: () => void
* }} [options]
*/
async function createDoc(html, options) {
@@ -42,6 +43,7 @@ async function createDoc(html, options) {
() => {
throw new Error("Cannot request a new port");
},
+ options?.mockedReportVisibleChange ?? (() => {}),
performance.now(),
() => performance.now(),
new LRUCache()
@@ -81,12 +83,21 @@ async function createDoc(html, options) {
return { htmlMatches, cleanup, translate, document };
}
-add_task(async function test_translated_div_element() {
- const { translate, htmlMatches, cleanup } = await createDoc(/* html */ `
+add_task(async function test_translated_div_element_and_visible_change() {
+ let hasVisibleChangeOccurred = false;
+ const { translate, htmlMatches, cleanup } = await createDoc(
+ /* html */ `
<div>
This is a simple translation.
</div>
- `);
+ `,
+ {
+ mockedTranslatorPort: createMockedTranslatorPort(),
+ mockedReportVisibleChange: () => {
+ hasVisibleChangeOccurred = true;
+ },
+ }
+ );
translate();
@@ -99,6 +110,7 @@ add_task(async function test_translated_div_element() {
`
);
+ Assert.ok(hasVisibleChangeOccurred, "A visible change was reported.");
cleanup();
});
@@ -197,18 +209,18 @@ add_task(async function test_translated_nested_elements() {
<div class="menu-main-menu-container">
<ul class="menu-list">
<li class="menu-item menu-item-top-level">
- <a href="/" data-moz-translations-id="0">
+ <a href="/">
LATEST WORK
</a>
</li>
<li class="menu-item menu-item-top-level">
- <a href="/category/interactive/" data-moz-translations-id="0">
+ <a href="/category/interactive/">
CREATIVE CODING
</a>
</li>
<li id="menu-id-categories" class="menu-item menu-item-top-level">
- <a href="#" data-moz-translations-id="0">
- <span class="category-arrow" data-moz-translations-id="1">
+ <a href="#">
+ <span class="category-arrow">
CATEGORIES
</span>
</a>
@@ -434,11 +446,11 @@ add_task(async function test_translation_batching() {
aaaa aa a aaaaaa aaaaaaa.
</div>
<div>
- <span data-moz-translations-id="0">
+ <span>
bbbb bbbbbb
</span>
bbbbbbb bbbbbbbbb bb b
- <b data-moz-translations-id="1">
+ <b>
bbbbb
</b>
.
@@ -479,7 +491,7 @@ add_task(async function test_translation_inline_styling() {
aaaa aaaa aa aaaa aa a aaaaa.
<span>
bbbbbb bbbb bb bbbb bb b
- <b data-moz-translations-id="0">
+ <b>
bbbbb
</b>
.
@@ -529,22 +541,22 @@ add_task(async function test_many_inlines() {
"Batching",
/* html */ `
<div>
- <span data-moz-translations-id="0">
+ <span>
aaaa aa a
</span>
- <span data-moz-translations-id="1">
+ <span>
aaaa aaaaaa
</span>
- <span data-moz-translations-id="2">
+ <span>
aaaaaaa aaaa aaaaaaaa
</span>
- <span data-moz-translations-id="3">
+ <span>
aaaa aaaa aaaaaaaa
</span>
- <span data-moz-translations-id="4">
+ <span>
aa aaaa aaaa aaaaaaa
</span>
- <span data-moz-translations-id="5">
+ <span>
aa aaaaa aaaa aaaa.
</span>
</div>
@@ -746,10 +758,10 @@ add_task(async function test_display_none() {
/* html */ `
<p>
aaaa aa aaaa aaaa.
- <span data-moz-translations-id="0">
+ <span>
aa aaa aaaaaa aaaaaaaa
</span>
- <style data-moz-translations-id="1">
+ <style>
</style>
</p>
`
@@ -882,13 +894,13 @@ add_task(async function test_reordering() {
await htmlMatches(
"Nodes can be re-ordered by the translator",
/* html */ `
- <span data-moz-translations-id="1">
+ <span>
A - THIS WAS SECOND.
</span>
- <span data-moz-translations-id="0">
+ <span>
B - THIS WAS FIRST.
</span>
- <span data-moz-translations-id="2">
+ <span>
C - THIS WAS THIRD.
</span>
`
@@ -916,7 +928,7 @@ add_task(async function test_reordering2() {
await htmlMatches(
"Text nodes can be re-ordered.",
/* html */ `
- <span data-moz-translations-id="0">
+ <span>
A - THIS WAS SECOND.
</span>
B - THIS WAS FIRST.
@@ -1271,11 +1283,11 @@ add_task(async function test_title_attribute_subnodes() {
"Titles are translated",
/* html */ `
<div>
- <span data-moz-translations-id="0">SPAN TEXT 1</span>
- <span data-moz-translations-id="1">SPAN TEXT 2</span>
- <span data-moz-translations-id="2">SPAN TEXT 3</span>
- <span data-moz-translations-id="3">SPAN TEXT 4</span>
- <span data-moz-translations-id="4">SPAN TEXT 5</span>
+ <span>SPAN TEXT 1</span>
+ <span>SPAN TEXT 2</span>
+ <span>SPAN TEXT 3</span>
+ <span>SPAN TEXT 4</span>
+ <span>SPAN TEXT 5</span>
THIS IS TEXT.
</div>
`
@@ -1302,11 +1314,11 @@ add_task(async function test_title_attribute_subnodes() {
"Titles are translated",
/* html */ `
<div title="TITLE IN DIV">
- <span title="TITLE 1" data-moz-translations-id="0">SPAN TEXT 1</span>
- <span title="TITLE 2" data-moz-translations-id="1">SPAN TEXT 2</span>
- <span title="TITLE 3" data-moz-translations-id="2">SPAN TEXT 3</span>
- <span title="TITLE 4" data-moz-translations-id="3">SPAN TEXT 4</span>
- <span title="TITLE 5" data-moz-translations-id="4">SPAN TEXT 5</span>
+ <span title="TITLE 1">SPAN TEXT 1</span>
+ <span title="TITLE 2">SPAN TEXT 2</span>
+ <span title="TITLE 3">SPAN TEXT 3</span>
+ <span title="TITLE 4">SPAN TEXT 4</span>
+ <span title="TITLE 5">SPAN TEXT 5</span>
THIS IS TEXT.
</div>
`
@@ -1334,9 +1346,9 @@ add_task(async function test_attributes() {
/* html */ `
<div>
THIS IS THE OUTER DIV
- <label data-moz-translations-id="0">
+ <label>
ENTER INFORMATION:
- <input type="text" data-moz-translations-id="1">
+ <input type="text">
</label>
</div>
`
@@ -1364,9 +1376,9 @@ add_task(async function test_attributes() {
/* html */ `
<div title="TITLES ARE USER VISIBLE">
THIS IS THE OUTER DIV
- <label data-moz-translations-id="0">
+ <label>
ENTER INFORMATION:
- <input type="text" placeholder="THIS IS A PLACEHOLDER" data-moz-translations-id="1">
+ <input type="text" placeholder="THIS IS A PLACEHOLDER">
</label>
</div>
`
@@ -1395,9 +1407,9 @@ add_task(async function test_attributes() {
/* html */ `
<div>
THIS IS THE OUTER DIV
- <label data-moz-translations-id="0">
+ <label>
ENTER INFORMATION 1:
- <label data-moz-translations-id="1">
+ <label>
ENTER INFORMATION 2:
</label>
</label>
diff --git a/toolkit/components/translations/tests/browser/shared-head.js b/toolkit/components/translations/tests/browser/shared-head.js
index afa060c8a3..021534c2e0 100644
--- a/toolkit/components/translations/tests/browser/shared-head.js
+++ b/toolkit/components/translations/tests/browser/shared-head.js
@@ -33,8 +33,8 @@ const SPANISH_PAGE_URL_DOT_ORG =
URL_ORG_PREFIX + DIR_PATH + "translations-tester-es.html";
const NO_LANGUAGE_URL =
URL_COM_PREFIX + DIR_PATH + "translations-tester-no-tag.html";
-const EMPTY_PDF_URL =
- URL_COM_PREFIX + DIR_PATH + "translations-tester-empty-pdf-file.pdf";
+const PDF_TEST_PAGE_URL =
+ URL_COM_PREFIX + DIR_PATH + "translations-tester-pdf-file.pdf";
const SELECT_TEST_PAGE_URL =
URL_COM_PREFIX + DIR_PATH + "translations-tester-select.html";
@@ -358,8 +358,9 @@ function getTranslationsParent() {
* @param {ChromeWindow} [win]
*/
async function closeAllOpenPanelsAndMenus(win) {
- await closeSettingsMenuIfOpen(win);
+ await closeFullPagePanelSettingsMenuIfOpen(win);
await closeFullPageTranslationsPanelIfOpen(win);
+ await closeSelectPanelSettingsMenuIfOpen(win);
await closeSelectTranslationsPanelIfOpen(win);
await closeContextMenuIfOpen(win);
}
@@ -400,11 +401,11 @@ async function closeContextMenuIfOpen(win) {
}
/**
- * Closes the translations panel settings menu if it is open.
+ * Closes the full-page translations panel settings menu if it is open.
*
* @param {ChromeWindow} [win]
*/
-async function closeSettingsMenuIfOpen(win) {
+async function closeFullPagePanelSettingsMenuIfOpen(win) {
await closePopupIfOpen(
"full-page-translations-panel-settings-menupopup",
win
@@ -412,6 +413,15 @@ async function closeSettingsMenuIfOpen(win) {
}
/**
+ * Closes the select translations panel settings menu if it is open.
+ *
+ * @param {ChromeWindow} [win]
+ */
+async function closeSelectPanelSettingsMenuIfOpen(win) {
+ await closePopupIfOpen("select-translations-panel-settings-menupopup", win);
+}
+
+/**
* Closes the translations panel if it is open.
*
* @param {ChromeWindow} [win]
@@ -543,6 +553,10 @@ async function loadTestPage({
["browser.translations.automaticallyPopup", true],
["browser.translations.alwaysTranslateLanguages", ""],
["browser.translations.neverTranslateLanguages", ""],
+ // Bug 1893100 - This is needed to ensure that switching focus
+ // with tab works in tests independent of macOS settings that
+ // would otherwise disable keyboard navigation at the OS level.
+ ["accessibility.tabfocus_applies_to_xul", false],
...(prefs ?? []),
],
});
@@ -956,21 +970,21 @@ async function selectAboutPreferencesElements() {
);
const frenchLabel = frenchRow.querySelector("label");
const frenchDownload = frenchRow.querySelector(
- `[data-l10n-id="translations-manage-language-install-button"]`
+ `[data-l10n-id="translations-manage-language-download-button"]`
);
const frenchDelete = frenchRow.querySelector(
`[data-l10n-id="translations-manage-language-remove-button"]`
);
const spanishLabel = spanishRow.querySelector("label");
const spanishDownload = spanishRow.querySelector(
- `[data-l10n-id="translations-manage-language-install-button"]`
+ `[data-l10n-id="translations-manage-language-download-button"]`
);
const spanishDelete = spanishRow.querySelector(
`[data-l10n-id="translations-manage-language-remove-button"]`
);
const ukrainianLabel = ukrainianRow.querySelector("label");
const ukrainianDownload = ukrainianRow.querySelector(
- `[data-l10n-id="translations-manage-language-install-button"]`
+ `[data-l10n-id="translations-manage-language-download-button"]`
);
const ukrainianDelete = ukrainianRow.querySelector(
`[data-l10n-id="translations-manage-language-remove-button"]`
diff --git a/toolkit/components/translations/tests/browser/translations-test.mjs b/toolkit/components/translations/tests/browser/translations-test.mjs
index 3ff107a699..7d9910b9a0 100644
--- a/toolkit/components/translations/tests/browser/translations-test.mjs
+++ b/toolkit/components/translations/tests/browser/translations-test.mjs
@@ -35,6 +35,19 @@ export function getSelectors() {
getH1() {
return content.document.querySelector("h1");
},
+ getPdfSpan() {
+ return ContentTaskUtils.waitForCondition(
+ () =>
+ !!content.document.querySelector(
+ `.page[data-page-number='1'] .textLayer .endOfContent`
+ ),
+ "The text layer must be displayed"
+ ).then(() =>
+ content.document.querySelector(
+ ".page[data-page-number='1'] .textLayer span"
+ )
+ );
+ },
getHeader() {
return content.document.querySelector("header");
},
diff --git a/toolkit/components/translations/tests/browser/translations-tester-empty-pdf-file.pdf b/toolkit/components/translations/tests/browser/translations-tester-empty-pdf-file.pdf
deleted file mode 100644
index e69de29bb2..0000000000
--- a/toolkit/components/translations/tests/browser/translations-tester-empty-pdf-file.pdf
+++ /dev/null
diff --git a/toolkit/components/translations/tests/browser/translations-tester-pdf-file.pdf b/toolkit/components/translations/tests/browser/translations-tester-pdf-file.pdf
new file mode 100644
index 0000000000..7ad87e3c2e
--- /dev/null
+++ b/toolkit/components/translations/tests/browser/translations-tester-pdf-file.pdf
Binary files differ
diff --git a/toolkit/components/translations/translations.d.ts b/toolkit/components/translations/translations.d.ts
index 9823f6a845..c3fcf7ab63 100644
--- a/toolkit/components/translations/translations.d.ts
+++ b/toolkit/components/translations/translations.d.ts
@@ -271,8 +271,27 @@ export interface SupportedLanguages {
export type TranslationErrors = "engine-load-error";
export type SelectTranslationsPanelState =
+ // The panel is closed.
| { phase: "closed"; }
+
+ // The panel is idle after successful initialization and ready to attempt translation.
| { phase: "idle"; fromLanguage: string; toLanguage: string, sourceText: string, }
+
+ // The language dropdown menus failed to populate upon opening the panel.
+ // This state contains all of the information for the try-again button to close and re-open the panel.
+ | { phase: "init-failure"; event: Event, screenX: number, screenY: number, sourceText: string, langPairPromise: Promise<{fromLang?: string, toLang?: string}> }
+
+ // The translation failed to complete.
+ | { phase: "translation-failure"; fromLanguage: string; toLanguage: string, sourceText: string, }
+
+ // The selected language pair is determined to be translatable.
| { phase: "translatable"; fromLanguage: string; toLanguage: string, sourceText: string, }
+
+ // The panel is actively translating the source text.
| { phase: "translating"; fromLanguage: string; toLanguage: string, sourceText: string, }
+
+ // The source text has been translated successfully.
| { phase: "translated"; fromLanguage: string; toLanguage: string, sourceText: string, translatedText: string, }
+
+ // The source language is not currently supported by Translations in Firefox.
+ | { phase: "unsupported"; detectedLanguage: string; toLanguage: string, sourceText: string }