summaryrefslogtreecommitdiffstats
path: root/dom
diff options
context:
space:
mode:
authorjanekptacijarabaci <janekptacijarabaci@seznam.cz>2018-03-14 11:55:23 +0100
committerjanekptacijarabaci <janekptacijarabaci@seznam.cz>2018-03-14 11:55:23 +0100
commitc58cec26c73da9a5c48f7d75555c6a2409965693 (patch)
tree318932a7d433433d94ad2ac0f7c46a1ae66a17fe /dom
parentaade91b13a50ee4f246016fa8d8d1561f58f80ee (diff)
downloadUXP-c58cec26c73da9a5c48f7d75555c6a2409965693.tar
UXP-c58cec26c73da9a5c48f7d75555c6a2409965693.tar.gz
UXP-c58cec26c73da9a5c48f7d75555c6a2409965693.tar.lz
UXP-c58cec26c73da9a5c48f7d75555c6a2409965693.tar.xz
UXP-c58cec26c73da9a5c48f7d75555c6a2409965693.zip
Bug 1202333: AnimationEvent elapsedTime should reflect playbackRate (added tests)
Issue #55
Diffstat (limited to 'dom')
-rw-r--r--dom/animation/test/css-animations/file_event-dispatch.html252
-rw-r--r--dom/animation/test/css-animations/file_event-order.html160
-rw-r--r--dom/animation/test/css-animations/test_event-dispatch.html15
-rw-r--r--dom/animation/test/css-animations/test_event-order.html15
-rw-r--r--dom/animation/test/mochitest.ini4
-rw-r--r--dom/events/test/test_legacy_event.html21
6 files changed, 453 insertions, 14 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 000000000..266205bc3
--- /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 000000000..da78b6541
--- /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 000000000..de3be0301
--- /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 000000000..57f1f3876
--- /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 c694e4d25..07e04d4d6 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 d772be106..b2105a6df 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