Source code

Revision control

Copy as Markdown

Other Tools

Test Info: Warnings

/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
/* vim: set sts=2 sw=2 et tw=80: */
"use strict";
ChromeUtils.defineESModuleGetters(this, {
ExtensionPreferencesManager:
"resource://gre/modules/ExtensionPreferencesManager.sys.mjs",
ExtensionSettingsStore:
"resource://gre/modules/ExtensionSettingsStore.sys.mjs",
Preferences: "resource://gre/modules/Preferences.sys.mjs",
});
const { createAppInfo, promiseShutdownManager, promiseStartupManager } =
AddonTestUtils;
AddonTestUtils.init(this);
createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "42");
let lastSetPref;
const STORE_TYPE = "prefs";
// Test settings to use with the preferences manager.
const SETTINGS = {
multiple_prefs: {
prefNames: ["my.pref.1", "my.pref.2", "my.pref.3"],
initalValues: ["value1", "value2", "value3"],
valueFn(pref, value) {
return `${pref}-${value}`;
},
setCallback(value) {
let prefs = {};
for (let pref of this.prefNames) {
prefs[pref] = this.valueFn(pref, value);
}
return prefs;
},
},
singlePref: {
prefNames: ["my.single.pref"],
initalValues: ["value1"],
onPrefsChanged(item) {
lastSetPref = item;
},
valueFn(pref, value) {
return value;
},
setCallback(value) {
return { [this.prefNames[0]]: this.valueFn(null, value) };
},
},
};
ExtensionPreferencesManager.addSetting(
"multiple_prefs",
SETTINGS.multiple_prefs
);
ExtensionPreferencesManager.addSetting("singlePref", SETTINGS.singlePref);
// Set initial values for prefs.
for (let setting in SETTINGS) {
setting = SETTINGS[setting];
for (let i = 0; i < setting.prefNames.length; i++) {
Preferences.set(setting.prefNames[i], setting.initalValues[i]);
}
}
function checkPrefs(settingObj, value, msg) {
for (let pref of settingObj.prefNames) {
equal(Preferences.get(pref), settingObj.valueFn(pref, value), msg);
}
}
function checkOnPrefsChanged(setting, value, msg) {
if (value) {
deepEqual(lastSetPref, value, msg);
lastSetPref = null;
} else {
ok(!lastSetPref, msg);
}
}
add_task(async function test_preference_manager() {
await promiseStartupManager();
// Create an array of test framework extension wrappers to install.
let testExtensions = [
ExtensionTestUtils.loadExtension({
useAddonManager: "temporary",
manifest: {},
}),
ExtensionTestUtils.loadExtension({
useAddonManager: "temporary",
manifest: {},
}),
];
for (let extension of testExtensions) {
await extension.startup();
}
// Create an array actual Extension objects which correspond to the
// test framework extension wrappers.
let extensions = testExtensions.map(extension => extension.extension);
for (let setting in SETTINGS) {
let settingObj = SETTINGS[setting];
let newValue1 = "newValue1";
let levelOfControl = await ExtensionPreferencesManager.getLevelOfControl(
extensions[1].id,
setting
);
if (settingObj.onPrefsChanged) {
checkOnPrefsChanged(
setting,
null,
"onPrefsChanged has not been called yet"
);
}
equal(
levelOfControl,
"controllable_by_this_extension",
"getLevelOfControl returns correct levelOfControl with no settings set."
);
let prefsChanged = await ExtensionPreferencesManager.setSetting(
extensions[1].id,
setting,
newValue1
);
ok(prefsChanged, "setSetting returns true when the pref(s) have been set.");
checkPrefs(
settingObj,
newValue1,
"setSetting sets the prefs for the first extension."
);
if (settingObj.onPrefsChanged) {
checkOnPrefsChanged(
setting,
{ id: extensions[1].id, value: newValue1, key: setting },
"onPrefsChanged is called when pref changes"
);
}
levelOfControl = await ExtensionPreferencesManager.getLevelOfControl(
extensions[1].id,
setting
);
equal(
levelOfControl,
"controlled_by_this_extension",
"getLevelOfControl returns correct levelOfControl when a pref has been set."
);
let checkSetting = await ExtensionPreferencesManager.getSetting(setting);
equal(
checkSetting.value,
newValue1,
"getSetting returns the expected value."
);
let newValue2 = "newValue2";
prefsChanged = await ExtensionPreferencesManager.setSetting(
extensions[0].id,
setting,
newValue2
);
ok(
!prefsChanged,
"setSetting returns false when the pref(s) have not been set."
);
checkPrefs(
settingObj,
newValue1,
"setSetting does not set the pref(s) for an earlier extension."
);
if (settingObj.onPrefsChanged) {
checkOnPrefsChanged(
setting,
null,
"onPrefsChanged isn't called without control change"
);
}
prefsChanged = await ExtensionPreferencesManager.disableSetting(
extensions[0].id,
setting
);
ok(
!prefsChanged,
"disableSetting returns false when the pref(s) have not been set."
);
checkPrefs(
settingObj,
newValue1,
"disableSetting does not change the pref(s) for the non-top extension."
);
if (settingObj.onPrefsChanged) {
checkOnPrefsChanged(
setting,
null,
"onPrefsChanged isn't called without control change on disable"
);
}
prefsChanged = await ExtensionPreferencesManager.enableSetting(
extensions[0].id,
setting
);
ok(
!prefsChanged,
"enableSetting returns false when the pref(s) have not been set."
);
checkPrefs(
settingObj,
newValue1,
"enableSetting does not change the pref(s) for the non-top extension."
);
if (settingObj.onPrefsChanged) {
checkOnPrefsChanged(
setting,
null,
"onPrefsChanged isn't called without control change on enable"
);
}
prefsChanged = await ExtensionPreferencesManager.removeSetting(
extensions[0].id,
setting
);
ok(
!prefsChanged,
"removeSetting returns false when the pref(s) have not been set."
);
checkPrefs(
settingObj,
newValue1,
"removeSetting does not change the pref(s) for the non-top extension."
);
if (settingObj.onPrefsChanged) {
checkOnPrefsChanged(
setting,
null,
"onPrefsChanged isn't called without control change on remove"
);
}
prefsChanged = await ExtensionPreferencesManager.setSetting(
extensions[0].id,
setting,
newValue2
);
ok(
!prefsChanged,
"setSetting returns false when the pref(s) have not been set."
);
checkPrefs(
settingObj,
newValue1,
"setSetting does not set the pref(s) for an earlier extension."
);
if (settingObj.onPrefsChanged) {
checkOnPrefsChanged(
setting,
null,
"onPrefsChanged isn't called without control change again"
);
}
prefsChanged = await ExtensionPreferencesManager.disableSetting(
extensions[1].id,
setting
);
ok(
prefsChanged,
"disableSetting returns true when the pref(s) have been set."
);
checkPrefs(
settingObj,
newValue2,
"disableSetting sets the pref(s) to the next value when disabling the top extension."
);
if (settingObj.onPrefsChanged) {
checkOnPrefsChanged(
setting,
{ id: extensions[0].id, key: setting, value: newValue2 },
"onPrefsChanged is called when control changes on disable"
);
}
prefsChanged = await ExtensionPreferencesManager.enableSetting(
extensions[1].id,
setting
);
ok(
prefsChanged,
"enableSetting returns true when the pref(s) have been set."
);
checkPrefs(
settingObj,
newValue1,
"enableSetting sets the pref(s) to the previous value(s)."
);
if (settingObj.onPrefsChanged) {
checkOnPrefsChanged(
setting,
{ id: extensions[1].id, key: setting, value: newValue1 },
"onPrefsChanged is called when control changes on enable"
);
}
prefsChanged = await ExtensionPreferencesManager.removeSetting(
extensions[1].id,
setting
);
ok(
prefsChanged,
"removeSetting returns true when the pref(s) have been set."
);
checkPrefs(
settingObj,
newValue2,
"removeSetting sets the pref(s) to the next value when removing the top extension."
);
if (settingObj.onPrefsChanged) {
checkOnPrefsChanged(
setting,
{ id: extensions[0].id, key: setting, value: newValue2 },
"onPrefsChanged is called when control changes on remove"
);
}
prefsChanged = await ExtensionPreferencesManager.removeSetting(
extensions[0].id,
setting
);
ok(
prefsChanged,
"removeSetting returns true when the pref(s) have been set."
);
if (settingObj.onPrefsChanged) {
checkOnPrefsChanged(
setting,
{ key: setting, initialValue: { "my.single.pref": "value1" } },
"onPrefsChanged is called when control is entirely removed"
);
}
for (let i = 0; i < settingObj.prefNames.length; i++) {
equal(
Preferences.get(settingObj.prefNames[i]),
settingObj.initalValues[i],
"removeSetting sets the pref(s) to the initial value(s) when removing the last extension."
);
}
checkSetting = await ExtensionPreferencesManager.getSetting(setting);
equal(
checkSetting,
null,
"getSetting returns null when nothing has been set."
);
}
// Tests for unsetAll.
let newValue3 = "newValue3";
for (let setting in SETTINGS) {
let settingObj = SETTINGS[setting];
await ExtensionPreferencesManager.setSetting(
extensions[0].id,
setting,
newValue3
);
checkPrefs(settingObj, newValue3, "setSetting set the pref.");
}
let setSettings = await ExtensionSettingsStore.getAllForExtension(
extensions[0].id,
STORE_TYPE
);
deepEqual(
setSettings,
Object.keys(SETTINGS),
"Expected settings were set for extension."
);
await ExtensionPreferencesManager.disableAll(extensions[0].id);
for (let setting in SETTINGS) {
let settingObj = SETTINGS[setting];
for (let i = 0; i < settingObj.prefNames.length; i++) {
equal(
Preferences.get(settingObj.prefNames[i]),
settingObj.initalValues[i],
"disableAll unset the pref."
);
}
}
setSettings = await ExtensionSettingsStore.getAllForExtension(
extensions[0].id,
STORE_TYPE
);
deepEqual(
setSettings,
Object.keys(SETTINGS),
"disableAll retains the settings."
);
await ExtensionPreferencesManager.enableAll(extensions[0].id);
for (let setting in SETTINGS) {
let settingObj = SETTINGS[setting];
checkPrefs(settingObj, newValue3, "enableAll re-set the pref.");
}
await ExtensionPreferencesManager.removeAll(extensions[0].id);
for (let setting in SETTINGS) {
let settingObj = SETTINGS[setting];
for (let i = 0; i < settingObj.prefNames.length; i++) {
equal(
Preferences.get(settingObj.prefNames[i]),
settingObj.initalValues[i],
"removeAll unset the pref."
);
}
}
setSettings = await ExtensionSettingsStore.getAllForExtension(
extensions[0].id,
STORE_TYPE
);
deepEqual(setSettings, [], "removeAll removed all settings.");
// Tests for preventing automatic changes to manually edited prefs.
for (let setting in SETTINGS) {
let apiValue = "newValue";
let manualValue = "something different";
let settingObj = SETTINGS[setting];
let extension = extensions[1];
await ExtensionPreferencesManager.setSetting(
extension.id,
setting,
apiValue
);
let checkResetPrefs = method => {
let prefNames = settingObj.prefNames;
for (let i = 0; i < prefNames.length; i++) {
if (i === 0) {
equal(
Preferences.get(prefNames[0]),
manualValue,
`${method} did not change a manually set pref.`
);
} else {
equal(
Preferences.get(prefNames[i]),
settingObj.valueFn(prefNames[i], apiValue),
`${method} did not change another pref when a pref was manually set.`
);
}
}
};
// Manually set the preference to a different value.
Preferences.set(settingObj.prefNames[0], manualValue);
await ExtensionPreferencesManager.disableAll(extension.id);
checkResetPrefs("disableAll");
await ExtensionPreferencesManager.enableAll(extension.id);
checkResetPrefs("enableAll");
await ExtensionPreferencesManager.removeAll(extension.id);
checkResetPrefs("removeAll");
}
// Test with an uninitialized pref.
let setting = "singlePref";
let settingObj = SETTINGS[setting];
let pref = settingObj.prefNames[0];
let newValue = "newValue";
Preferences.reset(pref);
await ExtensionPreferencesManager.setSetting(
extensions[1].id,
setting,
newValue
);
equal(
Preferences.get(pref),
settingObj.valueFn(pref, newValue),
"Uninitialized pref is set."
);
await ExtensionPreferencesManager.removeSetting(extensions[1].id, setting);
ok(!Preferences.has(pref), "removeSetting removed the pref.");
// Test levelOfControl with a locked pref.
setting = "multiple_prefs";
let prefToLock = SETTINGS[setting].prefNames[0];
Preferences.lock(prefToLock, 1);
ok(Preferences.locked(prefToLock), `Preference ${prefToLock} is locked.`);
let levelOfControl = await ExtensionPreferencesManager.getLevelOfControl(
extensions[1].id,
setting
);
equal(
levelOfControl,
"not_controllable",
"getLevelOfControl returns correct levelOfControl when a pref is locked."
);
for (let extension of testExtensions) {
await extension.unload();
}
await promiseShutdownManager();
});
add_task(async function test_preference_manager_set_when_disabled() {
await promiseStartupManager();
let id = "@set-disabled-pref";
let extension = ExtensionTestUtils.loadExtension({
useAddonManager: "temporary",
manifest: {
browser_specific_settings: { gecko: { id } },
},
});
await extension.startup();
// We test both a default pref and a user-set pref. Get the default
// value off the pref we'll use. We fake the default pref by setting
// a value on it before creating the setting.
Services.prefs.setBoolPref("bar", true);
function isUndefinedPref(pref) {
try {
Services.prefs.getStringPref(pref);
return false;
} catch (e) {
return true;
}
}
ok(isUndefinedPref("foo"), "test pref is not set");
await ExtensionSettingsStore.initialize();
let lastItemChange = Promise.withResolvers();
ExtensionPreferencesManager.addSetting("some-pref", {
prefNames: ["foo", "bar"],
onPrefsChanged(item) {
lastItemChange.resolve(item);
lastItemChange = Promise.withResolvers();
},
setCallback(value) {
return { [this.prefNames[0]]: value, [this.prefNames[1]]: false };
},
});
await ExtensionPreferencesManager.setSetting(id, "some-pref", "my value");
let item = ExtensionSettingsStore.getSetting("prefs", "some-pref");
equal(item.value, "my value", "The value has been set");
equal(
Services.prefs.getStringPref("foo"),
"my value",
"The user pref has been set"
);
equal(
Services.prefs.getBoolPref("bar"),
false,
"The default pref has been set"
);
await ExtensionPreferencesManager.disableSetting(id, "some-pref");
// test that a disabled setting has been returned to the default value. In this
// case the pref is not a default pref, so it will be undefined.
item = ExtensionSettingsStore.getSetting("prefs", "some-pref");
equal(item.value, undefined, "The value is back to default");
equal(item.initialValue.foo, undefined, "The initialValue is correct");
ok(isUndefinedPref("foo"), "user pref is not set");
equal(
Services.prefs.getBoolPref("bar"),
true,
"The default pref has been restored to the default"
);
// test that setSetting() will enable a disabled setting
await ExtensionPreferencesManager.setSetting(id, "some-pref", "new value");
item = ExtensionSettingsStore.getSetting("prefs", "some-pref");
equal(item.value, "new value", "The value is set again");
equal(
Services.prefs.getStringPref("foo"),
"new value",
"The user pref is set again"
);
equal(
Services.prefs.getBoolPref("bar"),
false,
"The default pref has been set again"
);
// Force settings to be serialized and reloaded to mimick what happens
// with settings through a restart of Firefox. Bug 1576266.
await ExtensionSettingsStore._reloadFile(true);
// Now unload the extension to test prefs are reset properly.
let promise = lastItemChange.promise;
await extension.unload();
// Test that the pref is unset when an extension is uninstalled.
item = await promise;
deepEqual(
item,
{ key: "some-pref", initialValue: { bar: true } },
"The value has been reset"
);
ok(isUndefinedPref("foo"), "user pref is not set");
equal(
Services.prefs.getBoolPref("bar"),
true,
"The default pref has been restored to the default"
);
Services.prefs.clearUserPref("bar");
await promiseShutdownManager();
});
add_task(async function test_preference_default_upgraded() {
await promiseStartupManager();
let id = "@upgrade-pref";
let extension = ExtensionTestUtils.loadExtension({
useAddonManager: "temporary",
manifest: {
browser_specific_settings: { gecko: { id } },
},
});
await extension.startup();
// We set the default value for a pref here so it will be
// picked up by EPM.
let defaultPrefs = Services.prefs.getDefaultBranch(null);
defaultPrefs.setStringPref("bar", "initial default");
await ExtensionSettingsStore.initialize();
ExtensionPreferencesManager.addSetting("some-pref", {
prefNames: ["bar"],
setCallback(value) {
return { [this.prefNames[0]]: value };
},
});
await ExtensionPreferencesManager.setSetting(id, "some-pref", "new value");
let item = ExtensionSettingsStore.getSetting("prefs", "some-pref");
equal(item.value, "new value", "The value is set");
defaultPrefs.setStringPref("bar", "new default");
item = ExtensionSettingsStore.getSetting("prefs", "some-pref");
equal(item.value, "new value", "The value is still set");
let prefsChanged = await ExtensionPreferencesManager.removeSetting(
id,
"some-pref"
);
ok(prefsChanged, "pref changed on removal of setting.");
equal(Preferences.get("bar"), "new default", "default value is correct");
await extension.unload();
await promiseShutdownManager();
});
add_task(async function test_preference_select() {
await promiseStartupManager();
let extensionData = {
useAddonManager: "temporary",
manifest: {
browser_specific_settings: { gecko: { id: "@one" } },
},
};
let one = ExtensionTestUtils.loadExtension(extensionData);
await one.startup();
// We set the default value for a pref here so it will be
// picked up by EPM.
let defaultPrefs = Services.prefs.getDefaultBranch(null);
defaultPrefs.setStringPref("bar", "initial default");
await ExtensionSettingsStore.initialize();
ExtensionPreferencesManager.addSetting("some-pref", {
prefNames: ["bar"],
setCallback(value) {
return { [this.prefNames[0]]: value };
},
});
ok(
await ExtensionPreferencesManager.setSetting(
one.id,
"some-pref",
"new value"
),
"setting was changed"
);
let item = await ExtensionPreferencesManager.getSetting("some-pref");
equal(item.value, "new value", "The value is set");
// User-set the setting.
await ExtensionPreferencesManager.selectSetting(null, "some-pref");
item = await ExtensionPreferencesManager.getSetting("some-pref");
deepEqual(
item,
{ key: "some-pref", initialValue: {} },
"The value is user-set"
);
// Extensions installed before cannot gain control again.
let levelOfControl = await ExtensionPreferencesManager.getLevelOfControl(
one.id,
"some-pref"
);
equal(
levelOfControl,
"not_controllable",
"getLevelOfControl returns correct levelOfControl when user-set."
);
// Enabling the top-precedence addon does not take over a user-set setting.
await ExtensionPreferencesManager.disableSetting(one.id, "some-pref");
await ExtensionPreferencesManager.enableSetting(one.id, "some-pref");
item = await ExtensionPreferencesManager.getSetting("some-pref");
deepEqual(
item,
{ key: "some-pref", initialValue: {} },
"The value is user-set"
);
// Upgrading does not override the user-set setting.
extensionData.manifest.version = "2.0";
extensionData.manifest.incognito = "not_allowed";
await one.upgrade(extensionData);
levelOfControl = await ExtensionPreferencesManager.getLevelOfControl(
one.id,
"some-pref"
);
equal(
levelOfControl,
"not_controllable",
"getLevelOfControl returns correct levelOfControl when user-set after addon upgrade."
);
// We can re-select the extension.
await ExtensionPreferencesManager.selectSetting(one.id, "some-pref");
item = await ExtensionPreferencesManager.getSetting("some-pref");
deepEqual(item.value, "new value", "The value is extension set");
// An extension installed after user-set can take over the setting.
await ExtensionPreferencesManager.selectSetting(null, "some-pref");
item = await ExtensionPreferencesManager.getSetting("some-pref");
deepEqual(
item,
{ key: "some-pref", initialValue: {} },
"The value is user-set"
);
let two = ExtensionTestUtils.loadExtension({
useAddonManager: "temporary",
manifest: {
browser_specific_settings: { gecko: { id: "@two" } },
},
});
await two.startup();
levelOfControl = await ExtensionPreferencesManager.getLevelOfControl(
two.id,
"some-pref"
);
equal(
levelOfControl,
"controllable_by_this_extension",
"getLevelOfControl returns correct levelOfControl when user-set after addon install."
);
await ExtensionPreferencesManager.setSetting(
two.id,
"some-pref",
"another value"
);
item = ExtensionSettingsStore.getSetting("prefs", "some-pref");
equal(item.value, "another value", "The value is set");
// A new installed extension can override a user selected extension.
let three = ExtensionTestUtils.loadExtension({
useAddonManager: "temporary",
manifest: {
browser_specific_settings: { gecko: { id: "@three" } },
},
});
// user selects specific extension to take control
await ExtensionPreferencesManager.selectSetting(one.id, "some-pref");
// two cannot control
levelOfControl = await ExtensionPreferencesManager.getLevelOfControl(
two.id,
"some-pref"
);
equal(
levelOfControl,
"not_controllable",
"getLevelOfControl returns correct levelOfControl when user-set after addon install."
);
// three can control after install
await three.startup();
levelOfControl = await ExtensionPreferencesManager.getLevelOfControl(
three.id,
"some-pref"
);
equal(
levelOfControl,
"controllable_by_this_extension",
"getLevelOfControl returns correct levelOfControl when user-set after addon install."
);
await ExtensionPreferencesManager.setSetting(
three.id,
"some-pref",
"third value"
);
item = ExtensionSettingsStore.getSetting("prefs", "some-pref");
equal(item.value, "third value", "The value is set");
// We have returned to precedence based settings.
await ExtensionPreferencesManager.removeSetting(three.id, "some-pref");
await ExtensionPreferencesManager.removeSetting(two.id, "some-pref");
item = await ExtensionPreferencesManager.getSetting("some-pref");
equal(item.value, "new value", "The value is extension set");
await one.unload();
await two.unload();
await three.unload();
await promiseShutdownManager();
});
add_task(async function test_preference_select() {
let prefNames = await ExtensionPreferencesManager.getManagedPrefDetails();
// Just check a subset of settings that are in this test file.
Assert.ok(prefNames.size > 0, "some prefs exist");
for (let settingName in SETTINGS) {
let setting = SETTINGS[settingName];
for (let prefName of setting.prefNames) {
Assert.equal(
prefNames.get(prefName),
settingName,
"setting retrieved prefNames"
);
}
}
});