<!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>
