Visit Mozilla.org

Code snippets:Preferences

From MDC

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 XULPlanet and on mozilla.org (links below in Resources section).

Note: This article doesn't cover all available methods for manipulating preferences; please refer to the XULPlanet 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.

Contents

[edit] 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 list of preferences-related interfaces.

Three used interfaces are nsIPrefService, nsIPrefBranch and nsIPrefBranch2. They are frozen and will not change.

It's worth noting that there also is an nsIPref interface. Despite it being used in some places, it is deprecated and should not be used.

The preferences service is instantiated in the same way you instantiate any XPCOM service; see the article Creating XPCOM Components at XULPlanet for details.) 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.");

[edit] 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:

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

[edit] Complex types

As noted in 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.

[edit] 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);

[edit] 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 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);

[edit] nsILocalFile and nsIRelativeFilePref

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

[edit] Default preferences

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, 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.

[edit] 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)

[edit] Where the default values are read from

  • All Mozilla-based applications read (application directory)/defaults/pref/*.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 usually (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);

[edit] 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.

[edit] 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.

[edit] 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 nsIPrefBranchInternal 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 1.8 development, nsIPrefBranchInternal was renamed to nsIPrefBranch2 [1] and was frozen. nsIPrefBranchInternal name is still supported in Gecko 1.8, so this is what you should use in extensions that need to be compatible with Gecko 1.7 and Gecko 1.8 (Firefox 1.0/1.5). For newer extensions use nsIPrefBranch2.

Here's an example:

var myPrefObserver =
{
  register: function()
  {
    var prefService = Components.classes["@mozilla.org/preferences-service;1"]
                                .getService(Components.interfaces.nsIPrefService);
    this._branch = prefService.getBranch("extensions.myextension.");
    this._branch.QueryInterface(Components.interfaces.nsIPrefBranch2);
    this._branch.addObserver("", this, false);
  },

  unregister: function()
  {
    if(!this._branch) return;
    this._branch.removeObserver("", this);
  },

  observe: function(aSubject, aTopic, aData)
  {
    if(aTopic != "nsPref:changed") return;
    // 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();

Here's the code I like to use, as it makes for much better code reuse both within a project (because you can easily create multiple listeners without duplicating the code) and across projects (I put it in a common include file.) It's pretty similar to the previous example.

function prefListener(branchName, func)
{
    var prefService = Components.classes["@mozilla.org/preferences-service;1"]
                                .getService(Components.interfaces.nsIPrefService);
    var branch = prefService.getBranch(branchName);
    branch.QueryInterface(Components.interfaces.nsIPrefBranch2);

    this.register = function()
    {
        branch.addObserver("", this, false);
        branch.getChildList("", { })
              .forEach(function (name) { func(branch, name); });
    };

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

    this.observe = function(subject, topic, data)
    {
        if (topic == "nsPref:changed")
            func(branch, data);
    };
}

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();

nsIPrefBranch2.idl has more documentation. For a working example, see the StockWatcher2 tutorial "Adding preferences to an extension", linked below in the Resources section.

[edit] 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 nonexistent preference using one of 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.

[edit] 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.

[edit] JavaScript wrappers for preferences system

There are a few JavaScript wrappers to make your life easier, such as the one found at http://mozilla.doslash.org/prefutils and the nsPreferences wrapper included with Firefox and Thunderbird (chrome://global/content/nsUserSettings.js).

[edit] How to Save Preferences

Here is the secret recipe for saving preferences into the default location:

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

[edit] Resources