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.

While JavaScript modules can be extremely useful, and provide significant performance benefits, they should be used wisely. Loading modules incurs a small cost, so breaking code up to an unnecessary degree can be counter-productive. Code should be modularized to the extent that doing so increases clarity, and loading of large or expensive chunks of code fragments can be significantly deferred.

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.

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

DOM mutation event listeners are extremely expensive and, once added to a document even briefly, significantly harm its performance. As mutation events are officially deprecated, and there are many alternatives, they should be avoided at all costs.

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.

Many common services are already cached for you in Services.jsm.

Use asynchronous I/O

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

Any kind of I/O on the main thread, be it disk or network I/O, can cause serious UI responsiveness issues.

  • Never use synchronous XMLHttpRequests.
  • NetUtils.jsm provides helpers for asynchronous reading and copying of files.
  • Never access a SQLite database synchronously. Use the asynchronous API instead.
  • Performing sequential, asynchronous operations can often be greatly simplified using Promises.

Avoid mouse movement events

Avoid using mouse event listeners, including mouseover, mouseout, mouseenter, mouseexit, and especially mousemove. These events happen with high frequency, so their listeners can trivially create very high CPU overhead.

When these events cannot be avoided, computation during the listeners should be kept to a minimum and real work throttled. The listeners should be added to the most specific element possible, and removed when not immediately necessary.

Avoid animated images

Animated images are much more expensive than generally expected, especially when used in XUL tree elements..

Consider using Chrome Workers

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

See also