summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/tests/animation-worklet/stateful-animator.https.html
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/tests/animation-worklet/stateful-animator.https.html')
-rw-r--r--testing/web-platform/tests/animation-worklet/stateful-animator.https.html216
1 files changed, 216 insertions, 0 deletions
diff --git a/testing/web-platform/tests/animation-worklet/stateful-animator.https.html b/testing/web-platform/tests/animation-worklet/stateful-animator.https.html
new file mode 100644
index 0000000000..be29fa109c
--- /dev/null
+++ b/testing/web-platform/tests/animation-worklet/stateful-animator.https.html
@@ -0,0 +1,216 @@
+<!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>