Got boilerplate?

How many times do you see code like this:

var factory = {
createInstance: function createInstance(outer, iid) {
// ...
if (outer != null) {
throw Components.results.NS_ERROR_NO_AGGREGATION;
}
return (new FooComponent()).QueryInterface(iid);
}
};
var Module = {
registerSelf: function registerSelf(compMgr, fileSpec, location, type) {
// ...
},
getClassObject: function getClassObject(compMgr, aCID, aIID) {
// ...
},
// ...
}

It’s everywhere in Firefox and XULRunner-based applications. But not for long…

Robert Sayre resurrected bug 238324, which implements a JavaScript code sharing system. This means common code which gets repeated ad nauseum (and risks bugs from duplication) can be reduced to a much smaller boilerplate. Fifty to sixty lines of JS reduce to fifteen:

Components.utils.import("rel:XPCOMUtils.jsm");
var NSGetModule = XPCOMUtils.generateNSGetModule([
{
className:  BarComponent.prototype.classDescription,
cid:        BarComponent.prototype.classID,
contractID: BarComponent.prototype.contractID,
factory: XPCOMUtils.generateFactory(
function barCtor() {
return new BarComponent();
},
[Components.interfaces.nsIClassInfo]
)
}
], null, null);

By the way, the above code also means you don’t have to implement QueryInterface() either. (Yes, you do – classinfo interface flattening doesn’t work without it, and many others will be broken. Oops.) Expect much code bloat reduction from this in Gecko 1.9, including Firefox and Thunderbird.

This is just the tip of the iceberg. I find myself writing a lot of boilerplate JS code for enumerators, and a bit of arrays. Maybe we can eliminate that, too. Or perhaps other shortcuts for implementing nsIClassInfo or other interfaces. With XUL/JavaScript preprocessing, maybe I could even contribute my designbycontract library to mozilla.org now – though that could mean preprocessing a lot of component files too. I’m not sure we want to do that.

Sure, all the above could be done via the JavaScript subscript loader, and in fact, that’s how the common code gets imported. That route is slow, though. Now it’s standardized through C++.

If you can come up with other frequently repeated code, file bugs for code redux under Core -> XPConnect and dependent on bug 238324. Feel free to cc me too; I’ll be very happy to implement it, if someone else doesn’t beat me to it!

4 thoughts on “Got boilerplate?”

  1. I don’t see how it helps to provide an initial list of interfaces; your component is not going to be very useful if you can’t call QueryInterface on it.
    (Well, the supposed beauty of the new code is that QI wasn’t necessary. Although I found that isn’t true. See bug 238324 comment 68.)

  2. I was thinking exactly this when I wrote my first XPCOM components a week ago. It seemed like I had to copy the same code over and over and I didn’t understand any of it.
    With this change, is the actual JS object/prototype, any includes, and this boilerplate the only things that need to be in the component?
    (From Alex: Essentially. We’re also looking for other ways to reduce boilerplate.)
    What difficulties are you talking about dealing with arrays? The only weird thing I’ve experienced is the need to pass in an object parameter that gets the length set.
    (From Alex: I see the same – but I very, very frequently have to get a simple enumerator for that array. Things like that get annoying.)

  3. A couple of thoughts for frequently repeated code… I’m fairly new to XPCOM so I figure I’d bounce possibly stupid ideas off you before bothering people on Bugzilla…
    XMLHttpRequests work much differently in an XPCOM than in a window. Perhaps we can provide choice window properties to XPCOM components?
    (From Alex: Too vague right now; feel free to e-mail me off the blog list.)
    I don’t know if this exists already. Is there an easy way to test if an object supports an interface other than trying to QI to it and possibly catching an exception?
    (From Alex: See my article on nsIClassInfo.)
    Probably not related to XPCOM per se: I like to have the source and the binaries in the same place. The Error Console complains about the idl files I leave in the components directory. It’d be nice if it ignored them.

  4. Along with an array to nsISimpleEnumerator code, could we get one for returning an nsIStringEnumerator of a javascript object’s property names? In our forecastfox extension we had to create a helper function to do this like below:

    /******************************************************************************
    * String enumerator of hash table keys.
    *
    * @param   Javascript hash table.
    * @return  A nsIStringEnumerator of the keys.
    *****************************************************************************/
    function KeyEnumerator(aHashTable)
    {
    //setup key array
    this._keys = [];
    this._index = 0;
    //load with data
    if (aHashTable) {
    for (var name in aHashTable)
    this._keys.push(name);
    }
    }
    KeyEnumerator.prototype = {
    _index: null,
    _keys: null,
    QueryInterface: function KeyEnumerator_QueryInterface(aIID)
    {
    if (!aIID.equals(Ci.nsIStringEnumerator) ||
    !aIID.equals(Ci.nsISupports))
    throw Cr.NS_ERROR_NO_INTERFACE;
    return this;
    },
    hasMore: function KeyEnumerator_hasMore()
    {
    return this._index
    

    From Alex: Can you file this on the ArrayConverter bug?

Comments are closed.