tor-browser

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

commit f62d1cfded97d1145f26e5b61f5077d3d4fe6d2f
parent 4d0bfd13bc758feddb3c76c28ecc4465028785a6
Author: Tim Giles <tgiles@mozilla.com>
Date:   Tue,  7 Oct 2025 15:58:31 +0000

Bug 1984122 - Hide setting-group if child controls are hidden. r=desktop-theme-reviewers,hjones

This change introduces the ability to hide a group if all the controls
in that group are hidden. If a setting-group is hidden, then the closest
groupbox will also be hidden using `display: none`. We cannot use
`.hidden` on these top level groupboxes because the show/hide pane code
will modify the `.hidden` attribute.

This change is implemented using the "visibility-change" custom event.
This event is fired by SettingControl and is handled by SettingGroup.
When a SettingGroup's controlEls are all hidden, then the group will
hide itself and its closest groupbox element. Once the SettingGroup
provides its own card wrapper element, we can remove the closest
groupbox hiding logic.

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

Diffstat:
Mbrowser/components/preferences/main.js | 1+
Mbrowser/components/preferences/tests/chrome/test_setting_group.html | 94++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------
Mbrowser/components/preferences/widgets/setting-control/setting-control.mjs | 9++++++++-
Mbrowser/components/preferences/widgets/setting-group/setting-group.mjs | 25+++++++++++++++++++++++++
4 files changed, 120 insertions(+), 9 deletions(-)

diff --git a/browser/components/preferences/main.js b/browser/components/preferences/main.js @@ -406,6 +406,7 @@ Preferences.addSetting({ id: "useSmoothScrolling", pref: "general.smoothScroll", }); + Preferences.addSetting({ id: "useOverlayScrollbars", pref: "widget.gtk.overlay-scrollbars.enabled", diff --git a/browser/components/preferences/tests/chrome/test_setting_group.html b/browser/components/preferences/tests/chrome/test_setting_group.html @@ -33,6 +33,8 @@ 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` @@ -60,12 +62,14 @@ }); MozXULElement.insertFTLIfNeeded("branding/brand.ftl"); MozXULElement.insertFTLIfNeeded("browser/preferences/preferences.ftl"); + Preferences.addAll([ + { id: PREF_ONE, type: "bool" }, + { id: PREF_TWO, type: "bool" }, + ]); }); add_task(async function testSettingGroupSimple() { - const PREF_ONE = "test.settings-group.itemone"; const SETTING_ONE = "setting-item-one"; - const PREF_TWO = "test.settings-group.itemtwo"; const SETTING_TWO = "setting-item-two"; await SpecialPowers.pushPrefEnv({ set: [ @@ -73,10 +77,6 @@ [PREF_TWO, false], ], }); - Preferences.addAll([ - { id: PREF_ONE, type: "bool" }, - { id: PREF_TWO, type: "bool" }, - ]); Preferences.addSetting({ id: SETTING_ONE, pref: PREF_ONE, @@ -113,10 +113,89 @@ SETTING_TWO, "Second setting-control id is correct" ); + await SpecialPowers.popPrefEnv(); + }); + + add_task(async function testSettingGroupVisibility() { + const SETTING_ONE = "setting-item-one"; + const SETTING_TWO = "setting-item-two"; + await SpecialPowers.pushPrefEnv({ + set: [ + [PREF_ONE, false], + [PREF_TWO, false], + ], + }); + Preferences.addSetting({ + id: SETTING_ONE, + pref: PREF_ONE, + visible: (_, setting) => setting.value, + }); + Preferences.addSetting({ + id: SETTING_TWO, + pref: PREF_TWO, + visible: (_, setting) => setting.value, + }); + let config = { + l10nId: GROUP_L10N_ID, + items: [ + { l10nId: LABEL_L10N_ID, id: SETTING_ONE }, + { l10nId: LABEL_L10N_ID, id: SETTING_TWO }, + ], + }; + + 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"); + + 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" + ); + + await TestUtils.waitForCondition( + () => group.hidden, + "Group should be hidden now that the controls are hidden" + ); }); add_task(async function testSettingGroupCallbacks() { - const PREF_ONE = "test.settings-group.first"; const SETTING_ONE = "setting-first"; const SETTING_TWO = "setting-second"; @@ -126,7 +205,6 @@ await SpecialPowers.pushPrefEnv({ set: [[PREF_ONE, true]], }); - Preferences.addAll([{ id: PREF_ONE, type: "bool" }]); Preferences.addSetting({ id: SETTING_ONE, pref: PREF_ONE, diff --git a/browser/components/preferences/widgets/setting-control/setting-control.mjs b/browser/components/preferences/widgets/setting-control/setting-control.mjs @@ -180,7 +180,7 @@ export class SettingControl extends MozLitElement { async getUpdateComplete() { let result = await super.getUpdateComplete(); - await this.controlEl.updateComplete; + await this.controlEl?.updateComplete; return result; } @@ -201,9 +201,16 @@ export class SettingControl extends MozLitElement { this.setValue(); this.setting.on("change", this.onSettingChange); } + let prevHidden = this.hidden; this.hidden = !this.setting.visible; + if (prevHidden != this.hidden) { + this.dispatchEvent(new Event("visibility-change", { bubbles: true })); + } } + /** + * @type {MozLitElement['updated']} + */ updated() { const control = this.controlRef?.value; if (!control) { diff --git a/browser/components/preferences/widgets/setting-group/setting-group.mjs b/browser/components/preferences/widgets/setting-group/setting-group.mjs @@ -46,6 +46,30 @@ export class SettingGroup extends MozLitElement { return this; } + async handleVisibilityChange() { + await this.updateComplete; + let visibleControls = [...this.controlEls].filter(el => !el.hidden); + if (!visibleControls.length) { + this.hidden = true; + } 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 = ""; + } + } + async getUpdateComplete() { let result = await super.getUpdateComplete(); await Promise.all([...this.controlEls].map(el => el.updateComplete)); @@ -94,6 +118,7 @@ export class SettingGroup extends MozLitElement { .supportPage=${ifDefined(this.config.supportPage)} @change=${this.onChange} @click=${this.onClick} + @visibility-change=${this.handleVisibilityChange} >${this.config.items.map(item => this.itemTemplate(item))}</moz-fieldset >`; }