Performance/Addons/BestPractices

Draft-template-image.png THIS PAGE IS A WORKING DRAFT Pencil-emoji U270F-gray.png
The page may be difficult to navigate, and some information on its subject might be incomplete and/or evolving rapidly.
If you have any questions or ideas, please add them as a new topic on the discussion page.

A list of recommendations for add-on authors to help keep Firefox fast and responsive.

Once it matures, we'll move it over to MDC. Please contribute your tips!


Use JavaScript Modules

Instead of the <script> tag in overlays: JavaScript that is loaded via the script tag can slow window load. If your script is not needed as part of window setup, and doesn't need to be loaded for each individual window, then load it on-demand as a module.

Instead of JS XPCOM services: JavaScript code modules are singletons, are fastloaded, and don't require XPCOM like a full-blown JS XPCOM service does.

Avoid mozIJSSubScriptLoader: Scripts imported this way do not use fastload and are loaded and evaluated from the original source every time loadSubScript is called. Convert any uses to proper modules if possible.

And the rest... JS Modules have the "load-once use-often" property and that this means they are excellent to share cross-window shared information as well, or can be used to implement observers (instead of registering one redundant observer per window), which may even keep accidental observer leaks to a minimum.

Leaks Keep in mind that JS modules may cause leaks, if the exported functions/properties store references to window-scope objects.

For more on how JavaScript modules work, check the MDC page.

Memoization

Create XPCOM services lazily, and cache frequently accessed ones, using memoizing getters, either by replacing the getter with a property:

o = {
  get _observerSvc() {
    delete this._observerSvc;
    let svc = Cc["@mozilla.org/observer-service;1"].
              getService(Ci.nsIObserverService);
    return this._observerSvc = svc;
  }
};

Or, for properties whose memoizing getter is in a prototype, by shadowing the prototype getter with an instance getter:

function O() {}
O.prototype = {
  get _observerSvc() {
    let svc = Cc["@mozilla.org/observer-service;1"].
              getService(Ci.nsIObserverService);
    this.__defineGetter__("_observerSvc", function() svc);
    return this._observerSvc;
  }
}

(Examples cribbed from the DTrace page.)

Avoid Writing Slow CSS

  1. Read the "writing efficient CSS" guide.
  2. Remember that any selector in your rule which might match many different nodes is a source of inefficiency during either selector matching or dynamic update processing. This is especially bad for the latter if the selector can dynamically start or stop matching. Avoid unqualified ":hover" like the plague.

Lazily Load Services

The XPCOMUtils JavaScript module provides two methods for lazily loading things:

  • defineLazyGetter() defines a function on a specified object that acts as a getter which will be created the first time it's used. See examples.
  • defineLazyServiceGetter() defines a function on a specified object which acts as a getter for a service. The service isn't obtained until the first time it's used. See examples.

Reduce File I/O

TODO: Give examples below, link to code, bugs, docs.

  • If you're targeting Firefox 3.6 and earlier, or if you're specifying em:unpack then use chrome JARs!
  • Combine CSS
  • Combine pref files
  • Combine interfaces into a single .idl to reduce xpt files
  • Combine toolbar icons in a single file.

Asynchronous I/O

  • Never use synchronous XMLHttpRequests (XHR). Use asynchronous requests instead and show a throbber image or message in case you need the user to wait.

Asynchronous SQL Queries

Mozilla's SQLite module is called mozStorage, and it has an API for executing your SQL queries on a separate thread. View documentation and examples.

Unnecessary onreadystatechange in XHR

addEventListener(load/error) and/or xhr.onload/.onerror are usually sufficient for most uses and will only be called once, contrary to onreadystatechange.

When using XHR in websites people tend to use onreadystatechange (for compatiblity reasons). Often it is enough to just load the resource or handle errors. load/error event listener are far less often called than onreadystatechange, i.e. only once, and you don't need to check readyState or figure out if it is an error or not. Only use onreadystatechange if you want to process the response while it is still arriving.

Removing Event Listeners

Remove event listener if they not needed any more. It is better to actually remove event listener instead of just having some flag to check if the listener is active which is checked every time when an event is propagated. Abandon schemes like:

function onMouseOver(evt) { if (is_active) { /* doSomeThing */ } }

Also, remove "fire-once" listeners again:

function init() {
  var largeArray;
  addEventListener('load', function() {
       removeEventListener('load', arguments.callee, true);
       largeArray.forEach();
}, true);

Else a lot of closure stuff might be still referenced (largeArray in this example). And the listener will sit idle in some internal table.

Populate menus as needed

Populate "context" menus (page, tabs, tools) as needed and keep computation to a minimum (UI responsiveness). There is no need to populate the context menu every time something changes. It is enough to populate it once the user actually needs it. Add a listener to the "popupshowing" event and compute there.

Avoid mouse movement events

Avoid mouse movement events (enter/over/exit) or at least keep computation to a minimum. Mouse movement events, especially the mouseover event, usually happen at high frequency. Best would be to only store the new information and compute "stuff" once the user actually requests it (e.g. in a popupshowing event). Also don't forget to remove the event listeners when no longer needed (see above).

Avoid polling

Use nsIObserverService functionality instead. Everybody is free to post "custom" notifications via nsIObserverService, but few extensions actually use this. Although, a lot of other services also provide observer functionality, such as nsIPrefBranch2.

apng/agif inappropriate in a lot of cases

Animations require a lot of time to set up (basically decoding many images, not just one; add timers and such to that). Then the image caches often get invalidated, causing your animated images to be reloaded lots of times. nsITree/<tree> seems to be extra special in this regard, as it seem to not cache animations at all under some circumstances.

base64/md5/sha1 implementations

Do not ship own base64/md5/sha1 implementations. For base64 there are the built-in atob/btoa functions that do the job just well (also available in modules and js components). Hashes can be computed nsICryptoHash, which accepts either a string or an nsIInputStream. See MDC documentation.

Image sprites

You may combine multiple images into one (sprites). See "-moz-image-region". Most XUL widgets that are used to display some image (incl. button and toolbarbutton) allow to use "list-style-image". Avoid the imagesrc/src attributes to define images where possible.

Avoid early resource/chrome URL access

Accessing resource/chrome URLs your or other extensions register too early, i.e. before chrome registration is complete and the profile is fully loaded, is bad as it may bypass the fastload cache, may trigger bugs or have other side effects (e.g. it just to incorrectly initialize the plugin registration so that plugin the user explicitly disabled got enabled again). A common error is to use the Stringbundle service to load bundles from "your" chrome.

Consider using web workers and/or nsIThread

The latter is admittedly difficult to get right (no DOM access, store references to avoid garbage collector hazards). Web workers are far less "dangerous". See MDC for some examples. If you use threads then make sure to test on a multi-core system. A single core system will hide crashed.

Please note that as of Firefox 4, nsIThread.dispatch does not accept nsIRunnable-s implemented in Javascript and created on a different thread than the thread you're trying to dispatch the nsIRunnable to. This limitation was implemented to avoid crashes caused by changes especially to the Javascript strings implementation. This effectively means that javascript extensions cannot use the nsIThread API anymore to execute own jobs on different threads than the main thread. Consider Web/ChromeWorker as a replacement, which are severely limited in what you can do with them, or just don't use threads.

Cache Stringbundles

Consider to cache stringbundles if you have lots and lots of strings in a bundle.

Generators

Leaks