OLD | NEW |
(Empty) | |
| 1 All of the IntersectionObserver tests feature the following idiom: |
| 2 |
| 3 <script> |
| 4 var observer = new IntersectionObserver(...) |
| 5 function test_function() { |
| 6 var entries = observer.takeRecords(); |
| 7 // Verify entries |
| 8 } |
| 9 onload = function() { |
| 10 observer.observe(target); |
| 11 requestAnimationFrame(() => { requestAnimationFrame(test_function) }); |
| 12 } |
| 13 |
| 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: |
| 17 |
| 18 - onload |
| 19 - observer.observe() |
| 20 - First RAF handler is registered |
| 21 - BeginFrame |
| 22 - RAF handlers run |
| 23 - Second RAF handler is registered |
| 24 - UpdateAllLifecyclePhases |
| 25 - IntersectionObserver generates notifications |
| 26 - BeginFrame |
| 27 - RAF handlers run |
| 28 - Second RAF handler verifies observer notifications. |
| 29 |
| 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 |