Revision 49885 of Common causes of memory leaks in extensions

  • Revision slug: Extensions/Common_causes_of_memory_leaks_in_extensions
  • Revision title: Common causes of memory leaks in extensions
  • Revision id: 49885
  • Created:
  • Creator: nnethercote
  • Is current revision? No
  • Comment 118 words added

Revision Content

This page explains coding patterns that cause extension to cause memory leaks.

Causes of zombie compartments

Zombie compartments are a particular kind of memory leak.  All zombie compartments in extensions are caused by a failure to release resources appropriately in a certain circumstances, such as when a window is closed, a page unloads, or an extension is disabled or removed.

Storing references to window objects and DOM nodes

The most common problem is extensions holding onto references to content windows for too long.

For example, in XUL overlay code:

let contentWindows = [];
function inbrowserxuloverlay(contentWindow) {
  // forgetting or failing to pop the content window thing again
  contentWindows.push(contentWindow);
}

This will keep the content window compartments alive until the browser window is closed. Users often only open a single browser window per session and use tabs, in which case the leaked compartments will live for the whole life of the session.

A similar problem is holding onto window objects or DOM nodes (such as window.document) for too long by storing them in a JavaScript module.  For example:

let windows = [];
function injsmod(window) {
  // forgetting or failing to pop the window again
  windows.push(window);
}

Both of these cases can happen if you forget to prefix local variables with var, which means they end up belonging to the global scope.  For example:

function injsmod2(window) {
  // Implicit var declaration in the js global, holding a strong ref to the document
  doc = window.document;
}

For real-world examples, see bug 712733, bug 725875, and bug 727552.  Unprefixed local variables can be avoided by using ECMAScript 5's strict mode.  Strict mode also excludes several other error-prone code patterns.

Problems in bootstrapped (restartless) add-ons

Bootstrapped extensions use a bootstrap.js compartment.  If you put a reference to anything within this compartment into a long-lived window object (such as browser.xul) or JavaScript module or XPCOM components, the bootstrap.js compartment will become a zombie.  For example:

// Holding a strong ref to the bootstrapped component
var a = document.createElement("a");
a.onlick = my_bootstrap_js_method;

See also Kris Maglione's guide to cleaning up bootstrapped extensions.

Failing to clean up event listeners

Extensions can be disabled and removed by user actions, but it also happens when an add-on is updated.  If a bootstrapped (restartless) extension fails to clean up event listeners when disabled/removed, the listeners will still reference the enclosing scope -- usually the bootstrap.js Sandbox -- and therefore keep that scope (and its enclosing compartment) alive until the window is unloaded. If the window in question is browser.xul or some long-lived web app such as Gmail, the leaked compartment might survive for quite some time.

function leakref() {}

function main(window) {
  // This is a potential leak, as the window (browser.xul) will hold on to this scope via
  // the listener.
  window.addEventListener("leaky", leakref, true);

  // The following line still fails to avoid the leak, as useCapture differs.
  // This kind of subtle bug is very common.
  // unload(function() window.removeEventListener("leaky", leakref, false), window);

  // This is the right way to do it remove the listener.
  unload(function() window.removeEventListener("leaky", leakref, true), window);
}

Observers

Holding onto event observers for too long is another problem.  Observers that use strong references are a common cause of leaking whole chrome windows; it is possible to leak content windows, too, but that is less common.

Consider the following example:

Services.obs.addObserver({
  observe: function(s, t, d) {
    window.document.documentElement.setAttribute(
      "pbm", (d == "enter") ? "private" : "normal");
  }
}, "private-browsing", false);

The false parameter causes the observer service to use a strong reference to the Observer object, which will cause it to hold onto the whole window.

To avoid this problem, either specify true for the third parameter to addObserver, or explicitly call removeObserver in an unload event listener.

Finally, a lot of services other than nsIObserverService accept nsIObserver parameters or other interfaces and will keep strong references around.

Causes of other kinds of leaks

Another common cause of leaks is forgetting to unload JavaScript modules in bootstrapped add-ons.  These leaks cannot be detected by looking at about:compartments or about:memory because such modules live within the main System compartment.

Other information

Also see Using XPCOM in JavaScript without leaking (though that page could use some updating).

Revision Source

<p>This page explains coding patterns that cause extension to cause memory leaks.</p>
<h2>Causes of zombie compartments</h2>
<p><a href="/en/Zombie_compartments" title="Zombie compartments">Zombie compartments</a> are a particular kind of memory leak.  All zombie compartments in extensions are caused by a failure to release resources appropriately in a certain circumstances, such as when a window is closed, a page unloads, or an extension is disabled or removed.</p>
<h3>Storing references to <code>window</code> objects and DOM nodes</h3>
<p>The most common problem is extensions holding onto references to content windows for too long.</p>
<p>For example, in XUL overlay code:</p>
<pre class="bz_comment_text" id="comment_text_7">let contentWindows = [];
function inbrowserxuloverlay(contentWindow) {
  // forgetting or failing to pop the content window thing again
  contentWindows.push(contentWindow);
}
</pre>
<p id="comment_text_7">This will keep the content window compartments alive until the browser window is closed. Users often only open a single browser window per session and use tabs, in which case the leaked compartments will live for the whole life of the session.</p>
<p>A similar problem is holding onto<code> window</code> objects or DOM nodes (such as <code>window.document</code>) for too long by storing them in a JavaScript module.  For example:</p>
<pre class="bz_comment_text" id="comment_text_0"><span class="quote">let windows = [];
function injsmod(window) {
  // forgetting or failing to pop the window again
  windows.push(window);
}</span>
</pre>
<p>Both of these cases can happen if you forget to prefix local variables with <code>var</code>, which means they end up belonging to the global scope.  For example:</p>
<pre class="bz_comment_text" id="comment_text_0"><span class="quote">function injsmod2(window) {
  // Implicit var declaration in the js global, holding a strong ref to the document
  doc = window.document;
}</span>
</pre>
<p>For real-world examples, see <a class="link-https" href="https://bugzilla.mozilla.org/show_bug.cgi?id=712733#c17" title="https://bugzilla.mozilla.org/show_bug.cgi?id=712733#c17">bug 712733</a>, <a class="link-https" href="https://bugzilla.mozilla.org/show_bug.cgi?id=725875" title="https://bugzilla.mozilla.org/show_bug.cgi?id=725875">bug 725875</a>, and <a class="link-https" href="https://bugzilla.mozilla.org/show_bug.cgi?id=727552#c3" title="https://bugzilla.mozilla.org/show_bug.cgi?id=727552#c3">bug 727552</a>.  Unprefixed local variables can be avoided by using <a href="/en/JavaScript/Reference/Functions_and_function_scope/Strict_mode" title="en/JavaScript/Strict_mode">ECMAScript 5's strict mode</a>.  Strict mode also excludes several other error-prone code patterns.</p>
<h3>Problems in bootstrapped (restartless) add-ons</h3>
<p id="comment_text_7">Bootstrapped extensions use a <code>bootstrap.js</code> compartment.  If you put a reference to anything within this compartment into a long-lived window object (such as <code>browser.xul</code>) or JavaScript module or XPCOM components, the <code>bootstrap.js</code> compartment will become a zombie.  For example:</p>
<pre class="bz_comment_text" id="comment_text_0"><span class="quote">// Holding a strong ref to the bootstrapped component
var a = document.createElement("a");
a.onlick = my_bootstrap_js_method;</span>
</pre>
<p>See also Kris Maglione's <a class="external" href="http://maglione-k.users.sourceforge.net/bootstrapped.xhtml" title="http://maglione-k.users.sourceforge.net/bootstrapped.xhtml">guide to cleaning up bootstrapped extensions</a>.</p>
<h3>Failing to clean up event listeners</h3>
<p>Extensions can be disabled and removed by user actions, but it also happens when an add-on is updated.  If a bootstrapped (restartless) extension fails to clean up event listeners when disabled/removed, the listeners will still reference the enclosing scope -- usually the <code>bootstrap.js</code> Sandbox -- and therefore keep that scope (and its enclosing compartment) alive until the window is unloaded. If the window in question is <code>browser.xul</code> or some long-lived web app such as Gmail, the leaked compartment might survive for quite some time.</p>
<pre class="bz_comment_text" id="comment_text_1">function leakref() {}

function main(window) {
  // This is a potential leak, as the window (browser.xul) will hold on to this scope via
  // the listener.
  window.addEventListener("leaky", leakref, true);

  // The following line still fails to avoid the leak, as useCapture differs.
  // This kind of subtle bug is very common.
  // unload(function() window.removeEventListener("leaky", leakref, false), window);

  // This is the right way to do it remove the listener.
  unload(function() window.removeEventListener("leaky", leakref, true), window);
}
</pre>
<h3>Observers</h3>
<p>Holding onto event observers for too long is another problem.  Observers that use strong references are a common cause of leaking whole chrome windows; it is possible to leak content windows, too, but that is less common.</p>
<p>Consider the following example:</p>
<pre class="bz_comment_text" id="comment_text_7">Services.obs.addObserver({
  observe: function(s, t, d) {
    window.document.documentElement.setAttribute(
      "pbm", (d == "enter") ? "private" : "normal");
  }
}, "private-browsing", false);
</pre>
<p>The <code>false </code>parameter causes the observer service to use a strong reference to the Observer object, which will cause it to hold onto the whole window.</p>
<p>To avoid this problem, either specify <code>true</code> for the third parameter to <a href="/en/XPCOM_Interface_Reference/nsIObserverService#addObserver%28%29" title="en/XPCOM_Interface_Reference/nsIObserverService#addObserver%28%29"><code>addObserver</code></a>, or explicitly call <a href="/en/XPCOM_Interface_Reference/nsIObserverService#addObserver()" title="en/XPCOM_Interface_Reference/nsIObserverService#addObserver()"><code>removeObserver</code></a> in an unload event listener.</p>
<p>Finally, a lot of services other than <code>nsIObserverService</code> accept <code>nsIObserver</code> parameters or other interfaces and will keep strong references around.</p>
<h2>Causes of other kinds of leaks</h2>
<p>Another common cause of leaks is forgetting to unload JavaScript modules in bootstrapped add-ons.  These leaks cannot be detected by looking at about:compartments or about:memory because such modules live within the main System compartment.</p>
<h2>Other information</h2>
<p>Also see <a href="/en/Using_XPCOM_in_JavaScript_without_leaking" title="Using XPCOM in JavaScript without leaking">Using XPCOM in JavaScript without leaking</a> (though that page could use some updating).</p>
Revert to this revision