We'll be using the placeholder 〈Namespace〉 below. This needs to be replaced with an identifier name which is unique to your add-on. If your add-on is called Sergeant Pepper, for instance, then
SgtPepper would be a good namespace name.
Notice how the 〈Namespace〉 namespace is declared using var. We need the namespace to be a global object that it can be used everywhere in the window chrome.
You can include functions in any namespace, since namespaces are just regular JS objects. That should come in handy when you have general utility functions or properties that you want to use across all objects within the namespace. For instance, there are frequently used XPCOM services such as the Observer service that can be included as members in the namespace:
JS objects can also be treated as string-indexed arrays:
You usually need only one JS file to control a XUL window, since the code required is normally not that much. If you have complex behavior that requires too much code, look for ways to divide it into multiple objects and files. You can include as many scripts in a XUL window as you need.
To initialize your chrome objects, it's usually better to run the initialization code from the "load" event handler for the window. The load event is fired after the DOM on the window has loaded completely, but before it's displayed to the user. This allows you to manipulate and possibly change elements in the window without the user noticing the changes.
There are some things you can't (or shouldn't) do inside load handlers, such as closing the window being loaded, or opening new windows, alerts or dialogs. The window has to finish loading before it can do any of these things. They are bad UI practices anyway and you should avoid them. If you really need to do something like this anyway, one way to do it is to have a timeout execute the code after a delay:
The setTimeout function executes the function in the first parameter, after a delay in miliseconds specified by the second parameter. In this case we set the delay to 0, which means the function should be executed as soon as possible. Firefox has a minimum delay of 10-15ms (taken from this blog post), so it won't really run instantly. It is more than enough to let the window finish its load.
Here's a short exercise to test a particular aspect of the chrome. Modify the Hello World extension so that the message says how many times it has been displayed. The message could say something like "Hello! This message has been shown 5 times." Keep the counter as a variable in the BrowserOverlay object, and increment it every time the message is going to be shown.
Once you have this working right, try the following: open the message a few times, so that the number increments. Now open a new window and display the message from the new window. What do you think will happen? What will the count be this time?
You probably didn't expect this, but the count was reset in the new window. Each window keeps its own counter, and now the extension is not behaving as expected. This is a fundamental lesson: the chrome is not global, it's window-specific. All of your scripts and objects are replicated for each window, and they work independently from each other. This is an issue that is very easy to overlook, since most Firefox users, specially power users, have a single window open at all times. You have to make sure you test your extension with multiple windows open; never assume everything will work the same as with a single window.
Code modules are regular JS files, so there's nothing new in regards to naming or file types. Mozilla has adopted a standard of using the extension .jsm for these files, but they say .js is fine as well. To keep things simple, specially regarding code editors and default file associations in the developer's system, we have decided to stick with .js.
Download this version of the Hello World project with JSM to see the changes you need to make to the build system in order to include the files in the modules folder. They are minimal, and we add a very small Makefile.in file in the modules directory, just to keep everything separated and organized.
EXPORTED_SYMBOLS is a special identifier that tells Firefox that this file is only publishing the object named 〈ModuleNamespace〉. Several objects, functions and variables can be declared on this file, but the only object visible from the outside will be 〈ModuleNamespace〉, which is a namespace in our case. Because of namespacing, we don't need to worry much about what to export, usually we just need the namespace object. All of the objects inside of it are exported as well, since they are members of the 〈ModuleNamespace〉 object.
Module files can be imported to a chrome script or to other code modules with the following line:
To get a better idea, let's look at the code of the modified Hello World example. We have defined two files, one to declare namespaces and another one for the message count functionality mentioned in the previous exercise.
Here again we're using a placeholder, 〈ModuleNamespace〉, for the identifier name that you'll need to choose.
This should all be familiar enough. We're declaring the namespace we'll use at the module level. We need a separate namespace for the chrome because the chrome namespace objects are repeated for each window, while the module namespace objects are unique for all windows. Setting window-specific data on code modules will lead to nothing but problems, so be careful when deciding what should be chrome and what shouldn't be. We needn't test for the pre-existence of our namespace object here, as modules are given their own namespace.
The 2 declared constants above are used to reduce code size. We frequently need to use XPCOM components in our code, so instead of doing this:
It's better to do this:
These 2 constants don't need to be defined in the overlay because they are already defined globally in the browser.js file in Firefox. We only need to define them when we're making windows of our own, or when we're working with code outside of the chrome (or porting your code to SeaMonkey, which doesn't have those constants declared in the main window).
This is a point that is worth highlighting: modules work outside of the window scope. Unlike scripts in the chrome, modules don't have access to objects such as window, document, or other global functions, such as openUILink. These are all UI components and UI operations anyway, so they are better done in the chrome.
We handle most of our code through static objects, singleton objects that don't require instantiation. But it is sometimes necessary to define classes and be able to create multiple instances. Common cases include interacting with a local database or a remote API. Data will often be translated into arrays of entities, and those are better represented through classes. One way to define a class is as follows:
Creating an instance and using it is simple enough:
This is something you can do with JS in general. You can use it in JSM, chrome, even on regular web pages. Since entities tend to be used all throughout an application, we think that having those classes defined at the module level is the best approach.
JSM is the best solution to handle objects that are window-independent. In the following section we'll discuss XPCOM, which is an older alternative to JSM and one of the foundations of Mozilla applications. You shouldn't skip that section because there are many common situations in extension development where you'll have to use XPCOM, maybe even implement XPCOM components of your own.
This tutorial was kindly donated to Mozilla by Appcoast.