Performance/Addons/BestPractices

From MozillaWiki
Jump to: navigation, search

This page has MOVED to MDN:

MDN: Performance Best Practices in Extensions

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

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, 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 js modules and components. Hashes can be computed using nsICryptoHash, which accepts either a string or an nsIInputStream. See the MDC documentation for nsICryptoHash

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.