inactive-timeline.https.html (5164B)
1 <!DOCTYPE html> 2 <meta charset=utf-8> 3 <title>Correctness of worklet animation state when timeline becomes newly 4 active or inactive.</title> 5 <link rel="help" href="https://drafts.css-houdini.org/css-animationworklet/"> 6 <script src="/resources/testharness.js"></script> 7 <script src="/resources/testharnessreport.js"></script> 8 <script src="/web-animations/testcommon.js"></script> 9 <script src="common.js"></script> 10 <style> 11 .scroller { 12 overflow: auto; 13 height: 100px; 14 width: 100px; 15 } 16 .contents { 17 height: 1000px; 18 width: 100%; 19 } 20 </style> 21 <body> 22 <div id="log"></div> 23 <script> 24 'use strict'; 25 26 function createScroller(test) { 27 var scroller = createDiv(test); 28 scroller.innerHTML = "<div class='contents'></div>"; 29 scroller.classList.add('scroller'); 30 return scroller; 31 } 32 33 function createScrollLinkedWorkletAnimation(test) { 34 const timeline = new ScrollTimeline({ 35 scrollSource: createScroller(test), 36 }); 37 const DURATION = 1000; // ms 38 const KEYFRAMES = { transform: ['translateY(100px)', 'translateY(200px)'] }; 39 return new WorkletAnimation('passthrough', new KeyframeEffect(createDiv(test), 40 KEYFRAMES, DURATION), timeline); 41 } 42 43 setup(setupAndRegisterTests, {explicit_done: true}); 44 45 function setupAndRegisterTests() { 46 registerPassthroughAnimator().then(() => { 47 48 promise_test(async t => { 49 const animation = createScrollLinkedWorkletAnimation(t); 50 const scroller = animation.timeline.scrollSource; 51 const target = animation.effect.target; 52 53 // There is no direct way to control when local times of composited 54 // animations are synced to the main thread. This test uses another 55 // composited worklet animation with an always active timeline as an 56 // indicator of when the sync is ready. The sync is done when animation 57 // effect's output has changed as a result of advancing the timeline. 58 const animationRef = createScrollLinkedWorkletAnimation(t); 59 const scrollerRef = animationRef.timeline.scrollSource; 60 const targetRef = animationRef.effect.target; 61 62 const maxScroll = scroller.scrollHeight - scroller.clientHeight; 63 scroller.scrollTop = 0.2 * maxScroll; 64 65 // Make the timeline inactive. 66 scroller.style.display = "none" 67 // Force relayout. 68 scroller.scrollTop; 69 70 animation.play(); 71 animationRef.play(); 72 assert_equals(animation.currentTime, null, 73 'Initial current time must be unresolved in idle state.'); 74 assert_equals(animation.startTime, null, 75 'Initial start time must be unresolved in idle state.'); 76 waitForAnimationFrameWithCondition(_=> { 77 return animation.playState == "running" 78 }); 79 assert_equals(animation.currentTime, null, 80 'Initial current time must be unresolved in playing state.'); 81 assert_equals(animation.startTime, null, 82 'Initial start time must be unresolved in playing state.'); 83 84 scrollerRef.scrollTop = 0.2 * maxScroll; 85 86 // Wait until local times are synced back to the main thread. 87 await waitForAnimationFrameWithCondition(_ => { 88 return animationRef.effect.getComputedTiming().localTime == 200; 89 }); 90 91 assert_equals(animation.effect.getComputedTiming().localTime, null, 92 'The underlying effect local time must be undefined while the ' + 93 'timeline is inactive.'); 94 95 // Make the timeline active. 96 scroller.style.display = ""; 97 // Wait for new animation frame which allows the timeline to compute new 98 // current time. 99 await waitForNextFrame(); 100 101 assert_times_equal(animation.currentTime, 200, 102 'Current time must be initialized.'); 103 assert_times_equal(animation.startTime, 0, 104 'Start time must be initialized.'); 105 106 scrollerRef.scrollTop = 0.4 * maxScroll; 107 // Wait until local times are synced back to the main thread. 108 await waitForAnimationFrameWithCondition(_ => { 109 return animationRef.effect.getComputedTiming().localTime == 400; 110 }); 111 assert_times_equal(animation.effect.getComputedTiming().localTime, 200, 112 'When the timeline becomes newly active, the underlying effect\'s ' + 113 'timing should be properly updated.'); 114 115 // Make the timeline inactive again. 116 scroller.style.display = "none" 117 await waitForNextFrame(); 118 119 assert_times_equal(animation.currentTime, 200, 120 'Current time must be the previous current time.'); 121 assert_equals(animation.startTime, null, 122 'Initial start time must be unresolved.'); 123 124 scrollerRef.scrollTop = 0.6 * maxScroll; 125 // Wait until local times are synced back to the main thread. 126 await waitForAnimationFrameWithCondition(_ => { 127 return animationRef.effect.getComputedTiming().localTime == 600; 128 }); 129 130 assert_times_equal(animation.effect.getComputedTiming().localTime, 200, 131 'When the timeline becomes newly inactive, the underlying effect\'s ' + 132 'timing should stay unchanged.'); 133 }, 'When timeline time becomes inactive previous current time must be ' + 134 'the current time and start time unresolved'); 135 done(); 136 }); 137 } 138 </script> 139 </body>