Changeset 261391 in webkit


Ignore:
Timestamp:
May 8, 2020 10:14:15 AM (4 years ago)
Author:
ysuzuki@apple.com
Message:

Code pattern in GC tests in LayoutTests is broken
https://bugs.webkit.org/show_bug.cgi?id=211595

Reviewed by Saam Barati.

LayoutTests have several tests which attempt to ensure that document is not leaked. But they are broken since these tests are not correctly
handling conservative GC of JavaScriptCore. These tests are taking a following approach.

  1. Allocate *one* iframe doing something inside it.
  2. Remove iframe from the parent document to make it released.
  3. Repeatedly invoke GC and test whether (1)'s document gets collected.

This is not the right approach. Since JavaScriptCore has conservative GC, it is easily possible that (1)'s pointer value remains in some of
conservative roots: CPU registers, stack etc. And JavaScriptCore conservative GC scans it and keeps it alive. The above approach makes the
test super flaky and any unrelated changes in JavaScriptCore and C compiler can make this test failed.

This is not a problem in practice. Web pages are executing various kind of code and they clobber conservative roots. However, tests in
LayoutTests are too simple to keep it alive accidentally.

The right approach for conservative GC is the following.

  1. Allocate *many* iframes doing something inside them with loop. By creating iframes with loop, for every iteration, it is likely that the same CPU registers and stack locations are overwritten by the last created iframe reference. This dramatically reduces the possibility for GC to find these addresses in conservative roots.
  2. Remove iframes from the parent document to make them released.
  3. Repeatedly invoke GC and test whether *one of (1) iframes* gets collected at least. Theoretically this is still possible that completely unrelated integer value in conservative roots can point to the reference of (1). By allocating many iframes in (1) and testing one of them, we can reduce the possibility of this conflict.

This patch adds testDocumentIsNotLeaked helper function to enforce this pattern. And rewrite broken tests with this helper to make it less-flaky.

  • highlight/highlight-world-leak.html:
  • http/tests/IndexedDB/collect-IDB-objects.https.html:
  • http/tests/media/clearkey/collect-webkit-media-session.html:
  • http/tests/media/media-stream/collect-media-devices.https.html:
  • http/tests/resources/gc.js: Added.

(window.gc.gcRec):
(window.gc.window.gc):
(nukeArray):
(async testDocumentIsNotLeaked):

  • intersection-observer/no-document-leak.html:
  • performance-api/performance-observer-no-document-leak.html:
  • resources/gc.js:

(nukeArray):
(async testDocumentIsNotLeaked):

  • webanimations/leak-document-with-web-animation.html:
Location:
trunk/LayoutTests
Files:
1 added
9 edited

Legend:

Unmodified
Added
Removed
  • trunk/LayoutTests/ChangeLog

    r261383 r261391  
     12020-05-07  Yusuke Suzuki  <ysuzuki@apple.com>
     2
     3        Code pattern in GC tests in LayoutTests is broken
     4        https://bugs.webkit.org/show_bug.cgi?id=211595
     5
     6        Reviewed by Saam Barati.
     7
     8        LayoutTests have several tests which attempt to ensure that document is not leaked. But they are broken since these tests are not correctly
     9        handling conservative GC of JavaScriptCore. These tests are taking a following approach.
     10
     11            1. Allocate *one* iframe doing something inside it.
     12            2. Remove iframe from the parent document to make it released.
     13            3. Repeatedly invoke GC and test whether (1)'s document gets collected.
     14
     15        This is not the right approach. Since JavaScriptCore has conservative GC, it is easily possible that (1)'s pointer value remains in some of
     16        conservative roots: CPU registers, stack etc. And JavaScriptCore conservative GC scans it and keeps it alive. The above approach makes the
     17        test super flaky and any unrelated changes in JavaScriptCore and C compiler can make this test failed.
     18
     19        This is not a problem in practice. Web pages are executing various kind of code and they clobber conservative roots. However, tests in
     20        LayoutTests are too simple to keep it alive accidentally.
     21
     22        The right approach for conservative GC is the following.
     23
     24            1. Allocate *many* iframes doing something inside them with loop. By creating iframes with loop, for every iteration, it is likely that
     25               the same CPU registers and stack locations are overwritten by the last created iframe reference. This dramatically reduces the possibility
     26               for GC to find these addresses in conservative roots.
     27            2. Remove iframes from the parent document to make them released.
     28            3. Repeatedly invoke GC and test whether *one of (1) iframes* gets collected at least. Theoretically this is still possible that completely
     29               unrelated integer value in conservative roots can point to the reference of (1). By allocating many iframes in (1) and testing one of them,
     30               we can reduce the possibility of this conflict.
     31
     32        This patch adds testDocumentIsNotLeaked helper function to enforce this pattern. And rewrite broken tests with this helper to make it less-flaky.
     33
     34        * highlight/highlight-world-leak.html:
     35        * http/tests/IndexedDB/collect-IDB-objects.https.html:
     36        * http/tests/media/clearkey/collect-webkit-media-session.html:
     37        * http/tests/media/media-stream/collect-media-devices.https.html:
     38        * http/tests/resources/gc.js: Added.
     39        (window.gc.gcRec):
     40        (window.gc.window.gc):
     41        (nukeArray):
     42        (async testDocumentIsNotLeaked):
     43        * intersection-observer/no-document-leak.html:
     44        * performance-api/performance-observer-no-document-leak.html:
     45        * resources/gc.js:
     46        (nukeArray):
     47        (async testDocumentIsNotLeaked):
     48        * webanimations/leak-document-with-web-animation.html:
     49
    1502020-05-08  Lauro Moura  <lmoura@igalia.com>
    251
  • trunk/LayoutTests/highlight/highlight-world-leak.html

    r254008 r261391  
    22<html>
    33<body>
    4 <iframe id="testFrame" src="resources/highlight-frame.html"></iframe>
    54<script src="../resources/js-test-pre.js"></script>
     5<script src="../resources/gc.js"></script>
    66<script>
    77description("Tests that using Highlight does not cause the document to get leaked.");
    88window.jsTestIsAsync = true;
    99
    10 function documentShouldDie(documentIdentifier)
    11 {
    12     return new Promise(function(resolve, reject) {
    13         handle = setInterval(function() {
    14             gc();
    15             if (internals && !internals.isDocumentAlive(documentIdentifier)) {
    16                 clearInterval(handle);
    17                 resolve();
     10window.onload = function () {
     11    testDocumentIsNotLeaked(
     12        async function initAndRemove(frameCount)
     13        {
     14            let frames = await new Promise((resolve, reject) => {
     15                let frames = [];
     16                let counter = 0;
     17                function onLoad() {
     18                    counter++;
     19                    if (counter == frameCount)
     20                        resolve(frames);
     21                }
     22                for (let i = 0; i < frameCount; ++i) {
     23                    let frame = document.createElement('iframe');
     24                    frame.src = "resources/highlight-frame.html";
     25                    frame.onload = onLoad;
     26                    document.body.appendChild(frame);
     27                    frames.push(frame);
     28                }
     29            });
     30            let frameIdentifiers = [];
     31            for (let i = 0; i < frameCount; ++i) {
     32                let frame = frames[i];
     33                frameIdentifiers.push(internals.documentIdentifier(frame.contentDocument));
     34                frame.remove();
    1835            }
    19         }, 10);
    20     });
    21 }
     36            nukeArray(frames);
     37            frames = null;
     38            return frameIdentifiers;
     39        }
     40    ).then(
     41        () => testPassed("Document did not leak"),
     42        (error) => testFailed(error.message)
     43    ).finally(finishJSTest);
     44};
    2245
    23 var testFrame = document.getElementById("testFrame");
    24 onload = function() {
    25     let frameDocumentIdentifier = internals.documentIdentifier(testFrame.contentDocument);
    26     testFrame.remove();
    27     setTimeout(function() {
    28         documentShouldDie(frameDocumentIdentifier).then(function() {
    29             testPassed("Document did not leak");
    30             finishJSTest();
    31         });
    32     });
    33 };
    3446</script>
    3547<script src="../resources/js-test-post.js"></script>
  • trunk/LayoutTests/http/tests/IndexedDB/collect-IDB-objects.https.html

    r235819 r261391  
    33<script src="/resources/testharness.js"></script>
    44<script src="/resources/testharnessreport.js"></script>
     5<script src="/resources/gc.js"></script>
    56<script>
    6 function waitFor(duration)
    7 {
    8     return new Promise((resolve) => setTimeout(resolve, duration));
    9 }
    107
    11 var resolveCallback, rejectCallback;
    128var promise = new Promise((resolve, reject) => {
    13     resolveCallback = resolve;
    14     rejectCallback = reject;
     9    if (!window.internals) {
     10        reject("Test require internals API");
     11        return;
     12    }
     13    window.onload = function () {
     14        let promise = testDocumentIsNotLeaked(
     15            async function initAndRemove(frameCount)
     16            {
     17                let frames = await new Promise((resolve, reject) => {
     18                    let frames = [];
     19                    let counter = 0;
     20                    function onMessage() {
     21                        counter++;
     22                        if (counter == frameCount)
     23                            resolve(frames);
     24                    }
     25                    window.addEventListener("message", onMessage);
     26                    for (let i = 0; i < frameCount; ++i) {
     27                        let frame = document.createElement('iframe');
     28                        frame.src = "resources/myidbframe.htm";
     29                        document.body.appendChild(frame);
     30                        frames.push(frame);
     31                    }
     32                });
     33                let frameIdentifiers = [];
     34                for (let i = 0; i < frameCount; ++i) {
     35                    let frame = frames[i];
     36                    frameIdentifiers.push(internals.documentIdentifier(frame.contentDocument));
     37                    frame.src = "non-existent-frame";
     38                }
     39                nukeArray(frames);
     40                frames = null;
     41                return frameIdentifiers;
     42            }
     43        );
     44        resolve(promise);
     45    };
    1546});
    1647
    17 async function done()
    18 {
    19     try {
    20         const frameIdentifier = internals.documentIdentifier(iframe.contentDocument);
    21         iframe.src = "non-existent-frame";
    22         let counter = 0;
    23         while (++counter < 50) {
    24             if (!internals.isDocumentAlive(frameIdentifier)) {
    25                 resolveCallback();
    26                 return;
    27             }
    28             if (window.GCController)
    29                 GCController.collect();
    30 
    31             await waitFor(50);
    32         }
    33         rejectCallback("Test failed");
    34     } catch (e) {
    35         rejectCallback("Test failed: exception " + e);
    36     }
    37 }
    38 
    39 window.addEventListener("message", done);
    40 
    4148promise_test((test) => {
    42     if (!window.internals)
    43         rejectCallback("Test require internals API");
    4449    return promise;
    4550}, "Ensuring frame document gets collected after being stopped in the middle of IDB operations");
    4651
    4752</script>
    48 <iframe src="resources/myidbframe.htm" id="iframe"></iframe>
  • trunk/LayoutTests/http/tests/media/clearkey/collect-webkit-media-session.html

    r235819 r261391  
    33<script src="/resources/testharness.js"></script>
    44<script src="/resources/testharnessreport.js"></script>
     5<script src="/resources/gc.js"></script>
    56<script>
    6 function waitFor(duration)
    7 {
    8     return new Promise((resolve) => setTimeout(resolve, duration));
    9 }
    107
    11 var resolveCallback, rejectCallback;
    128var promise = new Promise((resolve, reject) => {
    13     resolveCallback = resolve;
    14     rejectCallback = reject;
     9    if (!window.internals) {
     10        reject("Test require internals API");
     11        return;
     12    }
     13    window.onload = function () {
     14        let promise = testDocumentIsNotLeaked(
     15            async function initAndRemove(frameCount)
     16            {
     17                let frames = await new Promise((resolve, reject) => {
     18                    let frames = [];
     19                    let counter = 0;
     20                    function onMessage() {
     21                        counter++;
     22                        if (counter == frameCount)
     23                            resolve(frames);
     24                    }
     25                    window.addEventListener("message", onMessage);
     26                    for (let i = 0; i < frameCount; ++i) {
     27                        let frame = document.createElement('iframe');
     28                        frame.src = "resources/mywebkitmediasessionframe.htm";
     29                        document.body.appendChild(frame);
     30                        frames.push(frame);
     31                    }
     32                });
     33                let frameIdentifiers = [];
     34                for (let i = 0; i < frameCount; ++i) {
     35                    let frame = frames[i];
     36                    frameIdentifiers.push(internals.documentIdentifier(frame.contentDocument));
     37                    frame.src = "non-existent-frame";
     38                }
     39                nukeArray(frames);
     40                frames = null;
     41                return frameIdentifiers;
     42            }
     43        );
     44        resolve(promise);
     45    };
    1546});
    1647
    17 async function done()
    18 {
    19     try {
    20         const frameIdentifier = internals.documentIdentifier(iframe.contentDocument);
    21         iframe.src = "non-existent-frame";
    22         let counter = 0;
    23         while (++counter < 50) {
    24             if (!internals.isDocumentAlive(frameIdentifier)) {
    25                 resolveCallback();
    26                 return;
    27             }
    28             if (window.GCController)
    29                 GCController.collect();
    30 
    31             await waitFor(50);
    32         }
    33         rejectCallback("Test failed");
    34     } catch (e) {
    35         rejectCallback("Test failed: exception " + e);
    36     }
    37 }
    38 
    39 window.addEventListener("message", done);
    40 
    4148promise_test((test) => {
    42     if (!window.internals)
    43         rejectCallback("Test require internals API");
    4449    return promise;
    4550}, "Ensure that the frame's document get collected after being stopped while doing some webkit media session calls");
    4651
    4752</script>
    48 <iframe src="resources/mywebkitmediasessionframe.htm" id="iframe"></iframe>
  • trunk/LayoutTests/http/tests/media/media-stream/collect-media-devices.https.html

    r235819 r261391  
    33<script src="/resources/testharness.js"></script>
    44<script src="/resources/testharnessreport.js"></script>
     5<script src="/resources/gc.js"></script>
    56<script>
    6 function waitFor(duration)
    7 {
    8     return new Promise((resolve) => setTimeout(resolve, duration));
    9 }
    107
    11 var resolveCallback, rejectCallback;
    128var promise = new Promise((resolve, reject) => {
    13     resolveCallback = resolve;
    14     rejectCallback = reject;
     9    if (!window.internals) {
     10        reject("Test require internals API");
     11        return;
     12    }
     13    window.onload = function () {
     14        let promise = testDocumentIsNotLeaked(
     15            async function initAndRemove(frameCount)
     16            {
     17                let frames = await new Promise((resolve, reject) => {
     18                    let frames = [];
     19                    let counter = 0;
     20                    function onMessage() {
     21                        counter++;
     22                        if (counter == frameCount)
     23                            resolve(frames);
     24                    }
     25                    window.addEventListener("message", onMessage);
     26                    for (let i = 0; i < frameCount; ++i) {
     27                        let frame = document.createElement('iframe');
     28                        frame.src = "resources/mymediadevicesframe.htm";
     29                        document.body.appendChild(frame);
     30                        frames.push(frame);
     31                    }
     32                });
     33                let frameIdentifiers = [];
     34                for (let i = 0; i < frameCount; ++i) {
     35                    let frame = frames[i];
     36                    frameIdentifiers.push(internals.documentIdentifier(frame.contentDocument));
     37                    frame.src = "non-existent-frame";
     38                }
     39                nukeArray(frames);
     40                frames = null;
     41                return frameIdentifiers;
     42            }
     43        );
     44        resolve(promise);
     45    };
    1546});
    1647
    17 async function done()
    18 {
    19     try {
    20         const frameIdentifier = internals.documentIdentifier(iframe.contentDocument);
    21         iframe.src = "non-existent-frame";
    22         let counter = 0;
    23         while (++counter < 50) {
    24             if (!internals.isDocumentAlive(frameIdentifier)) {
    25                 resolveCallback();
    26                 return;
    27             }
    28             if (window.GCController)
    29                 GCController.collect();
    30 
    31             await waitFor(50);
    32         }
    33         rejectCallback("Test failed");
    34     } catch (e) {
    35         rejectCallback("Test failed: exception " + e);
    36     }
    37 }
    38 
    39 window.addEventListener("message", done);
    40 
    4148promise_test((test) => {
    42     if (!window.internals)
    43         rejectCallback("Test require internals API");
    4449    return promise;
    4550}, "Ensure that the frame's document get collected after being stopped while using MediaDevices");
    4651
    4752</script>
    48 <iframe src="resources/mymediadevicesframe.htm" id="iframe"></iframe>
  • trunk/LayoutTests/intersection-observer/no-document-leak.html

    r235736 r261391  
    33<head>
    44<script src="../resources/js-test-pre.js"></script>
     5<script src="../resources/gc.js"></script>
    56</head>
    67<body>
    7 <iframe id="testFrame" src="resources/no-document-leak-frame.html"></iframe>
     8<iframe id="testFrame" src=></iframe>
    89<script>
    910description("Tests that using IntersectionObserver does not cause the document to get leaked.");
    1011window.jsTestIsAsync = true;
    1112
    12 function documentShouldDie(documentIdentifier)
    13 {
    14     return new Promise(function(resolve, reject) {
    15         handle = setInterval(function() {
    16             gc();
    17             if (internals && !internals.isDocumentAlive(documentIdentifier) && internals.numberOfIntersectionObservers(document) == 0) {
    18                 clearInterval(handle);
    19                 resolve();
     13let totalCount = 0;
     14window.onload = function () {
     15    testDocumentIsNotLeaked(
     16        async function initAndRemove(frameCount)
     17        {
     18            totalCount = frameCount;
     19            let frames = await new Promise((resolve, reject) => {
     20                let frames = [];
     21                let counter = 0;
     22                function onLoad() {
     23                    counter++;
     24                    if (counter == frameCount)
     25                        resolve(frames);
     26                }
     27                for (let i = 0; i < frameCount; ++i) {
     28                    let frame = document.createElement('iframe');
     29                    frame.src = "resources/no-document-leak-frame.html";
     30                    frame.onload = onLoad;
     31                    document.body.appendChild(frame);
     32                    frames.push(frame);
     33                }
     34            });
     35            totalCount = internals.numberOfIntersectionObservers(document);
     36            let frameIdentifiers = [];
     37            for (let i = 0; i < frameCount; ++i) {
     38                let frame = frames[i];
     39                frameIdentifiers.push(internals.documentIdentifier(frame.contentDocument));
     40                frame.remove();
    2041            }
    21         }, 10);
    22     });
    23 }
     42            nukeArray(frames);
     43            frames = null;
     44            return frameIdentifiers;
     45        },
     46        {
     47            additionalCheck: function()
     48            {
     49                let count = internals.numberOfIntersectionObservers(document);
     50                return count < totalCount || count === 0;
     51            }
     52        }
     53    ).then(
     54        () => testPassed("Document did not leak"),
     55        (error) => testFailed(error.message)
     56    ).finally(finishJSTest);
     57};
    2458
    25 var testFrame = document.getElementById("testFrame");
    26 testFrame.onload = function() {
    27     let frameDocumentIdentifier = internals.documentIdentifier(testFrame.contentDocument);
    28     testFrame.remove();
    29     setTimeout(function() {
    30         documentShouldDie(frameDocumentIdentifier).then(function() {
    31             testPassed("Document did not leak");
    32             finishJSTest();
    33         });
    34     });
    35 };
    3659</script>
    3760<script src="../resources/js-test-post.js"></script>
  • trunk/LayoutTests/performance-api/performance-observer-no-document-leak.html

    r233490 r261391  
    33<head>
    44<script src="../resources/js-test-pre.js"></script>
     5<script src="../resources/gc.js"></script>
    56</head>
    67<body>
    7 <iframe id="testFrame" src="resources/performance-observer-no-document-leak-frame.html"></iframe>
    88<script>
    99description("Tests that using PerformanceObserver does not cause the document to get leaked.");
    1010window.jsTestIsAsync = true;
    1111
    12 function documentShouldDie(documentIdentifier)
    13 {
    14     return new Promise(function(resolve, reject) {
    15         handle = setInterval(function() {
    16             gc();
    17             if (!internals.isDocumentAlive(documentIdentifier)) {
    18                 clearInterval(handle);
    19                 resolve();
     12window.onload = function () {
     13    testDocumentIsNotLeaked(
     14        async function initAndRemove(frameCount)
     15        {
     16            let frames = await new Promise((resolve, reject) => {
     17                let frames = [];
     18                let counter = 0;
     19                function onLoad() {
     20                    counter++;
     21                    if (counter == frameCount)
     22                        resolve(frames);
     23                }
     24                for (let i = 0; i < frameCount; ++i) {
     25                    let frame = document.createElement('iframe');
     26                    frame.src = "resources/performance-observer-no-document-leak-frame.html";
     27                    frame.onload = onLoad;
     28                    document.body.appendChild(frame);
     29                    frames.push(frame);
     30                }
     31            });
     32            let frameIdentifiers = [];
     33            for (let i = 0; i < frameCount; ++i) {
     34                let frame = frames[i];
     35                frameIdentifiers.push(internals.documentIdentifier(frame.contentDocument));
     36                frame.remove();
    2037            }
    21         }, 10);
    22     });
    23 }
     38            nukeArray(frames);
     39            frames = null;
     40            return frameIdentifiers;
     41        }
     42    ).then(
     43        () => testPassed("Document did not leak"),
     44        (error) => testFailed(error.message)
     45    ).finally(finishJSTest);
     46};
    2447
    25 onload = function() {
    26     setTimeout(function() {
    27         let frameDocumentIdentifier = internals.documentIdentifier(testFrame.contentDocument);
    28         testFrame.remove();
    29         documentShouldDie(frameDocumentIdentifier).then(function() {
    30             testPassed("Document did not leak");
    31             finishJSTest();
    32         });
    33     }, 10);
    34 }
    3548</script>
    3649<script src="../resources/js-test-post.js"></script>
  • trunk/LayoutTests/resources/gc.js

    r185586 r261391  
    2121    }
    2222}
     23
     24// Fill array with null to avoid potential GC reachability. frames = null can be enough in 99.9% cases,
     25// but we are extra careful to make tests non-flaky: considering about the case that array is captured
     26// somewhere (like DFG constants).
     27function nukeArray(array)
     28{
     29    for (let index = 0; index < array.length; ++index)
     30        array[index] = null;
     31    array.length = 0;
     32}
     33
     34async function testDocumentIsNotLeaked(init, options = {})
     35{
     36    let gcCount = options.gcCount ?? 50;
     37    let documentCount = options.documentCount ?? 20;
     38    let additionalCheck = options.additionalCheck ?? function () { return true; };
     39
     40    function waitFor(duration)
     41    {
     42        return new Promise((resolve) => setTimeout(resolve, duration));
     43    }
     44
     45    let frameIdentifiers = await init(documentCount);
     46    for (let counter = 0; counter < gcCount; ++counter) {
     47        for (let i = 0; i < documentCount; ++i) {
     48            let frameIdentifier = frameIdentifiers[i];
     49            if (!internals.isDocumentAlive(frameIdentifier) && additionalCheck())
     50                return "Document did not leak";
     51        }
     52        gc();
     53        await waitFor(50);
     54    }
     55
     56    throw new Error("Document is leaked");
     57}
  • trunk/LayoutTests/webanimations/leak-document-with-web-animation.html

    r251742 r261391  
    33<body onload="runTest()">
    44<script src="../resources/js-test.js"></script>
     5<script src="../resources/gc.js"></script>
    56<script>
    67description("This test asserts that Document doesn't leak when a Web Animation is created.");
    78
    8 if (window.internals)
     9if (window.internals) {
    910    jsTestIsAsync = true;
    1011
    11 function runTest() {
    12     if (!window.internals)
    13         return;
    14 
    15     var frame = document.body.appendChild(document.createElement("iframe"));
    16 
    17     frame.onload = function() {
    18         if (frame.src === 'about:blank')
    19             return true;
    20 
    21         documentIdentifier = internals.documentIdentifier(frame.contentDocument);
    22         debug("The iframe has finished loading.");
    23 
    24         frame.remove();
    25         frame = null;
    26 
    27         gc();
    28         timeout = 0;
    29         handle = setInterval(() => {
    30             if (!internals.isDocumentAlive(documentIdentifier)) {
    31                 clearInterval(handle);
    32                 testPassed("The document was destroyed");
    33                 finishJSTest();
    34                 return;
     12    window.onload = function () {
     13        testDocumentIsNotLeaked(
     14            async function initAndRemove(frameCount)
     15            {
     16                let frames = await new Promise((resolve, reject) => {
     17                    let frames = [];
     18                    let counter = 0;
     19                    function onLoad() {
     20                        counter++;
     21                        if (counter == frameCount)
     22                            resolve(frames);
     23                    }
     24                    for (let i = 0; i < frameCount; ++i) {
     25                        let frame = document.createElement('iframe');
     26                        frame.src = "resources/web-animation-leak-iframe.html";
     27                        frame.onload = onLoad;
     28                        document.body.appendChild(frame);
     29                        frames.push(frame);
     30                    }
     31                });
     32                debug("The iframe has finished loading.");
     33                let frameIdentifiers = [];
     34                for (let i = 0; i < frameCount; ++i) {
     35                    let frame = frames[i];
     36                    frameIdentifiers.push(internals.documentIdentifier(frame.contentDocument));
     37                    frame.remove();
     38                }
     39                nukeArray(frames);
     40                frames = null;
     41                return frameIdentifiers;
    3542            }
    36             timeout++;
    37             if (timeout == 500) {
    38                 clearInterval(handle);
    39                 testFailed("The document was leaked");
    40                 finishJSTest();
    41                 return;
    42             }
    43             gc();
    44         }, 10);
    45     }
    46 
    47     frame.src = 'resources/web-animation-leak-iframe.html';
     43        ).then(
     44            () => testPassed("The document was destroyed"),
     45            (error) => testFailed(error.message)
     46        ).finally(finishJSTest);
     47    };
    4848}
    4949
Note: See TracChangeset for help on using the changeset viewer.