1/*
2 * Copyright (C) 2007 Apple Inc. All rights reserved.
3 *
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
6 * are met:
7 *
8 * 1. Redistributions of source code must retain the above copyright
9 * notice, this list of conditions and the following disclaimer.
10 * 2. Redistributions in binary form must reproduce the above copyright
11 * notice, this list of conditions and the following disclaimer in the
12 * documentation and/or other materials provided with the distribution.
13 * 3. Neither the name of Apple Inc. ("Apple") nor the names of
14 * its contributors may be used to endorse or promote products derived
15 * from this software without specific prior written permission.
16 *
17 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
18 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
19 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
20 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
21 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
22 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
23 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
24 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 */
28
29#include "config.h"
30#include "CompositeAnimation.h"
31
32#include "CSSAnimationControllerPrivate.h"
33#include "CSSPropertyAnimation.h"
34#include "CSSPropertyNames.h"
35#include "ImplicitAnimation.h"
36#include "KeyframeAnimation.h"
37#include "Logging.h"
38#include "RenderElement.h"
39#include "RenderStyle.h"
40#include <wtf/NeverDestroyed.h>
41#include <wtf/text/CString.h>
42
43namespace WebCore {
44
45CompositeAnimation::CompositeAnimation(CSSAnimationControllerPrivate& animationController)
46 : m_animationController(animationController)
47{
48 m_suspended = m_animationController.isSuspended() && !m_animationController.allowsNewAnimationsWhileSuspended();
49}
50
51CompositeAnimation::~CompositeAnimation()
52{
53 // Toss the refs to all animations, but make sure we remove them from
54 // any waiting lists first.
55
56 clearElement();
57 m_transitions.clear();
58 m_keyframeAnimations.clear();
59}
60
61void CompositeAnimation::clearElement()
62{
63 if (!m_transitions.isEmpty()) {
64 // Clear the renderers from all running animations, in case we are in the middle of
65 // an animation callback (see https://bugs.webkit.org/show_bug.cgi?id=22052)
66 for (auto& transition : m_transitions.values()) {
67 animationController().animationWillBeRemoved(*transition);
68 transition->clear();
69 }
70 }
71 if (!m_keyframeAnimations.isEmpty()) {
72 m_keyframeAnimations.checkConsistency();
73 for (auto& animation : m_keyframeAnimations.values()) {
74 animationController().animationWillBeRemoved(*animation);
75 animation->clear();
76 }
77 }
78}
79
80void CompositeAnimation::updateTransitions(Element& element, const RenderStyle* currentStyle, const RenderStyle& targetStyle)
81{
82 // If currentStyle is null or there are no old or new transitions, just skip it
83 if (!currentStyle || (!targetStyle.transitions() && m_transitions.isEmpty()))
84 return;
85
86 // Mark all existing transitions as no longer active. We will mark the still active ones
87 // in the next loop and then toss the ones that didn't get marked.
88 for (auto& transition : m_transitions.values())
89 transition->setActive(false);
90
91 std::unique_ptr<RenderStyle> modifiedCurrentStyle;
92
93 // Check to see if we need to update the active transitions
94 if (targetStyle.transitions()) {
95 for (size_t i = 0; i < targetStyle.transitions()->size(); ++i) {
96 auto& animation = targetStyle.transitions()->animation(i);
97 bool isActiveTransition = animation.duration() || animation.delay() > 0;
98
99 Animation::AnimationMode mode = animation.animationMode();
100 if (mode == Animation::AnimateNone || mode == Animation::AnimateUnknownProperty)
101 continue;
102
103 CSSPropertyID prop = animation.property();
104
105 bool all = mode == Animation::AnimateAll;
106
107 // Handle both the 'all' and single property cases. For the single prop case, we make only one pass
108 // through the loop.
109 for (int propertyIndex = 0; propertyIndex < CSSPropertyAnimation::getNumProperties(); ++propertyIndex) {
110 if (all) {
111 // Get the next property which is not a shorthand.
112 Optional<bool> isShorthand;
113 prop = CSSPropertyAnimation::getPropertyAtIndex(propertyIndex, isShorthand);
114 if (isShorthand && *isShorthand)
115 continue;
116 }
117
118 if (prop == CSSPropertyInvalid) {
119 if (!all)
120 break;
121 continue;
122 }
123
124 // ImplicitAnimations are always hashed by actual properties, never animateAll.
125 ASSERT(prop >= firstCSSProperty && prop < (firstCSSProperty + numCSSProperties));
126
127 // If there is a running animation for this property, the transition is overridden
128 // and we have to use the unanimatedStyle from the animation. We do the test
129 // against the unanimated style here, but we "override" the transition later.
130 auto* keyframeAnimation = animationForProperty(prop);
131 auto* fromStyle = keyframeAnimation ? &keyframeAnimation->unanimatedStyle() : currentStyle;
132
133 // See if there is a current transition for this prop
134 ImplicitAnimation* implAnim = m_transitions.get(prop);
135 bool equal = true;
136
137 if (implAnim) {
138 // If we are post active don't bother setting the active flag. This will cause
139 // this animation to get removed at the end of this function.
140 if (!implAnim->postActive())
141 implAnim->setActive(true);
142
143 // This might be a transition that is just finishing. That would be the case
144 // if it were postActive. But we still need to check for equality because
145 // it could be just finishing AND changing to a new goal state.
146 //
147 // This implAnim might also not be an already running transition. It might be
148 // newly added to the list in a previous iteration. This would happen if
149 // you have both an explicit transition-property and 'all' in the same
150 // list. In this case, the latter one overrides the earlier one, so we
151 // behave as though this is a running animation being replaced.
152 if (!implAnim->isTargetPropertyEqual(prop, &targetStyle)) {
153 // For accelerated animations we need to return a new RenderStyle with the _current_ value
154 // of the property, so that restarted transitions use the correct starting point.
155 if (CSSPropertyAnimation::animationOfPropertyIsAccelerated(prop) && implAnim->isAccelerated()) {
156 if (!modifiedCurrentStyle)
157 modifiedCurrentStyle = RenderStyle::clonePtr(*currentStyle);
158
159 implAnim->blendPropertyValueInStyle(prop, modifiedCurrentStyle.get());
160 }
161 LOG(Animations, "Removing existing ImplicitAnimation %p for property %s", implAnim, getPropertyName(prop));
162 animationController().animationWillBeRemoved(*implAnim);
163 m_transitions.remove(prop);
164 equal = false;
165 }
166 } else {
167 // We need to start a transition if it is active and the properties don't match
168 equal = !isActiveTransition || CSSPropertyAnimation::propertiesEqual(prop, fromStyle, &targetStyle) || !CSSPropertyAnimation::canPropertyBeInterpolated(prop, fromStyle, &targetStyle);
169 }
170
171 // We can be in this loop with an inactive transition (!isActiveTransition). We need
172 // to do that to check to see if we are canceling a transition. But we don't want to
173 // start one of the inactive transitions. So short circuit that here. (See
174 // <https://bugs.webkit.org/show_bug.cgi?id=24787>
175 if (!equal && isActiveTransition) {
176 // Add the new transition
177 auto implicitAnimation = ImplicitAnimation::create(animation, prop, element, *this, modifiedCurrentStyle ? *modifiedCurrentStyle : *fromStyle);
178 if (m_suspended && implicitAnimation->hasStyle())
179 implicitAnimation->updatePlayState(AnimationPlayState::Paused);
180
181 LOG(Animations, "Created ImplicitAnimation %p on element %p for property %s duration %.2f delay %.2f", implicitAnimation.ptr(), &element, getPropertyName(prop), animation.duration(), animation.delay());
182 m_transitions.set(prop, WTFMove(implicitAnimation));
183 }
184
185 // We only need one pass for the single prop case
186 if (!all)
187 break;
188 }
189 }
190 }
191
192 // Make a list of transitions to be removed
193 Vector<int> toBeRemoved;
194 for (auto& transition : m_transitions.values()) {
195 if (!transition->active()) {
196 animationController().animationWillBeRemoved(*transition);
197 toBeRemoved.append(transition->animatingProperty());
198 LOG(Animations, "Removing ImplicitAnimation %p from element %p for property %s", transition.get(), &element, getPropertyName(transition->animatingProperty()));
199 }
200 }
201
202 // Now remove the transitions from the list
203 for (auto propertyToRemove : toBeRemoved)
204 m_transitions.remove(propertyToRemove);
205}
206
207void CompositeAnimation::updateKeyframeAnimations(Element& element, const RenderStyle* currentStyle, const RenderStyle& targetStyle)
208{
209 // Nothing to do if we don't have any animations, and didn't have any before
210 if (m_keyframeAnimations.isEmpty() && !targetStyle.hasAnimations())
211 return;
212
213 m_keyframeAnimations.checkConsistency();
214
215 if (currentStyle && currentStyle->hasAnimations() && targetStyle.hasAnimations() && *(currentStyle->animations()) == *(targetStyle.animations()))
216 return;
217
218 AnimationNameMap newAnimations;
219
220 // Toss the animation order map.
221 m_keyframeAnimationOrderMap.clear();
222
223 static NeverDestroyed<const AtomString> none("none", AtomString::ConstructFromLiteral);
224
225 // Now mark any still active animations as active and add any new animations.
226 if (targetStyle.animations()) {
227 int numAnims = targetStyle.animations()->size();
228 for (int i = 0; i < numAnims; ++i) {
229 auto& animation = targetStyle.animations()->animation(i);
230 AtomString animationName(animation.name());
231
232 if (!animation.isValidAnimation())
233 continue;
234
235 // See if there is a current animation for this name.
236 RefPtr<KeyframeAnimation> keyframeAnim = m_keyframeAnimations.get(animationName.impl());
237 if (keyframeAnim) {
238 newAnimations.add(keyframeAnim->name().impl(), keyframeAnim);
239
240 if (keyframeAnim->postActive())
241 continue;
242
243 // Animations match, but play states may differ. Update if needed.
244 keyframeAnim->updatePlayState(animation.playState());
245
246 // Set the saved animation to this new one, just in case the play state has changed.
247 keyframeAnim->setAnimation(animation);
248 } else if ((animation.duration() || animation.delay()) && animation.iterationCount() && animationName != none) {
249 keyframeAnim = KeyframeAnimation::create(animation, element, *this, targetStyle);
250 LOG(Animations, "Creating KeyframeAnimation %p on element %p with keyframes %s, duration %.2f, delay %.2f, iterations %.2f", keyframeAnim.get(), &element, animation.name().utf8().data(), animation.duration(), animation.delay(), animation.iterationCount());
251
252 if (m_suspended) {
253 keyframeAnim->updatePlayState(AnimationPlayState::Paused);
254 LOG(Animations, " (created in suspended/paused state)");
255 }
256#if !LOG_DISABLED
257 for (auto propertyID : keyframeAnim->keyframes().properties())
258 LOG(Animations, " property %s", getPropertyName(propertyID));
259#endif
260
261 newAnimations.set(keyframeAnim->name().impl(), keyframeAnim);
262 }
263
264 // Add this to the animation order map.
265 if (keyframeAnim)
266 m_keyframeAnimationOrderMap.append(keyframeAnim->name().impl());
267 }
268 }
269
270 // Make a list of animations to be removed.
271 for (auto& animation : m_keyframeAnimations.values()) {
272 if (!newAnimations.contains(animation->name().impl())) {
273 animationController().animationWillBeRemoved(*animation);
274 animation->clear();
275 LOG(Animations, "Removing KeyframeAnimation %p from element %p", animation.get(), &element);
276 }
277 }
278
279 std::swap(newAnimations, m_keyframeAnimations);
280}
281
282AnimationUpdate CompositeAnimation::animate(Element& element, const RenderStyle* currentStyle, const RenderStyle& targetStyle)
283{
284 // We don't do any transitions if we don't have a currentStyle (on startup).
285 updateTransitions(element, currentStyle, targetStyle);
286 updateKeyframeAnimations(element, currentStyle, targetStyle);
287 m_keyframeAnimations.checkConsistency();
288
289 bool animationChangeRequiresRecomposite = false;
290 bool forceStackingContext = false;
291
292 std::unique_ptr<RenderStyle> animatedStyle;
293
294 if (currentStyle) {
295 // Now that we have transition objects ready, let them know about the new goal state. We want them
296 // to fill in a RenderStyle*& only if needed.
297 bool checkForStackingContext = false;
298 for (auto& transition : m_transitions.values()) {
299 auto changes = transition->animate(*this, targetStyle, animatedStyle);
300 if (changes.contains(AnimateChange::StyleBlended))
301 checkForStackingContext |= WillChangeData::propertyCreatesStackingContext(transition->animatingProperty());
302
303 animationChangeRequiresRecomposite = changes.contains(AnimateChange::RunningStateChange) && transition->affectsAcceleratedProperty();
304 }
305
306 if (animatedStyle && checkForStackingContext) {
307 // Note that this is similar to code in StyleResolver::adjustRenderStyle() but only needs to consult
308 // animatable properties that can trigger stacking context.
309 if (animatedStyle->opacity() < 1.0f
310 || animatedStyle->hasTransformRelatedProperty()
311 || animatedStyle->hasMask()
312 || animatedStyle->clipPath()
313 || animatedStyle->boxReflect()
314 || animatedStyle->hasFilter()
315#if ENABLE(FILTERS_LEVEL_2)
316 || animatedStyle->hasBackdropFilter()
317#endif
318 )
319 forceStackingContext = true;
320 }
321 }
322
323 // Now that we have animation objects ready, let them know about the new goal state. We want them
324 // to fill in a RenderStyle*& only if needed.
325 for (auto& name : m_keyframeAnimationOrderMap) {
326 RefPtr<KeyframeAnimation> keyframeAnim = m_keyframeAnimations.get(name);
327 if (keyframeAnim) {
328 auto changes = keyframeAnim->animate(*this, targetStyle, animatedStyle);
329 animationChangeRequiresRecomposite = changes.contains(AnimateChange::RunningStateChange) && keyframeAnim->affectsAcceleratedProperty();
330 forceStackingContext |= changes.contains(AnimateChange::StyleBlended) && keyframeAnim->triggersStackingContext();
331 m_hasAnimationThatDependsOnLayout |= keyframeAnim->dependsOnLayout();
332 }
333 }
334
335 // https://drafts.csswg.org/css-animations-1/
336 // While an animation is applied but has not finished, or has finished but has an animation-fill-mode of forwards or both,
337 // the user agent must act as if the will-change property ([css-will-change-1]) on the element additionally
338 // includes all the properties animated by the animation.
339 if (forceStackingContext && animatedStyle) {
340 if (animatedStyle->hasAutoZIndex())
341 animatedStyle->setZIndex(0);
342 }
343
344 return { WTFMove(animatedStyle), animationChangeRequiresRecomposite };
345}
346
347std::unique_ptr<RenderStyle> CompositeAnimation::getAnimatedStyle() const
348{
349 std::unique_ptr<RenderStyle> resultStyle;
350 for (auto& transition : m_transitions.values())
351 transition->getAnimatedStyle(resultStyle);
352
353 m_keyframeAnimations.checkConsistency();
354
355 for (auto& name : m_keyframeAnimationOrderMap) {
356 RefPtr<KeyframeAnimation> keyframeAnimation = m_keyframeAnimations.get(name);
357 if (keyframeAnimation)
358 keyframeAnimation->getAnimatedStyle(resultStyle);
359 }
360
361 return resultStyle;
362}
363
364Optional<Seconds> CompositeAnimation::timeToNextService() const
365{
366 // Returns the time at which next service is required. WTF::nullopt means no service is required. 0 means
367 // service is required now, and > 0 means service is required that many seconds in the future.
368 Optional<Seconds> minT;
369
370 if (!m_transitions.isEmpty()) {
371 for (auto& transition : m_transitions.values()) {
372 Optional<Seconds> t = transition->timeToNextService();
373 if (!t)
374 continue;
375 if (!minT || t.value() < minT.value())
376 minT = t.value();
377 if (minT.value() == 0_s)
378 return 0_s;
379 }
380 }
381 if (!m_keyframeAnimations.isEmpty()) {
382 m_keyframeAnimations.checkConsistency();
383 for (auto& animation : m_keyframeAnimations.values()) {
384 Optional<Seconds> t = animation->timeToNextService();
385 if (!t)
386 continue;
387 if (!minT || t.value() < minT.value())
388 minT = t.value();
389 if (minT.value() == 0_s)
390 return 0_s;
391 }
392 }
393
394 return minT;
395}
396
397KeyframeAnimation* CompositeAnimation::animationForProperty(CSSPropertyID property) const
398{
399 KeyframeAnimation* result = nullptr;
400
401 // We want to send back the last animation with the property if there are multiples.
402 // So we need to iterate through all animations
403 if (!m_keyframeAnimations.isEmpty()) {
404 m_keyframeAnimations.checkConsistency();
405 for (auto& animation : m_keyframeAnimations.values()) {
406 if (animation->hasAnimationForProperty(property))
407 result = animation.get();
408 }
409 }
410
411 return result;
412}
413
414bool CompositeAnimation::computeExtentOfTransformAnimation(LayoutRect& bounds) const
415{
416 // If more than one transition and animation affect transform, give up.
417 bool seenTransformAnimation = false;
418
419 for (auto& animation : m_keyframeAnimations.values()) {
420 if (!animation->hasAnimationForProperty(CSSPropertyTransform))
421 continue;
422
423 if (seenTransformAnimation)
424 return false;
425
426 seenTransformAnimation = true;
427
428 if (!animation->computeExtentOfTransformAnimation(bounds))
429 return false;
430 }
431
432 for (auto& transition : m_transitions.values()) {
433 if (transition->animatingProperty() != CSSPropertyTransform || !transition->hasStyle())
434 continue;
435
436 if (seenTransformAnimation)
437 return false;
438
439 if (!transition->computeExtentOfTransformAnimation(bounds))
440 return false;
441 }
442
443 return true;
444}
445
446void CompositeAnimation::suspendAnimations()
447{
448 if (m_suspended)
449 return;
450
451 m_suspended = true;
452
453 if (!m_keyframeAnimations.isEmpty()) {
454 m_keyframeAnimations.checkConsistency();
455 for (auto& animation : m_keyframeAnimations.values())
456 animation->updatePlayState(AnimationPlayState::Paused);
457 }
458
459 if (!m_transitions.isEmpty()) {
460 for (auto& transition : m_transitions.values()) {
461 if (transition->hasStyle())
462 transition->updatePlayState(AnimationPlayState::Paused);
463 }
464 }
465}
466
467void CompositeAnimation::resumeAnimations()
468{
469 if (!m_suspended)
470 return;
471
472 m_suspended = false;
473
474 if (!m_keyframeAnimations.isEmpty()) {
475 m_keyframeAnimations.checkConsistency();
476 for (auto& animation : m_keyframeAnimations.values()) {
477 if (animation->playStatePlaying())
478 animation->updatePlayState(AnimationPlayState::Playing);
479 }
480 }
481
482 if (!m_transitions.isEmpty()) {
483 for (auto& transition : m_transitions.values()) {
484 if (transition->hasStyle())
485 transition->updatePlayState(AnimationPlayState::Playing);
486 }
487 }
488}
489
490void CompositeAnimation::overrideImplicitAnimations(CSSPropertyID property)
491{
492 if (!m_transitions.isEmpty()) {
493 for (auto& transition : m_transitions.values()) {
494 if (transition->animatingProperty() == property)
495 transition->setOverridden(true);
496 }
497 }
498}
499
500void CompositeAnimation::resumeOverriddenImplicitAnimations(CSSPropertyID property)
501{
502 if (!m_transitions.isEmpty()) {
503 for (auto& transition : m_transitions.values()) {
504 if (transition->animatingProperty() == property)
505 transition->setOverridden(false);
506 }
507 }
508}
509
510bool CompositeAnimation::isAnimatingProperty(CSSPropertyID property, bool acceleratedOnly) const
511{
512 if (!m_keyframeAnimations.isEmpty()) {
513 m_keyframeAnimations.checkConsistency();
514 for (auto& animation : m_keyframeAnimations.values()) {
515 if (animation->isAnimatingProperty(property, acceleratedOnly))
516 return true;
517 }
518 }
519
520 if (!m_transitions.isEmpty()) {
521 for (auto& transition : m_transitions.values()) {
522 if (transition->isAnimatingProperty(property, acceleratedOnly))
523 return true;
524 }
525 }
526 return false;
527}
528
529bool CompositeAnimation::pauseAnimationAtTime(const AtomString& name, double t)
530{
531 m_keyframeAnimations.checkConsistency();
532
533 RefPtr<KeyframeAnimation> keyframeAnim = m_keyframeAnimations.get(name.impl());
534 if (!keyframeAnim || !keyframeAnim->running())
535 return false;
536
537 keyframeAnim->freezeAtTime(t);
538 return true;
539}
540
541bool CompositeAnimation::pauseTransitionAtTime(CSSPropertyID property, double t)
542{
543 if ((property < firstCSSProperty) || (property >= firstCSSProperty + numCSSProperties))
544 return false;
545
546 ImplicitAnimation* implAnim = m_transitions.get(property);
547 if (!implAnim) {
548 // Check to see if this property is being animated via a shorthand.
549 // This code is only used for testing, so performance is not critical here.
550 HashSet<CSSPropertyID> shorthandProperties = CSSPropertyAnimation::animatableShorthandsAffectingProperty(property);
551 bool anyPaused = false;
552 for (auto propertyID : shorthandProperties) {
553 if (pauseTransitionAtTime(propertyID, t))
554 anyPaused = true;
555 }
556 return anyPaused;
557 }
558
559 if (!implAnim->running())
560 return false;
561
562 if ((t >= 0.0) && (t <= implAnim->duration())) {
563 implAnim->freezeAtTime(t);
564 return true;
565 }
566
567 return false;
568}
569
570unsigned CompositeAnimation::numberOfActiveAnimations() const
571{
572 unsigned count = 0;
573
574 m_keyframeAnimations.checkConsistency();
575 for (auto& animation : m_keyframeAnimations.values()) {
576 if (animation->running())
577 ++count;
578 }
579
580 for (auto& transition : m_transitions.values()) {
581 if (transition->running())
582 ++count;
583 }
584
585 return count;
586}
587
588} // namespace WebCore
589