diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-12 05:43:14 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-12 05:43:14 +0000 |
commit | 8dd16259287f58f9273002717ec4d27e97127719 (patch) | |
tree | 3863e62a53829a84037444beab3abd4ed9dfc7d0 /toolkit/components/translations | |
parent | Releasing progress-linux version 126.0.1-1~progress7.99u1. (diff) | |
download | firefox-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')
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 Binary files differnew file mode 100644 index 0000000000..7ad87e3c2e --- /dev/null +++ b/toolkit/components/translations/tests/browser/translations-tester-pdf-file.pdf 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 } |