tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

commit dabbf9a4cd16cb65fe410e0cd5e9232330db10f6
parent bd005d4ae09f1e8021b55dec464fec033238d890
Author: Erik Nordin <enordin@mozilla.com>
Date:   Tue, 23 Dec 2025 16:05:29 +0000

Bug 1992233 - Part 3/3: Implement Clear Button Functionality r=translations-reviewers,gregtatum

This commit implements the logic for clicking the source-section clear
button on the about:translations page. Invoking the clear button will
clear the source text, resulting in the button now being hidden, and
the source-text section having focus.

Differential Revision: https://phabricator.services.mozilla.com/D277057

Diffstat:
Mtoolkit/components/translations/content/about-translations.mjs | 43+++++++++++++++++++++++++++++++++++++++++++
Mtoolkit/components/translations/tests/browser/browser.toml | 2++
Atoolkit/components/translations/tests/browser/browser_about_translations_source_clear_button_functionality.js | 140+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtoolkit/components/translations/tests/browser/shared-head.js | 48+++++++++++++++++++++++++++++++++++-------------
4 files changed, 220 insertions(+), 13 deletions(-)

diff --git a/toolkit/components/translations/content/about-translations.mjs b/toolkit/components/translations/content/about-translations.mjs @@ -372,6 +372,7 @@ class AboutTranslations { learnMoreLink, sourceLanguageSelector, sourceSectionActionsColumn, + sourceSectionClearButton, sourceSectionTextArea, swapLanguagesButton, targetLanguageSelector, @@ -389,6 +390,14 @@ class AboutTranslations { "pointerdown", this.#onSourceSectionActionsPointerDown ); + sourceSectionClearButton.addEventListener( + "click", + this.#onSourceSectionClearButton + ); + sourceSectionClearButton.addEventListener( + "mousedown", + this.#onSourceSectionClearButtonMouseDown + ); sourceSectionTextArea.addEventListener("input", this.#onSourceTextInput); sourceSectionTextArea.addEventListener( "focus", @@ -421,6 +430,32 @@ class AboutTranslations { }; /** + * Handles mousedown on the source section clear button. + */ + #onSourceSectionClearButtonMouseDown = event => { + if (this.elements.sourceSection.classList.contains("focus-section")) { + // When the source section has a focus outline, clicking the clear button will cause the outline + // to disappear and then reappear since clicking the clear button re-focuses the source section. + // We should just avoid the outline flash all together when the source section is focused. + event.preventDefault(); + } + }; + + /** + * Handles clicks on the source section clear button. + */ + #onSourceSectionClearButton = event => { + if (!this.#sourceTextAreaHasValue()) { + return; + } + + event.preventDefault(); + this.#setSourceText(""); + this.#maybeUpdateDetectedSourceLanguage(); + this.elements.sourceSectionTextArea.focus(); + }; + + /** * Handles clicks on the swap-languages button. */ #onSwapLanguagesButton = () => { @@ -1059,12 +1094,20 @@ class AboutTranslations { */ #setSourceText(value) { const { sourceSectionTextArea } = this.elements; + const hadValueBefore = Boolean(sourceSectionTextArea.value); sourceSectionTextArea.value = value; sourceSectionTextArea.dispatchEvent(new Event("input")); + this.#maybeUpdateDetectedSourceLanguage(); this.#updateSourceScriptDirection(); this.#ensureSectionHeightsMatch({ scheduleCallback: false }); + + if (!value && hadValueBefore) { + document.dispatchEvent( + new CustomEvent("AboutTranslationsTest:ClearSourceText") + ); + } } /** diff --git a/toolkit/components/translations/tests/browser/browser.toml b/toolkit/components/translations/tests/browser/browser.toml @@ -41,6 +41,8 @@ skip-if = [ ["browser_about_translations_flip_lexical_shortlist.js"] +["browser_about_translations_source_clear_button_functionality.js"] + ["browser_about_translations_source_clear_button_visibility.js"] ["browser_about_translations_swap_languages_button_detected.js"] diff --git a/toolkit/components/translations/tests/browser/browser_about_translations_source_clear_button_functionality.js b/toolkit/components/translations/tests/browser/browser_about_translations_source_clear_button_functionality.js @@ -0,0 +1,140 @@ +/* Any copyright is dedicated to the Public Domain. + https://creativecommons.org/publicdomain/zero/1.0/ */ + +"use strict"; + +add_task( + async function test_source_clear_button_clears_text_and_target_output() { + const { aboutTranslationsTestUtils, cleanup } = await openAboutTranslations( + { + languagePairs: LANGUAGE_PAIRS, + autoDownloadFromRemoteSettings: false, + } + ); + + await aboutTranslationsTestUtils.setSourceLanguageSelectorValue("en"); + await aboutTranslationsTestUtils.setTargetLanguageSelectorValue("fr"); + + await aboutTranslationsTestUtils.setSourceTextAreaValue("Hello world"); + await aboutTranslationsTestUtils.assertEvents( + { + expected: [ + [ + AboutTranslationsTestUtils.Events.TranslationComplete, + AboutTranslationsTestUtils.AnyEventDetail, + ], + [AboutTranslationsTestUtils.Events.CopyButtonEnabled], + ], + }, + async () => { + await aboutTranslationsTestUtils.resolveDownloads(1); + } + ); + + await aboutTranslationsTestUtils.assertSourceClearButton({ + visible: true, + }); + + await aboutTranslationsTestUtils.assertEvents( + { + expected: [ + [AboutTranslationsTestUtils.Events.ClearSourceText], + [AboutTranslationsTestUtils.Events.SourceTextClearButtonHidden], + [AboutTranslationsTestUtils.Events.ClearTargetText], + [AboutTranslationsTestUtils.Events.CopyButtonDisabled], + ], + unexpected: [ + AboutTranslationsTestUtils.Events.TranslationRequested, + AboutTranslationsTestUtils.Events.CopyButtonEnabled, + ], + }, + async () => { + await aboutTranslationsTestUtils.clickClearButton(); + } + ); + + await aboutTranslationsTestUtils.assertSourceTextArea({ + showsPlaceholder: true, + }); + await aboutTranslationsTestUtils.assertTargetTextArea({ + showsPlaceholder: true, + }); + await aboutTranslationsTestUtils.assertCopyButton({ enabled: false }); + await aboutTranslationsTestUtils.assertSourceClearButton({ + visible: false, + }); + + await cleanup(); + } +); + +add_task( + async function test_source_clear_button_cancels_translation_in_progress() { + const { aboutTranslationsTestUtils, cleanup } = await openAboutTranslations( + { + languagePairs: LANGUAGE_PAIRS, + autoDownloadFromRemoteSettings: false, + } + ); + + await aboutTranslationsTestUtils.setSourceLanguageSelectorValue("en"); + await aboutTranslationsTestUtils.setTargetLanguageSelectorValue("fr"); + + await aboutTranslationsTestUtils.assertEvents( + { + expected: [ + [ + AboutTranslationsTestUtils.Events.TranslationRequested, + { translationId: 1 }, + ], + [AboutTranslationsTestUtils.Events.ShowTranslatingPlaceholder], + [AboutTranslationsTestUtils.Events.SourceTextClearButtonShown], + ], + }, + async () => { + await aboutTranslationsTestUtils.setSourceTextAreaValue( + "Clearing in progress" + ); + } + ); + + await aboutTranslationsTestUtils.assertEvents( + { + expected: [ + [AboutTranslationsTestUtils.Events.ClearSourceText], + [AboutTranslationsTestUtils.Events.SourceTextClearButtonHidden], + [AboutTranslationsTestUtils.Events.ClearTargetText], + ], + unexpected: [ + AboutTranslationsTestUtils.Events.CopyButtonEnabled, + AboutTranslationsTestUtils.Events.TranslationRequested, + AboutTranslationsTestUtils.Events.TranslationComplete, + ], + }, + async () => { + await aboutTranslationsTestUtils.clickClearButton(); + } + ); + + await aboutTranslationsTestUtils.assertSourceTextArea({ + showsPlaceholder: true, + }); + await aboutTranslationsTestUtils.assertTargetTextArea({ + showsPlaceholder: true, + }); + await aboutTranslationsTestUtils.assertSourceClearButton({ + visible: false, + }); + + await aboutTranslationsTestUtils.assertEvents( + { + unexpected: [AboutTranslationsTestUtils.Events.TranslationComplete], + }, + async () => { + await aboutTranslationsTestUtils.resolveDownloads(1); + } + ); + + await cleanup(); + } +); diff --git a/toolkit/components/translations/tests/browser/shared-head.js b/toolkit/components/translations/tests/browser/shared-head.js @@ -4188,7 +4188,7 @@ class AboutTranslationsTestUtils { static CopyButtonReset = "AboutTranslationsTest:CopyButtonReset"; /** - * Event fired when the source clear button becomes visible. + * Event fired when the clear button becomes visible. * * @type {string} */ @@ -4196,7 +4196,7 @@ class AboutTranslationsTestUtils { "AboutTranslationsTest:SourceTextClearButtonShown"; /** - * Event fired when the source clear button becomes hidden. + * Event fired when the clear button becomes hidden. * * @type {string} */ @@ -4220,6 +4220,13 @@ class AboutTranslationsTestUtils { "AboutTranslationsTest:TextAreaHeightsChanged"; /** + * Event fired when the source text is cleared programmatically. + * + * @type {string} + */ + static ClearSourceText = "AboutTranslationsTest:ClearSourceText"; + + /** * Event fired when the target text is cleared programmatically. * * @type {string} @@ -5196,7 +5203,7 @@ class AboutTranslationsTestUtils { } /** - * Retrieves the state of the source clear button. + * Retrieves the state of the clear button. * * @returns {Promise<{exists: boolean, hidden: boolean, tabIndex: string | null}>} */ @@ -5224,7 +5231,7 @@ class AboutTranslationsTestUtils { } /** - * Asserts properties of the source clear button. + * Asserts properties of the clear button. * * @param {object} options * @param {boolean} [options.visible=false] @@ -5238,24 +5245,43 @@ class AboutTranslationsTestUtils { tabIndex: actualTabIndex, } = await this.getSourceClearButtonState(); - ok(exists, "Expected source clear button to be present."); + ok(exists, "Expected clear button to be present."); if (visible) { - ok(!hidden, "Expected source clear button to be visible."); + ok(!hidden, "Expected clear button to be visible."); } else { - ok(hidden, "Expected source clear button to be hidden."); + ok(hidden, "Expected clear button to be hidden."); } if (tabIndex !== undefined) { is( actualTabIndex, tabIndex, - `Expected source clear button tabindex to be "${tabIndex}".` + `Expected clear button tabindex to be "${tabIndex}".` ); } } /** + * Clicks the clear button. + * + * @returns {Promise<void>} + */ + async clickClearButton() { + await doubleRaf(document); + try { + await this.#runInPage(selectors => { + const clearButton = content.document.querySelector( + selectors.clearButton + ); + clearButton.click(); + }); + } catch (error) { + AboutTranslationsTestUtils.#reportTestFailure(error); + } + } + + /** * Retrieves the current value of the target textarea. * * @returns {Promise<string>} @@ -5575,11 +5601,7 @@ class AboutTranslationsTestUtils { visibilityMap.sourceSectionTextArea, "source textarea" ); - assertVisibility( - clearButton, - visibilityMap.clearButton, - "source clear button" - ); + assertVisibility(clearButton, visibilityMap.clearButton, "clear button"); assertVisibility( targetSectionTextArea, visibilityMap.targetSectionTextArea,