Skip to content

Commit 67fa2b5

Browse files
committed
Bug 1966542 - Add support for config-based prefs that need to respond to changes in more than one pref r=mstriemer,reusable-components-reviewers
Differential Revision: https://phabricator.services.mozilla.com/D257555
1 parent e096034 commit 67fa2b5

5 files changed

Lines changed: 136 additions & 13 deletions

File tree

browser/components/preferences/tests/chrome/test_setting_control_checkbox.html

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,44 @@
308308
"Nested is disabled after pref change"
309309
);
310310
});
311+
312+
add_task(async function testDepsChangeVisibility() {
313+
const DEP_PREF_ID = "test.depsChange.dep";
314+
const DEP_SETTING_ID = "testDepsChangeDep";
315+
await SpecialPowers.pushPrefEnv({
316+
set: [[DEP_PREF_ID, true]],
317+
});
318+
Preferences.add({ id: DEP_PREF_ID, type: "bool" });
319+
Preferences.addSetting({
320+
id: DEP_SETTING_ID,
321+
pref: DEP_PREF_ID,
322+
});
323+
324+
const PARENT_SETTING_ID = "testDepsChangeParent";
325+
Preferences.addSetting({
326+
id: PARENT_SETTING_ID,
327+
deps: [DEP_SETTING_ID],
328+
visible: deps => deps[DEP_SETTING_ID].value,
329+
});
330+
331+
let itemConfig = { l10nId: LABEL_L10N_ID, id: PARENT_SETTING_ID };
332+
let setting = Preferences.getSetting(PARENT_SETTING_ID);
333+
let control = await renderTemplate(itemConfig, setting);
334+
is(
335+
control.controlEl.localName,
336+
"moz-checkbox",
337+
"The control rendered a checkbox"
338+
);
339+
ok(!control.hidden, "The control is visible");
340+
ok(setting.visible, "Setting is visible initially");
341+
342+
let settingChanged = waitForSettingChange(setting);
343+
Services.prefs.setBoolPref(DEP_PREF_ID, false);
344+
await settingChanged;
345+
346+
ok(!setting.visible, "Setting is not visible based on dep");
347+
ok(control.hidden, "The control is now hidden");
348+
});
311349
</script>
312350
</head>
313351
<body>

browser/components/preferences/widgets/setting-control/setting-control.mjs

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -136,15 +136,21 @@ export class SettingControl extends MozLitElement {
136136
return result;
137137
}
138138

139+
onSettingChange = () => {
140+
this.setValue();
141+
this.requestUpdate();
142+
};
143+
139144
willUpdate(changedProperties) {
140145
if (changedProperties.has("setting")) {
141146
if (this.#lastSetting) {
142-
this.#lastSetting.off("change", this.setValue);
147+
this.#lastSetting.off("change", this.onSettingChange);
143148
}
144149
this.#lastSetting = this.setting;
145150
this.setValue();
146-
this.setting.on("change", this.setValue);
151+
this.setting.on("change", this.onSettingChange);
147152
}
153+
this.hidden = !this.setting.visible;
148154
}
149155

150156
/**
@@ -198,7 +204,6 @@ export class SettingControl extends MozLitElement {
198204
// Called by our parent when our input changed.
199205
onChange(el) {
200206
this.setting.userChange(this.controlValue(el));
201-
this.setValue();
202207
}
203208

204209
render() {
@@ -208,12 +213,10 @@ export class SettingControl extends MozLitElement {
208213

209214
// Prepare nested item config and settings.
210215
let itemArgs =
211-
config.items
212-
?.map(i => ({
213-
config: i,
214-
setting: this.getSetting(i.id),
215-
}))
216-
.filter(i => i.setting.visible) || [];
216+
config.items?.map(i => ({
217+
config: i,
218+
setting: this.getSetting(i.id),
219+
})) || [];
217220
let nestedSettings = itemArgs.map(
218221
opts =>
219222
html`<setting-control

browser/components/preferences/widgets/setting-group/setting-group.mjs

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,9 +40,6 @@ export class SettingGroup extends MozLitElement {
4040

4141
itemTemplate(item) {
4242
let setting = this.getSetting(item.id);
43-
if (!setting.visible) {
44-
return "";
45-
}
4643
return html`<setting-control
4744
.setting=${setting}
4845
.config=${item}

toolkit/content/preferencesBindings.js

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,14 @@
44

55
"use strict";
66

7+
/**
8+
* A map of Setting instances (values) along with their IDs
9+
* (keys) so that the dependencies of a setting can
10+
* be easily looked up by just their ID.
11+
*
12+
* @typedef {Record<string, any>} PreferenceSettingDepsMap
13+
*/
14+
715
// We attach Preferences to the window object so other contexts (tests, JSMs)
816
// have access to it.
917
const Preferences = (window.Preferences = (function () {
@@ -689,11 +697,29 @@ const Preferences = (window.Preferences = (function () {
689697
}
690698

691699
class Setting extends EventEmitter {
700+
/**
701+
* @type {Preference | undefined | null}
702+
*/
703+
pref;
704+
705+
/**
706+
* Keeps a cache of each dep's Setting so that
707+
* it can be easily looked up by its ID.
708+
*
709+
* @type {PreferenceSettingDepsMap | undefined}
710+
*/
711+
_deps;
712+
692713
constructor(id, config) {
693714
super();
694715
this.id = id;
695716
this.config = config;
696717
this.pref = config.pref && Preferences.get(config.pref);
718+
719+
for (const setting of Object.values(this.deps)) {
720+
setting.on("change", this.onChange);
721+
}
722+
697723
if (this.pref) {
698724
this.pref.on("change", this.onChange);
699725
}
@@ -706,6 +732,32 @@ const Preferences = (window.Preferences = (function () {
706732
this.emit("change");
707733
};
708734

735+
/**
736+
* A map of each dep and it's associated {@link Setting} instance.
737+
*
738+
* @type {PreferenceSettingDepsMap}
739+
*/
740+
get deps() {
741+
if (this._deps) {
742+
return this._deps;
743+
}
744+
/**
745+
* @type {PreferenceSettingDepsMap}
746+
*/
747+
const deps = {};
748+
749+
if (this.config.deps) {
750+
for (let id of this.config.deps) {
751+
const setting = Preferences.getSetting(id);
752+
if (setting) {
753+
deps[id] = setting;
754+
}
755+
}
756+
}
757+
this._deps = deps;
758+
return this._deps;
759+
}
760+
709761
get value() {
710762
let prefVal = this.pref?.value;
711763
if (this.config.get) {
@@ -726,7 +778,7 @@ const Preferences = (window.Preferences = (function () {
726778
}
727779

728780
get visible() {
729-
return this.config.visible ? this.config.visible() : true;
781+
return this.config.visible ? this.config.visible(this.deps) : true;
730782
}
731783

732784
getControlConfig(config) {

toolkit/content/tests/chrome/test_preferencesBindings_setting.html

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,39 @@
212212
"There's a default implementation"
213213
);
214214
});
215+
216+
add_task(async function testDepsChange() {
217+
const DEP_PREF_ID = "test.depsChange.dep";
218+
const DEP_SETTING_ID = "testDepsChangeDep";
219+
await SpecialPowers.pushPrefEnv({
220+
set: [[DEP_PREF_ID, true]],
221+
});
222+
Preferences.add({ id: DEP_PREF_ID, type: "bool" });
223+
Preferences.addSetting({
224+
id: DEP_SETTING_ID,
225+
pref: DEP_PREF_ID,
226+
});
227+
228+
const PARENT_SETTING_ID = "testDepsChangeParent";
229+
Preferences.addSetting({
230+
id: PARENT_SETTING_ID,
231+
deps: [DEP_SETTING_ID],
232+
visible: deps => deps[DEP_SETTING_ID].value,
233+
});
234+
235+
const parentSetting = Preferences.getSetting(PARENT_SETTING_ID);
236+
237+
ok(parentSetting.visible, "Parent setting is visible initially");
238+
239+
let settingChanged = waitForSettingChange(parentSetting);
240+
Services.prefs.setBoolPref(DEP_PREF_ID, false);
241+
await settingChanged;
242+
243+
ok(
244+
!parentSetting.visible,
245+
"Parent setting is not visible based on dep"
246+
);
247+
});
215248
</script>
216249
</head>
217250
<body>

0 commit comments

Comments
 (0)