OLD | NEW |
1 All of the IntersectionObserver tests feature the following idiom: | 1 All of the IntersectionObserver tests feature the following idiom: |
2 | 2 |
3 <script> | 3 <script> |
4 var observer = new IntersectionObserver(...) | 4 var observer = new IntersectionObserver(...) |
5 function test_function() { | 5 function test_function() { |
6 var entries = observer.takeRecords(); | 6 var entries = observer.takeRecords(); |
7 // Verify entries | 7 // Verify entries |
8 } | 8 } |
9 onload = function() { | 9 onload = function() { |
10 observer.observe(target); | 10 observer.observe(target); |
11 requestAnimationFrame(() => { requestAnimationFrame(test_function) }); | 11 requestAnimationFrame(() => { |
| 12 setTimeout(() => { |
| 13 setTimeout(test_function); |
| 14 }); |
| 15 }); |
12 } | 16 } |
13 | 17 |
14 Subsequent steps in the test use a single RAF to give the observer a chance to | |
15 generate notifications, but the double RAF is required in the onload handler. | |
16 Here's the chain of events: | 18 Here's the chain of events: |
17 | 19 |
18 - onload | 20 - onload |
19 - observer.observe() | 21 - observer.observe() |
20 - First RAF handler is registered | 22 - RAF handler is registered |
21 - BeginFrame | 23 - BeginFrame |
22 - RAF handlers run | 24 - RAF handler runs |
23 - Second RAF handler is registered | 25 - First setTimeout is registered |
24 - UpdateAllLifecyclePhases | 26 - UpdateAllLifecyclePhases |
25 - IntersectionObserver generates notifications | 27 - IntersectionObserver generates notifications |
| 28 - First setTimeout runs |
| 29 - Second setTimeout is regsitered |
| 30 - Notifications that haven't be taken via takeRecords are delivered |
| 31 - Second setTimeout runs and queues another RAF handler |
26 - BeginFrame | 32 - BeginFrame |
27 - RAF handlers run | 33 - RAF handler runs |
28 - Second RAF handler verifies observer notifications. | 34 - RAF handler verifies observer notifications via takeRecords or |
29 | 35 does the setTimeout dance to respond after they've been delivered. |
30 #------------------------------------------------------------------------------- | |
31 | |
32 All of the IntersectionObserver tests are currently listed in LeakExpecations. | |
33 | |
34 To avoid leaking DOM objects, the tests must ensure that all posted tasks have | |
35 run before exiting. IntersectionObserverController requests an idle callback | |
36 through the document's ScriptedIdleTaskController with a timeout of 100ms. That | |
37 callback must be allowed to run before the test finishes to avoid a leak. | |
38 | |
39 ScriptedIdleTaskController posts two tasks to the WebScheduler: one to run at | |
40 the next idle time, and another to run when the timeout expires. | |
41 crbug.com/595155 explains that when one of those tasks runs, it does not release | |
42 its reference to the ScriptedIdleTaskController, which is needlessly kept alive | |
43 until the both tasks have fired. If a test exits before both tasks have fired, | |
44 it will have a DOM leak. | |
45 | |
46 crbug.com/595152 explains that when running without the threaded compositor -- | |
47 as the layout tests do -- idle tasks are never serviced. They will still run | |
48 when their timeout expires, but the idle task posted by | |
49 ScriptedIdleTaskController will never run. | |
50 | |
51 Fixing the first bug means that requestIdleCallback will not leak as long as it | |
52 actually runs before exit. The second bug means that when running without the | |
53 threaded compositor, requestIdleCallback will only ever run when its timeout | |
54 expires. | |
55 | |
56 The upshot of all of this is that the only way to ensure that | |
57 IntersectionObserver tests don't leak is to ensure that their idle tasks all run | |
58 before the tests exit, and those idle tasks will only run when their 100ms | |
59 timeout expires. | |
60 | |
61 Note that all of this still holds when observer.takeRecords() is called before | |
62 the idle task runs. In that case, the idle task is not cancelled (because the | |
63 idle task needs to service all observers tracked by a given | |
64 IntersectionObserverController); when the idle task runs, it's simply a no-op. | |
65 There is no way in javascript to detect that such a no-op has occurred, but | |
66 using requestIdleCallback with a timeout of 100 should be guaranteed to run | |
67 after the IntersectionObserverController's idle task has run, as long as | |
68 ScriptedIdleTaskController honors first-in-first-out semantics. | |
69 | |
70 At the time of writing, there is a patch out to add testRunner.runIdleTasks(): | |
71 | |
72 https://codereview.chromium.org/1806133002/ | |
73 | |
74 With that patch, the tests can be removed from LeakExpectations as long as they | |
75 end with: | |
76 | |
77 if (window.testRunner) | |
78 testRunner.runIdleTasks(finishJSTest); | |
OLD | NEW |