mirror of
https://github.com/LadybirdBrowser/ladybird.git
synced 2025-12-07 21:59:54 +00:00
LibWeb: Update style before getting animation play state
Pending style updates can influence this value
This commit is contained in:
parent
a95cde3660
commit
fbcef936a9
Notes:
github-actions[bot]
2025-11-02 22:55:11 +00:00
Author: https://github.com/Calme1709
Commit: fbcef936a9
Pull-request: https://github.com/LadybirdBrowser/ladybird/pull/6642
Reviewed-by: https://github.com/AtkinsSJ ✅
7 changed files with 286 additions and 1 deletions
|
|
@ -277,6 +277,14 @@ WebIDL::ExceptionOr<void> Animation::set_playback_rate(double new_playback_rate)
|
|||
}
|
||||
|
||||
// https://www.w3.org/TR/web-animations-1/#animation-play-state
|
||||
Bindings::AnimationPlayState Animation::play_state_for_bindings() const
|
||||
{
|
||||
if (m_owning_element)
|
||||
m_owning_element->document().update_style();
|
||||
|
||||
return play_state();
|
||||
}
|
||||
|
||||
Bindings::AnimationPlayState Animation::play_state() const
|
||||
{
|
||||
// The play state of animation, animation, at a given moment is the state corresponding to the first matching
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ public:
|
|||
double playback_rate() const { return m_playback_rate; }
|
||||
WebIDL::ExceptionOr<void> set_playback_rate(double value);
|
||||
|
||||
Bindings::AnimationPlayState play_state_for_bindings() const;
|
||||
Bindings::AnimationPlayState play_state() const;
|
||||
|
||||
bool is_relevant() const;
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ interface Animation : EventTarget {
|
|||
attribute double? startTime;
|
||||
attribute double? currentTime;
|
||||
attribute double playbackRate;
|
||||
readonly attribute AnimationPlayState playState;
|
||||
[ImplementedAs=play_state_for_bindings] readonly attribute AnimationPlayState playState;
|
||||
readonly attribute AnimationReplaceState replaceState;
|
||||
readonly attribute boolean pending;
|
||||
readonly attribute Promise<Animation> ready;
|
||||
|
|
|
|||
|
|
@ -0,0 +1,15 @@
|
|||
Harness status: OK
|
||||
|
||||
Found 9 tests
|
||||
|
||||
4 Pass
|
||||
5 Fail
|
||||
Fail Animated style is cleared after canceling a running CSS animation
|
||||
Fail Animated style is cleared after canceling a filling CSS animation
|
||||
Pass After canceling an animation, it can still be seeked
|
||||
Fail After canceling an animation, it can still be re-used
|
||||
Fail After canceling an animation, updating animation properties doesn't make it live again
|
||||
Fail After canceling an animation, updating animation-play-state doesn't make it live again
|
||||
Pass Setting animation-name to 'none' cancels the animation
|
||||
Pass Setting display:none on an element cancel its animations
|
||||
Pass Setting display:none on an ancestor element cancels animations on descendants
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
Harness status: OK
|
||||
|
||||
Found 5 tests
|
||||
|
||||
5 Pass
|
||||
Pass A new CSS animation is initially play-pending
|
||||
Pass Animation returns correct playState when paused
|
||||
Pass Animation.playState updates when paused by script
|
||||
Pass Animation.playState updates when resumed by setting style
|
||||
Pass Animation returns correct playState when canceled
|
||||
|
|
@ -0,0 +1,196 @@
|
|||
<!doctype html>
|
||||
<meta charset=utf-8>
|
||||
<title>Canceling a CSS animation</title>
|
||||
<!-- TODO: Add a more specific link for this once it is specified. -->
|
||||
<link rel="help" href="https://drafts.csswg.org/css-animations-2/">
|
||||
<script src="../../resources/testharness.js"></script>
|
||||
<script src="../../resources/testharnessreport.js"></script>
|
||||
<script src="support/testcommon.js"></script>
|
||||
<style>
|
||||
@keyframes translateAnim {
|
||||
to { transform: translate(100px) }
|
||||
}
|
||||
@keyframes marginLeftAnim {
|
||||
to { margin-left: 100px }
|
||||
}
|
||||
@keyframes marginLeftAnim100To200 {
|
||||
from { margin-left: 100px }
|
||||
to { margin-left: 200px }
|
||||
}
|
||||
</style>
|
||||
<div id="log"></div>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
promise_test(async t => {
|
||||
const div = addDiv(t, { style: 'animation: translateAnim 100s' });
|
||||
const animation = div.getAnimations()[0];
|
||||
|
||||
await animation.ready;
|
||||
|
||||
assert_not_equals(getComputedStyle(div).transform, 'none',
|
||||
'transform style is animated before canceling');
|
||||
animation.cancel();
|
||||
assert_equals(getComputedStyle(div).transform, 'none',
|
||||
'transform style is no longer animated after canceling');
|
||||
}, 'Animated style is cleared after canceling a running CSS animation');
|
||||
|
||||
promise_test(async t => {
|
||||
const div = addDiv(t, { style: 'animation: translateAnim 100s forwards' });
|
||||
const animation = div.getAnimations()[0];
|
||||
animation.finish();
|
||||
|
||||
await animation.ready;
|
||||
|
||||
assert_not_equals(getComputedStyle(div).transform, 'none',
|
||||
'transform style is filling before canceling');
|
||||
animation.cancel();
|
||||
assert_equals(getComputedStyle(div).transform, 'none',
|
||||
'fill style is cleared after canceling');
|
||||
}, 'Animated style is cleared after canceling a filling CSS animation');
|
||||
|
||||
test(t => {
|
||||
const div = addDiv(t, { style: 'animation: marginLeftAnim 100s linear' });
|
||||
const animation = div.getAnimations()[0];
|
||||
animation.cancel();
|
||||
|
||||
assert_equals(getComputedStyle(div).marginLeft, '0px',
|
||||
'margin-left style is not animated after canceling');
|
||||
|
||||
animation.currentTime = 50 * 1000;
|
||||
assert_equals(getComputedStyle(div).marginLeft, '50px',
|
||||
'margin-left style is updated when canceled animation is'
|
||||
+ ' seeked');
|
||||
}, 'After canceling an animation, it can still be seeked');
|
||||
|
||||
promise_test(async t => {
|
||||
const div =
|
||||
addDiv(t, { style: 'animation: marginLeftAnim100To200 100s linear' });
|
||||
const animation = div.getAnimations()[0];
|
||||
|
||||
await animation.ready;
|
||||
|
||||
animation.cancel();
|
||||
assert_equals(getComputedStyle(div).marginLeft, '0px',
|
||||
'margin-left style is not animated after canceling');
|
||||
animation.play();
|
||||
assert_equals(getComputedStyle(div).marginLeft, '100px',
|
||||
'margin-left style is animated after re-starting animation');
|
||||
|
||||
await animation.ready;
|
||||
|
||||
assert_equals(animation.playState, 'running',
|
||||
'Animation succeeds in running after being re-started');
|
||||
}, 'After canceling an animation, it can still be re-used');
|
||||
|
||||
test(t => {
|
||||
const div =
|
||||
addDiv(t, { style: 'animation: marginLeftAnim100To200 100s linear' });
|
||||
const animation = div.getAnimations()[0];
|
||||
animation.cancel();
|
||||
assert_equals(getComputedStyle(div).marginLeft, '0px',
|
||||
'margin-left style is not animated after canceling');
|
||||
|
||||
// Trigger a change to some animation properties and check that this
|
||||
// doesn't cause the animation to become live again
|
||||
div.style.animationDuration = '200s';
|
||||
assert_equals(getComputedStyle(div).marginLeft, '0px',
|
||||
'margin-left style is still not animated after updating'
|
||||
+ ' animation-duration');
|
||||
assert_equals(animation.playState, 'idle',
|
||||
'Animation is still idle after updating animation-duration');
|
||||
}, 'After canceling an animation, updating animation properties doesn\'t make'
|
||||
+ ' it live again');
|
||||
|
||||
test(t => {
|
||||
const div =
|
||||
addDiv(t, { style: 'animation: marginLeftAnim100To200 100s linear' });
|
||||
const animation = div.getAnimations()[0];
|
||||
animation.cancel();
|
||||
assert_equals(getComputedStyle(div).marginLeft, '0px',
|
||||
'margin-left style is not animated after canceling');
|
||||
|
||||
// Make some changes to animation-play-state and check that the
|
||||
// animation doesn't become live again. This is because it should be
|
||||
// possible to cancel an animation from script such that all future
|
||||
// changes to style are ignored.
|
||||
|
||||
// Redundant change
|
||||
div.style.animationPlayState = 'running';
|
||||
assert_equals(animation.playState, 'idle',
|
||||
'Animation is still idle after a redundant change to'
|
||||
+ ' animation-play-state');
|
||||
|
||||
// Pause
|
||||
div.style.animationPlayState = 'paused';
|
||||
assert_equals(animation.playState, 'idle',
|
||||
'Animation is still idle after setting'
|
||||
+ ' animation-play-state: paused');
|
||||
|
||||
// Play
|
||||
div.style.animationPlayState = 'running';
|
||||
assert_equals(animation.playState, 'idle',
|
||||
'Animation is still idle after re-setting'
|
||||
+ ' animation-play-state: running');
|
||||
|
||||
}, 'After canceling an animation, updating animation-play-state doesn\'t'
|
||||
+ ' make it live again');
|
||||
|
||||
promise_test(async t => {
|
||||
const div = addDiv(t, { style: 'animation: translateAnim 10s both' });
|
||||
div.style.marginLeft = '0px';
|
||||
|
||||
const animation = div.getAnimations()[0];
|
||||
|
||||
await animation.ready;
|
||||
|
||||
assert_equals(animation.playState, 'running');
|
||||
|
||||
div.style.animationName = 'none';
|
||||
flushComputedStyle(div);
|
||||
|
||||
await waitForFrame();
|
||||
|
||||
assert_equals(animation.playState, 'idle');
|
||||
assert_equals(getComputedStyle(div).marginLeft, '0px');
|
||||
}, 'Setting animation-name to \'none\' cancels the animation');
|
||||
|
||||
promise_test(async t => {
|
||||
const div = addDiv(t, { style: 'animation: translateAnim 10s both' });
|
||||
const animation = div.getAnimations()[0];
|
||||
|
||||
await animation.ready;
|
||||
|
||||
assert_equals(animation.playState, 'running');
|
||||
|
||||
div.style.display = 'none';
|
||||
|
||||
await waitForFrame();
|
||||
|
||||
assert_equals(animation.playState, 'idle');
|
||||
assert_equals(getComputedStyle(div).marginLeft, '0px');
|
||||
}, 'Setting display:none on an element cancel its animations');
|
||||
|
||||
promise_test(async t => {
|
||||
const parentDiv = addDiv(t);
|
||||
const childDiv = document.createElement('div');
|
||||
parentDiv.appendChild(childDiv);
|
||||
|
||||
childDiv.setAttribute('style', 'animation: translateAnim 10s both');
|
||||
flushComputedStyle(childDiv);
|
||||
|
||||
const animation = childDiv.getAnimations()[0];
|
||||
|
||||
await animation.ready;
|
||||
|
||||
assert_equals(animation.playState, 'running');
|
||||
|
||||
parentDiv.style.display = 'none';
|
||||
await waitForFrame();
|
||||
|
||||
assert_equals(animation.playState, 'idle');
|
||||
assert_equals(getComputedStyle(childDiv).marginLeft, '0px');
|
||||
}, 'Setting display:none on an ancestor element cancels animations on ' +
|
||||
'descendants');
|
||||
|
||||
</script>
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
<!doctype html>
|
||||
<meta charset=utf-8>
|
||||
<title>CSSAnimation.playState</title>
|
||||
<!-- TODO: Add a more specific link for this once it is specified. -->
|
||||
<link rel="help" href="https://drafts.csswg.org/css-animations-2/#cssanimation">
|
||||
<script src="../../resources/testharness.js"></script>
|
||||
<script src="../../resources/testharnessreport.js"></script>
|
||||
<script src="support/testcommon.js"></script>
|
||||
<style>
|
||||
@keyframes anim { }
|
||||
</style>
|
||||
<div id="log"></div>
|
||||
<script>
|
||||
'use strict';
|
||||
|
||||
test(t => {
|
||||
const div = addDiv(t, { 'style': 'animation: anim 100s' });
|
||||
const animation = div.getAnimations()[0];
|
||||
assert_true(animation.pending);
|
||||
assert_equals(animation.playState, 'running');
|
||||
assert_equals(animation.startTime, null);
|
||||
}, 'A new CSS animation is initially play-pending');
|
||||
|
||||
test(t => {
|
||||
const div = addDiv(t, { 'style': 'animation: anim 1000s paused' });
|
||||
const animation = div.getAnimations()[0];
|
||||
assert_equals(animation.playState, 'paused');
|
||||
}, 'Animation returns correct playState when paused');
|
||||
|
||||
test(t => {
|
||||
const div = addDiv(t, { 'style': 'animation: anim 1000s' });
|
||||
const animation = div.getAnimations()[0];
|
||||
animation.pause();
|
||||
assert_equals(animation.playState, 'paused');
|
||||
}, 'Animation.playState updates when paused by script');
|
||||
|
||||
test(t => {
|
||||
const div = addDiv(t, { 'style': 'animation: anim 1000s paused' });
|
||||
const animation = div.getAnimations()[0];
|
||||
div.style.animationPlayState = 'running';
|
||||
|
||||
// This test also checks that calling playState flushes style
|
||||
assert_equals(animation.playState, 'running',
|
||||
'Animation.playState reports running after updating'
|
||||
+ ' animation-play-state (got: ' + animation.playState + ')');
|
||||
}, 'Animation.playState updates when resumed by setting style');
|
||||
|
||||
test(t => {
|
||||
const div = addDiv(t, { 'style': 'animation: anim 1000s' });
|
||||
const animation = div.getAnimations()[0];
|
||||
animation.cancel();
|
||||
assert_equals(animation.playState, 'idle');
|
||||
}, 'Animation returns correct playState when canceled');
|
||||
|
||||
</script>
|
||||
Loading…
Add table
Add a link
Reference in a new issue