REGRESSION (Safari 10.1): When 'transition' contains -ms-transform, transform-origin...
[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     clearRenderer();
57     m_transitions.clear();
58     m_keyframeAnimations.clear();
59 }
60
61 void CompositeAnimation::clearRenderer()
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(RenderElement* renderer, 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 = !m_suspended && (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, renderer, this, modifiedCurrentStyle ? modifiedCurrentStyle.get() : fromStyle);
172                     LOG(Animations, "Created ImplicitAnimation %p on renderer %p for property %s duration %.2f delay %.2f", implicitAnimation.ptr(), renderer, getPropertyName(prop), animation.duration(), animation.delay());
173                     m_transitions.set(prop, WTFMove(implicitAnimation));
174                 }
175
176                 // We only need one pass for the single prop case
177                 if (!all)
178                     break;
179             }
180         }
181     }
182
183     // Make a list of transitions to be removed
184     Vector<int> toBeRemoved;
185     for (auto& transition : m_transitions.values()) {
186         if (!transition->active()) {
187             animationController().animationWillBeRemoved(transition.get());
188             toBeRemoved.append(transition->animatingProperty());
189             LOG(Animations, "Removing ImplicitAnimation %p from renderer %p for property %s", transition.get(), renderer, getPropertyName(transition->animatingProperty()));
190         }
191     }
192
193     // Now remove the transitions from the list
194     for (auto propertyToRemove : toBeRemoved)
195         m_transitions.remove(propertyToRemove);
196 }
197
198 void CompositeAnimation::updateKeyframeAnimations(RenderElement* renderer, const RenderStyle* currentStyle, const RenderStyle* targetStyle)
199 {
200     // Nothing to do if we don't have any animations, and didn't have any before
201     if (m_keyframeAnimations.isEmpty() && !targetStyle->hasAnimations())
202         return;
203
204     m_keyframeAnimations.checkConsistency();
205     
206     if (currentStyle && currentStyle->hasAnimations() && targetStyle->hasAnimations() && *(currentStyle->animations()) == *(targetStyle->animations()))
207         return;
208
209 #if ENABLE(CSS_ANIMATIONS_LEVEL_2)
210     m_hasScrollTriggeredAnimation = false;
211 #endif
212
213     AnimationNameMap newAnimations;
214
215     // Toss the animation order map.
216     m_keyframeAnimationOrderMap.clear();
217
218     static NeverDestroyed<const AtomicString> none("none", AtomicString::ConstructFromLiteral);
219     
220     // Now mark any still active animations as active and add any new animations.
221     if (targetStyle->animations()) {
222         int numAnims = targetStyle->animations()->size();
223         for (int i = 0; i < numAnims; ++i) {
224             auto& animation = targetStyle->animations()->animation(i);
225             AtomicString animationName(animation.name());
226
227             if (!animation.isValidAnimation())
228                 continue;
229             
230             // See if there is a current animation for this name.
231             RefPtr<KeyframeAnimation> keyframeAnim = m_keyframeAnimations.get(animationName.impl());
232             if (keyframeAnim) {
233                 newAnimations.add(keyframeAnim->name().impl(), keyframeAnim);
234
235                 if (keyframeAnim->postActive())
236                     continue;
237
238 #if ENABLE(CSS_ANIMATIONS_LEVEL_2)
239                 if (animation.trigger()->isScrollAnimationTrigger())
240                     m_hasScrollTriggeredAnimation = true;
241 #endif
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, renderer, this, targetStyle);
250                 LOG(Animations, "Creating KeyframeAnimation %p on renderer %p with keyframes %s, duration %.2f, delay %.2f, iterations %.2f", keyframeAnim.get(), renderer, animation.name().utf8().data(), animation.duration(), animation.delay(), animation.iterationCount());
251
252                 if (m_suspended) {
253                     keyframeAnim->updatePlayState(AnimPlayStatePaused);
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 #if ENABLE(CSS_ANIMATIONS_LEVEL_2)
262                 if (animation.trigger()->isScrollAnimationTrigger())
263                     m_hasScrollTriggeredAnimation = true;
264 #endif
265
266                 newAnimations.set(keyframeAnim->name().impl(), keyframeAnim);
267             }
268             
269             // Add this to the animation order map.
270             if (keyframeAnim)
271                 m_keyframeAnimationOrderMap.append(keyframeAnim->name().impl());
272         }
273     }
274     
275     // Make a list of animations to be removed.
276     for (auto& animation : m_keyframeAnimations.values()) {
277         if (!newAnimations.contains(animation->name().impl())) {
278             animationController().animationWillBeRemoved(animation.get());
279             animation->clear();
280             LOG(Animations, "Removing KeyframeAnimation %p from renderer %p", animation.get(), renderer);
281         }
282     }
283     
284     std::swap(newAnimations, m_keyframeAnimations);
285 }
286
287 bool CompositeAnimation::animate(RenderElement& renderer, const RenderStyle* currentStyle, const RenderStyle& targetStyle, std::unique_ptr<RenderStyle>& blendedStyle)
288 {
289     // We don't do any transitions if we don't have a currentStyle (on startup).
290     updateTransitions(&renderer, currentStyle, &targetStyle);
291     updateKeyframeAnimations(&renderer, currentStyle, &targetStyle);
292     m_keyframeAnimations.checkConsistency();
293
294     bool animationStateChanged = false;
295     bool forceStackingContext = false;
296
297     if (currentStyle) {
298         // Now that we have transition objects ready, let them know about the new goal state.  We want them
299         // to fill in a RenderStyle*& only if needed.
300         bool checkForStackingContext = false;
301         for (auto& transition : m_transitions.values()) {
302             bool didBlendStyle = false;
303             if (transition->animate(this, &renderer, currentStyle, &targetStyle, blendedStyle, didBlendStyle))
304                 animationStateChanged = true;
305
306             if (didBlendStyle)
307                 checkForStackingContext |= WillChangeData::propertyCreatesStackingContext(transition->animatingProperty());
308         }
309
310         if (blendedStyle && checkForStackingContext) {
311             // Note that this is similar to code in StyleResolver::adjustRenderStyle() but only needs to consult
312             // animatable properties that can trigger stacking context.
313             if (blendedStyle->opacity() < 1.0f
314                 || blendedStyle->hasTransformRelatedProperty()
315                 || blendedStyle->hasMask()
316                 || blendedStyle->clipPath()
317                 || blendedStyle->boxReflect()
318                 || blendedStyle->hasFilter()
319 #if ENABLE(FILTERS_LEVEL_2)
320                 || blendedStyle->hasBackdropFilter()
321 #endif
322                 )
323             forceStackingContext = true;
324         }
325     }
326
327     // Now that we have animation objects ready, let them know about the new goal state.  We want them
328     // to fill in a RenderStyle*& only if needed.
329     for (auto& name : m_keyframeAnimationOrderMap) {
330         RefPtr<KeyframeAnimation> keyframeAnim = m_keyframeAnimations.get(name);
331         if (keyframeAnim) {
332             bool didBlendStyle = false;
333             if (keyframeAnim->animate(this, &renderer, currentStyle, &targetStyle, blendedStyle, didBlendStyle))
334                 animationStateChanged = true;
335
336             forceStackingContext |= didBlendStyle && keyframeAnim->triggersStackingContext();
337         }
338     }
339
340     // https://drafts.csswg.org/css-animations-1/
341     // While an animation is applied but has not finished, or has finished but has an animation-fill-mode of forwards or both,
342     // the user agent must act as if the will-change property ([css-will-change-1]) on the element additionally
343     // includes all the properties animated by the animation.
344     if (forceStackingContext && blendedStyle) {
345         if (blendedStyle->hasAutoZIndex())
346             blendedStyle->setZIndex(0);
347     }
348
349     return animationStateChanged;
350 }
351
352 std::unique_ptr<RenderStyle> CompositeAnimation::getAnimatedStyle() const
353 {
354     std::unique_ptr<RenderStyle> resultStyle;
355     for (auto& transition : m_transitions.values())
356         transition->getAnimatedStyle(resultStyle);
357
358     m_keyframeAnimations.checkConsistency();
359
360     for (auto& name : m_keyframeAnimationOrderMap) {
361         RefPtr<KeyframeAnimation> keyframeAnimation = m_keyframeAnimations.get(name);
362         if (keyframeAnimation)
363             keyframeAnimation->getAnimatedStyle(resultStyle);
364     }
365     
366     return resultStyle;
367 }
368
369 std::optional<Seconds> CompositeAnimation::timeToNextService() const
370 {
371     // Returns the time at which next service is required. std::nullopt means no service is required. 0 means
372     // service is required now, and > 0 means service is required that many seconds in the future.
373     std::optional<Seconds> minT;
374     
375     if (!m_transitions.isEmpty()) {
376         for (auto& transition : m_transitions.values()) {
377             std::optional<Seconds> t = transition->timeToNextService();
378             if (!t)
379                 continue;
380             if (!minT || t.value() < minT.value())
381                 minT = t.value();
382             if (minT.value() == 0_s)
383                 return 0_s;
384         }
385     }
386     if (!m_keyframeAnimations.isEmpty()) {
387         m_keyframeAnimations.checkConsistency();
388         for (auto& animation : m_keyframeAnimations.values()) {
389             std::optional<Seconds> t = animation->timeToNextService();
390             if (!t)
391                 continue;
392             if (!minT || t.value() < minT.value())
393                 minT = t.value();
394             if (minT.value() == 0_s)
395                 return 0_s;
396         }
397     }
398
399     return minT;
400 }
401
402 KeyframeAnimation* CompositeAnimation::animationForProperty(CSSPropertyID property) const
403 {
404     KeyframeAnimation* result = nullptr;
405
406     // We want to send back the last animation with the property if there are multiples.
407     // So we need to iterate through all animations
408     if (!m_keyframeAnimations.isEmpty()) {
409         m_keyframeAnimations.checkConsistency();
410         for (auto& animation : m_keyframeAnimations.values()) {
411             if (animation->hasAnimationForProperty(property))
412                 result = animation.get();
413         }
414     }
415
416     return result;
417 }
418
419 bool CompositeAnimation::computeExtentOfTransformAnimation(LayoutRect& bounds) const
420 {
421     // If more than one transition and animation affect transform, give up.
422     bool seenTransformAnimation = false;
423     
424     for (auto& animation : m_keyframeAnimations.values()) {
425         if (!animation->hasAnimationForProperty(CSSPropertyTransform))
426             continue;
427
428         if (seenTransformAnimation)
429             return false;
430
431         seenTransformAnimation = true;
432
433         if (!animation->computeExtentOfTransformAnimation(bounds))
434             return false;
435     }
436
437     for (auto& transition : m_transitions.values()) {
438         if (transition->animatingProperty() != CSSPropertyTransform || !transition->hasStyle())
439             continue;
440
441         if (seenTransformAnimation)
442             return false;
443
444         if (!transition->computeExtentOfTransformAnimation(bounds))
445             return false;
446     }
447     
448     return true;
449 }
450
451 void CompositeAnimation::suspendAnimations()
452 {
453     if (m_suspended)
454         return;
455
456     m_suspended = true;
457
458     if (!m_keyframeAnimations.isEmpty()) {
459         m_keyframeAnimations.checkConsistency();
460         for (auto& animation : m_keyframeAnimations.values())
461             animation->updatePlayState(AnimPlayStatePaused);
462     }
463
464     if (!m_transitions.isEmpty()) {
465         for (auto& transition : m_transitions.values()) {
466             if (transition->hasStyle())
467                 transition->updatePlayState(AnimPlayStatePaused);
468         }
469     }
470 }
471
472 void CompositeAnimation::resumeAnimations()
473 {
474     if (!m_suspended)
475         return;
476
477     m_suspended = false;
478
479     if (!m_keyframeAnimations.isEmpty()) {
480         m_keyframeAnimations.checkConsistency();
481         for (auto& animation : m_keyframeAnimations.values()) {
482             if (animation->playStatePlaying())
483                 animation->updatePlayState(AnimPlayStatePlaying);
484         }
485     }
486
487     if (!m_transitions.isEmpty()) {
488         for (auto& transition : m_transitions.values()) {
489             if (transition->hasStyle())
490                 transition->updatePlayState(AnimPlayStatePlaying);
491         }
492     }
493 }
494
495 void CompositeAnimation::overrideImplicitAnimations(CSSPropertyID property)
496 {
497     if (!m_transitions.isEmpty()) {
498         for (auto& transition : m_transitions.values()) {
499             if (transition->animatingProperty() == property)
500                 transition->setOverridden(true);
501         }
502     }
503 }
504
505 void CompositeAnimation::resumeOverriddenImplicitAnimations(CSSPropertyID property)
506 {
507     if (!m_transitions.isEmpty()) {
508         for (auto& transition : m_transitions.values()) {
509             if (transition->animatingProperty() == property)
510                 transition->setOverridden(false);
511         }
512     }
513 }
514
515 bool CompositeAnimation::isAnimatingProperty(CSSPropertyID property, bool acceleratedOnly, AnimationBase::RunningState runningState) const
516 {
517     if (!m_keyframeAnimations.isEmpty()) {
518         m_keyframeAnimations.checkConsistency();
519         for (auto& animation : m_keyframeAnimations.values()) {
520             if (animation->isAnimatingProperty(property, acceleratedOnly, runningState))
521                 return true;
522         }
523     }
524
525     if (!m_transitions.isEmpty()) {
526         for (auto& transition : m_transitions.values()) {
527             if (transition->isAnimatingProperty(property, acceleratedOnly, runningState))
528                 return true;
529         }
530     }
531     return false;
532 }
533
534 bool CompositeAnimation::pauseAnimationAtTime(const AtomicString& name, double t)
535 {
536     m_keyframeAnimations.checkConsistency();
537
538     RefPtr<KeyframeAnimation> keyframeAnim = m_keyframeAnimations.get(name.impl());
539     if (!keyframeAnim || !keyframeAnim->running())
540         return false;
541
542     keyframeAnim->freezeAtTime(t);
543     return true;
544 }
545
546 bool CompositeAnimation::pauseTransitionAtTime(CSSPropertyID property, double t)
547 {
548     if ((property < firstCSSProperty) || (property >= firstCSSProperty + numCSSProperties))
549         return false;
550
551     ImplicitAnimation* implAnim = m_transitions.get(property);
552     if (!implAnim) {
553         // Check to see if this property is being animated via a shorthand.
554         // This code is only used for testing, so performance is not critical here.
555         HashSet<CSSPropertyID> shorthandProperties = CSSPropertyAnimation::animatableShorthandsAffectingProperty(property);
556         bool anyPaused = false;
557         for (auto propertyID : shorthandProperties) {
558             if (pauseTransitionAtTime(propertyID, t))
559                 anyPaused = true;
560         }
561         return anyPaused;
562     }
563
564     if (!implAnim->running())
565         return false;
566
567     if ((t >= 0.0) && (t <= implAnim->duration())) {
568         implAnim->freezeAtTime(t);
569         return true;
570     }
571
572     return false;
573 }
574
575 unsigned CompositeAnimation::numberOfActiveAnimations() const
576 {
577     unsigned count = 0;
578     
579     m_keyframeAnimations.checkConsistency();
580     for (auto& animation : m_keyframeAnimations.values()) {
581         if (animation->running())
582             ++count;
583     }
584
585     for (auto& transition : m_transitions.values()) {
586         if (transition->running())
587             ++count;
588     }
589     
590     return count;
591 }
592
593 } // namespace WebCore