Subscribe to RSS Feed

I have been recently asked by couple of developers how to properly design architecture of a Firefox extension. The first thing that immediately came to my mind at that point was a problem with global variables defined by extensions in ChromeWindow scope.

This problem can easily cause collisions among various extensions. Something that should be always avoided (and is also part of AMO review process) since this kind of issues is very hard to find. Yes, global variables are still evil, especially in OOP world.

I don’t want to describe how to develop a new extension from scratch. For this there is already bunch of detailed articles. I am rather concentrating on effective tactics how to make Firefox extension architecture maintainable and well designed.

So, read more if you are interested…

Namespace Architecture

Defining global variables is a way how to risk collisions with other extensions. I think that creating just one global variable per extension that is unique enough (composed e.g. from the name of the extension, domain URL, etc.) is sufficient strategy how to avoid undesirable collisions.

The architecture for namespaces used in Firebug, is based (more or less) on well known module pattern (originally described by Douglas Crockford). It’s really simple and transparent so, I hadn’t understand how it actually works for a long time. I believe other extension developers can utilize this approach as well.

The basic idea is to wrap content of every JS file into its own scope that is represented by a function so, there are no global objects. See following snippet.

function() {
// TODO: entire JS code in this file is here
}

This is what I am going to call a namespace.

The first question is how to ensure that the function is actually called and the code executed at the right time. The second question is how to share objects among more files (see Sharing among namespaces chapter below). Firebug solves this by registering every namespace and executing all when Firefox chrome UI is ready. See modified example.

myExtension.ns(function() {
// TODO: entire JS code in this file is here
});

The namespace (regular function) is passed as a parameter to myExtension.ns function. The myExtension object is the only global object that is defined by the extension. This is the singleton object that represents entire extension. Don’t worry if the name is long, there’ll be a shortcut for it (in real application it could be e.g. comSoftwareIsHardMyExtension).

The ns function is simple. Every function is pushed into an array.

var namespaces = [];
this.ns = function(fn) {
    var ns = {};
    namespaces.push(fn, ns);
    return ns;
};

Actual execution of registered namespaces (functions) is only matter of calling apply on them.

this.initialize = function() {
    for (var i=0; i<namespaces.length; i+=2) {
        var fn = namespaces[i];
        var ns = namespaces[i+1];
        fn.apply(ns);
    }
};

Now, let’s put all together and see how the global extension (singleton) object is defined and initialized.

The following source code snippet represents a browserOverlay.js file that is included into an overlay (browserOverlay.xul)

// The only global object for this extension.
var myExtension = {};

(function() {
// Registration
var namespaces = [];
this.ns = function(fn) {
    var ns = {};
    namespaces.push(fn, ns);
    return ns;
};
// Initialization
this.initialize = function() {
    for (var i=0; i<namespaces.length; i+=2) {
        var fn = namespaces[i];
        var ns = namespaces[i+1];
        fn.apply(ns);
    }
};
// Clean up
this.shutdown = function() {
    window.removeEventListener("load", myExtension.initialize, false);
    window.removeEventListener("unload", myExtension.shutdown, false);
};
// Register handlers to maintain extension life cycle.
window.addEventListener("load", myExtension.initialize, false);
window.addEventListener("unload", myExtension.shutdown, false);
}).apply(myExtension);

As I mentioned above, there is just one global object myExtension.

To summarize, the object implements following methods:

  • ns - register a new namespace.
  • initialize - initialize all namespaces.
  • shutdown - clean up.

And also, the code makes sure that initialize and shutdown methods are called at the right time. This is why event handlers are registered.

The browserOverlay.xul looks as follows now.

<?xml version="1.0"?>
<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
    <script src="chrome://namespace/content/browserOverlay.js" type="application/x-javascript"/>
    <script src="chrome://namespace/content/Module1.js" type="application/x-javascript"/>
    <script src="chrome://namespace/content/Module2.js" type="application/x-javascript"/>
</overlay>

And the Module1.js and Module2.js files are both the same.

myExtension.ns(function() {
// TODO: entire JS code in this file is here
});

Sharing among namespaces

Now, when we have our script within a local scope(s), let’s answer the question how to share functionality and data among individual namespaces. The general idea is to use the one global object we have - myExtension.

First of all, see the following source code (lib.js file).

myExtension.LIB = {

    // Shared APIs
    getCurrentURI: function() {
        return window.location.href;
    },

    // Extension singleton shortcut
    theApp: myExtension,

    // XPCOM shortcuts
    Cc: Components.classes,
    Ci: Components.interfaces,

    // Etc.
};

You can see that a new LIB property is created within our global myExtension singleton. This objects represents a library of functions that should be shared among all modules in our extension. At this point, you can also get inspiration from Java Packaging and create whole tree of namespaces within the global singleton (just like e.g. YUI does)

The lib.js file is included in browserOvelay.xul (just after browserOverlay.js)

<?xml version="1.0"?>
<overlay xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul">
    <script src="chrome://myextension/content/browserOverlay.js" type="application/x-javascript"/>
    <script src="chrome://myextension/content/lib.js" type="application/x-javascript"/>
    <script src="chrome://myextension/content/Module1.js" type="application/x-javascript"/>
    <script src="chrome://myextension/content/Module2.js" type="application/x-javascript"/>
</overlay>

Let’s also improve our module script a bit.

myExtension.ns(function() { with (myExtension.LIB) {
// TODO: entire JS code in this file is here

var moduleVariable = "Accessible only from withing this module";
dump("myExtension.Module initialization " + getCurrentURI() + "\n");

}});

By utilizing with statement we can simply access all library functions as if they would be a global functions.

In case we want to access our singleton global object we can also utilize theApp shortcut (useful especially if the name is long) as follows:

myExtension.ns(function() { with (myExtension.LIB) {
// TODO: entire JS code in this file is here

theApp.sharedValue = "A new shared property";

}});

Here is how the architecture look like from UML perspective.

Namespaces within a Firefox extension.

Download example extension here.


Rss Commenti

15 Comments

  1. (The syntax highlighting is slightly screwed up starting from the third code block)

    Annoyingly, use of "with" triggers a strict warning because assignment to undeclared variables still go on the global scope.

    How do you deal with JS load order? The myExtension.ns definition doesn't always happen before the other scripts are loaded. Do you have to repeat it in each file? (If they _currently_ have any specific behaviour, I wouldn't trust that - been burned by Mozilla randomly changing unspecified behaviour before. Unless you feel Firebug is big enough that the platform will bend over backwards to support you... Which is probably the case for now.)

    #1 Mook
  2. > (The syntax highlighting is slightly screwed up
    > starting from the third code block)
    Should be fixed, thanks!

    > How do you deal with JS load order? The
    > myExtension.ns definition doesn't always happen
    > before the other scripts are loaded.
    The myExtension.ns should happen before other scripts are loaded (i.e. before module1.js and module2.js from the example) so, it it's only defined once. I don't think this behavior will change.

    #2 admin
  3. It's worth pointing out that this is probably overly complex for many extensions, which are just 1 or 2 .js overlays. If this is intended to be the basis of a Best Practices guide, I'd suggest starting with a simple example, just using methods/properties in their global object:

    var MYEXT = { foo : function() { }, bar: 123 }

    [Perhaps with a note about using functions/prototypes as a slightly more complex variant, but I guess that's mostly only useful for components.]

    Big extensions with lots of code spread across multiple modules benefit from using namespaces and registration, but for small extensions and new developers it's a lot of confusing overhead.

    #3 Justin Dolske
  4. Yes, you put it well. I would like to write more about "Firefox Extension Development Best practices" since examples on the web are rather basic showing just specific APIs and not architecture of an extension. I'll put your comments on my list. Thanks!

    #4 admin
  5. Hey, greate Blog!
    I have just seen something wich you would find very interesting!

    http://mankzblog.wordpress.com/2009/01/18/ajax-frameworks-global-namespace-pollution/

    There you can see many Javascript Frameworks and how much they pollute the global namespace!

    #5 Basti
  6. Thanks!

    Yeah, I have seen the page already, pretty darn cool.

    #6 admin
  7. Wow Brilliant job it's really helpful on Software Development please keep it up.

    Thank
    MAG Studios
    mag-corp.com

    #7 Mag Studios
  8. [...] [upmod] [downmod] Firefox Extensions: Global Namespace Pollution (softwareishard.com) -1 points posted 2 months ago by jeethu tags firefox javascript namespace [...]

    #8 Tagz | "Firefox Extensions: Global Namespace Pollution" | Comments
  9. Are you sure the browserOverlay.js initialize function gets called? window.addEventListener("load", myExtension.initialize, false); doesn't seem to have any effect. If I put debug output in the initialize function I get nothing. Putting debug output in shutdown does though.

    #9 Mat
  10. @Mat: I am pretty sure it works. I don't know why it doesn't for you :-(

    #10 Honza
  11. Even with your sample extension, I can't get any debug output from the browserOverlay.js initialize function. window.addEventListener is executed, but the load event never calls initialize or shutdown (or at least I can't get any debug output from there).

    I resorted to calling initialize directly rather than addEventListener but that won't work for shutdown.

    I'm also struggling to share variables from Lib but this may well due to lack of initialization? Static variables are fine, but anything I try to create/change dynamically can't be seen by other modules.

    I'm running Firefox 3.0.11.

    #11 Mat
  12. Having thought about this a played a bit more, I think the reason I don't get any debug output is because the debug console isn't up properly at that point.

    I've managed to get some of my variables sharing properly now. I think the ones I was struggling with (and still am) are in a sidebar. I presume this is running in a separate instance. Is it possible to use the same Lib between the main chrome window and a sidebar?

    #12 Mat
  13. @Mat: yes, take a look at this, it could help: https://developer.mozilla.org/en/Working_with_windows_in_chrome_code

    #13 Honza
  14. Let's say i have 2 library files, how to call one function from the lib1.js in lib2.js

    lib1.js
    myExtension.ns(function() { with (myExtension.LIB) {
    function myFirstFunction () {}
    }});

    lib2.js
    myExtension.ns(function() { with (myExtension.LIB) {
    function mySecondFunction () {
    myFirstFunction();//this does not work
    theApp.myFirstFunction();//this doesnt work either
    }
    }});

    #14 bz
  15. myExtension.ns(function() { with (myExtension.LIB) {
    myExtension.myFirstFunction = function() {}
    }});

    lib2.js
    myExtension.ns(function() { with (myExtension.LIB) {
    function mySecondFunction () {
    myExtension.myFirstFunction();//this does work
    }
    }});

    #15 Honza

Sorry, the comment form is closed at this time.