summaryrefslogtreecommitdiffstats
path: root/browser/components/translations
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/translations')
-rw-r--r--browser/components/translations/content/fullPageTranslationsPanel.inc.xhtml8
-rw-r--r--browser/components/translations/content/fullPageTranslationsPanel.js6
-rw-r--r--browser/components/translations/content/selectTranslationsPanel.inc.xhtml211
-rw-r--r--browser/components/translations/content/selectTranslationsPanel.js1070
-rw-r--r--browser/components/translations/tests/browser/browser.toml24
-rw-r--r--browser/components/translations/tests/browser/browser_translations_about_preferences_manage_downloaded_languages.js8
-rw-r--r--browser/components/translations/tests/browser/browser_translations_about_preferences_settings_ui.js10
-rw-r--r--browser/components/translations/tests/browser/browser_translations_select_context_menu_engine_unsupported.js35
-rw-r--r--browser/components/translations/tests/browser/browser_translations_select_context_menu_with_hyperlink.js8
-rw-r--r--browser/components/translations/tests/browser/browser_translations_select_context_menu_with_text_selected.js8
-rw-r--r--browser/components/translations/tests/browser/browser_translations_select_panel_close_on_new_tab.js46
-rw-r--r--browser/components/translations/tests/browser/browser_translations_select_panel_copy_button.js95
-rw-r--r--browser/components/translations/tests/browser/browser_translations_select_panel_init_failure.js119
-rw-r--r--browser/components/translations/tests/browser/browser_translations_select_panel_open_to_idle_state.js61
-rw-r--r--browser/components/translations/tests/browser/browser_translations_select_panel_pdf.js42
-rw-r--r--browser/components/translations/tests/browser/browser_translations_select_panel_reader_mode.js44
-rw-r--r--browser/components/translations/tests/browser/browser_translations_select_panel_select_same_from_and_to_languages_directly.js39
-rw-r--r--browser/components/translations/tests/browser/browser_translations_select_panel_select_same_from_and_to_languages_from_dropdown_menu.js37
-rw-r--r--browser/components/translations/tests/browser/browser_translations_select_panel_settings_menu.js70
-rw-r--r--browser/components/translations/tests/browser/browser_translations_select_panel_translate_full_page_button.js83
-rw-r--r--browser/components/translations/tests/browser/browser_translations_select_panel_translate_on_change_language_directly.js16
-rw-r--r--browser/components/translations/tests/browser/browser_translations_select_panel_translate_on_change_language_from_dropdown_menu.js16
-rw-r--r--browser/components/translations/tests/browser/browser_translations_select_panel_translate_on_change_language_multiple_times_directly.js13
-rw-r--r--browser/components/translations/tests/browser/browser_translations_select_panel_translate_on_change_language_multiple_times_from_dropdown_menu.js13
-rw-r--r--browser/components/translations/tests/browser/browser_translations_select_panel_translation_failure_after_unsupported_language.js55
-rw-r--r--browser/components/translations/tests/browser/browser_translations_select_panel_translation_failure_on_open.js92
-rw-r--r--browser/components/translations/tests/browser/browser_translations_select_panel_translation_failure_on_retranslate.js128
-rw-r--r--browser/components/translations/tests/browser/browser_translations_select_panel_unsupported_language.js163
-rw-r--r--browser/components/translations/tests/browser/head.js784
29 files changed, 2722 insertions, 582 deletions
diff --git a/browser/components/translations/content/fullPageTranslationsPanel.inc.xhtml b/browser/components/translations/content/fullPageTranslationsPanel.inc.xhtml
index bc0c5b319f..6b3e19538d 100644
--- a/browser/components/translations/content/fullPageTranslationsPanel.inc.xhtml
+++ b/browser/components/translations/content/fullPageTranslationsPanel.inc.xhtml
@@ -24,7 +24,7 @@
<html:h1 class="translations-panel-header-wrapper">
<html:span id="full-page-translations-panel-header"></html:span>
</html:h1>
- <hbox class="translations-panel-beta">
+ <hbox class="translations-panel-beta" role="image" aria-label="Beta">
<image class="translations-panel-beta-icon"></image>
</hbox>
<toolbarbutton id="translations-panel-settings"
@@ -49,7 +49,7 @@
flex="1"
value="detect"
size="large"
- aria-labelledby="translations-panel-from-label"
+ aria-labelledby="full-page-translations-panel-from-label"
oncommand="FullPageTranslationsPanel.onChangeFromLanguage(event)">
<menupopup id="full-page-translations-panel-from-menupopup"
class="translations-panel-language-menupopup-from">
@@ -63,7 +63,7 @@
flex="1"
value="detect"
size="large"
- aria-labelledby="translations-panel-to-label"
+ aria-labelledby="full-page-translations-panel-to-label"
oncommand="FullPageTranslationsPanel.onChangeToLanguage(event)">
<menupopup id="full-page-translations-panel-to-menupopup"
class="translations-panel-language-menupopup-to">
@@ -85,7 +85,7 @@
</vbox>
</vbox>
- <html:moz-button-group class="panel-footer translations-panel-footer">
+ <html:moz-button-group class="panel-footer translations-panel-footer translations-panel-button-group">
<button id="full-page-translations-panel-restore-button"
class="footer-button"
oncommand="FullPageTranslationsPanel.onRestore(event);"
diff --git a/browser/components/translations/content/fullPageTranslationsPanel.js b/browser/components/translations/content/fullPageTranslationsPanel.js
index eddd3566f1..2875333d61 100644
--- a/browser/components/translations/content/fullPageTranslationsPanel.js
+++ b/browser/components/translations/content/fullPageTranslationsPanel.js
@@ -1041,8 +1041,6 @@ var FullPageTranslationsPanel = new (class {
isFirstUserInteraction = null,
}
) {
- await window.ensureCustomElements("moz-button-group");
-
const { panel, appMenuButton } = this.elements;
const openedFromAppMenu = target.id === appMenuButton.id;
const { docLangTag } = await this.#getCachedDetectedLanguages();
@@ -1107,10 +1105,6 @@ var FullPageTranslationsPanel = new (class {
return;
}
- const window =
- gBrowser.selectedBrowser.browsingContext.top.embedderElement.ownerGlobal;
- window.ensureCustomElements("moz-support-link");
-
const { button } = this.buttonElements;
const { requestedTranslationPair } =
diff --git a/browser/components/translations/content/selectTranslationsPanel.inc.xhtml b/browser/components/translations/content/selectTranslationsPanel.inc.xhtml
index 8c643ea3f6..287bd65679 100644
--- a/browser/components/translations/content/selectTranslationsPanel.inc.xhtml
+++ b/browser/components/translations/content/selectTranslationsPanel.inc.xhtml
@@ -8,95 +8,158 @@
type="arrow"
role="alertdialog"
noautofocus="true"
+ tabspecific="true"
+ locationspecific="true"
aria-labelledby="translations-panel-header"
- orient="vertical"
- onpopupshown="SelectTranslationsPanel.handlePanelPopupShownEvent(event)"
- onpopuphidden="SelectTranslationsPanel.handlePanelPopupHiddenEvent(event)">
+ orient="vertical">
<hbox class="panel-header select-translations-panel-header">
<html:h1 class="translations-panel-header-wrapper">
<html:span id="select-translations-panel-header" data-l10n-id="select-translations-panel-header">
</html:span>
</html:h1>
- <hbox class="translations-panel-beta">
+ <hbox class="translations-panel-beta" role="image" aria-label="Beta">
<image id="select-translations-panel-beta-icon"
class="translations-panel-beta-icon">
</image>
</hbox>
- <toolbarbutton id="select-translations-panel-settings"
+ <toolbarbutton id="select-translations-panel-settings-button"
class="panel-info-button translations-panel-settings-gear-icon"
data-l10n-id="translations-panel-settings-button"
- closemenu="none" />
+ tabindex="0"
+ closemenu="none"/>
</hbox>
- <vbox class="select-translations-panel-content">
- <hbox id="select-translations-panel-lang-selection">
- <vbox flex="1">
- <label id="select-translations-panel-from-label"
- class="select-translations-panel-label"
- data-l10n-id="select-translations-panel-from-label">
- </label>
- <menulist id="select-translations-panel-from"
- flex="1"
- value=""
- size="large"
- data-l10n-id="translations-panel-choose-language"
- aria-labelledby="select-translations-panel-from-label"
- noinitialselection="true"
- oncommand="SelectTranslationsPanel.onChangeFromLanguage(event)">
- <menupopup id="select-translations-panel-from-menupopup"
- onpopupshown="SelectTranslationsPanel.handlePanelPopupShownEvent(event)"
- class="translations-panel-language-menupopup-from">
- <!-- The list of <menuitem> will be dynamically inserted. -->
- </menupopup>
- </menulist>
- </vbox>
- <vbox flex="1">
- <label id="select-translations-panel-to-label"
- class="select-translations-panel-label"
- data-l10n-id="select-translations-panel-to-label">
- </label>
- <menulist id="select-translations-panel-to"
- flex="1"
- value=""
- size="large"
- data-l10n-id="translations-panel-choose-language"
- aria-labelledby="select-translations-panel-to-label"
- noinitialselection="true"
- oncommand="SelectTranslationsPanel.onChangeToLanguage(event)">
- <menupopup id="select-translations-panel-to-menupopup"
- onpopupshown="SelectTranslationsPanel.handlePanelPopupShownEvent(event)"
- class="translations-panel-language-menupopup-to">
- <!-- The list of <menuitem> will be dynamically inserted. -->
- </menupopup>
- </menulist>
- </vbox>
- </hbox>
- </vbox>
- <vbox class="select-translations-panel-content">
- <html:textarea id="select-translations-panel-text-area"
- class="select-translations-panel-text-area"
- readonly="true"
- tabindex="0">
- </html:textarea>
- </vbox>
-
- <hbox class="select-translations-panel-content">
+ <html:div id="select-translations-panel-init-failure-content"
+ class="translations-panel-content"
+ hidden="true">
+ <html:moz-message-bar id="select-translations-panel-init-failure-message-bar"
+ type="error"
+ class="select-translations-panel-message-bar"
+ data-l10n-id="select-translations-panel-init-failure-message"
+ data-l10n-attrs="message">
+ </html:moz-message-bar>
+ </html:div>
+ <html:div id="select-translations-panel-unsupported-language-content" hidden="true">
+ <vbox flex="1" class="select-translations-panel-content">
+ <html:moz-message-bar id="select-translations-panel-unsupported-language-message-bar"
+ type="info"
+ class="select-translations-panel-message-bar"
+ data-l10n-id="select-translations-panel-unsupported-language-message-unknown"
+ data-l10n-attrs="message">
+ </html:moz-message-bar>
+ <label id="select-translations-panel-try-another-language-label"
+ class="select-translations-panel-label"
+ data-l10n-id="select-translations-panel-try-another-language-label">
+ </label>
+ <menulist id="select-translations-panel-try-another-language"
+ flex="1"
+ value=""
+ size="large"
+ data-l10n-id="translations-panel-choose-language"
+ aria-labelledby="select-translations-panel-try-another-language-label"
+ noinitialselection="true">
+ <menupopup id="select-translations-panel-try-another-language-menupopup"
+ class="translations-panel-language-menupopup-from">
+ <!-- The list of <menuitem> will be dynamically inserted. -->
+ </menupopup>
+ </menulist>
+ </vbox>
+ </html:div>
+ <html:div id="select-translations-panel-main-content">
+ <vbox class="select-translations-panel-content">
+ <hbox id="select-translations-panel-lang-selection">
+ <vbox flex="1">
+ <label id="select-translations-panel-from-label"
+ class="select-translations-panel-label"
+ data-l10n-id="select-translations-panel-from-label">
+ </label>
+ <menulist id="select-translations-panel-from"
+ flex="1"
+ value=""
+ size="large"
+ data-l10n-id="translations-panel-choose-language"
+ aria-labelledby="select-translations-panel-from-label"
+ noinitialselection="true">
+ <menupopup id="select-translations-panel-from-menupopup"
+ class="translations-panel-language-menupopup-from">
+ <!-- The list of <menuitem> will be dynamically inserted. -->
+ </menupopup>
+ </menulist>
+ </vbox>
+ <vbox flex="1">
+ <label id="select-translations-panel-to-label"
+ class="select-translations-panel-label"
+ data-l10n-id="select-translations-panel-to-label">
+ </label>
+ <menulist id="select-translations-panel-to"
+ flex="1"
+ value=""
+ size="large"
+ data-l10n-id="translations-panel-choose-language"
+ aria-labelledby="select-translations-panel-to-label"
+ noinitialselection="true">
+ <menupopup id="select-translations-panel-to-menupopup"
+ class="translations-panel-language-menupopup-to">
+ <!-- The list of <menuitem> will be dynamically inserted. -->
+ </menupopup>
+ </menulist>
+ </vbox>
+ </hbox>
+ </vbox>
+ <vbox class="select-translations-panel-content">
+ <html:textarea id="select-translations-panel-text-area"
+ class="select-translations-panel-text-area"
+ readonly="true"
+ tabindex="0">
+ </html:textarea>
+ <html:moz-message-bar id="select-translations-panel-translation-failure-message-bar"
+ type="error"
+ hidden="true"
+ data-l10n-id="select-translations-panel-translation-failure-message"
+ data-l10n-attrs="message">
+ </html:moz-message-bar>
+ </vbox>
+ </html:div>
+ <html:div id="select-translations-panel-footer"
+ class="panel-footer translations-panel-footer">
<button id="select-translations-panel-copy-button"
- class="footer-button select-translations-panel-button select-translations-panel-copy-button"
+ class="footer-button select-translations-panel-copy-button"
data-l10n-id="select-translations-panel-copy-button">
</button>
- </hbox>
-
- <html:moz-button-group class="panel-footer translations-panel-footer">
- <button id="select-translations-panel-translate-full-page-button"
- class="footer-button select-translations-panel-button"
- data-l10n-id="select-translations-panel-translate-full-page-button">
- </button>
- <button id="select-translations-panel-done-button"
- class="footer-button select-translations-panel-button"
- data-l10n-id="select-translations-panel-done-button"
- default="true"
- oncommand = "SelectTranslationsPanel.close()">
- </button>
- </html:moz-button-group>
+ <html:moz-button-group id="select-translations-panel-footer-button-group"
+ class="translations-panel-button-group">
+ <button id="select-translations-panel-cancel-button"
+ class="footer-button"
+ hidden="true"
+ data-l10n-id="select-translations-panel-cancel-button">
+ </button>
+ <button id="select-translations-panel-done-button-secondary"
+ hidden="true"
+ class="footer-button"
+ data-l10n-id="select-translations-panel-done-button">
+ </button>
+ <button id="select-translations-panel-translate-full-page-button"
+ class="footer-button"
+ data-l10n-id="select-translations-panel-translate-full-page-button">
+ </button>
+ <button id="select-translations-panel-done-button-primary"
+ class="footer-button"
+ data-l10n-id="select-translations-panel-done-button"
+ default="true">
+ </button>
+ <button id="select-translations-panel-translate-button"
+ class="footer-button"
+ data-l10n-id="select-translations-panel-translate-button"
+ hidden="true"
+ default="true"
+ disabled="true">
+ </button>
+ <button id="select-translations-panel-try-again-button"
+ class="footer-button"
+ data-l10n-id="select-translations-panel-try-again-button"
+ hidden="true"
+ default="true">
+ </button>
+ </html:moz-button-group>
+ </html:div>
</panel>
</html:template>
diff --git a/browser/components/translations/content/selectTranslationsPanel.js b/browser/components/translations/content/selectTranslationsPanel.js
index bb825eaefa..36452d4cc0 100644
--- a/browser/components/translations/content/selectTranslationsPanel.js
+++ b/browser/components/translations/content/selectTranslationsPanel.js
@@ -16,6 +16,13 @@ ChromeUtils.defineESModuleGetters(this, {
Translator: "chrome://global/content/translations/Translator.mjs",
});
+XPCOMUtils.defineLazyServiceGetter(
+ this,
+ "ClipboardHelper",
+ "@mozilla.org/widget/clipboardhelper;1",
+ "nsIClipboardHelper"
+);
+
/**
* This singleton class controls the SelectTranslations panel.
*
@@ -122,18 +129,28 @@ var SelectTranslationsPanel = new (class {
#lazyElements;
/**
- * The internal state of the SelectTranslationsPanel.
+ * Set to true the first time event listeners are initialized.
*
- * @type {SelectTranslationsPanelState}
+ * @type {boolean}
*/
- #translationState = { phase: "closed" };
+ #eventListenersInitialized = false;
+
+ /**
+ * This value is true if this page does not allow Full Page Translations,
+ * e.g. PDFs, reader mode, internal Firefox pages.
+ *
+ * Many of these are cases where the SelectTranslationsPanel is available
+ * even though the FullPageTranslationsPanel is not, so this helps inform
+ * whether the translate-full-page button should be allowed in this context.
+ */
+ #isFullPageTranslationsRestrictedForPage = true;
/**
- * The Translator for the current language pair.
+ * The internal state of the SelectTranslationsPanel.
*
- * @type {Translator}
+ * @type {SelectTranslationsPanelState}
*/
- #translator;
+ #translationState = { phase: "closed" };
/**
* An Id that increments with each translation, used to help keep track
@@ -171,18 +188,37 @@ var SelectTranslationsPanel = new (class {
TranslationsPanelShared.defineLazyElements(document, this.#lazyElements, {
betaIcon: "select-translations-panel-beta-icon",
+ cancelButton: "select-translations-panel-cancel-button",
copyButton: "select-translations-panel-copy-button",
- doneButton: "select-translations-panel-done-button",
+ doneButtonPrimary: "select-translations-panel-done-button-primary",
+ doneButtonSecondary: "select-translations-panel-done-button-secondary",
fromLabel: "select-translations-panel-from-label",
fromMenuList: "select-translations-panel-from",
fromMenuPopup: "select-translations-panel-from-menupopup",
header: "select-translations-panel-header",
+ initFailureContent: "select-translations-panel-init-failure-content",
+ initFailureMessageBar:
+ "select-translations-panel-init-failure-message-bar",
+ mainContent: "select-translations-panel-main-content",
+ settingsButton: "select-translations-panel-settings-button",
textArea: "select-translations-panel-text-area",
toLabel: "select-translations-panel-to-label",
toMenuList: "select-translations-panel-to",
toMenuPopup: "select-translations-panel-to-menupopup",
+ translateButton: "select-translations-panel-translate-button",
translateFullPageButton:
"select-translations-panel-translate-full-page-button",
+ translationFailureMessageBar:
+ "select-translations-panel-translation-failure-message-bar",
+ tryAgainButton: "select-translations-panel-try-again-button",
+ tryAnotherSourceMenuList:
+ "select-translations-panel-try-another-language",
+ tryAnotherSourceMenuPopup:
+ "select-translations-panel-try-another-language-menupopup",
+ unsupportedLanguageContent:
+ "select-translations-panel-unsupported-language-content",
+ unsupportedLanguageMessageBar:
+ "select-translations-panel-unsupported-language-message-bar",
});
}
@@ -213,12 +249,32 @@ var SelectTranslationsPanel = new (class {
// Since none of the detected languages were supported, check to see if the
// document has a specified language tag that is supported.
- const actor = TranslationsParent.getTranslationsActor(
- gBrowser.selectedBrowser
- );
- const detectedLanguages = actor.languageState.detectedLanguages;
- if (detectedLanguages?.isDocLangTagSupported) {
- return detectedLanguages.docLangTag;
+ try {
+ const actor = TranslationsParent.getTranslationsActor(
+ gBrowser.selectedBrowser
+ );
+ const detectedLanguages = actor.languageState.detectedLanguages;
+ if (detectedLanguages?.isDocLangTagSupported) {
+ return detectedLanguages.docLangTag;
+ }
+ } catch (error) {
+ // Failed to retrieve the Translations actor to detect the document language.
+ // This is most likely due to attempting to retrieve the actor in a page that
+ // is restricted for Full Page Translations, such as a PDF or reader mode, but
+ // Select Translations is often still available, so we can safely continue to
+ // the final return fallback.
+ if (
+ !TranslationsParent.isFullPageTranslationsRestrictedForPage(gBrowser)
+ ) {
+ // If we failed to retrieve the TranslationsParent actor on a non-restricted page,
+ // we should warn about this, because it is unexpected. The SelectTranslationsPanel
+ // itself will display an error state if this causes a failure, and this will help
+ // diagnose the issue if this scenario should ever occur.
+ this.console?.warn(
+ "Failed to retrieve the TranslationsParent actor on a page where Full Page Translations is not restricted."
+ );
+ this.console?.error(error);
+ }
}
// No supported language was found, so return the top detected language
@@ -233,19 +289,27 @@ var SelectTranslationsPanel = new (class {
* @param {string} textToTranslate - The text for which the language detection and target language retrieval are performed.
* @returns {Promise<{fromLang?: string, toLang?: string}>} - An object containing the language pair for the translation.
* The `fromLang` property is omitted if it is a language that is not currently supported by Firefox Translations.
- * The `toLang` property is omitted if it is the same as `fromLang`.
*/
async getLangPairPromise(textToTranslate) {
+ if (
+ TranslationsParent.isInAutomation() &&
+ !TranslationsParent.isTranslationsEngineMocked()
+ ) {
+ // If we are in automation, and the Translations Engine is NOT mocked, then that means
+ // we are in a test case in which we are not explicitly testing Select Translations,
+ // and the code to get the supported languages below will not be available. However,
+ // we still need to ensure that the translate-selection menuitem in the context menu
+ // is compatible with all code in other tests, so we will return "en" for the purpose
+ // of being able to localize and display the context-menu item in other test cases.
+ return { toLang: "en" };
+ }
+
const [fromLang, toLang] = await Promise.all([
SelectTranslationsPanel.getTopSupportedDetectedLanguage(textToTranslate),
TranslationsParent.getTopPreferredSupportedToLang(),
]);
- return {
- fromLang,
- // If the fromLang and toLang are the same, discard the toLang.
- toLang: fromLang === toLang ? undefined : toLang,
- };
+ return { fromLang, toLang };
}
/**
@@ -300,11 +364,49 @@ var SelectTranslationsPanel = new (class {
*/
async #initializeLanguageMenuLists(langPairPromise) {
const { fromLang, toLang } = await langPairPromise;
- const { fromMenuList, toMenuList } = this.elements;
+ const {
+ fromMenuList,
+ fromMenuPopup,
+ toMenuList,
+ toMenuPopup,
+ tryAnotherSourceMenuList,
+ } = this.elements;
+
await Promise.all([
this.#initializeLanguageMenuList(fromLang, fromMenuList),
this.#initializeLanguageMenuList(toLang, toMenuList),
+ this.#initializeLanguageMenuList(null, tryAnotherSourceMenuList),
]);
+
+ this.#maybeTranslateOnEvents(["keypress"], fromMenuList);
+ this.#maybeTranslateOnEvents(["keypress"], toMenuList);
+
+ this.#maybeTranslateOnEvents(["popuphidden"], fromMenuPopup);
+ this.#maybeTranslateOnEvents(["popuphidden"], toMenuPopup);
+ }
+
+ /**
+ * Initializes event listeners on the panel class the first time
+ * this function is called, and is a no-op on subsequent calls.
+ */
+ #initializeEventListeners() {
+ if (this.#eventListenersInitialized) {
+ // Event listeners have already been initialized, do nothing.
+ return;
+ }
+
+ const { panel, fromMenuList, toMenuList, tryAnotherSourceMenuList } =
+ this.elements;
+
+ panel.addEventListener("popupshown", this);
+ panel.addEventListener("popuphidden", this);
+
+ panel.addEventListener("command", this);
+ fromMenuList.addEventListener("command", this);
+ toMenuList.addEventListener("command", this);
+ tryAnotherSourceMenuList.addEventListener("command", this);
+
+ this.#eventListenersInitialized = true;
}
/**
@@ -320,20 +422,60 @@ var SelectTranslationsPanel = new (class {
*/
async open(event, screenX, screenY, sourceText, langPairPromise) {
if (this.#isOpen()) {
+ await this.#forceReopen(
+ event,
+ screenX,
+ screenY,
+ sourceText,
+ langPairPromise
+ );
return;
}
- this.#registerSourceText(sourceText);
- await this.#ensureLangListsBuilt();
+ try {
+ this.#isFullPageTranslationsRestrictedForPage =
+ TranslationsParent.isFullPageTranslationsRestrictedForPage(gBrowser);
+ this.#initializeEventListeners();
+ await this.#ensureLangListsBuilt();
+ await Promise.all([
+ this.#cachePlaceholderText(),
+ this.#initializeLanguageMenuLists(langPairPromise),
+ this.#registerSourceText(sourceText, langPairPromise),
+ ]);
+ this.#maybeRequestTranslation();
+ } catch (error) {
+ this.console?.error(error);
+ this.#changeStateToInitFailure(
+ event,
+ screenX,
+ screenY,
+ sourceText,
+ langPairPromise
+ );
+ }
- await Promise.all([
- this.#cachePlaceholderText(),
- this.#initializeLanguageMenuLists(langPairPromise),
- ]);
+ this.#openPopup(event, screenX, screenY);
+ }
- this.#displayIdlePlaceholder();
- this.#maybeRequestTranslation();
- await this.#openPopup(event, screenX, screenY);
+ /**
+ * Forces the panel to close and reopen at the same location.
+ *
+ * This should never be called in the regular flow of events, but is good to have in case
+ * the panel somehow gets into an invalid state.
+ *
+ * @param {Event} event - The triggering event for opening the panel.
+ * @param {number} screenX - The x-axis location of the screen at which to open the popup.
+ * @param {number} screenY - The y-axis location of the screen at which to open the popup.
+ * @param {string} sourceText - The text to translate.
+ * @param {Promise} langPairPromise - Promise resolving to language pair data for initializing dropdowns.
+ *
+ * @returns {Promise<void>}
+ */
+ async #forceReopen(event, screenX, screenY, sourceText, langPairPromise) {
+ this.console?.warn("The SelectTranslationsPanel was forced to reopen.");
+ this.close();
+ this.#changeStateToClosed();
+ await this.open(event, screenX, screenY, sourceText, langPairPromise);
}
/**
@@ -343,9 +485,7 @@ var SelectTranslationsPanel = new (class {
* @param {number} screenX - The x-axis location of the screen at which to open the popup.
* @param {number} screenY - The y-axis location of the screen at which to open the popup.
*/
- async #openPopup(event, screenX, screenY) {
- await window.ensureCustomElements("moz-button-group");
-
+ #openPopup(event, screenX, screenY) {
this.console?.log("Showing SelectTranslationsPanel");
const { panel } = this.elements;
panel.openPopupAtScreen(screenX, screenY, /* isContextMenu */ false, event);
@@ -356,20 +496,38 @@ var SelectTranslationsPanel = new (class {
* on the length of the text.
*
* @param {string} sourceText - The text to translate.
+ * @param {Promise<{fromLang?: string, toLang?: string}>} langPairPromise
*
* @returns {Promise<void>}
*/
- #registerSourceText(sourceText) {
+ async #registerSourceText(sourceText, langPairPromise) {
const { textArea } = this.elements;
- this.#changeStateTo("idle", /* retainEntries */ false, {
- sourceText,
- });
+ const { fromLang, toLang } = await langPairPromise;
+ const isFromLangSupported = await TranslationsParent.isSupportedAsFromLang(
+ fromLang
+ );
+
+ if (isFromLangSupported) {
+ this.#changeStateTo("idle", /* retainEntries */ false, {
+ sourceText,
+ fromLanguage: fromLang,
+ toLanguage: toLang,
+ });
+ } else {
+ this.#changeStateTo("unsupported", /* retainEntries */ false, {
+ sourceText,
+ detectedLanguage: fromLang,
+ toLanguage: toLang,
+ });
+ }
if (sourceText.length < SelectTranslationsPanel.textLengthThreshold) {
textArea.style.height = SelectTranslationsPanel.shortTextHeight;
} else {
textArea.style.height = SelectTranslationsPanel.longTextHeight;
}
+
+ this.#maybeTranslateOnEvents(["focus"], textArea);
}
/**
@@ -385,24 +543,122 @@ var SelectTranslationsPanel = new (class {
}
/**
- * Handles events when a popup is shown within the panel, including showing
- * the panel itself.
+ * Opens the settings menu popup at the settings button gear-icon.
+ */
+ #openSettingsPopup() {
+ const { settingsButton } = this.elements;
+ const popup = settingsButton.ownerDocument.getElementById(
+ "select-translations-panel-settings-menupopup"
+ );
+ popup.openPopup(settingsButton, "after_start");
+ }
+
+ /**
+ * Opens the "About translation in Firefox" Mozilla support page in a new tab.
+ */
+ onAboutTranslations() {
+ this.close();
+ const window =
+ gBrowser.selectedBrowser.browsingContext.top.embedderElement.ownerGlobal;
+ window.openTrustedLinkIn(
+ "https://support.mozilla.org/kb/website-translation",
+ "tab",
+ {
+ forceForeground: true,
+ triggeringPrincipal:
+ Services.scriptSecurityManager.getSystemPrincipal(),
+ }
+ );
+ }
+
+ /**
+ * Opens the Translations section of about:preferences in a new tab.
+ */
+ openTranslationsSettingsPage() {
+ this.close();
+ const window =
+ gBrowser.selectedBrowser.browsingContext.top.embedderElement.ownerGlobal;
+ window.openTrustedLinkIn("about:preferences#general-translations", "tab");
+ }
+
+ /**
+ * Handles events when a command event is triggered within the panel.
*
- * @param {Event} event - The event that triggered the popup to show.
+ * @param {Element} target - The event target
*/
- handlePanelPopupShownEvent(event) {
- const { panel, fromMenuPopup, toMenuPopup } = this.elements;
- switch (event.target.id) {
- case panel.id: {
- this.#updatePanelUIFromState();
+ #handleCommandEvent(target) {
+ const {
+ cancelButton,
+ copyButton,
+ doneButtonPrimary,
+ doneButtonSecondary,
+ fromMenuList,
+ fromMenuPopup,
+ settingsButton,
+ toMenuList,
+ toMenuPopup,
+ translateButton,
+ translateFullPageButton,
+ tryAgainButton,
+ tryAnotherSourceMenuList,
+ tryAnotherSourceMenuPopup,
+ } = this.elements;
+ switch (target.id) {
+ case cancelButton.id:
+ case doneButtonPrimary.id:
+ case doneButtonSecondary.id: {
+ this.close();
break;
}
+ case copyButton.id: {
+ this.onClickCopyButton();
+ break;
+ }
+ case fromMenuList.id:
case fromMenuPopup.id: {
- this.#maybeTranslateOnEvents(["popuphidden"], fromMenuPopup);
+ this.onChangeFromLanguage();
+ break;
+ }
+ case settingsButton.id: {
+ this.#openSettingsPopup();
break;
}
+ case toMenuList.id:
case toMenuPopup.id: {
- this.#maybeTranslateOnEvents(["popuphidden"], toMenuPopup);
+ this.onChangeToLanguage();
+ break;
+ }
+ case translateButton.id: {
+ this.onClickTranslateButton();
+ break;
+ }
+ case translateFullPageButton.id: {
+ this.onClickTranslateFullPageButton();
+ break;
+ }
+ case tryAgainButton.id: {
+ this.onClickTryAgainButton();
+ break;
+ }
+ case tryAnotherSourceMenuList.id:
+ case tryAnotherSourceMenuPopup.id: {
+ this.onChangeTryAnotherSourceLanguage();
+ break;
+ }
+ }
+ }
+
+ /**
+ * Handles events when a popup is shown within the panel, including showing
+ * the panel itself.
+ *
+ * @param {Element} target - The event target
+ */
+ #handlePopupShownEvent(target) {
+ const { panel } = this.elements;
+ switch (target.id) {
+ case panel.id: {
+ this.#updatePanelUIFromState();
break;
}
}
@@ -412,13 +668,44 @@ var SelectTranslationsPanel = new (class {
* Handles events when a popup is closed within the panel, including closing
* the panel itself.
*
- * @param {Event} event - The event that triggered the popup to close.
+ * @param {Element} target - The event target
*/
- handlePanelPopupHiddenEvent(event) {
+ #handlePopupHiddenEvent(target) {
const { panel } = this.elements;
- switch (event.target.id) {
+ switch (target.id) {
case panel.id: {
this.#changeStateToClosed();
+ this.#removeActiveTranslationListeners();
+ break;
+ }
+ }
+ }
+
+ /**
+ * Handles events in the SelectTranslationsPanel.
+ *
+ * @param {Event} event - The event to handle.
+ */
+ handleEvent(event) {
+ let target = event.target;
+
+ // If a menuitem within a menulist is the target, those don't have ids,
+ // so we want to traverse until we get to a parent element with an id.
+ while (!target.id && target.parentElement) {
+ target = target.parentElement;
+ }
+
+ switch (event.type) {
+ case "command": {
+ this.#handleCommandEvent(target);
+ break;
+ }
+ case "popupshown": {
+ this.#handlePopupShownEvent(target);
+ break;
+ }
+ case "popuphidden": {
+ this.#handlePopupHiddenEvent(target);
break;
}
}
@@ -428,18 +715,138 @@ var SelectTranslationsPanel = new (class {
* Handles events when the panels select from-language is changed.
*/
onChangeFromLanguage() {
- const { fromMenuList, toMenuList } = this.elements;
- this.#maybeTranslateOnEvents(["blur", "keypress"], fromMenuList);
- this.#maybeStealLanguageFrom(toMenuList);
+ this.#updateConditionalUIEnabledState();
}
/**
* Handles events when the panels select to-language is changed.
*/
onChangeToLanguage() {
- const { toMenuList, fromMenuList } = this.elements;
- this.#maybeTranslateOnEvents(["blur", "keypress"], toMenuList);
- this.#maybeStealLanguageFrom(fromMenuList);
+ this.#updateConditionalUIEnabledState();
+ }
+
+ /**
+ * Handles events when the panel's try-another-source language is changed.
+ */
+ onChangeTryAnotherSourceLanguage() {
+ const { tryAnotherSourceMenuList, translateButton } = this.elements;
+ if (tryAnotherSourceMenuList.value) {
+ translateButton.disabled = false;
+ }
+ }
+
+ /**
+ * Handles events when the panel's copy button is clicked.
+ */
+ onClickCopyButton() {
+ try {
+ ClipboardHelper.copyString(this.getTranslatedText());
+ } catch (error) {
+ this.console?.error(error);
+ return;
+ }
+
+ this.#checkCopyButton();
+ }
+
+ /**
+ * Handles events when the panel's translate button is clicked.
+ */
+ onClickTranslateButton() {
+ const { fromMenuList, tryAnotherSourceMenuList } = this.elements;
+ fromMenuList.value = tryAnotherSourceMenuList.value;
+ this.#maybeRequestTranslation();
+ }
+
+ /**
+ * Handles events when the panel's translate-full-page button is clicked.
+ */
+ onClickTranslateFullPageButton() {
+ const { panel } = this.elements;
+ const { fromLanguage, toLanguage } = this.#getSelectedLanguagePair();
+
+ try {
+ const actor = TranslationsParent.getTranslationsActor(
+ gBrowser.selectedBrowser
+ );
+ panel.addEventListener(
+ "popuphidden",
+ () =>
+ actor.translate(
+ fromLanguage,
+ toLanguage,
+ false // reportAsAutoTranslate
+ ),
+ { once: true }
+ );
+ } catch (error) {
+ // This situation would only occur if the translate-full-page button as invoked
+ // while Translations actor is not available. the logic within this class explicitly
+ // hides the button in this case, and this should not be possible under normal conditions,
+ // but if this button were to somehow still be invoked, the best thing we can do here is log
+ // an error to the console because the FullPageTranslationsPanel assumes that the actor is available.
+ this.console?.error(error);
+ }
+
+ this.close();
+ }
+
+ /**
+ * Handles events when the panel's try-again button is clicked.
+ */
+ onClickTryAgainButton() {
+ switch (this.phase()) {
+ case "translation-failure": {
+ // If the translation failed, we just need to try translating again.
+ this.#maybeRequestTranslation();
+ break;
+ }
+ case "init-failure": {
+ // If the initialization failed, we need to close the panel and try reopening it
+ // which will attempt to initialize everything again after failure.
+ const { panel } = this.elements;
+ const { event, screenX, screenY, sourceText, langPairPromise } =
+ this.#translationState;
+
+ panel.addEventListener(
+ "popuphidden",
+ () => this.open(event, screenX, screenY, sourceText, langPairPromise),
+ { once: true }
+ );
+
+ this.close();
+ break;
+ }
+ default: {
+ this.console?.error(
+ `Unexpected state "${this.phase()}" on try-again button click.`
+ );
+ }
+ }
+ }
+
+ /**
+ * Changes the copy button's visual icon to checked, and its localized text to "Copied".
+ */
+ #checkCopyButton() {
+ const { copyButton } = this.elements;
+ copyButton.classList.add("copied");
+ document.l10n.setAttributes(
+ copyButton,
+ "select-translations-panel-copy-button-copied"
+ );
+ }
+
+ /**
+ * Changes the copy button's visual icon to unchecked, and its localized text to "Copy".
+ */
+ #uncheckCopyButton() {
+ const { copyButton } = this.elements;
+ copyButton.classList.remove("copied");
+ document.l10n.setAttributes(
+ copyButton,
+ "select-translations-panel-copy-button"
+ );
}
/**
@@ -455,21 +862,6 @@ var SelectTranslationsPanel = new (class {
}
/**
- * Deselects the language from the target menu list if both menu lists
- * have the same language selected, simulating the effect of one menu
- * list stealing the selected language value from the other.
- *
- * @param {Element} menuList - The target menu list element to update.
- */
- async #maybeStealLanguageFrom(menuList) {
- const { fromLanguage, toLanguage } = this.#getSelectedLanguagePair();
- if (fromLanguage === toLanguage) {
- await this.#deselectLanguage(menuList);
- this.#maybeFocusMenuList(menuList);
- }
- }
-
- /**
* Focuses on the given menu list if provided and empty, or defaults to focusing one
* of the from-menu or to-menu lists if either is empty.
*
@@ -525,21 +917,6 @@ var SelectTranslationsPanel = new (class {
}
/**
- * Checks if the translator's language configuration matches the given language pair.
- *
- * @param {string} fromLanguage - The from-language to compare.
- * @param {string} toLanguage - The to-language to compare.
- *
- * @returns {boolean} - True if the translator's languages match the given pair, otherwise false.
- */
- #translatorMatchesLangPair(fromLanguage, toLanguage) {
- return (
- this.#translator?.fromLanguage === fromLanguage &&
- this.#translator?.toLanguage === toLanguage
- );
- }
-
- /**
* Retrieves the currently selected language pair from the menu lists.
*
* @returns {{fromLanguage: string, toLanguage: string}} An object containing the selected languages.
@@ -575,9 +952,9 @@ var SelectTranslationsPanel = new (class {
/**
* Retrieves the current phase of the translation state.
*
- * @returns {SelectTranslationsPanelState}
+ * @returns {string}
*/
- #phase() {
+ phase() {
return this.#translationState.phase;
}
@@ -585,14 +962,14 @@ var SelectTranslationsPanel = new (class {
* @returns {boolean} True if the panel is open, otherwise false.
*/
#isOpen() {
- return this.#phase() !== "closed";
+ return this.phase() !== "closed";
}
/**
* @returns {boolean} True if the panel is closed, otherwise false.
*/
#isClosed() {
- return this.#phase() === "closed";
+ return this.phase() === "closed";
}
/**
@@ -604,17 +981,16 @@ var SelectTranslationsPanel = new (class {
* @throws {Error} If an invalid phase is specified.
*/
#changeStateTo(phase, retainEntries, data = null) {
- const { textArea } = this.elements;
switch (phase) {
- case "translating": {
- textArea.classList.add("translating");
- break;
- }
case "closed":
case "idle":
+ case "init-failure":
+ case "translation-failure":
case "translatable":
- case "translated": {
- textArea.classList.remove("translating");
+ case "translating":
+ case "translated":
+ case "unsupported": {
+ // Phase is valid, continue on.
break;
}
default: {
@@ -622,7 +998,7 @@ var SelectTranslationsPanel = new (class {
}
}
- const previousPhase = this.#phase();
+ const previousPhase = this.phase();
if (data && retainEntries) {
// Change the phase and apply new entries from data, but retain non-overwritten entries from previous state.
this.#translationState = { ...this.#translationState, phase, ...data };
@@ -637,19 +1013,26 @@ var SelectTranslationsPanel = new (class {
this.#translationState = { phase };
}
- if (previousPhase === this.#phase()) {
+ if (previousPhase === this.phase()) {
// Do not continue on to update the UI because the phase didn't change.
return;
}
- const { fromLanguage, toLanguage } = this.#translationState;
+ const { fromLanguage, toLanguage, detectedLanguage } =
+ this.#translationState;
+ const sourceLanguage = fromLanguage ? fromLanguage : detectedLanguage;
this.console?.debug(
- `SelectTranslationsPanel (${fromLanguage ? fromLanguage : "??"}-${
+ `SelectTranslationsPanel (${sourceLanguage ? sourceLanguage : "??"}-${
toLanguage ? toLanguage : "??"
}) state change (${previousPhase} => ${phase})`
);
this.#updatePanelUIFromState();
+ document.dispatchEvent(
+ new CustomEvent("SelectTranslationsPanelStateChanged", {
+ detail: { phase },
+ })
+ );
}
/**
@@ -665,7 +1048,7 @@ var SelectTranslationsPanel = new (class {
* @throws {Error} If the current state is not "translatable".
*/
#changeStateToTranslating() {
- const phase = this.#phase();
+ const phase = this.phase();
if (phase !== "translatable") {
throw new Error(`Invalid state change (${phase} => translating)`);
}
@@ -678,7 +1061,7 @@ var SelectTranslationsPanel = new (class {
* @throws {Error} If the current state is not "translating".
*/
#changeStateToTranslated(translatedText) {
- const phase = this.#phase();
+ const phase = this.phase();
if (phase !== "translating") {
throw new Error(`Invalid state change (${phase} => translated)`);
}
@@ -688,45 +1071,176 @@ var SelectTranslationsPanel = new (class {
}
/**
- * Transitions the phase of the state based on the given language pair.
+ * Changes the phase to "init-failure".
+ */
+ #changeStateToInitFailure(
+ event,
+ screenX,
+ screenY,
+ sourceText,
+ langPairPromise
+ ) {
+ this.#changeStateTo("init-failure", /* retainEntries */ true, {
+ event,
+ screenX,
+ screenY,
+ sourceText,
+ langPairPromise,
+ });
+ }
+
+ /**
+ * Changes the phase from "translating" to "translation-failure".
+ */
+ #changeStateToTranslationFailure() {
+ const phase = this.phase();
+ if (phase !== "translating") {
+ this.console?.error(
+ `Invalid state change (${phase} => translation-failure)`
+ );
+ }
+ this.#changeStateTo("translation-failure", /* retainEntries */ true);
+ }
+
+ /**
+ * Transitions the phase to "translatable" if the proper conditions are met,
+ * otherwise retains the same phase as before.
*
* @param {string} fromLanguage - The BCP-47 from-language tag.
* @param {string} toLanguage - The BCP-47 to-language tag.
- *
- * @returns {SelectTranslationsPanelState} The new phase of the translation state.
*/
- #changeStateByLanguagePair(fromLanguage, toLanguage) {
+ #maybeChangeStateToTranslatable(fromLanguage, toLanguage) {
const {
- phase: previousPhase,
fromLanguage: previousFromLanguage,
toLanguage: previousToLanguage,
} = this.#translationState;
- let nextPhase = "translatable";
+ const langSelectionChanged = () =>
+ previousFromLanguage !== fromLanguage ||
+ previousToLanguage !== toLanguage;
+
+ const shouldTranslateEvenIfLangSelectionHasNotChanged = () => {
+ const phase = this.phase();
+ return (
+ // The panel has just opened, and this is the initial translation.
+ phase === "idle" ||
+ // The previous translation failed and we are about to try again.
+ phase === "translation-failure"
+ );
+ };
if (
- // No from-language is selected, so we cannot translate.
- !fromLanguage ||
- // No to-language is selected, so we cannot translate.
- !toLanguage ||
- // The same language has been selected, so we cannot translate.
- fromLanguage === toLanguage
- ) {
- nextPhase = "idle";
- } else if (
- // The languages have not changed, so there is nothing to do.
- previousFromLanguage === fromLanguage &&
- previousToLanguage === toLanguage
+ // A valid from-language is actively selected.
+ fromLanguage &&
+ // A valid to-language is actively selected.
+ toLanguage &&
+ // The language selection has changed, requiring a new translation.
+ (langSelectionChanged() ||
+ // We should try to translate even if the language selection has not changed.
+ shouldTranslateEvenIfLangSelectionHasNotChanged())
) {
- nextPhase = previousPhase;
+ this.#changeStateTo("translatable", /* retainEntries */ true, {
+ fromLanguage,
+ toLanguage,
+ });
}
+ }
- this.#changeStateTo(nextPhase, /* retainEntries */ true, {
- fromLanguage,
- toLanguage,
- });
+ /**
+ * Handles changes to the copy button based on the current translation state.
+ *
+ * @param {string} phase - The current phase of the translation state.
+ */
+ #handleCopyButtonChanges(phase) {
+ switch (phase) {
+ case "closed":
+ case "translation-failure":
+ case "translated": {
+ this.#uncheckCopyButton();
+ break;
+ }
+ case "idle":
+ case "init-failure":
+ case "translatable":
+ case "translating":
+ case "unsupported": {
+ // Do nothing.
+ break;
+ }
+ default: {
+ throw new Error(`Invalid state change to '${phase}'`);
+ }
+ }
+ }
- return nextPhase;
+ /**
+ * Handles changes to the text area's background image based on the current translation state.
+ *
+ * @param {string} phase - The current phase of the translation state.
+ */
+ #handleTextAreaBackgroundChanges(phase) {
+ const { textArea } = this.elements;
+ switch (phase) {
+ case "translating": {
+ textArea.classList.add("translating");
+ break;
+ }
+ case "closed":
+ case "idle":
+ case "init-failure":
+ case "translation-failure":
+ case "translatable":
+ case "translated":
+ case "unsupported": {
+ textArea.classList.remove("translating");
+ break;
+ }
+ default: {
+ throw new Error(`Invalid state change to '${phase}'`);
+ }
+ }
+ }
+
+ /**
+ * Handles changes to the primary UI components based on the current translation state.
+ *
+ * @param {string} phase - The current phase of the translation state.
+ */
+ #handlePrimaryUIChanges(phase) {
+ switch (phase) {
+ case "closed":
+ case "idle": {
+ this.#displayIdlePlaceholder();
+ break;
+ }
+ case "init-failure": {
+ this.#displayInitFailureMessage();
+ break;
+ }
+ case "translation-failure": {
+ this.#displayTranslationFailureMessage();
+ break;
+ }
+ case "translatable": {
+ // Do nothing.
+ break;
+ }
+ case "translating": {
+ this.#displayTranslatingPlaceholder();
+ break;
+ }
+ case "translated": {
+ this.#displayTranslatedText();
+ break;
+ }
+ case "unsupported": {
+ this.#displayUnsupportedLanguageMessage();
+ break;
+ }
+ default: {
+ throw new Error(`Invalid state change to '${phase}'`);
+ }
+ }
}
/**
@@ -745,9 +1259,7 @@ var SelectTranslationsPanel = new (class {
// Continue only if the current translationId matches.
translationId === this.#translationId &&
// Continue only if the given language pair is still the actively selected pair.
- this.#isSelectedLangPair(fromLanguage, toLanguage) &&
- // Continue only if the given language pair matches the current translator.
- this.#translatorMatchesLangPair(fromLanguage, toLanguage)
+ this.#isSelectedLangPair(fromLanguage, toLanguage)
);
}
@@ -755,6 +1267,8 @@ var SelectTranslationsPanel = new (class {
* Displays the placeholder text for the translation state's "idle" phase.
*/
#displayIdlePlaceholder() {
+ this.#showMainContent();
+
const { textArea } = SelectTranslationsPanel.elements;
textArea.value = this.#idlePlaceholderText;
this.#updateTextDirection();
@@ -766,6 +1280,8 @@ var SelectTranslationsPanel = new (class {
* Displays the placeholder text for the translation state's "translating" phase.
*/
#displayTranslatingPlaceholder() {
+ this.#showMainContent();
+
const { textArea } = SelectTranslationsPanel.elements;
textArea.value = this.#translatingPlaceholderText;
this.#updateTextDirection();
@@ -777,6 +1293,8 @@ var SelectTranslationsPanel = new (class {
* Displays the translated text for the translation state's "translated" phase.
*/
#displayTranslatedText() {
+ this.#showMainContent();
+
const { toLanguage } = this.#getSelectedLanguagePair();
const { textArea } = SelectTranslationsPanel.elements;
textArea.value = this.getTranslatedText();
@@ -786,38 +1304,238 @@ var SelectTranslationsPanel = new (class {
}
/**
+ * Sets attributes on panel elements that are specifically relevant
+ * to the SelectTranslationsPanel's state.
+ *
+ * @param {object} options - Options of which attributes to set.
+ * @param {Record<string, Element[]>} options.makeHidden - Make these elements hidden.
+ * @param {Record<string, Element[]>} options.makeVisible - Make these elements visible.
+ */
+ #setPanelElementAttributes({ makeHidden = [], makeVisible = [] }) {
+ for (const element of makeHidden) {
+ element.hidden = true;
+ }
+ for (const element of makeVisible) {
+ element.hidden = false;
+ }
+ }
+
+ /**
* Enables or disables UI components that are conditional on a valid language pair being selected.
*/
#updateConditionalUIEnabledState() {
const { fromLanguage, toLanguage } = this.#getSelectedLanguagePair();
- const { copyButton, translateFullPageButton, textArea } = this.elements;
+ const {
+ copyButton,
+ textArea,
+ translateButton,
+ translateFullPageButton,
+ tryAnotherSourceMenuList,
+ } = this.elements;
const invalidLangPairSelected = !fromLanguage || !toLanguage;
- const isTranslating = this.#phase() === "translating";
+ const isTranslating = this.phase() === "translating";
textArea.disabled = invalidLangPairSelected;
- translateFullPageButton.disabled = invalidLangPairSelected;
copyButton.disabled = invalidLangPairSelected || isTranslating;
+ translateButton.disabled = !tryAnotherSourceMenuList.value;
+ translateFullPageButton.disabled =
+ invalidLangPairSelected ||
+ fromLanguage === toLanguage ||
+ this.#isFullPageTranslationsRestrictedForPage;
}
/**
* Updates the panel UI based on the current phase of the translation state.
*/
#updatePanelUIFromState() {
- switch (this.#phase()) {
- case "idle": {
- this.#displayIdlePlaceholder();
- break;
- }
- case "translating": {
- this.#displayTranslatingPlaceholder();
- break;
- }
- case "translated": {
- this.#displayTranslatedText();
- break;
+ const phase = this.phase();
+ this.#handlePrimaryUIChanges(phase);
+ this.#handleCopyButtonChanges(phase);
+ this.#handleTextAreaBackgroundChanges(phase);
+ }
+
+ /**
+ * Shows the panel's main-content group of elements.
+ */
+ #showMainContent() {
+ const {
+ cancelButton,
+ copyButton,
+ doneButtonPrimary,
+ doneButtonSecondary,
+ initFailureContent,
+ mainContent,
+ unsupportedLanguageContent,
+ textArea,
+ translateButton,
+ translateFullPageButton,
+ translationFailureMessageBar,
+ tryAgainButton,
+ } = this.elements;
+ this.#setPanelElementAttributes({
+ makeHidden: [
+ cancelButton,
+ doneButtonSecondary,
+ initFailureContent,
+ translateButton,
+ translationFailureMessageBar,
+ tryAgainButton,
+ unsupportedLanguageContent,
+ ...(this.#isFullPageTranslationsRestrictedForPage
+ ? [translateFullPageButton]
+ : []),
+ ],
+ makeVisible: [
+ mainContent,
+ copyButton,
+ doneButtonPrimary,
+ textArea,
+ ...(this.#isFullPageTranslationsRestrictedForPage
+ ? []
+ : [translateFullPageButton]),
+ ],
+ });
+ }
+
+ /**
+ * Shows the panel's unsupported-language group of elements.
+ */
+ #showUnsupportedLanguageContent() {
+ const {
+ cancelButton,
+ copyButton,
+ doneButtonPrimary,
+ doneButtonSecondary,
+ initFailureContent,
+ mainContent,
+ unsupportedLanguageContent,
+ translateButton,
+ translateFullPageButton,
+ tryAgainButton,
+ } = this.elements;
+ this.#setPanelElementAttributes({
+ makeHidden: [
+ cancelButton,
+ doneButtonPrimary,
+ copyButton,
+ initFailureContent,
+ mainContent,
+ translateFullPageButton,
+ tryAgainButton,
+ ],
+ makeVisible: [
+ doneButtonSecondary,
+ translateButton,
+ unsupportedLanguageContent,
+ ],
+ });
+ }
+
+ /**
+ * Displays the panel content for when the language dropdowns fail to populate.
+ */
+ #displayInitFailureMessage() {
+ const {
+ cancelButton,
+ copyButton,
+ doneButtonPrimary,
+ doneButtonSecondary,
+ initFailureContent,
+ mainContent,
+ unsupportedLanguageContent,
+ translateButton,
+ translateFullPageButton,
+ tryAgainButton,
+ } = this.elements;
+ this.#setPanelElementAttributes({
+ makeHidden: [
+ doneButtonPrimary,
+ doneButtonSecondary,
+ copyButton,
+ mainContent,
+ translateButton,
+ translateFullPageButton,
+ unsupportedLanguageContent,
+ ],
+ makeVisible: [initFailureContent, cancelButton, tryAgainButton],
+ });
+ tryAgainButton.focus({ focusVisible: true });
+ }
+
+ /**
+ * Displays the panel content for when a translation fails to complete.
+ */
+ #displayTranslationFailureMessage() {
+ const {
+ cancelButton,
+ copyButton,
+ doneButtonPrimary,
+ doneButtonSecondary,
+ initFailureContent,
+ mainContent,
+ textArea,
+ translateButton,
+ translateFullPageButton,
+ translationFailureMessageBar,
+ tryAgainButton,
+ unsupportedLanguageContent,
+ } = this.elements;
+ this.#setPanelElementAttributes({
+ makeHidden: [
+ doneButtonPrimary,
+ doneButtonSecondary,
+ copyButton,
+ initFailureContent,
+ translateButton,
+ translateFullPageButton,
+ textArea,
+ unsupportedLanguageContent,
+ ],
+ makeVisible: [
+ cancelButton,
+ mainContent,
+ translationFailureMessageBar,
+ tryAgainButton,
+ ],
+ });
+ tryAgainButton.focus({ focusVisible: true });
+ }
+
+ /**
+ * Displays the panel's unsupported language message bar, showing
+ * the panel's unsupported-language elements.
+ */
+ #displayUnsupportedLanguageMessage() {
+ const { detectedLanguage } = this.#translationState;
+ const { unsupportedLanguageMessageBar, tryAnotherSourceMenuList } =
+ this.elements;
+ const displayNames = new Services.intl.DisplayNames(undefined, {
+ type: "language",
+ });
+ try {
+ const language = displayNames.of(detectedLanguage);
+ if (language) {
+ document.l10n.setAttributes(
+ unsupportedLanguageMessageBar,
+ "select-translations-panel-unsupported-language-message-known",
+ { language }
+ );
+ } else {
+ // Will be immediately caught.
+ throw new Error();
}
+ } catch {
+ // Either displayNames.of() threw, or we threw due to no display name found.
+ // In either case, localize the message for an unknown language.
+ document.l10n.setAttributes(
+ unsupportedLanguageMessageBar,
+ "select-translations-panel-unsupported-language-message-unknown"
+ );
}
+ this.#updateConditionalUIEnabledState();
+ this.#showUnsupportedLanguageContent();
+ this.#maybeFocusMenuList(tryAnotherSourceMenuList);
}
/**
@@ -868,25 +1586,16 @@ var SelectTranslationsPanel = new (class {
*
* @returns {Promise<Translator>} A promise that resolves to a `Translator` instance for the given language pair.
*/
- async #getOrCreateTranslator(fromLanguage, toLanguage) {
- if (this.#translatorMatchesLangPair(fromLanguage, toLanguage)) {
- return this.#translator;
- }
-
+ async #createTranslator(fromLanguage, toLanguage) {
this.console?.log(
`Creating new Translator (${fromLanguage}-${toLanguage})`
);
- if (this.#translator) {
- this.#translator.destroy();
- this.#translator = null;
- }
- this.#translator = await Translator.create(
- fromLanguage,
- toLanguage,
- this.#requestTranslationsPort
- );
- return this.#translator;
+ const translator = await Translator.create(fromLanguage, toLanguage, {
+ allowSameLanguage: true,
+ requestTranslationsPort: this.#requestTranslationsPort,
+ });
+ return translator;
}
/**
@@ -897,14 +1606,16 @@ var SelectTranslationsPanel = new (class {
if (this.#isClosed()) {
return;
}
+
const { fromLanguage, toLanguage } = this.#getSelectedLanguagePair();
- const nextState = this.#changeStateByLanguagePair(fromLanguage, toLanguage);
- if (nextState !== "translatable") {
+ this.#maybeChangeStateToTranslatable(fromLanguage, toLanguage);
+
+ if (this.phase() !== "translatable") {
return;
}
const translationId = ++this.#translationId;
- this.#getOrCreateTranslator(fromLanguage, toLanguage)
+ this.#createTranslator(fromLanguage, toLanguage)
.then(translator => {
if (
this.#shouldContinueTranslation(
@@ -928,13 +1639,12 @@ var SelectTranslationsPanel = new (class {
)
) {
this.#changeStateToTranslated(translatedText);
- } else if (this.#isOpen()) {
- this.#changeStateTo("idle", /* retainEntires */ false, {
- sourceText: this.getSourceText(),
- });
}
})
- .catch(error => this.console?.error(error));
+ .catch(error => {
+ this.console?.error(error);
+ this.#changeStateToTranslationFailure();
+ });
}
/**
@@ -952,11 +1662,10 @@ var SelectTranslationsPanel = new (class {
for (const eventType of eventTypes) {
let callback;
switch (eventType) {
- case "blur":
+ case "focus":
case "popuphidden": {
callback = () => {
this.#maybeRequestTranslation();
- this.#removeTranslationListeners(target);
};
break;
}
@@ -965,7 +1674,6 @@ var SelectTranslationsPanel = new (class {
if (event.key === "Enter") {
this.#maybeRequestTranslation();
}
- this.#removeTranslationListeners(target);
};
break;
}
@@ -975,21 +1683,39 @@ var SelectTranslationsPanel = new (class {
);
}
}
- target.addEventListener(eventType, callback, { once: true });
+ target.addEventListener(eventType, callback);
target.translationListenerCallbacks.push({ eventType, callback });
}
}
}
/**
+ * Removes all translation event listeners from any panel elements that would have one.
+ */
+ #removeActiveTranslationListeners() {
+ const { fromMenuList, fromMenuPopup, textArea, toMenuList, toMenuPopup } =
+ SelectTranslationsPanel.elements;
+ this.#removeTranslationListenersFrom(fromMenuList);
+ this.#removeTranslationListenersFrom(fromMenuPopup);
+ this.#removeTranslationListenersFrom(textArea);
+ this.#removeTranslationListenersFrom(toMenuList);
+ this.#removeTranslationListenersFrom(toMenuPopup);
+ }
+
+ /**
* Removes all translation event listeners from the target element.
*
* @param {Element} target - The element from which event listeners are to be removed.
*/
- #removeTranslationListeners(target) {
+ #removeTranslationListenersFrom(target) {
+ if (!target.translationListenerCallbacks) {
+ return;
+ }
+
for (const { eventType, callback } of target.translationListenerCallbacks) {
target.removeEventListener(eventType, callback);
}
+
target.translationListenerCallbacks = [];
}
})();
diff --git a/browser/components/translations/tests/browser/browser.toml b/browser/components/translations/tests/browser/browser.toml
index 472ae28866..78ffd04c09 100644
--- a/browser/components/translations/tests/browser/browser.toml
+++ b/browser/components/translations/tests/browser/browser.toml
@@ -105,6 +105,8 @@ skip-if = ["os == 'linux' && !debug"] # Bug 1863227
["browser_translations_full_page_telemetry_translation_request.js"]
+["browser_translations_select_context_menu_engine_unsupported.js"]
+
["browser_translations_select_context_menu_feature_disabled.js"]
["browser_translations_select_context_menu_with_full_page_translations_active.js"]
@@ -115,11 +117,19 @@ skip-if = ["os == 'linux' && !debug"] # Bug 1863227
["browser_translations_select_context_menu_with_text_selected.js"]
+["browser_translations_select_panel_close_on_new_tab.js"]
+
+["browser_translations_select_panel_copy_button.js"]
+
["browser_translations_select_panel_engine_cache.js"]
["browser_translations_select_panel_fallback_to_doc_language.js"]
-["browser_translations_select_panel_open_to_idle_state.js"]
+["browser_translations_select_panel_init_failure.js"]
+
+["browser_translations_select_panel_pdf.js"]
+
+["browser_translations_select_panel_reader_mode.js"]
["browser_translations_select_panel_retranslate_on_change_language_directly.js"]
@@ -133,6 +143,10 @@ skip-if = ["os == 'linux' && !debug"] # Bug 1863227
["browser_translations_select_panel_select_same_from_and_to_languages_from_dropdown_menu.js"]
+["browser_translations_select_panel_settings_menu.js"]
+
+["browser_translations_select_panel_translate_full_page_button.js"]
+
["browser_translations_select_panel_translate_on_change_language_directly.js"]
["browser_translations_select_panel_translate_on_change_language_from_dropdown_menu.js"]
@@ -142,3 +156,11 @@ skip-if = ["os == 'linux' && !debug"] # Bug 1863227
["browser_translations_select_panel_translate_on_change_language_multiple_times_from_dropdown_menu.js"]
["browser_translations_select_panel_translate_on_open.js"]
+
+["browser_translations_select_panel_translation_failure_after_unsupported_language.js"]
+
+["browser_translations_select_panel_translation_failure_on_open.js"]
+
+["browser_translations_select_panel_translation_failure_on_retranslate.js"]
+
+["browser_translations_select_panel_unsupported_language.js"]
diff --git a/browser/components/translations/tests/browser/browser_translations_about_preferences_manage_downloaded_languages.js b/browser/components/translations/tests/browser/browser_translations_about_preferences_manage_downloaded_languages.js
index 383f2094a7..6d5b10f26c 100644
--- a/browser/components/translations/tests/browser/browser_translations_about_preferences_manage_downloaded_languages.js
+++ b/browser/components/translations/tests/browser/browser_translations_about_preferences_manage_downloaded_languages.js
@@ -36,7 +36,7 @@ add_task(async function test_about_preferences_manage_languages() {
is(
downloadAllLabel.getAttribute("data-l10n-id"),
- "translations-manage-install-description",
+ "translations-manage-download-description",
"The first row is all of the languages."
);
is(frenchLabel.textContent, "French", "There is a French row.");
@@ -178,7 +178,7 @@ add_task(async function test_about_preferences_download_reject() {
click(frenchDownload, "Downloading French");
is(
- maybeGetByL10nId("translations-manage-error-install", document),
+ maybeGetByL10nId("translations-manage-error-download", document),
null,
"No error messages are present."
);
@@ -200,13 +200,13 @@ add_task(async function test_about_preferences_download_reject() {
}
await waitForCondition(
- () => maybeGetByL10nId("translations-manage-error-install", document),
+ () => maybeGetByL10nId("translations-manage-error-download", document),
"The error message is now visible."
);
click(frenchDownload, "Attempting to download French again", document);
is(
- maybeGetByL10nId("translations-manage-error-install", document),
+ maybeGetByL10nId("translations-manage-error-download", document),
null,
"The error message is hidden again."
);
diff --git a/browser/components/translations/tests/browser/browser_translations_about_preferences_settings_ui.js b/browser/components/translations/tests/browser/browser_translations_about_preferences_settings_ui.js
index f618b27814..39495a823c 100644
--- a/browser/components/translations/tests/browser/browser_translations_about_preferences_settings_ui.js
+++ b/browser/components/translations/tests/browser/browser_translations_about_preferences_settings_ui.js
@@ -161,7 +161,7 @@ async function testLanguageList(translateSection, menuList) {
let langButton = languagelist.children[0].querySelector("moz-button");
let clickButton = BrowserTestUtils.waitForEvent(langButton, "click");
- langButton.dispatchEvent(new Event("click"));
+ langButton.click();
await clickButton;
if (i < langNum - 1) {
@@ -242,9 +242,7 @@ add_task(async function test_translations_settings_download_languages() {
langList.children[i].querySelector("moz-button"),
"click"
);
- langList.children[i]
- .querySelector("moz-button")
- .dispatchEvent(new Event("click"));
+ langList.children[i].querySelector("moz-button").click();
await clickButton;
is(
@@ -259,9 +257,7 @@ add_task(async function test_translations_settings_download_languages() {
langList.children[i].querySelector("moz-button"),
"click"
);
- langList.children[i]
- .querySelector("moz-button")
- .dispatchEvent(new Event("click"));
+ langList.children[i].querySelector("moz-button").click();
await clickButton;
is(
diff --git a/browser/components/translations/tests/browser/browser_translations_select_context_menu_engine_unsupported.js b/browser/components/translations/tests/browser/browser_translations_select_context_menu_engine_unsupported.js
new file mode 100644
index 0000000000..b5fe9de0ef
--- /dev/null
+++ b/browser/components/translations/tests/browser/browser_translations_select_context_menu_engine_unsupported.js
@@ -0,0 +1,35 @@
+/* Any copyright is dedicated to the Public Domain.
+ https://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * This test checks the availability of the translate-selection menu item in the context menu,
+ * ensuring it is not visible when the hardware does not support Translations. In this case
+ * we simulate this scenario by setting "browser.translations.simulateUnsupportedEngine" to true.
+ */
+add_task(
+ async function test_translate_selection_menuitem_is_unavailable_when_engine_is_unsupported() {
+ const { cleanup, runInPage } = await loadTestPage({
+ page: SELECT_TEST_PAGE_URL,
+ languagePairs: LANGUAGE_PAIRS,
+ prefs: [
+ ["browser.translations.enable", true],
+ ["browser.translations.select.enable", true],
+ ["browser.translations.simulateUnsupportedEngine", true],
+ ],
+ });
+
+ await SelectTranslationsTestUtils.assertContextMenuTranslateSelectionItem(
+ runInPage,
+ {
+ selectSpanishSentence: true,
+ openAtSpanishSentence: true,
+ expectMenuItemVisible: false,
+ },
+ "The translate-selection context menu item should be unavailable the translations engine is unsupported."
+ );
+
+ await cleanup();
+ }
+);
diff --git a/browser/components/translations/tests/browser/browser_translations_select_context_menu_with_hyperlink.js b/browser/components/translations/tests/browser/browser_translations_select_context_menu_with_hyperlink.js
index 83e836489f..cb0f3601d9 100644
--- a/browser/components/translations/tests/browser/browser_translations_select_context_menu_with_hyperlink.js
+++ b/browser/components/translations/tests/browser/browser_translations_select_context_menu_with_hyperlink.js
@@ -41,8 +41,8 @@ add_task(
/**
* This test case verifies the functionality of the translate-selection context menu item
* when a hyperlink is right-clicked, and the link text is in the top preferred language.
- * The menu item should offer to translate the link text without specifying a target language,
- * since it is already in the preferred language for the user.
+ * The menu item should still offer to translate the link text to the top preferred language,
+ * since the Select Translations Panel should pass through the text for same-language translation.
*/
add_task(
async function test_translate_selection_menuitem_translate_link_text_in_preferred_language() {
@@ -63,10 +63,10 @@ add_task(
selectSpanishSentence: false,
openAtEnglishHyperlink: true,
expectMenuItemVisible: true,
- expectedTargetLanguage: null,
+ expectedTargetLanguage: "en",
},
"The translate-selection context menu item should be localized to translate the link text" +
- "without a target language."
+ "to the target language."
);
await cleanup();
diff --git a/browser/components/translations/tests/browser/browser_translations_select_context_menu_with_text_selected.js b/browser/components/translations/tests/browser/browser_translations_select_context_menu_with_text_selected.js
index 6b44f2ca1f..562eef3efb 100644
--- a/browser/components/translations/tests/browser/browser_translations_select_context_menu_with_text_selected.js
+++ b/browser/components/translations/tests/browser/browser_translations_select_context_menu_with_text_selected.js
@@ -43,8 +43,8 @@ add_task(
/**
* This test case verifies the functionality of the translate-selection context menu item
* when the selected text is detected to be in the user's preferred language. The menu item
- * should not be localized to display a target language when the selected text matches the
- * user's top preferred language.
+ * still be localized to the user's preferred language as a target, since the Select Translations
+ * Panel allows passing through the text for same-language translation.
*/
add_task(
async function test_translate_selection_menuitem_when_selected_text_is_preferred_language() {
@@ -65,9 +65,9 @@ add_task(
selectEnglishSentence: true,
openAtEnglishSentence: true,
expectMenuItemVisible: true,
- expectedTargetLanguage: null,
+ expectedTargetLanguage: "en",
},
- "The translate-selection context menu item should not display a target language " +
+ "The translate-selection context menu item should still display a target language " +
"when the selected text is in the preferred language."
);
diff --git a/browser/components/translations/tests/browser/browser_translations_select_panel_close_on_new_tab.js b/browser/components/translations/tests/browser/browser_translations_select_panel_close_on_new_tab.js
new file mode 100644
index 0000000000..86e563b157
--- /dev/null
+++ b/browser/components/translations/tests/browser/browser_translations_select_panel_close_on_new_tab.js
@@ -0,0 +1,46 @@
+/* Any copyright is dedicated to the Public Domain.
+ https://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * This test case tests the scenario where the SelectTranslationsPanel is open
+ * and the user opens a new tab while the panel is still open. The panel should
+ * close appropriately, as the content relevant to the selection is no longer
+ * in the active tab.
+ */
+add_task(
+ async function test_select_translations_panel_translate_sentence_on_open() {
+ const { cleanup, runInPage, resolveDownloads } = await loadTestPage({
+ page: SELECT_TEST_PAGE_URL,
+ languagePairs: LANGUAGE_PAIRS,
+ prefs: [["browser.translations.select.enable", true]],
+ });
+
+ await SelectTranslationsTestUtils.openPanel(runInPage, {
+ selectFrenchSentence: true,
+ openAtFrenchSentence: true,
+ expectedFromLanguage: "fr",
+ expectedToLanguage: "en",
+ downloadHandler: resolveDownloads,
+ onOpenPanel: SelectTranslationsTestUtils.assertPanelViewTranslated,
+ });
+
+ let tab;
+
+ await SelectTranslationsTestUtils.waitForPanelPopupEvent(
+ "popuphidden",
+ async () => {
+ tab = await BrowserTestUtils.openNewForegroundTab(
+ gBrowser,
+ SPANISH_PAGE_URL,
+ true // waitForLoad
+ );
+ }
+ );
+
+ BrowserTestUtils.removeTab(tab);
+
+ await cleanup();
+ }
+);
diff --git a/browser/components/translations/tests/browser/browser_translations_select_panel_copy_button.js b/browser/components/translations/tests/browser/browser_translations_select_panel_copy_button.js
new file mode 100644
index 0000000000..29eafb980d
--- /dev/null
+++ b/browser/components/translations/tests/browser/browser_translations_select_panel_copy_button.js
@@ -0,0 +1,95 @@
+/* Any copyright is dedicated to the Public Domain.
+ https://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * This test case tests functionality of the SelectTranslationsPanel copy button
+ * when retranslating by closing the panel and re-opening the panel to new links
+ * or selections of text.
+ */
+add_task(async function test_select_translations_panel_copy_button_on_reopen() {
+ const { cleanup, runInPage, resolveDownloads } = await loadTestPage({
+ page: SELECT_TEST_PAGE_URL,
+ languagePairs: LANGUAGE_PAIRS,
+ prefs: [["browser.translations.select.enable", true]],
+ });
+
+ await SelectTranslationsTestUtils.openPanel(runInPage, {
+ openAtSpanishHyperlink: true,
+ expectedFromLanguage: "es",
+ expectedToLanguage: "en",
+ downloadHandler: resolveDownloads,
+ onOpenPanel: SelectTranslationsTestUtils.assertPanelViewTranslated,
+ });
+
+ await SelectTranslationsTestUtils.clickCopyButton();
+ await SelectTranslationsTestUtils.clickDoneButton();
+
+ await SelectTranslationsTestUtils.openPanel(runInPage, {
+ selectEnglishSection: true,
+ openAtEnglishSection: true,
+ expectedFromLanguage: "en",
+ expectedToLanguage: "en",
+ onOpenPanel: SelectTranslationsTestUtils.assertPanelViewTranslated,
+ });
+
+ await SelectTranslationsTestUtils.clickCopyButton();
+ await SelectTranslationsTestUtils.clickDoneButton();
+
+ await SelectTranslationsTestUtils.openPanel(runInPage, {
+ selectFrenchSection: true,
+ openAtFrenchSection: true,
+ expectedFromLanguage: "fr",
+ expectedToLanguage: "en",
+ downloadHandler: resolveDownloads,
+ onOpenPanel: SelectTranslationsTestUtils.assertPanelViewTranslated,
+ });
+
+ await SelectTranslationsTestUtils.clickCopyButton();
+ await SelectTranslationsTestUtils.clickDoneButton();
+
+ await cleanup();
+});
+
+/**
+ * This test case tests functionality of the SelectTranslationsPanel copy button
+ * when retranslating by changing the from-language and to-language values for
+ * the same selection of source text.
+ */
+add_task(
+ async function test_select_translations_panel_copy_button_on_retranslate() {
+ const { cleanup, runInPage, resolveDownloads } = await loadTestPage({
+ page: SELECT_TEST_PAGE_URL,
+ languagePairs: LANGUAGE_PAIRS,
+ prefs: [["browser.translations.select.enable", true]],
+ });
+
+ await SelectTranslationsTestUtils.openPanel(runInPage, {
+ selectFrenchSection: true,
+ openAtFrenchSection: true,
+ expectedFromLanguage: "fr",
+ expectedToLanguage: "en",
+ downloadHandler: resolveDownloads,
+ onOpenPanel: SelectTranslationsTestUtils.assertPanelViewTranslated,
+ });
+
+ await SelectTranslationsTestUtils.clickCopyButton();
+ await SelectTranslationsTestUtils.changeSelectedFromLanguage(["es"], {
+ openDropdownMenu: true,
+ downloadHandler: resolveDownloads,
+ onChangeLanguage: SelectTranslationsTestUtils.assertPanelViewTranslated,
+ });
+
+ await SelectTranslationsTestUtils.clickCopyButton();
+ await SelectTranslationsTestUtils.changeSelectedToLanguage(["es"], {
+ openDropdownMenu: false,
+ onChangeLanguage: SelectTranslationsTestUtils.assertPanelViewTranslated,
+ });
+
+ await SelectTranslationsTestUtils.clickCopyButton();
+ await SelectTranslationsTestUtils.clickDoneButton();
+
+ await cleanup();
+ }
+);
diff --git a/browser/components/translations/tests/browser/browser_translations_select_panel_init_failure.js b/browser/components/translations/tests/browser/browser_translations_select_panel_init_failure.js
new file mode 100644
index 0000000000..9e17b0705f
--- /dev/null
+++ b/browser/components/translations/tests/browser/browser_translations_select_panel_init_failure.js
@@ -0,0 +1,119 @@
+/* Any copyright is dedicated to the Public Domain.
+ https://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * This test case verifies the scenario of clicking the cancel button to close
+ * the SelectTranslationsPanel after the language lists fail to initialize upon
+ * opening the panel, and the proper error message is displayed.
+ */
+add_task(async function test_select_translations_panel_init_failure_cancel() {
+ const { cleanup, runInPage } = await loadTestPage({
+ page: SELECT_TEST_PAGE_URL,
+ languagePairs: LANGUAGE_PAIRS,
+ prefs: [["browser.translations.select.enable", true]],
+ });
+
+ TranslationsPanelShared.simulateLangListError();
+ await SelectTranslationsTestUtils.openPanel(runInPage, {
+ selectFrenchSentence: true,
+ openAtFrenchSentence: true,
+ onOpenPanel: SelectTranslationsTestUtils.assertPanelViewInitFailure,
+ });
+
+ await SelectTranslationsTestUtils.clickCancelButton();
+
+ await cleanup();
+});
+
+/**
+ * This test case verifies the scenario of opening the SelectTranslationsPanel to a valid
+ * language pair, but having the language lists fail to initialize, then clicking the try-again
+ * button multiple times until both initialization and translation succeed.
+ */
+add_task(
+ async function test_select_translations_panel_init_failure_try_again_into_translation() {
+ const { cleanup, runInPage, resolveDownloads, rejectDownloads } =
+ await loadTestPage({
+ page: SELECT_TEST_PAGE_URL,
+ languagePairs: LANGUAGE_PAIRS,
+ prefs: [["browser.translations.select.enable", true]],
+ });
+
+ TranslationsPanelShared.simulateLangListError();
+ await SelectTranslationsTestUtils.openPanel(runInPage, {
+ selectFrenchSentence: true,
+ openAtFrenchSentence: true,
+ onOpenPanel: SelectTranslationsTestUtils.assertPanelViewInitFailure,
+ });
+
+ TranslationsPanelShared.simulateLangListError();
+ await SelectTranslationsTestUtils.waitForPanelPopupEvent(
+ "popupshown",
+ SelectTranslationsTestUtils.clickTryAgainButton,
+ SelectTranslationsTestUtils.assertPanelViewInitFailure
+ );
+
+ await SelectTranslationsTestUtils.waitForPanelPopupEvent(
+ "popupshown",
+ async () =>
+ SelectTranslationsTestUtils.clickTryAgainButton({
+ downloadHandler: rejectDownloads,
+ }),
+ SelectTranslationsTestUtils.assertPanelViewTranslationFailure
+ );
+
+ await SelectTranslationsTestUtils.clickTryAgainButton({
+ downloadHandler: resolveDownloads,
+ viewAssertion: SelectTranslationsTestUtils.assertPanelViewTranslated,
+ });
+
+ await SelectTranslationsTestUtils.clickDoneButton();
+
+ await cleanup();
+ }
+);
+
+/**
+ * This test case verifies the scenario of opening the SelectTranslationsPanel to an unsupported
+ * language, but having the language lists fail to initialize, then clicking the try-again
+ * button multiple times until the unsupported-language view is shown.
+ */
+add_task(
+ async function test_select_translations_panel_init_failure_try_again_into_unsupported() {
+ const { cleanup, runInPage } = await loadTestPage({
+ page: SELECT_TEST_PAGE_URL,
+ languagePairs: [
+ // Do not include Spanish.
+ { fromLang: "fr", toLang: "en" },
+ { fromLang: "en", toLang: "fr" },
+ ],
+ prefs: [["browser.translations.select.enable", true]],
+ });
+
+ TranslationsPanelShared.simulateLangListError();
+ await SelectTranslationsTestUtils.openPanel(runInPage, {
+ selectSpanishSection: true,
+ openAtSpanishSection: true,
+ onOpenPanel: SelectTranslationsTestUtils.assertPanelViewInitFailure,
+ });
+
+ TranslationsPanelShared.simulateLangListError();
+ await SelectTranslationsTestUtils.waitForPanelPopupEvent(
+ "popupshown",
+ SelectTranslationsTestUtils.clickTryAgainButton,
+ SelectTranslationsTestUtils.assertPanelViewInitFailure
+ );
+
+ await SelectTranslationsTestUtils.waitForPanelPopupEvent(
+ "popupshown",
+ SelectTranslationsTestUtils.clickTryAgainButton,
+ SelectTranslationsTestUtils.assertPanelViewUnsupportedLanguage
+ );
+
+ await SelectTranslationsTestUtils.clickDoneButton();
+
+ await cleanup();
+ }
+);
diff --git a/browser/components/translations/tests/browser/browser_translations_select_panel_open_to_idle_state.js b/browser/components/translations/tests/browser/browser_translations_select_panel_open_to_idle_state.js
deleted file mode 100644
index d5a1096e70..0000000000
--- a/browser/components/translations/tests/browser/browser_translations_select_panel_open_to_idle_state.js
+++ /dev/null
@@ -1,61 +0,0 @@
-/* Any copyright is dedicated to the Public Domain.
- http://creativecommons.org/publicdomain/zero/1.0/ */
-
-"use strict";
-
-/**
- * This test case verifies the select translations panel's functionality when opened with an unsupported
- * from-language, ensuring it opens with the correct view with no from-language selected.
- */
-add_task(
- async function test_select_translations_panel_open_no_selected_from_lang() {
- const { cleanup, runInPage } = await loadTestPage({
- page: SELECT_TEST_PAGE_URL,
- languagePairs: [
- // Do not include Spanish.
- { fromLang: "fr", toLang: "en" },
- { fromLang: "en", toLang: "fr" },
- ],
- prefs: [["browser.translations.select.enable", true]],
- });
-
- await SelectTranslationsTestUtils.openPanel(runInPage, {
- selectSpanishSentence: true,
- openAtSpanishSentence: true,
- expectedFromLanguage: null,
- expectedToLanguage: "en",
- onOpenPanel:
- SelectTranslationsTestUtils.assertPanelViewNoFromLangSelected,
- });
-
- await SelectTranslationsTestUtils.clickDoneButton();
-
- await cleanup();
- }
-);
-
-/**
- * This test case verifies the select translations panel's functionality when opened with an undetermined
- * to-language, ensuring it opens with the correct view with no to-language selected.
- */
-add_task(
- async function test_select_translations_panel_open_no_selected_to_lang() {
- const { cleanup, runInPage } = await loadTestPage({
- page: SELECT_TEST_PAGE_URL,
- languagePairs: LANGUAGE_PAIRS,
- prefs: [["browser.translations.select.enable", true]],
- });
-
- await SelectTranslationsTestUtils.openPanel(runInPage, {
- selectEnglishSentence: true,
- openAtEnglishSentence: true,
- expectedFromLanguage: "en",
- expectedToLanguage: null,
- onOpenPanel: SelectTranslationsTestUtils.assertPanelViewNoToLangSelected,
- });
-
- await SelectTranslationsTestUtils.clickDoneButton();
-
- await cleanup();
- }
-);
diff --git a/browser/components/translations/tests/browser/browser_translations_select_panel_pdf.js b/browser/components/translations/tests/browser/browser_translations_select_panel_pdf.js
new file mode 100644
index 0000000000..fd675e9cea
--- /dev/null
+++ b/browser/components/translations/tests/browser/browser_translations_select_panel_pdf.js
@@ -0,0 +1,42 @@
+/* Any copyright is dedicated to the Public Domain.
+ https://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * This test case verifies that the Select Translations Panel functionality
+ * is available and works within PDF files.
+ */
+add_task(async function test_the_select_translations_panel_in_pdf_files() {
+ const { cleanup, runInPage, resolveDownloads } = await loadTestPage({
+ page: PDF_TEST_PAGE_URL,
+ languagePairs: LANGUAGE_PAIRS,
+ prefs: [["browser.translations.select.enable", true]],
+ });
+
+ await SelectTranslationsTestUtils.openPanel(runInPage, {
+ selectPdfSpan: true,
+ openAtPdfSpan: true,
+ expectedFromLanguage: "en",
+ expectedToLanguage: "en",
+ onOpenPanel: SelectTranslationsTestUtils.assertPanelViewTranslated,
+ });
+
+ await SelectTranslationsTestUtils.changeSelectedFromLanguage(["es"], {
+ openDropdownMenu: true,
+ pivotTranslation: false,
+ downloadHandler: resolveDownloads,
+ onChangeLanguage: SelectTranslationsTestUtils.assertPanelViewTranslated,
+ });
+
+ await SelectTranslationsTestUtils.changeSelectedToLanguage(["fr"], {
+ openDropdownMenu: false,
+ pivotTranslation: true,
+ downloadHandler: resolveDownloads,
+ onChangeLanguage: SelectTranslationsTestUtils.assertPanelViewTranslated,
+ });
+
+ await SelectTranslationsTestUtils.clickDoneButton();
+
+ await cleanup();
+});
diff --git a/browser/components/translations/tests/browser/browser_translations_select_panel_reader_mode.js b/browser/components/translations/tests/browser/browser_translations_select_panel_reader_mode.js
new file mode 100644
index 0000000000..5f05b1a878
--- /dev/null
+++ b/browser/components/translations/tests/browser/browser_translations_select_panel_reader_mode.js
@@ -0,0 +1,44 @@
+/* Any copyright is dedicated to the Public Domain.
+ https://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * This test case verifies that the Select Translations Panel functionality
+ * is available and works within reader mode.
+ */
+add_task(async function test_the_select_translations_panel_in_reader_mode() {
+ const { cleanup, runInPage, resolveDownloads } = await loadTestPage({
+ page: SELECT_TEST_PAGE_URL,
+ languagePairs: LANGUAGE_PAIRS,
+ prefs: [["browser.translations.select.enable", true]],
+ });
+
+ await toggleReaderMode();
+
+ await SelectTranslationsTestUtils.openPanel(runInPage, {
+ selectH1: true,
+ openAtH1: true,
+ expectedFromLanguage: "en",
+ expectedToLanguage: "en",
+ onOpenPanel: SelectTranslationsTestUtils.assertPanelViewTranslated,
+ });
+
+ await SelectTranslationsTestUtils.changeSelectedFromLanguage(["es"], {
+ openDropdownMenu: true,
+ pivotTranslation: false,
+ downloadHandler: resolveDownloads,
+ onChangeLanguage: SelectTranslationsTestUtils.assertPanelViewTranslated,
+ });
+
+ await SelectTranslationsTestUtils.changeSelectedToLanguage(["fr"], {
+ openDropdownMenu: false,
+ pivotTranslation: true,
+ downloadHandler: resolveDownloads,
+ onChangeLanguage: SelectTranslationsTestUtils.assertPanelViewTranslated,
+ });
+
+ await SelectTranslationsTestUtils.clickDoneButton();
+
+ await cleanup();
+});
diff --git a/browser/components/translations/tests/browser/browser_translations_select_panel_select_same_from_and_to_languages_directly.js b/browser/components/translations/tests/browser/browser_translations_select_panel_select_same_from_and_to_languages_directly.js
index 95feac6708..673faee796 100644
--- a/browser/components/translations/tests/browser/browser_translations_select_panel_select_same_from_and_to_languages_directly.js
+++ b/browser/components/translations/tests/browser/browser_translations_select_panel_select_same_from_and_to_languages_directly.js
@@ -10,29 +10,24 @@
*/
add_task(
async function test_select_translations_panel_select_same_from_language_directly() {
- const { cleanup, runInPage } = await loadTestPage({
+ const { cleanup, runInPage, resolveDownloads } = await loadTestPage({
page: SELECT_TEST_PAGE_URL,
- languagePairs: [
- // Do not include Spanish.
- { fromLang: "fr", toLang: "en" },
- { fromLang: "en", toLang: "fr" },
- ],
+ languagePairs: LANGUAGE_PAIRS,
prefs: [["browser.translations.select.enable", true]],
});
await SelectTranslationsTestUtils.openPanel(runInPage, {
selectSpanishSection: true,
openAtSpanishSection: true,
- expectedFromLanguage: null,
+ expectedFromLanguage: "es",
expectedToLanguage: "en",
- onOpenPanel:
- SelectTranslationsTestUtils.assertPanelViewNoFromLangSelected,
+ downloadHandler: resolveDownloads,
+ onOpenPanel: SelectTranslationsTestUtils.assertPanelViewTranslated,
});
await SelectTranslationsTestUtils.changeSelectedFromLanguage(["en"], {
openDropdownMenu: false,
- onChangeLanguage:
- SelectTranslationsTestUtils.assertPanelViewNoFromToSelected,
+ onChangeLanguage: SelectTranslationsTestUtils.assertPanelViewTranslated,
});
await cleanup();
@@ -41,29 +36,29 @@ add_task(
/**
* This test case verifies the behavior of switching the to-language to the same value
- * that is currently selected in the from-language, effectively stealing the from-language's
- * value, leaving it unselected and focused.
+ * that is currently selected in the from-language, creating a passthrough translation
+ * of the source text directly into the text area.
*/
add_task(
async function test_select_translations_panel_select_same_to_language_directly() {
- const { cleanup, runInPage } = await loadTestPage({
+ const { cleanup, runInPage, resolveDownloads } = await loadTestPage({
page: SELECT_TEST_PAGE_URL,
languagePairs: LANGUAGE_PAIRS,
prefs: [["browser.translations.select.enable", true]],
});
await SelectTranslationsTestUtils.openPanel(runInPage, {
- selectEnglishSection: true,
- openAtEnglishSection: true,
- expectedFromLanguage: "en",
- expectedToLanguage: null,
- onOpenPanel: SelectTranslationsTestUtils.assertPanelViewNoToLangSelected,
+ selectFrenchSentence: true,
+ openAtFrenchSentence: true,
+ expectedFromLanguage: "fr",
+ expectedToLanguage: "en",
+ downloadHandler: resolveDownloads,
+ onOpenPanel: SelectTranslationsTestUtils.assertPanelViewTranslated,
});
- await SelectTranslationsTestUtils.changeSelectedToLanguage(["en"], {
+ await SelectTranslationsTestUtils.changeSelectedToLanguage(["fr"], {
openDropdownMenu: false,
- onChangeLanguage:
- SelectTranslationsTestUtils.assertPanelViewNoFromLangSelected,
+ onChangeLanguage: SelectTranslationsTestUtils.assertPanelViewTranslated,
});
await cleanup();
diff --git a/browser/components/translations/tests/browser/browser_translations_select_panel_select_same_from_and_to_languages_from_dropdown_menu.js b/browser/components/translations/tests/browser/browser_translations_select_panel_select_same_from_and_to_languages_from_dropdown_menu.js
index 5c27be411f..eea7a76bf2 100644
--- a/browser/components/translations/tests/browser/browser_translations_select_panel_select_same_from_and_to_languages_from_dropdown_menu.js
+++ b/browser/components/translations/tests/browser/browser_translations_select_panel_select_same_from_and_to_languages_from_dropdown_menu.js
@@ -10,29 +10,24 @@
*/
add_task(
async function test_select_translations_panel_select_same_from_language_via_popup() {
- const { cleanup, runInPage } = await loadTestPage({
+ const { cleanup, runInPage, resolveDownloads } = await loadTestPage({
page: SELECT_TEST_PAGE_URL,
- languagePairs: [
- // Do not include Spanish.
- { fromLang: "fr", toLang: "en" },
- { fromLang: "en", toLang: "fr" },
- ],
+ languagePairs: LANGUAGE_PAIRS,
prefs: [["browser.translations.select.enable", true]],
});
await SelectTranslationsTestUtils.openPanel(runInPage, {
selectSpanishSection: true,
openAtSpanishSection: true,
- expectedFromLanguage: null,
+ expectedFromLanguage: "es",
expectedToLanguage: "en",
- onOpenPanel:
- SelectTranslationsTestUtils.assertPanelViewNoFromLangSelected,
+ downloadHandler: resolveDownloads,
+ onOpenPanel: SelectTranslationsTestUtils.assertPanelViewTranslated,
});
await SelectTranslationsTestUtils.changeSelectedFromLanguage(["en"], {
openDropdownMenu: true,
- onChangeLanguage:
- SelectTranslationsTestUtils.assertPanelViewNoFromToSelected,
+ onChangeLanguage: SelectTranslationsTestUtils.assertPanelViewTranslated,
});
await cleanup();
@@ -42,28 +37,28 @@ add_task(
/**
* This test case verifies the behavior of switching the to-language to the same value
* that is currently selected in the from-language by opening the language dropdown menu,
- * effectively stealing the from-language's value, leaving it unselected and focused.
+ * creating a passthrough translation of the source text directly into the text area.
*/
add_task(
async function test_select_translations_panel_select_same_to_language_via_popup() {
- const { cleanup, runInPage } = await loadTestPage({
+ const { cleanup, runInPage, resolveDownloads } = await loadTestPage({
page: SELECT_TEST_PAGE_URL,
languagePairs: LANGUAGE_PAIRS,
prefs: [["browser.translations.select.enable", true]],
});
await SelectTranslationsTestUtils.openPanel(runInPage, {
- selectEnglishSection: true,
- openAtEnglishSection: true,
- expectedFromLanguage: "en",
- expectedToLanguage: null,
- onOpenPanel: SelectTranslationsTestUtils.assertPanelViewNoToLangSelected,
+ selectFrenchSentence: true,
+ openAtFrenchSentence: true,
+ expectedFromLanguage: "fr",
+ expectedToLanguage: "en",
+ downloadHandler: resolveDownloads,
+ onOpenPanel: SelectTranslationsTestUtils.assertPanelViewTranslated,
});
- await SelectTranslationsTestUtils.changeSelectedToLanguage(["en"], {
+ await SelectTranslationsTestUtils.changeSelectedToLanguage(["fr"], {
openDropdownMenu: true,
- onChangeLanguage:
- SelectTranslationsTestUtils.assertPanelViewNoFromLangSelected,
+ onChangeLanguage: SelectTranslationsTestUtils.assertPanelViewTranslated,
});
await cleanup();
diff --git a/browser/components/translations/tests/browser/browser_translations_select_panel_settings_menu.js b/browser/components/translations/tests/browser/browser_translations_select_panel_settings_menu.js
new file mode 100644
index 0000000000..b6263325d5
--- /dev/null
+++ b/browser/components/translations/tests/browser/browser_translations_select_panel_settings_menu.js
@@ -0,0 +1,70 @@
+/* Any copyright is dedicated to the Public Domain.
+ https://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * This test case tests the scenario of clicking the settings menu item
+ * that leads to the translations section of the about:preferences settings
+ * page in Firefox.
+ */
+add_task(async function test_select_translations_panel_open_settings_page() {
+ const { cleanup, runInPage, resolveDownloads } = await loadTestPage({
+ page: SELECT_TEST_PAGE_URL,
+ languagePairs: LANGUAGE_PAIRS,
+ prefs: [["browser.translations.select.enable", true]],
+ });
+
+ await SelectTranslationsTestUtils.openPanel(runInPage, {
+ selectFrenchSentence: true,
+ openAtFrenchSentence: true,
+ expectedFromLanguage: "fr",
+ expectedToLanguage: "en",
+ downloadHandler: resolveDownloads,
+ onOpenPanel: SelectTranslationsTestUtils.assertPanelViewTranslated,
+ });
+
+ await SelectTranslationsTestUtils.openPanelSettingsMenu();
+ SelectTranslationsTestUtils.clickTranslationsSettingsPageMenuItem();
+
+ await waitForCondition(
+ () => gBrowser.currentURI.spec === "about:preferences#general",
+ "Waiting for about:preferences to be opened."
+ );
+
+ info("Remove the about:preferences tab");
+ gBrowser.removeCurrentTab();
+
+ await cleanup();
+});
+
+/**
+ * This test case tests the scenario of opening the SelectTranslationsPanel
+ * settings menu from the unsupported-language panel state.
+ */
+add_task(
+ async function test_select_translations_panel_open_settings_menu_from_unsupported_language() {
+ const { cleanup, runInPage } = await loadTestPage({
+ page: SELECT_TEST_PAGE_URL,
+ languagePairs: [
+ // Do not include Spanish.
+ { fromLang: "fr", toLang: "en" },
+ { fromLang: "en", toLang: "fr" },
+ ],
+ prefs: [["browser.translations.select.enable", true]],
+ });
+
+ await SelectTranslationsTestUtils.openPanel(runInPage, {
+ selectSpanishSection: true,
+ openAtSpanishSection: true,
+ onOpenPanel:
+ SelectTranslationsTestUtils.assertPanelViewUnsupportedLanguage,
+ });
+
+ await SelectTranslationsTestUtils.openPanelSettingsMenu();
+
+ await SelectTranslationsTestUtils.clickDoneButton();
+
+ await cleanup();
+ }
+);
diff --git a/browser/components/translations/tests/browser/browser_translations_select_panel_translate_full_page_button.js b/browser/components/translations/tests/browser/browser_translations_select_panel_translate_full_page_button.js
new file mode 100644
index 0000000000..a2e9727798
--- /dev/null
+++ b/browser/components/translations/tests/browser/browser_translations_select_panel_translate_full_page_button.js
@@ -0,0 +1,83 @@
+/* Any copyright is dedicated to the Public Domain.
+ https://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * Simulates clicking the translate-full-page button with a from-language that
+ * matches the language of the given document.
+ */
+add_task(
+ async function test_select_translations_panel_translat_full_page_button_matching_doc_lang() {
+ const { cleanup, runInPage, resolveDownloads } = await loadTestPage({
+ page: SELECT_TEST_PAGE_URL,
+ languagePairs: LANGUAGE_PAIRS,
+ prefs: [["browser.translations.select.enable", true]],
+ });
+
+ await SelectTranslationsTestUtils.openPanel(runInPage, {
+ openAtSpanishHyperlink: true,
+ expectedFromLanguage: "es",
+ expectedToLanguage: "en",
+ downloadHandler: resolveDownloads,
+ onOpenPanel: SelectTranslationsTestUtils.assertPanelViewTranslated,
+ });
+
+ await SelectTranslationsTestUtils.clickTranslateFullPageButton();
+
+ await FullPageTranslationsTestUtils.assertPageIsTranslated(
+ "es",
+ "en",
+ runInPage
+ );
+
+ await cleanup();
+ }
+);
+
+/**
+ * Simulates clicking the translate-full-page button after changing the from-language
+ * and to-language values to values that don't match the document language or the
+ * user's app locale, ensuring that the current selection is respected.
+ */
+add_task(
+ async function test_select_translations_panel_translat_full_page_button_matching_doc_lang() {
+ const { cleanup, runInPage, resolveDownloads } = await loadTestPage({
+ page: SELECT_TEST_PAGE_URL,
+ languagePairs: LANGUAGE_PAIRS,
+ prefs: [["browser.translations.select.enable", true]],
+ });
+
+ await SelectTranslationsTestUtils.openPanel(runInPage, {
+ selectSpanishSection: true,
+ openAtSpanishSection: true,
+ expectedFromLanguage: "es",
+ expectedToLanguage: "en",
+ downloadHandler: resolveDownloads,
+ onOpenPanel: SelectTranslationsTestUtils.assertPanelViewTranslated,
+ });
+
+ await SelectTranslationsTestUtils.changeSelectedFromLanguage(["fr"], {
+ openDropdownMenu: false,
+ downloadHandler: resolveDownloads,
+ onChangeLanguage: SelectTranslationsTestUtils.assertPanelViewTranslated,
+ });
+
+ await SelectTranslationsTestUtils.changeSelectedToLanguage(["uk"], {
+ openDropdownMenu: true,
+ downloadHandler: resolveDownloads,
+ pivotTranslation: true,
+ onChangeLanguage: SelectTranslationsTestUtils.assertPanelViewTranslated,
+ });
+
+ await SelectTranslationsTestUtils.clickTranslateFullPageButton();
+
+ await FullPageTranslationsTestUtils.assertPageIsTranslated(
+ "fr",
+ "uk",
+ runInPage
+ );
+
+ await cleanup();
+ }
+);
diff --git a/browser/components/translations/tests/browser/browser_translations_select_panel_translate_on_change_language_directly.js b/browser/components/translations/tests/browser/browser_translations_select_panel_translate_on_change_language_directly.js
index 64d067d1f4..7ea721fb0f 100644
--- a/browser/components/translations/tests/browser/browser_translations_select_panel_translate_on_change_language_directly.js
+++ b/browser/components/translations/tests/browser/browser_translations_select_panel_translate_on_change_language_directly.js
@@ -12,21 +12,17 @@ add_task(
async function test_select_translations_panel_translate_on_change_from_language_directly() {
const { cleanup, runInPage, resolveDownloads } = await loadTestPage({
page: SELECT_TEST_PAGE_URL,
- languagePairs: [
- // Do not include Spanish.
- { fromLang: "fr", toLang: "en" },
- { fromLang: "en", toLang: "fr" },
- ],
+ languagePairs: LANGUAGE_PAIRS,
prefs: [["browser.translations.select.enable", true]],
});
await SelectTranslationsTestUtils.openPanel(runInPage, {
selectSpanishSection: true,
openAtSpanishSection: true,
- expectedFromLanguage: null,
+ expectedFromLanguage: "es",
expectedToLanguage: "en",
- onOpenPanel:
- SelectTranslationsTestUtils.assertPanelViewNoFromLangSelected,
+ downloadHandler: resolveDownloads,
+ onOpenPanel: SelectTranslationsTestUtils.assertPanelViewTranslated,
});
await SelectTranslationsTestUtils.changeSelectedFromLanguage(["fr"], {
@@ -56,8 +52,8 @@ add_task(
selectEnglishSection: true,
openAtEnglishSection: true,
expectedFromLanguage: "en",
- expectedToLanguage: null,
- onOpenPanel: SelectTranslationsTestUtils.assertPanelViewNoToLangSelected,
+ expectedToLanguage: "en",
+ onOpenPanel: SelectTranslationsTestUtils.assertPanelViewTranslated,
});
await SelectTranslationsTestUtils.changeSelectedToLanguage(["es"], {
diff --git a/browser/components/translations/tests/browser/browser_translations_select_panel_translate_on_change_language_from_dropdown_menu.js b/browser/components/translations/tests/browser/browser_translations_select_panel_translate_on_change_language_from_dropdown_menu.js
index 0cd205d721..c0ba02f6db 100644
--- a/browser/components/translations/tests/browser/browser_translations_select_panel_translate_on_change_language_from_dropdown_menu.js
+++ b/browser/components/translations/tests/browser/browser_translations_select_panel_translate_on_change_language_from_dropdown_menu.js
@@ -12,21 +12,17 @@ add_task(
async function test_select_translations_panel_translate_on_change_from_language() {
const { cleanup, runInPage, resolveDownloads } = await loadTestPage({
page: SELECT_TEST_PAGE_URL,
- languagePairs: [
- // Do not include Spanish.
- { fromLang: "fr", toLang: "en" },
- { fromLang: "en", toLang: "fr" },
- ],
+ languagePairs: LANGUAGE_PAIRS,
prefs: [["browser.translations.select.enable", true]],
});
await SelectTranslationsTestUtils.openPanel(runInPage, {
selectSpanishSection: true,
openAtSpanishSection: true,
- expectedFromLanguage: null,
+ expectedFromLanguage: "es",
expectedToLanguage: "en",
- onOpenPanel:
- SelectTranslationsTestUtils.assertPanelViewNoFromLangSelected,
+ downloadHandler: resolveDownloads,
+ onOpenPanel: SelectTranslationsTestUtils.assertPanelViewTranslated,
});
await SelectTranslationsTestUtils.changeSelectedFromLanguage(["fr"], {
@@ -56,8 +52,8 @@ add_task(
selectEnglishSection: true,
openAtEnglishSection: true,
expectedFromLanguage: "en",
- expectedToLanguage: null,
- onOpenPanel: SelectTranslationsTestUtils.assertPanelViewNoToLangSelected,
+ expectedToLanguage: "en",
+ onOpenPanel: SelectTranslationsTestUtils.assertPanelViewTranslated,
});
await SelectTranslationsTestUtils.changeSelectedToLanguage(["es"], {
diff --git a/browser/components/translations/tests/browser/browser_translations_select_panel_translate_on_change_language_multiple_times_directly.js b/browser/components/translations/tests/browser/browser_translations_select_panel_translate_on_change_language_multiple_times_directly.js
index b3c02a96f6..cd60f73e6c 100644
--- a/browser/components/translations/tests/browser/browser_translations_select_panel_translate_on_change_language_multiple_times_directly.js
+++ b/browser/components/translations/tests/browser/browser_translations_select_panel_translate_on_change_language_multiple_times_directly.js
@@ -13,7 +13,8 @@ add_task(
const { cleanup, runInPage, resolveDownloads } = await loadTestPage({
page: SELECT_TEST_PAGE_URL,
languagePairs: [
- // Do not include Spanish.
+ { fromLang: "es", toLang: "en" },
+ { fromLang: "en", toLang: "es" },
{ fromLang: "fa", toLang: "en" },
{ fromLang: "en", toLang: "fa" },
{ fromLang: "fi", toLang: "en" },
@@ -31,10 +32,10 @@ add_task(
await SelectTranslationsTestUtils.openPanel(runInPage, {
selectSpanishSentence: true,
openAtSpanishSentence: true,
- expectedFromLanguage: null,
+ expectedFromLanguage: "es",
expectedToLanguage: "en",
- onOpenPanel:
- SelectTranslationsTestUtils.assertPanelViewNoFromLangSelected,
+ downloadHandler: resolveDownloads,
+ onOpenPanel: SelectTranslationsTestUtils.assertPanelViewTranslated,
});
await SelectTranslationsTestUtils.changeSelectedFromLanguage(
@@ -79,8 +80,8 @@ add_task(
await SelectTranslationsTestUtils.openPanel(runInPage, {
openAtEnglishHyperlink: true,
expectedFromLanguage: "en",
- expectedToLanguage: null,
- onOpenPanel: SelectTranslationsTestUtils.assertPanelViewNoToLangSelected,
+ expectedToLanguage: "en",
+ onOpenPanel: SelectTranslationsTestUtils.assertPanelViewTranslated,
});
await SelectTranslationsTestUtils.changeSelectedToLanguage(
diff --git a/browser/components/translations/tests/browser/browser_translations_select_panel_translate_on_change_language_multiple_times_from_dropdown_menu.js b/browser/components/translations/tests/browser/browser_translations_select_panel_translate_on_change_language_multiple_times_from_dropdown_menu.js
index 50c877cfbc..1b2044ef97 100644
--- a/browser/components/translations/tests/browser/browser_translations_select_panel_translate_on_change_language_multiple_times_from_dropdown_menu.js
+++ b/browser/components/translations/tests/browser/browser_translations_select_panel_translate_on_change_language_multiple_times_from_dropdown_menu.js
@@ -13,7 +13,8 @@ add_task(
const { cleanup, runInPage, resolveDownloads } = await loadTestPage({
page: SELECT_TEST_PAGE_URL,
languagePairs: [
- // Do not include Spanish.
+ { fromLang: "es", toLang: "en" },
+ { fromLang: "en", toLang: "es" },
{ fromLang: "fa", toLang: "en" },
{ fromLang: "en", toLang: "fa" },
{ fromLang: "fi", toLang: "en" },
@@ -31,10 +32,10 @@ add_task(
await SelectTranslationsTestUtils.openPanel(runInPage, {
selectSpanishSentence: true,
openAtSpanishSentence: true,
- expectedFromLanguage: null,
+ expectedFromLanguage: "es",
expectedToLanguage: "en",
- onOpenPanel:
- SelectTranslationsTestUtils.assertPanelViewNoFromLangSelected,
+ downloadHandler: resolveDownloads,
+ onOpenPanel: SelectTranslationsTestUtils.assertPanelViewTranslated,
});
await SelectTranslationsTestUtils.changeSelectedFromLanguage(
@@ -80,8 +81,8 @@ add_task(
selectEnglishSentence: true,
openAtEnglishSentence: true,
expectedFromLanguage: "en",
- expectedToLanguage: null,
- onOpenPanel: SelectTranslationsTestUtils.assertPanelViewNoToLangSelected,
+ expectedToLanguage: "en",
+ onOpenPanel: SelectTranslationsTestUtils.assertPanelViewTranslated,
});
await SelectTranslationsTestUtils.changeSelectedToLanguage(
diff --git a/browser/components/translations/tests/browser/browser_translations_select_panel_translation_failure_after_unsupported_language.js b/browser/components/translations/tests/browser/browser_translations_select_panel_translation_failure_after_unsupported_language.js
new file mode 100644
index 0000000000..79ce46b7dc
--- /dev/null
+++ b/browser/components/translations/tests/browser/browser_translations_select_panel_translation_failure_after_unsupported_language.js
@@ -0,0 +1,55 @@
+/* Any copyright is dedicated to the Public Domain.
+ https://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * This test case tests the scenario of encountering the translation failure message
+ * as a result of changing the source language from the unsupported-language state.
+ */
+add_task(
+ async function test_select_translations_panel_failure_after_unsupported_language() {
+ const { cleanup, runInPage, resolveDownloads, rejectDownloads } =
+ await loadTestPage({
+ page: SELECT_TEST_PAGE_URL,
+ languagePairs: [
+ // Do not include Spanish.
+ { fromLang: "fr", toLang: "en" },
+ { fromLang: "en", toLang: "fr" },
+ ],
+ prefs: [["browser.translations.select.enable", true]],
+ });
+
+ await SelectTranslationsTestUtils.openPanel(runInPage, {
+ selectSpanishSentence: true,
+ openAtSpanishSentence: true,
+ onOpenPanel:
+ SelectTranslationsTestUtils.assertPanelViewUnsupportedLanguage,
+ });
+
+ await SelectTranslationsTestUtils.changeSelectedTryAnotherSourceLanguage(
+ "fr"
+ );
+
+ await SelectTranslationsTestUtils.clickTranslateButton({
+ downloadHandler: rejectDownloads,
+ viewAssertion:
+ SelectTranslationsTestUtils.assertPanelViewTranslationFailure,
+ });
+
+ await SelectTranslationsTestUtils.clickTryAgainButton({
+ downloadHandler: rejectDownloads,
+ viewAssertion:
+ SelectTranslationsTestUtils.assertPanelViewTranslationFailure,
+ });
+
+ await SelectTranslationsTestUtils.clickTryAgainButton({
+ downloadHandler: resolveDownloads,
+ viewAssertion: SelectTranslationsTestUtils.assertPanelViewTranslated,
+ });
+
+ await SelectTranslationsTestUtils.clickDoneButton();
+
+ await cleanup();
+ }
+);
diff --git a/browser/components/translations/tests/browser/browser_translations_select_panel_translation_failure_on_open.js b/browser/components/translations/tests/browser/browser_translations_select_panel_translation_failure_on_open.js
new file mode 100644
index 0000000000..0fc133f269
--- /dev/null
+++ b/browser/components/translations/tests/browser/browser_translations_select_panel_translation_failure_on_open.js
@@ -0,0 +1,92 @@
+/* Any copyright is dedicated to the Public Domain.
+ https://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * This test case tests the scenario of opening the SelectTranslationsPanel to a translation
+ * attempt that fails, followed by closing the panel via the cancel button, and then re-attempting
+ * the translation by re-opening the panel and having it succeed.
+ */
+add_task(
+ async function test_select_translations_panel_translation_failure_on_open_then_cancel_and_reopen() {
+ const { cleanup, runInPage, rejectDownloads, resolveDownloads } =
+ await loadTestPage({
+ page: SELECT_TEST_PAGE_URL,
+ languagePairs: LANGUAGE_PAIRS,
+ prefs: [["browser.translations.select.enable", true]],
+ });
+
+ await SelectTranslationsTestUtils.openPanel(runInPage, {
+ selectFrenchSentence: true,
+ openAtFrenchSentence: true,
+ expectedFromLanguage: "fr",
+ expectedToLanguage: "en",
+ downloadHandler: rejectDownloads,
+ onOpenPanel:
+ SelectTranslationsTestUtils.assertPanelViewTranslationFailure,
+ });
+
+ await SelectTranslationsTestUtils.clickCancelButton();
+
+ await SelectTranslationsTestUtils.openPanel(runInPage, {
+ selectFrenchSentence: true,
+ openAtFrenchSentence: true,
+ expectedFromLanguage: "fr",
+ expectedToLanguage: "en",
+ downloadHandler: resolveDownloads,
+ onOpenPanel: SelectTranslationsTestUtils.assertPanelViewTranslated,
+ });
+
+ await SelectTranslationsTestUtils.clickDoneButton();
+
+ await cleanup();
+ }
+);
+
+/**
+ * This test case tests the scenario of opening the SelectTranslationsPanel to a translation
+ * attempt that fails, followed by clicking the try-again button multiple times to retry the
+ * translation until it finally succeeds.
+ */
+add_task(
+ async function test_select_translations_panel_translation_failure_on_open_then_try_again() {
+ const { cleanup, runInPage, rejectDownloads, resolveDownloads } =
+ await loadTestPage({
+ page: SELECT_TEST_PAGE_URL,
+ languagePairs: LANGUAGE_PAIRS,
+ prefs: [["browser.translations.select.enable", true]],
+ });
+
+ await SelectTranslationsTestUtils.openPanel(runInPage, {
+ selectFrenchSentence: true,
+ openAtFrenchSentence: true,
+ expectedFromLanguage: "fr",
+ expectedToLanguage: "en",
+ downloadHandler: rejectDownloads,
+ onOpenPanel:
+ SelectTranslationsTestUtils.assertPanelViewTranslationFailure,
+ });
+
+ await SelectTranslationsTestUtils.clickTryAgainButton({
+ downloadHandler: rejectDownloads,
+ viewAssertion:
+ SelectTranslationsTestUtils.assertPanelViewTranslationFailure,
+ });
+
+ await SelectTranslationsTestUtils.clickTryAgainButton({
+ downloadHandler: rejectDownloads,
+ viewAssertion:
+ SelectTranslationsTestUtils.assertPanelViewTranslationFailure,
+ });
+
+ await SelectTranslationsTestUtils.clickTryAgainButton({
+ downloadHandler: resolveDownloads,
+ viewAssertion: SelectTranslationsTestUtils.assertPanelViewTranslated,
+ });
+
+ await SelectTranslationsTestUtils.clickDoneButton();
+
+ await cleanup();
+ }
+);
diff --git a/browser/components/translations/tests/browser/browser_translations_select_panel_translation_failure_on_retranslate.js b/browser/components/translations/tests/browser/browser_translations_select_panel_translation_failure_on_retranslate.js
new file mode 100644
index 0000000000..d19439c6a4
--- /dev/null
+++ b/browser/components/translations/tests/browser/browser_translations_select_panel_translation_failure_on_retranslate.js
@@ -0,0 +1,128 @@
+/* Any copyright is dedicated to the Public Domain.
+ https://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * This test case tests the scenario of encountering the translation failure message
+ * as a result of changing the selected from-language, along with moving from the failure
+ * state to a successful translation also by changing the selected from-language.
+ */
+add_task(
+ async function test_select_translations_panel_translation_failure_on_change_from_language() {
+ const { cleanup, runInPage, rejectDownloads, resolveDownloads } =
+ await loadTestPage({
+ page: SELECT_TEST_PAGE_URL,
+ languagePairs: LANGUAGE_PAIRS,
+ prefs: [["browser.translations.select.enable", true]],
+ });
+
+ await SelectTranslationsTestUtils.openPanel(runInPage, {
+ selectFrenchSentence: true,
+ openAtFrenchSentence: true,
+ expectedFromLanguage: "fr",
+ expectedToLanguage: "en",
+ downloadHandler: resolveDownloads,
+ onOpenPanel: SelectTranslationsTestUtils.assertPanelViewTranslated,
+ });
+
+ await SelectTranslationsTestUtils.changeSelectedFromLanguage(["es"], {
+ openDropdownMenu: false,
+ downloadHandler: rejectDownloads,
+ onChangeLanguage:
+ SelectTranslationsTestUtils.assertPanelViewTranslationFailure,
+ });
+
+ await SelectTranslationsTestUtils.changeSelectedFromLanguage(["uk"], {
+ openDropdownMenu: true,
+ downloadHandler: rejectDownloads,
+ onChangeLanguage:
+ SelectTranslationsTestUtils.assertPanelViewTranslationFailure,
+ });
+
+ await SelectTranslationsTestUtils.changeSelectedFromLanguage(["es"], {
+ openDropdownMenu: true,
+ downloadHandler: resolveDownloads,
+ onChangeLanguage: SelectTranslationsTestUtils.assertPanelViewTranslated,
+ });
+
+ await SelectTranslationsTestUtils.changeSelectedFromLanguage(["uk"], {
+ openDropdownMenu: false,
+ downloadHandler: rejectDownloads,
+ onChangeLanguage:
+ SelectTranslationsTestUtils.assertPanelViewTranslationFailure,
+ });
+
+ await SelectTranslationsTestUtils.clickTryAgainButton({
+ downloadHandler: resolveDownloads,
+ viewAssertion: SelectTranslationsTestUtils.assertPanelViewTranslated,
+ });
+
+ await SelectTranslationsTestUtils.clickDoneButton();
+
+ await cleanup();
+ }
+);
+
+/**
+ * This test case tests the scenario of encountering the translation failure message
+ * as a result of changing the selected to-language, along with moving from the failure
+ * state to a successful translation also by changing the selected to-language.
+ */
+add_task(
+ async function test_select_translations_panel_translation_failure_on_change_to_language() {
+ const { cleanup, runInPage, rejectDownloads, resolveDownloads } =
+ await loadTestPage({
+ page: SELECT_TEST_PAGE_URL,
+ languagePairs: LANGUAGE_PAIRS,
+ prefs: [["browser.translations.select.enable", true]],
+ });
+
+ await SelectTranslationsTestUtils.openPanel(runInPage, {
+ selectFrenchSentence: true,
+ openAtFrenchSentence: true,
+ expectedFromLanguage: "fr",
+ expectedToLanguage: "en",
+ downloadHandler: resolveDownloads,
+ onOpenPanel: SelectTranslationsTestUtils.assertPanelViewTranslated,
+ });
+
+ await SelectTranslationsTestUtils.changeSelectedToLanguage(["es"], {
+ openDropdownMenu: false,
+ downloadHandler: rejectDownloads,
+ onChangeLanguage:
+ SelectTranslationsTestUtils.assertPanelViewTranslationFailure,
+ });
+
+ await SelectTranslationsTestUtils.changeSelectedToLanguage(["uk"], {
+ openDropdownMenu: true,
+ downloadHandler: rejectDownloads,
+ onChangeLanguage:
+ SelectTranslationsTestUtils.assertPanelViewTranslationFailure,
+ });
+
+ await SelectTranslationsTestUtils.changeSelectedToLanguage(["es"], {
+ openDropdownMenu: true,
+ downloadHandler: resolveDownloads,
+ pivotTranslation: true,
+ onChangeLanguage: SelectTranslationsTestUtils.assertPanelViewTranslated,
+ });
+
+ await SelectTranslationsTestUtils.changeSelectedToLanguage(["uk"], {
+ openDropdownMenu: false,
+ downloadHandler: rejectDownloads,
+ onChangeLanguage:
+ SelectTranslationsTestUtils.assertPanelViewTranslationFailure,
+ });
+
+ await SelectTranslationsTestUtils.clickTryAgainButton({
+ downloadHandler: resolveDownloads,
+ pivotTranslation: true,
+ viewAssertion: SelectTranslationsTestUtils.assertPanelViewTranslated,
+ });
+
+ await SelectTranslationsTestUtils.clickDoneButton();
+
+ await cleanup();
+ }
+);
diff --git a/browser/components/translations/tests/browser/browser_translations_select_panel_unsupported_language.js b/browser/components/translations/tests/browser/browser_translations_select_panel_unsupported_language.js
new file mode 100644
index 0000000000..0898bd125b
--- /dev/null
+++ b/browser/components/translations/tests/browser/browser_translations_select_panel_unsupported_language.js
@@ -0,0 +1,163 @@
+/* Any copyright is dedicated to the Public Domain.
+ https://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+/**
+ * This test case verifies the behavior of opening the SelectTranslationsPanel to an unsupported language
+ * and then clicking the done button to close the panel.
+ */
+add_task(
+ async function test_select_translations_panel_unsupported_click_done_button() {
+ const { cleanup, runInPage } = await loadTestPage({
+ page: SELECT_TEST_PAGE_URL,
+ languagePairs: [
+ // Do not include Spanish.
+ { fromLang: "fr", toLang: "en" },
+ { fromLang: "en", toLang: "fr" },
+ ],
+ prefs: [["browser.translations.select.enable", true]],
+ });
+
+ await SelectTranslationsTestUtils.openPanel(runInPage, {
+ selectSpanishSentence: true,
+ openAtSpanishSentence: true,
+ onOpenPanel:
+ SelectTranslationsTestUtils.assertPanelViewUnsupportedLanguage,
+ });
+
+ await SelectTranslationsTestUtils.clickDoneButton();
+
+ await cleanup();
+ }
+);
+
+/**
+ * This test case verifies the behavior of opening the SelectTranslationsPanel to an unsupported language
+ * then changing the source language to the same language as the app locale, triggering a same-language
+ * translation, then changing the from-language and to-language multiple times.
+ */
+add_task(
+ async function test_select_translations_panel_unsupported_then_to_same_language_translation() {
+ const { cleanup, runInPage, resolveDownloads } = await loadTestPage({
+ page: SELECT_TEST_PAGE_URL,
+ languagePairs: [
+ // Do not include Spanish.
+ { fromLang: "fa", toLang: "en" },
+ { fromLang: "en", toLang: "fa" },
+ { fromLang: "fi", toLang: "en" },
+ { fromLang: "en", toLang: "fi" },
+ { fromLang: "fr", toLang: "en" },
+ { fromLang: "en", toLang: "fr" },
+ { fromLang: "sl", toLang: "en" },
+ { fromLang: "en", toLang: "sl" },
+ { fromLang: "uk", toLang: "en" },
+ { fromLang: "en", toLang: "uk" },
+ ],
+ prefs: [["browser.translations.select.enable", true]],
+ });
+
+ await SelectTranslationsTestUtils.openPanel(runInPage, {
+ selectSpanishSentence: true,
+ openAtSpanishSentence: true,
+ onOpenPanel:
+ SelectTranslationsTestUtils.assertPanelViewUnsupportedLanguage,
+ });
+
+ await SelectTranslationsTestUtils.changeSelectedTryAnotherSourceLanguage(
+ "en"
+ );
+
+ await SelectTranslationsTestUtils.clickTranslateButton({
+ viewAssertion: SelectTranslationsTestUtils.assertPanelViewTranslated,
+ });
+
+ await SelectTranslationsTestUtils.changeSelectedToLanguage(["uk", "fi"], {
+ openDropdownMenu: false,
+ downloadHandler: resolveDownloads,
+ onChangeLanguage: SelectTranslationsTestUtils.assertPanelViewTranslated,
+ });
+
+ await SelectTranslationsTestUtils.changeSelectedFromLanguage(["sl", "fr"], {
+ openDropdownMenu: true,
+ downloadHandler: resolveDownloads,
+ pivotTranslation: true,
+ onChangeLanguage: SelectTranslationsTestUtils.assertPanelViewTranslated,
+ });
+
+ await SelectTranslationsTestUtils.changeSelectedToLanguage(["fr"], {
+ openDropdownMenu: true,
+ onChangeLanguage: SelectTranslationsTestUtils.assertPanelViewTranslated,
+ });
+
+ await SelectTranslationsTestUtils.clickDoneButton();
+
+ await cleanup();
+ }
+);
+
+/**
+ * This test case verifies the behavior of opening the SelectTranslationsPanel to an unsupported language
+ * then changing the source language to a valid language, followed by changing the from-language and to-language
+ * multiple times.
+ */
+add_task(
+ async function test_select_translations_panel_unsupported_into_different_language_translation() {
+ const { cleanup, runInPage, resolveDownloads } = await loadTestPage({
+ page: SELECT_TEST_PAGE_URL,
+ languagePairs: [
+ // Do not include Spanish.
+ { fromLang: "fa", toLang: "en" },
+ { fromLang: "en", toLang: "fa" },
+ { fromLang: "fi", toLang: "en" },
+ { fromLang: "en", toLang: "fi" },
+ { fromLang: "fr", toLang: "en" },
+ { fromLang: "en", toLang: "fr" },
+ { fromLang: "sl", toLang: "en" },
+ { fromLang: "en", toLang: "sl" },
+ { fromLang: "uk", toLang: "en" },
+ { fromLang: "en", toLang: "uk" },
+ ],
+ prefs: [["browser.translations.select.enable", true]],
+ });
+
+ await SelectTranslationsTestUtils.openPanel(runInPage, {
+ selectSpanishSentence: true,
+ openAtSpanishSentence: true,
+ onOpenPanel:
+ SelectTranslationsTestUtils.assertPanelViewUnsupportedLanguage,
+ });
+
+ await SelectTranslationsTestUtils.changeSelectedTryAnotherSourceLanguage(
+ "fr"
+ );
+
+ await SelectTranslationsTestUtils.clickTranslateButton({
+ downloadHandler: resolveDownloads,
+ viewAssertion: SelectTranslationsTestUtils.assertPanelViewTranslated,
+ });
+
+ await SelectTranslationsTestUtils.changeSelectedFromLanguage(["uk", "fi"], {
+ openDropdownMenu: false,
+ downloadHandler: resolveDownloads,
+ pivotTranslation: true,
+ onChangeLanguage: SelectTranslationsTestUtils.assertPanelViewTranslated,
+ });
+
+ await SelectTranslationsTestUtils.changeSelectedToLanguage(["sl", "uk"], {
+ openDropdownMenu: true,
+ downloadHandler: resolveDownloads,
+ pivotTranslation: true,
+ onChangeLanguage: SelectTranslationsTestUtils.assertPanelViewTranslated,
+ });
+
+ await SelectTranslationsTestUtils.changeSelectedFromLanguage(["uk"], {
+ openDropdownMenu: false,
+ onChangeLanguage: SelectTranslationsTestUtils.assertPanelViewTranslated,
+ });
+
+ await SelectTranslationsTestUtils.clickDoneButton();
+
+ await cleanup();
+ }
+);
diff --git a/browser/components/translations/tests/browser/head.js b/browser/components/translations/tests/browser/head.js
index 454de9146b..4bd5dc074f 100644
--- a/browser/components/translations/tests/browser/head.js
+++ b/browser/components/translations/tests/browser/head.js
@@ -297,6 +297,20 @@ class SharedTranslationsTestUtils {
}
/**
+ * Asserts that the given element has the expected L10nId.
+ *
+ * @param {Element} element - The element to assert against.
+ * @param {string} l10nId - The expected localization id.
+ */
+ static _assertL10nId(element, l10nId) {
+ is(
+ element.getAttribute("data-l10n-id"),
+ l10nId,
+ `The element ${element.id} should have L10n Id ${l10nId}.`
+ );
+ }
+
+ /**
* Asserts that the mainViewId of the panel matches the given string.
*
* @param {FullPageTranslationsPanel | SelectTranslationsPanel} panel
@@ -369,6 +383,28 @@ class SharedTranslationsTestUtils {
}
/**
+ * Asserts that the given elements are focusable in order
+ * via the tab key, starting with the first element already
+ * focused and ending back on that same first element.
+ *
+ * @param {Element[]} elements - The focusable elements.
+ */
+ static _assertTabIndexOrder(elements) {
+ const activeElementAtStart = document.activeElement;
+
+ if (elements.length) {
+ elements[0].focus();
+ elements.push(elements[0]);
+ }
+ for (const element of elements) {
+ SharedTranslationsTestUtils._assertHasFocus(element);
+ EventUtils.synthesizeKey("KEY_Tab");
+ }
+
+ activeElementAtStart.focus();
+ }
+
+ /**
* Executes the provided callback before waiting for the event and then waits for the given event
* to be fired for the element corresponding to the provided elementId.
*
@@ -696,11 +732,7 @@ class FullPageTranslationsTestUtils {
*/
static #assertPanelHeaderL10nId(l10nId) {
const { header } = FullPageTranslationsPanel.elements;
- is(
- header.getAttribute("data-l10n-id"),
- l10nId,
- "The translations panel header should match the expected data-l10n-id"
- );
+ SharedTranslationsTestUtils._assertL10nId(header, l10nId);
}
/**
@@ -710,11 +742,7 @@ class FullPageTranslationsTestUtils {
*/
static #assertPanelErrorL10nId(l10nId) {
const { errorMessage } = FullPageTranslationsPanel.elements;
- is(
- errorMessage.getAttribute("data-l10n-id"),
- l10nId,
- "The translations panel error message should match the expected data-l10n-id"
- );
+ SharedTranslationsTestUtils._assertL10nId(errorMessage, l10nId);
}
/**
@@ -1111,7 +1139,7 @@ class FullPageTranslationsTestUtils {
static async #clickSettingsMenuItemByL10nId(l10nId) {
info(`Toggling the "${l10nId}" settings menu item.`);
click(getByL10nId(l10nId), `Clicking the "${l10nId}" settings menu item.`);
- await closeSettingsMenuIfOpen();
+ await closeFullPagePanelSettingsMenuIfOpen();
}
/**
@@ -1363,10 +1391,21 @@ class SelectTranslationsTestUtils {
* @param {Function} runInPage - A content-exposed function to run within the context of the page.
* @param {object} options - Options for how to open the context menu and what properties to assert about the translate-selection item.
*
- * The following options will only work when testing SELECT_TEST_PAGE_URL.
- *
* @param {boolean} options.expectMenuItemVisible - Whether the select-translations menu item should be present in the context menu.
* @param {boolean} options.expectedTargetLanguage - The expected target language to be shown in the context menu.
+ *
+ * The following options will work on all test pages that have an <h1> element.
+ *
+ * @param {boolean} options.selectH1 - Selects the first H1 element of the page.
+ * @param {boolean} options.openAtH1 - Opens the context menu at the first H1 element of the page.
+ *
+ * The following options will work only in the PDF_TEST_PAGE_URL.
+ *
+ * @param {boolean} options.selectPdfSpan - Selects the first span of text on the first page of a pdf.
+ * @param {boolean} options.openAtPdfSpan - Opens the context menu at the first span of text on the first page of a pdf.
+ *
+ * The following options will only work when testing SELECT_TEST_PAGE_URL.
+ *
* @param {boolean} options.selectFrenchSection - Selects the section of French text.
* @param {boolean} options.selectEnglishSection - Selects the section of English text.
* @param {boolean} options.selectSpanishSection - Selects the section of Spanish text.
@@ -1390,12 +1429,16 @@ class SelectTranslationsTestUtils {
{
expectMenuItemVisible,
expectedTargetLanguage,
+ selectH1,
+ selectPdfSpan,
selectFrenchSection,
selectEnglishSection,
selectSpanishSection,
selectFrenchSentence,
selectEnglishSentence,
selectSpanishSentence,
+ openAtH1,
+ openAtPdfSpan,
openAtFrenchSection,
openAtEnglishSection,
openAtSpanishSection,
@@ -1419,12 +1462,16 @@ class SelectTranslationsTestUtils {
await SelectTranslationsTestUtils.openContextMenu(runInPage, {
expectMenuItemVisible,
expectedTargetLanguage,
+ selectH1,
+ selectPdfSpan,
selectFrenchSection,
selectEnglishSection,
selectSpanishSection,
selectFrenchSentence,
selectEnglishSentence,
selectSpanishSentence,
+ openAtH1,
+ openAtPdfSpan,
openAtFrenchSection,
openAtEnglishSection,
openAtSpanishSection,
@@ -1501,21 +1548,6 @@ class SelectTranslationsTestUtils {
}
/**
- * Elements that should always be visible in the SelectTranslationsPanel.
- */
- static #alwaysPresentElements = {
- betaIcon: true,
- copyButton: true,
- doneButton: true,
- fromLabel: true,
- fromMenuList: true,
- header: true,
- toLabel: true,
- toMenuList: true,
- textArea: true,
- };
-
- /**
* Asserts that for each provided expectation, the visible state of the corresponding
* element in FullPageTranslationsPanel.elements both exists and matches the visibility expectation.
*
@@ -1526,7 +1558,31 @@ class SelectTranslationsTestUtils {
SharedTranslationsTestUtils._assertPanelElementVisibility(
SelectTranslationsPanel.elements,
{
- ...SelectTranslationsTestUtils.#alwaysPresentElements,
+ betaIcon: false,
+ cancelButton: false,
+ copyButton: false,
+ doneButtonPrimary: false,
+ doneButtonSecondary: false,
+ fromLabel: false,
+ fromMenuList: false,
+ fromMenuPopup: false,
+ header: false,
+ initFailureContent: false,
+ initFailureMessageBar: false,
+ mainContent: false,
+ settingsButton: false,
+ textArea: false,
+ toLabel: false,
+ toMenuList: false,
+ toMenuPopup: false,
+ translateButton: false,
+ translateFullPageButton: false,
+ translationFailureMessageBar: false,
+ tryAgainButton: false,
+ tryAnotherSourceMenuList: false,
+ tryAnotherSourceMenuPopup: false,
+ unsupportedLanguageContent: false,
+ unsupportedLanguageMessageBar: false,
// Overwrite any of the above defaults with the passed in expectations.
...expectations,
}
@@ -1534,26 +1590,172 @@ class SelectTranslationsTestUtils {
}
/**
+ * Waits for the panel's translation state to reach the given phase,
+ * if it is not currently in that phase already.
+ *
+ * @param {string} phase - The phase of the panel's translation state to wait for.
+ */
+ static async waitForPanelState(phase) {
+ const currentPhase = SelectTranslationsPanel.phase();
+ if (currentPhase !== phase) {
+ info(
+ `Waiting for SelectTranslationsPanel to change state from "${currentPhase}" to "${phase}"`
+ );
+ await BrowserTestUtils.waitForEvent(
+ document,
+ "SelectTranslationsPanelStateChanged",
+ event => event.detail.phase === phase
+ );
+ }
+ }
+
+ /**
* Asserts that the SelectTranslationsPanel UI matches the expected
* state when the panel has completed its translation.
*/
- static assertPanelViewTranslated() {
- const { textArea } = SelectTranslationsPanel.elements;
+ static async assertPanelViewTranslated() {
+ const {
+ copyButton,
+ doneButtonPrimary,
+ fromMenuList,
+ settingsButton,
+ textArea,
+ toMenuList,
+ translateFullPageButton,
+ } = SelectTranslationsPanel.elements;
+ const sameLanguageSelected = fromMenuList.value === toMenuList.value;
+ await SelectTranslationsTestUtils.waitForPanelState("translated");
ok(
!textArea.classList.contains("translating"),
"The textarea should not have the translating class."
);
+ const isFullPageTranslationsRestrictedForPage =
+ TranslationsParent.isFullPageTranslationsRestrictedForPage(gBrowser);
SelectTranslationsTestUtils.#assertPanelElementVisibility({
- ...SelectTranslationsTestUtils.#alwaysPresentElements,
+ betaIcon: true,
+ copyButton: true,
+ doneButtonPrimary: true,
+ fromLabel: true,
+ fromMenuList: true,
+ header: true,
+ mainContent: true,
+ settingsButton: true,
+ textArea: true,
+ toLabel: true,
+ toMenuList: true,
+ translateFullPageButton: !isFullPageTranslationsRestrictedForPage,
});
SelectTranslationsTestUtils.#assertConditionalUIEnabled({
- textArea: true,
copyButton: true,
- translateFullPageButton: true,
+ doneButtonPrimary: true,
+ textArea: true,
+ translateFullPageButton:
+ !sameLanguageSelected && !isFullPageTranslationsRestrictedForPage,
});
+
+ await waitForCondition(
+ () =>
+ !copyButton.classList.contains("copied") &&
+ copyButton.getAttribute("data-l10n-id") ===
+ "select-translations-panel-copy-button",
+ "Waiting for copy button to match the not-copied state."
+ );
+
SelectTranslationsTestUtils.#assertPanelHasTranslatedText();
SelectTranslationsTestUtils.#assertPanelTextAreaHeight();
- SelectTranslationsTestUtils.#assertPanelTextAreaOverflow();
+ await SelectTranslationsTestUtils.#assertPanelTextAreaOverflow();
+
+ let footerButtons;
+ if (sameLanguageSelected || isFullPageTranslationsRestrictedForPage) {
+ footerButtons = [copyButton, doneButtonPrimary];
+ } else {
+ footerButtons =
+ AppConstants.platform === "win"
+ ? [copyButton, doneButtonPrimary, translateFullPageButton]
+ : [copyButton, translateFullPageButton, doneButtonPrimary];
+ }
+
+ SharedTranslationsTestUtils._assertTabIndexOrder([
+ settingsButton,
+ fromMenuList,
+ toMenuList,
+ textArea,
+ ...footerButtons,
+ ]);
+ }
+
+ /**
+ * Asserts that the SelectTranslationsPanel UI matches the expected
+ * state when the language lists fail to initialize upon opening the panel.
+ */
+ static async assertPanelViewInitFailure() {
+ const { cancelButton, settingsButton, tryAgainButton } =
+ SelectTranslationsPanel.elements;
+ await SelectTranslationsTestUtils.waitForPanelState("init-failure");
+ SelectTranslationsTestUtils.#assertPanelElementVisibility({
+ header: true,
+ betaIcon: true,
+ cancelButton: true,
+ initFailureContent: true,
+ initFailureMessageBar: true,
+ settingsButton: true,
+ tryAgainButton: true,
+ });
+ SharedTranslationsTestUtils._assertTabIndexOrder([
+ settingsButton,
+ ...(AppConstants.platform === "win"
+ ? [tryAgainButton, cancelButton]
+ : [cancelButton, tryAgainButton]),
+ ]);
+ SharedTranslationsTestUtils._assertHasFocus(tryAgainButton);
+ }
+
+ /**
+ * Asserts that the SelectTranslationsPanel UI matches the expected
+ * state when a translation has failed to complete.
+ */
+ static async assertPanelViewTranslationFailure() {
+ const {
+ cancelButton,
+ fromMenuList,
+ settingsButton,
+ toMenuList,
+ translationFailureMessageBar,
+ tryAgainButton,
+ } = SelectTranslationsPanel.elements;
+ await SelectTranslationsTestUtils.waitForPanelState("translation-failure");
+ SelectTranslationsTestUtils.#assertPanelElementVisibility({
+ header: true,
+ betaIcon: true,
+ cancelButton: true,
+ fromLabel: true,
+ fromMenuList: true,
+ mainContent: true,
+ settingsButton: true,
+ toLabel: true,
+ toMenuList: true,
+ translationFailureMessageBar: true,
+ tryAgainButton: true,
+ });
+ is(
+ document.activeElement,
+ tryAgainButton,
+ "The try-again button should have focus."
+ );
+ is(
+ translationFailureMessageBar.getAttribute("role"),
+ "alert",
+ "The translation failure message bar is an alert."
+ );
+ SharedTranslationsTestUtils._assertTabIndexOrder([
+ settingsButton,
+ fromMenuList,
+ toMenuList,
+ ...(AppConstants.platform === "win"
+ ? [tryAgainButton, cancelButton]
+ : [cancelButton, tryAgainButton]),
+ ]);
+ SharedTranslationsTestUtils._assertHasFocus(tryAgainButton);
}
static #assertPanelTextAreaDirection(langTag = null) {
@@ -1571,23 +1773,65 @@ class SelectTranslationsTestUtils {
}
/**
+ * Asserts that the SelectTranslationsPanel UI matches the expected
+ * state when the panel has completed its translation.
+ */
+ static async assertPanelViewUnsupportedLanguage() {
+ await SelectTranslationsTestUtils.waitForPanelState("unsupported");
+ const {
+ doneButtonSecondary,
+ settingsButton,
+ translateButton,
+ tryAnotherSourceMenuList,
+ unsupportedLanguageMessageBar,
+ } = SelectTranslationsPanel.elements;
+ SelectTranslationsTestUtils.#assertPanelElementVisibility({
+ betaIcon: true,
+ doneButtonSecondary: true,
+ header: true,
+ settingsButton: true,
+ translateButton: true,
+ tryAnotherSourceMenuList: true,
+ unsupportedLanguageContent: true,
+ unsupportedLanguageMessageBar: true,
+ });
+ SelectTranslationsTestUtils.#assertConditionalUIEnabled({
+ doneButtonSecondary: true,
+ translateButton: false,
+ });
+ ok(
+ translateButton.disabled,
+ "The translate button should be disabled when first shown."
+ );
+ SharedTranslationsTestUtils._assertL10nId(
+ unsupportedLanguageMessageBar,
+ "select-translations-panel-unsupported-language-message-known"
+ );
+ SharedTranslationsTestUtils._assertHasFocus(tryAnotherSourceMenuList);
+ SharedTranslationsTestUtils._assertTabIndexOrder([
+ settingsButton,
+ tryAnotherSourceMenuList,
+ doneButtonSecondary,
+ ]);
+ }
+
+ /**
* Asserts that the SelectTranslationsPanel translated text area is
* both scrollable and scrolled to the top.
*/
- static #assertPanelTextAreaOverflow() {
+ static async #assertPanelTextAreaOverflow() {
const { textArea } = SelectTranslationsPanel.elements;
- is(
- textArea.style.overflow,
- "auto",
- "The translated-text area should be scrollable."
- );
- if (textArea.scrollHeight > textArea.clientHeight) {
- is(
- textArea.scrollTop,
- 0,
- "The translated-text area should be scrolled to the top."
+ if (textArea.style.overflow !== "auto") {
+ await BrowserTestUtils.waitForMutationCondition(
+ textArea,
+ { attributes: true, attributeFilter: ["style"] },
+ () => textArea.style.overflow === "auto"
);
}
+ if (textArea.scrollHeight > textArea.clientHeight) {
+ info("Ensuring that the textarea is scrolled to the top.");
+ await waitForCondition(() => textArea.scrollTop === 0);
+ }
}
/**
@@ -1619,88 +1863,44 @@ class SelectTranslationsTestUtils {
* Asserts that the SelectTranslationsPanel UI matches the expected
* state when the panel is actively translating text.
*/
- static assertPanelViewActivelyTranslating() {
+ static async assertPanelViewActivelyTranslating() {
const { textArea } = SelectTranslationsPanel.elements;
+ const isFullPageTranslationsRestrictedForPage =
+ TranslationsParent.isFullPageTranslationsRestrictedForPage(gBrowser);
+ await SelectTranslationsTestUtils.waitForPanelState("translating");
ok(
textArea.classList.contains("translating"),
"The textarea should have the translating class."
);
SelectTranslationsTestUtils.#assertPanelElementVisibility({
- ...SelectTranslationsTestUtils.#alwaysPresentElements,
+ betaIcon: true,
+ copyButton: true,
+ doneButtonPrimary: true,
+ fromLabel: true,
+ fromMenuList: true,
+ header: true,
+ mainContent: true,
+ settingsButton: true,
+ textArea: true,
+ toLabel: true,
+ toMenuList: true,
+ translateFullPageButton: !isFullPageTranslationsRestrictedForPage,
});
SelectTranslationsTestUtils.#assertPanelHasTranslatingPlaceholder();
}
/**
- * Asserts that the SelectTranslationsPanel UI matches the expected
- * state when no from-language is selected in the panel.
- */
- static async assertPanelViewNoFromLangSelected() {
- const { textArea } = SelectTranslationsPanel.elements;
- ok(
- !textArea.classList.contains("translating"),
- "The textarea should not have the translating class."
- );
- SelectTranslationsTestUtils.#assertPanelElementVisibility({
- ...SelectTranslationsTestUtils.#alwaysPresentElements,
- });
- await SelectTranslationsTestUtils.#assertPanelHasIdlePlaceholder();
- SelectTranslationsTestUtils.#assertConditionalUIEnabled({
- textArea: false,
- copyButton: false,
- translateFullPageButton: false,
- });
- SelectTranslationsTestUtils.assertSelectedFromLanguage(null);
- }
-
- /**
- * Asserts that the SelectTranslationsPanel UI matches the expected
- * state when no to-language is selected in the panel.
- */
- static async assertPanelViewNoToLangSelected() {
- const { textArea } = SelectTranslationsPanel.elements;
- ok(
- !textArea.classList.contains("translating"),
- "The textarea should not have the translating class."
- );
- SelectTranslationsTestUtils.#assertPanelElementVisibility({
- ...SelectTranslationsTestUtils.#alwaysPresentElements,
- });
- SelectTranslationsTestUtils.assertSelectedToLanguage(null);
- SelectTranslationsTestUtils.#assertConditionalUIEnabled({
- textArea: false,
- copyButton: false,
- translateFullPageButton: false,
- });
- await SelectTranslationsTestUtils.#assertPanelHasIdlePlaceholder();
- }
-
- /**
- * Asserts that the SelectTranslationsPanel UI contains the
- * idle placeholder text.
- */
- static async #assertPanelHasIdlePlaceholder() {
- const { textArea } = SelectTranslationsPanel.elements;
- const expected = await document.l10n.formatValue(
- "select-translations-panel-idle-placeholder-text"
- );
- is(
- textArea.value,
- expected,
- "Translated text area should be the idle placeholder."
- );
- SelectTranslationsTestUtils.#assertPanelTextAreaDirection();
- }
-
- /**
* Asserts that the SelectTranslationsPanel UI contains the
* translating placeholder text.
*/
static async #assertPanelHasTranslatingPlaceholder() {
- const { textArea } = SelectTranslationsPanel.elements;
+ const { textArea, fromMenuList, toMenuList } =
+ SelectTranslationsPanel.elements;
const expected = await document.l10n.formatValue(
"select-translations-panel-translating-placeholder-text"
);
+ const isFullPageTranslationsRestrictedForPage =
+ TranslationsParent.isFullPageTranslationsRestrictedForPage(gBrowser);
is(
textArea.value,
expected,
@@ -1710,7 +1910,10 @@ class SelectTranslationsTestUtils {
SelectTranslationsTestUtils.#assertConditionalUIEnabled({
textArea: true,
copyButton: false,
- translateFullPageButton: true,
+ doneButtonPrimary: true,
+ translateFullPageButton:
+ fromMenuList.value !== toMenuList.value &&
+ !isFullPageTranslationsRestrictedForPage,
});
}
@@ -1723,6 +1926,27 @@ class SelectTranslationsTestUtils {
SelectTranslationsPanel.elements;
const fromLanguage = fromMenuList.value;
const toLanguage = toMenuList.value;
+ const isFullPageTranslationsRestrictedForPage =
+ TranslationsParent.isFullPageTranslationsRestrictedForPage(gBrowser);
+
+ SelectTranslationsTestUtils.#assertPanelTextAreaDirection(toLanguage);
+ SelectTranslationsTestUtils.#assertConditionalUIEnabled({
+ textArea: true,
+ copyButton: true,
+ doneButtonPrimary: true,
+ translateFullPageButton:
+ fromLanguage !== toLanguage && !isFullPageTranslationsRestrictedForPage,
+ });
+
+ if (fromLanguage === toLanguage) {
+ is(
+ SelectTranslationsPanel.getSourceText(),
+ SelectTranslationsPanel.getTranslatedText(),
+ "The source text should passthrough as the translated text."
+ );
+ return;
+ }
+
const translatedSuffix = ` [${fromLanguage} to ${toLanguage}]`;
ok(
textArea.value.endsWith(translatedSuffix),
@@ -1734,45 +1958,32 @@ class SelectTranslationsTestUtils {
translatedSuffix.length,
"Expected translated text length to correspond to the source text length."
);
- SelectTranslationsTestUtils.#assertPanelTextAreaDirection(toLanguage);
- SelectTranslationsTestUtils.#assertConditionalUIEnabled({
- textArea: true,
- copyButton: true,
- translateFullPageButton: true,
- });
}
/**
* Asserts the enabled state of action buttons in the SelectTranslationsPanel.
*
- * @param {boolean} expectEnabled - Whether the buttons should be enabled (true) or not (false).
+ * @param {Record<string, boolean>} enabledStates
+ * - An object that maps whether each element should be enabled (true) or disabled (false).
*/
- static #assertConditionalUIEnabled({
- copyButton: copyButtonEnabled,
- translateFullPageButton: translateFullPageButtonEnabled,
- textArea: textAreaEnabled,
- }) {
- const { copyButton, translateFullPageButton, textArea } =
- SelectTranslationsPanel.elements;
- is(
- copyButton.disabled,
- !copyButtonEnabled,
- `The copy button should be ${copyButtonEnabled ? "enabled" : "disabled"}.`
- );
- is(
- translateFullPageButton.disabled,
- !translateFullPageButtonEnabled,
- `The translate-full-page button should be ${
- translateFullPageButtonEnabled ? "enabled" : "disabled"
- }.`
- );
- is(
- textArea.disabled,
- !textAreaEnabled,
- `The translated-text area should be ${
- textAreaEnabled ? "enabled" : "disabled"
- }.`
- );
+ static #assertConditionalUIEnabled(enabledStates) {
+ const elements = SelectTranslationsPanel.elements;
+
+ for (const [elementName, expectEnabled] of Object.entries(enabledStates)) {
+ const element = elements[elementName];
+ if (!element) {
+ throw new Error(
+ `SelectTranslationsPanel element '${elementName}' not found.`
+ );
+ }
+ is(
+ element.disabled,
+ !expectEnabled,
+ `The element '${elementName} should be ${
+ expectEnabled ? "enabled" : "disabled"
+ }.`
+ );
+ }
}
/**
@@ -1820,22 +2031,235 @@ class SelectTranslationsTestUtils {
*/
static async clickDoneButton() {
logAction();
- const { doneButton } = SelectTranslationsPanel.elements;
- assertVisibility({ visible: { doneButton } });
+ const { doneButtonPrimary, doneButtonSecondary } =
+ SelectTranslationsPanel.elements;
+ let visibleDoneButton;
+ let hiddenDoneButton;
+ if (BrowserTestUtils.isVisible(doneButtonPrimary)) {
+ visibleDoneButton = doneButtonPrimary;
+ hiddenDoneButton = doneButtonSecondary;
+ } else if (BrowserTestUtils.isVisible(doneButtonSecondary)) {
+ visibleDoneButton = doneButtonSecondary;
+ hiddenDoneButton = doneButtonPrimary;
+ } else {
+ throw new Error(
+ "Expected either the primary or secondary done button to be visible."
+ );
+ }
+ assertVisibility({
+ visible: { visibleDoneButton },
+ hidden: { hiddenDoneButton },
+ });
+ await SelectTranslationsTestUtils.waitForPanelPopupEvent(
+ "popuphidden",
+ () => {
+ click(visibleDoneButton, "Clicking the done button");
+ }
+ );
+ }
+
+ /**
+ * Simulates clicking the cancel button and waits for the panel to close.
+ */
+ static async clickCancelButton() {
+ logAction();
+ const { cancelButton } = SelectTranslationsPanel.elements;
+ assertVisibility({ visible: { cancelButton } });
await SelectTranslationsTestUtils.waitForPanelPopupEvent(
"popuphidden",
() => {
- click(doneButton, "Clicking the done button");
+ click(cancelButton, "Clicking the cancel button");
}
);
}
/**
+ * Simulates clicking the copy button and asserts that all relevant states are correctly updated.
+ */
+ static async clickCopyButton() {
+ logAction();
+ const { copyButton } = SelectTranslationsPanel.elements;
+
+ assertVisibility({ visible: { copyButton } });
+ is(
+ SelectTranslationsPanel.phase(),
+ "translated",
+ 'The copy button should only be clickable in the "translated" phase'
+ );
+
+ click(copyButton, "Clicking the copy button");
+ await waitForCondition(
+ () =>
+ copyButton.classList.contains("copied") &&
+ copyButton.getAttribute("data-l10n-id") ===
+ "select-translations-panel-copy-button-copied",
+ "Waiting for copy button to match the copied state."
+ );
+
+ const copiedText = SpecialPowers.getClipboardData("text/plain");
+ is(
+ // Because of differences in the clipboard code on Windows, we are going
+ // to explicitly sanitize carriage returns here when checking equality.
+ copiedText.replaceAll("\r", ""),
+ SelectTranslationsPanel.getTranslatedText().replaceAll("\r", ""),
+ "The clipboard should contain the translated text."
+ );
+ }
+
+ /**
+ * Simulates clicking the Translate button in the SelectTranslationsPanel,
+ * then waits for any pending translation effects, based on the provided options.
+ *
+ * @param {object} config
+ * @param {Function} [config.downloadHandler]
+ * - The function handle expected downloads, resolveDownloads() or rejectDownloads()
+ * Leave as null to test more granularly, such as testing opening the loading view,
+ * or allowing for the automatic downloading of files.
+ * @param {boolean} [config.pivotTranslation]
+ * - True if the expected translation is a pivot translation, otherwise false.
+ * Affects the number of expected downloads.
+ * @param {Function} [config.viewAssertion]
+ * - An optional callback function to execute for asserting the panel UI state.
+ */
+ static async clickTranslateButton({
+ downloadHandler,
+ pivotTranslation,
+ viewAssertion,
+ }) {
+ logAction();
+ const {
+ doneButtonSecondary,
+ settingsButton,
+ translateButton,
+ tryAnotherSourceMenuList,
+ } = SelectTranslationsPanel.elements;
+ assertVisibility({ visible: { doneButtonPrimary: translateButton } });
+
+ ok(!translateButton.disabled, "The translate button should be enabled.");
+ SharedTranslationsTestUtils._assertTabIndexOrder([
+ settingsButton,
+ tryAnotherSourceMenuList,
+ ...(AppConstants.platform === "win"
+ ? [translateButton, doneButtonSecondary]
+ : [doneButtonSecondary, translateButton]),
+ ]);
+
+ click(translateButton);
+ await SelectTranslationsTestUtils.waitForPanelState("translatable");
+ if (downloadHandler) {
+ await this.handleDownloads({ downloadHandler, pivotTranslation });
+ }
+ if (viewAssertion) {
+ await viewAssertion();
+ }
+ }
+
+ /**
+ * Simulates clicking the translate-full-page button.
+ */
+ static async clickTranslateFullPageButton() {
+ logAction();
+ const { translateFullPageButton } = SelectTranslationsPanel.elements;
+ assertVisibility({ visible: { translateFullPageButton } });
+ click(translateFullPageButton);
+ await FullPageTranslationsTestUtils.assertTranslationsButton(
+ { button: true, circleArrows: true, locale: false, icon: true },
+ "The icon presents the loading indicator."
+ );
+ }
+
+ /**
+ * Simulates clicking the try-again button.
+ *
+ * @param {object} config
+ * @param {Function} [config.downloadHandler]
+ * - The function handle expected downloads, resolveDownloads() or rejectDownloads()
+ * Leave as null to test more granularly, such as testing opening the loading view,
+ * or allowing for the automatic downloading of files.
+ * @param {boolean} [config.pivotTranslation]
+ * - True if the expected translation is a pivot translation, otherwise false.
+ * Affects the number of expected downloads.
+ * @param {Function} [config.viewAssertion]
+ * - An optional callback function to execute for asserting the panel UI state.
+ */
+ static async clickTryAgainButton({
+ downloadHandler,
+ pivotTranslation,
+ viewAssertion,
+ } = {}) {
+ logAction();
+ const { tryAgainButton } = SelectTranslationsPanel.elements;
+ assertVisibility({ visible: { tryAgainButton } });
+ click(tryAgainButton, "Clicking the try-again button");
+ await SelectTranslationsTestUtils.waitForPanelState("translatable");
+ if (downloadHandler) {
+ await this.handleDownloads({ downloadHandler, pivotTranslation });
+ }
+ if (viewAssertion) {
+ await viewAssertion();
+ }
+ }
+
+ /**
+ * Opens the SelectTranslationsPanel settings menu.
+ * Requires that the translations panel is already open.
+ */
+ static async openPanelSettingsMenu() {
+ logAction();
+ const { settingsButton } = SelectTranslationsPanel.elements;
+ assertVisibility({ visible: { settingsButton } });
+ await SharedTranslationsTestUtils._waitForPopupEvent(
+ "select-translations-panel-settings-menupopup",
+ "popupshown",
+ () => click(settingsButton, "Opening the settings menu")
+ );
+ const settingsPageMenuItem = document.getElementById(
+ "select-translations-panel-open-settings-page-menuitem"
+ );
+ const aboutTranslationsMenuItem = document.getElementById(
+ "select-translations-panel-about-translations-menuitem"
+ );
+
+ assertVisibility({
+ visible: {
+ settingsPageMenuItem,
+ aboutTranslationsMenuItem,
+ },
+ });
+ }
+
+ /**
+ * Clicks the SelectTranslationsPanel settings menu item
+ * that leads to the Translations Settings in about:preferences.
+ */
+ static clickTranslationsSettingsPageMenuItem() {
+ logAction();
+ const settingsPageMenuItem = document.getElementById(
+ "select-translations-panel-open-settings-page-menuitem"
+ );
+ assertVisibility({ visible: { settingsPageMenuItem } });
+ click(settingsPageMenuItem);
+ }
+
+ /**
* Opens the context menu at a specified element on the page, based on the provided options.
*
* @param {Function} runInPage - A content-exposed function to run within the context of the page.
* @param {object} options - Options for opening the context menu.
*
+ * @param {boolean} options.expectMenuItemVisible - Whether the select-translations menu item should be present in the context menu.
+ * @param {boolean} options.expectedTargetLanguage - The expected target language to be shown in the context menu.
+ *
+ * The following options will work on all test pages that have an <h1> element.
+ *
+ * @param {boolean} options.selectH1 - Selects the first H1 element of the page.
+ * @param {boolean} options.openAtH1 - Opens the context menu at the first H1 element of the page.
+ *
+ * The following options will work only in the PDF_TEST_PAGE_URL.
+ *
+ * @param {boolean} options.selectPdfSpan - Selects the first span of text on the first page of a pdf.
+ * @param {boolean} options.openAtPdfSpan - Opens the context menu at the first span of text on the first page of a pdf.
+ *
* The following options will only work when testing SELECT_TEST_PAGE_URL.
*
* @param {boolean} options.selectFrenchSection - Selects the section of French text.
@@ -1868,8 +2292,8 @@ class SelectTranslationsTestUtils {
const selectorFunction =
TranslationsTest.getSelectors()[data.selectorFunctionName];
if (typeof selectorFunction === "function") {
- const paragraph = selectorFunction();
- TranslationsTest.selectContentElement(paragraph);
+ const element = await selectorFunction();
+ TranslationsTest.selectContentElement(element);
}
},
{ selectorFunctionName }
@@ -1877,6 +2301,8 @@ class SelectTranslationsTestUtils {
}
};
+ await maybeSelectContentFrom("H1");
+ await maybeSelectContentFrom("PdfSpan");
await maybeSelectContentFrom("FrenchSection");
await maybeSelectContentFrom("EnglishSection");
await maybeSelectContentFrom("SpanishSection");
@@ -1898,7 +2324,7 @@ class SelectTranslationsTestUtils {
const selectorFunction =
TranslationsTest.getSelectors()[data.selectorFunctionName];
if (typeof selectorFunction === "function") {
- const element = selectorFunction();
+ const element = await selectorFunction();
await TranslationsTest.rightClickContentElement(element);
}
},
@@ -1909,6 +2335,8 @@ class SelectTranslationsTestUtils {
}
};
+ await maybeOpenContextMenuAt("H1");
+ await maybeOpenContextMenuAt("PdfSpan");
await maybeOpenContextMenuAt("FrenchSection");
await maybeOpenContextMenuAt("EnglishSection");
await maybeOpenContextMenuAt("SpanishSection");
@@ -1931,28 +2359,10 @@ class SelectTranslationsTestUtils {
* @returns {Promise<void>}
*/
static async handleDownloads({ downloadHandler, pivotTranslation }) {
- const { textArea } = SelectTranslationsPanel.elements;
-
if (downloadHandler) {
- if (textArea.style.overflow !== "hidden") {
- await BrowserTestUtils.waitForMutationCondition(
- textArea,
- { attributes: true, attributeFilter: ["style"] },
- () => textArea.style.overflow === "hidden"
- );
- }
-
await SelectTranslationsTestUtils.assertPanelViewActivelyTranslating();
await downloadHandler(pivotTranslation ? 2 : 1);
}
-
- if (textArea.style.overflow === "hidden") {
- await BrowserTestUtils.waitForMutationCondition(
- textArea,
- { attributes: true, attributeFilter: ["style"] },
- () => textArea.style.overflow === "auto"
- );
- }
}
/**
@@ -1965,6 +2375,7 @@ class SelectTranslationsTestUtils {
* @returns {Promise<void>}
*/
static async changeSelectedFromLanguage(langTags, options) {
+ logAction(langTags);
const { fromMenuList, fromMenuPopup } = SelectTranslationsPanel.elements;
const { openDropdownMenu } = options;
@@ -1980,6 +2391,28 @@ class SelectTranslationsTestUtils {
}
/**
+ * Change the selected language in the try-another-source-language dropdown.
+ *
+ * @param {string} langTag - A BCP-47 language tag.
+ */
+ static async changeSelectedTryAnotherSourceLanguage(langTag) {
+ logAction(langTag);
+ const { tryAnotherSourceMenuList, translateButton } =
+ SelectTranslationsPanel.elements;
+ await SelectTranslationsTestUtils.#changeSelectedLanguageDirectly(
+ [langTag],
+ { menuList: tryAnotherSourceMenuList },
+ {
+ onChangeLanguage: () =>
+ ok(
+ !translateButton.disabled,
+ "The translate button should be enabled after selecting a language."
+ ),
+ }
+ );
+ }
+
+ /**
* Switches the selected to-language to the provided language tag.
*
* @param {string[]} langTags - An array of BCP-47 language tags.
@@ -1991,6 +2424,7 @@ class SelectTranslationsTestUtils {
* @returns {Promise<void>}
*/
static async changeSelectedToLanguage(langTags, options) {
+ logAction(langTags);
const { toMenuList, toMenuPopup } = SelectTranslationsPanel.elements;
const { openDropdownMenu } = options;
@@ -2019,6 +2453,7 @@ class SelectTranslationsTestUtils {
*/
static async #changeSelectedLanguageDirectly(langTags, elements, options) {
const { menuList } = elements;
+ const { textArea } = SelectTranslationsPanel.elements;
const { onChangeLanguage, downloadHandler } = options;
for (const langTag of langTags) {
@@ -2028,14 +2463,23 @@ class SelectTranslationsTestUtils {
() => menuList.value === langTag
);
+ menuList.focus();
menuList.value = langTag;
menuList.dispatchEvent(new Event("command"));
await menuListUpdated;
}
- if (downloadHandler) {
- menuList.focus();
+ // Either of these events should trigger a translation after the selected
+ // language has been changed directly.
+ if (Math.random() < 0.5) {
+ info("Attempting to trigger translation via text-area focus.");
+ textArea.focus();
+ } else {
+ info("Attempting to trigger translation via pressing Enter.");
EventUtils.synthesizeKey("KEY_Enter");
+ }
+
+ if (downloadHandler) {
await SelectTranslationsTestUtils.handleDownloads(options);
}