forked from mirrors/gecko-dev
		
	
		
			
				
	
	
		
			712 lines
		
	
	
	
		
			22 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			712 lines
		
	
	
	
		
			22 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
/* -*- Mode: indent-tabs-mode: nil; js-indent-level: 2 -*- */
 | 
						|
/* vim: set sts=2 sw=2 et tw=80: */
 | 
						|
/* This Source Code Form is subject to the terms of the Mozilla Public
 | 
						|
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 | 
						|
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 | 
						|
 | 
						|
/**
 | 
						|
 * @file
 | 
						|
 * This module is used for managing preferences from WebExtension APIs.
 | 
						|
 * It takes care of the precedence chain and decides whether a preference
 | 
						|
 * needs to be updated when a change is requested by an API.
 | 
						|
 *
 | 
						|
 * It deals with preferences via settings objects, which are objects with
 | 
						|
 * the following properties:
 | 
						|
 *
 | 
						|
 * prefNames:   An array of strings, each of which is a preference on
 | 
						|
 *              which the setting depends.
 | 
						|
 * setCallback: A function that returns an object containing properties and
 | 
						|
 *              values that correspond to the prefs to be set.
 | 
						|
 */
 | 
						|
 | 
						|
export let ExtensionPreferencesManager;
 | 
						|
 | 
						|
import { Management } from "resource://gre/modules/Extension.sys.mjs";
 | 
						|
 | 
						|
const lazy = {};
 | 
						|
 | 
						|
ChromeUtils.defineESModuleGetters(lazy, {
 | 
						|
  ExtensionCommon: "resource://gre/modules/ExtensionCommon.sys.mjs",
 | 
						|
  ExtensionSettingsStore:
 | 
						|
    "resource://gre/modules/ExtensionSettingsStore.sys.mjs",
 | 
						|
  Preferences: "resource://gre/modules/Preferences.sys.mjs",
 | 
						|
});
 | 
						|
import { ExtensionUtils } from "resource://gre/modules/ExtensionUtils.sys.mjs";
 | 
						|
 | 
						|
const { ExtensionError } = ExtensionUtils;
 | 
						|
 | 
						|
ChromeUtils.defineLazyGetter(lazy, "defaultPreferences", function () {
 | 
						|
  return new lazy.Preferences({ defaultBranch: true });
 | 
						|
});
 | 
						|
 | 
						|
/* eslint-disable mozilla/balanced-listeners */
 | 
						|
Management.on("uninstall", async (type, { id }) => {
 | 
						|
  // Ensure managed preferences are cleared if they were
 | 
						|
  // not cleared at the module level.
 | 
						|
  await Management.asyncLoadSettingsModules();
 | 
						|
  return ExtensionPreferencesManager.removeAll(id);
 | 
						|
});
 | 
						|
 | 
						|
Management.on("disable", async (type, id) => {
 | 
						|
  await Management.asyncLoadSettingsModules();
 | 
						|
  return ExtensionPreferencesManager.disableAll(id);
 | 
						|
});
 | 
						|
 | 
						|
Management.on("enabling", async (type, id) => {
 | 
						|
  await Management.asyncLoadSettingsModules();
 | 
						|
  return ExtensionPreferencesManager.enableAll(id);
 | 
						|
});
 | 
						|
 | 
						|
Management.on("change-permissions", (type, change) => {
 | 
						|
  // Called for added or removed, but we only care about removed here.
 | 
						|
  if (!change.removed) {
 | 
						|
    return;
 | 
						|
  }
 | 
						|
  ExtensionPreferencesManager.removeSettingsForPermissions(
 | 
						|
    change.extensionId,
 | 
						|
    change.removed.permissions
 | 
						|
  );
 | 
						|
});
 | 
						|
 | 
						|
/* eslint-enable mozilla/balanced-listeners */
 | 
						|
 | 
						|
const STORE_TYPE = "prefs";
 | 
						|
 | 
						|
// Definitions of settings, each of which correspond to a different API.
 | 
						|
let settingsMap = new Map();
 | 
						|
 | 
						|
/**
 | 
						|
 * This function is passed into the ExtensionSettingsStore to determine the
 | 
						|
 * initial value of the setting. It reads an array of preference names from
 | 
						|
 * the this scope, which gets bound to a settings object.
 | 
						|
 *
 | 
						|
 * @returns {object}
 | 
						|
 *          An object with one property per preference, which holds the current
 | 
						|
 *          value of that preference.
 | 
						|
 */
 | 
						|
function initialValueCallback() {
 | 
						|
  let initialValue = {};
 | 
						|
  for (let pref of this.prefNames) {
 | 
						|
    // If there is a prior user-set value, get it.
 | 
						|
    if (lazy.Preferences.isSet(pref)) {
 | 
						|
      initialValue[pref] = lazy.Preferences.get(pref);
 | 
						|
    }
 | 
						|
  }
 | 
						|
  return initialValue;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Updates the initialValue stored to exclude any values that match
 | 
						|
 * default preference values.
 | 
						|
 *
 | 
						|
 * @param {object} initialValue Initial Value data from settings store.
 | 
						|
 * @returns {object}
 | 
						|
 *          The initialValue object after updating the values.
 | 
						|
 */
 | 
						|
function settingsUpdate(initialValue) {
 | 
						|
  for (let pref of this.prefNames) {
 | 
						|
    try {
 | 
						|
      if (
 | 
						|
        initialValue[pref] !== undefined &&
 | 
						|
        initialValue[pref] === lazy.defaultPreferences.get(pref)
 | 
						|
      ) {
 | 
						|
        initialValue[pref] = undefined;
 | 
						|
      }
 | 
						|
    } catch (e) {
 | 
						|
      // Exception thrown if a default value doesn't exist.  We
 | 
						|
      // presume that this pref had a user-set value initially.
 | 
						|
    }
 | 
						|
  }
 | 
						|
  return initialValue;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Loops through a set of prefs, either setting or resetting them.
 | 
						|
 *
 | 
						|
 * @param {string} name
 | 
						|
 *        The api name of the setting.
 | 
						|
 * @param {object} setting
 | 
						|
 *        An object that represents a setting, which will have a setCallback
 | 
						|
 *        property. If a onPrefsChanged function is provided it will be called
 | 
						|
 *        with item when the preferences change.
 | 
						|
 * @param {object} item
 | 
						|
 *        An object that represents an item handed back from the setting store
 | 
						|
 *        from which the new pref values can be calculated.
 | 
						|
 */
 | 
						|
function setPrefs(name, setting, item) {
 | 
						|
  let prefs = item.initialValue || setting.setCallback(item.value);
 | 
						|
  let changed = false;
 | 
						|
  for (let pref of setting.prefNames) {
 | 
						|
    if (prefs[pref] === undefined) {
 | 
						|
      if (lazy.Preferences.isSet(pref)) {
 | 
						|
        changed = true;
 | 
						|
        lazy.Preferences.reset(pref);
 | 
						|
      }
 | 
						|
    } else if (lazy.Preferences.get(pref) != prefs[pref]) {
 | 
						|
      lazy.Preferences.set(pref, prefs[pref]);
 | 
						|
      changed = true;
 | 
						|
    }
 | 
						|
  }
 | 
						|
  if (changed && typeof setting.onPrefsChanged == "function") {
 | 
						|
    setting.onPrefsChanged(item);
 | 
						|
  }
 | 
						|
  Management.emit(`extension-setting-changed:${name}`);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * Commits a change to a setting and conditionally sets preferences.
 | 
						|
 *
 | 
						|
 * If the change to the setting causes a different extension to gain
 | 
						|
 * control of the pref (or removes all extensions with control over the pref)
 | 
						|
 * then the prefs should be updated, otherwise they should not be.
 | 
						|
 * In addition, if the current value of any of the prefs does not
 | 
						|
 * match what we expect the value to be (which could be the result of a
 | 
						|
 * user manually changing the pref value), then we do not change any
 | 
						|
 * of the prefs.
 | 
						|
 *
 | 
						|
 * @param {string} id
 | 
						|
 *        The id of the extension for which a setting is being modified.  Also
 | 
						|
 *        see selectSetting.
 | 
						|
 * @param {string} name
 | 
						|
 *        The name of the setting being processed.
 | 
						|
 * @param {string} action
 | 
						|
 *        The action that is being performed. Will be one of disable, enable
 | 
						|
 *        or removeSetting.
 | 
						|
 | 
						|
 * @returns {Promise}
 | 
						|
 *          Resolves to true if preferences were set as a result and to false
 | 
						|
 *          if preferences were not set.
 | 
						|
*/
 | 
						|
async function processSetting(id, name, action) {
 | 
						|
  await lazy.ExtensionSettingsStore.initialize();
 | 
						|
  let expectedItem = lazy.ExtensionSettingsStore.getSetting(STORE_TYPE, name);
 | 
						|
  let item = lazy.ExtensionSettingsStore[action](id, STORE_TYPE, name);
 | 
						|
  if (item) {
 | 
						|
    let setting = settingsMap.get(name);
 | 
						|
    let expectedPrefs =
 | 
						|
      expectedItem.initialValue || setting.setCallback(expectedItem.value);
 | 
						|
    if (
 | 
						|
      Object.keys(expectedPrefs).some(
 | 
						|
        pref =>
 | 
						|
          expectedPrefs[pref] &&
 | 
						|
          lazy.Preferences.get(pref) != expectedPrefs[pref]
 | 
						|
      )
 | 
						|
    ) {
 | 
						|
      return false;
 | 
						|
    }
 | 
						|
    setPrefs(name, setting, item);
 | 
						|
    return true;
 | 
						|
  }
 | 
						|
  return false;
 | 
						|
}
 | 
						|
 | 
						|
ExtensionPreferencesManager = {
 | 
						|
  /**
 | 
						|
   * Adds a setting to the settingsMap. This is how an API tells the
 | 
						|
   * preferences manager what its setting object is. The preferences
 | 
						|
   * manager needs to know this when settings need to be removed
 | 
						|
   * automatically.
 | 
						|
   *
 | 
						|
   * @param {string} name The unique id of the setting.
 | 
						|
   * @param {object} setting
 | 
						|
   *        A setting object that should have properties for
 | 
						|
   *        prefNames, getCallback and setCallback.
 | 
						|
   */
 | 
						|
  addSetting(name, setting) {
 | 
						|
    settingsMap.set(name, setting);
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Gets the default value for a preference.
 | 
						|
   *
 | 
						|
   * @param {string} prefName The name of the preference.
 | 
						|
   *
 | 
						|
   * @returns {string|number|boolean} The default value of the preference.
 | 
						|
   */
 | 
						|
  getDefaultValue(prefName) {
 | 
						|
    return lazy.defaultPreferences.get(prefName);
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Returns a map of prefName to setting Name for use in about:config, about:preferences or
 | 
						|
   * other areas of Firefox that need to know whether a specific pref is controlled by an
 | 
						|
   * extension.
 | 
						|
   *
 | 
						|
   * Given a prefName, you can get the settingName.  Call EPM.getSetting(settingName) to
 | 
						|
   * get the details of the setting, including which id if any is in control of the
 | 
						|
   * setting.
 | 
						|
   *
 | 
						|
   * @returns {Promise}
 | 
						|
   *          Resolves to a Map of prefName->settingName
 | 
						|
   */
 | 
						|
  async getManagedPrefDetails() {
 | 
						|
    await Management.asyncLoadSettingsModules();
 | 
						|
    let prefs = new Map();
 | 
						|
    settingsMap.forEach((setting, name) => {
 | 
						|
      for (let prefName of setting.prefNames) {
 | 
						|
        prefs.set(prefName, name);
 | 
						|
      }
 | 
						|
    });
 | 
						|
    return prefs;
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Indicates that an extension would like to change the value of a previously
 | 
						|
   * defined setting.
 | 
						|
   *
 | 
						|
   * @param {string} id
 | 
						|
   *        The id of the extension for which a setting is being set.
 | 
						|
   * @param {string} name
 | 
						|
   *        The unique id of the setting.
 | 
						|
   * @param {any} value
 | 
						|
   *        The value to be stored in the settings store for this
 | 
						|
   *        group of preferences.
 | 
						|
   *
 | 
						|
   * @returns {Promise}
 | 
						|
   *          Resolves to true if the preferences were changed and to false if
 | 
						|
   *          the preferences were not changed.
 | 
						|
   */
 | 
						|
  async setSetting(id, name, value) {
 | 
						|
    let setting = settingsMap.get(name);
 | 
						|
    await lazy.ExtensionSettingsStore.initialize();
 | 
						|
    let item = await lazy.ExtensionSettingsStore.addSetting(
 | 
						|
      id,
 | 
						|
      STORE_TYPE,
 | 
						|
      name,
 | 
						|
      value,
 | 
						|
      initialValueCallback.bind(setting),
 | 
						|
      name,
 | 
						|
      settingsUpdate.bind(setting)
 | 
						|
    );
 | 
						|
    if (item) {
 | 
						|
      setPrefs(name, setting, item);
 | 
						|
      return true;
 | 
						|
    }
 | 
						|
    return false;
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Indicates that this extension wants to temporarily cede control over the
 | 
						|
   * given setting.
 | 
						|
   *
 | 
						|
   * @param {string} id
 | 
						|
   *        The id of the extension for which a preference setting is being disabled.
 | 
						|
   * @param {string} name
 | 
						|
   *        The unique id of the setting.
 | 
						|
   *
 | 
						|
   * @returns {Promise}
 | 
						|
   *          Resolves to true if the preferences were changed and to false if
 | 
						|
   *          the preferences were not changed.
 | 
						|
   */
 | 
						|
  disableSetting(id, name) {
 | 
						|
    return processSetting(id, name, "disable");
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Enable a setting that has been disabled.
 | 
						|
   *
 | 
						|
   * @param {string} id
 | 
						|
   *        The id of the extension for which a setting is being enabled.
 | 
						|
   * @param {string} name
 | 
						|
   *        The unique id of the setting.
 | 
						|
   *
 | 
						|
   * @returns {Promise}
 | 
						|
   *          Resolves to true if the preferences were changed and to false if
 | 
						|
   *          the preferences were not changed.
 | 
						|
   */
 | 
						|
  enableSetting(id, name) {
 | 
						|
    return processSetting(id, name, "enable");
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Specifically select an extension, the user, or the precedence order that will
 | 
						|
   * be in control of this setting.
 | 
						|
   *
 | 
						|
   * @param {string | null} id
 | 
						|
   *        The id of the extension for which a setting is being selected, or
 | 
						|
   *        ExtensionSettingStore.SETTING_USER_SET (null).
 | 
						|
   * @param {string} name
 | 
						|
   *        The unique id of the setting.
 | 
						|
   *
 | 
						|
   * @returns {Promise}
 | 
						|
   *          Resolves to true if the preferences were changed and to false if
 | 
						|
   *          the preferences were not changed.
 | 
						|
   */
 | 
						|
  selectSetting(id, name) {
 | 
						|
    return processSetting(id, name, "select");
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Indicates that this extension no longer wants to set the given setting.
 | 
						|
   *
 | 
						|
   * @param {string} id
 | 
						|
   *        The id of the extension for which a preference setting is being removed.
 | 
						|
   * @param {string} name
 | 
						|
   *        The unique id of the setting.
 | 
						|
   *
 | 
						|
   * @returns {Promise}
 | 
						|
   *          Resolves to true if the preferences were changed and to false if
 | 
						|
   *          the preferences were not changed.
 | 
						|
   */
 | 
						|
  removeSetting(id, name) {
 | 
						|
    return processSetting(id, name, "removeSetting");
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Disables all previously set settings for an extension. This can be called when
 | 
						|
   * an extension is being disabled, for example.
 | 
						|
   *
 | 
						|
   * @param {string} id
 | 
						|
   *        The id of the extension for which all settings are being unset.
 | 
						|
   */
 | 
						|
  async disableAll(id) {
 | 
						|
    await lazy.ExtensionSettingsStore.initialize();
 | 
						|
    let settings = lazy.ExtensionSettingsStore.getAllForExtension(
 | 
						|
      id,
 | 
						|
      STORE_TYPE
 | 
						|
    );
 | 
						|
    let disablePromises = [];
 | 
						|
    for (let name of settings) {
 | 
						|
      disablePromises.push(this.disableSetting(id, name));
 | 
						|
    }
 | 
						|
    await Promise.all(disablePromises);
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Enables all disabled settings for an extension. This can be called when
 | 
						|
   * an extension has finished updating or is being re-enabled, for example.
 | 
						|
   *
 | 
						|
   * @param {string} id
 | 
						|
   *        The id of the extension for which all settings are being enabled.
 | 
						|
   */
 | 
						|
  async enableAll(id) {
 | 
						|
    await lazy.ExtensionSettingsStore.initialize();
 | 
						|
    let settings = lazy.ExtensionSettingsStore.getAllForExtension(
 | 
						|
      id,
 | 
						|
      STORE_TYPE
 | 
						|
    );
 | 
						|
    let enablePromises = [];
 | 
						|
    for (let name of settings) {
 | 
						|
      enablePromises.push(this.enableSetting(id, name));
 | 
						|
    }
 | 
						|
    await Promise.all(enablePromises);
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Removes all previously set settings for an extension. This can be called when
 | 
						|
   * an extension is being uninstalled, for example.
 | 
						|
   *
 | 
						|
   * @param {string} id
 | 
						|
   *        The id of the extension for which all settings are being unset.
 | 
						|
   */
 | 
						|
  async removeAll(id) {
 | 
						|
    await lazy.ExtensionSettingsStore.initialize();
 | 
						|
    let settings = lazy.ExtensionSettingsStore.getAllForExtension(
 | 
						|
      id,
 | 
						|
      STORE_TYPE
 | 
						|
    );
 | 
						|
    let removePromises = [];
 | 
						|
    for (let name of settings) {
 | 
						|
      removePromises.push(this.removeSetting(id, name));
 | 
						|
    }
 | 
						|
    await Promise.all(removePromises);
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Removes a set of settings that are available under certain addon permissions.
 | 
						|
   *
 | 
						|
   * @param {string} id
 | 
						|
   *        The extension id.
 | 
						|
   * @param {Array<string>} permissions
 | 
						|
   *        The permission name from the extension manifest.
 | 
						|
   * @returns {Promise}
 | 
						|
   *        A promise that resolves when all related settings are removed.
 | 
						|
   */
 | 
						|
  async removeSettingsForPermissions(id, permissions) {
 | 
						|
    if (!permissions || !permissions.length) {
 | 
						|
      return;
 | 
						|
    }
 | 
						|
    await Management.asyncLoadSettingsModules();
 | 
						|
    let removePromises = [];
 | 
						|
    settingsMap.forEach((setting, name) => {
 | 
						|
      if (permissions.includes(setting.permission)) {
 | 
						|
        removePromises.push(this.removeSetting(id, name));
 | 
						|
      }
 | 
						|
    });
 | 
						|
    return Promise.all(removePromises);
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Return the currently active value for a setting.
 | 
						|
   *
 | 
						|
   * @param {string} name
 | 
						|
   *        The unique id of the setting.
 | 
						|
   *
 | 
						|
   * @returns {object} The current setting object.
 | 
						|
   */
 | 
						|
  async getSetting(name) {
 | 
						|
    await lazy.ExtensionSettingsStore.initialize();
 | 
						|
    return lazy.ExtensionSettingsStore.getSetting(STORE_TYPE, name);
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Return the levelOfControl for a setting / extension combo.
 | 
						|
   * This queries the levelOfControl from the ExtensionSettingsStore and also
 | 
						|
   * takes into account whether any of the setting's preferences are locked.
 | 
						|
   *
 | 
						|
   * @param {string} id
 | 
						|
   *        The id of the extension for which levelOfControl is being requested.
 | 
						|
   * @param {string} name
 | 
						|
   *        The unique id of the setting.
 | 
						|
   * @param {string} storeType
 | 
						|
   *        The name of the store in ExtensionSettingsStore.
 | 
						|
   *        Defaults to STORE_TYPE.
 | 
						|
   *
 | 
						|
   * @returns {Promise}
 | 
						|
   *          Resolves to the level of control of the extension over the setting.
 | 
						|
   */
 | 
						|
  async getLevelOfControl(id, name, storeType = STORE_TYPE) {
 | 
						|
    // This could be called for a setting that isn't defined to the PreferencesManager,
 | 
						|
    // in which case we simply defer to the SettingsStore.
 | 
						|
    if (storeType === STORE_TYPE) {
 | 
						|
      let setting = settingsMap.get(name);
 | 
						|
      if (!setting) {
 | 
						|
        return "not_controllable";
 | 
						|
      }
 | 
						|
      for (let prefName of setting.prefNames) {
 | 
						|
        if (lazy.Preferences.locked(prefName)) {
 | 
						|
          return "not_controllable";
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
    await lazy.ExtensionSettingsStore.initialize();
 | 
						|
    return lazy.ExtensionSettingsStore.getLevelOfControl(id, storeType, name);
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Returns an API object with get/set/clear used for a setting.
 | 
						|
   *
 | 
						|
   * @param {string|object} extensionId or params object
 | 
						|
   * @param {string} name
 | 
						|
   *          The unique id of the setting.
 | 
						|
   * @param {Function} callback
 | 
						|
   *          The function that retreives the current setting from prefs.
 | 
						|
   * @param {string} storeType
 | 
						|
   *          The name of the store in ExtensionSettingsStore.
 | 
						|
   *          Defaults to STORE_TYPE.
 | 
						|
   * @param {boolean} readOnly
 | 
						|
   * @param {Function} validate
 | 
						|
   *          Utility function for any specific validation, such as checking
 | 
						|
   *          for supported platform.  Function should throw an error if necessary.
 | 
						|
   *
 | 
						|
   * @returns {object} API object with get/set/clear methods
 | 
						|
   */
 | 
						|
  getSettingsAPI(
 | 
						|
    extensionId,
 | 
						|
    name,
 | 
						|
    callback,
 | 
						|
    storeType,
 | 
						|
    readOnly = false,
 | 
						|
    validate
 | 
						|
  ) {
 | 
						|
    if (arguments.length > 1) {
 | 
						|
      Services.console.logStringMessage(
 | 
						|
        `ExtensionPreferencesManager.getSettingsAPI for ${name} should be updated to use a single paramater object.`
 | 
						|
      );
 | 
						|
    }
 | 
						|
    return ExtensionPreferencesManager._getInternalSettingsAPI(
 | 
						|
      arguments.length === 1
 | 
						|
        ? extensionId
 | 
						|
        : {
 | 
						|
            extensionId,
 | 
						|
            name,
 | 
						|
            callback,
 | 
						|
            storeType,
 | 
						|
            readOnly,
 | 
						|
            validate,
 | 
						|
          }
 | 
						|
    ).api;
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * getPrimedSettingsListener returns a function used to create
 | 
						|
   * a primed event listener.
 | 
						|
   *
 | 
						|
   * If a module overrides onChange then it must provide it's own
 | 
						|
   * persistent listener logic.  See homepage_override in browserSettings
 | 
						|
   * for an example.
 | 
						|
   *
 | 
						|
   * addSetting must be called prior to priming listeners.
 | 
						|
   *
 | 
						|
   * @param {object} config see getSettingsAPI
 | 
						|
   *        {Extension} extension, passed through to validate and used for extensionId
 | 
						|
   *        {string} name
 | 
						|
   *          The unique id of the settings api in the module, e.g. "settings"
 | 
						|
   * @returns {object} prime listener object
 | 
						|
   */
 | 
						|
  getPrimedSettingsListener(config) {
 | 
						|
    let { name, extension } = config;
 | 
						|
    if (!name || !extension) {
 | 
						|
      throw new Error(
 | 
						|
        `name and extension are required for getPrimedSettingListener`
 | 
						|
      );
 | 
						|
    }
 | 
						|
    if (!settingsMap.get(name)) {
 | 
						|
      throw new Error(
 | 
						|
        `addSetting must be called prior to getPrimedSettingListener`
 | 
						|
      );
 | 
						|
    }
 | 
						|
    return ExtensionPreferencesManager._getInternalSettingsAPI({
 | 
						|
      name,
 | 
						|
      extension,
 | 
						|
    }).registerEvent;
 | 
						|
  },
 | 
						|
 | 
						|
  /**
 | 
						|
   * Returns an object with a public API containing get/set/clear used for a setting,
 | 
						|
   * and a registerEvent function used for registering the event listener.
 | 
						|
   *
 | 
						|
   * @param {object} params The params object contains the following:
 | 
						|
   *        {BaseContext} context
 | 
						|
   *        {Extension} extension, optional, passed through to validate and used for extensionId
 | 
						|
   *        {string} extensionId, optional to support old API
 | 
						|
   *        {string} module
 | 
						|
   *          The name of the api module, e.g. "proxy"
 | 
						|
   *        {string} name
 | 
						|
   *          The unique id of the settings api in the module, e.g. "settings"
 | 
						|
   *          "name" should match the name given in the addSetting call.
 | 
						|
   *        {Function} callback
 | 
						|
   *          The function that retreives the current setting from prefs.
 | 
						|
   *        {string} storeType
 | 
						|
   *          The name of the store in ExtensionSettingsStore.
 | 
						|
   *          Defaults to STORE_TYPE.
 | 
						|
   *        {boolean} readOnly
 | 
						|
   *        {Function} validate
 | 
						|
   *          Utility function for any specific validation, such as checking
 | 
						|
   *          for supported platform.  Function should throw an error if necessary.
 | 
						|
   *
 | 
						|
   * @returns {object} internal API object with
 | 
						|
   *          {object} api
 | 
						|
   *            the public api available to extensions
 | 
						|
   *          {Function} registerEvent
 | 
						|
   *            the registration function used for priming events
 | 
						|
   */
 | 
						|
  _getInternalSettingsAPI(params) {
 | 
						|
    let {
 | 
						|
      extensionId,
 | 
						|
      context,
 | 
						|
      extension,
 | 
						|
      module,
 | 
						|
      name,
 | 
						|
      callback,
 | 
						|
      storeType,
 | 
						|
      readOnly = false,
 | 
						|
      onChange,
 | 
						|
      validate,
 | 
						|
    } = params;
 | 
						|
    if (context) {
 | 
						|
      extension = context.extension;
 | 
						|
    }
 | 
						|
    if (!extensionId && extension) {
 | 
						|
      extensionId = extension.id;
 | 
						|
    }
 | 
						|
 | 
						|
    const checkScope = details => {
 | 
						|
      let { scope } = details;
 | 
						|
      if (scope && scope !== "regular") {
 | 
						|
        throw new ExtensionError(
 | 
						|
          `Firefox does not support the ${scope} settings scope.`
 | 
						|
        );
 | 
						|
      }
 | 
						|
    };
 | 
						|
 | 
						|
    // Check the setting for anything we may need.
 | 
						|
    let setting = settingsMap.get(name);
 | 
						|
    readOnly = readOnly || !!setting?.readOnly;
 | 
						|
    validate = validate || setting?.validate || (() => {});
 | 
						|
    let getValue = callback || setting?.getCallback;
 | 
						|
    if (!getValue || typeof getValue !== "function") {
 | 
						|
      throw new Error(`Invalid get callback for setting ${name} in ${module}`);
 | 
						|
    }
 | 
						|
 | 
						|
    let settingsAPI = {
 | 
						|
      async get(details) {
 | 
						|
        validate(extension);
 | 
						|
        let levelOfControl = details.incognito
 | 
						|
          ? "not_controllable"
 | 
						|
          : await ExtensionPreferencesManager.getLevelOfControl(
 | 
						|
              extensionId,
 | 
						|
              name,
 | 
						|
              storeType
 | 
						|
            );
 | 
						|
        levelOfControl =
 | 
						|
          readOnly && levelOfControl === "controllable_by_this_extension"
 | 
						|
            ? "not_controllable"
 | 
						|
            : levelOfControl;
 | 
						|
        return {
 | 
						|
          levelOfControl,
 | 
						|
          value: await getValue(),
 | 
						|
        };
 | 
						|
      },
 | 
						|
      set(details) {
 | 
						|
        validate(extension);
 | 
						|
        checkScope(details);
 | 
						|
        if (!readOnly) {
 | 
						|
          return ExtensionPreferencesManager.setSetting(
 | 
						|
            extensionId,
 | 
						|
            name,
 | 
						|
            details.value
 | 
						|
          );
 | 
						|
        }
 | 
						|
        return false;
 | 
						|
      },
 | 
						|
      clear(details) {
 | 
						|
        validate(extension);
 | 
						|
        checkScope(details);
 | 
						|
        if (!readOnly) {
 | 
						|
          return ExtensionPreferencesManager.removeSetting(extensionId, name);
 | 
						|
        }
 | 
						|
        return false;
 | 
						|
      },
 | 
						|
      onChange,
 | 
						|
    };
 | 
						|
    let registerEvent = fire => {
 | 
						|
      let listener = async () => {
 | 
						|
        fire.async(await settingsAPI.get({}));
 | 
						|
      };
 | 
						|
      Management.on(`extension-setting-changed:${name}`, listener);
 | 
						|
      return {
 | 
						|
        unregister: () => {
 | 
						|
          Management.off(`extension-setting-changed:${name}`, listener);
 | 
						|
        },
 | 
						|
        convert(_fire) {
 | 
						|
          fire = _fire;
 | 
						|
        },
 | 
						|
      };
 | 
						|
    };
 | 
						|
 | 
						|
    // Any caller using the old call signature will not have passed
 | 
						|
    // context to us.  This should only be experimental addons in the
 | 
						|
    // wild.
 | 
						|
    if (onChange === undefined && context) {
 | 
						|
      // Some settings that are read-only may not have called addSetting, in
 | 
						|
      // which case we have no way to listen on the pref changes.
 | 
						|
      if (setting) {
 | 
						|
        settingsAPI.onChange = new lazy.ExtensionCommon.EventManager({
 | 
						|
          context,
 | 
						|
          module,
 | 
						|
          event: name,
 | 
						|
          name: `${name}.onChange`,
 | 
						|
          register: fire => {
 | 
						|
            return registerEvent(fire).unregister;
 | 
						|
          },
 | 
						|
        }).api();
 | 
						|
      } else {
 | 
						|
        Services.console.logStringMessage(
 | 
						|
          `ExtensionPreferencesManager API ${name} created but addSetting was not called.`
 | 
						|
        );
 | 
						|
      }
 | 
						|
    }
 | 
						|
    return { api: settingsAPI, registerEvent };
 | 
						|
  },
 | 
						|
};
 |