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:
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;
+ }
}
}