servo/tests/wpt/web-platform-tests/web-animations/timing-model/animations/updating-the-finished-state.html

410 lines
15 KiB
HTML

<!DOCTYPE html>
<meta charset=utf-8>
<title>Tests for updating the finished state of an animation</title>
<link rel="help" href="https://w3c.github.io/web-animations/#updating-the-finished-state">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="../../testcommon.js"></script>
<body>
<div id="log"></div>
<script>
'use strict';
// --------------------------------------------------------------------
//
// TESTS FOR UPDATING THE HOLD TIME
//
// --------------------------------------------------------------------
// CASE 1: playback rate > 0 and current time >= target effect end
// (Also the start time is resolved and there is pending task)
// Did seek = false
promise_test(function(t) {
var anim = createDiv(t).animate(null, 100 * MS_PER_SEC);
// Here and in the following tests we wait until ready resolves as
// otherwise we don't have a resolved start time. We test the case
// where the start time is unresolved in a subsequent test.
return anim.ready.then(function() {
// Seek to 1ms before the target end and then wait 1ms
anim.currentTime = 100 * MS_PER_SEC - 1;
return waitForAnimationFramesWithDelay(1);
}).then(function() {
assert_equals(anim.currentTime, 100 * MS_PER_SEC,
'Hold time is set to target end clamping current time');
});
}, 'Updating the finished state when playing past end');
// Did seek = true
promise_test(function(t) {
var anim = createDiv(t).animate(null, 100 * MS_PER_SEC);
return anim.ready.then(function() {
anim.currentTime = 200 * MS_PER_SEC;
return waitForAnimationFrames(1);
}).then(function() {
assert_equals(anim.currentTime, 200 * MS_PER_SEC,
'Hold time is set so current time should NOT change');
});
}, 'Updating the finished state when seeking past end');
// Test current time == target end
//
// We can't really write a test for current time == target end with
// did seek = false since that would imply setting up an animation where
// the next animation frame time happens to exactly align with the target end.
//
// Fortunately, we don't need to test that case since even if the implementation
// fails to set the hold time on such a tick, it should be mostly unobservable
// (on the subsequent tick the hold time will be set to the same value anyway).
// Did seek = true
promise_test(function(t) {
var anim = createDiv(t).animate(null, 100 * MS_PER_SEC);
return anim.ready.then(function() {
anim.currentTime = 100 * MS_PER_SEC;
return waitForAnimationFrames(1);
}).then(function() {
assert_equals(anim.currentTime, 100 * MS_PER_SEC,
'Hold time is set so current time should NOT change');
});
}, 'Updating the finished state when seeking exactly to end');
// CASE 2: playback rate < 0 and current time <= 0
// (Also the start time is resolved and there is pending task)
// Did seek = false
promise_test(function(t) {
var anim = createDiv(t).animate(null, 100 * MS_PER_SEC);
anim.playbackRate = -1;
anim.play(); // Make sure animation is not initially finished
return anim.ready.then(function() {
// Seek to 1ms before 0 and then wait 1ms
anim.currentTime = 1;
return waitForAnimationFramesWithDelay(1);
}).then(function() {
assert_equals(anim.currentTime, 0 * MS_PER_SEC,
'Hold time is set to zero clamping current time');
});
}, 'Updating the finished state when playing in reverse past zero');
// Did seek = true
promise_test(function(t) {
var anim = createDiv(t).animate(null, 100 * MS_PER_SEC);
anim.playbackRate = -1;
anim.play();
return anim.ready.then(function() {
anim.currentTime = -100 * MS_PER_SEC;
return waitForAnimationFrames(1);
}).then(function() {
assert_equals(anim.currentTime, -100 * MS_PER_SEC,
'Hold time is set so current time should NOT change');
});
}, 'Updating the finished state when seeking a reversed animation past zero');
// As before, it's difficult to test current time == 0 for did seek = false but
// it doesn't really matter.
// Did seek = true
promise_test(function(t) {
var anim = createDiv(t).animate(null, 100 * MS_PER_SEC);
anim.playbackRate = -1;
anim.play();
return anim.ready.then(function() {
anim.currentTime = 0;
return waitForAnimationFrames(1);
}).then(function() {
assert_equals(anim.currentTime, 0 * MS_PER_SEC,
'Hold time is set so current time should NOT change');
});
}, 'Updating the finished state when seeking a reversed animation exactly'
+ ' to zero');
// CASE 3: playback rate > 0 and current time < target end OR
// playback rate < 0 and current time > 0
// (Also the start time is resolved and there is pending task)
// Did seek = false; playback rate > 0
promise_test(function(t) {
var anim = createDiv(t).animate(null, 100 * MS_PER_SEC);
// We want to test that the hold time is cleared so first we need to
// put the animation in a state where the hold time is set.
anim.finish();
return anim.ready.then(function() {
assert_equals(anim.currentTime, 100 * MS_PER_SEC,
'Hold time is initially set');
// Then extend the duration so that the hold time is cleared and on
// the next tick the current time will increase.
anim.effect.timing.duration *= 2;
return waitForAnimationFrames(1);
}).then(function() {
assert_greater_than(anim.currentTime, 100 * MS_PER_SEC,
'Hold time is not set so current time should increase');
});
}, 'Updating the finished state when playing before end');
// Did seek = true; playback rate > 0
promise_test(function(t) {
var anim = createDiv(t).animate(null, 100 * MS_PER_SEC);
anim.finish();
return anim.ready.then(function() {
anim.currentTime = 50 * MS_PER_SEC;
// When did seek = true, updating the finished state: (i) updates
// the animation's start time and (ii) clears the hold time.
// We can test both by checking that the currentTime is initially
// updated and then increases.
assert_equals(anim.currentTime, 50 * MS_PER_SEC, 'Start time is updated');
return waitForAnimationFrames(1);
}).then(function() {
assert_greater_than(anim.currentTime, 50 * MS_PER_SEC,
'Hold time is not set so current time should increase');
});
}, 'Updating the finished state when seeking before end');
// Did seek = false; playback rate < 0
//
// Unfortunately it is not possible to test this case. We need to have
// a hold time set, a resolved start time, and then perform some
// operation that updates the finished state with did seek set to true.
//
// However, the only situation where this could arrive is when we
// replace the timeline and that procedure is likely to change. For all
// other cases we either have an unresolved start time (e.g. when
// paused), we don't have a set hold time (e.g. regular playback), or
// the current time is zero (and anything that gets us out of that state
// will set did seek = true).
// Did seek = true; playback rate < 0
promise_test(function(t) {
var anim = createDiv(t).animate(null, 100 * MS_PER_SEC);
anim.playbackRate = -1;
return anim.ready.then(function() {
anim.currentTime = 50 * MS_PER_SEC;
assert_equals(anim.currentTime, 50 * MS_PER_SEC, 'Start time is updated');
return waitForAnimationFrames(1);
}).then(function() {
assert_less_than(anim.currentTime, 50 * MS_PER_SEC,
'Hold time is not set so current time should decrease');
});
}, 'Updating the finished state when seeking a reversed animation before end');
// CASE 4: playback rate == 0
// current time < 0
promise_test(function(t) {
var anim = createDiv(t).animate(null, 100 * MS_PER_SEC);
anim.playbackRate = 0;
return anim.ready.then(function() {
anim.currentTime = -100 * MS_PER_SEC;
return waitForAnimationFrames(1);
}).then(function() {
assert_equals(anim.currentTime, -100 * MS_PER_SEC,
'Hold time should not be cleared so current time should'
+ ' NOT change');
});
}, 'Updating the finished state when playback rate is zero and the'
+ ' current time is less than zero');
// current time < target end
promise_test(function(t) {
var anim = createDiv(t).animate(null, 100 * MS_PER_SEC);
anim.playbackRate = 0;
return anim.ready.then(function() {
anim.currentTime = 50 * MS_PER_SEC;
return waitForAnimationFrames(1);
}).then(function() {
assert_equals(anim.currentTime, 50 * MS_PER_SEC,
'Hold time should not be cleared so current time should'
+ ' NOT change');
});
}, 'Updating the finished state when playback rate is zero and the'
+ ' current time is less than end');
// current time > target end
promise_test(function(t) {
var anim = createDiv(t).animate(null, 100 * MS_PER_SEC);
anim.playbackRate = 0;
return anim.ready.then(function() {
anim.currentTime = 200 * MS_PER_SEC;
return waitForAnimationFrames(1);
}).then(function() {
assert_equals(anim.currentTime, 200 * MS_PER_SEC,
'Hold time should not be cleared so current time should'
+ ' NOT change');
});
}, 'Updating the finished state when playback rate is zero and the'
+ ' current time is greater than end');
// CASE 5: current time unresolved
promise_test(function(t) {
var anim = createDiv(t).animate(null, 100 * MS_PER_SEC);
anim.cancel();
// Trigger a change that will cause the "update the finished state"
// procedure to run.
anim.effect.timing.duration = 200 * MS_PER_SEC;
assert_equals(anim.currentTime, null,
'The animation hold time / start time should not be updated');
// The "update the finished state" procedure is supposed to run after any
// change to timing, but just in case an implementation defers that, let's
// wait a frame and check that the hold time / start time has still not been
// updated.
return waitForAnimationFrames(1).then(function() {
assert_equals(anim.currentTime, null,
'The animation hold time / start time should not be updated');
});
}, 'Updating the finished state when current time is unresolved');
// CASE 6: has a pending task
test(function(t) {
var anim = createDiv(t).animate(null, 100 * MS_PER_SEC);
anim.cancel();
anim.currentTime = 75 * MS_PER_SEC;
anim.play();
// We now have a pending task and a resolved current time.
//
// In the next step we will adjust the timing so that the current time
// is greater than the target end. At this point the "update the finished
// state" procedure should run and if we fail to check for a pending task
// we will set the hold time to the target end, i.e. 50ms.
anim.effect.timing.duration = 50 * MS_PER_SEC;
assert_equals(anim.currentTime, 75 * MS_PER_SEC,
'Hold time should not be updated');
}, 'Updating the finished state when there is a pending task');
// CASE 7: start time unresolved
// Did seek = false
promise_test(function(t) {
var anim = createDiv(t).animate(null, 100 * MS_PER_SEC);
anim.cancel();
// Make it so that only the start time is unresolved (to avoid overlapping
// with the test case where current time is unresolved)
anim.currentTime = 150 * MS_PER_SEC;
// Trigger a change that will cause the "update the finished state"
// procedure to run (did seek = false).
anim.effect.timing.duration = 200 * MS_PER_SEC;
return waitForAnimationFrames(1).then(function() {
assert_equals(anim.currentTime, 150 * MS_PER_SEC,
'The animation hold time should not be updated');
assert_equals(anim.startTime, null,
'The animation start time should not be updated');
});
}, 'Updating the finished state when start time is unresolved and'
+ ' did seek = false');
// Did seek = true
test(function(t) {
var anim = createDiv(t).animate(null, 100 * MS_PER_SEC);
anim.cancel();
anim.currentTime = 150 * MS_PER_SEC;
// Trigger a change that will cause the "update the finished state"
// procedure to run.
anim.currentTime = 50 * MS_PER_SEC;
assert_equals(anim.currentTime, 50 * MS_PER_SEC,
'The animation hold time should not be updated');
assert_equals(anim.startTime, null,
'The animation start time should not be updated');
}, 'Updating the finished state when start time is unresolved and'
+ ' did seek = true');
// --------------------------------------------------------------------
//
// TESTS FOR RUNNING FINISH NOTIFICATION STEPS
//
// --------------------------------------------------------------------
function waitForFinishEventAndPromise(animation) {
var eventPromise = new Promise(function(resolve) {
animation.onfinish = function() { resolve(); }
});
return Promise.all([eventPromise, animation.finished]);
}
promise_test(function(t) {
var animation = createDiv(t).animate(null, 1);
animation.onfinish =
t.unreached_func('Seeking to finish should not fire finish event');
animation.finished.then(
t.unreached_func('Seeking to finish should not resolve finished promise'));
animation.currentTime = 1;
animation.currentTime = 0;
animation.pause();
return waitForAnimationFrames(3);
}, 'Finish notification steps don\'t run when the animation seeks to finish'
+ ' and then seeks back again');
promise_test(function(t) {
var animation = createDiv(t).animate(null, 1);
return animation.ready.then(function() {
return waitForFinishEventAndPromise(animation);
});
}, 'Finish notification steps run when the animation completes normally');
promise_test(function(t) {
var animation = createDiv(t).animate(null, 1);
return animation.ready.then(function() {
animation.currentTime = 10;
return waitForFinishEventAndPromise(animation);
});
}, 'Finish notification steps run when the animation seeks past finish');
promise_test(function(t) {
var animation = createDiv(t).animate(null, 1);
return animation.ready.then(function() {
// Register for notifications now since once we seek away from being
// finished the 'finished' promise will be replaced.
var finishNotificationSteps = waitForFinishEventAndPromise(animation);
animation.finish();
animation.currentTime = 0;
animation.pause();
return finishNotificationSteps;
});
}, 'Finish notification steps run when the animation completes with .finish(),'
+ ' even if we then seek away');
promise_test(function(t) {
var animation = createDiv(t).animate(null, 1);
var initialFinishedPromise = animation.finished;
return animation.finished.then(function(target) {
animation.currentTime = 0;
assert_not_equals(initialFinishedPromise, animation.finished);
});
}, 'Animation finished promise is replaced after seeking back to start');
promise_test(function(t) {
var animation = createDiv(t).animate(null, 1);
var initialFinishedPromise = animation.finished;
return animation.finished.then(function(target) {
animation.play();
assert_not_equals(initialFinishedPromise, animation.finished);
});
}, 'Animation finished promise is replaced after replaying from start');
async_test(function(t) {
var animation = createDiv(t).animate(null, 1);
animation.onfinish = function(event) {
animation.currentTime = 0;
animation.onfinish = function(event) {
t.done();
};
};
}, 'Animation finish event is fired again after seeking back to start');
async_test(function(t) {
var animation = createDiv(t).animate(null, 1);
animation.onfinish = function(event) {
animation.play();
animation.onfinish = function(event) {
t.done();
};
};
}, 'Animation finish event is fired again after replaying from start');
</script>
</body>