Performance best practices in extensions

One of Firefox's great advantages is its extreme extensibility. Extensions can do almost anything. There is a down side to this: poorly written extensions can have a severe impact on the browsing experience, including on the overall performance of Firefox itself. This article offers some best practices and suggestions that can not only improve the performance and speed of your extension, but also of Firefox itself.

Improving startup performance

Extensions are loaded and run whenever a new browser window opens. That means every time a window opens, your extension can have an impact on how long it takes the user to see the content they're trying to view. There are several things you can do to reduce the amount of time your extension delays the appearance of the user's desired content.

Load only what you need, when you need it

Don't load things during startup that are only needed if the user clicks a button, or if a given preference is enabled when it's not. If your extension has features that only work when the user has logged into a service, don't load the resources for those features until the user actually logs in.

Use JavaScript code modules

You can create your own JavaScript code modules incorporating sets of features that are only needed under specific circumstances. This makes it easy to load chunks of your extension on the fly as needed, instead of loading everything all at once.

This has an advantage over XPCOM modules, which are always loaded when your extension starts up.

Of course, for extremely simple extensions it may not make sense to modularize your code.

Defer everything that you can

Most extensions have a load event listener in the main overlay that runs their startup functions. Do as little as possible here. The browser window is blocked while your add-on's load handler runs, so the more it does, the slower Firefox will appear to the user.

If there is anything that can be done even a fraction of a second later, you can use an nsITimer or the window.setTimeout() method to schedule that work for later.  Even a short delay can have a big impact.

General Performance Tips

Avoid Creating Memory Leaks

Memory leaks require the garbage collector and the cycle collector to work harder, which can significantly degrade performance.

Zombie compartments are a particular kind of memory leak that you can detect with minimal effort.  See the Zombie compartments page, especially the Proactive checking of add-ons section.

See Common causes of memory leaks in extensions for ways to avoid zombie compartments and other kinds of leaks.

As well as looking for these specific kinds of leaks, it's worth exercising your extension's functionality and checking the contents of about:memory for any excessive memory usage.  For example, bug 719601 featured a "System Principal" JavaScript compartment containing 100s of MBs of memory, which is much larger than usual.

Use JavaScript Modules

JavaScript modules are just like any other JavaScript, with the exception that they are singletons and Firefox can cache the compiled code for faster use the next time the browser is started. Any time your add-on loads JavaScript from an <script> element you should consider using a JavaScript Module instead. For more on how JavaScript modules work, see the Using JavaScript Code Modules page.

Avoid Writing Slow CSS

  • Read the "writing efficient CSS" guide.
  • 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.

Avoid DOM mutation event listeners

Adding DOM mutation listeners to a document disables most DOM modification optimizations and profoundly degrades the performance of further DOM modifications to that document. Moreover, removing the listeners does not reverse the damage. For these reasons, the following events should be avoided wherever possible: DOMAttrModified, DOMAttributeNameChanged, DOMCharacterDataModified, DOMElementNameChanged, DOMNodeInserted, DOMNodeInsertedIntoDocument, DOMNodeRemoved, DOMNodeRemovedFromDocument, DOMSubtreeModified

For more on these events and their deprecation, see Mutation events. Use Mutation Observers instead if possible.

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. Look through the source for examples.

As of Firefox 4.0, many common services are already cached for you in Services.jsm.

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.

Use the right compression level for JAR and XPI files

Reading data from compressed archives costs time. The higher the compression level of the archive, the higher also the performance cost of reading the data from it. So any JAR files in your extension should always be packed with compression level 0 (no compression) for better performance. It may seem counter-intuitive, but doing this will increase the JAR file size and actually decrease the XPI file size as it allows for compression between files inside the JAR to be done when compressing the XPI (essentially a poor-man's solid archive effect).

If your extension doesn't specify em:unpack then its XPI file will not be unpacked in Firefox 4 and used directly instead. This makes choosing a low compression level preferable; we recommend using compression level 1. It will increase the download size only a small amount, even compared to maximum compression.

Use asynchronous I/O

This cannot be stressed enough: never do synchronous I/O on the GUI thread.

  • Never use synchronous XMLHttpRequests (XHR). Use asynchronous requests instead and show a throbber image or message in case you need the user to wait.
  • NetUtils.jsm provides helpers for asynchronous reading and copying of files.
  • Never access a SQLite database synchronously. Use the asynchronous API instead.

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 are 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 onLoad() {
        removeEventListener('load', onLoad, true);
 }, 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. However, 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, as a lot of images are decoded (the frames). Animated images may have their cached representations evicted quite often, causing the frames of your animated images to be reloaded lots of times, not just once. nsITree / tree seems to be extra special in this regard, as it doesn't seem to cache animations at all under certain circumstances.

base64/md5/sha1 implementations

Do not ship your own base64/md5/sha1 implementations. Regarding base64 there are the built-in atob/btoa functions that do the job just well and are available in overlay script as well as in in JavaScript modules and components. Hashes can be computed using nsICryptoHash, which accepts either a string or an nsIInputStream.

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.

Consider using Chrome Workers

You can use a ChromeWorker to execute long running tasks or do data processing.

See also

Document Tags and Contributors

Contributors to this page: wbamberg
Last updated by: wbamberg,