tor-browser

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

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

Bug 1992233 - Part 2/3: Implement Clear Butotn Visibility r=translations-reviewers,gregtatum

This commit introduces the logic for when the source-section clear button
should be visible or hidden on the about:translations page.

At this point, the button has no functionality when clicked.
This will be added in the next commit.

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

Diffstat:
Mtoolkit/components/translations/content/about-translations.html | 1+
Mtoolkit/components/translations/content/about-translations.mjs | 37+++++++++++++++++++++++++++++++++++++
Mtoolkit/components/translations/tests/browser/browser.toml | 2++
Atoolkit/components/translations/tests/browser/browser_about_translations_source_clear_button_visibility.js | 96+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Mtoolkit/components/translations/tests/browser/shared-head.js | 97+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
5 files changed, 233 insertions(+), 0 deletions(-)

diff --git a/toolkit/components/translations/content/about-translations.html b/toolkit/components/translations/content/about-translations.html @@ -126,6 +126,7 @@ type="ghost icon" data-l10n-id="about-translations-clear-button" tabindex="-1" + hidden ></moz-button> </div> </div> diff --git a/toolkit/components/translations/content/about-translations.mjs b/toolkit/components/translations/content/about-translations.mjs @@ -320,6 +320,7 @@ class AboutTranslations { this.#updateTargetScriptDirection(); this.#showMainUserInterface(); + this.#updateSourceSectionClearButtonVisibility(); this.#ensureSectionHeightsMatch({ scheduleCallback: false }); this.#setInitialFocus(); } @@ -457,6 +458,7 @@ class AboutTranslations { * Handles input events on the source textarea. */ #onSourceTextInput = () => { + this.#updateSourceSectionClearButtonVisibility(); this.#maybeRequestTranslation(); }; @@ -968,6 +970,7 @@ class AboutTranslations { this.#updateSourceScriptDirection(); this.#updateTargetScriptDirection(); this.#ensureSectionHeightsMatch({ scheduleCallback: false }); + this.#updateSourceSectionClearButtonVisibility(); if (sourceSectionTextArea.value) { this.#displayTranslatingPlaceholder(); @@ -1016,6 +1019,40 @@ class AboutTranslations { } /** + * Returns true if the source textarea contains any text, otherwise false. + * + * @returns {boolean} + */ + #sourceTextAreaHasValue() { + return Boolean(this.elements.sourceSectionTextArea.value); + } + + /** + * Shows or hides the source clear button based on whether the textarea has text. + */ + #updateSourceSectionClearButtonVisibility() { + const { sourceSectionClearButton } = this.elements; + + const shouldShow = this.#sourceTextAreaHasValue(); + const isHidden = sourceSectionClearButton.hidden; + + const shouldHide = !shouldShow; + const isShown = !isHidden; + + if (shouldShow && isHidden) { + sourceSectionClearButton.hidden = false; + document.dispatchEvent( + new CustomEvent("AboutTranslationsTest:SourceTextClearButtonShown") + ); + } else if (shouldHide && isShown) { + sourceSectionClearButton.hidden = true; + document.dispatchEvent( + new CustomEvent("AboutTranslationsTest:SourceTextClearButtonHidden") + ); + } + } + + /** * Sets the value of the source <textarea> and dispatches an input event. * * @param {string} value 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_visibility.js"] + ["browser_about_translations_swap_languages_button_detected.js"] ["browser_about_translations_swap_languages_button_explicit.js"] diff --git a/toolkit/components/translations/tests/browser/browser_about_translations_source_clear_button_visibility.js b/toolkit/components/translations/tests/browser/browser_about_translations_source_clear_button_visibility.js @@ -0,0 +1,96 @@ +/* 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_shows_and_hides_with_input() { + const { aboutTranslationsTestUtils, cleanup } = await openAboutTranslations({ + languagePairs: LANGUAGE_PAIRS, + }); + + await aboutTranslationsTestUtils.assertSourceClearButton({ + visible: false, + }); + + await aboutTranslationsTestUtils.assertEvents( + { + expected: [ + [AboutTranslationsTestUtils.Events.SourceTextClearButtonShown], + ], + unexpected: [ + AboutTranslationsTestUtils.Events.SourceTextClearButtonHidden, + ], + }, + async () => { + await aboutTranslationsTestUtils.setSourceTextAreaValue("Hello world"); + } + ); + + await aboutTranslationsTestUtils.assertSourceClearButton({ + visible: true, + }); + + await aboutTranslationsTestUtils.assertEvents( + { + expected: [ + [AboutTranslationsTestUtils.Events.SourceTextClearButtonHidden], + ], + unexpected: [ + AboutTranslationsTestUtils.Events.SourceTextClearButtonShown, + ], + }, + async () => { + await aboutTranslationsTestUtils.setSourceTextAreaValue(""); + } + ); + + await aboutTranslationsTestUtils.assertSourceClearButton({ + visible: false, + }); + + await cleanup(); +}); + +add_task( + async function test_source_clear_button_remains_visible_for_whitespace_only_values() { + const { aboutTranslationsTestUtils, cleanup } = await openAboutTranslations( + { languagePairs: LANGUAGE_PAIRS } + ); + + await aboutTranslationsTestUtils.assertSourceClearButton({ + visible: false, + }); + + await aboutTranslationsTestUtils.assertEvents( + { + expected: [ + [AboutTranslationsTestUtils.Events.SourceTextClearButtonShown], + ], + }, + async () => { + await aboutTranslationsTestUtils.setSourceTextAreaValue("Text"); + } + ); + + await aboutTranslationsTestUtils.assertSourceClearButton({ + visible: true, + }); + + await aboutTranslationsTestUtils.assertEvents( + { + unexpected: [ + AboutTranslationsTestUtils.Events.SourceTextClearButtonHidden, + ], + }, + async () => { + await aboutTranslationsTestUtils.setSourceTextAreaValue(" "); + } + ); + + await aboutTranslationsTestUtils.assertSourceClearButton({ + visible: true, + }); + + await cleanup(); + } +); diff --git a/toolkit/components/translations/tests/browser/shared-head.js b/toolkit/components/translations/tests/browser/shared-head.js @@ -210,6 +210,7 @@ async function openAboutTranslations({ swapLanguagesButton: "moz-button#about-translations-swap-languages-button", sourceSectionTextArea: "textarea#about-translations-source-textarea", targetSectionTextArea: "textarea#about-translations-target-textarea", + clearButton: "moz-button#about-translations-clear-button", copyButton: "moz-button#about-translations-copy-button", unsupportedInfoMessage: "moz-message-bar#about-translations-unsupported-info-message", @@ -4187,6 +4188,22 @@ class AboutTranslationsTestUtils { static CopyButtonReset = "AboutTranslationsTest:CopyButtonReset"; /** + * Event fired when the source clear button becomes visible. + * + * @type {string} + */ + static SourceTextClearButtonShown = + "AboutTranslationsTest:SourceTextClearButtonShown"; + + /** + * Event fired when the source clear button becomes hidden. + * + * @type {string} + */ + static SourceTextClearButtonHidden = + "AboutTranslationsTest:SourceTextClearButtonHidden"; + + /** * Event fired when the page layout changes. * * @type {string} @@ -5179,6 +5196,66 @@ class AboutTranslationsTestUtils { } /** + * Retrieves the state of the source clear button. + * + * @returns {Promise<{exists: boolean, hidden: boolean, tabIndex: string | null}>} + */ + async getSourceClearButtonState() { + await doubleRaf(document); + + try { + return await this.#runInPage(selectors => { + const button = content.document.querySelector(selectors.clearButton); + return { + exists: !!button, + hidden: button?.hasAttribute("hidden") ?? true, + tabIndex: button?.getAttribute("tabindex") ?? null, + }; + }); + } catch (error) { + AboutTranslationsTestUtils.#reportTestFailure(error); + } + + return { + exists: false, + hidden: true, + tabIndex: null, + }; + } + + /** + * Asserts properties of the source clear button. + * + * @param {object} options + * @param {boolean} [options.visible=false] + * @param {string} [options.tabIndex="-1"] + * @returns {Promise<void>} + */ + async assertSourceClearButton({ visible = false, tabIndex = "-1" } = {}) { + const { + exists, + hidden, + tabIndex: actualTabIndex, + } = await this.getSourceClearButtonState(); + + ok(exists, "Expected source clear button to be present."); + + if (visible) { + ok(!hidden, "Expected source clear button to be visible."); + } else { + ok(hidden, "Expected source clear button to be hidden."); + } + + if (tabIndex !== undefined) { + is( + actualTabIndex, + tabIndex, + `Expected source clear button tabindex to be "${tabIndex}".` + ); + } + } + + /** * Retrieves the current value of the target textarea. * * @returns {Promise<string>} @@ -5380,6 +5457,8 @@ class AboutTranslationsTestUtils { * @param {boolean} [options.mainUserInterface=false] * @param {boolean} [options.sourceLanguageSelector=false] * @param {boolean} [options.targetLanguageSelector=false] + * @param {boolean} [options.clearButton=undefined] + * The clear button visibility is automatically determined from source text if undefined. * @param {boolean} [options.copyButton=false] * @param {boolean} [options.swapLanguagesButton=false] * @param {boolean} [options.sourceSectionTextArea=false] @@ -5393,6 +5472,7 @@ class AboutTranslationsTestUtils { mainUserInterface = false, sourceLanguageSelector = false, targetLanguageSelector = false, + clearButton = undefined, copyButton = false, swapLanguagesButton = false, sourceSectionTextArea = false, @@ -5404,6 +5484,16 @@ class AboutTranslationsTestUtils { await doubleRaf(document); try { + if (clearButton === undefined) { + const sourceTextAreaValue = await this.#runInPage(selectors => { + const sourceTextArea = content.document.querySelector( + selectors.sourceSectionTextArea + ); + return sourceTextArea?.value ?? ""; + }); + clearButton = Boolean(sourceTextAreaValue.trim()); + } + const visibilityMap = await this.#runInPage(selectors => { const { document, window } = content; const isElementVisible = selector => { @@ -5420,6 +5510,7 @@ class AboutTranslationsTestUtils { const { display, visibility } = computedStyle; return !(display === "none" || visibility === "hidden"); }; + return { pageHeader: isElementVisible(selectors.pageHeader), mainUserInterface: isElementVisible(selectors.mainUserInterface), @@ -5429,6 +5520,7 @@ class AboutTranslationsTestUtils { targetLanguageSelector: isElementVisible( selectors.targetLanguageSelector ), + clearButton: isElementVisible(selectors.clearButton), copyButton: isElementVisible(selectors.copyButton), swapLanguagesButton: isElementVisible(selectors.swapLanguagesButton), sourceSectionTextArea: isElementVisible( @@ -5484,6 +5576,11 @@ class AboutTranslationsTestUtils { "source textarea" ); assertVisibility( + clearButton, + visibilityMap.clearButton, + "source clear button" + ); + assertVisibility( targetSectionTextArea, visibilityMap.targetSectionTextArea, "target textarea"