diff options
Diffstat (limited to 'toolkit/components/extensions/test/xpcshell/test_ext_extensionPreferencesManager.js')
-rw-r--r-- | toolkit/components/extensions/test/xpcshell/test_ext_extensionPreferencesManager.js | 877 |
1 files changed, 877 insertions, 0 deletions
diff --git a/toolkit/components/extensions/test/xpcshell/test_ext_extensionPreferencesManager.js b/toolkit/components/extensions/test/xpcshell/test_ext_extensionPreferencesManager.js new file mode 100644 index 0000000000..2f6e3dbe14 --- /dev/null +++ b/toolkit/components/extensions/test/xpcshell/test_ext_extensionPreferencesManager.js @@ -0,0 +1,877 @@ +/* -*- 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", +}); + +var { PromiseUtils } = ChromeUtils.importESModule( + "resource://gre/modules/PromiseUtils.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 = PromiseUtils.defer(); + ExtensionPreferencesManager.addSetting("some-pref", { + prefNames: ["foo", "bar"], + onPrefsChanged(item) { + lastItemChange.resolve(item); + lastItemChange = PromiseUtils.defer(); + }, + 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" + ); + } + } +}); |