Skip to content

Commit fbcef93

Browse files
Calme1709kalenikaliaksandr
authored andcommitted
LibWeb: Update style before getting animation play state
Pending style updates can influence this value
1 parent a95cde3 commit fbcef93

File tree

7 files changed

+286
-1
lines changed

7 files changed

+286
-1
lines changed

Libraries/LibWeb/Animations/Animation.cpp

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,14 @@ WebIDL::ExceptionOr<void> Animation::set_playback_rate(double new_playback_rate)
277277
}
278278

279279
// https://www.w3.org/TR/web-animations-1/#animation-play-state
280+
Bindings::AnimationPlayState Animation::play_state_for_bindings() const
281+
{
282+
if (m_owning_element)
283+
m_owning_element->document().update_style();
284+
285+
return play_state();
286+
}
287+
280288
Bindings::AnimationPlayState Animation::play_state() const
281289
{
282290
// The play state of animation, animation, at a given moment is the state corresponding to the first matching

Libraries/LibWeb/Animations/Animation.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ class Animation : public DOM::EventTarget {
4848
double playback_rate() const { return m_playback_rate; }
4949
WebIDL::ExceptionOr<void> set_playback_rate(double value);
5050

51+
Bindings::AnimationPlayState play_state_for_bindings() const;
5152
Bindings::AnimationPlayState play_state() const;
5253

5354
bool is_relevant() const;

Libraries/LibWeb/Animations/Animation.idl

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ interface Animation : EventTarget {
1414
attribute double? startTime;
1515
attribute double? currentTime;
1616
attribute double playbackRate;
17-
readonly attribute AnimationPlayState playState;
17+
[ImplementedAs=play_state_for_bindings] readonly attribute AnimationPlayState playState;
1818
readonly attribute AnimationReplaceState replaceState;
1919
readonly attribute boolean pending;
2020
readonly attribute Promise<Animation> ready;
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
Harness status: OK
2+
3+
Found 9 tests
4+
5+
4 Pass
6+
5 Fail
7+
Fail Animated style is cleared after canceling a running CSS animation
8+
Fail Animated style is cleared after canceling a filling CSS animation
9+
Pass After canceling an animation, it can still be seeked
10+
Fail After canceling an animation, it can still be re-used
11+
Fail After canceling an animation, updating animation properties doesn't make it live again
12+
Fail After canceling an animation, updating animation-play-state doesn't make it live again
13+
Pass Setting animation-name to 'none' cancels the animation
14+
Pass Setting display:none on an element cancel its animations
15+
Pass Setting display:none on an ancestor element cancels animations on descendants
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
Harness status: OK
2+
3+
Found 5 tests
4+
5+
5 Pass
6+
Pass A new CSS animation is initially play-pending
7+
Pass Animation returns correct playState when paused
8+
Pass Animation.playState updates when paused by script
9+
Pass Animation.playState updates when resumed by setting style
10+
Pass Animation returns correct playState when canceled
Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
<!doctype html>
2+
<meta charset=utf-8>
3+
<title>Canceling a CSS animation</title>
4+
<!-- TODO: Add a more specific link for this once it is specified. -->
5+
<link rel="help" href="https://drafts.csswg.org/css-animations-2/">
6+
<script src="../../resources/testharness.js"></script>
7+
<script src="../../resources/testharnessreport.js"></script>
8+
<script src="support/testcommon.js"></script>
9+
<style>
10+
@keyframes translateAnim {
11+
to { transform: translate(100px) }
12+
}
13+
@keyframes marginLeftAnim {
14+
to { margin-left: 100px }
15+
}
16+
@keyframes marginLeftAnim100To200 {
17+
from { margin-left: 100px }
18+
to { margin-left: 200px }
19+
}
20+
</style>
21+
<div id="log"></div>
22+
<script>
23+
'use strict';
24+
25+
promise_test(async t => {
26+
const div = addDiv(t, { style: 'animation: translateAnim 100s' });
27+
const animation = div.getAnimations()[0];
28+
29+
await animation.ready;
30+
31+
assert_not_equals(getComputedStyle(div).transform, 'none',
32+
'transform style is animated before canceling');
33+
animation.cancel();
34+
assert_equals(getComputedStyle(div).transform, 'none',
35+
'transform style is no longer animated after canceling');
36+
}, 'Animated style is cleared after canceling a running CSS animation');
37+
38+
promise_test(async t => {
39+
const div = addDiv(t, { style: 'animation: translateAnim 100s forwards' });
40+
const animation = div.getAnimations()[0];
41+
animation.finish();
42+
43+
await animation.ready;
44+
45+
assert_not_equals(getComputedStyle(div).transform, 'none',
46+
'transform style is filling before canceling');
47+
animation.cancel();
48+
assert_equals(getComputedStyle(div).transform, 'none',
49+
'fill style is cleared after canceling');
50+
}, 'Animated style is cleared after canceling a filling CSS animation');
51+
52+
test(t => {
53+
const div = addDiv(t, { style: 'animation: marginLeftAnim 100s linear' });
54+
const animation = div.getAnimations()[0];
55+
animation.cancel();
56+
57+
assert_equals(getComputedStyle(div).marginLeft, '0px',
58+
'margin-left style is not animated after canceling');
59+
60+
animation.currentTime = 50 * 1000;
61+
assert_equals(getComputedStyle(div).marginLeft, '50px',
62+
'margin-left style is updated when canceled animation is'
63+
+ ' seeked');
64+
}, 'After canceling an animation, it can still be seeked');
65+
66+
promise_test(async t => {
67+
const div =
68+
addDiv(t, { style: 'animation: marginLeftAnim100To200 100s linear' });
69+
const animation = div.getAnimations()[0];
70+
71+
await animation.ready;
72+
73+
animation.cancel();
74+
assert_equals(getComputedStyle(div).marginLeft, '0px',
75+
'margin-left style is not animated after canceling');
76+
animation.play();
77+
assert_equals(getComputedStyle(div).marginLeft, '100px',
78+
'margin-left style is animated after re-starting animation');
79+
80+
await animation.ready;
81+
82+
assert_equals(animation.playState, 'running',
83+
'Animation succeeds in running after being re-started');
84+
}, 'After canceling an animation, it can still be re-used');
85+
86+
test(t => {
87+
const div =
88+
addDiv(t, { style: 'animation: marginLeftAnim100To200 100s linear' });
89+
const animation = div.getAnimations()[0];
90+
animation.cancel();
91+
assert_equals(getComputedStyle(div).marginLeft, '0px',
92+
'margin-left style is not animated after canceling');
93+
94+
// Trigger a change to some animation properties and check that this
95+
// doesn't cause the animation to become live again
96+
div.style.animationDuration = '200s';
97+
assert_equals(getComputedStyle(div).marginLeft, '0px',
98+
'margin-left style is still not animated after updating'
99+
+ ' animation-duration');
100+
assert_equals(animation.playState, 'idle',
101+
'Animation is still idle after updating animation-duration');
102+
}, 'After canceling an animation, updating animation properties doesn\'t make'
103+
+ ' it live again');
104+
105+
test(t => {
106+
const div =
107+
addDiv(t, { style: 'animation: marginLeftAnim100To200 100s linear' });
108+
const animation = div.getAnimations()[0];
109+
animation.cancel();
110+
assert_equals(getComputedStyle(div).marginLeft, '0px',
111+
'margin-left style is not animated after canceling');
112+
113+
// Make some changes to animation-play-state and check that the
114+
// animation doesn't become live again. This is because it should be
115+
// possible to cancel an animation from script such that all future
116+
// changes to style are ignored.
117+
118+
// Redundant change
119+
div.style.animationPlayState = 'running';
120+
assert_equals(animation.playState, 'idle',
121+
'Animation is still idle after a redundant change to'
122+
+ ' animation-play-state');
123+
124+
// Pause
125+
div.style.animationPlayState = 'paused';
126+
assert_equals(animation.playState, 'idle',
127+
'Animation is still idle after setting'
128+
+ ' animation-play-state: paused');
129+
130+
// Play
131+
div.style.animationPlayState = 'running';
132+
assert_equals(animation.playState, 'idle',
133+
'Animation is still idle after re-setting'
134+
+ ' animation-play-state: running');
135+
136+
}, 'After canceling an animation, updating animation-play-state doesn\'t'
137+
+ ' make it live again');
138+
139+
promise_test(async t => {
140+
const div = addDiv(t, { style: 'animation: translateAnim 10s both' });
141+
div.style.marginLeft = '0px';
142+
143+
const animation = div.getAnimations()[0];
144+
145+
await animation.ready;
146+
147+
assert_equals(animation.playState, 'running');
148+
149+
div.style.animationName = 'none';
150+
flushComputedStyle(div);
151+
152+
await waitForFrame();
153+
154+
assert_equals(animation.playState, 'idle');
155+
assert_equals(getComputedStyle(div).marginLeft, '0px');
156+
}, 'Setting animation-name to \'none\' cancels the animation');
157+
158+
promise_test(async t => {
159+
const div = addDiv(t, { style: 'animation: translateAnim 10s both' });
160+
const animation = div.getAnimations()[0];
161+
162+
await animation.ready;
163+
164+
assert_equals(animation.playState, 'running');
165+
166+
div.style.display = 'none';
167+
168+
await waitForFrame();
169+
170+
assert_equals(animation.playState, 'idle');
171+
assert_equals(getComputedStyle(div).marginLeft, '0px');
172+
}, 'Setting display:none on an element cancel its animations');
173+
174+
promise_test(async t => {
175+
const parentDiv = addDiv(t);
176+
const childDiv = document.createElement('div');
177+
parentDiv.appendChild(childDiv);
178+
179+
childDiv.setAttribute('style', 'animation: translateAnim 10s both');
180+
flushComputedStyle(childDiv);
181+
182+
const animation = childDiv.getAnimations()[0];
183+
184+
await animation.ready;
185+
186+
assert_equals(animation.playState, 'running');
187+
188+
parentDiv.style.display = 'none';
189+
await waitForFrame();
190+
191+
assert_equals(animation.playState, 'idle');
192+
assert_equals(getComputedStyle(childDiv).marginLeft, '0px');
193+
}, 'Setting display:none on an ancestor element cancels animations on ' +
194+
'descendants');
195+
196+
</script>
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
<!doctype html>
2+
<meta charset=utf-8>
3+
<title>CSSAnimation.playState</title>
4+
<!-- TODO: Add a more specific link for this once it is specified. -->
5+
<link rel="help" href="https://drafts.csswg.org/css-animations-2/#cssanimation">
6+
<script src="../../resources/testharness.js"></script>
7+
<script src="../../resources/testharnessreport.js"></script>
8+
<script src="support/testcommon.js"></script>
9+
<style>
10+
@keyframes anim { }
11+
</style>
12+
<div id="log"></div>
13+
<script>
14+
'use strict';
15+
16+
test(t => {
17+
const div = addDiv(t, { 'style': 'animation: anim 100s' });
18+
const animation = div.getAnimations()[0];
19+
assert_true(animation.pending);
20+
assert_equals(animation.playState, 'running');
21+
assert_equals(animation.startTime, null);
22+
}, 'A new CSS animation is initially play-pending');
23+
24+
test(t => {
25+
const div = addDiv(t, { 'style': 'animation: anim 1000s paused' });
26+
const animation = div.getAnimations()[0];
27+
assert_equals(animation.playState, 'paused');
28+
}, 'Animation returns correct playState when paused');
29+
30+
test(t => {
31+
const div = addDiv(t, { 'style': 'animation: anim 1000s' });
32+
const animation = div.getAnimations()[0];
33+
animation.pause();
34+
assert_equals(animation.playState, 'paused');
35+
}, 'Animation.playState updates when paused by script');
36+
37+
test(t => {
38+
const div = addDiv(t, { 'style': 'animation: anim 1000s paused' });
39+
const animation = div.getAnimations()[0];
40+
div.style.animationPlayState = 'running';
41+
42+
// This test also checks that calling playState flushes style
43+
assert_equals(animation.playState, 'running',
44+
'Animation.playState reports running after updating'
45+
+ ' animation-play-state (got: ' + animation.playState + ')');
46+
}, 'Animation.playState updates when resumed by setting style');
47+
48+
test(t => {
49+
const div = addDiv(t, { 'style': 'animation: anim 1000s' });
50+
const animation = div.getAnimations()[0];
51+
animation.cancel();
52+
assert_equals(animation.playState, 'idle');
53+
}, 'Animation returns correct playState when canceled');
54+
55+
</script>

0 commit comments

Comments
 (0)