Source code

Revision control

Copy as Markdown

Other Tools

// Any copyright is dedicated to the Public Domain.
"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}`
);
}