layout/style/test/test_animations_omta.html

Thu, 15 Jan 2015 21:03:48 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 15 Jan 2015 21:03:48 +0100
branch
TOR_BUG_9701
changeset 11
deefc01c0e14
permissions
-rw-r--r--

Integrate friendly tips from Tor colleagues to make (or not) 4.5 alpha 3;
This includes removal of overloaded (but unused) methods, and addition of
a overlooked call to DataStruct::SetData(nsISupports, uint32_t, bool.)

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

mercurial