This article needs a technical review. How you can help.
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).
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 ingetComplexValue()
when there's no user value for the given preference; see below for details. -
nsILocalFile
andnsIRelativeFilePref
-
Store paths in preferences.
nsILocalFile
is used to store absolute paths, whilensIRelativeFilePref
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:
- Add this line to some
.properties
file (for all of your locales), say tochrome://myext/locale/defaults.properties
:extensions.myext.welcomemessage=Localized default value
- 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");
- Read the preference with
getComplexValue
, passingnsIPrefLocalizedString
asaType
: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:
- Checks whether the current tree has a value for the preference and whether or not the preference is locked.
- If there's a value of the correct type (for example,
getBoolValue()
expects a value of typensIPrefBranch.PREF_BOOL
), and the preference is not locked, the method returns that value. - If there's a value of the wrong type and the preference is not locked, an
NS_ERROR_UNEXPECTED
exception is thrown. - 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. - If there's a value of the expected type in the default tree, it is returned (with the only exception being that calling
getComplexValue()
withaType
parameter specified asnsIPrefLocalizedString
, described above). - 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.
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);
// 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 error 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:
https://mozilla.doslash.org/prefutils
chrome://global/content/nsUserSettings.js
https://wiki.mozilla.org/Labs/JS_Modules
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! }
Examples
- GitHub - Gist :: _ff-addon-template-bootstrapPrefsSkeleton - This Gist here is a fully working example of a fully funcitonal preferences skeleton, it uses the observer example from above. This example also uses a technique to trigger an onChange function that can be defined per preference and has the old value and along with the new value of the preference. It also uses a technique so you can just do prefs.preferenceName.setval('blah') instead of having to branch.setCharPref etc.
Resources
- Other documentation on preferences
- Preferences API
- A Brief Guide to Mozilla Preferences — describes preferences system from user's/administrator's POV
- Mozilla XPCOM interfaces of the preferences system
- Most used interfaces (these are frozen and will not change):
nsIPrefBranch
andnsIPrefService
nsIPrefBranch2
interface (before Gecko 1.8 it was callednsIPrefBranchInternal
)
- Most used interfaces (these are frozen and will not change):
- Preferences System - an easy way to create a XUL Options window for your extension or application
- Syncing preferences across clients using Sync
- LXR pages for libpref, the source code module that implements the preferences system
- A JavaScript wrapper for preferences API
- Adding preferences to an extension — a simple tutorial with a working extension that illustrates the use of preference observers
- Inline Options - How to use the new preference UI that appears inline in the Add-on Manager window starting in Firefox 7 .