tor-browser

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

commit 0a5135c08676253cf8c61526e56d8a92c7bfcfa6
parent e6104507fe3bd2fbbbf09915e36d5cd5f108ff70
Author: Mark Striemer <mstriemer@mozilla.com>
Date:   Thu, 13 Nov 2025 17:12:22 +0000

Bug 1993571 - Hide a standalone setting-group if it has no visible settings r=tgiles

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

Diffstat:
Mbrowser/components/preferences/tests/browser_bug731866.js | 7+++++++
Mbrowser/components/preferences/tests/browser_bug795764_cachedisabled.js | 6++++++
Mbrowser/components/preferences/tests/chrome/test_setting_group.html | 191++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---------------------
Mbrowser/components/preferences/widgets/setting-group/setting-group.mjs | 34++++++++++++++++------------------
4 files changed, 169 insertions(+), 69 deletions(-)

diff --git a/browser/components/preferences/tests/browser_bug731866.js b/browser/components/preferences/tests/browser_bug731866.js @@ -79,6 +79,7 @@ function checkElements(expectedPane) { // Profiles is only enabled in Nightly by default (bug 1947633) if (element.id === "profilesGroup" && profilesGroupDisabled) { + is_element_hidden(element, "Disabled profilesGroup should be hidden"); continue; } @@ -87,6 +88,12 @@ function checkElements(expectedPane) { element.id === "dataIPProtectionGroup" && ipProtectionExperiment !== "beta" ) { + is_element_hidden(element, "Disabled ipProtection should be hidden"); + continue; + } + + if (element.getAttribute("data-hidden-from-search") == "true") { + is_element_hidden(element, "Hidden from search element should be hidden"); continue; } diff --git a/browser/components/preferences/tests/browser_bug795764_cachedisabled.js b/browser/components/preferences/tests/browser_bug795764_cachedisabled.js @@ -49,6 +49,12 @@ async function runTest(win) { element.id === "dataIPProtectionGroup" && ipProtectionExperiment !== "beta" ) { + is_element_hidden(element, "Disabled ipProtection should be hidden"); + continue; + } + + if (element.getAttribute("data-hidden-from-search") == "true") { + is_element_hidden(element, "Hidden from search element should be hidden"); continue; } diff --git a/browser/components/preferences/tests/chrome/test_setting_group.html b/browser/components/preferences/tests/chrome/test_setting_group.html @@ -30,20 +30,24 @@ ></script> <script> /* import-globals-from /toolkit/content/preferencesBindings.js */ - let html, testHelpers; + let html, nothing, testHelpers; const LABEL_L10N_ID = "browsing-use-autoscroll"; const GROUP_L10N_ID = "pane-experimental-reset"; const PREF_ONE = "test.settings-group.itemone"; const PREF_TWO = "test.settings-group.itemtwo"; - function renderTemplate(config) { - return testHelpers.renderTemplate(html` + function settingGroupTemplate(config) { + return html` <setting-group .config=${config} .getSetting=${(...args) => Preferences.getSetting(...args)} ></setting-group> - `); + `; + } + + function renderTemplate(config) { + return testHelpers.renderTemplate(settingGroupTemplate(config)); } function waitForSettingChange(setting) { @@ -57,7 +61,7 @@ add_setup(async function setup() { testHelpers = new InputTestHelpers(); - ({ html } = await testHelpers.setupLit()); + ({ html, nothing } = await testHelpers.setupLit()); testHelpers.setupTests({ templateFn: () => html`<setting-group></setting-group>`, }); @@ -118,6 +122,124 @@ }); add_task(async function testSettingGroupVisibility() { + /** + * Wait for a requestAnimationFrame so lit renders have completed. + */ + function waitForRenderUpdate() { + return new Promise(r => requestAnimationFrame(r)); + } + + /** + * Render the common template into a groupbox that may be hidden from search. + */ + async function renderTemplateInGroupbox( + config, + hiddenFromSearch = false + ) { + return testHelpers.renderTemplate(html` + <groupbox + data-hidden-from-search=${hiddenFromSearch ? "true" : nothing} + ?hidden=${hiddenFromSearch} + > + ${settingGroupTemplate(config)} + </groupbox> + `); + } + + async function assertVisibilityChanges(group, staysHidden = false) { + await waitForRenderUpdate(); + ok(group, "setting-group is created"); + let [control1, control2] = group.querySelectorAll("setting-control"); + is(control1.hidden, true, "First setting-control should be hidden"); + is(control2.hidden, true, "Second setting-control should be hidden"); + + ok( + group.hidden, + "setting-group should be hidden since its controls are hidden" + ); + is( + group.getAttribute("data-hidden-from-search"), + "true", + "setting-group is hidden from search when hidden" + ); + info(group.hasAttribute("data-hidden-by-setting-group")); + + Services.prefs.setBoolPref(PREF_ONE, true); + await waitForRenderUpdate(); + if (staysHidden) { + ok(BrowserTestUtils.isHidden(group), "Group should stay hidden"); + is( + group.getAttribute("data-hidden-from-search"), + "true", + "setting-group is hidden from search when hidden" + ); + } else { + ok( + !control1.hidden, + "Control 1 should be visible after changing pref value" + ); + ok( + !group.hidden, + "Group should be visible since one of the controls is visible" + ); + ok( + !group.hasAttribute("data-hidden-from-search"), + "setting-group is not hidden from search when visible" + ); + is( + control2.hidden, + true, + "The second setting-control should still be hidden" + ); + } + Services.prefs.setBoolPref(PREF_TWO, true); + await waitForRenderUpdate(); + if (staysHidden) { + ok( + BrowserTestUtils.isHidden(group), + "Group should still stay hidden" + ); + is( + group.getAttribute("data-hidden-from-search"), + "true", + "setting-group is hidden from search when hidden" + ); + } else { + ok( + !control2.hidden, + "Control 2 should be visible after changing pref value" + ); + ok(!control1.hidden, "Control 1 should still be visible"); + ok(!group.hidden, "Group should still be visible"); + ok( + !group.hasAttribute("data-hidden-from-search"), + "setting-group is not hidden from search when visible" + ); + } + + Services.prefs.setBoolPref(PREF_ONE, false); + Services.prefs.setBoolPref(PREF_TWO, false); + + await waitForRenderUpdate(); + ok( + control1.hidden, + "Control 1 should be hidden after changing pref value" + ); + ok( + control2.hidden, + "Control 2 should be hidden after changing pref value" + ); + ok( + group.hidden, + "Group should be hidden now that the controls are hidden" + ); + is( + group.getAttribute("data-hidden-from-search"), + "true", + "setting-group is hidden from search when hidden" + ); + } + const SETTING_ONE = "setting-item-one"; const SETTING_TWO = "setting-item-two"; await SpecialPowers.pushPrefEnv({ @@ -144,56 +266,23 @@ ], }; + // Check without a groupbox let result = await renderTemplate(config); let group = result.querySelector("setting-group"); - ok(group, "setting-group is created"); - let [control1, control2] = group.children[0].children; - is(control1.hidden, true, "First setting-control should be hidden"); - is(control2.hidden, true, "Second setting-control should be hidden"); + info("-- assertVisibilityChanges setting-group --"); + await assertVisibilityChanges(group); - ok( - group.hidden, - "setting-group should be hidden since its controls are hidden" - ); - - Services.prefs.setBoolPref(PREF_ONE, true); - await TestUtils.waitForCondition( - () => !control1.hidden, - "Control 1 should be visible after changing pref value" - ); - await TestUtils.waitForCondition( - () => !group.hidden, - "Group should be visible since one of the controls is visible" - ); - is( - control2.hidden, - true, - "The second setting-control should still be hidden" - ); - Services.prefs.setBoolPref(PREF_TWO, true); - await TestUtils.waitForCondition( - () => !control2.hidden, - "Control 2 should be visible after changing pref value" - ); - ok(!control1.hidden, "Control 1 should still be visible"); - ok(!group.hidden, "Group should still be visible"); - - Services.prefs.setBoolPref(PREF_ONE, false); - Services.prefs.setBoolPref(PREF_TWO, false); - - await TestUtils.waitForCondition( - () => control1.hidden, - "Control 1 should be hidden after changing pref value" - ); - await TestUtils.waitForCondition( - () => control2.hidden, - "Control 2 should be hidden after changing pref value" - ); + // Check with an initially visible groupbox + result = await renderTemplateInGroupbox(config); + group = result.querySelector("groupbox"); + info("-- assertVisibilityChanges groupbox --"); + await assertVisibilityChanges(group); - await TestUtils.waitForCondition( - () => group.hidden, - "Group should be hidden now that the controls are hidden" - ); + // Check with a groupbox that should stay hidden + result = await renderTemplateInGroupbox(config, true); + group = result.querySelector("groupbox"); + info("-- assertVisibilityChanges groupbox[data-hidden-from-search] --"); + await assertVisibilityChanges(group, true); }); add_task(async function testCommonControlProperties() { diff --git a/browser/components/preferences/widgets/setting-group/setting-group.mjs b/browser/components/preferences/widgets/setting-group/setting-group.mjs @@ -51,25 +51,23 @@ export class SettingGroup extends SettingElement { async handleVisibilityChange() { await this.updateComplete; - let visibleControls = [...this.controlEls].filter(el => !el.hidden); - if (!visibleControls.length) { - this.hidden = true; + let hasVisibleControls = [...this.controlEls].some(el => !el.hidden); + this.hidden = !hasVisibleControls; + let groupbox = this.closest("groupbox"); + if (hasVisibleControls) { + this.removeAttribute("data-hidden-from-search"); + if (groupbox && groupbox.hasAttribute("data-hidden-by-setting-group")) { + groupbox.removeAttribute("data-hidden-from-search"); + groupbox.removeAttribute("data-hidden-by-setting-group"); + groupbox.hidden = false; + } } else { - this.hidden = false; - } - // FIXME: We need to replace this.closest() once the SettingGroup - // provides its own card wrapper/groupbox replacement element. - let closestGroupbox = this.closest("groupbox"); - if (!closestGroupbox) { - return; - } - if (this.hidden) { - // Can't rely on .hidden for the toplevel groupbox because - // of the pane hiding/showing code potentially changing the - // hidden attribute. - closestGroupbox.style.display = "none"; - } else { - closestGroupbox.style.display = ""; + this.setAttribute("data-hidden-from-search", "true"); + if (groupbox && !groupbox.hasAttribute("data-hidden-from-search")) { + groupbox.setAttribute("data-hidden-from-search", "true"); + groupbox.setAttribute("data-hidden-by-setting-group", ""); + groupbox.hidden = true; + } } }