scroll-timeline-multi-pass.tentative.html (3766B)
1 <!DOCTYPE html> 2 <title>ScrollTimelines may trigger multiple style/layout passes</title> 3 <link rel="help" src="https://github.com/w3c/csswg-drafts/issues/5261"> 4 <link rel="help" src="https://drafts.csswg.org/scroll-animations-1/#avoiding-cycles"> 5 <script src="/resources/testharness.js"></script> 6 <script src="/resources/testharnessreport.js"></script> 7 <script src="/web-animations/testcommon.js"></script> 8 <script src="support/testcommon.js"></script> 9 <style> 10 @keyframes expand_width { 11 from { width: 100px; } 12 to { width: 100px; } 13 } 14 @keyframes expand_height { 15 from { height: 100px; } 16 to { height: 100px; } 17 } 18 main { 19 height: 0px; 20 overflow: hidden; 21 timeline-scope: --timeline1, --timeline2; 22 } 23 .scroller { 24 height: 100px; 25 overflow: scroll; 26 } 27 .scroller > div { 28 height: 200px; 29 } 30 #element1 { 31 width: 1px; 32 animation: expand_width 10s; 33 animation-timeline: --timeline1; 34 } 35 #element2 { 36 height: 1px; 37 animation: expand_height 10s; 38 animation-timeline: --timeline2; 39 } 40 </style> 41 <main id=main> 42 <div id=element1></div> 43 <div> 44 <div id=element2></div> 45 </div> 46 </main> 47 <script> 48 setup(assert_implements_animation_timeline); 49 50 function insertScroller(timeline_name) { 51 let scroller = document.createElement('div'); 52 scroller.classList.add('scroller'); 53 scroller.style.scrollTimeline = timeline_name; 54 scroller.append(document.createElement('div')); 55 main.insertBefore(scroller, element1); 56 } 57 58 promise_test(async () => { 59 await waitForNextFrame(); 60 61 let events1 = []; 62 let events2 = []; 63 64 insertScroller('--timeline1'); 65 // Even though the scroller was just inserted into the DOM, |timeline1| 66 // remains inactive until the next frame. 67 // 68 // https://drafts.csswg.org/scroll-animations-1/#avoiding-cycles 69 assert_equals(getComputedStyle(element1).width, '1px'); 70 (new ResizeObserver(entries => { 71 events1.push(entries); 72 insertScroller('--timeline2'); 73 assert_equals(getComputedStyle(element2).height, '1px'); 74 })).observe(element1); 75 76 (new ResizeObserver(entries => { 77 events2.push(entries); 78 })).observe(element2); 79 80 await waitForNextFrame(); 81 82 // According to the basic rules of the spec [1], the timeline is 83 // inactive at the time the resize observer event was delivered, because 84 // #scroller1 did not have a layout box at the time style recalc for 85 // #element1 happened. 86 // 87 // However, an additional style/layout pass should take place 88 // (before resize observer deliveries) if we detect new ScrollTimelines 89 // in this situation, hence we ultimately do expect the animation to 90 // apply [2]. 91 // 92 // [1] https://drafts.csswg.org/scroll-animations-1/#avoiding-cycles 93 // [2] https://github.com/w3c/csswg-drafts/issues/5261 94 assert_equals(events1.length, 1); 95 assert_equals(events1[0].length, 1); 96 assert_equals(events1[0][0].contentBoxSize.length, 1); 97 assert_equals(events1[0][0].contentBoxSize[0].inlineSize, 100); 98 99 // ScrollTimelines created during the ResizeObserver should remain 100 // inactive during the frame they're created, so the ResizeObserver 101 // event should not reflect the animated value. 102 assert_equals(events2.length, 1); 103 assert_equals(events2[0].length, 1); 104 assert_equals(events2[0][0].contentBoxSize.length, 1); 105 assert_equals(events2[0][0].contentBoxSize[0].blockSize, 1); 106 107 assert_equals(getComputedStyle(element1).width, '100px'); 108 109 await waitForNextFrame(); 110 assert_equals(getComputedStyle(element2).height, '100px'); 111 }, 'Multiple style/layout passes occur when necessary'); 112 </script>