1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/layout/style/test/test_animations_omta.html Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,954 @@ 1.4 +<!DOCTYPE HTML> 1.5 +<html> 1.6 +<!-- 1.7 +https://bugzilla.mozilla.org/show_bug.cgi?id=964646 1.8 +--> 1.9 +<!-- 1.10 + 1.11 + ========= PLEASE KEEP THIS IN SYNC WITH test_animations.html ========= 1.12 + 1.13 + This test mimicks the content of test_animations.html but performs tests 1.14 + specific to animations that run on the compositor thread since they require 1.15 + special (asynchronous) handling. Furthermore, these tests check that 1.16 + animations that are expected to run on the compositor thread, are actually 1.17 + doing so. 1.18 + 1.19 + If you are making changes to this file or to test_animations.html, please 1.20 + try to keep them consistent where appropriate. 1.21 + 1.22 +--> 1.23 +<head> 1.24 + <meta charset="utf-8"> 1.25 + <title>Test for css3-animations running on the compositor thread (Bug 1.26 + 964646)</title> 1.27 + <script type="application/javascript" 1.28 + src="/tests/SimpleTest/SimpleTest.js"></script> 1.29 + <script type="application/javascript" 1.30 + src="/tests/SimpleTest/paint_listener.js"></script> 1.31 + <script type="application/javascript" src="animation_utils.js"></script> 1.32 + <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/> 1.33 + <style type="text/css"> 1.34 + @keyframes transform-anim { 1.35 + to { 1.36 + transform: translate(100px); 1.37 + } 1.38 + } 1.39 + @keyframes anim1 { 1.40 + 0% { transform: translate(0px) } 1.41 + 50% { transform: translate(80px) } 1.42 + 100% { transform: translate(100px) } 1.43 + } 1.44 + @keyframes anim2 { 1.45 + from { opacity: 0 } to { opacity: 1 } 1.46 + } 1.47 + @keyframes anim3 { 1.48 + from { opacity: 0 } to { opacity: 1 } 1.49 + } 1.50 + 1.51 + @keyframes kf1 { 1.52 + 50% { transform: translate(50px) } 1.53 + to { transform: translate(150px) } 1.54 + } 1.55 + @keyframes kf2 { 1.56 + from { transform: translate(150px) } 1.57 + 50% { transform: translate(50px) } 1.58 + } 1.59 + @keyframes kf3 { 1.60 + 25% { transform: translate(100px) } 1.61 + } 1.62 + @keyframes kf4 { 1.63 + to, from { display: inline; transform: translate(37px) } 1.64 + } 1.65 + @keyframes kf_cascade1 { 1.66 + from { transform: translate(50px) } 1.67 + 50%, from { transform: translate(30px) } /* wins: 0% */ 1.68 + 75%, 85%, 50% { transform: translate(20px) } /* wins: 75%, 50% */ 1.69 + 100%, 85% { transform: translate(70px) } /* wins: 100% */ 1.70 + 85.1% { transform: translate(60px) } /* wins: 85.1% */ 1.71 + 85% { transform: translate(30px) } /* wins: 85% */ 1.72 + } 1.73 + @keyframes kf_cascade2 { from, to { opacity: 0.3 } } 1.74 + @keyframes kf_cascade2 { from, to { transform: translate(50px) } } 1.75 + @keyframes kf_cascade2 { from, to { transform: translate(100px) } } 1.76 + 1.77 + .target { 1.78 + /* The animation target needs geometry in order to qualify for OMTA */ 1.79 + width: 100px; 1.80 + height: 100px; 1.81 + background-color: white; 1.82 + } 1.83 + </style> 1.84 +</head> 1.85 +<body> 1.86 +<a target="_blank" 1.87 + href="https://bugzilla.mozilla.org/show_bug.cgi?id=964646">Mozilla Bug 1.88 + 964646</a> 1.89 +<div id="display"></div> 1.90 +<pre id="test"> 1.91 +<script type="application/javascript"> 1.92 +"use strict"; 1.93 + 1.94 +/** Test for css3-animations running on the compositor thread (Bug 964646) **/ 1.95 + 1.96 +// Global state 1.97 +var gAsyncTests = [], 1.98 + gDisplay = document.getElementById("display"), 1.99 + gDiv = null, 1.100 + gEventsReceived = []; 1.101 + 1.102 +SimpleTest.waitForExplicitFinish(); 1.103 +runOMTATest(function() { 1.104 + // The async test runner returns a Promise that is resolved when the 1.105 + // test is finished so we can chain them together 1.106 + gAsyncTests.reduce(function(sequence, test) { 1.107 + return sequence.then(function() { return runAsyncTest(test); }); 1.108 + }, Promise.resolve() /* the start of the sequence */) 1.109 + // Final step in the sequence 1.110 + .then(function() { 1.111 + SimpleTest.finish(); 1.112 + }); 1.113 +}, SimpleTest.finish); 1.114 + 1.115 +// Takes a generator function that represents a test case. Each point in the 1.116 +// test case that waits asynchronously for some result yields a Promise that is 1.117 +// resolved when the asychronous action has completed. By chaining these 1.118 +// intermediate results together we run the test to completion. 1.119 +// 1.120 +// This method itself returns a Promise that is resolved when the generator 1.121 +// function has completed. 1.122 +// 1.123 +// This arrangement is based on add_task() which is currently only available 1.124 +// in mochitest-chrome (bug 872229). Once add_task is available in 1.125 +// mochitest-plain we can remove this function and use add_task instead. 1.126 +function runAsyncTest(test) { 1.127 + var generator; 1.128 + 1.129 + function step(arg) { 1.130 + var next; 1.131 + try { 1.132 + next = generator.next(arg); 1.133 + } catch (e) { 1.134 + return Promise.reject(e); 1.135 + } 1.136 + if (next.done) { 1.137 + return Promise.resolve(next.value); 1.138 + } else { 1.139 + return Promise.resolve(next.value) 1.140 + .then(step, function(err) { throw err; }); 1.141 + } 1.142 + } 1.143 + 1.144 + // Put refresh driver under test control 1.145 + advance_clock(0); 1.146 + 1.147 + // Run test 1.148 + generator = test(); 1.149 + return step() 1.150 + .catch(function(err) { 1.151 + ok(false, err.message); 1.152 + // Clear up the test div in case we aborted the test before doing clean-up 1.153 + if (gDiv) { 1.154 + done_div(); 1.155 + } 1.156 + }).then(function() { 1.157 + // Restore clock 1.158 + SpecialPowers.DOMWindowUtils.restoreNormalRefresh(); 1.159 + }); 1.160 +} 1.161 + 1.162 +function addAsyncTest(generator) { 1.163 + gAsyncTests.push(generator); 1.164 +} 1.165 + 1.166 +//---------------------------------------------------------------------- 1.167 +// 1.168 +// Test cases 1.169 +// 1.170 +//---------------------------------------------------------------------- 1.171 + 1.172 +// This test is not in test_animations.html but is here to test that 1.173 +// transform animations are actually run on the compositor thread as expected. 1.174 +addAsyncTest(function *() { 1.175 + new_div("animation: transform-anim linear 300s"); 1.176 + 1.177 + yield waitForPaints(); 1.178 + 1.179 + advance_clock(200000); 1.180 + omta_is("transform", { tx: 100 * 2 / 3 }, RunningOn.Compositor, 1.181 + "OMTA animation is animating as expected"); 1.182 + done_div(); 1.183 +}); 1.184 + 1.185 +function *testFillMode(fillMode, fillsBackwards, fillsForwards) 1.186 +{ 1.187 + var style = "transform: translate(30px); animation: 10s 3s anim1 linear"; 1.188 + var desc; 1.189 + if (fillMode.length > 0) { 1.190 + style += " " + fillMode; 1.191 + desc = "fill mode " + fillMode + ": "; 1.192 + } else { 1.193 + desc = "default fill mode: "; 1.194 + } 1.195 + new_div(style); 1.196 + listen(); 1.197 + 1.198 + // Currently backwards fill is not performed on the compositor thread but we 1.199 + // should wait for paints so we can test that transform values are *not* being 1.200 + // set on the compositor thread. 1.201 + yield waitForPaints(); 1.202 + 1.203 + if (fillsBackwards) 1.204 + omta_is("transform", { tx: 0 }, RunningOn.MainThread, 1.205 + desc + "does affect value during delay (0s)"); 1.206 + else 1.207 + omta_is("transform", { tx: 30 }, RunningOn.MainThread, 1.208 + desc + "doesn't affect value during delay (0s)"); 1.209 + 1.210 + advance_clock(2000); 1.211 + if (fillsBackwards) 1.212 + omta_is("transform", { tx: 0 }, RunningOn.MainThead, 1.213 + desc + "does affect value during delay (0s)"); 1.214 + else 1.215 + omta_is("transform", { tx: 30 }, RunningOn.MainThread, 1.216 + desc + "does affect value during delay (0s)"); 1.217 + 1.218 + check_events([], "before start in testFillMode"); 1.219 + advance_clock(1000); 1.220 + check_events([{ type: "animationstart", target: gDiv, 1.221 + bubbles: true, cancelable: false, 1.222 + animationName: "anim1", elapsedTime: 0.0, 1.223 + pseudoElement: "" }], 1.224 + "right after start in testFillMode"); 1.225 + 1.226 + // If we have a backwards fill then at the start of the animation we will end 1.227 + // up applying the same value as the fill value. Various optimizations in 1.228 + // RestyleManager may filter out this meaning that the animation doesn't get 1.229 + // added to the compositor thread until the first time the value changes. 1.230 + // 1.231 + // As a result we look for this first sample on either the compositor or the 1.232 + // computed style 1.233 + yield waitForPaints(); 1.234 + omta_is("transform", { tx: 0 }, RunningOn.Either, 1.235 + desc + "affects value at start of animation"); 1.236 + advance_clock(125); 1.237 + // We might not add the animation to compositor until the second sample (due 1.238 + // to the optimizations mentioned above) so we should wait for paints before 1.239 + // proceeding 1.240 + yield waitForPaints(); 1.241 + omta_is("transform", { tx: 2 }, RunningOn.Compositor, 1.242 + desc + "affects value during animation"); 1.243 + advance_clock(2375); 1.244 + omta_is("transform", { tx: 40 }, RunningOn.Compositor, 1.245 + desc + "affects value during animation"); 1.246 + advance_clock(2500); 1.247 + omta_is("transform", { tx: 80 }, RunningOn.Compositor, 1.248 + desc + "affects value during animation"); 1.249 + advance_clock(2500); 1.250 + omta_is("transform", { tx: 90 }, RunningOn.Compositor, 1.251 + desc + "affects value during animation"); 1.252 + advance_clock(2375); 1.253 + omta_is("transform", { tx: 99.5 }, RunningOn.Compositor, 1.254 + desc + "affects value during animation"); 1.255 + check_events([], "before end in testFillMode"); 1.256 + advance_clock(125); 1.257 + check_events([{ type: "animationend", target: gDiv, 1.258 + bubbles: true, cancelable: false, 1.259 + animationName: "anim1", elapsedTime: 10.0, 1.260 + pseudoElement: "" }], 1.261 + "right after end in testFillMode"); 1.262 + 1.263 + // Currently the compositor will apply a forwards fill until it gets told by 1.264 + // the main thread to clear the animation. As a result we should wait for 1.265 + // paints to be flushed before checking that the animated value does *not* 1.266 + // appear on the compositor thread. 1.267 + yield waitForPaints(); 1.268 + if (fillsForwards) 1.269 + omta_is("transform", { tx: 100 }, RunningOn.MainThread, 1.270 + desc + "affects value at end of animation"); 1.271 + advance_clock(10); 1.272 + if (fillsForwards) 1.273 + omta_is("transform", { tx: 100 }, RunningOn.MainThread, 1.274 + desc + "affects value after animation"); 1.275 + else 1.276 + omta_is("transform", { tx: 30 }, RunningOn.MainThread, 1.277 + desc + "does not affect value after animation"); 1.278 + 1.279 + done_div(); 1.280 +} 1.281 + 1.282 +addAsyncTest(function() { return testFillMode("", false, false); }); 1.283 +addAsyncTest(function() { return testFillMode("none", false, false); }); 1.284 +addAsyncTest(function() { return testFillMode("forwards", false, true); }); 1.285 +addAsyncTest(function() { return testFillMode("backwards", true, false); }); 1.286 +addAsyncTest(function() { return testFillMode("both", true, true); }); 1.287 + 1.288 +// Test that animations continue running when the animation name 1.289 +// list is changed. 1.290 +// 1.291 +// test_animations.html combines all these tests into one block but this is 1.292 +// difficult for OMTA because currently there are only two properties to which 1.293 +// we apply OMTA. Instead we break the test down into a few independent pieces 1.294 +// in order to exercise the same functionality. 1.295 + 1.296 +// Append to list 1.297 +addAsyncTest(function *() { 1.298 + new_div("animation: anim1 linear 10s"); 1.299 + yield waitForPaints(); 1.300 + omta_is("transform", { tx: 0 }, RunningOn.Either, 1.301 + "just anim1, translate at start"); 1.302 + advance_clock(1000); 1.303 + omta_is("transform", { tx: 16 }, RunningOn.Compositor, 1.304 + "just anim1, translate at 1s"); 1.305 + // append anim2 1.306 + gDiv.style.animation = "anim1 linear 10s, anim2 linear 10s"; 1.307 + yield waitForPaintsFlushed(); 1.308 + omta_is("transform", { tx: 16 }, RunningOn.Compositor, 1.309 + "anim1 + anim2, translate at 1s"); 1.310 + omta_is("opacity", 0, RunningOn.Compositor, 1.311 + "anim1 + anim2, opacity at 1s"); 1.312 + advance_clock(1000); 1.313 + omta_is("transform", { tx: 32 }, RunningOn.Compositor, 1.314 + "anim1 + anim2, translate at 2s"); 1.315 + omta_is("opacity", 0.1, RunningOn.Compositor, 1.316 + "anim1 + anim2, opacity at 2s"); 1.317 + done_div(); 1.318 +}); 1.319 + 1.320 +// Prepend to list; delete from list 1.321 +addAsyncTest(function *() { 1.322 + new_div("animation: anim1 linear 10s"); 1.323 + yield waitForPaints(); 1.324 + omta_is("transform", { tx: 0 }, RunningOn.Either, 1.325 + "just anim1, translate at start"); 1.326 + advance_clock(1000); 1.327 + omta_is("transform", { tx: 16 }, RunningOn.Compositor, 1.328 + "just anim1, translate at 1s"); 1.329 + // prepend anim2 1.330 + gDiv.style.animation = "anim2 linear 10s, anim1 linear 10s"; 1.331 + yield waitForPaintsFlushed(); 1.332 + omta_is("transform", { tx: 16 }, RunningOn.Compositor, 1.333 + "anim2 + anim1, translate at 1s"); 1.334 + omta_is("opacity", 0, RunningOn.Compositor, 1.335 + "anim2 + anim1, opacity at 1s"); 1.336 + advance_clock(1000); 1.337 + omta_is("transform", { tx: 32 }, RunningOn.Compositor, 1.338 + "anim2 + anim1, translate at 2s"); 1.339 + omta_is("opacity", 0.1, RunningOn.Compositor, 1.340 + "anim2 + anim1, opacity at 2s"); 1.341 + // remove anim2 from list 1.342 + gDiv.style.animation = "anim1 linear 10s"; 1.343 + yield waitForPaintsFlushed(); 1.344 + omta_is("transform", { tx: 32 }, RunningOn.Compositor, 1.345 + "just anim1, translate at 2s"); 1.346 + omta_is("opacity", 1, RunningOn.MainThread, "just anim1, opacity at 2s"); 1.347 + advance_clock(1000); 1.348 + omta_is("transform", { tx: 48 }, RunningOn.Compositor, 1.349 + "just anim1, translate at 3s"); 1.350 + omta_is("opacity", 1, RunningOn.MainThread, "just anim1, opacity at 3s"); 1.351 + done_div(); 1.352 +}); 1.353 + 1.354 +// Swap elements 1.355 +addAsyncTest(function *() { 1.356 + new_div("animation: anim1 linear 10s, anim2 linear 10s"); 1.357 + yield waitForPaints(); 1.358 + omta_is("transform", { tx: 0 }, RunningOn.Either, 1.359 + "anim1 + anim2, translate at start"); 1.360 + omta_is("opacity", 0, RunningOn.Compositor, 1.361 + "anim1 + anim2, opacity at start"); 1.362 + advance_clock(1000); 1.363 + omta_is("transform", { tx: 16 }, RunningOn.Compositor, 1.364 + "anim1 + anim2, translate at 1s"); 1.365 + omta_is("opacity", 0.1, RunningOn.Compositor, 1.366 + "anim1 + anim2, opacity at 1s"); 1.367 + // swap anim1 and anim2, change duration of anim2 1.368 + gDiv.style.animation = "anim2 linear 5s, anim1 linear 10s"; 1.369 + yield waitForPaintsFlushed(); 1.370 + omta_is("transform", { tx: 16 }, RunningOn.Compositor, 1.371 + "anim2 + anim1, translate at 1s"); 1.372 + omta_is("opacity", 0.2, RunningOn.Compositor, 1.373 + "anim2 + anim1, opacity at 1s"); 1.374 + advance_clock(1000); 1.375 + omta_is("transform", { tx: 32 }, RunningOn.Compositor, 1.376 + "anim2 + anim1, translate at 2s"); 1.377 + omta_is("opacity", 0.4, RunningOn.Compositor, 1.378 + "anim2 + anim1, opacity at 2s"); 1.379 + // list anim2 twice, last duration wins, original start time still applies 1.380 + gDiv.style.animation = "anim2 linear 5s, anim1 linear 10s, anim2 linear 20s"; 1.381 + yield waitForPaintsFlushed(); 1.382 + omta_is("transform", { tx: 32 }, RunningOn.Compositor, 1.383 + "anim2 + anim1 + anim2, translate at 2s"); 1.384 + // Bug 980769 1.385 + todo_is(SpecialPowers.DOMWindowUtils.getOMTAStyle(gDiv, "opacity"), "0.1", 1.386 + "anim2 + anim1 + anim2, opacity at 2s"); 1.387 + // drop one of the anim2, and list anim3 as well, which animates 1.388 + // the same property as anim2 1.389 + gDiv.style.animation = "anim1 linear 10s, anim2 linear 20s, anim3 linear 10s"; 1.390 + yield waitForPaintsFlushed(); 1.391 + omta_is("transform", { tx: 32 }, RunningOn.Compositor, 1.392 + "anim1 + anim2 + anim3, translate at 2s"); 1.393 + // Bug 980769 1.394 + todo_is(SpecialPowers.DOMWindowUtils.getOMTAStyle(gDiv, "opacity"), "0", 1.395 + "anim1 + anim2 + anim3, opacity at 2s"); 1.396 + advance_clock(1000); 1.397 + omta_is("transform", { tx: 48 }, RunningOn.Compositor, 1.398 + "anim1 + anim2 + anim3, translate at 3s"); 1.399 + // Bug 980769 1.400 + todo_is(SpecialPowers.DOMWindowUtils.getOMTAStyle(gDiv, "opacity"), "0.1", 1.401 + "anim1 + anim2 + anim3, opacity at 3s"); 1.402 + // now swap the anim3 and anim2 order 1.403 + gDiv.style.animation = "anim1 linear 10s, anim3 linear 10s, anim2 linear 20s"; 1.404 + yield waitForPaintsFlushed(); 1.405 + omta_is("transform", { tx: 48 }, RunningOn.Compositor, 1.406 + "anim1 + anim3 + anim2, translate at 3s"); 1.407 + // Bug 980769 1.408 + todo_is(SpecialPowers.DOMWindowUtils.getOMTAStyle(gDiv, "opacity"), "0.15", 1.409 + "anim1 + anim3 + anim2, opacity at 3s"); 1.410 + advance_clock(2000); // (unlike test_animations.html, we seek 2s forwards here 1.411 + // since at 4s anim2 and anim3 produce the same result so 1.412 + // we can't tell which won.) 1.413 + omta_is("transform", { tx: 80 }, RunningOn.Compositor, 1.414 + "anim1 + anim3 + anim2, translate at 5s"); 1.415 + // Bug 980769 1.416 + todo_is(SpecialPowers.DOMWindowUtils.getOMTAStyle(gDiv, "opacity"), "0.25", 1.417 + "anim1 + anim3 + anim2, opacity at 5s"); 1.418 + // swap anim3 and anim2 back 1.419 + gDiv.style.animation = "anim1 linear 10s, anim2 linear 20s, anim3 linear 10s"; 1.420 + yield waitForPaintsFlushed(); 1.421 + omta_is("transform", { tx: 80 }, RunningOn.Compositor, 1.422 + "anim1 + anim2 + anim3, translate at 5s"); 1.423 + // Bug 980769 1.424 + todo_is(SpecialPowers.DOMWindowUtils.getOMTAStyle(gDiv, "opacity"), "0.3", 1.425 + "anim1 + anim2 + anim3, opacity at 5s"); 1.426 + // seek past end of anim1 1.427 + advance_clock(5100); 1.428 + yield waitForPaints(); 1.429 + omta_is("transform", { tx: 0 }, RunningOn.MainThread, 1.430 + "anim1 + anim2 + anim3, translate at 10.1s"); 1.431 + // Change the animation fill mode on the completed animation. 1.432 + gDiv.style.animation = 1.433 + "anim1 linear 10s forwards, anim2 linear 20s, anim3 linear 10s"; 1.434 + yield waitForPaintsFlushed(); 1.435 + omta_is("transform", { tx: 100 }, RunningOn.MainThread, 1.436 + "anim1 + anim2 + anim3, translate at 10.1s with fill mode"); 1.437 + advance_clock(900); 1.438 + omta_is("transform", { tx: 100 }, RunningOn.MainThread, 1.439 + "anim1 + anim2 + anim3, translate at 11s with fill mode"); 1.440 + // Change the animation duration on the completed animation, so it is 1.441 + // no longer completed. 1.442 + // XXX Not sure about this---there seems to be a bug in test_animations.html 1.443 + // in that it drops the fill mode but the test comment says it has a fill mode 1.444 + gDiv.style.animation = "anim1 linear 20s, anim2 linear 20s, anim3 linear 10s"; 1.445 + yield waitForPaintsFlushed(); 1.446 + omta_is("transform", { tx: 82 }, RunningOn.Compositor, 1.447 + "anim1 + anim2 + anim3, translate at 11s with fill mode"); 1.448 + // Bug 980769 - We should get 0.9 but instead 1.449 + todo_is(SpecialPowers.DOMWindowUtils.getOMTAStyle(gDiv, "opacity"), "0.9", 1.450 + "anim1 + anim2 + anim3, opacity at 11s"); 1.451 + done_div(); 1.452 +}); 1.453 + 1.454 +/* 1.455 + * css3-animations: 3. Keyframes 1.456 + * http://dev.w3.org/csswg/css3-animations/#keyframes 1.457 + */ 1.458 + 1.459 +// Test the rules on keyframes that lack a 0% or 100% rule: 1.460 +// (simultaneously, test that reverse animations have their keyframes 1.461 +// run backwards) 1.462 + 1.463 +addAsyncTest(function *() { 1.464 + // 100px at 0%, 50px at 50%, 150px at 100% 1.465 + new_div("transform: translate(100px); " + 1.466 + "animation: kf1 ease 1s alternate infinite"); 1.467 + advance_clock(0); 1.468 + yield waitForPaints(); 1.469 + omta_is("transform", { tx: 100 }, RunningOn.Compositor, "no-0% at 0.0s"); 1.470 + advance_clock(100); 1.471 + omta_is_approx("transform", { tx: 100 - 50 * gTF.ease(0.2) }, 1.472 + RunningOn.Compositor, 0.01, "no-0% at 0.1s"); 1.473 + advance_clock(200); 1.474 + omta_is_approx("transform", { tx: 100 - 50 * gTF.ease(0.6) }, 1.475 + RunningOn.Compositor, 0.01, "no-0% at 0.3s"); 1.476 + advance_clock(200); 1.477 + omta_is("transform", { tx: 50 }, RunningOn.Compositor, "no-0% at 0.5s"); 1.478 + advance_clock(200); 1.479 + omta_is_approx("transform", { tx: 50 + 100 * gTF.ease(0.4) }, 1.480 + RunningOn.Compositor, 0.01, "no-0% at 0.7s"); 1.481 + advance_clock(200); 1.482 + omta_is_approx("transform", { tx: 50 + 100 * gTF.ease(0.8) }, 1.483 + RunningOn.Compositor, 0.01, "no-0% at 0.9s"); 1.484 + advance_clock(100); 1.485 + omta_is("transform", { tx: 150 }, RunningOn.Compositor, "no-0% at 1.0s"); 1.486 + advance_clock(100); 1.487 + omta_is_approx("transform", { tx: 50 + 100 * gTF.ease(0.8) }, 1.488 + RunningOn.Compositor, 0.01, "no-0% at 1.1s"); 1.489 + advance_clock(300); 1.490 + omta_is_approx("transform", { tx: 50 + 100 * gTF.ease(0.2) }, 1.491 + RunningOn.Compositor, 0.01, "no-0% at 1.4s"); 1.492 + advance_clock(300); 1.493 + omta_is_approx("transform", { tx: 100 - 50 * gTF.ease(0.6) }, 1.494 + RunningOn.Compositor, 0.01, "no-0% at 1.7s"); 1.495 + advance_clock(200); 1.496 + omta_is_approx("transform", { tx: 100 - 50 * gTF.ease(0.2) }, 1.497 + RunningOn.Compositor, 0.01, "no-0% at 1.9s"); 1.498 + advance_clock(100); 1.499 + omta_is("transform", { tx: 100 }, RunningOn.Compositor, "no-0% at 2.0s"); 1.500 + done_div(); 1.501 + 1.502 + // 150px at 0%, 50px at 50%, 100px at 100% 1.503 + new_div("transform: translate(100px); " + 1.504 + "animation: kf2 ease-in 1s alternate infinite"); 1.505 + yield waitForPaints(); 1.506 + omta_is("transform", { tx: 150 }, RunningOn.Compositor, "no-100% at 0.0s"); 1.507 + advance_clock(100); 1.508 + omta_is_approx("transform", { tx: 150 - 100 * gTF.ease_in(0.2) }, 1.509 + RunningOn.Compositor, 0.01, "no-100% at 0.1s"); 1.510 + advance_clock(200); 1.511 + omta_is_approx("transform", { tx: 150 - 100 * gTF.ease_in(0.6) }, 1.512 + RunningOn.Compositor, 0.01, "no-100% at 0.3s"); 1.513 + advance_clock(200); 1.514 + omta_is("transform", { tx: 50 }, RunningOn.Compositor, "no-100% at 0.5s"); 1.515 + advance_clock(200); 1.516 + omta_is_approx("transform", { tx: 50 + 50 * gTF.ease_in(0.4) }, 1.517 + RunningOn.Compositor, 0.01, "no-100% at 0.7s"); 1.518 + advance_clock(200); 1.519 + omta_is_approx("transform", { tx: 50 + 50 * gTF.ease_in(0.8) }, 1.520 + RunningOn.Compositor, 0.01, "no-100% at 0.9s"); 1.521 + advance_clock(100); 1.522 + omta_is("transform", { tx: 100 }, RunningOn.Compositor, "no-100% at 1.0s"); 1.523 + advance_clock(100); 1.524 + omta_is_approx("transform", { tx: 50 + 50 * gTF.ease_in(0.8) }, 1.525 + RunningOn.Compositor, 0.01, "no-100% at 1.1s"); 1.526 + advance_clock(300); 1.527 + omta_is_approx("transform", { tx: 50 + 50 * gTF.ease_in(0.2) }, 1.528 + RunningOn.Compositor, 0.01, "no-100% at 1.4s"); 1.529 + advance_clock(300); 1.530 + omta_is_approx("transform", { tx: 150 - 100 * gTF.ease_in(0.6) }, 1.531 + RunningOn.Compositor, 0.01, "no-100% at 1.7s"); 1.532 + advance_clock(200); 1.533 + omta_is_approx("transform", { tx: 150 - 100 * gTF.ease_in(0.2) }, 1.534 + RunningOn.Compositor, 0.01, "no-100% at 1.9s"); 1.535 + advance_clock(100); 1.536 + omta_is("transform", { tx: 150 }, RunningOn.Compositor, "no-100% at 2.0s"); 1.537 + done_div(); 1.538 + 1.539 + // 50px at 0%, 100px at 25%, 50px at 100% 1.540 + new_div("transform: translate(50px); " + 1.541 + "animation: kf3 ease-out 1s alternate infinite"); 1.542 + yield waitForPaints(); 1.543 + omta_is("transform", { tx: 50 }, RunningOn.Compositor, 1.544 + "no-0%-no-100% at 0.0s"); 1.545 + advance_clock(50); 1.546 + omta_is_approx("transform", { tx: 50 + 50 * gTF.ease_out(0.2) }, 1.547 + RunningOn.Compositor, 0.01, "no-0%-no-100% at 0.05s"); 1.548 + advance_clock(100); 1.549 + omta_is_approx("transform", { tx: 50 + 50 * gTF.ease_out(0.6) }, 1.550 + RunningOn.Compositor, 0.01, "no-0%-no-100% at 0.15s"); 1.551 + advance_clock(100); 1.552 + omta_is("transform", { tx: "100px" }, RunningOn.Compositor, 1.553 + "no-0%-no-100% at 0.25s"); 1.554 + advance_clock(300); 1.555 + omta_is_approx("transform", { tx: 100 - 50 * gTF.ease_out(0.4) }, 1.556 + RunningOn.Compositor, 0.01, "no-0%-no-100% at 0.55s"); 1.557 + advance_clock(300); 1.558 + omta_is_approx("transform", { tx: 100 - 50 * gTF.ease_out(0.8) }, 1.559 + RunningOn.Compositor, 0.01, "no-0%-no-100% at 0.85s"); 1.560 + advance_clock(150); 1.561 + omta_is("transform", { tx: 50 }, RunningOn.Compositor, 1.562 + "no-0%-no-100% at 1.0s"); 1.563 + advance_clock(150); 1.564 + omta_is_approx("transform", { tx: 100 - 50 * gTF.ease_out(0.8) }, 1.565 + RunningOn.Compositor, 0.01, "no-0%-no-100% at 1.15s"); 1.566 + advance_clock(450); 1.567 + omta_is_approx("transform", { tx: 100 - 50 * gTF.ease_out(0.2) }, 1.568 + RunningOn.Compositor, 0.01, "no-0%-no-100% at 1.6s"); 1.569 + advance_clock(250); 1.570 + omta_is_approx("transform", { tx: 50 + 50 * gTF.ease_out(0.6) }, 1.571 + RunningOn.Compositor, 0.01, "no-0%-no-100% at 1.85s"); 1.572 + advance_clock(100); 1.573 + omta_is_approx("transform", { tx: 50 + 50 * gTF.ease_out(0.2) }, 1.574 + RunningOn.Compositor, 0.01, "no-0%-no-100% at 1.95s"); 1.575 + advance_clock(50); 1.576 + omta_is("transform", { tx: 50 }, RunningOn.Compositor, 1.577 + "no-0%-no-100% at 2.0s"); 1.578 + done_div(); 1.579 + 1.580 + // Test that non-animatable properties are ignored. 1.581 + // Simultaneously, test that the block is still honored, and that 1.582 + // we still override the value when two consecutive keyframes have 1.583 + // the same value. 1.584 + new_div("animation: kf4 ease 10s"); 1.585 + yield waitForPaints(); 1.586 + var cs = window.getComputedStyle(gDiv); 1.587 + is(cs.display, "block", 1.588 + "non-animatable properties should be ignored (linear, 0s)"); 1.589 + omta_is("transform", { tx: 37 }, RunningOn.Compositor, 1.590 + "animatable properties should still apply (linear, 0s)"); 1.591 + advance_clock(1000); 1.592 + is(cs.display, "block", 1.593 + "non-animatable properties should be ignored (linear, 1s)"); 1.594 + omta_is("transform", { tx: 37 }, RunningOn.Compositor, 1.595 + "animatable properties should still apply (linear, 1s)"); 1.596 + done_div(); 1.597 + new_div("animation: kf4 step-start 10s"); 1.598 + yield waitForPaints(); 1.599 + cs = window.getComputedStyle(gDiv); 1.600 + is(cs.display, "block", 1.601 + "non-animatable properties should be ignored (step-start, 0s)"); 1.602 + omta_is("transform", { tx: 37 }, RunningOn.Compositor, 1.603 + "animatable properties should still apply (step-start, 0s)"); 1.604 + advance_clock(1000); 1.605 + is(cs.display, "block", 1.606 + "non-animatable properties should be ignored (step-start, 1s)"); 1.607 + omta_is("transform", { tx: 37 }, RunningOn.Compositor, 1.608 + "animatable properties should still apply (step-start, 1s)"); 1.609 + done_div(); 1.610 + 1.611 + // Test cascading of the keyframes within an @keyframes rule. 1.612 + new_div("animation: kf_cascade1 linear 10s"); 1.613 + yield waitForPaints(); 1.614 + // 0%: 30px 1.615 + // 50%: 20px 1.616 + // 75%: 20px 1.617 + // 85%: 30px 1.618 + // 85.1%: 60px 1.619 + // 100%: 70px 1.620 + omta_is("transform", { tx: 30 }, RunningOn.Compositor, "kf_cascade1 at 0s"); 1.621 + advance_clock(2500); 1.622 + omta_is("transform", { tx: 25 }, RunningOn.Compositor, "kf_cascade1 at 2.5s"); 1.623 + advance_clock(2500); 1.624 + omta_is("transform", { tx: 20 }, RunningOn.Compositor, "kf_cascade1 at 5s"); 1.625 + advance_clock(2000); 1.626 + omta_is("transform", { tx: 20 }, RunningOn.Compositor, "kf_cascade1 at 7s"); 1.627 + advance_clock(500); 1.628 + omta_is("transform", { tx: 20 }, RunningOn.Compositor, "kf_cascade1 at 7.5s"); 1.629 + advance_clock(500); 1.630 + omta_is("transform", { tx: 25 }, RunningOn.Compositor, "kf_cascade1 at 8s"); 1.631 + advance_clock(500); 1.632 + omta_is("transform", { tx: 30 }, RunningOn.Compositor, "kf_cascade1 at 8.5s"); 1.633 + advance_clock(10); 1.634 + // For some reason we get an error of 0.0003 for this test only 1.635 + omta_is_approx("transform", { tx: 60 }, RunningOn.Compositor, 0.001, 1.636 + "kf_cascade1 at 8.51s"); 1.637 + advance_clock(745); 1.638 + omta_is("transform", { tx: 65 }, RunningOn.Compositor, 1.639 + "kf_cascade1 at 9.2505s"); 1.640 + done_div(); 1.641 + 1.642 + // Test cascading of the @keyframes rules themselves. 1.643 + new_div("animation: kf_cascade2 linear 10s"); 1.644 + yield waitForPaints(); 1.645 + omta_is("opacity", 1, RunningOn.MainThread, 1.646 + "last @keyframes rule with transform should win"); 1.647 + omta_is("transform", { tx: 100 }, RunningOn.Compositor, 1.648 + "last @keyframes rule with transform should win"); 1.649 + done_div(); 1.650 +}); 1.651 + 1.652 +//---------------------------------------------------------------------- 1.653 +// 1.654 +// Helper functions from test_animations.html 1.655 +// 1.656 +//---------------------------------------------------------------------- 1.657 + 1.658 +function new_div(style) { 1.659 + if (gDiv !== null) { 1.660 + ok(false, "test author forgot to call done_div"); 1.661 + } 1.662 + if (typeof(style) != "string") { 1.663 + ok(false, "test author forgot to pass style argument"); 1.664 + } 1.665 + gDiv = document.createElement("div"); 1.666 + gDiv.classList.add("target"); 1.667 + gDiv.setAttribute("style", style); 1.668 + gDisplay.appendChild(gDiv); 1.669 + gDiv.clientTop; 1.670 +} 1.671 + 1.672 +function done_div() { 1.673 + if (gDiv === null) { 1.674 + ok(false, "test author forgot to call new_div"); 1.675 + } 1.676 + gDisplay.removeChild(gDiv); 1.677 + gDiv = null; 1.678 +} 1.679 + 1.680 +function listen() { 1.681 + gEventsReceived = []; 1.682 + function listener(event) { 1.683 + gEventsReceived.push(event); 1.684 + } 1.685 + gDiv.addEventListener("animationstart", listener, false); 1.686 + gDiv.addEventListener("animationiteration", listener, false); 1.687 + gDiv.addEventListener("animationend", listener, false); 1.688 +} 1.689 + 1.690 +function check_events(events_expected, desc) { 1.691 + // This function checks that the list of events_expected matches 1.692 + // the received events -- but it only checks the properties that 1.693 + // are present on events_expected. 1.694 + is(gEventsReceived.length, events_expected.length, 1.695 + "number of events received for " + desc); 1.696 + for (var i = 0, 1.697 + i_end = Math.min(events_expected.length, gEventsReceived.length); 1.698 + i != i_end; ++i) { 1.699 + var exp = events_expected[i]; 1.700 + var rec = gEventsReceived[i]; 1.701 + for (var prop in exp) { 1.702 + if (prop == "elapsedTime") { 1.703 + // Allow floating point error. 1.704 + ok(Math.abs(rec.elapsedTime - exp.elapsedTime) < 0.000002, 1.705 + "events[" + i + "]." + prop + " for " + desc + 1.706 + " received=" + rec.elapsedTime + " expected=" + exp.elapsedTime); 1.707 + } else { 1.708 + is(rec[prop], exp[prop], "events[" + i + "]." + prop + " for " + desc); 1.709 + } 1.710 + } 1.711 + } 1.712 + for (i = events_expected.length; i < gEventsReceived.length; ++i) { 1.713 + ok(false, "unexpected " + gEventsReceived[i].type + " event for " + desc); 1.714 + } 1.715 + gEventsReceived = []; 1.716 +} 1.717 + 1.718 +function advance_clock(milliseconds) { 1.719 + SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(milliseconds); 1.720 +} 1.721 + 1.722 +//---------------------------------------------------------------------- 1.723 +// 1.724 +// Helper functions for querying the compositor thread 1.725 +// 1.726 +//---------------------------------------------------------------------- 1.727 + 1.728 +// Returns a Promise that resolves once all paints have completed 1.729 +function waitForPaints() { 1.730 + return new Promise(function(resolve, reject) { 1.731 + waitForAllPaints(resolve); 1.732 + }); 1.733 +} 1.734 + 1.735 +// As with waitForPaints but also flushes pending style changes before waiting 1.736 +function waitForPaintsFlushed() { 1.737 + return new Promise(function(resolve, reject) { 1.738 + waitForAllPaintsFlushed(resolve); 1.739 + }); 1.740 +} 1.741 + 1.742 +//---------------------------------------------------------------------- 1.743 +// 1.744 +// Helper functions for working with animated values 1.745 +// 1.746 +//---------------------------------------------------------------------- 1.747 + 1.748 +const RunningOn = { 1.749 + MainThread: 0, 1.750 + Compositor: 1, 1.751 + Either: 2 1.752 +}; 1.753 + 1.754 +function omta_is(property, expected, runningOn, desc) { 1.755 + return omta_is_approx(property, expected, runningOn, 0, desc); 1.756 +} 1.757 + 1.758 +function omta_is_approx(property, expected, runningOn, tolerance, desc) { 1.759 + // Check input 1.760 + const omtaProperties = [ "transform", "opacity" ]; 1.761 + if (omtaProperties.indexOf(property) === -1) { 1.762 + ok(false, property + " is not an OMTA property"); 1.763 + return; 1.764 + } 1.765 + var isTransform = property == "transform"; 1.766 + var normalize = isTransform ? convertTo3dMatrix : parseFloat; 1.767 + var compare = isTransform ? 1.768 + matricesRoughlyEqual : 1.769 + function(a, b, error) { return Math.abs(a - b) <= error; }; 1.770 + var normalizedToString = isTransform ? 1.771 + convert3dMatrixToString : 1.772 + JSON.stringify; 1.773 + 1.774 + // Get actual values 1.775 + var compositorStr = SpecialPowers.DOMWindowUtils.getOMTAStyle(gDiv, property); 1.776 + var computedStr = window.getComputedStyle(gDiv)[property]; 1.777 + 1.778 + // Prepare expected value 1.779 + var expectedValue = normalize(expected); 1.780 + if (expectedValue === null) { 1.781 + ok(false, desc + ": test author should provide a valid 'expected' value" + 1.782 + " - got " + expected.toString()); 1.783 + return; 1.784 + } 1.785 + 1.786 + // Check expected value appears in the right place 1.787 + var actualStr; 1.788 + switch (runningOn) { 1.789 + case RunningOn.Either: 1.790 + runningOn = compositorStr !== "" ? 1.791 + RunningOn.Compositor : 1.792 + RunningOn.MainThread; 1.793 + actualStr = compositorStr !== "" ? compositorStr : computedStr; 1.794 + break; 1.795 + 1.796 + case RunningOn.Compositor: 1.797 + if (compositorStr === "") { 1.798 + ok(false, desc + ": should be animating on compositor"); 1.799 + return; 1.800 + } 1.801 + actualStr = compositorStr; 1.802 + break; 1.803 + 1.804 + default: 1.805 + if (compositorStr !== "") { 1.806 + ok(false, desc + ": should NOT be animating on compositor"); 1.807 + return; 1.808 + } 1.809 + actualStr = computedStr; 1.810 + break; 1.811 + } 1.812 + 1.813 + // Compare animated value with expected 1.814 + var actualValue = normalize(actualStr); 1.815 + if (actualValue === null) { 1.816 + ok(false, desc + ": should return a valid result - got " + actualStr); 1.817 + return; 1.818 + } 1.819 + ok(compare(expectedValue, actualValue, tolerance), 1.820 + desc + " - got " + actualStr + ", expected " + 1.821 + normalizedToString(expectedValue)); 1.822 + 1.823 + // For compositor animations do an additional check that they match 1.824 + // the value calculated on the main thread 1.825 + if (runningOn === RunningOn.Compositor) { 1.826 + var computedValue = normalize(computedStr); 1.827 + if (computedValue === null) { 1.828 + ok(false, desc + ": test framework should parse computed style" + 1.829 + " - got " + computedStr); 1.830 + return; 1.831 + } 1.832 + ok(compare(computedValue, actualValue, 0), 1.833 + desc + ": OMTA style and computed style should be equal" + 1.834 + " - OMTA " + actualStr + ", computed " + computedStr); 1.835 + } 1.836 +} 1.837 + 1.838 +function matricesRoughlyEqual(a, b, tolerance) { 1.839 + tolerance = tolerance || 0.0001; 1.840 + for (var i = 0; i < 4; i++) { 1.841 + for (var j = 0; j < 4; j++) { 1.842 + if (Math.abs(a[i][j] - b[i][j]) > tolerance) 1.843 + return false; 1.844 + } 1.845 + } 1.846 + return true; 1.847 +} 1.848 + 1.849 +// Converts something representing an transform into a 3d matrix in column-major 1.850 +// order. 1.851 +// The following are supported: 1.852 +// "matrix(...)" 1.853 +// "matrix3d(...)" 1.854 +// [ 1, 0, 0, ... ] 1.855 +// { a: 1, ty: 23 } etc. 1.856 +function convertTo3dMatrix(matrixLike) { 1.857 + if (typeof(matrixLike) == "string") { 1.858 + return convertStringTo3dMatrix(matrixLike); 1.859 + } else if (Array.isArray(matrixLike)) { 1.860 + return convertArrayTo3dMatrix(matrixLike); 1.861 + } else if (typeof(matrixLike) == "object") { 1.862 + return convertObjectTo3dMatrix(matrixLike); 1.863 + } else { 1.864 + return null; 1.865 + } 1.866 +} 1.867 + 1.868 +// Converts strings of the format "matrix(...)" and "matrix3d(...)" to a 3d 1.869 +// matrix 1.870 +function convertStringTo3dMatrix(str) { 1.871 + if (str == "none") 1.872 + return convertArrayTo3dMatrix([1, 0, 0, 1, 0, 0]); 1.873 + var result = str.match("^matrix(3d)?\\("); 1.874 + if (result === null) 1.875 + return null; 1.876 + 1.877 + return convertArrayTo3dMatrix( 1.878 + str.substring(result[0].length, str.length-1) 1.879 + .split(",") 1.880 + .map(function(component) { 1.881 + return Number(component); 1.882 + }) 1.883 + ); 1.884 +} 1.885 + 1.886 +// Takes an array of numbers of length 6 (2d matrix) or 16 (3d matrix) 1.887 +// representing a matrix specified in column-major order and returns a 3d matrix 1.888 +// represented as an array of arrays 1.889 +function convertArrayTo3dMatrix(array) { 1.890 + if (array.length == 6) { 1.891 + return convertObjectTo3dMatrix( 1.892 + { a: array[0], b: array[1], 1.893 + c: array[2], d: array[3], 1.894 + e: array[4], f: array[5] } ); 1.895 + } else if (array.length == 16) { 1.896 + return [ 1.897 + array.slice(0, 3), 1.898 + array.slice(4, 7), 1.899 + array.slice(8, 11), 1.900 + array.slice(12, 15) 1.901 + ]; 1.902 + } else { 1.903 + return null; 1.904 + } 1.905 +} 1.906 + 1.907 +// Takes an object of the form { a: 1.1, e: 23 } and builds up a 3d matrix 1.908 +// with unspecified values filled in with identity values. 1.909 +function convertObjectTo3dMatrix(obj) { 1.910 + return [ 1.911 + [ 1.912 + obj.a || obj.sx || obj.m11 || 1, 1.913 + obj.b || obj.m12 || 0, 1.914 + obj.m13 || 0, 1.915 + obj.m14 || 0 1.916 + ], [ 1.917 + obj.c || obj.m21 || 0, 1.918 + obj.d || obj.sy || obj.m22 || 1, 1.919 + obj.m23 || 0, 1.920 + obj.m24 || 0 1.921 + ], [ 1.922 + obj.m31 || 0, 1.923 + obj.m32 || 0, 1.924 + obj.sz || obj.m33 || 1, 1.925 + obj.m34 || 0 1.926 + ], [ 1.927 + obj.e || obj.tx || obj.m41 || 0, 1.928 + obj.f || obj.ty || obj.m42 || 0, 1.929 + obj.tz || obj.m43 || 0, 1.930 + obj.m44 || 1 1.931 + ] 1.932 + ]; 1.933 +} 1.934 + 1.935 +function convert3dMatrixToString(matrix) { 1.936 + if (is2d(matrix)) { 1.937 + return "matrix(" + 1.938 + [ matrix[0][0], matrix[0][1], 1.939 + matrix[1][0], matrix[1][1], 1.940 + matrix[3][0], matrix[3][1] ].join(", ") + ")"; 1.941 + } else { 1.942 + return "matrix3d(" + 1.943 + matrix.reduce(function(outer, inner) { 1.944 + return outer.concat(inner); 1.945 + }).join(", ") + ")"; 1.946 + } 1.947 +} 1.948 + 1.949 +function is2d(matrix) { 1.950 + return matrix[0][2] === 0 && matrix[0][3] === 0 && 1.951 + matrix[1][2] === 0 && matrix[1][3] === 0 && 1.952 + matrix[2][0] === 0 && matrix[2][1] === 0 && 1.953 + matrix[2][2] === 1 && matrix[2][3] === 0 && 1.954 + matrix[3][2] === 0 && matrix[3][3] === 1; 1.955 +} 1.956 +</script> 1.957 +</html>