Computing animated style should not require renderers
[WebKit-https.git] / Source / WebCore / page / animation / CompositeAnimation.cpp
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
43 namespace WebCore {
44
45 CompositeAnimation::CompositeAnimation(CSSAnimationControllerPrivate& animationController)
46     : m_animationController(animationController)
47 {
48     m_suspended = m_animationController.isSuspended() && !m_animationController.allowsNewAnimationsWhileSuspended();
49 }
50
51 CompositeAnimation::~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
61 void 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.get());
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.get());
75             animation->clear();
76         }
77     }
78 }
79
80 void 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                     bool isShorthand;
113                     prop = CSSPropertyAnimation::getPropertyAtIndex(propertyIndex, isShorthand);
114                     if (isShorthand)
115                         continue;
116                 }
117
118                 // ImplicitAnimations are always hashed by actual properties, never animateAll.
119                 ASSERT(prop >= firstCSSProperty && prop < (firstCSSProperty + numCSSProperties));
120
121                 // If there is a running animation for this property, the transition is overridden
122                 // and we have to use the unanimatedStyle from the animation. We do the test
123                 // against the unanimated style here, but we "override" the transition later.
124                 auto* keyframeAnimation = animationForProperty(prop);
125                 auto* fromStyle = keyframeAnimation ? keyframeAnimation->unanimatedStyle() : currentStyle;
126
127                 // See if there is a current transition for this prop
128                 ImplicitAnimation* implAnim = m_transitions.get(prop);
129                 bool equal = true;
130
131                 if (implAnim) {
132                     // If we are post active don't bother setting the active flag. This will cause
133                     // this animation to get removed at the end of this function.
134                     if (!implAnim->postActive())
135                         implAnim->setActive(true);
136                     
137                     // This might be a transition that is just finishing. That would be the case
138                     // if it were postActive. But we still need to check for equality because
139                     // it could be just finishing AND changing to a new goal state.
140                     //
141                     // This implAnim might also not be an already running transition. It might be
142                     // newly added to the list in a previous iteration. This would happen if
143                     // you have both an explicit transition-property and 'all' in the same
144                     // list. In this case, the latter one overrides the earlier one, so we
145                     // behave as though this is a running animation being replaced.
146                     if (!implAnim->isTargetPropertyEqual(prop, &targetStyle)) {
147                         // For accelerated animations we need to return a new RenderStyle with the _current_ value
148                         // of the property, so that restarted transitions use the correct starting point.
149                         if (CSSPropertyAnimation::animationOfPropertyIsAccelerated(prop) && implAnim->isAccelerated()) {
150                             if (!modifiedCurrentStyle)
151                                 modifiedCurrentStyle = RenderStyle::clonePtr(*currentStyle);
152
153                             implAnim->blendPropertyValueInStyle(prop, modifiedCurrentStyle.get());
154                         }
155                         LOG(Animations, "Removing existing ImplicitAnimation %p for property %s", implAnim, getPropertyName(prop));
156                         animationController().animationWillBeRemoved(implAnim);
157                         m_transitions.remove(prop);
158                         equal = false;
159                     }
160                 } else {
161                     // We need to start a transition if it is active and the properties don't match
162                     equal = !isActiveTransition || CSSPropertyAnimation::propertiesEqual(prop, fromStyle, &targetStyle);
163                 }
164
165                 // We can be in this loop with an inactive transition (!isActiveTransition). We need
166                 // to do that to check to see if we are canceling a transition. But we don't want to
167                 // start one of the inactive transitions. So short circuit that here. (See
168                 // <https://bugs.webkit.org/show_bug.cgi?id=24787>
169                 if (!equal && isActiveTransition) {
170                     // Add the new transition
171                     auto implicitAnimation = ImplicitAnimation::create(animation, prop, element, *this, modifiedCurrentStyle ? *modifiedCurrentStyle : *fromStyle);
172                     if (m_suspended && implicitAnimation->hasStyle())
173                         implicitAnimation->updatePlayState(AnimPlayStatePaused);
174
175                     LOG(Animations, "Created ImplicitAnimation %p on element %p for property %s duration %.2f delay %.2f", implicitAnimation.ptr(), &element, getPropertyName(prop), animation.duration(), animation.delay());
176                     m_transitions.set(prop, WTFMove(implicitAnimation));
177                 }
178
179                 // We only need one pass for the single prop case
180                 if (!all)
181                     break;
182             }
183         }
184     }
185
186     // Make a list of transitions to be removed
187     Vector<int> toBeRemoved;
188     for (auto& transition : m_transitions.values()) {
189         if (!transition->active()) {
190             animationController().animationWillBeRemoved(transition.get());
191             toBeRemoved.append(transition->animatingProperty());
192             LOG(Animations, "Removing ImplicitAnimation %p from element %p for property %s", transition.get(), &element, getPropertyName(transition->animatingProperty()));
193         }
194     }
195
196     // Now remove the transitions from the list
197     for (auto propertyToRemove : toBeRemoved)
198         m_transitions.remove(propertyToRemove);
199 }
200
201 void CompositeAnimation::updateKeyframeAnimations(Element& element, const RenderStyle* currentStyle, const RenderStyle& targetStyle)
202 {
203     // Nothing to do if we don't have any animations, and didn't have any before
204     if (m_keyframeAnimations.isEmpty() && !targetStyle.hasAnimations())
205         return;
206
207     m_keyframeAnimations.checkConsistency();
208     
209     if (currentStyle && currentStyle->hasAnimations() && targetStyle.hasAnimations() && *(currentStyle->animations()) == *(targetStyle.animations()))
210         return;
211
212 #if ENABLE(CSS_ANIMATIONS_LEVEL_2)
213     m_hasScrollTriggeredAnimation = false;
214 #endif
215
216     AnimationNameMap newAnimations;
217
218     // Toss the animation order map.
219     m_keyframeAnimationOrderMap.clear();
220
221     static NeverDestroyed<const AtomicString> none("none", AtomicString::ConstructFromLiteral);
222     
223     // Now mark any still active animations as active and add any new animations.
224     if (targetStyle.animations()) {
225         int numAnims = targetStyle.animations()->size();
226         for (int i = 0; i < numAnims; ++i) {
227             auto& animation = targetStyle.animations()->animation(i);
228             AtomicString animationName(animation.name());
229
230             if (!animation.isValidAnimation())
231                 continue;
232             
233             // See if there is a current animation for this name.
234             RefPtr<KeyframeAnimation> keyframeAnim = m_keyframeAnimations.get(animationName.impl());
235             if (keyframeAnim) {
236                 newAnimations.add(keyframeAnim->name().impl(), keyframeAnim);
237
238                 if (keyframeAnim->postActive())
239                     continue;
240
241 #if ENABLE(CSS_ANIMATIONS_LEVEL_2)
242                 if (animation.trigger()->isScrollAnimationTrigger())
243                     m_hasScrollTriggeredAnimation = true;
244 #endif
245
246                 // Animations match, but play states may differ. Update if needed.
247                 keyframeAnim->updatePlayState(animation.playState());
248
249                 // Set the saved animation to this new one, just in case the play state has changed.
250                 keyframeAnim->setAnimation(animation);
251             } else if ((animation.duration() || animation.delay()) && animation.iterationCount() && animationName != none) {
252                 keyframeAnim = KeyframeAnimation::create(animation, element, *this, targetStyle);
253                 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());
254
255                 if (m_suspended) {
256                     keyframeAnim->updatePlayState(AnimPlayStatePaused);
257                     LOG(Animations, "  (created in suspended/paused state)");
258                 }
259 #if !LOG_DISABLED
260                 for (auto propertyID : keyframeAnim->keyframes().properties())
261                     LOG(Animations, "  property %s", getPropertyName(propertyID));
262 #endif
263
264 #if ENABLE(CSS_ANIMATIONS_LEVEL_2)
265                 if (animation.trigger()->isScrollAnimationTrigger())
266                     m_hasScrollTriggeredAnimation = true;
267 #endif
268
269                 newAnimations.set(keyframeAnim->name().impl(), keyframeAnim);
270             }
271             
272             // Add this to the animation order map.
273             if (keyframeAnim)
274                 m_keyframeAnimationOrderMap.append(keyframeAnim->name().impl());
275         }
276     }
277     
278     // Make a list of animations to be removed.
279     for (auto& animation : m_keyframeAnimations.values()) {
280         if (!newAnimations.contains(animation->name().impl())) {
281             animationController().animationWillBeRemoved(animation.get());
282             animation->clear();
283             LOG(Animations, "Removing KeyframeAnimation %p from element %p", animation.get(), &element);
284         }
285     }
286     
287     std::swap(newAnimations, m_keyframeAnimations);
288 }
289
290 AnimationUpdate CompositeAnimation::animate(Element& element, const RenderStyle* currentStyle, const RenderStyle& targetStyle)
291 {
292     // We don't do any transitions if we don't have a currentStyle (on startup).
293     updateTransitions(element, currentStyle, targetStyle);
294     updateKeyframeAnimations(element, currentStyle, targetStyle);
295     m_keyframeAnimations.checkConsistency();
296
297     bool animationStateChanged = false;
298     bool forceStackingContext = false;
299
300     std::unique_ptr<RenderStyle> animatedStyle;
301
302     if (currentStyle) {
303         // Now that we have transition objects ready, let them know about the new goal state.  We want them
304         // to fill in a RenderStyle*& only if needed.
305         bool checkForStackingContext = false;
306         for (auto& transition : m_transitions.values()) {
307             bool didBlendStyle = false;
308             if (transition->animate(*this, targetStyle, animatedStyle, didBlendStyle))
309                 animationStateChanged = true;
310
311             if (didBlendStyle)
312                 checkForStackingContext |= WillChangeData::propertyCreatesStackingContext(transition->animatingProperty());
313         }
314
315         if (animatedStyle && checkForStackingContext) {
316             // Note that this is similar to code in StyleResolver::adjustRenderStyle() but only needs to consult
317             // animatable properties that can trigger stacking context.
318             if (animatedStyle->opacity() < 1.0f
319                 || animatedStyle->hasTransformRelatedProperty()
320                 || animatedStyle->hasMask()
321                 || animatedStyle->clipPath()
322                 || animatedStyle->boxReflect()
323                 || animatedStyle->hasFilter()
324 #if ENABLE(FILTERS_LEVEL_2)
325                 || animatedStyle->hasBackdropFilter()
326 #endif
327                 )
328             forceStackingContext = true;
329         }
330     }
331
332     // Now that we have animation objects ready, let them know about the new goal state.  We want them
333     // to fill in a RenderStyle*& only if needed.
334     for (auto& name : m_keyframeAnimationOrderMap) {
335         RefPtr<KeyframeAnimation> keyframeAnim = m_keyframeAnimations.get(name);
336         if (keyframeAnim) {
337             bool didBlendStyle = false;
338             if (keyframeAnim->animate(*this, targetStyle, animatedStyle, didBlendStyle))
339                 animationStateChanged = true;
340
341             forceStackingContext |= didBlendStyle && keyframeAnim->triggersStackingContext();
342             m_hasAnimationThatDependsOnLayout |= keyframeAnim->dependsOnLayout();
343         }
344     }
345
346     // https://drafts.csswg.org/css-animations-1/
347     // While an animation is applied but has not finished, or has finished but has an animation-fill-mode of forwards or both,
348     // the user agent must act as if the will-change property ([css-will-change-1]) on the element additionally
349     // includes all the properties animated by the animation.
350     if (forceStackingContext && animatedStyle) {
351         if (animatedStyle->hasAutoZIndex())
352             animatedStyle->setZIndex(0);
353     }
354
355     return { WTFMove(animatedStyle), animationStateChanged };
356 }
357
358 std::unique_ptr<RenderStyle> CompositeAnimation::getAnimatedStyle() const
359 {
360     std::unique_ptr<RenderStyle> resultStyle;
361     for (auto& transition : m_transitions.values())
362         transition->getAnimatedStyle(resultStyle);
363
364     m_keyframeAnimations.checkConsistency();
365
366     for (auto& name : m_keyframeAnimationOrderMap) {
367         RefPtr<KeyframeAnimation> keyframeAnimation = m_keyframeAnimations.get(name);
368         if (keyframeAnimation)
369             keyframeAnimation->getAnimatedStyle(resultStyle);
370     }
371     
372     return resultStyle;
373 }
374
375 std::optional<Seconds> CompositeAnimation::timeToNextService() const
376 {
377     // Returns the time at which next service is required. std::nullopt means no service is required. 0 means
378     // service is required now, and > 0 means service is required that many seconds in the future.
379     std::optional<Seconds> minT;
380     
381     if (!m_transitions.isEmpty()) {
382         for (auto& transition : m_transitions.values()) {
383             std::optional<Seconds> t = transition->timeToNextService();
384             if (!t)
385                 continue;
386             if (!minT || t.value() < minT.value())
387                 minT = t.value();
388             if (minT.value() == 0_s)
389                 return 0_s;
390         }
391     }
392     if (!m_keyframeAnimations.isEmpty()) {
393         m_keyframeAnimations.checkConsistency();
394         for (auto& animation : m_keyframeAnimations.values()) {
395             std::optional<Seconds> t = animation->timeToNextService();
396             if (!t)
397                 continue;
398             if (!minT || t.value() < minT.value())
399                 minT = t.value();
400             if (minT.value() == 0_s)
401                 return 0_s;
402         }
403     }
404
405     return minT;
406 }
407
408 KeyframeAnimation* CompositeAnimation::animationForProperty(CSSPropertyID property) const
409 {
410     KeyframeAnimation* result = nullptr;
411
412     // We want to send back the last animation with the property if there are multiples.
413     // So we need to iterate through all animations
414     if (!m_keyframeAnimations.isEmpty()) {
415         m_keyframeAnimations.checkConsistency();
416         for (auto& animation : m_keyframeAnimations.values()) {
417             if (animation->hasAnimationForProperty(property))
418                 result = animation.get();
419         }
420     }
421
422     return result;
423 }
424
425 bool CompositeAnimation::computeExtentOfTransformAnimation(LayoutRect& bounds) const
426 {
427     // If more than one transition and animation affect transform, give up.
428     bool seenTransformAnimation = false;
429     
430     for (auto& animation : m_keyframeAnimations.values()) {
431         if (!animation->hasAnimationForProperty(CSSPropertyTransform))
432             continue;
433
434         if (seenTransformAnimation)
435             return false;
436
437         seenTransformAnimation = true;
438
439         if (!animation->computeExtentOfTransformAnimation(bounds))
440             return false;
441     }
442
443     for (auto& transition : m_transitions.values()) {
444         if (transition->animatingProperty() != CSSPropertyTransform || !transition->hasStyle())
445             continue;
446
447         if (seenTransformAnimation)
448             return false;
449
450         if (!transition->computeExtentOfTransformAnimation(bounds))
451             return false;
452     }
453     
454     return true;
455 }
456
457 void CompositeAnimation::suspendAnimations()
458 {
459     if (m_suspended)
460         return;
461
462     m_suspended = true;
463
464     if (!m_keyframeAnimations.isEmpty()) {
465         m_keyframeAnimations.checkConsistency();
466         for (auto& animation : m_keyframeAnimations.values())
467             animation->updatePlayState(AnimPlayStatePaused);
468     }
469
470     if (!m_transitions.isEmpty()) {
471         for (auto& transition : m_transitions.values()) {
472             if (transition->hasStyle())
473                 transition->updatePlayState(AnimPlayStatePaused);
474         }
475     }
476 }
477
478 void CompositeAnimation::resumeAnimations()
479 {
480     if (!m_suspended)
481         return;
482
483     m_suspended = false;
484
485     if (!m_keyframeAnimations.isEmpty()) {
486         m_keyframeAnimations.checkConsistency();
487         for (auto& animation : m_keyframeAnimations.values()) {
488             if (animation->playStatePlaying())
489                 animation->updatePlayState(AnimPlayStatePlaying);
490         }
491     }
492
493     if (!m_transitions.isEmpty()) {
494         for (auto& transition : m_transitions.values()) {
495             if (transition->hasStyle())
496                 transition->updatePlayState(AnimPlayStatePlaying);
497         }
498     }
499 }
500
501 void CompositeAnimation::overrideImplicitAnimations(CSSPropertyID property)
502 {
503     if (!m_transitions.isEmpty()) {
504         for (auto& transition : m_transitions.values()) {
505             if (transition->animatingProperty() == property)
506                 transition->setOverridden(true);
507         }
508     }
509 }
510
511 void CompositeAnimation::resumeOverriddenImplicitAnimations(CSSPropertyID property)
512 {
513     if (!m_transitions.isEmpty()) {
514         for (auto& transition : m_transitions.values()) {
515             if (transition->animatingProperty() == property)
516                 transition->setOverridden(false);
517         }
518     }
519 }
520
521 bool CompositeAnimation::isAnimatingProperty(CSSPropertyID property, bool acceleratedOnly, AnimationBase::RunningState runningState) const
522 {
523     if (!m_keyframeAnimations.isEmpty()) {
524         m_keyframeAnimations.checkConsistency();
525         for (auto& animation : m_keyframeAnimations.values()) {
526             if (animation->isAnimatingProperty(property, acceleratedOnly, runningState))
527                 return true;
528         }
529     }
530
531     if (!m_transitions.isEmpty()) {
532         for (auto& transition : m_transitions.values()) {
533             if (transition->isAnimatingProperty(property, acceleratedOnly, runningState))
534                 return true;
535         }
536     }
537     return false;
538 }
539
540 bool CompositeAnimation::pauseAnimationAtTime(const AtomicString& name, double t)
541 {
542     m_keyframeAnimations.checkConsistency();
543
544     RefPtr<KeyframeAnimation> keyframeAnim = m_keyframeAnimations.get(name.impl());
545     if (!keyframeAnim || !keyframeAnim->running())
546         return false;
547
548     keyframeAnim->freezeAtTime(t);
549     return true;
550 }
551
552 bool CompositeAnimation::pauseTransitionAtTime(CSSPropertyID property, double t)
553 {
554     if ((property < firstCSSProperty) || (property >= firstCSSProperty + numCSSProperties))
555         return false;
556
557     ImplicitAnimation* implAnim = m_transitions.get(property);
558     if (!implAnim) {
559         // Check to see if this property is being animated via a shorthand.
560         // This code is only used for testing, so performance is not critical here.
561         HashSet<CSSPropertyID> shorthandProperties = CSSPropertyAnimation::animatableShorthandsAffectingProperty(property);
562         bool anyPaused = false;
563         for (auto propertyID : shorthandProperties) {
564             if (pauseTransitionAtTime(propertyID, t))
565                 anyPaused = true;
566         }
567         return anyPaused;
568     }
569
570     if (!implAnim->running())
571         return false;
572
573     if ((t >= 0.0) && (t <= implAnim->duration())) {
574         implAnim->freezeAtTime(t);
575         return true;
576     }
577
578     return false;
579 }
580
581 unsigned CompositeAnimation::numberOfActiveAnimations() const
582 {
583     unsigned count = 0;
584     
585     m_keyframeAnimations.checkConsistency();
586     for (auto& animation : m_keyframeAnimations.values()) {
587         if (animation->running())
588             ++count;
589     }
590
591     for (auto& transition : m_transitions.values()) {
592         if (transition->running())
593             ++count;
594     }
595     
596     return count;
597 }
598
599 } // namespace WebCore