blob: 4d1cff5a1a5f760c1be24bcb7ffc5ac4a3f8bc40 [file] [log] [blame]
<!DOCTYPE html>
<script src="../../resources/js-test.js"></script>
<script>
description('Tests for leaks caused by reference cycles that pass through the DOM implementation');
function checkForNodeLeaks(testFunction, underlyingClass)
{
// Bump this number as high as we need to, to get reproducible results.
const repetitions = 40;
gc();
const beforeCount = internals.numberOfLiveNodes();
for (var i = 0; i < repetitions; ++i)
testFunction();
gc();
const leaks = internals.numberOfLiveNodes() - beforeCount;
if (leaks == repetitions)
return "leaked";
if (leaks < repetitions / 10)
return "did not leak";
return "leaked an unexpected number of nodes: " + leaks + " leaks in " + repetitions + " runs";
}
function emptyFunction()
{
}
function createNode()
{
document.createTextNode("");
}
function createEventListenerCycle()
{
const leakDetectionNode = document.createTextNode("");
leakDetectionNode.addEventListener("x", function () { return leakDetectionNode; });
}
function createPromiseCycle()
{
const leakDetectionNode = document.createTextNode("");
const promise = new Promise(function (resolve, reject) { });
promise.cycle = promise;
promise.leakDetectionNode = leakDetectionNode;
}
function createTreeWalkerNodeCycle()
{
const leakDetectionNode = document.createTextNode("");
leakDetectionNode.treeWalker = document.createTreeWalker(leakDetectionNode);
}
function createTreeWalkerFilterCycle()
{
const leakDetectionNode = document.createTextNode("");
const filter = { leakDetectionNode: leakDetectionNode, acceptNode: function(node) { return leakDetectionNode; } };
leakDetectionNode.treeWalker = document.createTreeWalker(document, 0, filter);
}
function createRangeCycle()
{
const leakDetectionNode = document.createTextNode("");
const range = new Range();
range.setStart(leakDetectionNode, 0);
leakDetectionNode.range = range;
}
function createStaticRangeCycle()
{
const leakDetectionNode = document.createTextNode("");
leakDetectionNode.staticRange = new StaticRange({startContainer: leakDetectionNode, startOffset: 0, endContainer: leakDetectionNode, endOffset: 0});
}
function createCustomEventDetailsCycle()
{
const leakDetectionNode = document.createTextNode("");
leakDetectionNode.event = new CustomEvent("x", { detail: leakDetectionNode });
}
function createErrorEventDataCycle()
{
const leakDetectionNode = document.createTextNode("");
leakDetectionNode.event = new ErrorEvent("x", { error: leakDetectionNode });
}
function createExtendableMessageEventDataCycle()
{
const leakDetectionNode = document.createTextNode("");
leakDetectionNode.event = new ExtendableMessageEvent("x", { data: leakDetectionNode });
}
function createMessageEventDataCycle()
{
const leakDetectionNode = document.createTextNode("");
leakDetectionNode.event = new MessageEvent("x", { data: leakDetectionNode });
}
function createPaymentMethodChangeEventMethodDetailsCycle()
{
const leakDetectionNode = document.createTextNode("");
leakDetectionNode.event = new PaymentMethodChangeEvent("x", { methodDetails: leakDetectionNode });
}
function createPromiseRejectionEventPromiseCycle()
{
const leakDetectionNode = document.createTextNode("");
const promise = new Promise(function (resolve, reject) { });
promise.leakDetectionNode = leakDetectionNode;
leakDetectionNode.event = new PromiseRejectionEvent("x", { promise: promise });
}
function createPromiseRejectionEventPromiseFunctionCycle()
{
const leakDetectionNode = document.createTextNode("");
const promise = new Promise(function (resolve, reject) { return leakDetectionNode; });
leakDetectionNode.event = new PromiseRejectionEvent("x", { promise: promise });
}
function createPromiseRejectionEventReasonCycle()
{
const leakDetectionNode = document.createTextNode("");
const promise = new Promise(function (resolve, reject) { });
leakDetectionNode.event = new PromiseRejectionEvent("x", { promise: promise, reason: leakDetectionNode });
}
function createPopStateEventStateCycle()
{
const leakDetectionNode = document.createTextNode("");
leakDetectionNode.event = new PopStateEvent("x", { state: leakDetectionNode });
}
// Reference cycle of IDBRequest and IDBCursor is tested in
// storage/indexeddb/value-cursor-cycle.html and LayoutTests/storage/indexeddb/result-request-cycle.html.
// PaymentResponse details reference cycle is tested in
// http/tests/paymentrequest/payment-response-reference-cycle-leak.https.html.
function createRTCStatsReportCycle()
{
// FIXME: Need to write this test and reorganize so it can be asynchronous.
// Get an RTCStatsReport.
// Get one of the objects from the map.
// Add a property to that object that references the report.
// Add another property to that object that references a leak detection node.
}
function runLeakTest(testFunctionName, underlyingClassName)
{
if (underlyingClassName && !(underlyingClassName in window))
debug('---- Did not test ' + underlyingClassName + ' because it is not enabled.');
else
shouldBeEqualToString('checkForNodeLeaks(' + testFunctionName + ')', 'did not leak');
}
function startTest()
{
if (!window.internals || !internals.numberOfLiveNodes) {
testFailed('Test requires windows.internals, so must be run inside WebKitTestRunner');
return;
}
runLeakTest('emptyFunction');
runLeakTest('createNode');
runLeakTest('createEventListenerCycle');
runLeakTest('createTreeWalkerNodeCycle');
runLeakTest('createTreeWalkerFilterCycle');
runLeakTest('createRangeCycle');
runLeakTest('createStaticRangeCycle');
runLeakTest('createPromiseCycle');
runLeakTest('createCustomEventDetailsCycle');
runLeakTest('createErrorEventDataCycle');
runLeakTest('createExtendableMessageEventDataCycle', 'ExtendableMessageEvent');
runLeakTest('createMessageEventDataCycle');
runLeakTest('createPaymentMethodChangeEventMethodDetailsCycle', 'PaymentMethodChangeEvent');
runLeakTest('createPopStateEventStateCycle');
runLeakTest('createPromiseRejectionEventPromiseCycle');
runLeakTest('createPromiseRejectionEventPromiseFunctionCycle');
runLeakTest('createPromiseRejectionEventReasonCycle');
}
startTest();
</script>