Modified: trunk/LayoutTests/ChangeLog (225728 => 225729)
--- trunk/LayoutTests/ChangeLog 2017-12-09 21:42:02 UTC (rev 225728)
+++ trunk/LayoutTests/ChangeLog 2017-12-10 02:53:29 UTC (rev 225729)
@@ -1,3 +1,13 @@
+2017-12-09 Darin Adler <[email protected]>
+
+ Add test demonstrating leaks that happen when we create reference cycles with DOM objects
+ https://bugs.webkit.org/show_bug.cgi?id=180323
+
+ Reviewed by Filip Pizlo.
+
+ * fast/dom/reference-cycle-leaks-expected.txt: Added.
+ * fast/dom/reference-cycle-leaks.html: Added.
+
2017-12-09 Ryosuke Niwa <[email protected]>
iOS: Crash in Document::updateLayout() via Document::processViewport
Added: trunk/LayoutTests/fast/dom/reference-cycle-leaks-expected.txt (0 => 225729)
--- trunk/LayoutTests/fast/dom/reference-cycle-leaks-expected.txt (rev 0)
+++ trunk/LayoutTests/fast/dom/reference-cycle-leaks-expected.txt 2017-12-10 02:53:29 UTC (rev 225729)
@@ -0,0 +1,24 @@
+Tests for leaks caused by reference cycles that pass through the DOM implementation
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS checkForNodeLeaks(emptyFunction) is "did not leak"
+PASS checkForNodeLeaks(createNode) is "did not leak"
+PASS checkForNodeLeaks(createEventListenerCycle) is "did not leak"
+PASS checkForNodeLeaks(createTreeWalkerNodeCycle) is "did not leak"
+PASS checkForNodeLeaks(createTreeWalkerFilterCycle) is "did not leak"
+PASS checkForNodeLeaks(createPromiseCycle) is "did not leak"
+FAIL checkForNodeLeaks(createCustomEventDetailsCycle) should be did not leak. Was leaked.
+FAIL checkForNodeLeaks(createErrorEventDataCycle) should be did not leak. Was leaked.
+---- Did not test ExtendableMessageEvent because it is not enabled.
+FAIL checkForNodeLeaks(createMessageEventDataCycle) should be did not leak. Was leaked.
+FAIL checkForNodeLeaks(createPopStateEventStateCycle) should be did not leak. Was leaked.
+FAIL checkForNodeLeaks(createPromiseRejectionEventPromiseCycle) should be did not leak. Was leaked.
+PASS checkForNodeLeaks(createPromiseRejectionEventPromiseFunctionCycle) is "did not leak"
+FAIL checkForNodeLeaks(createPromiseRejectionEventReasonCycle) should be did not leak. Was leaked.
+PASS successfullyParsed is true
+Some tests failed.
+
+TEST COMPLETE
+
Added: trunk/LayoutTests/fast/dom/reference-cycle-leaks.html (0 => 225729)
--- trunk/LayoutTests/fast/dom/reference-cycle-leaks.html (rev 0)
+++ trunk/LayoutTests/fast/dom/reference-cycle-leaks.html 2017-12-10 02:53:29 UTC (rev 225729)
@@ -0,0 +1,173 @@
+<!DOCTYPE html>
+<script src=""
+<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 = 20;
+
+ 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 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 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 });
+}
+
+function createIDBRequestResultCycle()
+{
+ // FIXME: Need to write this test and reorganize so it can be asynchronous.
+ // Get an IDBRequest with a result that is a structured clone (see IDBTransaction::didGetRecordOnServer).
+ // Add a property to the result object that references the request.
+ // Add another property to the result object that references a leak detection node.
+}
+
+function createPaymentResponseDetailsCycle()
+{
+ // FIXME: Need to write this test and reorganize so it can be asynchronous.
+ // Get a PaymentResponse, requires simulating a successful payment.
+ // Add a property to the details object that references the PaymentResponse.
+ // Add another property to the details object that references a leak detection node.
+}
+
+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('createPromiseCycle');
+
+ runLeakTest('createCustomEventDetailsCycle');
+ runLeakTest('createErrorEventDataCycle');
+ runLeakTest('createExtendableMessageEventDataCycle', 'ExtendableMessageEvent');
+ runLeakTest('createMessageEventDataCycle');
+ runLeakTest('createPopStateEventStateCycle');
+ runLeakTest('createPromiseRejectionEventPromiseCycle');
+ runLeakTest('createPromiseRejectionEventPromiseFunctionCycle');
+ runLeakTest('createPromiseRejectionEventReasonCycle');
+}
+
+startTest();
+
+</script>