• Skip to main content
  • Select language
  • Skip to search
MDN Web Docs
  • Technologies
    • HTML
    • CSS
    • JavaScript
    • Graphics
    • HTTP
    • APIs / DOM
    • WebExtensions
    • MathML
  • References & Guides
    • Learn web development
    • Tutorials
    • References
    • Developer Guides
    • Accessibility
    • Game development
    • ...more docs
Add-ons
  1. MDN
  2. Mozilla
  3. Add-ons
  4. Overlay extensions
  5. XUL School Tutorial
  6. JavaScript Object Management

JavaScript Object Management

In This Article
  1. Chrome JavaScript
    1. Exercise
  2. JavaScript Code Modules

Add-ons using the techniques described in this document are considered a legacy technology in Firefox. Don't use these techniques to develop new add-ons. Use WebExtensions instead. If you maintain an add-on which uses the techniques described here, consider migrating it to use WebExtensions.

From Firefox 53 onwards, no new legacy add-ons will be accepted on addons.mozilla.org (AMO).

From Firefox 57 onwards, WebExtensions will be the only supported extension type, and Firefox will not load other types.

Even before Firefox 57, changes coming up in the Firefox platform will break many legacy extensions. These changes include multiprocess Firefox (e10s), sandboxing, and multiple content processes. Legacy extensions that are affected by these changes should migrate to WebExtensions if they can. See the "Compatibility Milestones" document for more.

A wiki page containing resources, migration paths, office hours, and more, is available to help developers transition to the new technologies.

« PreviousNext »

Chrome JavaScript

In this section we'll look into how to handle JavaScript data effectively, beginning with chrome code, in ways which will prevent pollution of shared namespaces and conflicts with other add-ons resulting from such global namespace pollution.

The first step to good JavaScript object management is having a namespace, or a JavaScript object that contains our code and data, that you know will not conflict with Firefox code or other extensions. Namespace declaration is best located in a file of its own, so that you have this one JS file that should be included in all of your XUL files.

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.

/**
 * 〈Namespace〉 namespace.
 */
if (typeof 〈Namespace〉 == "undefined") {
  var 〈Namespace〉 = {};
};
Note: The naming standard that we normally follow is that the first part of the namespace corresponds to the development group (or company), and the second to the specific project. However, most extensions are small projects by individuals, so these examples follow a more practical approach of having just one namespace with the project 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:

/**
 * 〈Namespace〉 namespace.
 */
if (typeof 〈Namespace〉 == "undefined") {
  var 〈Namespace〉 = {
    /**
     * Initializes this object.
     */
    init : function() {
      this.obsService =
        Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);
    }
  };
  〈Namespace〉.init();
};

JS objects can also be treated as string-indexed arrays:

// equivalent.
〈Namespace〉.Hello = {};
〈Namespace〉["Hello"] = {};
// equivalent.
〈Namespace〉.Hello.init();
〈Namespace〉.Hello["init"]();

This is very useful in cases where you have to set attributes or functions with dynamically generated names. It's one of the funky properties of JavaScript: all objects are nothing more than name / value mappings. You can add or replace functions and attributes to any Javascript object, at any moment you want. This is an odd, but powerful feature that comes in handy at times when things get complicated. For instance, you could replace a method in any object in the Firefox chrome, so that it behaves differently than how it normally does. This should be a last resort option, but it is very useful at times.

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.

/**
 * Controls the browser overlay for the Hello World extension.
 */
〈Namespace〉.BrowserOverlay = {
  /**
   * Initializes this object.
   */
  init : function(aEvent) {
    this._stringBundle = document.getElementById("xulschoolhello-string-bundle");
    // you can make changes to the window DOM here.
  }
  // more stuff
};
window.addEventListener(
  "load", function() { 〈Namespace〉.BrowserOverlay.init(); }, false);

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:

init : function(aEvent) {
  let that = this;
  this._stringBundle = document.getElementById("xs-hw-string-bundle");
  window.setTimeout(
    function() { 
      window.alert(that._stringBundle.getString("xulschoolhello.greeting.label")); }, 0);
}

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.

Use window.setTimeout and window.setInterval to control timed code execution. In case you're using JavaScript Code Modules or XPCOM objects, where a window object is not readily available, use an nsITimer instead. 
This post suggests a way to achieve a true zero ms timeout, as a simple way to achieve parallelism in JS code.

Notice the way we send callback functions as parameters, and the use of an alternate reference for this which we always name that. This is all necessary due to a JavaScript feature (quirk would be a better word for it) called Method Binding. The consequence of doing this wrong is to have a this reference that doesn't do what you expected it to do. There are a few workarounds for this, and we use the ones we have found to be the most elegant and clear to read.

The general guideline we follow is this: whenever you have to set a callback function parameter, wrap it in an anonymous function. That is, something like function() { /* your code, usually a single function call. */ }. If you have to use a reference to this inside the function, declare a variable called that that equals this, and use that in the anonymous function.

JavaScript has a host of features that make it extremely flexible, but it also has some disadvantages, as it is not as strict as other languages, such as Java. A clear example of this is the fact that there are no private or public keywords that allow you to protect object members. As a alternative for this, a naming standard is frequently used to distinguish private and public members. There's no scope enforcement whatsoever, but this standard give others the chance to "play nice" and don't use private members.

Use "_" at the beginning of private attributes and methods in JS objects. For example: _stringBundle, _createUserNode().

Exercise

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.

Now, in most cases you'll need to coordinate data in a way that it is consistent for all open Firefox windows. There are several ways in which you can do this. Preferences is one of them, and they are covered in another section of this tutorial. Two other ways are JavaScript Code Modules (Firefox 3 and above), and XPCOM.

JavaScript Code Modules

JavaScript Code Modules (also known as JSM) are new to Firefox 3, and they're the best tool for keeping everything in sync between windows. They're very simple to set up. The first thing you need to do is add an entry in the chrome.manifest file:

resource  xulschoolhello     modules/

Javascript code modules are accessed with the resource protocol, which is very similar to the chrome protocol. Just like with the chrome, we define the package name and then a path. To keep things simple, just locate the JSM files in a modules directory under the root of our project. In order to access a file messageCount.js in this directory, the URL would be:

resource://xulschoolhello/messageCount.js

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.

With the setup out of the way, let's get to it. What are JavaScript Code Modules?

A JavaScript Code Module is a regular JS file that specifies which of the declared elements in it are public. All module files should begin with a declaration like this:

var EXPORTED_SYMBOLS = ["〈ModuleNamespace〉"];

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:

Components.utils.import("resource://xulschoolhello/messageCount.js");
When using Components.utils.import, code modules must be loaded using a file: or resource: URL pointing to a file on the disk. In particular, chrome: URLs (even those that point to a file outside a jar archive) are not valid.

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.

var EXPORTED_SYMBOLS = [ "〈ModuleNamespace〉" ];
const { classes: Cc, interfaces: Ci } = Components;
/**
 * 〈ModuleNamespace〉 namespace.
 */
var 〈ModuleNamespace〉 = {};

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:

this.obsService = 
  Components.classes["@mozilla.org/observer-service;1"].getService(Components.interfaces.nsIObserverService);

It's better to do this:

this.obsService = 
  Cc["@mozilla.org/observer-service;1"].getService(Ci.nsIObserverService);

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).

Include the Cc and Ci constants in all XUL windows that are not overlays, all JSM files, and all XPCOM components (see further ahead). Do this even if you don't need them now. It's better to just make a habit out of it.

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.

As a general guideline, we keep all of our business logic in JSM, available through service objects, and chrome scripts are limited to handle presentation logic.

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:

/**
 * User class. Represents a Hello World user (whatever that may be).
 */
〈ModuleNamespace〉.User = function(aName, aURL) {
  this._name = aName;
  this._url = aURL;
};
/**
 * User class methods.
 */
〈ModuleNamespace〉.User.prototype = {
  /* The name of the user. */
  _name : null,
  /* The URL of the user. */
  _url : null,
  /**
   * Gets the user name.
   * @return the user name.
   */
  get name() {
    return this._name;
  },
  /**
   * Gets the user URL.
   * @return the user URL.
   */
  get url() {
    return this._url;
  }
};

In this example we defined a fictitious User class for the Hello World extension. Using the function keyword to define a class is odd, but this is just the JavaScript way: functions are also objects. The definition of the class acts as a constructor as well, and then you can define all other members using the prototype attribute. In this case we defined "getter" properties for the name and url members. This way we have immutable instances of our class. Well, only if consumers of the class play nice and don't change anything they shouldn't.

Creating an instance and using it is simple enough:

let user = new 〈ModuleNamespace〉.User("Pete", "http://example.com/pete");
window.alert(user.name);

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.

« PreviousNext »

This tutorial was kindly donated to Mozilla by Appcoast.

Document Tags and Contributors

Tags: 
  • JavaScript
  • Object
  • Tutorials
 Contributors to this page: wbamberg, Kastor, teoli, kmaglione, pablog, Sheppy, Max1million, Jorge.villalobos, DaveG
 Last updated by: wbamberg, Jul 4, 2016, 1:46:16 PM
See also
  1. WebExtensions
  2. Getting started
    1. What are WebExtensions?
    2. Your first WebExtension
    3. Your second WebExtension
    4. Anatomy of a WebExtension
    5. Example WebExtensions
  3. How to
    1. Intercept HTTP requests
    2. Modify a web page
    3. Add a button to the toolbar
    4. Implement a settings page
  4. Concepts
    1. Using the JavaScript APIs
    2. User interface components
    3. Content scripts
    4. Match patterns
    5. Internationalization
    6. Content Security Policy
    7. Native messaging
  5. Porting
    1. Porting a Google Chrome extension
    2. Porting a legacy Firefox add-on
    3. Embedded WebExtensions
    4. Comparison with the Add-on SDK
    5. Comparison with XUL/XPCOM extensions
    6. Chrome incompatibilities
  6. Firefox workflow
    1. Temporary Installation in Firefox
    2. Debugging
    3. Getting started with web-ext
    4. web-ext command reference
    5. WebExtensions and the Add-on ID
    6. Publishing your WebExtension
  7. JavaScript APIs
    1. Browser support for JavaScript APIs
    2. alarms
    3. bookmarks
    4. browserAction
    5. browsingData
    6. commands
    7. contextMenus
    8. contextualIdentities
    9. cookies
    10. downloads
    11. events
    12. extension
    13. extensionTypes
    14. history
    15. i18n
    16. identity
    17. idle
    18. management
    19. notifications
    20. omnibox
    21. pageAction
    22. runtime
    23. sessions
    24. sidebarAction
    25. storage
    26. tabs
    27. topSites
    28. webNavigation
    29. webRequest
    30. windows
  8. Manifest keys
    1. applications
    2. author
    3. background
    4. browser_action
    5. chrome_url_overrides
    6. commands
    7. content_scripts
    8. content_security_policy
    9. default_locale
    10. description
    11. developer
    12. homepage_url
    13. icons
    14. manifest_version
    15. name
    16. omnibox
    17. options_ui
    18. page_action
    19. permissions
    20. short_name
    21. sidebar_action
    22. version
    23. web_accessible_resources
  9. Add-on SDK
  10. Getting started
    1. Installation
    2. Getting started
    3. Troubleshooting
  11. High-Level APIs
    1. addon-page
    2. base64
    3. clipboard
    4. context-menu
    5. hotkeys
    6. indexed-db
    7. l10n
    8. notifications
    9. page-mod
    10. page-worker
    11. panel
    12. passwords
    13. private-browsing
    14. querystring
    15. request
    16. selection
    17. self
    18. simple-prefs
    19. simple-storage
    20. system
    21. tabs
    22. timers
    23. ui
    24. url
    25. webextension
    26. widget
    27. windows
  12. Low-Level APIs
    1. /loader
    2. chrome
    3. console/plain-text
    4. console/traceback
    5. content/content
    6. content/loader
    7. content/mod
    8. content/symbiont
    9. content/worker
    10. core/heritage
    11. core/namespace
    12. core/promise
    13. dev/panel
    14. event/core
    15. event/target
    16. frame/hidden-frame
    17. frame/utils
    18. fs/path
    19. io/byte-streams
    20. io/file
    21. io/text-streams
    22. lang/functional
    23. lang/type
    24. loader/cuddlefish
    25. loader/sandbox
    26. net/url
    27. net/xhr
    28. places/bookmarks
    29. places/favicon
    30. places/history
    31. platform/xpcom
    32. preferences/event-target
    33. preferences/service
    34. remote/child
    35. remote/parent
    36. stylesheet/style
    37. stylesheet/utils
    38. system/child_process
    39. system/environment
    40. system/events
    41. system/runtime
    42. system/unload
    43. system/xul-app
    44. tabs/utils
    45. test/assert
    46. test/harness
    47. test/httpd
    48. test/runner
    49. test/utils
    50. ui/button/action
    51. ui/button/toggle
    52. ui/frame
    53. ui/id
    54. ui/sidebar
    55. ui/toolbar
    56. util/array
    57. util/collection
    58. util/deprecate
    59. util/list
    60. util/match-pattern
    61. util/object
    62. util/uuid
    63. window/utils
  13. Firefox for Android
  14. Getting started
    1. Walkthrough
    2. Debugging
    3. Code snippets
  15. APIs
    1. Accounts.jsm
    2. BrowserApp
    3. HelperApps.jsm
    4. Home.jsm
    5. HomeProvider.jsm
    6. JavaAddonManager.jsm
    7. NativeWindow
    8. Notifications.jsm
    9. PageActions.jsm
    10. Prompt.jsm
    11. RuntimePermissions.jsm
    12. Snackbars.jsm
    13. Sound.jsm
    14. Tab
  16. Legacy
  17. Restartless extensions
    1. Overview
  18. Overlay extensions
    1. Overview
  19. Themes
  20. Lightweight themes
    1. Overview
  21. Complete themes
    1. Overview
  22. Publishing add-ons
  23. Guides
    1. Signing and distribution overview
    2. Submit an add-on
    3. Review policies
    4. Developer agreement
    5. Featured add-ons
    6. Contact addons.mozilla.org
  24. Community and support
  25. Channels
    1. Add-ons blog
    2. Add-on forums
    3. Stack Overflow
    4. Development newsgroup
    5. IRC Channel