зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1341277 - Part 1: Update ExtensionSettingsStore to support disabled settings. r=aswan
MozReview-Commit-ID: 4N67JXfO81D --HG-- extra : rebase_source : 12d4ad1ede515f57d7256899e197e83e129877a0
This commit is contained in:
Родитель
5716d510ff
Коммит
767ea1e9b1
|
@ -24,7 +24,8 @@
|
|||
* {
|
||||
* id, // The id of the extension requesting the setting.
|
||||
* installDate, // The install date of the extension.
|
||||
* value // The value of the setting requested by the extension.
|
||||
* value, // The value of the setting requested by the extension.
|
||||
* enabled // Whether the setting is currently enabled.
|
||||
* }
|
||||
* ],
|
||||
* },
|
||||
|
@ -57,14 +58,14 @@ const STORE_PATH = OS.Path.join(Services.dirsvc.get("ProfD", Ci.nsIFile).path, J
|
|||
let _store;
|
||||
|
||||
// Get the internal settings store, which is persisted in a JSON file.
|
||||
async function getStore(type) {
|
||||
function getStore(type) {
|
||||
if (!_store) {
|
||||
_store = new JSONFile({
|
||||
let initStore = new JSONFile({
|
||||
path: STORE_PATH,
|
||||
});
|
||||
await _store.load();
|
||||
initStore.ensureDataReady();
|
||||
_store = initStore;
|
||||
}
|
||||
_store.ensureDataReady();
|
||||
|
||||
// Ensure a property exists for the given type.
|
||||
if (!_store.data[type]) {
|
||||
|
@ -77,18 +78,103 @@ async function getStore(type) {
|
|||
// Return an object with properties for key and value|initialValue, or null
|
||||
// if no setting has been stored for that key.
|
||||
async function getTopItem(type, key) {
|
||||
let store = await getStore(type);
|
||||
let store = getStore(type);
|
||||
|
||||
let keyInfo = store.data[type][key];
|
||||
if (!keyInfo) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!keyInfo.precedenceList.length) {
|
||||
return {key, initialValue: keyInfo.initialValue};
|
||||
// Find the highest precedence, enabled setting.
|
||||
for (let item of keyInfo.precedenceList) {
|
||||
if (item.enabled) {
|
||||
return {key, value: item.value};
|
||||
}
|
||||
}
|
||||
|
||||
return {key, value: keyInfo.precedenceList[0].value};
|
||||
// Nothing found in the precedenceList, return the initialValue.
|
||||
return {key, initialValue: keyInfo.initialValue};
|
||||
}
|
||||
|
||||
// Comparator used when sorting the precedence list.
|
||||
function precedenceComparator(a, b) {
|
||||
if (a.enabled && !b.enabled) {
|
||||
return -1;
|
||||
}
|
||||
if (b.enabled && !a.enabled) {
|
||||
return 1;
|
||||
}
|
||||
return b.installDate - a.installDate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method that alters a setting, either by changing its enabled status
|
||||
* or by removing it.
|
||||
*
|
||||
* @param {Extension} extension
|
||||
* The extension for which a setting is being removed/disabled.
|
||||
* @param {string} type
|
||||
* The type of setting to be altered.
|
||||
* @param {string} key
|
||||
* A string that uniquely identifies the setting.
|
||||
* @param {string} action
|
||||
* The action to perform on the setting.
|
||||
* Will be one of remove|enable|disable.
|
||||
*
|
||||
* @returns {object | null}
|
||||
* Either an object with properties for key and value, which
|
||||
* corresponds to the current top precedent setting, or null if
|
||||
* the current top precedent setting has not changed.
|
||||
*/
|
||||
async function alterSetting(extension, type, key, action) {
|
||||
let returnItem;
|
||||
let store = getStore(type);
|
||||
|
||||
let keyInfo = store.data[type][key];
|
||||
if (!keyInfo) {
|
||||
throw new Error(
|
||||
`Cannot alter the setting for ${type}:${key} as it does not exist.`);
|
||||
}
|
||||
|
||||
let id = extension.id;
|
||||
let foundIndex = keyInfo.precedenceList.findIndex(item => item.id == id);
|
||||
|
||||
if (foundIndex === -1) {
|
||||
throw new Error(
|
||||
`Cannot alter the setting for ${type}:${key} as it does not exist.`);
|
||||
}
|
||||
|
||||
switch (action) {
|
||||
case "remove":
|
||||
keyInfo.precedenceList.splice(foundIndex, 1);
|
||||
break;
|
||||
|
||||
case "enable":
|
||||
keyInfo.precedenceList[foundIndex].enabled = true;
|
||||
keyInfo.precedenceList.sort(precedenceComparator);
|
||||
foundIndex = keyInfo.precedenceList.findIndex(item => item.id == id);
|
||||
break;
|
||||
|
||||
case "disable":
|
||||
keyInfo.precedenceList[foundIndex].enabled = false;
|
||||
keyInfo.precedenceList.sort(precedenceComparator);
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Error(`${action} is not a valid action for alterSetting.`);
|
||||
}
|
||||
|
||||
if (foundIndex === 0) {
|
||||
returnItem = await getTopItem(type, key);
|
||||
}
|
||||
|
||||
if (action === "remove" && keyInfo.precedenceList.length === 0) {
|
||||
delete store.data[type][key];
|
||||
}
|
||||
|
||||
store.saveSoon();
|
||||
|
||||
return returnItem;
|
||||
}
|
||||
|
||||
this.ExtensionSettingsStore = {
|
||||
|
@ -124,7 +210,7 @@ this.ExtensionSettingsStore = {
|
|||
}
|
||||
|
||||
let id = extension.id;
|
||||
let store = await getStore(type);
|
||||
let store = getStore(type);
|
||||
|
||||
if (!store.data[type][key]) {
|
||||
// The setting for this key does not exist. Set the initial value.
|
||||
|
@ -137,19 +223,17 @@ this.ExtensionSettingsStore = {
|
|||
let keyInfo = store.data[type][key];
|
||||
// Check for this item in the precedenceList.
|
||||
let foundIndex = keyInfo.precedenceList.findIndex(item => item.id == id);
|
||||
if (foundIndex == -1) {
|
||||
if (foundIndex === -1) {
|
||||
// No item for this extension, so add a new one.
|
||||
let addon = await AddonManager.getAddonByID(id);
|
||||
keyInfo.precedenceList.push({id, installDate: addon.installDate, value});
|
||||
keyInfo.precedenceList.push({id, installDate: addon.installDate, value, enabled: true});
|
||||
} else {
|
||||
// Item already exists or this extension, so update it.
|
||||
keyInfo.precedenceList[foundIndex].value = value;
|
||||
}
|
||||
|
||||
// Sort the list.
|
||||
keyInfo.precedenceList.sort((a, b) => {
|
||||
return b.installDate - a.installDate;
|
||||
});
|
||||
keyInfo.precedenceList.sort(precedenceComparator);
|
||||
|
||||
store.saveSoon();
|
||||
|
||||
|
@ -161,48 +245,63 @@ this.ExtensionSettingsStore = {
|
|||
},
|
||||
|
||||
/**
|
||||
* Removes a setting from the store, returning the current top precedent
|
||||
* setting.
|
||||
* Removes a setting from the store, possibly returning the current top
|
||||
* precedent setting.
|
||||
*
|
||||
* @param {Extension} extension The extension for which a setting is being removed.
|
||||
* @param {string} type The type of setting to be removed.
|
||||
* @param {string} key A string that uniquely identifies the setting.
|
||||
* @param {Extension} extension
|
||||
* The extension for which a setting is being removed.
|
||||
* @param {string} type
|
||||
* The type of setting to be removed.
|
||||
* @param {string} key
|
||||
* A string that uniquely identifies the setting.
|
||||
*
|
||||
* @returns {object | null} Either an object with properties for key and
|
||||
* value, which corresponds to the current top
|
||||
* precedent setting, or null if the current top
|
||||
* precedent setting has not changed.
|
||||
* @returns {object | null}
|
||||
* Either an object with properties for key and value, which
|
||||
* corresponds to the current top precedent setting, or null if
|
||||
* the current top precedent setting has not changed.
|
||||
*/
|
||||
async removeSetting(extension, type, key) {
|
||||
let returnItem;
|
||||
let store = await getStore(type);
|
||||
return await alterSetting(extension, type, key, "remove");
|
||||
},
|
||||
|
||||
let keyInfo = store.data[type][key];
|
||||
if (!keyInfo) {
|
||||
throw new Error(
|
||||
`Cannot remove setting for ${type}:${key} as it does not exist.`);
|
||||
}
|
||||
/**
|
||||
* Enables a setting in the store, possibly returning the current top
|
||||
* precedent setting.
|
||||
*
|
||||
* @param {Extension} extension
|
||||
* The extension for which a setting is being enabled.
|
||||
* @param {string} type
|
||||
* The type of setting to be enabled.
|
||||
* @param {string} key
|
||||
* A string that uniquely identifies the setting.
|
||||
*
|
||||
* @returns {object | null}
|
||||
* Either an object with properties for key and value, which
|
||||
* corresponds to the current top precedent setting, or null if
|
||||
* the current top precedent setting has not changed.
|
||||
*/
|
||||
async enable(extension, type, key) {
|
||||
return await alterSetting(extension, type, key, "enable");
|
||||
},
|
||||
|
||||
let id = extension.id;
|
||||
let foundIndex = keyInfo.precedenceList.findIndex(item => item.id == id);
|
||||
|
||||
if (foundIndex == -1) {
|
||||
throw new Error(
|
||||
`Cannot remove setting for ${type}:${key} as it does not exist.`);
|
||||
}
|
||||
|
||||
keyInfo.precedenceList.splice(foundIndex, 1);
|
||||
|
||||
if (foundIndex == 0) {
|
||||
returnItem = await getTopItem(type, key);
|
||||
}
|
||||
|
||||
if (keyInfo.precedenceList.length == 0) {
|
||||
delete store.data[type][key];
|
||||
}
|
||||
store.saveSoon();
|
||||
|
||||
return returnItem;
|
||||
/**
|
||||
* Disables a setting in the store, possibly returning the current top
|
||||
* precedent setting.
|
||||
*
|
||||
* @param {Extension} extension
|
||||
* The extension for which a setting is being disabled.
|
||||
* @param {string} type
|
||||
* The type of setting to be disabled.
|
||||
* @param {string} key
|
||||
* A string that uniquely identifies the setting.
|
||||
*
|
||||
* @returns {object | null}
|
||||
* Either an object with properties for key and value, which
|
||||
* corresponds to the current top precedent setting, or null if
|
||||
* the current top precedent setting has not changed.
|
||||
*/
|
||||
async disable(extension, type, key) {
|
||||
return await alterSetting(extension, type, key, "disable");
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -214,7 +313,7 @@ this.ExtensionSettingsStore = {
|
|||
* @returns {array} A list of settings which have been stored for the extension.
|
||||
*/
|
||||
async getAllForExtension(extension, type) {
|
||||
let store = await getStore(type);
|
||||
let store = getStore(type);
|
||||
|
||||
let keysObj = store.data[type];
|
||||
let items = [];
|
||||
|
@ -251,16 +350,19 @@ this.ExtensionSettingsStore = {
|
|||
* controllable_by_this_extension: can be controlled by this extension
|
||||
* controlled_by_this_extension: controlled by this extension
|
||||
*
|
||||
* @param {Extension} extension The extension for which levelOfControl is being
|
||||
* requested.
|
||||
* @param {string} type The type of setting to be returned. For example `pref`.
|
||||
* @param {string} key A string that uniquely identifies the setting, for
|
||||
* example, a preference name.
|
||||
* @param {Extension} extension
|
||||
* The extension for which levelOfControl is being requested.
|
||||
* @param {string} type
|
||||
* The type of setting to be returned. For example `pref`.
|
||||
* @param {string} key
|
||||
* A string that uniquely identifies the setting, for example, a
|
||||
* preference name.
|
||||
*
|
||||
* @returns {string} The level of control of the extension over the key.
|
||||
* @returns {string}
|
||||
* The level of control of the extension over the key.
|
||||
*/
|
||||
async getLevelOfControl(extension, type, key) {
|
||||
let store = await getStore(type);
|
||||
let store = getStore(type);
|
||||
|
||||
let keyInfo = store.data[type][key];
|
||||
if (!keyInfo || !keyInfo.precedenceList.length) {
|
||||
|
@ -268,7 +370,12 @@ this.ExtensionSettingsStore = {
|
|||
}
|
||||
|
||||
let id = extension.id;
|
||||
let topItem = keyInfo.precedenceList[0];
|
||||
let enabledItems = keyInfo.precedenceList.filter(item => item.enabled);
|
||||
if (!enabledItems.length) {
|
||||
return "controllable_by_this_extension";
|
||||
}
|
||||
|
||||
let topItem = enabledItems[0];
|
||||
if (topItem.id == id) {
|
||||
return "controlled_by_this_extension";
|
||||
}
|
||||
|
|
|
@ -157,11 +157,23 @@ add_task(async function test_settings_store() {
|
|||
await Assert.rejects(
|
||||
ExtensionSettingsStore.removeSetting(
|
||||
extensions[0], "myType", "unset_key"),
|
||||
/Cannot remove setting for myType:unset_key as it does not exist/,
|
||||
/Cannot alter the setting for myType:unset_key as it does not exist/,
|
||||
"removeSetting rejects with an unset key.");
|
||||
|
||||
// Attempting to disable a setting that has not been set should throw an exception.
|
||||
await Assert.rejects(
|
||||
ExtensionSettingsStore.disable(extensions[0], "myType", "unset_key"),
|
||||
/Cannot alter the setting for myType:unset_key as it does not exist/,
|
||||
"disable rejects with an unset key.");
|
||||
|
||||
// Attempting to enable a setting that has not been set should throw an exception.
|
||||
await Assert.rejects(
|
||||
ExtensionSettingsStore.enable(extensions[0], "myType", "unset_key"),
|
||||
/Cannot alter the setting for myType:unset_key as it does not exist/,
|
||||
"enable rejects with an unset key.");
|
||||
|
||||
let expectedKeys = KEY_LIST;
|
||||
// Remove the non-top item for a key.
|
||||
// Disable the non-top item for a key.
|
||||
for (let key of KEY_LIST) {
|
||||
let extensionIndex = 0;
|
||||
let item = await ExtensionSettingsStore.addSetting(
|
||||
|
@ -170,7 +182,45 @@ add_task(async function test_settings_store() {
|
|||
expectedCallbackCount,
|
||||
"initialValueCallback called the expected number of times.");
|
||||
equal(item, null, "Updating non-top item for a key returns null");
|
||||
item = await ExtensionSettingsStore.removeSetting(extensions[extensionIndex], TEST_TYPE, key);
|
||||
item = await ExtensionSettingsStore.disable(extensions[extensionIndex], TEST_TYPE, key);
|
||||
equal(item, null, "Disabling non-top item for a key returns null.");
|
||||
let allForExtension = await ExtensionSettingsStore.getAllForExtension(extensions[extensionIndex], TEST_TYPE);
|
||||
deepEqual(allForExtension, expectedKeys, "getAllForExtension returns expected keys after a disable.");
|
||||
item = await ExtensionSettingsStore.getSetting(TEST_TYPE, key);
|
||||
deepEqual(
|
||||
item,
|
||||
ITEMS[key][2],
|
||||
"getSetting returns correct item after a disable.");
|
||||
let levelOfControl = await ExtensionSettingsStore.getLevelOfControl(extensions[extensionIndex], TEST_TYPE, key);
|
||||
equal(
|
||||
levelOfControl,
|
||||
"controlled_by_other_extensions",
|
||||
"getLevelOfControl returns correct levelOfControl after disabling of non-top item.");
|
||||
}
|
||||
|
||||
// Re-enable the non-top item for a key.
|
||||
for (let key of KEY_LIST) {
|
||||
let extensionIndex = 0;
|
||||
let item = await ExtensionSettingsStore.enable(extensions[extensionIndex], TEST_TYPE, key);
|
||||
equal(item, null, "Enabling non-top item for a key returns null.");
|
||||
let allForExtension = await ExtensionSettingsStore.getAllForExtension(extensions[extensionIndex], TEST_TYPE);
|
||||
deepEqual(allForExtension, expectedKeys, "getAllForExtension returns expected keys after an enable.");
|
||||
item = await ExtensionSettingsStore.getSetting(TEST_TYPE, key);
|
||||
deepEqual(
|
||||
item,
|
||||
ITEMS[key][2],
|
||||
"getSetting returns correct item after an enable.");
|
||||
let levelOfControl = await ExtensionSettingsStore.getLevelOfControl(extensions[extensionIndex], TEST_TYPE, key);
|
||||
equal(
|
||||
levelOfControl,
|
||||
"controlled_by_other_extensions",
|
||||
"getLevelOfControl returns correct levelOfControl after enabling of non-top item.");
|
||||
}
|
||||
|
||||
// Remove the non-top item for a key.
|
||||
for (let key of KEY_LIST) {
|
||||
let extensionIndex = 0;
|
||||
let item = await ExtensionSettingsStore.removeSetting(extensions[extensionIndex], TEST_TYPE, key);
|
||||
equal(item, null, "Removing non-top item for a key returns null.");
|
||||
expectedKeys = expectedKeys.filter(expectedKey => expectedKey != key);
|
||||
let allForExtension = await ExtensionSettingsStore.getAllForExtension(extensions[extensionIndex], TEST_TYPE);
|
||||
|
@ -188,8 +238,42 @@ add_task(async function test_settings_store() {
|
|||
}
|
||||
|
||||
for (let key of KEY_LIST) {
|
||||
// Disable the top item for a key.
|
||||
let item = await ExtensionSettingsStore.disable(extensions[2], TEST_TYPE, key);
|
||||
deepEqual(
|
||||
item,
|
||||
ITEMS[key][1],
|
||||
"Disabling top item for a key returns the new top item.");
|
||||
item = await ExtensionSettingsStore.getSetting(TEST_TYPE, key);
|
||||
deepEqual(
|
||||
item,
|
||||
ITEMS[key][1],
|
||||
"getSetting returns correct item after a disable.");
|
||||
let levelOfControl = await ExtensionSettingsStore.getLevelOfControl(extensions[2], TEST_TYPE, key);
|
||||
equal(
|
||||
levelOfControl,
|
||||
"controllable_by_this_extension",
|
||||
"getLevelOfControl returns correct levelOfControl after disabling of top item.");
|
||||
|
||||
// Re-enable the top item for a key.
|
||||
item = await ExtensionSettingsStore.enable(extensions[2], TEST_TYPE, key);
|
||||
deepEqual(
|
||||
item,
|
||||
ITEMS[key][2],
|
||||
"Re-enabling top item for a key returns the old top item.");
|
||||
item = await ExtensionSettingsStore.getSetting(TEST_TYPE, key);
|
||||
deepEqual(
|
||||
item,
|
||||
ITEMS[key][2],
|
||||
"getSetting returns correct item after an enable.");
|
||||
levelOfControl = await ExtensionSettingsStore.getLevelOfControl(extensions[2], TEST_TYPE, key);
|
||||
equal(
|
||||
levelOfControl,
|
||||
"controlled_by_this_extension",
|
||||
"getLevelOfControl returns correct levelOfControl after re-enabling top item.");
|
||||
|
||||
// Remove the top item for a key.
|
||||
let item = await ExtensionSettingsStore.removeSetting(extensions[2], TEST_TYPE, key);
|
||||
item = await ExtensionSettingsStore.removeSetting(extensions[2], TEST_TYPE, key);
|
||||
deepEqual(
|
||||
item,
|
||||
ITEMS[key][1],
|
||||
|
@ -199,7 +283,7 @@ add_task(async function test_settings_store() {
|
|||
item,
|
||||
ITEMS[key][1],
|
||||
"getSetting returns correct item after a removal.");
|
||||
let levelOfControl = await ExtensionSettingsStore.getLevelOfControl(extensions[2], TEST_TYPE, key);
|
||||
levelOfControl = await ExtensionSettingsStore.getLevelOfControl(extensions[2], TEST_TYPE, key);
|
||||
equal(
|
||||
levelOfControl,
|
||||
"controllable_by_this_extension",
|
||||
|
@ -227,11 +311,45 @@ add_task(async function test_settings_store() {
|
|||
"controlled_by_this_extension",
|
||||
"getLevelOfControl returns correct levelOfControl after updating.");
|
||||
|
||||
// Remove the last remaining item for a key.
|
||||
// Disable the last remaining item for a key.
|
||||
let expectedItem = {key, initialValue: initialValue(key)};
|
||||
// We're using the callback to set the expected value, so we need to increment the
|
||||
// expectedCallbackCount.
|
||||
expectedCallbackCount++;
|
||||
item = await ExtensionSettingsStore.disable(extensions[1], TEST_TYPE, key);
|
||||
deepEqual(
|
||||
item,
|
||||
expectedItem,
|
||||
"Disabling last item for a key returns the initial value.");
|
||||
item = await ExtensionSettingsStore.getSetting(TEST_TYPE, key);
|
||||
deepEqual(
|
||||
item,
|
||||
expectedItem,
|
||||
"getSetting returns the initial value after all are disabled.");
|
||||
levelOfControl = await ExtensionSettingsStore.getLevelOfControl(extensions[1], TEST_TYPE, key);
|
||||
equal(
|
||||
levelOfControl,
|
||||
"controllable_by_this_extension",
|
||||
"getLevelOfControl returns correct levelOfControl after all are disabled.");
|
||||
|
||||
// Re-enable the last remaining item for a key.
|
||||
item = await ExtensionSettingsStore.enable(extensions[1], TEST_TYPE, key);
|
||||
deepEqual(
|
||||
item,
|
||||
itemToAdd,
|
||||
"Re-enabling last item for a key returns the old value.");
|
||||
item = await ExtensionSettingsStore.getSetting(TEST_TYPE, key);
|
||||
deepEqual(
|
||||
item,
|
||||
itemToAdd,
|
||||
"getSetting returns expected value after re-enabling.");
|
||||
levelOfControl = await ExtensionSettingsStore.getLevelOfControl(extensions[1], TEST_TYPE, key);
|
||||
equal(
|
||||
levelOfControl,
|
||||
"controlled_by_this_extension",
|
||||
"getLevelOfControl returns correct levelOfControl after re-enabling.");
|
||||
|
||||
// Remove the last remaining item for a key.
|
||||
item = await ExtensionSettingsStore.removeSetting(extensions[1], TEST_TYPE, key);
|
||||
deepEqual(
|
||||
item,
|
||||
|
@ -249,7 +367,7 @@ add_task(async function test_settings_store() {
|
|||
"getLevelOfControl returns correct levelOfControl after all are removed.");
|
||||
|
||||
// Attempting to remove a setting that has had all extensions removed should throw an exception.
|
||||
let expectedMessage = new RegExp(`Cannot remove setting for ${TEST_TYPE}:${key} as it does not exist`);
|
||||
let expectedMessage = new RegExp(`Cannot alter the setting for ${TEST_TYPE}:${key} as it does not exist`);
|
||||
await Assert.rejects(
|
||||
ExtensionSettingsStore.removeSetting(
|
||||
extensions[1], TEST_TYPE, key),
|
||||
|
|
Загрузка…
Ссылка в новой задаче