isn't quite ashamed enough to present

jr conlin's ink stained banana

2009-07-14

:: Sage for Firefox 3.5

urgh. So it looks like my RSS reader of choice, Sage2, has once again been abandoned.

(Sorry, i'm not a fan of Google Reader, i tried using Akregator but it choked on images, and My Yahoo has never done a good job of displaying content.)

So i grabbed the source, redid the install.rdf and recompiled it into an xpi.

It works fine for me, but let me know if you find any bugs.

    What do you think, sirs?

    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.

    1. 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" ?
    2. 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?
    3. 2008-10-08 03:01:00
      Well, if it works out, let us know. I'd like to see it work...
    Wanna join in?

    2008-09-10

    :: Adding Tails to a Fox

    Right, moderate milestone reached. i can pick up here again.

    So, you've probably done the Firefox tutorial where you add a box to the status bar, but what if you want to do more than that?

    Well, turns out that you can (obviously) control damn near everything in the chrome from the protected chrome layer. The only trick is, of course, figuring out where things are.

    First off, you probably need to realize that the .xul file generally describes and replaces things… except for some things. This is controlled by the "overlay" element inside of the chrome.manifest file. Chances are, you have a like like:
    overlay chrome://browser/content/browser.xul chrome://flixo/content/flixo.xul
    This tells firefox to look for the special ID names and merge what's in your file with what's in browser.xul There are root elements that Firefox will attach your defined .xul elements to which you're not going to be able to easily replace. This is a good thing, but it can be a bit tricky for folks to grasp, particularly when coming from a CSS/Javascript world where defining an element by a particular ID pretty much means replacing that item.

    All of these can be found by sifting through the $MOZILLA_APPLICATION/chrome/browser.jar/content/browser/browser.xul file, but i'm going to annotate the main elements here. Here's a run down of the main "anchor" points for XUL:

    (more…)

      What do you think, sirs?

      2008-08-26

      :: Opening The Window

      Continuing on lessons learned from building a Mozilla Plugin, let me continue with what you probably really want to do anyway, get to the damn window content.

      Sure, having a little window that says "Hello World" may be nice and all, but it's not going to do anything really cool. No, for that, it'd be damn handy to get the contents of what's in the current browser. Except, which current browser do you mean?

      For that matter, which window do you mean? You can have a browser anywhere, including in that little "Hello World" micro window, each with their own context. Well, turns out that (surprise) there are a number of ways to get the window you want.

      First off, the FUEL way, since it's far cleaner than the other way.
      var activeWindowDocument = Application.activeWindow.activeTab.document;
      Like i said, far cleaner. The astute will note that there's a few telling elements to the above clean statement. The first being that while there's a Globally available Application, it has more than one .window beneath it. Good for you. The .windows property contains all of the browser windows available under Application, including any that might be squirreled away in an extension. (Ok, Application is globally defined for XUL apps, which include the default Firefox implementation and any extension you may be writing, but not any XPCOM modules you may be creating. If it's not available for whatever reason, you'll have to instantiate it from XPCOM via:
      var Application = Components.classes["@mozilla.org/fuel/application;1"].getService(Components.interfaces.fuelIApplication);

      It's also possible to walk the various tabs the user may have opened (or you've added) to get that content, and obviously, once you have activeWindowDocument, you've got full access to the DOM of whatever happens to be in that window. If you add a listener to the activeWindow:
      Application.activeWindow.addEventListener('DOMContentLoaded',this.onPageRefresh);
      onPageRefresh will get called after the DOM has finalized anytime a page is loaded, and since XUL applications run in protected space, you can make as many external calls back to your server as you like without asking for user permissions.

      (Starting to feel paranoid about what extensions you load? Good. You should.)

      The less clean manner is to take advantage of one of the older XUL globals and call getBrowser().contentWindow.document;. This instantiates and returns the global gBrowser which you can later refer to. Why is this less clean? Well for one, it's not really well documented, and considering that the Working with Windows in Chrome section offers the less than helpful way of getting the Application window:
      var mainWindow = window.QueryInterface(Components.interfaces.nsIInterfaceRequestor)
      .getInterface(Components.interfaces.nsIWebNavigation)
      .QueryInterface(Components.interfaces.nsIDocShellTreeItem)
      .rootTreeItem
      .QueryInterface(Components.interfaces.nsIInterfaceRequestor)
      .getInterface(Components.interfaces.nsIDOMWindow);

      You'd be looking for shortcuts too.

      A few additional notes:
      The XML pretty-printer is exceptionally fickle. If you modify the content of the page in any manner, it will revert the page to plain text. It does this because it hates you.

      To be honest, i'm using a combination of these approaches because i'm under the gun and learning. Eventually, i plan on cleaning things up to the point where i'm only doing things the right way, but that's not happening this week.

        What do you think, sirs?

        2008-08-25

        :: Mucking with Mozilla: Part 1

        One of the reasons you've not heard much from me lately is because i've joined a monastery and devoted my attention to transcribing the Manhattan Yellow Pages into pages of hand written Urdu been spelunking into Firefox 3 in order to build out an extension.

        The extension is, by and large, fairly simple. i can't really say the same thing about the Firefox docs, however.

        i've been taking notes and have been thinking about releasing a "how-to" guide for building these things. Kind of a step above building a "hello world" type plug-in where you start doing more useful things.

        The problem is, however, that engineers generally don't have time to write documentation. This is important to note because one of the things that's been most frustrating lately is the fact that once you try to dig into something, you inevitably fall down one or more rabbit holes of "nearly working solutions" that give "almost enough" info for you to work with. And then you discover that things recently changed and now it's much easier to do things.

        Let's take debugging for instance:

        The classic way one goes about dumping stuff to the Javascript Console according to the docs is to call dump(). That works, for the most part, except when it doesn't. There can be any number of reasons why (such as you're running inside of a protected chrome window and the local "window" object hasn't been created yet, or you've not enabled browser.dom.window.dump.enabled=true, etc., etc. and sadly, etc.). One of the outcomes is that the console buggers itself and stops showing any output at all. Digging around in the docs shows a slightly more direct way of getting to the javascript console by calling:
        var console = Components.classes["@mozilla.org/consoleservice;1"].getService(Components.interfaces.nsIConsoleService);
        console.logStringMessage(msg);

        which does a damn reliable job of getting your string to the javascript console.

        Well, unless you're running Firefox 3, in which case, you could call
        Application.console.log(msg);

        You probably don't know about that because it's part of FUEL, the brand, spakin' new library that makes writing extensions super easy. Granted, you probably have no idea that FUEL exists because it's not mentioned on the Mozilla Developer Home Page, although there are a veritable termite's nest of holes one can fall into right there.

        The absolute One Thing i've found out is that the only way to really get how this stuff falls together is to crack open the browser.jar file in your Mozilla App/chrome directory and get spelunkin'. (A jar is just a renamed .zip file.)

        So, what i'm going to do is keep notes here. i'll be posting up various things i've discovered while building out my plug-in and you can benefit from it. Consider this Mozilla 201, Slightly more advanced, but no where near doctorate level.

        (Plus this'll give me a topic to ramble about.)

          What do you think, sirs?

          Blogs of note
          personal that's my blog
          (The Official Blog of the Internet)
          memoirs of hydrogen guy matthew shepherd (quebec) rhapsodic.org Henriette's Herbal Blog lynne ydw i slumbering lungfish
          geek Y!Cool Thing jeremy z
          (The Official Website of the Internet)
          dave's picks ultramookie Josh Woodward derek balling simon willison
          news ars technica search engine watch

          Powered by WordPress
          Hosted on Dreamhost.