isn't quite ashamed enough to present

jr conlin's ink stained banana

2008-10-06

::Removing Mozilla Preferences

This took far longer to figure out than i wanted.

So, continuing the semi-regular series of "building mozilla firefox add-on extensions" that i'm doing, let's talk about preferences. Or, more importantly, how to do the one thing that Mozilla apparently doesn't want you to do, delete them.

To understand how to delete them, i'm going to do what's fundamental to computer science and teach you things you had absolutely no interest in ever learning. This means a bit of a history lesson.

Preferences are a bunch of customized values. There's stored in several places including the javascript file "prefs.js", located in your Mozilla Profile directory. If you look at said file, you'll see some lines that look like
user_pref("foo.bar.gorp",0);
user_pref("foo.bar.spoo","banana");
user_pref("foo.bar.flurg",false);

Let me note that the user_pref() function is absolutely no help because all that value does is tell firefox what value to store in it's in memory hash. Said in memory hash is then written back out to prefs.js when the last instance of firefox exists. This is important to know because it means that anything you change in that file will get overwritten unless there are no instances of firefox currently running.

Although the preferences are stored flat, each "." is considered a branch, so in the above example, "foo" has a "bar" branch with three node values under it.

So, how do you create and set values from inside of firefox? Well, there are two ways. The first (and oldest) is an insanely hairy method that involves calling XPCOM functions:
// get the preferences object for the "my.settings" branch.
var preferences = Components.classes['@mozilla.org/preferences-service;1'].getService(Components.interfaces.nsIPrefService).getBranch('my.settings');
// set the "spoo" node to "kumquat"
preferences.setCharPref("spoo","kumquat");

If you were to exit Firefox and look at the "prefs.js" file you'd now see
user_pref("foo.bar.spoo","kumquat");

The more astute will note that i called setCharPref(). That's because there's two other set*Pref functions, setIntPref() and setBoolPref(). There's matching get*Pref() functions and a helpful getPrefType() function which returns a value to describe what kind of value this is.

There are some serious drawbacks to using this, though, since there's the ever present threat of name collision (what if someone else picked "foo" as their start point?), the whole typing thing, and the copious amount of swearing involved in getting things working.

i'm not going to go into great detail about that because that's the old, hairy way of doing things and now there's a much better way. It's called FUEL. FUEL is a wonderful set of classes that GREATLY simplify stuff like getting parameters. A point only further proven because it's practically hidden on the mozilla developer site. To do a similar operation as above, you simply do:
Application.extensions.get(extentionId).prefs.setValue("spoo","kumquat");
extentionId is whatever your extension is named. As an added bonus, it tosses all of your extension based preferences under the extensions.extensionId., ensuring that unless you've named an extension the same thing as someone else, you're not going to collide with any values. It also auto-types the values you pass it so all you need are getValue() and setValue(). You can even call the method all to get all the child objects (nodes and branches) under your root branch.

Of course, we're talking mozilla here, so while things are certainly easier with FUEL, they're not perfect.

So, how do you delete a preference value? Well, you can't do it using FUEL. No, sadly, to do that, you need to go back to the old, nasty way of doing things. (Thus, the history lesson.)

The first thing you'll need to do is figure out the "branch" you want to delete. Let's say i've got an extension named "foo" with an extensionId of "foo@example.org". i want to delete everything under the "bar" branch. According to FUEL, that means that the root for that extension is "extensions.foo@example.org" + the extension branch we're interested in "bar". That gives us:
Components.classes['@mozilla.org/preferences-service;1'].getService(Components.interfaces.nsIPrefService).getBranch('extensions.foo@example.org.').deleteBranch('bar');

Likewise, if you wanted to just delete the bar.spoo value, you'd call:
Components.classes['@mozilla.org/preferences-service;1'].getService(Components.interfaces.nsIPrefService).getBranch('extensions.foo@example.org.').deleteBranch('bar.spoo');

A few things to note:
1. While not required, i've found that suffixing a "." to the end of the getBranch() path seems to help in resolving elements.
2. The getBranch() call apparently defines the prefix for the path (as well as get a stateful object to speed things along), so if you don't specify the content of deleteBranch(), it nukes everything beneath that path. You may want to use
Components.classes['@mozilla.org/preferences-service;1'].getService(Components.interfaces.nsIPrefService).getBranch('extensions.foo@example.org.bar.spoo').deleteBranch(");
to nuke the branch you desire.

Looks like i missed a step when i did this. You'll also need to flush the preferences to disk and re-read them back in order to clear the in-memory hashes.
var nsIPrefs = Components.classes['@mozilla.org/preferences-service;1'].getService(Components.interfaces.nsIPrefService);
nsIPrefs.savePrefFile(null);
nsIPrefs.readUserPrefs(null);

You'll also need to refresh whatever local caches you're keeping for the data.
DON'T CALL .resetPrefs() OR .resetUserPrefs() since these will nuke whatever preferences you've previously set. In other words, they blank the prefs.js file.

In some fairness, the nsIPrefBranch XPCom object provides a LOT more functions than FUEL does, but it's a pain in the ass to use. i wish like hell they'd add a companion "removeValue()" method to FUEL for this sort of thing.

DaveP
2008-10-07 - 03:21:51

But isn't Mozilla open source? Why don't you just go add a removeValue method to FUEL yourself?

And I'm only trying to be snarky a little bit. This is one of the pieces of open source interaction I just don't get. How does someone submit a change that most people would agree will be useful without spending three years "getting accepted by the community" ?


jrconlin
2008-10-07 - 07:39:33

Actually, Dave, you raise a really good point. I should try creating and submitting this to Mozilla. My previous experiences with Large Scale Efforts have been painful, but considering how fractured things feel with Mozilla, that may not be the case.

heh, who knew that raging inefficiency and lack of coherence could be a good thing?


DaveP
2008-10-08 - 03:01:00

Well, if it works out, let us know. I'd like to see it work…


Hey, delicious user, Save This Page
Blogs of note
personal that's my blog
(The Official Blog of the Internet)
memoirs of hydrogen guy matthew shepherd (quebec) rhapsodic.org j$ (right) Henriette's Herbal Blog fanatical apathy lynne ydw i iconophobia slumbering lungfish
geek Y!Cool Thing michael j radwin jeremy z
(The Official Website of the Internet)
dave's picks ultramookie Josh Woodward derek balling j$ (left) simon willison Yahoo! Search Blog
news ars technica search engine watch webmaster world.com
forums uh.net man-man killroy & tina

experimental

Firefox search plugins for Yahoo!

My Living Room media box config

The Official "Official" Registry of the Internet

Powered by WordPress
Hosted on Dreamhost.
And Steveo's page is Totally Fucking Awsome.