diff options
-rw-r--r-- | dom/animation/test/css-animations/file_event-dispatch.html | 252 | ||||
-rw-r--r-- | dom/animation/test/css-animations/file_event-order.html | 160 | ||||
-rw-r--r-- | dom/animation/test/css-animations/test_event-dispatch.html | 15 | ||||
-rw-r--r-- | dom/animation/test/css-animations/test_event-order.html | 15 | ||||
-rw-r--r-- | dom/animation/test/mochitest.ini | 4 | ||||
-rw-r--r-- | dom/events/test/test_legacy_event.html | 21 | ||||
-rw-r--r-- | layout/reftests/transform-3d/animate-backface-hidden.html | 10 | ||||
-rw-r--r-- | layout/reftests/transform-3d/animate-preserve3d-parent.html | 10 | ||||
-rw-r--r-- | layout/style/test/test_animations.html | 3 | ||||
-rw-r--r-- | layout/style/test/test_animations_omta.html | 3 |
10 files changed, 469 insertions, 24 deletions
diff --git a/dom/animation/test/css-animations/file_event-dispatch.html b/dom/animation/test/css-animations/file_event-dispatch.html new file mode 100644 index 0000000000..266205bc32 --- /dev/null +++ b/dom/animation/test/css-animations/file_event-dispatch.html @@ -0,0 +1,252 @@ +<!doctype html> +<meta charset=utf-8> +<title>Tests for CSS animation event dispatch</title> +<link rel="help" href="https://drafts.csswg.org/css-animations-2/#event-dispatch"/> +<script src="../testcommon.js"></script> +<style> + @keyframes anim { + from { margin-left: 0px; } + to { margin-left: 100px; } + } +</style> +<body> +<script> +'use strict'; + +/** + * Helper class to record the elapsedTime member of each event. + * The EventWatcher class in testharness.js allows us to wait on + * multiple events in a certain order but only records the event + * parameters of the most recent event. + */ +function AnimationEventHandler(target) { + this.target = target; + this.target.onanimationstart = function(evt) { + this.animationstart = evt.elapsedTime; + }.bind(this); + this.target.onanimationiteration = function(evt) { + this.animationiteration = evt.elapsedTime; + }.bind(this); + this.target.onanimationend = function(evt) { + this.animationend = evt.elapsedTime; + }.bind(this); +} +AnimationEventHandler.prototype.clear = function() { + this.animationstart = undefined; + this.animationiteration = undefined; + this.animationend = undefined; +} + +function setupAnimation(t, animationStyle) { + var div = addDiv(t, { style: "animation: " + animationStyle }); + var watcher = new EventWatcher(t, div, [ 'animationstart', + 'animationiteration', + 'animationend' ]); + var handler = new AnimationEventHandler(div); + var animation = div.getAnimations()[0]; + + return [animation, watcher, handler, div]; +} + +promise_test(function(t) { + // Add 1ms delay to ensure that the delay is not included in the elapsedTime. + const [animation, watcher] = setupAnimation(t, 'anim 100s 1ms'); + + return watcher.wait_for('animationstart').then(function(evt) { + assert_equals(evt.elapsedTime, 0.0); + }); +}, 'Idle -> Active'); + +promise_test(function(t) { + const [animation, watcher, handler] = setupAnimation(t, 'anim 100s'); + + // Seek to After phase. + animation.finish(); + return watcher.wait_for([ 'animationstart', + 'animationend' ]).then(function() { + assert_equals(handler.animationstart, 0.0); + assert_equals(handler.animationend, 100); + }); +}, 'Idle -> After'); + +promise_test(function(t) { + const [animation, watcher, handler] = + setupAnimation(t, 'anim 100s 100s paused'); + + return animation.ready.then(function() { + // Seek to Active phase. + animation.currentTime = 100 * MS_PER_SEC; + return watcher.wait_for('animationstart'); + }).then(function(evt) { + assert_equals(evt.elapsedTime, 0.0); + }); +}, 'Before -> Active'); + +promise_test(function(t) { + const [animation, watcher, handler] = + setupAnimation(t, 'anim 100s 100s paused'); + + return animation.ready.then(function() { + // Seek to After phase. + animation.finish(); + return watcher.wait_for([ 'animationstart', 'animationend' ]); + }).then(function(evt) { + assert_equals(handler.animationstart, 0.0); + assert_equals(handler.animationend, 100.0); + }); +}, 'Before -> After'); + +promise_test(function(t) { + const [animation, watcher, handler] = + setupAnimation(t, 'anim 100s 100s paused'); + + // Seek to Active phase. + animation.currentTime = 100 * MS_PER_SEC; + return watcher.wait_for('animationstart').then(function() { + // Seek to Before phase. + animation.currentTime = 0; + return watcher.wait_for('animationend'); + }).then(function(evt) { + assert_equals(evt.elapsedTime, 0.0); + }); +}, 'Active -> Before'); + +promise_test(function(t) { + const [animation, watcher, handler] = setupAnimation(t, 'anim 100s paused'); + + return watcher.wait_for('animationstart').then(function(evt) { + // Seek to After phase. + animation.finish(); + return watcher.wait_for('animationend'); + }).then(function(evt) { + assert_equals(evt.elapsedTime, 100.0); + }); +}, 'Active -> After'); + +promise_test(function(t) { + const [animation, watcher, handler] = + setupAnimation(t, 'anim 100s 100s paused'); + + // Seek to After phase. + animation.finish(); + return watcher.wait_for([ 'animationstart', + 'animationend' ]).then(function() { + // Seek to Before phase. + animation.currentTime = 0; + handler.clear(); + return watcher.wait_for([ 'animationstart', 'animationend' ]); + }).then(function() { + assert_equals(handler.animationstart, 100.0); + assert_equals(handler.animationend, 0.0); + }); +}, 'After -> Before'); + +promise_test(function(t) { + const [animation, watcher, handler] = + setupAnimation(t, 'anim 100s 100s paused'); + + // Seek to After phase. + animation.finish(); + return watcher.wait_for([ 'animationstart', + 'animationend' ]).then(function() { + // Seek to Active phase. + animation.currentTime = 100 * MS_PER_SEC; + handler.clear(); + return watcher.wait_for('animationstart'); + }).then(function(evt) { + assert_equals(evt.elapsedTime, 100.0); + }); +}, 'After -> Active'); + +promise_test(function(t) { + const [animation, watcher, handler] + = setupAnimation(t, 'anim 100s 100s 3 paused'); + + return animation.ready.then(function() { + // Seek to iteration 0 (no animationiteration event should be dispatched) + animation.currentTime = 100 * MS_PER_SEC; + return watcher.wait_for('animationstart'); + }).then(function(evt) { + // Seek to iteration 2 + animation.currentTime = 300 * MS_PER_SEC; + handler.clear(); + return watcher.wait_for('animationiteration'); + }).then(function(evt) { + assert_equals(evt.elapsedTime, 200); + // Seek to After phase (no animationiteration event should be dispatched) + animation.currentTime = 400 * MS_PER_SEC; + return watcher.wait_for('animationend'); + }).then(function(evt) { + assert_equals(evt.elapsedTime, 300); + }); +}, 'Active -> Active (forwards)'); + +promise_test(function(t) { + const [animation, watcher, handler] = setupAnimation(t, 'anim 100s 100s 3'); + + // Seek to After phase. + animation.finish(); + return watcher.wait_for([ 'animationstart', + 'animationend' ]).then(function() { + // Seek to iteration 2 (no animationiteration event should be dispatched) + animation.pause(); + animation.currentTime = 300 * MS_PER_SEC; + return watcher.wait_for('animationstart'); + }).then(function() { + // Seek to mid of iteration 0 phase. + animation.currentTime = 200 * MS_PER_SEC; + return watcher.wait_for('animationiteration'); + }).then(function(evt) { + assert_equals(evt.elapsedTime, 200.0); + // Seek to before phase (no animationiteration event should be dispatched) + animation.currentTime = 0; + return watcher.wait_for('animationend'); + }); +}, 'Active -> Active (backwards)'); + +promise_test(function(t) { + const [animation, watcher, handler, div] = + setupAnimation(t, 'anim 100s paused'); + return watcher.wait_for('animationstart').then(function(evt) { + // Seek to Idle phase. + div.style.display = 'none'; + flushComputedStyle(div); + + // FIXME: bug 1302648: Add test for animationcancel event here. + + // Restart this animation. + div.style.display = ''; + return watcher.wait_for('animationstart'); + }); +}, 'Active -> Idle -> Active: animationstart is fired by restarting animation'); + +promise_test(function(t) { + const [animation, watcher, handler, div] = + setupAnimation(t, 'anim 100s 100s 2 paused'); + + // Make After. + animation.finish(); + return watcher.wait_for([ 'animationstart', + 'animationend' ]).then(function(evt) { + animation.playbackRate = -1; + return watcher.wait_for('animationstart'); + }).then(function(evt) { + assert_equals(evt.elapsedTime, 200); + // Seek to 1st iteration + animation.currentTime = 200 * MS_PER_SEC - 1; + return watcher.wait_for('animationiteration'); + }).then(function(evt) { + assert_equals(evt.elapsedTime, 100); + // Seek to before + animation.currentTime = 100 * MS_PER_SEC - 1; + return watcher.wait_for('animationend'); + }).then(function(evt) { + assert_equals(evt.elapsedTime, 0); + assert_equals(animation.playState, 'running'); // delay + }); +}, 'Negative playbackRate sanity test(Before -> Active -> Before)'); + +done(); +</script> +</body> +</html> diff --git a/dom/animation/test/css-animations/file_event-order.html b/dom/animation/test/css-animations/file_event-order.html new file mode 100644 index 0000000000..da78b6541c --- /dev/null +++ b/dom/animation/test/css-animations/file_event-order.html @@ -0,0 +1,160 @@ +<!doctype html> +<meta charset=utf-8> +<title>Tests for CSS animation event order</title> +<link rel="help" href="https://drafts.csswg.org/css-animations-2/#event-dispatch"/> +<script src="../testcommon.js"></script> +<style> + @keyframes anim { + from { margin-left: 0px; } + to { margin-left: 100px; } + } +</style> +<body> +<script type='text/javascript'> +'use strict'; + +/** + * Asserts that the set of actual and received events match. + * @param actualEvents An array of the received AnimationEvent objects. + * @param expectedEvents A series of array objects representing the expected + * events, each having the form: + * [ event type, target element, elapsed time ] + */ +function checkEvents(actualEvents, ...expectedEvents) { + assert_equals(actualEvents.length, expectedEvents.length, + `Number of actual events (${actualEvents.length}: \ +${actualEvents.map(event => event.type).join(', ')}) should match expected \ +events (${expectedEvents.map(event => event.type).join(', ')})`); + + actualEvents.forEach((actualEvent, i) => { + assert_equals(expectedEvents[i][0], actualEvent.type, + 'Event type should match'); + assert_equals(expectedEvents[i][1], actualEvent.target, + 'Event target should match'); + assert_equals(expectedEvents[i][2], actualEvent.elapsedTime, + 'Event\'s elapsed time should match'); + }); +} + +function setupAnimation(t, animationStyle, receiveEvents) { + const div = addDiv(t, { style: "animation: " + animationStyle }); + const watcher = new EventWatcher(t, div, [ 'animationstart', + 'animationiteration', + 'animationend' ]); + + ['start', 'iteration', 'end'].forEach(name => { + div['onanimation' + name] = function(evt) { + receiveEvents.push({ type: evt.type, + target: evt.target, + elapsedTime: evt.elapsedTime }); + }.bind(this); + }); + + const animation = div.getAnimations()[0]; + + return [animation, watcher, div]; +} + +promise_test(function(t) { + let events = []; + const [animation1, watcher1, div1] = + setupAnimation(t, 'anim 100s 2 paused', events); + const [animation2, watcher2, div2] = + setupAnimation(t, 'anim 100s 2 paused', events); + + return Promise.all([ watcher1.wait_for('animationstart'), + watcher2.wait_for('animationstart') ]).then(function() { + checkEvents(events, ['animationstart', div1, 0], + ['animationstart', div2, 0]); + + events.length = 0; // Clear received event array + + animation1.currentTime = 100 * MS_PER_SEC; + animation2.currentTime = 100 * MS_PER_SEC; + return Promise.all([ watcher1.wait_for('animationiteration'), + watcher2.wait_for('animationiteration') ]); + }).then(function() { + checkEvents(events, ['animationiteration', div1, 100], + ['animationiteration', div2, 100]); + + events.length = 0; // Clear received event array + + animation1.finish(); + animation2.finish(); + + return Promise.all([ watcher1.wait_for('animationend'), + watcher2.wait_for('animationend') ]); + }).then(function() { + checkEvents(events, ['animationend', div1, 200], + ['animationend', div2, 200]); + }); +}, 'Test same events are ordered by elements.'); + +promise_test(function(t) { + let events = []; + const [animation1, watcher1, div1] = + setupAnimation(t, 'anim 200s 400s', events); + const [animation2, watcher2, div2] = + setupAnimation(t, 'anim 300s 2', events); + + return watcher2.wait_for('animationstart').then(function(evt) { + animation1.currentTime = 400 * MS_PER_SEC; + animation2.currentTime = 400 * MS_PER_SEC; + + events.length = 0; // Clear received event array + + return Promise.all([ watcher1.wait_for('animationstart'), + watcher2.wait_for('animationiteration') ]); + }).then(function() { + checkEvents(events, ['animationiteration', div2, 300], + ['animationstart', div1, 0]); + }); +}, 'Test start and iteration events are ordered by time.'); + +promise_test(function(t) { + let events = []; + const [animation1, watcher1, div1] = + setupAnimation(t, 'anim 150s', events); + const [animation2, watcher2, div2] = + setupAnimation(t, 'anim 100s 2', events); + + return Promise.all([ watcher1.wait_for('animationstart'), + watcher2.wait_for('animationstart') ]).then(function() { + animation1.currentTime = 150 * MS_PER_SEC; + animation2.currentTime = 150 * MS_PER_SEC; + + events.length = 0; // Clear received event array + + return Promise.all([ watcher1.wait_for('animationend'), + watcher2.wait_for('animationiteration') ]); + }).then(function() { + checkEvents(events, ['animationiteration', div2, 100], + ['animationend', div1, 150]); + }); +}, 'Test iteration and end events are ordered by time.'); + +promise_test(function(t) { + let events = []; + const [animation1, watcher1, div1] = + setupAnimation(t, 'anim 100s 100s', events); + const [animation2, watcher2, div2] = + setupAnimation(t, 'anim 100s 2', events); + + animation1.finish(); + animation2.finish(); + + return Promise.all([ watcher1.wait_for([ 'animationstart', + 'animationend' ]), + watcher2.wait_for([ 'animationstart', + 'animationend' ]) ]).then(function() { + checkEvents(events, ['animationstart', div2, 0], + ['animationstart', div1, 0], + ['animationend', div1, 100], + ['animationend', div2, 200]); + }); +}, 'Test start and end events are sorted correctly when fired simultaneously'); + +done(); +</script> +</body> +</html> diff --git a/dom/animation/test/css-animations/test_event-dispatch.html b/dom/animation/test/css-animations/test_event-dispatch.html new file mode 100644 index 0000000000..de3be03017 --- /dev/null +++ b/dom/animation/test/css-animations/test_event-dispatch.html @@ -0,0 +1,15 @@ +<!doctype html> +<meta charset=utf-8> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +'use strict'; +setup({explicit_done: true}); +SpecialPowers.pushPrefEnv( + { "set": [["dom.animations-api.core.enabled", true]]}, + function() { + window.open("file_event-dispatch.html"); + }); +</script> +</html> diff --git a/dom/animation/test/css-animations/test_event-order.html b/dom/animation/test/css-animations/test_event-order.html new file mode 100644 index 0000000000..57f1f3876b --- /dev/null +++ b/dom/animation/test/css-animations/test_event-order.html @@ -0,0 +1,15 @@ +<!doctype html> +<meta charset=utf-8> +<script src="/resources/testharness.js"></script> +<script src="/resources/testharnessreport.js"></script> +<div id="log"></div> +<script> +'use strict'; +setup({explicit_done: true}); +SpecialPowers.pushPrefEnv( + { "set": [["dom.animations-api.core.enabled", true]]}, + function() { + window.open("file_event-order.html"); + }); +</script> +</html> diff --git a/dom/animation/test/mochitest.ini b/dom/animation/test/mochitest.ini index c694e4d25a..07e04d4d61 100644 --- a/dom/animation/test/mochitest.ini +++ b/dom/animation/test/mochitest.ini @@ -19,6 +19,8 @@ support-files = css-animations/file_document-get-animations.html css-animations/file_effect-target.html css-animations/file_element-get-animations.html + css-animations/file_event-dispatch.html + css-animations/file_event-order.html css-animations/file_keyframeeffect-getkeyframes.html css-animations/file_pseudoElement-get-animations.html css-transitions/file_animation-cancel.html @@ -73,6 +75,8 @@ support-files = [css-animations/test_document-get-animations.html] [css-animations/test_effect-target.html] [css-animations/test_element-get-animations.html] +[css-animations/test_event-dispatch.html] +[css-animations/test_event-order.html] [css-animations/test_keyframeeffect-getkeyframes.html] [css-animations/test_pseudoElement-get-animations.html] [css-transitions/test_animation-cancel.html] diff --git a/dom/events/test/test_legacy_event.html b/dom/events/test/test_legacy_event.html index d772be1066..b2105a6dfc 100644 --- a/dom/events/test/test_legacy_event.html +++ b/dom/events/test/test_legacy_event.html @@ -73,22 +73,15 @@ function triggerShortAnimation(node) { node.style.animation = "anim1 1ms linear"; } -// This function triggers a long animation with two iterations, which is -// *nearly* at the end of its first iteration. It will hit the end of that -// iteration (firing an event) almost immediately, 1ms in the future. +// This function triggers a very short (10ms long) animation with many +// iterations, which will cause a start event followed by an iteration event +// on each subsequent tick, to fire. // -// NOTE: It's important that this animation have a *long* duration. If it were -// short (e.g. 1ms duration), then we might jump past all its iterations in -// a single refresh-driver tick. And if that were to happens, we'd *never* fire -// any animationiteration events -- the CSS Animations spec says this event -// must not be fired "...when an animationend event would fire at the same time" -// (which would be the case in this example with a 1ms duration). So, to make -// sure our event does fire, we use a long duration and a nearly-as-long -// negative delay. This ensures we hit the end of the first iteration right -// away, and that we don't risk hitting the end of the second iteration at the -// same time. +// NOTE: We need the many iterations since if an animation frame coincides +// with the animation starting or ending we dispatch only the start or end +// event and not the iteration event. function triggerAnimationIteration(node) { - node.style.animation = "anim1 300s -299.999s linear 2"; + node.style.animation = "anim1 10ms linear 20000"; } // GENERAL UTILITY FUNCTIONS diff --git a/layout/reftests/transform-3d/animate-backface-hidden.html b/layout/reftests/transform-3d/animate-backface-hidden.html index ce957bf73f..27b5870061 100644 --- a/layout/reftests/transform-3d/animate-backface-hidden.html +++ b/layout/reftests/transform-3d/animate-backface-hidden.html @@ -17,7 +17,7 @@ body { padding: 50px } height: 200px; width: 200px; backface-visibility: hidden; /* use a -99.9s delay to start at 99.9% and then move to 0% */ - animation: flip 100s -99.9s linear 2; + animation: flip 100s -99.9s linear 2 paused; } </style> @@ -27,7 +27,13 @@ body { padding: 50px } <script> -document.getElementById("test").addEventListener("animationiteration", IterationListener, false); +document.getElementById("test").addEventListener("animationstart", StartListener, false); + +function StartListener(event) { + var test = document.getElementById("test"); + test.style.animationPlayState = 'running'; + test.addEventListener("animationiteration", IterationListener, false); +} function IterationListener(event) { setTimeout(RemoveReftestWait, 0); diff --git a/layout/reftests/transform-3d/animate-preserve3d-parent.html b/layout/reftests/transform-3d/animate-preserve3d-parent.html index ae3fec1961..d05beac6ff 100644 --- a/layout/reftests/transform-3d/animate-preserve3d-parent.html +++ b/layout/reftests/transform-3d/animate-preserve3d-parent.html @@ -18,7 +18,7 @@ body { padding: 50px } border: 1px solid black; transform-style: preserve-3d; /* use a -99.9s delay to start at 99.9% and then move to 0% */ - animation: spin 100s -99.9s linear 2; + animation: spin 100s -99.9s linear 2 paused; } #child { @@ -39,7 +39,13 @@ body { padding: 50px } <script> -document.getElementById("parent").addEventListener("animationiteration", IterationListener, false); +document.getElementById("parent").addEventListener("animationstart", StartListener, false); + +function StartListener(event) { + var test = document.getElementById("parent"); + test.style.animationPlayState = 'running'; + test.addEventListener("animationiteration", IterationListener, false); +} function IterationListener(event) { setTimeout(RemoveReftestWait, 0); diff --git a/layout/style/test/test_animations.html b/layout/style/test/test_animations.html index eaccba1221..4019af77fc 100644 --- a/layout/style/test/test_animations.html +++ b/layout/style/test/test_animations.html @@ -1195,9 +1195,6 @@ is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.4), 0.01, "large negative delay test at 0ms"); check_events([{ type: 'animationstart', target: div, animationName: 'anim2', elapsedTime: 3.6, - pseudoElement: "" }, - { type: 'animationiteration', target: div, - animationName: 'anim2', elapsedTime: 3.6, pseudoElement: "" }], "right after start in large negative delay test"); advance_clock(380); diff --git a/layout/style/test/test_animations_omta.html b/layout/style/test/test_animations_omta.html index 4b276c8965..0b2a61ecc7 100644 --- a/layout/style/test/test_animations_omta.html +++ b/layout/style/test/test_animations_omta.html @@ -1408,9 +1408,6 @@ addAsyncAnimTest(function *() { "large negative delay test at 0ms"); check_events([{ type: 'animationstart', target: gDiv, animationName: 'anim2', elapsedTime: 3.6, - pseudoElement: "" }, - { type: 'animationiteration', target: gDiv, - animationName: 'anim2', elapsedTime: 3.6, pseudoElement: "" }], "right after start in large negative delay test"); advance_clock(380); |