blob: 47e2717ecdcc635feebb7a60c6e542af53dac4ac [file] [edit]
<!DOCTYPE html>
<meta charset=utf-8>
<title>Verify timeline time, animation time, effect time and effect value for all fill modes in all timeline states: before start, at start, in range, at end, after end while using various effect delay values</title>
<meta name="timeout" content="long">
<script src="/resources/testharness.js"></script>
<script src="/resources/testharnessreport.js"></script>
<script src="/web-animations/testcommon.js"></script>
<script src="testcommon.js"></script>
<style>
.scroller {
overflow: auto;
height: 100px;
width: 100px;
}
.contents {
height: 1000px;
width: 100%;
}
</style>
<div id="log"></div>
<script>
'use strict';
// Test cases are included where effect delay causes the effect iteration to
// overlap with the timeline start time and also the timeline end time.
// Timeline
// BEFORE +-----------------+ AFTER
// time: 0 timeRange
// 1) +--------+
// 2) +-------+
// 3) +------+
// 4) +------+
// Each entry is [[test input], [test expectations]]
// test input = ["description", delay, scroll percent]
// test expectations = [timeline time, animation current time,
// effect local time, effect progress, effect phase]
const test_cases = [
// Case 1: No delay. Effect starts at the same time as the timeline.
[["before timeline start", 0, 0.1 ], [0, 0, 0, null, "before"]],
[["at timeline start", 0, 0.2 ], [0, 0, 0, 0, "active"]],
[["in timeline range", 0, 0.5 ], [500, 500, 500, 0.833, "active"]],
// Case 2: Positive delay.
[["before timeline start", 100, 0.1 ], [0, 0, 0, null, "before"]],
[["at timeline start", 100, 0.2 ], [0, 0, 0, null, "before"]],
[["before effect delay", 100, 0.25], [83.333, 83.333, 83.333, null, "before"]],
[["at effect start", 100, 0.26], [100, 100, 100, 0, "active"]],
[["in timeline range", 100, 0.5 ], [500, 500, 500, 0.666, "active"]],
[["at effect end", 100, 0.62], [700, 700, 700, null, "after"]],
[["after effect end", 100, 0.65], [750, 700, 700, null, "after"]],
[["at timeline end", 100, 0.8 ], [1000, 700, 700, null, "after"]],
[["after timeline end", 100, 0.9 ], [1000, 700, 700, null, "after"]],
// Case 3: Negative delay.
// Can't test values for "before effect delay" and "at effect start" because
// they occur before the timeline start and are therefore unreachable.
[["before timeline start", -100, 0.1 ], [0, 0, 0, null, "before"]],
[["at timeline start", -100, 0.2 ], [0, 0, 0, 0.166, "active"]],
[["in timeline range", -100, 0.3 ], [166.666, 166.666, 166.666, 0.444, "active"]],
[["at effect end", -100, 0.5 ], [500, 500, 500, null, "after"]],
[["after effect end", -100, 0.51], [516.666, 500, 500, null, "after"]],
[["at timeline end", -100, 0.8 ], [1000, 500, 500, null, "after"]],
[["after timeline end", -100, 0.9 ], [1000, 500, 500, null, "after"]],
// Case 4: Effect delay is large enough to cause the effect to not finish
// before the timeline.
[["before timeline start", 500, 0.1 ], [0, 0, 0, null, "before"]],
[["at timeline start", 500, 0.2 ], [0, 0, 0, null, "before"]],
[["before effect delay", 500, 0.4 ], [333.333, 333.333, 333.333, null, "before"]],
[["at effect start", 500, 0.5 ], [500, 500, 500, 0, "active"]],
[["in timeline range", 500, 0.65], [750, 750, 750, 0.416, "active"]],
[["at timeline end", 500, 0.8 ], [1000, 1000, 1000, 0.833, "active"]],
[["after timeline end", 500, 0.9 ], [1000, 1000, 1000, 0.833, "active"]],
// Can't scroll past the end of the scroller and therefore cannot reach the
// effect end, so "at effect end" and "after effect end" states are not
// included.
];
for (const test_case of test_cases) {
const [inputs, expected] = test_case;
const [test_name, delay, scroll_percentage] = inputs;
const description = `Current times and effect phase ${test_name} when` +
` delay = ${delay} |`;
promise_test(
create_scroll_timeline_fill_test(delay, scroll_percentage, expected),
description);
}
function create_scroll_timeline_fill_test(delay, scroll_percentage, expected){
return async t => {
const target = createDiv(t);
const timeline = createScrollTimelineWithOffsets(t, CSS.percent(20), CSS.percent(80));
const effect = new KeyframeEffect(
target,
{
opacity: [0.3, 0.7]
},
{
duration: 600,
delay: delay
}
);
const animation = new Animation(effect, timeline);
const scroller = timeline.scrollSource;
const maxScroll = scroller.scrollHeight - scroller.clientHeight;
animation.play();
await animation.ready;
scroller.scrollTop = scroll_percentage * maxScroll;
// Wait for new animation frame which allows the timeline to compute
// new current time.
await waitForNextFrame();
const [expected_timeline_current_time,
expected_animation_current_time,
expected_effect_local_time,
expected_effect_progress,
expected_effect_phase] = expected;
assert_times_equal(
animation.timeline.currentTime,
expected_timeline_current_time,
"timeline current time"
);
assert_times_equal(
animation.currentTime,
expected_animation_current_time,
"animation current time"
);
assert_times_equal(
animation.effect.getComputedTiming().localTime,
expected_effect_local_time,
"animation effect local time"
);
assert_approx_equals_or_null(
animation.effect.getComputedTiming().progress,
expected_effect_progress,
0.001,
"animation effect progress"
);
assert_phase_at_time(
animation,
expected_effect_phase,
animation.currentTime
);
}
}
function createKeyframeEffectOpacity(test){
return new KeyframeEffect(
createDiv(test),
{
opacity: [0.3, 0.7]
},
{
duration: 1000
}
);
}
function verifyTimelineBeforePhase(animation){
assert_equals(animation.timeline.phase, "before");
assert_equals(animation.timeline.currentTime, 0);
assert_equals(animation.currentTime, 0);
assert_equals(
animation.effect.getComputedTiming().localTime,
0,
"effect local time in timeline before phase");
}
function verifyEffectBeforePhase(animation){
// progress == null AND opacity == 1 implies we are in the effect before
// phase
assert_equals(
animation.effect.getComputedTiming().progress,
null
);
assert_equals(
window.getComputedStyle(animation.effect.target).getPropertyValue("opacity"),
"1"
);
}
promise_test(async t => {
const animation = new Animation(
createKeyframeEffectOpacity(t),
createScrollTimelineWithOffsets(t, CSS.percent(20), CSS.percent(80))
);
const scroller = animation.timeline.scrollSource;
const maxScroll = scroller.scrollHeight - scroller.clientHeight;
animation.play();
await animation.ready;
verifyTimelineBeforePhase(animation);
verifyEffectBeforePhase(animation);
animation.pause();
await waitForNextFrame();
verifyTimelineBeforePhase(animation);
verifyEffectBeforePhase(animation);
animation.play();
await waitForNextFrame();
verifyTimelineBeforePhase(animation);
verifyEffectBeforePhase(animation);
}, 'Verify that (play -> pause -> play) doesn\'t change phase/progress.');
promise_test(async t => {
const animation = new Animation(
createKeyframeEffectOpacity(t),
createScrollTimelineWithOffsets(t, CSS.percent(20), CSS.percent(80))
);
const scroller = animation.timeline.scrollSource;
const maxScroll = scroller.scrollHeight - scroller.clientHeight;
animation.play();
await animation.ready;
verifyTimelineBeforePhase(animation);
verifyEffectBeforePhase(animation);
animation.pause();
await waitForNextFrame();
verifyTimelineBeforePhase(animation);
verifyEffectBeforePhase(animation);
// Scrolling should not cause the animation effect to change.
scroller.scrollTop = 0.5 * maxScroll;
await waitForNextFrame();
// Check timeline phase
assert_equals(animation.timeline.phase, "active");
assert_equals(animation.timeline.currentTime, 500);
assert_equals(animation.currentTime, 0);
assert_equals(
animation.effect.getComputedTiming().localTime,
0,
"effect local time"
);
// Make sure the effect is still in the before phase even though the
// timeline is not.
verifyEffectBeforePhase(animation);
}, 'Pause in before phase, scroll timeline into active phase, animation ' +
'should remain in the before phase');
promise_test(async t => {
const animation = new Animation(
createKeyframeEffectOpacity(t),
createScrollTimelineWithOffsets(t, CSS.percent(20), CSS.percent(80))
);
const scroller = animation.timeline.scrollSource;
const maxScroll = scroller.scrollHeight - scroller.clientHeight;
animation.play();
await animation.ready;
// Causes the timeline to be inactive
scroller.style.overflow = "visible";
await waitForNextFrame();
await waitForNextFrame();
// Check timeline phase
assert_equals(animation.timeline.phase, "inactive");
assert_equals(animation.timeline.currentTime, null);
assert_equals(animation.currentTime, null);
assert_equals(
animation.effect.getComputedTiming().localTime,
null,
"effect local time with inactive timeline"
);
verifyEffectBeforePhase(animation);
// Setting the current time while timeline is inactive should cause hold phase
// and hold time to be populated
animation.currentTime = 500;
await waitForNextFrame();
await waitForNextFrame();
// Check timeline phase
assert_equals(animation.timeline.phase, "inactive");
assert_equals(animation.timeline.currentTime, null);
assert_equals(animation.currentTime, 500);
assert_equals(
animation.effect.getComputedTiming().localTime,
500,
"effect local time after setting animation current time"
);
// Check effect phase
// progress == 0.5 AND opacity == 0.5 shows we are in the effect active phase
assert_equals(
animation.effect.getComputedTiming().progress,
0.5,
"effect progress"
);
assert_equals(
window.getComputedStyle(animation.effect.target).getPropertyValue("opacity"),
"0.5",
"effect opacity after setting animation current time"
);
}, 'Make scroller inactive, then set current time to an in range time');
promise_test(async t => {
const animation = new Animation(
createKeyframeEffectOpacity(t),
createScrollTimelineWithOffsets(t, CSS.percent(20), CSS.percent(80))
);
const scroller = animation.timeline.scrollSource;
const maxScroll = scroller.scrollHeight - scroller.clientHeight;
scroller.scrollTop = 0.5 * maxScroll;
// allow the scroll to finish.
await waitForNextFrame();
animation.pause();
await animation.ready;
// verify effect is applied.
const expected_progress = 0.5;
assert_equals(
animation.effect.getComputedTiming().progress,
expected_progress,
"Verify effect progress after pausing."
);
// cause the timeline to become inactive
scroller.style.overflow = 'visible';
await waitForAnimationFrames(2);
assert_equals(animation.timeline.currentTime, null,
'Sanity check the timeline is inactive.');
assert_equals(
animation.effect.getComputedTiming().progress,
expected_progress,
"Verify effect progress after the timeline goes inactive."
);
}, 'Animation effect is still applied after pausing and making timeline ' +
'inactive.');
</script>