diff options
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.html | 216 |
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> |