Preferences

This article is in need of a technical review.

This article provides examples for extension developers that wish to use the Mozilla preferences system. Information here applies to the Mozilla Suite, Firefox, Thunderbird, and possibly other Mozilla-based applications. For more details on preferences in Mozilla, see Preferences System.

If you haven't yet, read other documents about Mozilla preferences on MDN (links below in Resources section).

Note: This article doesn't cover all available methods for manipulating preferences; please refer to the XPCOM reference pages listed in Resources section for the complete list of methods. The interfaces dealing with preferences are fairly well documented, so using the methods not mentioned here should be straightforward.

XPCOM interfaces for preferences system

Mozilla exposes its preferences system through a few XPCOM interfaces. Look in the Resources section below for the link to a list of preferences-related interfaces.

Two used interfaces are nsIPrefService and nsIPrefBranch.

The preferences service is instantiated in the same way you instantiate any XPCOM service. To get an nsIPrefBranch, either QueryInterface() the pref service (that will give you the root branch) or call nsIPrefService.getBranch() to get a sub-branch.

Here are two examples:

// Get the root branch
var prefs = Components.classes["@mozilla.org/preferences-service;1"]
                    .getService(Components.interfaces.nsIPrefBranch);
// Get the "extensions.myext." branch
var prefs = Components.classes["@mozilla.org/preferences-service;1"]
                    .getService(Components.interfaces.nsIPrefService);
prefs = prefs.getBranch("extensions.myext.");

Simple types

There are three types of preferences: string, integer, and boolean. Each entry in the preferences database (prefs.js) has one of those types. There are six methods in nsIPrefBranch that read and write preferences: getBoolPref(), setBoolPref(), getCharPref(), setCharPref(), getIntPref(), and setIntPref(). Using them is as easy as:

// Get the "accessibility." branch
var prefs = Components.classes["@mozilla.org/preferences-service;1"]
                    .getService(Components.interfaces.nsIPrefService).getBranch("accessibility.");

// prefs is an nsIPrefBranch.
// Look in the above section for examples of getting one.
var value = prefs.getBoolPref("typeaheadfind"); // get a pref (accessibility.typeaheadfind)
prefs.setBoolPref("typeaheadfind", !value); // set a pref (accessibility.typeaheadfind)

Complex types

As noted in the previous section, each entry in the preferences database (prefs.js) must have a string, an integer, or a boolean value. However, there is a concept of complex types, which makes it easier for developers to save and load nsILocalFile and nsISupportsString objects in preferences (as strings — note that from the preferences system's point of view, complex values have a nsIPrefBranch.PREF_STRING type.)

There are two nsIPrefBranch methods implementing the concept — setComplexValue() and getComplexValue(). You can look up their implementations in nsPrefBranch.cpp. Here are the IDL definitions:

void getComplexValue(in string aPrefName, in nsIIDRef aType, 
                     [iid_is(aType), retval] out nsQIResult aValue);
void setComplexValue(in string aPrefName, in nsIIDRef aType, in nsISupports aValue);

As you can see, both of them take a parameter, aType, which can have one of the following values (to be precise, you should pass Components.interfaces.nsIWhatever  instead of just nsIWhatever, which is undefined).

nsISupportsString
Used to handle Unicode strings in preferences. Use this when the preference value may contain non-ASCII characters (for example, a user's name).
nsIPrefLocalizedString
Almost the same as nsISupportsString, but it is handled differently in getComplexValue() when there's no user value for the given preference; see below for details.
nsILocalFile and nsIRelativeFilePref
Store paths in preferences. nsILocalFile is used to store absolute paths, while nsIRelativeFilePref is used to store paths relative to a "special" directory, such as the profile folder.

nsISupportsString

As noted above, this is used to handle Unicode strings in preferences. Example:

// prefs is an nsIPrefBranch

// Example 1: getting Unicode value
var value = prefs.getComplexValue("preference.with.non.ascii.value",
      Components.interfaces.nsISupportsString).data;

// Example 2: setting Unicode value
var str = Components.classes["@mozilla.org/supports-string;1"]
      .createInstance(Components.interfaces.nsISupportsString);
str.data = "some non-ascii text";
prefs.setComplexValue("preference.with.non.ascii.value", 
      Components.interfaces.nsISupportsString, str);

nsIPrefLocalizedString

Another complex type supported by Mozilla is nsIPrefLocalizedString. It is similar to nsISupportsString, except that when there is no user value, getComplexValue() gets the default value from a locale file (thus making the default value localizable).

It's easier to explain this by example. Let's say you want to make the default value for the extensions.myext.welcomemessage preference localizable. You should do the following:

  1. Add this line to some .properties file (for all of your locales), say to chrome://myext/locale/defaults.properties:
    extensions.myext.welcomemessage=Localized default value
  2. Add the default value for extensions.myext.welcomemessage, pointing to that properties file, by adding the following line to your file with default preferences (see below).
    pref("extensions.myext.welcomemessage", "chrome://myext/locale/defaults.properties");
  3. Read the preference with getComplexValue, passing nsIPrefLocalizedString as aType:
    var prefs = Components.classes["@mozilla.org/preferences-service;1"]
                          .getService(Components.interfaces.nsIPrefService);
    var branch = prefs.getBranch("extensions.myext.");
    var value = branch.getComplexValue("welcomemessage",
          Components.interfaces.nsIPrefLocalizedString).data;
    

The code in step 3 will read the default value from chrome://myext/locale/defaults.properties when no user value is set, and will behave exactly the same as if nsISupportsString was passed as aType otherwise.


Setting nsIPrefLocalizedString preferences is similar to setting nsISupportsString:

var pls = Components.classes["@mozilla.org/pref-localizedstring;1"]
                    .createInstance(Components.interfaces.nsIPrefLocalizedString);
pls.data = val;
prefs.setComplexValue("preference.with.non.ascii.value", 
                      Components.interfaces.nsIPrefLocalizedString, pls);

nsILocalFile and nsIRelativeFilePref

Please see the File IO article for details on nsILocalFile and nsIRelativeFilePref.

Default preferences

FIXME: someone should reword this section Each preference may have up to two values — the current value and the default value. That means there are two "pref trees:" current and default, and each of them may or may not have a value for the preference in question.

You can see the list of preferences in about:config (where available). Preferences that have a user value are bold, and those that don't have a user value are printed in normal font.

You can get both trees using the nsIPrefService.getBranch() and nsIPrefService.getDefaultBranch() functions. See below for details.

The effect of default preferences on get methods

When one of the get methods of nsIPrefBranch (assuming it's a branch of the tree with current values) is called, it does the following:

  1. Checks whether the current tree has a value for the preference and whether or not the preference is locked.
  2. If there's a value of the correct type (for example, getBoolValue() expects a value of type nsIPrefBranch.PREF_BOOL), and the preference is not locked, the method returns that value.
  3. If there's a value of the wrong type and the preference is not locked, an NS_ERROR_UNEXPECTED exception is thrown.
  4. If the preference is locked or if there is no value for that preference in the current tree, the get method checks the default tree.
  5. If there's a value of the expected type in the default tree, it is returned (with the only exception being that calling getComplexValue() with aType parameter specified as nsIPrefLocalizedString, described above).
  6. Otherwise an NS_ERROR_UNEXPECTED exception is thrown.

If the branch is from the default tree, the get method doesn't check the tree with current values at all.

(This is not exactly how it's coded in libpref, but it's equivalent)

Where the default values are read from

  • All Mozilla-based applications read (application directory)/defaults/preferences/*.js
  • In addition to that, recent versions of Toolkit applications (Firefox 1.0, Thunderbird 1.0, and the like but not the Mozilla Suite) read extension defaults -- usually located in (profile folder)/extensions/(ID)/defaults/preferences/

These files use simple JavaScript-like syntax. To add a default value for a preference, you should add a line like this to your default preferences file:

pref("extensions.extensionname.preferencename", false);

How to install an extension's defaults files

For Mozilla Suite (not Firefox and Thunderbird), copy them to (appdir)/defaults/pref in your install script.

For Firefox/Thunderbird, just put them in myext.xpi/defaults/preferences/. They will be copied and registered with the preferences system automatically.

More about preferences "branches"

Preference names consist of a few strings separated with dots, and related preferences usually share the same prefix. For example, most accessibility preferences in Mozilla start with "accessibility."

This means that all existing preferences can be imagined as if they were in a tree, like this:

+
|
+-- accessibility
|         |
|         +-- typeaheadfind
|         |         |
|         |         +-- autostart (accessibility.typeaheadfind.autostart)
|         |         |
|         |         +-- enablesound (accessibility.typeaheadfind.enablesound)
|         |
|         +-- usebrailledisplay (accessibility.usebrailledisplay)
|
+-- extensions
          |
          +-- lastAppVersion (extensions.lastAppVersion)

This is the metaphor behind nsIPrefBranch. However, you should be aware of the fact that the Mozilla preferences system doesn't treat dots in a special way. For example this code will also read the value of accessibility.typeaheadfind.enablesound preference:

var prefs = Components.classes["@mozilla.org/preferences-service;1"]
                      .getService(Components.interfaces.nsIPrefService);
var branch = prefs.getBranch("acce");
var enablesound = branch.getBoolPref("ssibility.typeaheadfind.enablesound");

This is the reason why you should usually pass strings ending with a dot to getBranch(), like prefs.getBranch("accessibility.").

Another caveat you should be aware of is that nsIPrefBranch.getChildList("",{}) returns an array of preference names that start with that branch's root, for example

var branch = prefs.getBranch("accessibility.");
var children = branch.getChildList("", {});

will return these items (for the example tree above): "typeaheadfind.autostart", "typeaheadfind.enablesound", and "usebrailledisplay", not just direct children ("typeaheadfind" and "usebrailledisplay"), as you might have expected.

Using preference observers

Changes a user makes to your extension's preferences, such as through an options dialog, may not take effect until the browser is restarted (e.g., if you have initialized local variables when the browser loads). You may wish for such changes to be applied immediately in your extension. In this case, you can use the nsIPrefBranch2 interface to "listen" for changes to preferences in a certain branch. Note that it need not be your own extension's preferences; you can set an observer on any preference or branch. When a change is made to the preferences, you can take the appropriate action (such as reinitializing variables or toggling display properties in XUL components). This technique will work no matter how or where the preferences are changed: in another browser window, directly in the about:config interface, or even by another extension.

Note: During Gecko 13 development, nsIPrefBranch2 was deprecated, and its methods moved to nsIPrefBranch. Calling .QueryInterface(Components.interfaces.nsIPrefBranch2) is no longer required, although it still works.

Here is a straightforward example:

var myPrefObserver = {
  register: function() {
    // First we'll need the preference services to look for preferences.
    var prefService = Components.classes["@mozilla.org/preferences-service;1"]
                                .getService(Components.interfaces.nsIPrefService);

    // For this.branch we ask for the preferences for extensions.myextension. and children
    this.branch = prefService.getBranch("extensions.myextension.");

    // Now we queue the interface called nsIPrefBranch2. This interface is described as:  
    // "nsIPrefBranch2 allows clients to observe changes to pref values."
    // This is only necessary prior to Gecko 13
    if (!("addObserver" in this.branch))
        this.branch.QueryInterface(Components.interfaces.nsIPrefBranch2);

    // Finally add the observer.
    this.branch.addObserver("", this, false);
  },

  unregister: function() {
    this.branch.removeObserver("", this);
  },

  observe: function(aSubject, aTopic, aData) {
    // aSubject is the nsIPrefBranch we're observing (after appropriate QI)
    // aData is the name of the pref that's been changed (relative to aSubject)
    switch (aData) {
      case "pref1":
        // extensions.myextension.pref1 was changed
        break;
      case "pref2":
        // extensions.myextension.pref2 was changed
        break;
    }
  }
}
myPrefObserver.register();

And next, here is a more evolved version of the previous code better fit for code reuse both within a project and across projects (for example, using JavaScript code modules):

/**
 * @constructor
 *
 * @param {string} branch_name
 * @param {Function} callback must have the following arguments:
 *   branch, pref_leaf_name
 */
function PrefListener(branch_name, callback) {
  // Keeping a reference to the observed preference branch or it will get
  // garbage collected.
  var prefService = Components.classes["@mozilla.org/preferences-service;1"]
    .getService(Components.interfaces.nsIPrefService);
  this._branch = prefService.getBranch(branch_name);
  this._branch.QueryInterface(Components.interfaces.nsIPrefBranch2);
  this._callback = callback;
}

PrefListener.prototype.observe = function(subject, topic, data) {
  if (topic == 'nsPref:changed')
    this._callback(this._branch, data);
};

/**
 * @param {boolean=} trigger if true triggers the registered function
 *   on registration, that is, when this method is called.
 */
PrefListener.prototype.register = function(trigger) {
  this._branch.addObserver('', this, false);
  if (trigger) {
    let that = this;
    this._branch.getChildList('', {}).
      forEach(function (pref_leaf_name)
        { that._callback(that._branch, pref_leaf_name); });
  }
};

PrefListener.prototype.unregister = function() {
  if (this._branch)
    this._branch.removeObserver('', this);
};

var myListener = new PrefListener(
  "extensions.myextension.",
  function(branch, name) {
    switch (name) {
      case "pref1":
        // extensions.myextension.pref1 was changed
        break;
      case "pref2":
        // extensions.myextension.pref2 was changed
        break;
    }
  }
);

myListener.register(true);
Note: You need to keep a reference to the preference branch you are observing (unless it is the root branch) or it will get garbage collected and will never notify you. After it's garbage collected, you won't receive notifications anymore, so use the provided examples above, and don't write something like the following code:
// DON'T DO THIS
Components.classes["@mozilla.org/preferences-service;1"]
    .getService(Components.interfaces.nsIPrefService)
    .getBranch("myextension.prefs.")
    .QueryInterface(Components.interfaces.nsIPrefBranch2)
    .addObserver("", this, false);

Using prefHasUserValue()

nsIPrefBranch.prefHasUserValue(preference) checks whether the preference has been changed from the default value. If so, it will return true, otherwise false. In particular, when no default value exists for a preference, prefHasUserValue() indicates whether a preference exists.

Attempting to read a nonexistent preference using one of the get*Pref methods will throw an exception. Using prefHasUserValue() lets you check if the preference exists before attempting to read it. For example:

if (prefs.prefHasUserValue("mypref")) {
  alert(prefs.getCharPref("mypref"));
}

Note that the getCharPref() call may throw even if the preference exists, for example if it has a different type.

Using preferences in extensions

If you're writing your extension for one of the Toolkit applications (Firefox, Thunderbird, Nvu), you should provide default values for your extension's preferences (see above for information on how to do it). It has the following benefits:

  • You don't have to duplicate default values in various parts of your code.
  • The code for reading preferences is simplified, since you don't need to worry about the get methods throwing exceptions.

JavaScript wrappers for preferences system

There are a few JavaScript wrappers to make your life easier:

How to save preferences

To save preferences into the default location:

var prefService = Components.classes["@mozilla.org/preferences-service;1"]
                               .getService(Components.interfaces.nsIPrefService);
prefService.savePrefFile(null);

Checking for existence of a key

If you try to get the value of a nonexistent preference, an error will be thrown:

Error: NS_ERROR_UNEXPECTED: Component returned failure code: 0x8000ffff (NS_ERROR_UNEXPECTED) [nsIPrefBranch.getCharPref]

To avoid such errors, you should check whether the key exists or not using nsIPrefService.getPrefType(), as shown below:

var prefServiceBranch = Components.classes["@mozilla.org/preferences-service;1"]
                               .getService(Components.interfaces.nsIPrefService).getBranch("");
if(prefServiceBranch.getPrefType('extensions.myext.key')){
    //key exist!
}

Resources

FIXME: