Wed, 31 Dec 2014 13:27:57 +0100
Ignore runtime configuration files generated during quality assurance.
1 <!DOCTYPE HTML>
2 <html>
3 <!--
4 https://bugzilla.mozilla.org/show_bug.cgi?id=964646
5 -->
6 <!--
8 ========= PLEASE KEEP THIS IN SYNC WITH test_animations.html =========
10 This test mimicks the content of test_animations.html but performs tests
11 specific to animations that run on the compositor thread since they require
12 special (asynchronous) handling. Furthermore, these tests check that
13 animations that are expected to run on the compositor thread, are actually
14 doing so.
16 If you are making changes to this file or to test_animations.html, please
17 try to keep them consistent where appropriate.
19 -->
20 <head>
21 <meta charset="utf-8">
22 <title>Test for css3-animations running on the compositor thread (Bug
23 964646)</title>
24 <script type="application/javascript"
25 src="/tests/SimpleTest/SimpleTest.js"></script>
26 <script type="application/javascript"
27 src="/tests/SimpleTest/paint_listener.js"></script>
28 <script type="application/javascript" src="animation_utils.js"></script>
29 <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
30 <style type="text/css">
31 @keyframes transform-anim {
32 to {
33 transform: translate(100px);
34 }
35 }
36 @keyframes anim1 {
37 0% { transform: translate(0px) }
38 50% { transform: translate(80px) }
39 100% { transform: translate(100px) }
40 }
41 @keyframes anim2 {
42 from { opacity: 0 } to { opacity: 1 }
43 }
44 @keyframes anim3 {
45 from { opacity: 0 } to { opacity: 1 }
46 }
48 @keyframes kf1 {
49 50% { transform: translate(50px) }
50 to { transform: translate(150px) }
51 }
52 @keyframes kf2 {
53 from { transform: translate(150px) }
54 50% { transform: translate(50px) }
55 }
56 @keyframes kf3 {
57 25% { transform: translate(100px) }
58 }
59 @keyframes kf4 {
60 to, from { display: inline; transform: translate(37px) }
61 }
62 @keyframes kf_cascade1 {
63 from { transform: translate(50px) }
64 50%, from { transform: translate(30px) } /* wins: 0% */
65 75%, 85%, 50% { transform: translate(20px) } /* wins: 75%, 50% */
66 100%, 85% { transform: translate(70px) } /* wins: 100% */
67 85.1% { transform: translate(60px) } /* wins: 85.1% */
68 85% { transform: translate(30px) } /* wins: 85% */
69 }
70 @keyframes kf_cascade2 { from, to { opacity: 0.3 } }
71 @keyframes kf_cascade2 { from, to { transform: translate(50px) } }
72 @keyframes kf_cascade2 { from, to { transform: translate(100px) } }
74 .target {
75 /* The animation target needs geometry in order to qualify for OMTA */
76 width: 100px;
77 height: 100px;
78 background-color: white;
79 }
80 </style>
81 </head>
82 <body>
83 <a target="_blank"
84 href="https://bugzilla.mozilla.org/show_bug.cgi?id=964646">Mozilla Bug
85 964646</a>
86 <div id="display"></div>
87 <pre id="test">
88 <script type="application/javascript">
89 "use strict";
91 /** Test for css3-animations running on the compositor thread (Bug 964646) **/
93 // Global state
94 var gAsyncTests = [],
95 gDisplay = document.getElementById("display"),
96 gDiv = null,
97 gEventsReceived = [];
99 SimpleTest.waitForExplicitFinish();
100 runOMTATest(function() {
101 // The async test runner returns a Promise that is resolved when the
102 // test is finished so we can chain them together
103 gAsyncTests.reduce(function(sequence, test) {
104 return sequence.then(function() { return runAsyncTest(test); });
105 }, Promise.resolve() /* the start of the sequence */)
106 // Final step in the sequence
107 .then(function() {
108 SimpleTest.finish();
109 });
110 }, SimpleTest.finish);
112 // Takes a generator function that represents a test case. Each point in the
113 // test case that waits asynchronously for some result yields a Promise that is
114 // resolved when the asychronous action has completed. By chaining these
115 // intermediate results together we run the test to completion.
116 //
117 // This method itself returns a Promise that is resolved when the generator
118 // function has completed.
119 //
120 // This arrangement is based on add_task() which is currently only available
121 // in mochitest-chrome (bug 872229). Once add_task is available in
122 // mochitest-plain we can remove this function and use add_task instead.
123 function runAsyncTest(test) {
124 var generator;
126 function step(arg) {
127 var next;
128 try {
129 next = generator.next(arg);
130 } catch (e) {
131 return Promise.reject(e);
132 }
133 if (next.done) {
134 return Promise.resolve(next.value);
135 } else {
136 return Promise.resolve(next.value)
137 .then(step, function(err) { throw err; });
138 }
139 }
141 // Put refresh driver under test control
142 advance_clock(0);
144 // Run test
145 generator = test();
146 return step()
147 .catch(function(err) {
148 ok(false, err.message);
149 // Clear up the test div in case we aborted the test before doing clean-up
150 if (gDiv) {
151 done_div();
152 }
153 }).then(function() {
154 // Restore clock
155 SpecialPowers.DOMWindowUtils.restoreNormalRefresh();
156 });
157 }
159 function addAsyncTest(generator) {
160 gAsyncTests.push(generator);
161 }
163 //----------------------------------------------------------------------
164 //
165 // Test cases
166 //
167 //----------------------------------------------------------------------
169 // This test is not in test_animations.html but is here to test that
170 // transform animations are actually run on the compositor thread as expected.
171 addAsyncTest(function *() {
172 new_div("animation: transform-anim linear 300s");
174 yield waitForPaints();
176 advance_clock(200000);
177 omta_is("transform", { tx: 100 * 2 / 3 }, RunningOn.Compositor,
178 "OMTA animation is animating as expected");
179 done_div();
180 });
182 function *testFillMode(fillMode, fillsBackwards, fillsForwards)
183 {
184 var style = "transform: translate(30px); animation: 10s 3s anim1 linear";
185 var desc;
186 if (fillMode.length > 0) {
187 style += " " + fillMode;
188 desc = "fill mode " + fillMode + ": ";
189 } else {
190 desc = "default fill mode: ";
191 }
192 new_div(style);
193 listen();
195 // Currently backwards fill is not performed on the compositor thread but we
196 // should wait for paints so we can test that transform values are *not* being
197 // set on the compositor thread.
198 yield waitForPaints();
200 if (fillsBackwards)
201 omta_is("transform", { tx: 0 }, RunningOn.MainThread,
202 desc + "does affect value during delay (0s)");
203 else
204 omta_is("transform", { tx: 30 }, RunningOn.MainThread,
205 desc + "doesn't affect value during delay (0s)");
207 advance_clock(2000);
208 if (fillsBackwards)
209 omta_is("transform", { tx: 0 }, RunningOn.MainThead,
210 desc + "does affect value during delay (0s)");
211 else
212 omta_is("transform", { tx: 30 }, RunningOn.MainThread,
213 desc + "does affect value during delay (0s)");
215 check_events([], "before start in testFillMode");
216 advance_clock(1000);
217 check_events([{ type: "animationstart", target: gDiv,
218 bubbles: true, cancelable: false,
219 animationName: "anim1", elapsedTime: 0.0,
220 pseudoElement: "" }],
221 "right after start in testFillMode");
223 // If we have a backwards fill then at the start of the animation we will end
224 // up applying the same value as the fill value. Various optimizations in
225 // RestyleManager may filter out this meaning that the animation doesn't get
226 // added to the compositor thread until the first time the value changes.
227 //
228 // As a result we look for this first sample on either the compositor or the
229 // computed style
230 yield waitForPaints();
231 omta_is("transform", { tx: 0 }, RunningOn.Either,
232 desc + "affects value at start of animation");
233 advance_clock(125);
234 // We might not add the animation to compositor until the second sample (due
235 // to the optimizations mentioned above) so we should wait for paints before
236 // proceeding
237 yield waitForPaints();
238 omta_is("transform", { tx: 2 }, RunningOn.Compositor,
239 desc + "affects value during animation");
240 advance_clock(2375);
241 omta_is("transform", { tx: 40 }, RunningOn.Compositor,
242 desc + "affects value during animation");
243 advance_clock(2500);
244 omta_is("transform", { tx: 80 }, RunningOn.Compositor,
245 desc + "affects value during animation");
246 advance_clock(2500);
247 omta_is("transform", { tx: 90 }, RunningOn.Compositor,
248 desc + "affects value during animation");
249 advance_clock(2375);
250 omta_is("transform", { tx: 99.5 }, RunningOn.Compositor,
251 desc + "affects value during animation");
252 check_events([], "before end in testFillMode");
253 advance_clock(125);
254 check_events([{ type: "animationend", target: gDiv,
255 bubbles: true, cancelable: false,
256 animationName: "anim1", elapsedTime: 10.0,
257 pseudoElement: "" }],
258 "right after end in testFillMode");
260 // Currently the compositor will apply a forwards fill until it gets told by
261 // the main thread to clear the animation. As a result we should wait for
262 // paints to be flushed before checking that the animated value does *not*
263 // appear on the compositor thread.
264 yield waitForPaints();
265 if (fillsForwards)
266 omta_is("transform", { tx: 100 }, RunningOn.MainThread,
267 desc + "affects value at end of animation");
268 advance_clock(10);
269 if (fillsForwards)
270 omta_is("transform", { tx: 100 }, RunningOn.MainThread,
271 desc + "affects value after animation");
272 else
273 omta_is("transform", { tx: 30 }, RunningOn.MainThread,
274 desc + "does not affect value after animation");
276 done_div();
277 }
279 addAsyncTest(function() { return testFillMode("", false, false); });
280 addAsyncTest(function() { return testFillMode("none", false, false); });
281 addAsyncTest(function() { return testFillMode("forwards", false, true); });
282 addAsyncTest(function() { return testFillMode("backwards", true, false); });
283 addAsyncTest(function() { return testFillMode("both", true, true); });
285 // Test that animations continue running when the animation name
286 // list is changed.
287 //
288 // test_animations.html combines all these tests into one block but this is
289 // difficult for OMTA because currently there are only two properties to which
290 // we apply OMTA. Instead we break the test down into a few independent pieces
291 // in order to exercise the same functionality.
293 // Append to list
294 addAsyncTest(function *() {
295 new_div("animation: anim1 linear 10s");
296 yield waitForPaints();
297 omta_is("transform", { tx: 0 }, RunningOn.Either,
298 "just anim1, translate at start");
299 advance_clock(1000);
300 omta_is("transform", { tx: 16 }, RunningOn.Compositor,
301 "just anim1, translate at 1s");
302 // append anim2
303 gDiv.style.animation = "anim1 linear 10s, anim2 linear 10s";
304 yield waitForPaintsFlushed();
305 omta_is("transform", { tx: 16 }, RunningOn.Compositor,
306 "anim1 + anim2, translate at 1s");
307 omta_is("opacity", 0, RunningOn.Compositor,
308 "anim1 + anim2, opacity at 1s");
309 advance_clock(1000);
310 omta_is("transform", { tx: 32 }, RunningOn.Compositor,
311 "anim1 + anim2, translate at 2s");
312 omta_is("opacity", 0.1, RunningOn.Compositor,
313 "anim1 + anim2, opacity at 2s");
314 done_div();
315 });
317 // Prepend to list; delete from list
318 addAsyncTest(function *() {
319 new_div("animation: anim1 linear 10s");
320 yield waitForPaints();
321 omta_is("transform", { tx: 0 }, RunningOn.Either,
322 "just anim1, translate at start");
323 advance_clock(1000);
324 omta_is("transform", { tx: 16 }, RunningOn.Compositor,
325 "just anim1, translate at 1s");
326 // prepend anim2
327 gDiv.style.animation = "anim2 linear 10s, anim1 linear 10s";
328 yield waitForPaintsFlushed();
329 omta_is("transform", { tx: 16 }, RunningOn.Compositor,
330 "anim2 + anim1, translate at 1s");
331 omta_is("opacity", 0, RunningOn.Compositor,
332 "anim2 + anim1, opacity at 1s");
333 advance_clock(1000);
334 omta_is("transform", { tx: 32 }, RunningOn.Compositor,
335 "anim2 + anim1, translate at 2s");
336 omta_is("opacity", 0.1, RunningOn.Compositor,
337 "anim2 + anim1, opacity at 2s");
338 // remove anim2 from list
339 gDiv.style.animation = "anim1 linear 10s";
340 yield waitForPaintsFlushed();
341 omta_is("transform", { tx: 32 }, RunningOn.Compositor,
342 "just anim1, translate at 2s");
343 omta_is("opacity", 1, RunningOn.MainThread, "just anim1, opacity at 2s");
344 advance_clock(1000);
345 omta_is("transform", { tx: 48 }, RunningOn.Compositor,
346 "just anim1, translate at 3s");
347 omta_is("opacity", 1, RunningOn.MainThread, "just anim1, opacity at 3s");
348 done_div();
349 });
351 // Swap elements
352 addAsyncTest(function *() {
353 new_div("animation: anim1 linear 10s, anim2 linear 10s");
354 yield waitForPaints();
355 omta_is("transform", { tx: 0 }, RunningOn.Either,
356 "anim1 + anim2, translate at start");
357 omta_is("opacity", 0, RunningOn.Compositor,
358 "anim1 + anim2, opacity at start");
359 advance_clock(1000);
360 omta_is("transform", { tx: 16 }, RunningOn.Compositor,
361 "anim1 + anim2, translate at 1s");
362 omta_is("opacity", 0.1, RunningOn.Compositor,
363 "anim1 + anim2, opacity at 1s");
364 // swap anim1 and anim2, change duration of anim2
365 gDiv.style.animation = "anim2 linear 5s, anim1 linear 10s";
366 yield waitForPaintsFlushed();
367 omta_is("transform", { tx: 16 }, RunningOn.Compositor,
368 "anim2 + anim1, translate at 1s");
369 omta_is("opacity", 0.2, RunningOn.Compositor,
370 "anim2 + anim1, opacity at 1s");
371 advance_clock(1000);
372 omta_is("transform", { tx: 32 }, RunningOn.Compositor,
373 "anim2 + anim1, translate at 2s");
374 omta_is("opacity", 0.4, RunningOn.Compositor,
375 "anim2 + anim1, opacity at 2s");
376 // list anim2 twice, last duration wins, original start time still applies
377 gDiv.style.animation = "anim2 linear 5s, anim1 linear 10s, anim2 linear 20s";
378 yield waitForPaintsFlushed();
379 omta_is("transform", { tx: 32 }, RunningOn.Compositor,
380 "anim2 + anim1 + anim2, translate at 2s");
381 // Bug 980769
382 todo_is(SpecialPowers.DOMWindowUtils.getOMTAStyle(gDiv, "opacity"), "0.1",
383 "anim2 + anim1 + anim2, opacity at 2s");
384 // drop one of the anim2, and list anim3 as well, which animates
385 // the same property as anim2
386 gDiv.style.animation = "anim1 linear 10s, anim2 linear 20s, anim3 linear 10s";
387 yield waitForPaintsFlushed();
388 omta_is("transform", { tx: 32 }, RunningOn.Compositor,
389 "anim1 + anim2 + anim3, translate at 2s");
390 // Bug 980769
391 todo_is(SpecialPowers.DOMWindowUtils.getOMTAStyle(gDiv, "opacity"), "0",
392 "anim1 + anim2 + anim3, opacity at 2s");
393 advance_clock(1000);
394 omta_is("transform", { tx: 48 }, RunningOn.Compositor,
395 "anim1 + anim2 + anim3, translate at 3s");
396 // Bug 980769
397 todo_is(SpecialPowers.DOMWindowUtils.getOMTAStyle(gDiv, "opacity"), "0.1",
398 "anim1 + anim2 + anim3, opacity at 3s");
399 // now swap the anim3 and anim2 order
400 gDiv.style.animation = "anim1 linear 10s, anim3 linear 10s, anim2 linear 20s";
401 yield waitForPaintsFlushed();
402 omta_is("transform", { tx: 48 }, RunningOn.Compositor,
403 "anim1 + anim3 + anim2, translate at 3s");
404 // Bug 980769
405 todo_is(SpecialPowers.DOMWindowUtils.getOMTAStyle(gDiv, "opacity"), "0.15",
406 "anim1 + anim3 + anim2, opacity at 3s");
407 advance_clock(2000); // (unlike test_animations.html, we seek 2s forwards here
408 // since at 4s anim2 and anim3 produce the same result so
409 // we can't tell which won.)
410 omta_is("transform", { tx: 80 }, RunningOn.Compositor,
411 "anim1 + anim3 + anim2, translate at 5s");
412 // Bug 980769
413 todo_is(SpecialPowers.DOMWindowUtils.getOMTAStyle(gDiv, "opacity"), "0.25",
414 "anim1 + anim3 + anim2, opacity at 5s");
415 // swap anim3 and anim2 back
416 gDiv.style.animation = "anim1 linear 10s, anim2 linear 20s, anim3 linear 10s";
417 yield waitForPaintsFlushed();
418 omta_is("transform", { tx: 80 }, RunningOn.Compositor,
419 "anim1 + anim2 + anim3, translate at 5s");
420 // Bug 980769
421 todo_is(SpecialPowers.DOMWindowUtils.getOMTAStyle(gDiv, "opacity"), "0.3",
422 "anim1 + anim2 + anim3, opacity at 5s");
423 // seek past end of anim1
424 advance_clock(5100);
425 yield waitForPaints();
426 omta_is("transform", { tx: 0 }, RunningOn.MainThread,
427 "anim1 + anim2 + anim3, translate at 10.1s");
428 // Change the animation fill mode on the completed animation.
429 gDiv.style.animation =
430 "anim1 linear 10s forwards, anim2 linear 20s, anim3 linear 10s";
431 yield waitForPaintsFlushed();
432 omta_is("transform", { tx: 100 }, RunningOn.MainThread,
433 "anim1 + anim2 + anim3, translate at 10.1s with fill mode");
434 advance_clock(900);
435 omta_is("transform", { tx: 100 }, RunningOn.MainThread,
436 "anim1 + anim2 + anim3, translate at 11s with fill mode");
437 // Change the animation duration on the completed animation, so it is
438 // no longer completed.
439 // XXX Not sure about this---there seems to be a bug in test_animations.html
440 // in that it drops the fill mode but the test comment says it has a fill mode
441 gDiv.style.animation = "anim1 linear 20s, anim2 linear 20s, anim3 linear 10s";
442 yield waitForPaintsFlushed();
443 omta_is("transform", { tx: 82 }, RunningOn.Compositor,
444 "anim1 + anim2 + anim3, translate at 11s with fill mode");
445 // Bug 980769 - We should get 0.9 but instead
446 todo_is(SpecialPowers.DOMWindowUtils.getOMTAStyle(gDiv, "opacity"), "0.9",
447 "anim1 + anim2 + anim3, opacity at 11s");
448 done_div();
449 });
451 /*
452 * css3-animations: 3. Keyframes
453 * http://dev.w3.org/csswg/css3-animations/#keyframes
454 */
456 // Test the rules on keyframes that lack a 0% or 100% rule:
457 // (simultaneously, test that reverse animations have their keyframes
458 // run backwards)
460 addAsyncTest(function *() {
461 // 100px at 0%, 50px at 50%, 150px at 100%
462 new_div("transform: translate(100px); " +
463 "animation: kf1 ease 1s alternate infinite");
464 advance_clock(0);
465 yield waitForPaints();
466 omta_is("transform", { tx: 100 }, RunningOn.Compositor, "no-0% at 0.0s");
467 advance_clock(100);
468 omta_is_approx("transform", { tx: 100 - 50 * gTF.ease(0.2) },
469 RunningOn.Compositor, 0.01, "no-0% at 0.1s");
470 advance_clock(200);
471 omta_is_approx("transform", { tx: 100 - 50 * gTF.ease(0.6) },
472 RunningOn.Compositor, 0.01, "no-0% at 0.3s");
473 advance_clock(200);
474 omta_is("transform", { tx: 50 }, RunningOn.Compositor, "no-0% at 0.5s");
475 advance_clock(200);
476 omta_is_approx("transform", { tx: 50 + 100 * gTF.ease(0.4) },
477 RunningOn.Compositor, 0.01, "no-0% at 0.7s");
478 advance_clock(200);
479 omta_is_approx("transform", { tx: 50 + 100 * gTF.ease(0.8) },
480 RunningOn.Compositor, 0.01, "no-0% at 0.9s");
481 advance_clock(100);
482 omta_is("transform", { tx: 150 }, RunningOn.Compositor, "no-0% at 1.0s");
483 advance_clock(100);
484 omta_is_approx("transform", { tx: 50 + 100 * gTF.ease(0.8) },
485 RunningOn.Compositor, 0.01, "no-0% at 1.1s");
486 advance_clock(300);
487 omta_is_approx("transform", { tx: 50 + 100 * gTF.ease(0.2) },
488 RunningOn.Compositor, 0.01, "no-0% at 1.4s");
489 advance_clock(300);
490 omta_is_approx("transform", { tx: 100 - 50 * gTF.ease(0.6) },
491 RunningOn.Compositor, 0.01, "no-0% at 1.7s");
492 advance_clock(200);
493 omta_is_approx("transform", { tx: 100 - 50 * gTF.ease(0.2) },
494 RunningOn.Compositor, 0.01, "no-0% at 1.9s");
495 advance_clock(100);
496 omta_is("transform", { tx: 100 }, RunningOn.Compositor, "no-0% at 2.0s");
497 done_div();
499 // 150px at 0%, 50px at 50%, 100px at 100%
500 new_div("transform: translate(100px); " +
501 "animation: kf2 ease-in 1s alternate infinite");
502 yield waitForPaints();
503 omta_is("transform", { tx: 150 }, RunningOn.Compositor, "no-100% at 0.0s");
504 advance_clock(100);
505 omta_is_approx("transform", { tx: 150 - 100 * gTF.ease_in(0.2) },
506 RunningOn.Compositor, 0.01, "no-100% at 0.1s");
507 advance_clock(200);
508 omta_is_approx("transform", { tx: 150 - 100 * gTF.ease_in(0.6) },
509 RunningOn.Compositor, 0.01, "no-100% at 0.3s");
510 advance_clock(200);
511 omta_is("transform", { tx: 50 }, RunningOn.Compositor, "no-100% at 0.5s");
512 advance_clock(200);
513 omta_is_approx("transform", { tx: 50 + 50 * gTF.ease_in(0.4) },
514 RunningOn.Compositor, 0.01, "no-100% at 0.7s");
515 advance_clock(200);
516 omta_is_approx("transform", { tx: 50 + 50 * gTF.ease_in(0.8) },
517 RunningOn.Compositor, 0.01, "no-100% at 0.9s");
518 advance_clock(100);
519 omta_is("transform", { tx: 100 }, RunningOn.Compositor, "no-100% at 1.0s");
520 advance_clock(100);
521 omta_is_approx("transform", { tx: 50 + 50 * gTF.ease_in(0.8) },
522 RunningOn.Compositor, 0.01, "no-100% at 1.1s");
523 advance_clock(300);
524 omta_is_approx("transform", { tx: 50 + 50 * gTF.ease_in(0.2) },
525 RunningOn.Compositor, 0.01, "no-100% at 1.4s");
526 advance_clock(300);
527 omta_is_approx("transform", { tx: 150 - 100 * gTF.ease_in(0.6) },
528 RunningOn.Compositor, 0.01, "no-100% at 1.7s");
529 advance_clock(200);
530 omta_is_approx("transform", { tx: 150 - 100 * gTF.ease_in(0.2) },
531 RunningOn.Compositor, 0.01, "no-100% at 1.9s");
532 advance_clock(100);
533 omta_is("transform", { tx: 150 }, RunningOn.Compositor, "no-100% at 2.0s");
534 done_div();
536 // 50px at 0%, 100px at 25%, 50px at 100%
537 new_div("transform: translate(50px); " +
538 "animation: kf3 ease-out 1s alternate infinite");
539 yield waitForPaints();
540 omta_is("transform", { tx: 50 }, RunningOn.Compositor,
541 "no-0%-no-100% at 0.0s");
542 advance_clock(50);
543 omta_is_approx("transform", { tx: 50 + 50 * gTF.ease_out(0.2) },
544 RunningOn.Compositor, 0.01, "no-0%-no-100% at 0.05s");
545 advance_clock(100);
546 omta_is_approx("transform", { tx: 50 + 50 * gTF.ease_out(0.6) },
547 RunningOn.Compositor, 0.01, "no-0%-no-100% at 0.15s");
548 advance_clock(100);
549 omta_is("transform", { tx: "100px" }, RunningOn.Compositor,
550 "no-0%-no-100% at 0.25s");
551 advance_clock(300);
552 omta_is_approx("transform", { tx: 100 - 50 * gTF.ease_out(0.4) },
553 RunningOn.Compositor, 0.01, "no-0%-no-100% at 0.55s");
554 advance_clock(300);
555 omta_is_approx("transform", { tx: 100 - 50 * gTF.ease_out(0.8) },
556 RunningOn.Compositor, 0.01, "no-0%-no-100% at 0.85s");
557 advance_clock(150);
558 omta_is("transform", { tx: 50 }, RunningOn.Compositor,
559 "no-0%-no-100% at 1.0s");
560 advance_clock(150);
561 omta_is_approx("transform", { tx: 100 - 50 * gTF.ease_out(0.8) },
562 RunningOn.Compositor, 0.01, "no-0%-no-100% at 1.15s");
563 advance_clock(450);
564 omta_is_approx("transform", { tx: 100 - 50 * gTF.ease_out(0.2) },
565 RunningOn.Compositor, 0.01, "no-0%-no-100% at 1.6s");
566 advance_clock(250);
567 omta_is_approx("transform", { tx: 50 + 50 * gTF.ease_out(0.6) },
568 RunningOn.Compositor, 0.01, "no-0%-no-100% at 1.85s");
569 advance_clock(100);
570 omta_is_approx("transform", { tx: 50 + 50 * gTF.ease_out(0.2) },
571 RunningOn.Compositor, 0.01, "no-0%-no-100% at 1.95s");
572 advance_clock(50);
573 omta_is("transform", { tx: 50 }, RunningOn.Compositor,
574 "no-0%-no-100% at 2.0s");
575 done_div();
577 // Test that non-animatable properties are ignored.
578 // Simultaneously, test that the block is still honored, and that
579 // we still override the value when two consecutive keyframes have
580 // the same value.
581 new_div("animation: kf4 ease 10s");
582 yield waitForPaints();
583 var cs = window.getComputedStyle(gDiv);
584 is(cs.display, "block",
585 "non-animatable properties should be ignored (linear, 0s)");
586 omta_is("transform", { tx: 37 }, RunningOn.Compositor,
587 "animatable properties should still apply (linear, 0s)");
588 advance_clock(1000);
589 is(cs.display, "block",
590 "non-animatable properties should be ignored (linear, 1s)");
591 omta_is("transform", { tx: 37 }, RunningOn.Compositor,
592 "animatable properties should still apply (linear, 1s)");
593 done_div();
594 new_div("animation: kf4 step-start 10s");
595 yield waitForPaints();
596 cs = window.getComputedStyle(gDiv);
597 is(cs.display, "block",
598 "non-animatable properties should be ignored (step-start, 0s)");
599 omta_is("transform", { tx: 37 }, RunningOn.Compositor,
600 "animatable properties should still apply (step-start, 0s)");
601 advance_clock(1000);
602 is(cs.display, "block",
603 "non-animatable properties should be ignored (step-start, 1s)");
604 omta_is("transform", { tx: 37 }, RunningOn.Compositor,
605 "animatable properties should still apply (step-start, 1s)");
606 done_div();
608 // Test cascading of the keyframes within an @keyframes rule.
609 new_div("animation: kf_cascade1 linear 10s");
610 yield waitForPaints();
611 // 0%: 30px
612 // 50%: 20px
613 // 75%: 20px
614 // 85%: 30px
615 // 85.1%: 60px
616 // 100%: 70px
617 omta_is("transform", { tx: 30 }, RunningOn.Compositor, "kf_cascade1 at 0s");
618 advance_clock(2500);
619 omta_is("transform", { tx: 25 }, RunningOn.Compositor, "kf_cascade1 at 2.5s");
620 advance_clock(2500);
621 omta_is("transform", { tx: 20 }, RunningOn.Compositor, "kf_cascade1 at 5s");
622 advance_clock(2000);
623 omta_is("transform", { tx: 20 }, RunningOn.Compositor, "kf_cascade1 at 7s");
624 advance_clock(500);
625 omta_is("transform", { tx: 20 }, RunningOn.Compositor, "kf_cascade1 at 7.5s");
626 advance_clock(500);
627 omta_is("transform", { tx: 25 }, RunningOn.Compositor, "kf_cascade1 at 8s");
628 advance_clock(500);
629 omta_is("transform", { tx: 30 }, RunningOn.Compositor, "kf_cascade1 at 8.5s");
630 advance_clock(10);
631 // For some reason we get an error of 0.0003 for this test only
632 omta_is_approx("transform", { tx: 60 }, RunningOn.Compositor, 0.001,
633 "kf_cascade1 at 8.51s");
634 advance_clock(745);
635 omta_is("transform", { tx: 65 }, RunningOn.Compositor,
636 "kf_cascade1 at 9.2505s");
637 done_div();
639 // Test cascading of the @keyframes rules themselves.
640 new_div("animation: kf_cascade2 linear 10s");
641 yield waitForPaints();
642 omta_is("opacity", 1, RunningOn.MainThread,
643 "last @keyframes rule with transform should win");
644 omta_is("transform", { tx: 100 }, RunningOn.Compositor,
645 "last @keyframes rule with transform should win");
646 done_div();
647 });
649 //----------------------------------------------------------------------
650 //
651 // Helper functions from test_animations.html
652 //
653 //----------------------------------------------------------------------
655 function new_div(style) {
656 if (gDiv !== null) {
657 ok(false, "test author forgot to call done_div");
658 }
659 if (typeof(style) != "string") {
660 ok(false, "test author forgot to pass style argument");
661 }
662 gDiv = document.createElement("div");
663 gDiv.classList.add("target");
664 gDiv.setAttribute("style", style);
665 gDisplay.appendChild(gDiv);
666 gDiv.clientTop;
667 }
669 function done_div() {
670 if (gDiv === null) {
671 ok(false, "test author forgot to call new_div");
672 }
673 gDisplay.removeChild(gDiv);
674 gDiv = null;
675 }
677 function listen() {
678 gEventsReceived = [];
679 function listener(event) {
680 gEventsReceived.push(event);
681 }
682 gDiv.addEventListener("animationstart", listener, false);
683 gDiv.addEventListener("animationiteration", listener, false);
684 gDiv.addEventListener("animationend", listener, false);
685 }
687 function check_events(events_expected, desc) {
688 // This function checks that the list of events_expected matches
689 // the received events -- but it only checks the properties that
690 // are present on events_expected.
691 is(gEventsReceived.length, events_expected.length,
692 "number of events received for " + desc);
693 for (var i = 0,
694 i_end = Math.min(events_expected.length, gEventsReceived.length);
695 i != i_end; ++i) {
696 var exp = events_expected[i];
697 var rec = gEventsReceived[i];
698 for (var prop in exp) {
699 if (prop == "elapsedTime") {
700 // Allow floating point error.
701 ok(Math.abs(rec.elapsedTime - exp.elapsedTime) < 0.000002,
702 "events[" + i + "]." + prop + " for " + desc +
703 " received=" + rec.elapsedTime + " expected=" + exp.elapsedTime);
704 } else {
705 is(rec[prop], exp[prop], "events[" + i + "]." + prop + " for " + desc);
706 }
707 }
708 }
709 for (i = events_expected.length; i < gEventsReceived.length; ++i) {
710 ok(false, "unexpected " + gEventsReceived[i].type + " event for " + desc);
711 }
712 gEventsReceived = [];
713 }
715 function advance_clock(milliseconds) {
716 SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(milliseconds);
717 }
719 //----------------------------------------------------------------------
720 //
721 // Helper functions for querying the compositor thread
722 //
723 //----------------------------------------------------------------------
725 // Returns a Promise that resolves once all paints have completed
726 function waitForPaints() {
727 return new Promise(function(resolve, reject) {
728 waitForAllPaints(resolve);
729 });
730 }
732 // As with waitForPaints but also flushes pending style changes before waiting
733 function waitForPaintsFlushed() {
734 return new Promise(function(resolve, reject) {
735 waitForAllPaintsFlushed(resolve);
736 });
737 }
739 //----------------------------------------------------------------------
740 //
741 // Helper functions for working with animated values
742 //
743 //----------------------------------------------------------------------
745 const RunningOn = {
746 MainThread: 0,
747 Compositor: 1,
748 Either: 2
749 };
751 function omta_is(property, expected, runningOn, desc) {
752 return omta_is_approx(property, expected, runningOn, 0, desc);
753 }
755 function omta_is_approx(property, expected, runningOn, tolerance, desc) {
756 // Check input
757 const omtaProperties = [ "transform", "opacity" ];
758 if (omtaProperties.indexOf(property) === -1) {
759 ok(false, property + " is not an OMTA property");
760 return;
761 }
762 var isTransform = property == "transform";
763 var normalize = isTransform ? convertTo3dMatrix : parseFloat;
764 var compare = isTransform ?
765 matricesRoughlyEqual :
766 function(a, b, error) { return Math.abs(a - b) <= error; };
767 var normalizedToString = isTransform ?
768 convert3dMatrixToString :
769 JSON.stringify;
771 // Get actual values
772 var compositorStr = SpecialPowers.DOMWindowUtils.getOMTAStyle(gDiv, property);
773 var computedStr = window.getComputedStyle(gDiv)[property];
775 // Prepare expected value
776 var expectedValue = normalize(expected);
777 if (expectedValue === null) {
778 ok(false, desc + ": test author should provide a valid 'expected' value" +
779 " - got " + expected.toString());
780 return;
781 }
783 // Check expected value appears in the right place
784 var actualStr;
785 switch (runningOn) {
786 case RunningOn.Either:
787 runningOn = compositorStr !== "" ?
788 RunningOn.Compositor :
789 RunningOn.MainThread;
790 actualStr = compositorStr !== "" ? compositorStr : computedStr;
791 break;
793 case RunningOn.Compositor:
794 if (compositorStr === "") {
795 ok(false, desc + ": should be animating on compositor");
796 return;
797 }
798 actualStr = compositorStr;
799 break;
801 default:
802 if (compositorStr !== "") {
803 ok(false, desc + ": should NOT be animating on compositor");
804 return;
805 }
806 actualStr = computedStr;
807 break;
808 }
810 // Compare animated value with expected
811 var actualValue = normalize(actualStr);
812 if (actualValue === null) {
813 ok(false, desc + ": should return a valid result - got " + actualStr);
814 return;
815 }
816 ok(compare(expectedValue, actualValue, tolerance),
817 desc + " - got " + actualStr + ", expected " +
818 normalizedToString(expectedValue));
820 // For compositor animations do an additional check that they match
821 // the value calculated on the main thread
822 if (runningOn === RunningOn.Compositor) {
823 var computedValue = normalize(computedStr);
824 if (computedValue === null) {
825 ok(false, desc + ": test framework should parse computed style" +
826 " - got " + computedStr);
827 return;
828 }
829 ok(compare(computedValue, actualValue, 0),
830 desc + ": OMTA style and computed style should be equal" +
831 " - OMTA " + actualStr + ", computed " + computedStr);
832 }
833 }
835 function matricesRoughlyEqual(a, b, tolerance) {
836 tolerance = tolerance || 0.0001;
837 for (var i = 0; i < 4; i++) {
838 for (var j = 0; j < 4; j++) {
839 if (Math.abs(a[i][j] - b[i][j]) > tolerance)
840 return false;
841 }
842 }
843 return true;
844 }
846 // Converts something representing an transform into a 3d matrix in column-major
847 // order.
848 // The following are supported:
849 // "matrix(...)"
850 // "matrix3d(...)"
851 // [ 1, 0, 0, ... ]
852 // { a: 1, ty: 23 } etc.
853 function convertTo3dMatrix(matrixLike) {
854 if (typeof(matrixLike) == "string") {
855 return convertStringTo3dMatrix(matrixLike);
856 } else if (Array.isArray(matrixLike)) {
857 return convertArrayTo3dMatrix(matrixLike);
858 } else if (typeof(matrixLike) == "object") {
859 return convertObjectTo3dMatrix(matrixLike);
860 } else {
861 return null;
862 }
863 }
865 // Converts strings of the format "matrix(...)" and "matrix3d(...)" to a 3d
866 // matrix
867 function convertStringTo3dMatrix(str) {
868 if (str == "none")
869 return convertArrayTo3dMatrix([1, 0, 0, 1, 0, 0]);
870 var result = str.match("^matrix(3d)?\\(");
871 if (result === null)
872 return null;
874 return convertArrayTo3dMatrix(
875 str.substring(result[0].length, str.length-1)
876 .split(",")
877 .map(function(component) {
878 return Number(component);
879 })
880 );
881 }
883 // Takes an array of numbers of length 6 (2d matrix) or 16 (3d matrix)
884 // representing a matrix specified in column-major order and returns a 3d matrix
885 // represented as an array of arrays
886 function convertArrayTo3dMatrix(array) {
887 if (array.length == 6) {
888 return convertObjectTo3dMatrix(
889 { a: array[0], b: array[1],
890 c: array[2], d: array[3],
891 e: array[4], f: array[5] } );
892 } else if (array.length == 16) {
893 return [
894 array.slice(0, 3),
895 array.slice(4, 7),
896 array.slice(8, 11),
897 array.slice(12, 15)
898 ];
899 } else {
900 return null;
901 }
902 }
904 // Takes an object of the form { a: 1.1, e: 23 } and builds up a 3d matrix
905 // with unspecified values filled in with identity values.
906 function convertObjectTo3dMatrix(obj) {
907 return [
908 [
909 obj.a || obj.sx || obj.m11 || 1,
910 obj.b || obj.m12 || 0,
911 obj.m13 || 0,
912 obj.m14 || 0
913 ], [
914 obj.c || obj.m21 || 0,
915 obj.d || obj.sy || obj.m22 || 1,
916 obj.m23 || 0,
917 obj.m24 || 0
918 ], [
919 obj.m31 || 0,
920 obj.m32 || 0,
921 obj.sz || obj.m33 || 1,
922 obj.m34 || 0
923 ], [
924 obj.e || obj.tx || obj.m41 || 0,
925 obj.f || obj.ty || obj.m42 || 0,
926 obj.tz || obj.m43 || 0,
927 obj.m44 || 1
928 ]
929 ];
930 }
932 function convert3dMatrixToString(matrix) {
933 if (is2d(matrix)) {
934 return "matrix(" +
935 [ matrix[0][0], matrix[0][1],
936 matrix[1][0], matrix[1][1],
937 matrix[3][0], matrix[3][1] ].join(", ") + ")";
938 } else {
939 return "matrix3d(" +
940 matrix.reduce(function(outer, inner) {
941 return outer.concat(inner);
942 }).join(", ") + ")";
943 }
944 }
946 function is2d(matrix) {
947 return matrix[0][2] === 0 && matrix[0][3] === 0 &&
948 matrix[1][2] === 0 && matrix[1][3] === 0 &&
949 matrix[2][0] === 0 && matrix[2][1] === 0 &&
950 matrix[2][2] === 1 && matrix[2][3] === 0 &&
951 matrix[3][2] === 0 && matrix[3][3] === 1;
952 }
953 </script>
954 </html>