216 lines
8.3 KiB
HTML
216 lines
8.3 KiB
HTML
<!DOCTYPE html>
|
|
<title>Basic use of stateful animator</title>
|
|
<link rel="help" href="https://drafts.css-houdini.org/css-animationworklet/">
|
|
|
|
<script src="/resources/testharness.js"></script>
|
|
<script src="/resources/testharnessreport.js"></script>
|
|
<script src="/web-animations/testcommon.js"></script>
|
|
<script src="common.js"></script>
|
|
|
|
<div id="target"></div>
|
|
|
|
<script id="stateful_animator_basic" type="text/worklet">
|
|
registerAnimator("stateful_animator_basic", class {
|
|
constructor(options, state = { test_local_time: 0 }) {
|
|
this.test_local_time = state.test_local_time;
|
|
}
|
|
animate(currentTime, effect) {
|
|
effect.localTime = this.test_local_time++;
|
|
}
|
|
state() {
|
|
return {
|
|
test_local_time: this.test_local_time
|
|
};
|
|
}
|
|
});
|
|
</script>
|
|
|
|
<script id="stateless_animator_basic" type="text/worklet">
|
|
registerAnimator("stateless_animator_basic", class {
|
|
constructor(options, state = { test_local_time: 0 }) {
|
|
this.test_local_time = state.test_local_time;
|
|
}
|
|
animate(currentTime, effect) {
|
|
effect.localTime = this.test_local_time++;
|
|
}
|
|
// Unless a valid state function is provided, the animator is considered
|
|
// stateless. e.g. animator with incorrect state function name.
|
|
State() {
|
|
return {
|
|
test_local_time: this.test_local_time
|
|
};
|
|
}
|
|
});
|
|
</script>
|
|
|
|
<script id="stateless_animator_preserves_effect_local_time" type="text/worklet">
|
|
registerAnimator("stateless_animator_preserves_effect_local_time", class {
|
|
animate(currentTime, effect) {
|
|
// The local time will be carried over to the new global scope.
|
|
effect.localTime = effect.localTime ? effect.localTime + 1 : 1;
|
|
}
|
|
});
|
|
</script>
|
|
|
|
<script id="stateless_animator_does_not_copy_effect_object" type="text/worklet">
|
|
registerAnimator("stateless_animator_does_not_copy_effect_object", class {
|
|
animate(currentTime, effect) {
|
|
effect.localTime = effect.localTime ? effect.localTime + 1 : 1;
|
|
effect.foo = effect.foo ? effect.foo + 1 : 1;
|
|
// This condition becomes true once we switch global scope and only preserve local time
|
|
// otherwise these values keep increasing in lock step.
|
|
if (effect.localTime > effect.foo) {
|
|
// This works as long as we switch global scope before 10000 frames.
|
|
// which is a safe assumption.
|
|
effect.localTime = 10000;
|
|
}
|
|
}
|
|
});
|
|
</script>
|
|
|
|
<script id="state_function_returns_empty" type="text/worklet">
|
|
registerAnimator("state_function_returns_empty", class {
|
|
constructor(options, state = { test_local_time: 0 }) {
|
|
this.test_local_time = state.test_local_time;
|
|
}
|
|
animate(currentTime, effect) {
|
|
effect.localTime = this.test_local_time++;
|
|
}
|
|
state() {}
|
|
});
|
|
</script>
|
|
|
|
<script id="state_function_returns_not_serializable" type="text/worklet">
|
|
registerAnimator("state_function_returns_not_serializable", class {
|
|
constructor(options) {
|
|
this.test_local_time = 0;
|
|
}
|
|
animate(currentTime, effect) {
|
|
effect.localTime = this.test_local_time++;
|
|
}
|
|
state() {
|
|
return new Symbol('foo');
|
|
}
|
|
});
|
|
</script>
|
|
|
|
<script>
|
|
const EXPECTED_FRAMES_TO_A_SCOPE_SWITCH = 15;
|
|
async function localTimeDoesNotUpdate(animation) {
|
|
// The local time stops increasing after the animator instance being dropped.
|
|
// e.g. 0, 1, 2, .., n, n, n, n, .. where n is the frame that the global
|
|
// scope switches at.
|
|
let last_local_time = animation.effect.getComputedTiming().localTime;
|
|
let frame_count = 0;
|
|
const FRAMES_WITHOUT_CHANGE = 10;
|
|
do {
|
|
await new Promise(window.requestAnimationFrame);
|
|
let current_local_time = animation.effect.getComputedTiming().localTime;
|
|
if (approxEquals(last_local_time, current_local_time))
|
|
++frame_count;
|
|
else
|
|
frame_count = 0;
|
|
last_local_time = current_local_time;
|
|
} while (frame_count < FRAMES_WITHOUT_CHANGE);
|
|
}
|
|
|
|
async function localTimeResetsToZero(animation) {
|
|
// The local time is reset upon global scope switching. e.g.
|
|
// 0, 1, 2, .., 0, 1, 2, .., 0, 1, 2, .., 0, 1, 2, ...
|
|
let reset_count = 0;
|
|
const LOCAL_TIME_RESET_CHECK = 3;
|
|
do {
|
|
await new Promise(window.requestAnimationFrame);
|
|
if (approxEquals(0, animation.effect.getComputedTiming().localTime))
|
|
++reset_count;
|
|
} while (reset_count < LOCAL_TIME_RESET_CHECK);
|
|
}
|
|
|
|
promise_test(async t => {
|
|
await runInAnimationWorklet(document.getElementById('stateful_animator_basic').textContent);
|
|
const target = document.getElementById('target');
|
|
const effect = new KeyframeEffect(target, [{ opacity: 0 }], { duration: 1000 });
|
|
const animation = new WorkletAnimation('stateful_animator_basic', effect);
|
|
animation.play();
|
|
|
|
// effect.localTime should be correctly increased upon global scope
|
|
// switches for stateful animators.
|
|
await waitForAnimationFrameWithCondition(_ => {
|
|
return approxEquals(animation.effect.getComputedTiming().localTime,
|
|
EXPECTED_FRAMES_TO_A_SCOPE_SWITCH);
|
|
});
|
|
|
|
animation.cancel();
|
|
}, "Stateful animator can use its state to update the animation. Pass if test does not timeout");
|
|
|
|
promise_test(async t => {
|
|
await runInAnimationWorklet(document.getElementById('stateless_animator_basic').textContent);
|
|
const target = document.getElementById('target');
|
|
const effect = new KeyframeEffect(target, [{ opacity: 0 }], { duration: 1000 });
|
|
const animation = new WorkletAnimation('stateless_animator_basic', effect);
|
|
animation.play();
|
|
|
|
// The local time should be reset to 0 upon global scope switching for
|
|
// stateless animators.
|
|
await localTimeResetsToZero(animation);
|
|
|
|
animation.cancel();
|
|
}, "Stateless animator gets reecreated with 'undefined' state.");
|
|
|
|
promise_test(async t => {
|
|
await runInAnimationWorklet(document.getElementById('stateless_animator_preserves_effect_local_time').textContent);
|
|
const target = document.getElementById('target');
|
|
const effect = new KeyframeEffect(target, [{ opacity: 0 }], { duration: 1000 });
|
|
const animation = new WorkletAnimation('stateless_animator_preserves_effect_local_time', effect);
|
|
animation.play();
|
|
|
|
await waitForAnimationFrameWithCondition(_ => {
|
|
return approxEquals(animation.effect.getComputedTiming().localTime,
|
|
EXPECTED_FRAMES_TO_A_SCOPE_SWITCH);
|
|
});
|
|
|
|
animation.cancel();
|
|
}, "Stateless animator should preserve the local time of its effect.");
|
|
|
|
promise_test(async t => {
|
|
await runInAnimationWorklet(document.getElementById('stateless_animator_does_not_copy_effect_object').textContent);
|
|
const target = document.getElementById('target');
|
|
const effect = new KeyframeEffect(target, [{ opacity: 0 }], { duration: 1000 });
|
|
const animation = new WorkletAnimation('stateless_animator_does_not_copy_effect_object', effect);
|
|
animation.play();
|
|
|
|
await waitForAnimationFrameWithCondition(_ => {
|
|
return approxEquals(animation.effect.getComputedTiming().localTime, 10000);
|
|
});
|
|
|
|
animation.cancel();
|
|
}, "Stateless animator should not copy the effect object.");
|
|
|
|
promise_test(async t => {
|
|
await runInAnimationWorklet(document.getElementById('state_function_returns_empty').textContent);
|
|
const target = document.getElementById('target');
|
|
const effect = new KeyframeEffect(target, [{ opacity: 0 }], { duration: 1000 });
|
|
const animation = new WorkletAnimation('state_function_returns_empty', effect);
|
|
animation.play();
|
|
|
|
// The local time should be reset to 0 upon global scope switching for
|
|
// stateless animators.
|
|
await localTimeResetsToZero(animation);
|
|
|
|
animation.cancel();
|
|
}, "Stateful animator gets recreated with 'undefined' state if state function returns undefined.");
|
|
|
|
promise_test(async t => {
|
|
await runInAnimationWorklet(document.getElementById('state_function_returns_not_serializable').textContent);
|
|
const target = document.getElementById('target');
|
|
const effect = new KeyframeEffect(target, [{ opacity: 0 }], { duration: 1000, iteration: Infinity });
|
|
const animation = new WorkletAnimation('state_function_returns_not_serializable', effect);
|
|
animation.play();
|
|
|
|
// The local time of an animation increases until the registered animator
|
|
// gets removed.
|
|
await localTimeDoesNotUpdate(animation);
|
|
|
|
animation.cancel();
|
|
}, "Stateful Animator instance gets dropped (does not get migrated) if state function is not serializable.");
|
|
</script>
|