分类: 系统运维
2009-09-28 15:58:39
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 scope.
This problem can easily cause collisions among various extensions. Something that should be always avoided (and is also part of AMO 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…
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 ). 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.
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.
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.
Actual execution of registered namespaces (functions) is only matter of calling apply on them.
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)
(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
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:
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.