mozilla

Revision 49882 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: 49882
  • Created:
  • Creator: nnethercote
  • Is current revision? No
  • Comment 217 words added, 31 words removed

Revision Content

This page explains several common coding patterns that cause extension to cause zombie compartments, which 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 things in a JavaScript module

The most common problem is extensions holding onto references to 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);
}

This can also happen if you forget to prefix local variables with var, which means they end up belonging to the global scope:

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

For 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.

Storing references to things in windows

This is particularly bad if the window is a long-lived one such as browser.xul.

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

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.

Miscellaneous

Holding onto event observers for too long is another problem.  See bug 714509 for an example.

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

Revision Source

<p>This page explains several common coding patterns that cause extension to cause <a href="/en/Zombie_compartments" title="Zombie compartments">zombie compartments</a>, which are a particular kind of memory leak.</p>
<p>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>
<h2>Storing references to things in a JavaScript module</h2>
<p>The most common problem is extensions holding onto references to <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>This can also happen if you forget to prefix local variables with <code>var</code>, which means they end up belonging to the global scope:</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 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>
<h2>Storing references to things in windows</h2>
<p>This is particularly bad if the window is a long-lived one such as <code>browser.xul</code>.</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>
<h2>Failing to clean up event listeners</h2>
<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>
<h2>Miscellaneous</h2>
<p>Holding onto event observers for too long is another problem.  See <a class="link-https" href="https://bugzilla.mozilla.org/show_bug.cgi?id=714509#c7" title="https://bugzilla.mozilla.org/show_bug.cgi?id=714509#c7">bug 714509</a> for an example.</p>
<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