animation-display-lock.html (6859B)
1 <!DOCTYPE html> 2 <meta charset=utf8> 3 <title>Test getComputedStyle on a CSS animation in a display locked subtree</title> 4 <link rel="help" href="https://drafts.csswg.org/css-contain-2/"> 5 <script src="/web-animations/testcommon.js"></script> 6 <script src="/resources/testharness.js"></script> 7 <script src="/resources/testharnessreport.js"></script> 8 <style> 9 #container { 10 content-visibility: visible; 11 contain: style layout paint; 12 contain-intrinsic-size: 0 100px; 13 } 14 @keyframes fade { 15 from { opacity: 1; } 16 to { opacity: 0; } 17 } 18 #target { 19 background: 'green'; 20 height: 100px; 21 width: 100px; 22 } 23 .animate { 24 animation: fade 10s linear 2 alternate; 25 } 26 .transition { 27 transition: opacity 10s linear; 28 } 29 </style> 30 <body> 31 <div id="container"></div> 32 </body> 33 <script> 34 "use strict"; 35 36 function reset() { 37 const container = document.getElementById('container'); 38 const target = document.getElementById('target'); 39 container.style = ''; 40 container.removeChild(target); 41 } 42 43 function createAnimatingElement(test, name) { 44 const container = document.getElementById('container'); 45 const target = document.createElement('div'); 46 container.appendChild(target); 47 target.id = 'target'; 48 target.className = name; 49 test.add_cleanup(() => { 50 reset(); 51 }); 52 return target; 53 } 54 55 function waitForEvent(element, eventName) { 56 return new Promise(resolve => element.addEventListener(eventName, resolve, { once: true })); 57 } 58 59 promise_test(async t => { 60 const container = document.getElementById('container'); 61 const target = createAnimatingElement(t, 'animate'); 62 let animationIterationEvent = false; 63 target.addEventListener('animationiteration', () => { 64 animationIterationEvent = true; 65 }); 66 const animation = target.getAnimations()[0]; 67 await animation.ready; 68 await waitForAnimationFrames(1); 69 container.style.contentVisibility = 'hidden'; 70 animation.currentTime = 15000; 71 assert_approx_equals( 72 parseFloat(getComputedStyle(target).opacity), 0.5, 1e-6, 73 'Computed style is updated even when the animation is running in a ' + 74 'display locked subtree'); 75 await waitForAnimationFrames(2); 76 assert_false(animationIterationEvent, 77 'Animation events do not fire while the animation is ' + 78 'running in a display locked subtree'); 79 container.style.contentVisibility = 'visible'; 80 await waitForEvent(target, 'animationiteration'); 81 assert_true(animationIterationEvent, 82 'The animationiteration event fires once the animation is ' + 83 'no longer display locked'); 84 }, 'Animation events do not fire for a CSS animation running in a display ' + 85 'locked subtree'); 86 87 promise_test(async t => { 88 const container = document.getElementById('container'); 89 const target = createAnimatingElement(t, 'animate'); 90 const animation = target.getAnimations()[0]; 91 await animation.ready; 92 let finishedWhileDisplayLocked = false; 93 animation.finished.then(() => { 94 finishedWhileDisplayLocked = 95 getComputedStyle(container).contentVisibility == 'hidden'; 96 }); 97 await waitForAnimationFrames(1); 98 container.style.contentVisibility = 'hidden'; 99 // Advance to just shy of the effect end. 100 animation.currentTime = 19999; 101 assert_approx_equals( 102 parseFloat(getComputedStyle(target).opacity), 0.9999, 1e-6, 103 'Computed style is updated even when the animation is ' + 104 'running in a display locked subtree'); 105 // Advancing frames should not resolve the finished promise. 106 await waitForAnimationFrames(3); 107 container.style.contentVisibility = 'visible'; 108 // Now we can resolve the finished promise. 109 await animation.finished; 110 assert_equals(finishedWhileDisplayLocked, false); 111 }, 'The finished promise does not resolve due to the normal passage of time ' + 112 'for a CSS animation in a display locked subtree'); 113 114 promise_test(async t => { 115 const container = document.getElementById('container'); 116 await waitForAnimationFrames(1); 117 const target = createAnimatingElement(t, 'transition'); 118 await waitForAnimationFrames(1); 119 target.style.opacity = 0; 120 const animation = target.getAnimations()[0]; 121 await animation.ready; 122 let finishedWhileDisplayLocked = false; 123 animation.finished.then(() => { 124 finishedWhileDisplayLocked = 125 getComputedStyle(container).contentVisibility == 'hidden'; 126 }); 127 await waitForAnimationFrames(1); 128 container.style.contentVisibility = 'hidden'; 129 // Advance to just shy of the effect end. 130 animation.currentTime = 9999; 131 assert_approx_equals( 132 parseFloat(getComputedStyle(target).opacity), 0.0001, 1e-6, 133 'Computed style is updated even when the animation is ' + 134 'running in a display locked subtree'); 135 // Advancing frames should not resolve the finished promise. 136 await waitForAnimationFrames(3); 137 container.style.contentVisibility = 'visible'; 138 // Now we can resolve the finished promise. 139 await animation.finished; 140 assert_equals(finishedWhileDisplayLocked, false); 141 }, 'The finished promise does not resolve due to the normal passage of time ' + 142 'for a CSS transition in a display locked subtree'); 143 144 promise_test(async t => { 145 const container = document.getElementById('container'); 146 const target = createAnimatingElement(t, 'animate'); 147 const animation = target.getAnimations()[0]; 148 target.className = ''; 149 container.style.contentVisibility = 'hidden'; 150 assert_equals(target.getAnimations().length, 0); 151 152 // Though originally a CSS animation, it is no longer associated with 153 // CSS rules and no longer has an owning element. It now behaves like a 154 // programmatic web animation. Animation playback events (but not CSS 155 // animation events) should be dispatched and promises resolved despite 156 // being in a display locked subtree. 157 158 let cssAnimationEndEvent = false; 159 target.addEventListener('animationend', () => { 160 cssAnimationEndEvent = true; 161 }); 162 163 let animationFinishEvent = false; 164 animation.addEventListener('finish', () => { 165 animationFinishEvent = true; 166 }); 167 168 let animationFinished = false; 169 animation.finished.then(() => { 170 animationFinished = true; 171 }); 172 173 animation.play(); 174 assert_equals(target.getAnimations().length, 1); 175 176 animation.currentTime = 19999; 177 await animation.ready; 178 await waitForAnimationFrames(2); 179 180 assert_true(animationFinishEvent, 181 'Animation event not blocked on display locked subtree if ' + 182 'no owning element'); 183 assert_true(animationFinished, 184 'Finished promise not blocked on display locked subtrtee if ' + 185 'no owning element'); 186 assert_false(cssAnimationEndEvent, 187 'CSS animation events should not be dispatched if there is no ' + 188 'owning element'); 189 }, 'Events and promises are handled normally for animations without an ' + 190 'owning element'); 191 192 </script>