// Any copyright is dedicated to the Public Domain. // http://creativecommons.org/publicdomain/zero/1.0/ "use strict"; // This function runs a number of tests where: // // 1. An iframe is created // 2. The target callback is executed with the iframe's contentWindow as // an argument. // 3. The iframe is destroyed and GC is forced. // 4. Verifies that the iframe's contentWindow has been GC'd. // // Different ways of destroying the iframe are checked. Simple // remove(), destruction via bfcache, or replacement by document.open(). // // Please pass a target callback that exercises the API under // test using the given window. The callback should try to leave the // API active to increase the liklihood of provoking an API. Any activity // should be canceled by the destruction of the window. async function checkForEventListenerLeaks(name, target) { // Test if we leak in the case where we do nothing special to // the frame before removing it from the DOM. await _eventListenerLeakStep(target, `${name} default`); // Test the case where we navigate the frame before removing it // from the DOM so that the window using the target API ends up // in bfcache. await _eventListenerLeakStep(target, `${name} bfcache`, frame => { frame.src = "about:blank"; return new Promise(resolve => (frame.onload = resolve)); }); // Test the case where we document.open() the frame before removing // it from the DOM so that the window using the target API ends // up getting replaced. await _eventListenerLeakStep(target, `${name} document.open()`, frame => { frame.contentDocument.open(); frame.contentDocument.close(); }); } // ---------------- // Internal helpers // ---------------- // Utility function to create a loaded iframe. async function _withFrame(doc, url) { let frame = doc.createElement("iframe"); frame.src = url; doc.body.appendChild(frame); await new Promise(resolve => (frame.onload = resolve)); return frame; } // This function defines the basic form of the test cases. We create an // iframe, execute the target callback to manipulate the DOM, remove the frame // from the DOM, and then check to see if the frame was GC'd. The caller // may optionally pass in a callback that will be executed with the // frame as an argument before removing it from the DOM. async function _eventListenerLeakStep(target, name, extra) { let frame = await _withFrame(document, "empty.html"); await target(frame.contentWindow); let weakRef = SpecialPowers.Cu.getWeakReference(frame.contentWindow); ok(weakRef.get(), `should be able to create a weak reference - ${name}`); if (extra) { await extra(frame); } frame.remove(); frame = null; // Perform many GC's to avoid intermittent delayed collection. await new Promise(resolve => SpecialPowers.exactGC(resolve)); await new Promise(resolve => SpecialPowers.exactGC(resolve)); await new Promise(resolve => SpecialPowers.exactGC(resolve)); ok( !weakRef.get(), `iframe content window should be garbage collected - ${name}` ); }