file_animations_omta_scroll.html (12951B)
1 <!DOCTYPE HTML> 2 <html> 3 <head> 4 <meta charset="utf-8"> 5 <meta name="viewport" content="width=device-width,initial-scale=1"> 6 <title>Test for css-animations running on the compositor thread with scroll-timeline</title> 7 <script src="/tests/SimpleTest/SimpleTest.js"></script> 8 <script src="/tests/SimpleTest/paint_listener.js"></script> 9 <script src="/tests/gfx/layers/apz/test/mochitest/apz_test_utils.js"></script> 10 <script type="application/javascript" src="animation_utils.js"></script> 11 <style type="text/css"> 12 @keyframes transform_anim { 13 from { transform: translate(50px); } 14 to { transform: translate(150px); } 15 } 16 17 @keyframes always_fifty { 18 from, to { transform: translate(50px); } 19 } 20 21 @keyframes geometry { 22 from { width: 50px; } 23 to { width: 100px; } 24 } 25 26 .target { 27 /* The animation target needs geometry in order to qualify for OMTA */ 28 width: 100px; 29 height: 100px; 30 background-color: green; 31 } 32 33 .scroller { 34 width: 100px; 35 height: 100px; 36 overflow: scroll; 37 scroll-timeline-name: --scroll_timeline; 38 } 39 40 .content { 41 block-size: 100%; 42 padding-block-end: 100px; 43 } 44 </style> 45 </head> 46 <body> 47 <div id="display"></div> 48 <pre id="test"></pre> 49 </body> 50 <script type="application/javascript"> 51 "use strict"; 52 53 // Global state 54 var gScroller = null; 55 var gDiv = null; 56 57 // Shortcut omta_is and friends by filling in the initial 'elem' argument 58 // with gDiv. 59 [ 'omta_is', 'omta_todo_is', 'omta_is_approx' ].forEach(function(fn) { 60 var origFn = window[fn]; 61 window[fn] = function() { 62 var args = Array.from(arguments); 63 if (!(args[0] instanceof Element)) { 64 args.unshift(gDiv); 65 } 66 return origFn.apply(window, args); 67 }; 68 }); 69 70 // Shortcut new_div and done_div to update gDiv 71 var originalNewDiv = window.new_div; 72 window.new_div = function(style) { 73 [ gDiv ] = originalNewDiv(style); 74 }; 75 var originalDoneDiv = window.done_div; 76 window.done_div = function() { 77 originalDoneDiv(); 78 gDiv = null; 79 }; 80 81 // Bind the ok and todo to the opener, and close this window when we finish. 82 var ok = opener.ok.bind(opener); 83 var todo = opener.todo.bind(opener); 84 function finish() { 85 var o = opener; 86 self.close(); 87 o.SimpleTest.finish(); 88 } 89 90 function new_scroller() { 91 gScroller = document.createElement('div'); 92 gScroller.className = `scroller`; 93 94 let content = document.createElement('div'); 95 content.className = 'content'; 96 97 gScroller.appendChild(content); 98 document.getElementById("display").appendChild(gScroller); 99 return gScroller; 100 } 101 102 function done_scroller() { 103 gScroller.firstChild.remove(); 104 gScroller.remove(); 105 gScroller = null; 106 } 107 108 waitUntilApzStable().then(() => { 109 runOMTATest(function() { 110 var onAbort = function() { 111 if (gDiv) { 112 done_div(); 113 } 114 if (gScroller) { 115 done_scroller(); 116 } 117 }; 118 runAllAsyncAnimTests(onAbort).then(finish); 119 }, finish); 120 }); 121 122 //---------------------------------------------------------------------- 123 // 124 // Test cases 125 // 126 //---------------------------------------------------------------------- 127 128 // The non-omta property with scroll-timeline together with an omta property 129 // with document-timeline. 130 addAsyncAnimTest(async function() { 131 new_scroller(); 132 new_div("animation: geometry 10s, always_fifty 1s infinite; " + 133 "animation-timeline: --scroll_timeline, auto"); 134 await waitForPaintsFlushed(); 135 136 // Note: width is not a OMTA property, so it must be running on the main 137 // thread. 138 omta_is("transform", { tx: 50 }, RunningOn.Compositor, 139 "transform animations should runs on compositor thread"); 140 141 done_div(); 142 done_scroller(); 143 }); 144 145 // transform property with scroll-driven animations. 146 addAsyncAnimTest(async function() { 147 let scroller = new_scroller(); 148 new_div("animation: transform_anim 1s linear; " + 149 "animation-timeline: --scroll_timeline;"); 150 await waitForPaintsFlushed(); 151 152 scroller.scrollTop = 50; 153 await waitForPaintsFlushed(); 154 155 omta_is_approx("transform", { tx: 100 }, 0.1, RunningOn.Compositor, 156 "scroll transform animations should runs on compositor " + 157 "thread"); 158 159 done_div(); 160 done_scroller(); 161 }); 162 163 164 // The scroll-driven animation with an underlying value and make it go from the 165 // active phase to the before phase. 166 addAsyncAnimTest(async function() { 167 let scroller = new_scroller(); 168 new_div("animation: always_fifty 5s linear 5s; " + 169 "animation-timeline: --scroll_timeline; " + 170 "transform: translate(25px);"); 171 await waitForPaintsFlushed(); 172 173 // NOTE: getOMTAStyle() can't detect the animation is running on the 174 // compositor during the delay phase. 175 omta_is_approx("transform", { tx: 25 }, 0.1, RunningOn.Either, 176 "The scroll animation is in delay"); 177 178 scroller.scrollTop = 75; 179 await waitForPaintsFlushed(); 180 181 omta_is_approx("transform", { tx: 50 }, 0.1, RunningOn.Compositor, 182 "scroll transform animations should runs on compositor " + 183 "thread"); 184 185 // Use setAsyncScrollOffset() to update apz (compositor thread only) to make 186 // sure Bug 1776077 is reproducible. 187 let utils = SpecialPowers.wrap(window).windowUtils; 188 utils.setAsyncScrollOffset(scroller, 0, -50); 189 190 // NOTE: setAsyncScrollOffset() doesn't update main thread, so we check the 191 // OMTA style directly. 192 let compositorStr = 193 SpecialPowers.DOMWindowUtils.getOMTAStyle(gDiv, "transform"); 194 ok(compositorStr === "matrix(1, 0, 0, 1, 25, 0)", 195 "scroll animations is in delay phase before advancing time to next tick"); 196 197 utils.advanceTimeAndRefresh(16); 198 utils.restoreNormalRefresh(); 199 await waitForPaints(); 200 201 // We update the OMTA in the following tick, so the OMTA style gets cleared. 202 compositorStr = SpecialPowers.DOMWindowUtils.getOMTAStyle(gDiv, "transform"); 203 ok(compositorStr === "", 204 "scroll animation in delay phase clears its OMTA style"); 205 206 scroller.scrollTop = 25; 207 await waitForPaintsFlushed(); 208 209 compositorStr = SpecialPowers.DOMWindowUtils.getOMTAStyle(gDiv, "transform"); 210 ok(compositorStr === "", 211 "scroll animation in delay phase clears its OMTA style"); 212 omta_is_approx("transform", { tx: 25 }, 0.1, RunningOn.Either, 213 "The scroll animation is in delay"); 214 215 done_div(); 216 done_scroller(); 217 }); 218 219 // The scroll-driven animation without an underlying value and make it go from 220 // the active phase to the before phase. 221 addAsyncAnimTest(async function() { 222 let scroller = new_scroller(); 223 new_div("animation: always_fifty 5s linear 5s; " + 224 "animation-timeline: --scroll_timeline;"); 225 await waitForPaintsFlushed(); 226 227 // NOTE: getOMTAStyle() can't detect the animation is running on the 228 // compositor during the delay phase. 229 omta_is_approx("transform", { tx: 0 }, 0.1, RunningOn.Either, 230 "The scroll animation is in delay"); 231 232 scroller.scrollTop = 75; 233 await waitForPaintsFlushed(); 234 235 omta_is_approx("transform", { tx: 50 }, 0.1, RunningOn.Compositor, 236 "scroll transform animations should runs on compositor " + 237 "thread"); 238 239 // Use setAsyncScrollOffset() to update apz (compositor thread only) to make 240 // sure Bug 1776077 is reproducible. 241 let utils = SpecialPowers.wrap(window).windowUtils; 242 utils.setAsyncScrollOffset(scroller, 0, -50); 243 244 // NOTE: setAsyncScrollOffset() doesn't update main thread, so we check the 245 // OMTA style directly. 246 let compositorStr = 247 SpecialPowers.DOMWindowUtils.getOMTAStyle(gDiv, "transform"); 248 ok(compositorStr === "matrix(1, 0, 0, 1, 0, 0)", 249 "scroll animations is in delay phase before advancing time to next tick"); 250 251 utils.advanceTimeAndRefresh(16); 252 utils.restoreNormalRefresh(); 253 await waitForPaints(); 254 255 // We update the OMTA in the following tick, so the OMTA style gets cleared. 256 compositorStr = SpecialPowers.DOMWindowUtils.getOMTAStyle(gDiv, "transform"); 257 ok(compositorStr === "", 258 "scroll animation in delay phase clears its OMTA style"); 259 260 done_div(); 261 done_scroller(); 262 }); 263 264 // The scroll-driven animation is in delay, together with other runing 265 // animations. 266 addAsyncAnimTest(async function() { 267 let scroller = new_scroller(); 268 new_div("animation: transform_anim 10s linear -5s paused, " + 269 " always_fifty 5s linear 5s; " + 270 "animation-timeline: auto, --scroll_timeline;"); 271 await waitForPaintsFlushed(); 272 273 omta_is_approx("transform", { tx: 100 }, 0.1, RunningOn.Compositor, 274 "The scroll animation is in delay"); 275 276 scroller.scrollTop = 75; 277 await waitForPaintsFlushed(); 278 279 omta_is_approx("transform", { tx: 50 }, 0.1, RunningOn.Compositor, 280 "scroll transform animations should runs on compositor " + 281 "thread"); 282 283 let utils = SpecialPowers.wrap(window).windowUtils; 284 utils.setAsyncScrollOffset(scroller, 0, -50); 285 utils.advanceTimeAndRefresh(16); 286 utils.restoreNormalRefresh(); 287 await waitForPaints(); 288 289 // NOTE: setAsyncScrollOffset() doesn't update main thread, so we check the 290 // OMTA style directly. 291 let compositorStr = 292 SpecialPowers.DOMWindowUtils.getOMTAStyle(gDiv, "transform"); 293 ok(compositorStr === "matrix(1, 0, 0, 1, 100, 0)", 294 "scroll animations is in delay phase before calling main thread style " + 295 "udpate"); 296 297 done_div(); 298 done_scroller(); 299 }); 300 301 addAsyncAnimTest(async function() { 302 let iframe = document.createElement("iframe"); 303 iframe.setAttribute("style", "width: 200px; height: 200px"); 304 iframe.setAttribute("scrolling", "no"); 305 iframe.srcdoc = 306 "<!DOCTYPE HTML>" + 307 "<html style='min-height: 100%; padding-bottom: 100px;'>" + 308 "<style>" + 309 "@keyframes anim { from, to { transform: translate(50px) } }" + 310 "</style>" + 311 "<div id='target_in_iframe' " + 312 " style='width:50px; height:50px; background:green; " + 313 " animation: anim 10s linear; " + 314 " animation-timeline: scroll(root);'>" + 315 "</div>" + 316 "</html>"; 317 318 await new Promise(resolve => { 319 iframe.onload = resolve; 320 document.body.appendChild(iframe); 321 }); 322 323 gDiv = iframe.contentDocument.getElementById("target_in_iframe"); 324 325 const root = iframe.contentDocument.scrollingElement; 326 const maxScroll = root.scrollHeight - root.clientHeight; 327 root.scrollTop = 0.5 * maxScroll; 328 await waitForPaintsFlushed(); 329 330 omta_is_approx("transform", { tx: 50 }, 0, RunningOn.MainThread, 331 "scroll transform animations inside an iframe with " + 332 "scrolling:no should run on the main thread"); 333 334 gDiv = null; 335 iframe.remove(); 336 }); 337 338 // FIXME: Bug 1818346. Support OMTA for view-timeline. 339 addAsyncAnimTest(async function() { 340 let scroller = document.createElement("div"); 341 scroller.style.width = "100px"; 342 scroller.style.height = "100px"; 343 // Use hidden so we don't have scrollbar, to make sure the scrollport size 344 // is 100px x 100px, so view progress visibility range is 100px on block axis. 345 scroller.style.overflow = "hidden"; 346 347 let content1 = document.createElement("div"); 348 content1.style.height = "150px"; 349 scroller.appendChild(content1); 350 351 let subject = document.createElement("div"); 352 subject.style.width = "50px"; 353 subject.style.height = "50px"; 354 subject.style.viewTimelineName = "--view_timeline"; 355 scroller.appendChild(subject); 356 357 // Let |target| be the child of |subject|, so view-timeline-name property of 358 // |subject| is referenceable. 359 let target = document.createElement("div"); 360 target.style.width = "10px"; 361 target.style.height = "10px"; 362 subject.appendChild(target); 363 gDiv = target; 364 365 let content2 = document.createElement("div"); 366 content2.style.height = "150px"; 367 scroller.appendChild(content2); 368 369 // So the DOM tree looks like this: 370 // <div class=scroller> <!-- "scroller", height: 100px; --> 371 // <div></div> <!-- "", height: 150px --> 372 // <div></div> <!-- "subject", height: 50px; --> 373 // <div></div> <!-- "", height: 150px; --> 374 // </div> 375 // The subject is in view when scroller.scrollTop is [50px, 200px]. 376 document.getElementById("display").appendChild(scroller); 377 await waitForPaintsFlushed(); 378 379 scroller.scrollTop = 0; 380 target.style.animation = "transform_anim 10s linear"; 381 target.style.animationTimeline = "--view_timeline"; 382 await waitForPaintsFlushed(); 383 384 omta_is_approx("transform", { tx: 0 }, 0.1, RunningOn.OnMainThread, 385 "The scroll animation is out of view"); 386 387 scroller.scrollTop = 50; 388 await waitForPaintsFlushed(); 389 390 omta_is_approx("transform", { tx: 50 }, 0.1, RunningOn.OnMainThread, 391 "The scroll animation is 0%"); 392 393 scroller.scrollTop = 125; 394 await waitForPaintsFlushed(); 395 396 omta_is_approx("transform", { tx: 100 }, 0.1, RunningOn.OnMainThread, 397 "The scroll animation is 50%"); 398 399 target.remove(); 400 content2.remove(); 401 subject.remove(); 402 content1.remove(); 403 scroller.remove(); 404 gDiv = null; 405 }); 406 407 </script> 408 </html>