Rename AtomicString to AtomString
[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);
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
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                     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
207 void 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
282 AnimationUpdate 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
347 std::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
364 Optional<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
397 KeyframeAnimation* 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
414 bool 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
446 void 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
467 void 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
490 void 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
500 void 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
510 bool 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
529 bool 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
541 bool 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
570 unsigned 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