MozillaZine

Need help understanding Extension Preferences

Discuss building things with or for the Mozilla Platform.
mesh
 
Posts: 61
Joined: November 28th, 2004, 12:54 pm
December 1st, 2004, 5:18 pm

Post Posted December 1st, 2004, 5:18 pm

Does anyone have any links that show how to handle preferences when building an extension for FireFox?

I know how to access and store preferences:

http://www.xulplanet.com/tutorials/xulqa/q_prefs.html

but, I am not sure if my extension is supposed to use this. i.e. do i just set my own name space and have my extension's preferences mingle with FireFox's preferences?

Also, is there a way to set the default preferences for my extension?

I notice that a couple of other extensions have a

defaults/preferences/appnameprefs.js

file in their install that contains preferences. Does that automatically get parsed on install? (my tests seem to indicate that it doesnt)?

thanks for any help or tips...

mike chambers

asqueella
 
Posts: 3996
Joined: November 16th, 2003, 3:05 am
Location: Russia, Moscow
December 2nd, 2004, 3:36 am

Post Posted December 2nd, 2004, 3:36 am

Yes, you should use nsIPrefBranch or its JS wrappers, like nsPreferences or prefs utils from JSLib.
Be sure to make your prefs names begin with an unique string, like extensions.myextensionname.* (you should then get an nsIPrefBranch for "extensions.myextensionname.").

Prefs from defaults/preferences/appnameprefs.js are parsed into "default" branch (returned by nsIPrefService.getDefaultBranch()).

Most people use nsIPrefBranch or its js wrapper, nsPreferences.

---------
I think the way prefs are implemented now is not very convenient for JS extensions authors. I'm now trying to write a good wrapper for prefs. (I have already had a few unsuccessful attempts.)

(nsPreferences is not very good, because default values for prefs should be passed as params to getPref(), and default branch is not used.)

Here's what I have now:
Code: Select all
function PrefsWrapper_v1(aBranch)
{
  this.branch = aBranch;
  var prefs = Components.classes["@mozilla.org/preferences-service;1"].
              getService(Components.interfaces.nsIPrefService);
  this.userPrefs = prefs.getBranch(this.branch);
  this.defaultPrefs = prefs.getDefaultBranch(this.branch);
}

PrefsWrapper_v1.prototype = {
  nsISupportsString: Components.interfaces.nsISupportsString,
  _getInternal: function(aPrefName, aMethod) {
    try {
      return this.userPrefs[aMethod](aPrefName);
    } catch(e) {}
    try {
      return this.defaultPrefs[aMethod](aPrefName);
    } catch(e) {}
  },

  get: function(aPrefName, aType) {
    if(aType == "Unichar") {
      try {
        return this.userPrefs.getComplexValue(aPrefName, this.nsISupportsString).data;
      } catch(e) {}
      try {
        return this.defaultPrefs.getComplexValue(aPrefName, this.nsISupportsString).data;
      } catch(e) {}
      return ""; // if everything else fails, return empty string
    }
    return this._getInternal(aPrefName, "get" + aType + "Pref");
  },

  set: function(aPrefName, aValue, aType) {
    if(aType == "Unichar") {
      var str = Components.classes["@mozilla.org/supports-string;1"]
                .createInstance(this.nsISupportsString);
      str.data = aValue;
      this.userPrefs.setComplexValue(aPrefName, this.nsISupportsString, str);
    }
    try {
      this.userPrefs["set" + aType](aPrefName, aValue);
    } catch(e) {}
  }
}

var InfoListerPrefs = new PrefsWrapper_v1("extensions.infolister.");

InfoListerPrefs.get("ouputmode", "Int");
var oneList = InfoListerPrefs.get("onelist", "Bool");
var domifix = InfoListerPrefs.get("domi_workaround", "Bool");
var filename = InfoListerPrefs.get("filename", "Unichar"); //xxx add support for nsIFile-type prefs
var charset = InfoListerPrefs.get("charset", "Char");


A few comments:
  • Passsing pref type as param minimizes the number of similar functions (and code duplication) in the wrapper, maybe I should instead include type in function name: getBool, getChar, getUnichar etc. This will make the wrapper much longer, but much more straightforward.
  • I originally tried to use untyped prefs - using nsIPrefBranch.getPrefType and typeof operator - to have a single get() method and a single set() method. That didn't work, because getPrefType can't tell Unichar pref from nsIFile pref and simple char pref. (This is IMO a flaw in mozilla prefs design)
  • I also tried to store the prefs type in the wrapper itself, but it makes the wrapper quite ugly and hard to mantain.


I would like to hear suggestions from other extension devs. It would be nice to have a standart and really convenient prefs wrapper for everybody to use.

clav
 
Posts: 1974
Joined: November 5th, 2002, 3:25 am
Location: Lancaster, UK
December 2nd, 2004, 11:31 am

Post Posted December 2nd, 2004, 11:31 am

I don't see that your wrapper offers any advantages over creating an nsIPrefBranch in the usual way, other than the slightly nicer syntax for getting a pref branch (or an object rather like one, anyway). Having built-in try-catch blocks strikes me as a flaw, since, now that extensions can easily provide their own default pref files, a failure to read a pref is probably a programming error.


The following might make a nice minimal wrapper:
(i.e. it's just a shorthand way of creating a normal nsIPrefBranch)

Code: Select all
function PrefBranch(branchStr) {
  var prefsvc = Components.classes[...].getService(...nsIPrefService);
  this.__proto__ = prefsvc.getPrefBranch(branchStr);
}


(I haven't tested it, but setting __proto__ in a constructor does work for non-XPCOM objects at least.)

mesh
 
Posts: 61
Joined: November 28th, 2004, 12:54 pm
December 2nd, 2004, 12:02 pm

Post Posted December 2nd, 2004, 12:02 pm

Well, here is what I am doing right now:

Code: Select all

function initializePreferences()
{
   var prefs = Components.classes["@mozilla.org/preferences-service;1"]. getService(Components.interfaces.nsIPrefBranch);   
   
   try
   {
      this.trimTitle = prefs.getBoolPref("appname.trimTitle");
   }
   catch (exception)
   {
      prefs.setBoolPref("appname.trimTitle", this.trimTitle);
   }
}



The gets run whenever the extension loads. Basically, it checks to see if the preferences is in the preferences, if not, it saves the preference to the default value.

If I need to reference the preference, it is now stored locally, and I don't have to go back to the preferences system. Of course, this means that if someone changes the prefs in any way aside from my preferences window (such as about:config) the settings won't take affect until a restart. This is a downside, but I wanted to not have to go to the pref system every time i needed a setting as that seemed like it would be a pretty expensive process.

I had to use a try / catch because I could not figure out how to check to see if a name / value existed in the preferences.

Again, not sure if this is a "good" practice. I am trying to figure out the best way, which is why I started the thread.

mike chambers

mesh
 
Posts: 61
Joined: November 28th, 2004, 12:54 pm
December 2nd, 2004, 12:08 pm

Post Posted December 2nd, 2004, 12:08 pm

[quote="asqueella"
Prefs from defaults/preferences/appnameprefs.js are parsed into "default" branch (returned by nsIPrefService.getDefaultBranch()).[/quote]

Are these parsed just once? on install? or every time the app starts?

Is the main reason for having the default prefs, is to allow the developer seperate the prefs from the actual code?

Also, what is this how you would get the branch:

Code: Select all
var prefs = Components.classes["@mozilla.org/preferences-service;1"].getDefaultBranch();


mike chambers

asqueella
 
Posts: 3996
Joined: November 16th, 2003, 3:05 am
Location: Russia, Moscow
December 2nd, 2004, 3:57 pm

Post Posted December 2nd, 2004, 3:57 pm

mesh wrote:Are these parsed just once? on install? or every time the app starts?

every time app starts, I think.

Code: Select all
var prefs = Components.classes["@mozilla.org/preferences-service;1"].getDefaultBranch("(root or blank)");


-----
clav:
var branch = prefsvc.getBranch("whatever");
branch.getCharPref("whatever");

- doesn't read default prefs, does it? That is my main problem with nsIPrefBranch.

asqueella
 
Posts: 3996
Joined: November 16th, 2003, 3:05 am
Location: Russia, Moscow
December 3rd, 2004, 4:11 am

Post Posted December 3rd, 2004, 4:11 am

oops. Nevermind. It seems to work.
I swear it didn't work last time I tried :/
Anyway, sorry for wasting everybody's time.


So mesh, the correct way to use preferences (in Fx/Tb) seems to be this:
1) put default values for all/most of your preferences in xpi/defaults/preferences
2) in the code use nsIPrefService.getBranch("extenions.yourextensionname.") to get nsIPrefBranch object
3) use nsIPrefBranch.get calls without try/catch when you get a pref with specified default value; wrap it in try/catch or use nsPreferences when you get a pref without default value.

clav, is this correct?

richwklein
 
Posts: 331
Joined: November 24th, 2002, 8:20 pm
Location: Iowa
December 3rd, 2004, 5:24 am

Post Posted December 3rd, 2004, 5:24 am

Also you can use a pref listener if you expect the preference to be changed and you need notified of that change.
My Extensions:
<a href="http://forecastfox.mozdev.org">Forecastfox</a>
<a href="http://tipbar.mozdev.org">Tip of the Day</a>
<a href="http://urlnav.mozdev.org">Location Navigator</a>
<a href="http://finder.mozdev.org">Finder</a>
<a href="http://rsszilla.mozdev.org">RSSzilla</a>

mesh
 
Posts: 61
Joined: November 28th, 2004, 12:54 pm
December 3rd, 2004, 10:56 am

Post Posted December 3rd, 2004, 10:56 am

richwklein wrote:Also you can use a pref listener if you expect the preference to be changed and you need notified of that change.


Any chance you have a simple example of that?

mike chambers

richwklein
 
Posts: 331
Joined: November 24th, 2002, 8:20 pm
Location: Iowa
December 4th, 2004, 8:20 am

Post Posted December 4th, 2004, 8:20 am

Here is a stripped down version of what we're doing in forecastfox.

Code: Select all
  gForecastfox = new Forecastfox();
  gForecastfox.init();

Forecastfox.prototype =
{
  mDomain: "forecastfox.",
  mBranch: null,

  init: function()
  {
    //setup preference service
    var prefService = "@mozilla.org/preferences-service;1";
    prefService = Components.classes[prefService].getService(Components.interfaces.nsIPrefService);
    this.mBranch = prefService.getBranch(this.mDomain);

    //add preference observer
    var pbi = this.mBranch.QueryInterface(Components.interfaces.nsIPrefBranchInternal);
    pbi.addObserver("", this, false);
  },

  observe: function(aSubject, aTopic, aData)
  {
    switch (aTopic) {
      case "nsPref:changed":
        switch (aData) {
          case "current.tooltip":
            this.updateCurrent();
            break;
          case "forecast.tooltip":
            this.updateForecast();
            break;
        }
     }
   }
}
My Extensions:
<a href="http://forecastfox.mozdev.org">Forecastfox</a>
<a href="http://tipbar.mozdev.org">Tip of the Day</a>
<a href="http://urlnav.mozdev.org">Location Navigator</a>
<a href="http://finder.mozdev.org">Finder</a>
<a href="http://rsszilla.mozdev.org">RSSzilla</a>

mesh
 
Posts: 61
Joined: November 28th, 2004, 12:54 pm
December 4th, 2004, 12:13 pm

Post Posted December 4th, 2004, 12:13 pm

Thanks. I c an't get this to work.

Here is what I am doing:

Code: Select all
function observe(aSubject, aTopic, aData)
{
   dump("observe\n");
   dump("aSubject : " + aSubject + "\n");
   dump("aTopic : " + aTopic + "\n");
   dump("aData : " + aData + "\n");
         
}

var mBranch;
function initializePreferences()
{
   dump("initializePreferences\n");

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

    //add preference observer
    var pbi = mBranch.QueryInterface(Components.interfaces.nsIPrefBranchInternal);
    pbi.addObserver("", this, false);
}


However, when the last line is executed, the following error is thrown:

Error: [Exception... "Could not convert JavaScript argument arg 1 [nsIPrefBranchInternal.addObserver]" nsresult: "0x80570009 (NS_ERROR_XPC_BAD_CONVERT_JS)" location: "JS frame :: chrome://appname/content/appnameOverlay.js :: initializePreferences :: line 583" data: no]
Source File: chrome://appname/content/appnameOverlay.js
Line: 583

Any thoughts on what is going on? Are there any resources online for figuring this stuff out? I have not been able to find any docs on this.

mike chambers

richwklein
 
Posts: 331
Joined: November 24th, 2002, 8:20 pm
Location: Iowa
December 5th, 2004, 8:04 am

Post Posted December 5th, 2004, 8:04 am

your problem is in the addObserver. Your observe function needs to be part of an object. Replace the "this" in the addObserver with your object that has an observe function.
My Extensions:
<a href="http://forecastfox.mozdev.org">Forecastfox</a>
<a href="http://tipbar.mozdev.org">Tip of the Day</a>
<a href="http://urlnav.mozdev.org">Location Navigator</a>
<a href="http://finder.mozdev.org">Finder</a>
<a href="http://rsszilla.mozdev.org">RSSzilla</a>

mesh
 
Posts: 61
Joined: November 28th, 2004, 12:54 pm
December 5th, 2004, 10:44 am

Post Posted December 5th, 2004, 10:44 am

richwklein wrote:your problem is in the addObserver. Your observe function needs to be part of an object. Replace the "this" in the addObserver with your object that has an observe function.


Thanks. This works:

Code: Select all
    var prefs = Components.classes["@mozilla.org/preferences-service;1"].getService(Components.interfaces.nsIPrefService);
   
   var o = new Object();
   o.observe =  function(aSubject, aTopic, aData)
{
   dump("observe\n");
   dump("aSubject : " + aSubject + "\n");
   dump("aTopic : " + aTopic + "\n");
   dump("aData : " + aData + "\n");
         
}

   
   mBranch = prefs.getBranch("macromedianews.");
    var pbi = mBranch.QueryInterface(Components.interfaces.nsIPrefBranchInternal);
    pbi.addObserver("", o, false); 


But, to be honest I am not sure why that works, and passing in "this" does not work, as "this" is an object which also contains an observe method.

Weird.

mike c

richwklein
 
Posts: 331
Joined: November 24th, 2002, 8:20 pm
Location: Iowa
December 7th, 2004, 8:54 am

Post Posted December 7th, 2004, 8:54 am

"this" was your initializePreferences function. It did not have an observe function.
My Extensions:
<a href="http://forecastfox.mozdev.org">Forecastfox</a>
<a href="http://tipbar.mozdev.org">Tip of the Day</a>
<a href="http://urlnav.mozdev.org">Location Navigator</a>
<a href="http://finder.mozdev.org">Finder</a>
<a href="http://rsszilla.mozdev.org">RSSzilla</a>

mesh
 
Posts: 61
Joined: November 28th, 2004, 12:54 pm
December 7th, 2004, 10:02 am

Post Posted December 7th, 2004, 10:02 am

richwklein wrote:"this" was your initializePreferences function. It did not have an observe function.


That is interesting. Is that always the case? I expected "this" to refer the the scope that the function exists within, and not the function itself.

mike chambers

Return to Mozilla Development


Who is online

Users browsing this forum: No registered users and 0 guests