[Web Animations] Use a keyframe effect stack to resolve animations on an element
[WebKit-https.git] / Source / WebCore / animation / AnimationTimeline.cpp
1 /*
2  * Copyright (C) Canon Inc. 2016
3  * Copyright (C) 2017 Apple Inc. All rights reserved.
4  *
5  * Redistribution and use in source and binary forms, with or without
6  * modification, are permitted provided that the following conditions
7  * are met:
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  *
14  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
15  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
16  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
18  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
21  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
22  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
23  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
24  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
25  */
26
27 #include "config.h"
28 #include "AnimationTimeline.h"
29
30 #include "Animation.h"
31 #include "AnimationEffect.h"
32 #include "AnimationList.h"
33 #include "CSSAnimation.h"
34 #include "CSSPropertyAnimation.h"
35 #include "CSSTransition.h"
36 #include "DocumentTimeline.h"
37 #include "Element.h"
38 #include "KeyframeEffect.h"
39 #include "KeyframeEffectStack.h"
40 #include "RenderStyle.h"
41 #include "RenderView.h"
42 #include "StylePropertyShorthand.h"
43 #include "StyleResolver.h"
44 #include "WebAnimationUtilities.h"
45 #include <wtf/text/TextStream.h>
46 #include <wtf/text/WTFString.h>
47
48 namespace WebCore {
49
50 AnimationTimeline::AnimationTimeline()
51 {
52 }
53
54 AnimationTimeline::~AnimationTimeline()
55 {
56 }
57
58 void AnimationTimeline::forgetAnimation(WebAnimation* animation)
59 {
60     m_allAnimations.removeFirst(animation);
61 }
62
63 void AnimationTimeline::animationTimingDidChange(WebAnimation& animation)
64 {
65     if (m_animations.add(&animation)) {
66         animation.setGlobalPosition(m_allAnimations.size());
67         m_allAnimations.append(makeWeakPtr(&animation));
68         auto* timeline = animation.timeline();
69         if (timeline && timeline != this)
70             timeline->removeAnimation(animation);
71     }
72 }
73
74 void AnimationTimeline::removeAnimation(WebAnimation& animation)
75 {
76     ASSERT(!animation.timeline() || animation.timeline() == this);
77     m_animations.remove(&animation);
78     if (is<KeyframeEffect>(animation.effect())) {
79         if (auto* target = downcast<KeyframeEffect>(animation.effect())->target()) {
80             animationWasRemovedFromElement(animation, *target);
81             target->ensureKeyframeEffectStack().removeEffect(*downcast<KeyframeEffect>(animation.effect()));
82         }
83     }
84 }
85
86 Optional<double> AnimationTimeline::bindingsCurrentTime()
87 {
88     auto time = currentTime();
89     if (!time)
90         return WTF::nullopt;
91     return secondsToWebAnimationsAPITime(*time);
92 }
93
94 void AnimationTimeline::animationWasAddedToElement(WebAnimation& animation, Element& element)
95 {
96     [&] () -> ElementToAnimationsMap& {
97         if (is<CSSTransition>(animation) && downcast<CSSTransition>(animation).owningElement())
98             return m_elementToCSSTransitionsMap;
99         if (is<CSSAnimation>(animation) && downcast<CSSAnimation>(animation).owningElement())
100             return m_elementToCSSAnimationsMap;
101         return m_elementToAnimationsMap;
102     }().ensure(&element, [] {
103         return ListHashSet<RefPtr<WebAnimation>> { };
104     }).iterator->value.add(&animation);
105 }
106
107 static inline bool removeCSSTransitionFromMap(CSSTransition& transition, Element& element, HashMap<Element*, AnimationTimeline::PropertyToTransitionMap>& map)
108 {
109     auto iterator = map.find(&element);
110     if (iterator == map.end())
111         return false;
112
113     auto& cssTransitionsByProperty = iterator->value;
114
115     auto transitionIterator = cssTransitionsByProperty.find(transition.property());
116     if (transitionIterator == cssTransitionsByProperty.end() || transitionIterator->value != &transition)
117         return false;
118
119     cssTransitionsByProperty.remove(transitionIterator);
120
121     if (cssTransitionsByProperty.isEmpty())
122         map.remove(&element);
123     return true;
124 }
125
126 static inline void removeAnimationFromMapForElement(WebAnimation& animation, AnimationTimeline::ElementToAnimationsMap& map, Element& element)
127 {
128     auto iterator = map.find(&element);
129     if (iterator == map.end())
130         return;
131
132     auto& animations = iterator->value;
133     animations.remove(&animation);
134     if (!animations.size())
135         map.remove(iterator);
136 }
137
138 void AnimationTimeline::animationWasRemovedFromElement(WebAnimation& animation, Element& element)
139 {
140     removeAnimationFromMapForElement(animation, m_elementToCSSTransitionsMap, element);
141     removeAnimationFromMapForElement(animation, m_elementToCSSAnimationsMap, element);
142     removeAnimationFromMapForElement(animation, m_elementToAnimationsMap, element);
143
144     // Now, if we're dealing with a declarative animation, we remove it from either the m_elementToCSSAnimationByName
145     // or the m_elementToRunningCSSTransitionByCSSPropertyID map, whichever is relevant to this type of animation.
146     if (is<DeclarativeAnimation>(animation))
147         removeDeclarativeAnimationFromListsForOwningElement(animation, element);
148 }
149
150 void AnimationTimeline::removeDeclarativeAnimationFromListsForOwningElement(WebAnimation& animation, Element& element)
151 {
152     ASSERT(is<DeclarativeAnimation>(animation));
153
154     if (is<CSSAnimation>(animation)) {
155         auto iterator = m_elementToCSSAnimationByName.find(&element);
156         if (iterator != m_elementToCSSAnimationByName.end()) {
157             auto& cssAnimationsByName = iterator->value;
158             auto& name = downcast<CSSAnimation>(animation).animationName();
159             cssAnimationsByName.remove(name);
160             if (cssAnimationsByName.isEmpty())
161                 m_elementToCSSAnimationByName.remove(&element);
162         }
163     } else if (is<CSSTransition>(animation)) {
164         auto& transition = downcast<CSSTransition>(animation);
165         if (!removeCSSTransitionFromMap(transition, element, m_elementToRunningCSSTransitionByCSSPropertyID))
166             removeCSSTransitionFromMap(transition, element, m_elementToCompletedCSSTransitionByCSSPropertyID);
167     }
168 }
169
170 Vector<RefPtr<WebAnimation>> AnimationTimeline::animationsForElement(Element& element, Ordering ordering) const
171 {
172     Vector<RefPtr<WebAnimation>> animations;
173     if (m_elementToCSSTransitionsMap.contains(&element)) {
174         const auto& cssTransitions = m_elementToCSSTransitionsMap.get(&element);
175         if (ordering == Ordering::Sorted) {
176             auto sortedCSSTransitions = copyToVector(cssTransitions);
177             std::sort(sortedCSSTransitions.begin(), sortedCSSTransitions.end(), [](auto& lhs, auto& rhs) {
178                 // Sort transitions first by their generation time, and then by transition-property.
179                 // https://drafts.csswg.org/css-transitions-2/#animation-composite-order
180                 auto* lhsTransition = downcast<CSSTransition>(lhs.get());
181                 auto* rhsTransition = downcast<CSSTransition>(rhs.get());
182                 if (lhsTransition->generationTime() != rhsTransition->generationTime())
183                     return lhsTransition->generationTime() < rhsTransition->generationTime();
184                 return lhsTransition->transitionProperty().utf8() < rhsTransition->transitionProperty().utf8();
185             });
186             animations.appendVector(sortedCSSTransitions);
187         } else
188             animations.appendRange(cssTransitions.begin(), cssTransitions.end());
189     }
190     if (m_elementToCSSAnimationsMap.contains(&element)) {
191         const auto& cssAnimations = m_elementToCSSAnimationsMap.get(&element);
192         animations.appendRange(cssAnimations.begin(), cssAnimations.end());
193     }
194     if (m_elementToAnimationsMap.contains(&element)) {
195         const auto& webAnimations = m_elementToAnimationsMap.get(&element);
196         if (ordering == Ordering::Sorted) {
197             auto sortedWebAnimations = copyToVector(webAnimations);
198             std::sort(sortedWebAnimations.begin(), sortedWebAnimations.end(), [](auto& lha, auto& rha) {
199                 return lha->globalPosition() < rha->globalPosition();
200             });
201             animations.appendVector(sortedWebAnimations);
202         } else
203             animations.appendRange(webAnimations.begin(), webAnimations.end());
204     }
205     return animations;
206 }
207
208 void AnimationTimeline::elementWasRemoved(Element& element)
209 {
210     for (auto& cssTransition : m_elementToCSSTransitionsMap.get(&element))
211         cssTransition->cancel(WebAnimation::Silently::Yes);
212     for (auto& cssAnimation : m_elementToCSSAnimationsMap.get(&element))
213         cssAnimation->cancel(WebAnimation::Silently::Yes);
214 }
215
216 void AnimationTimeline::removeAnimationsForElement(Element& element)
217 {
218     for (auto& animation : animationsForElement(element))
219         animation->remove();
220 }
221
222 void AnimationTimeline::cancelDeclarativeAnimationsForElement(Element& element)
223 {
224     for (auto& cssTransition : m_elementToCSSTransitionsMap.get(&element))
225         cssTransition->cancel();
226     for (auto& cssAnimation : m_elementToCSSAnimationsMap.get(&element))
227         cssAnimation->cancel();
228 }
229
230 static bool shouldConsiderAnimation(Element& element, const Animation& animation)
231 {
232     if (!animation.isValidAnimation())
233         return false;
234
235     static NeverDestroyed<const String> animationNameNone(MAKE_STATIC_STRING_IMPL("none"));
236
237     auto& name = animation.name();
238     if (name == animationNameNone || name.isEmpty())
239         return false;
240
241     if (auto* styleScope = Style::Scope::forOrdinal(element, animation.nameStyleScopeOrdinal()))
242         return styleScope->resolver().isAnimationNameValid(name);
243
244     return false;
245 }
246
247 void AnimationTimeline::updateCSSAnimationsForElement(Element& element, const RenderStyle* currentStyle, const RenderStyle& afterChangeStyle)
248 {
249     Vector<String> animationNames;
250
251     // In case this element is newly getting a "display: none" we need to cancel all of its animations and disregard new ones.
252     if (currentStyle && currentStyle->hasAnimations() && currentStyle->display() != DisplayType::None && afterChangeStyle.display() == DisplayType::None) {
253         if (m_elementToCSSAnimationByName.contains(&element)) {
254             for (const auto& cssAnimationsByNameMapItem : m_elementToCSSAnimationByName.take(&element))
255                 cancelDeclarativeAnimation(*cssAnimationsByNameMapItem.value);
256         }
257         element.ensureKeyframeEffectStack().setCSSAnimationNames(WTFMove(animationNames));
258         return;
259     }
260
261     if (currentStyle && currentStyle->hasAnimations() && afterChangeStyle.hasAnimations() && *(currentStyle->animations()) == *(afterChangeStyle.animations()))
262         return;
263
264     // First, compile the list of animation names that were applied to this element up to this point.
265     HashSet<String> namesOfPreviousAnimations;
266     if (currentStyle && currentStyle->hasAnimations()) {
267         auto* previousAnimations = currentStyle->animations();
268         for (size_t i = 0; i < previousAnimations->size(); ++i) {
269             auto& previousAnimation = previousAnimations->animation(i);
270             if (shouldConsiderAnimation(element, previousAnimation))
271                 namesOfPreviousAnimations.add(previousAnimation.name());
272         }
273     }
274
275     // Create or get the CSSAnimations by animation name map for this element.
276     auto& cssAnimationsByName = m_elementToCSSAnimationByName.ensure(&element, [] {
277         return HashMap<String, RefPtr<CSSAnimation>> { };
278     }).iterator->value;
279
280     if (auto* currentAnimations = afterChangeStyle.animations()) {
281         for (size_t i = 0; i < currentAnimations->size(); ++i) {
282             auto& currentAnimation = currentAnimations->animation(i);
283             auto& name = currentAnimation.name();
284             animationNames.append(name);
285             if (namesOfPreviousAnimations.contains(name)) {
286                 // We've found the name of this animation in our list of previous animations, this means we've already
287                 // created a CSSAnimation object for it and need to ensure that this CSSAnimation is backed by the current
288                 // animation object for this animation name.
289                 if (auto cssAnimation = cssAnimationsByName.get(name))
290                     cssAnimation->setBackingAnimation(currentAnimation);
291             } else if (shouldConsiderAnimation(element, currentAnimation)) {
292                 // Otherwise we are dealing with a new animation name and must create a CSSAnimation for it.
293                 cssAnimationsByName.set(name, CSSAnimation::create(element, currentAnimation, currentStyle, afterChangeStyle));
294             }
295             // Remove the name of this animation from our list since it's now known to be current.
296             namesOfPreviousAnimations.remove(name);
297         }
298     }
299
300     // The animations names left in namesOfPreviousAnimations are now known to no longer apply so we need to
301     // remove the CSSAnimation object created for them.
302     for (const auto& nameOfAnimationToRemove : namesOfPreviousAnimations) {
303         if (auto animation = cssAnimationsByName.take(nameOfAnimationToRemove))
304             cancelDeclarativeAnimation(*animation);
305     }
306
307     element.ensureKeyframeEffectStack().setCSSAnimationNames(WTFMove(animationNames));
308 }
309
310 RefPtr<WebAnimation> AnimationTimeline::cssAnimationForElementAndProperty(Element& element, CSSPropertyID property)
311 {
312     RefPtr<WebAnimation> matchingAnimation;
313     for (const auto& animation : m_elementToCSSAnimationsMap.get(&element)) {
314         auto* effect = animation->effect();
315         if (is<KeyframeEffect>(effect) && downcast<KeyframeEffect>(effect)->animatedProperties().contains(property))
316             matchingAnimation = animation;
317     }
318     return matchingAnimation;
319 }
320
321 static bool propertyInStyleMatchesValueForTransitionInMap(CSSPropertyID property, const RenderStyle& style, AnimationTimeline::PropertyToTransitionMap& transitions)
322 {
323     if (auto* transition = transitions.get(property)) {
324         if (CSSPropertyAnimation::propertiesEqual(property, &style, &transition->targetStyle()))
325             return true;
326     }
327     return false;
328 }
329
330 static double transitionCombinedDuration(const Animation* transition)
331 {
332     return std::max(0.0, transition->duration()) + transition->delay();
333 }
334
335 static bool transitionMatchesProperty(const Animation& transition, CSSPropertyID property)
336 {
337     auto mode = transition.animationMode();
338     if (mode == Animation::AnimateNone || mode == Animation::AnimateUnknownProperty)
339         return false;
340     if (mode == Animation::AnimateSingleProperty) {
341         auto transitionProperty = transition.property();
342         if (transitionProperty != property) {
343             auto shorthand = shorthandForProperty(transitionProperty);
344             for (size_t i = 0; i < shorthand.length(); ++i) {
345                 if (shorthand.properties()[i] == property)
346                     return true;
347             }
348             return false;
349         }
350     }
351     return true;
352 }
353
354 AnimationTimeline::PropertyToTransitionMap& AnimationTimeline::ensureRunningTransitionsByProperty(Element& element)
355 {
356     return m_elementToRunningCSSTransitionByCSSPropertyID.ensure(&element, [] {
357         return PropertyToTransitionMap { };
358     }).iterator->value;
359 }
360
361 static void compileTransitionPropertiesInStyle(const RenderStyle& style, HashSet<CSSPropertyID>& transitionProperties, bool& transitionPropertiesContainAll)
362 {
363     if (transitionPropertiesContainAll)
364         return;
365
366     auto* transitions = style.transitions();
367     if (!transitions)
368         return;
369
370     for (size_t i = 0; i < transitions->size(); ++i) {
371         const auto& animation = transitions->animation(i);
372         auto mode = animation.animationMode();
373         if (mode == Animation::AnimateSingleProperty) {
374             auto property = animation.property();
375             if (isShorthandCSSProperty(property)) {
376                 auto shorthand = shorthandForProperty(property);
377                 for (size_t j = 0; j < shorthand.length(); ++j)
378                     transitionProperties.add(shorthand.properties()[j]);
379             } else
380                 transitionProperties.add(property);
381         } else if (mode == Animation::AnimateAll) {
382             transitionPropertiesContainAll = true;
383             return;
384         }
385     }
386 }
387
388 void AnimationTimeline::updateCSSTransitionsForElementAndProperty(Element& element, CSSPropertyID property, const RenderStyle& currentStyle, const RenderStyle& afterChangeStyle, AnimationTimeline::PropertyToTransitionMap& runningTransitionsByProperty, PropertyToTransitionMap& completedTransitionsByProperty, const MonotonicTime generationTime)
389 {
390     const Animation* matchingBackingAnimation = nullptr;
391     if (auto* transitions = afterChangeStyle.transitions()) {
392         for (size_t i = 0; i < transitions->size(); ++i) {
393             auto& backingAnimation = transitions->animation(i);
394             if (transitionMatchesProperty(backingAnimation, property))
395                 matchingBackingAnimation = &backingAnimation;
396         }
397     }
398
399     // https://drafts.csswg.org/css-transitions-1/#before-change-style
400     // Define the before-change style as the computed values of all properties on the element as of the previous style change event, except with
401     // any styles derived from declarative animations such as CSS Transitions, CSS Animations, and SMIL Animations updated to the current time.
402     auto existingAnimation = cssAnimationForElementAndProperty(element, property);
403     const auto& beforeChangeStyle = existingAnimation ? downcast<CSSAnimation>(existingAnimation.get())->unanimatedStyle() : currentStyle;
404
405     if (!runningTransitionsByProperty.contains(property)
406         && !CSSPropertyAnimation::propertiesEqual(property, &beforeChangeStyle, &afterChangeStyle)
407         && CSSPropertyAnimation::canPropertyBeInterpolated(property, &beforeChangeStyle, &afterChangeStyle)
408         && !propertyInStyleMatchesValueForTransitionInMap(property, afterChangeStyle, completedTransitionsByProperty)
409         && matchingBackingAnimation && transitionCombinedDuration(matchingBackingAnimation) > 0) {
410         // 1. If all of the following are true:
411         //   - the element does not have a running transition for the property,
412         //   - the before-change style is different from and can be interpolated with the after-change style for that property,
413         //   - the element does not have a completed transition for the property or the end value of the completed transition is different from the after-change style for the property,
414         //   - there is a matching transition-property value, and
415         //   - the combined duration is greater than 0s,
416
417         // then implementations must remove the completed transition (if present) from the set of completed transitions
418         completedTransitionsByProperty.remove(property);
419
420         // and start a transition whose:
421         //   - start time is the time of the style change event plus the matching transition delay,
422         //   - end time is the start time plus the matching transition duration,
423         //   - start value is the value of the transitioning property in the before-change style,
424         //   - end value is the value of the transitioning property in the after-change style,
425         //   - reversing-adjusted start value is the same as the start value, and
426         //   - reversing shortening factor is 1.
427         auto delay = Seconds(matchingBackingAnimation->delay());
428         auto duration = Seconds(matchingBackingAnimation->duration());
429         auto& reversingAdjustedStartStyle = beforeChangeStyle;
430         auto reversingShorteningFactor = 1;
431         runningTransitionsByProperty.set(property, CSSTransition::create(element, property, generationTime, *matchingBackingAnimation, &beforeChangeStyle, afterChangeStyle, delay, duration, reversingAdjustedStartStyle, reversingShorteningFactor));
432     } else if (completedTransitionsByProperty.contains(property) && !propertyInStyleMatchesValueForTransitionInMap(property, afterChangeStyle, completedTransitionsByProperty)) {
433         // 2. Otherwise, if the element has a completed transition for the property and the end value of the completed transition is different from
434         //    the after-change style for the property, then implementations must remove the completed transition from the set of completed transitions.
435         completedTransitionsByProperty.remove(property);
436     }
437
438     bool hasRunningTransition = runningTransitionsByProperty.contains(property);
439     if ((hasRunningTransition || completedTransitionsByProperty.contains(property)) && !matchingBackingAnimation) {
440         // 3. If the element has a running transition or completed transition for the property, and there is not a matching transition-property
441         //    value, then implementations must cancel the running transition or remove the completed transition from the set of completed transitions.
442         if (hasRunningTransition)
443             runningTransitionsByProperty.take(property)->cancel();
444         else
445             completedTransitionsByProperty.remove(property);
446     }
447
448     if (matchingBackingAnimation && runningTransitionsByProperty.contains(property) && !propertyInStyleMatchesValueForTransitionInMap(property, afterChangeStyle, runningTransitionsByProperty)) {
449         auto previouslyRunningTransition = runningTransitionsByProperty.take(property);
450         auto& previouslyRunningTransitionCurrentStyle = previouslyRunningTransition->currentStyle();
451         // 4. If the element has a running transition for the property, there is a matching transition-property value, and the end value of the running
452         //    transition is not equal to the value of the property in the after-change style, then:
453         if (CSSPropertyAnimation::propertiesEqual(property, &previouslyRunningTransitionCurrentStyle, &afterChangeStyle) || !CSSPropertyAnimation::canPropertyBeInterpolated(property, &currentStyle, &afterChangeStyle)) {
454             // 1. If the current value of the property in the running transition is equal to the value of the property in the after-change style,
455             //    or if these two values cannot be interpolated, then implementations must cancel the running transition.
456             cancelDeclarativeAnimation(*previouslyRunningTransition);
457         } else if (transitionCombinedDuration(matchingBackingAnimation) <= 0.0 || !CSSPropertyAnimation::canPropertyBeInterpolated(property, &previouslyRunningTransitionCurrentStyle, &afterChangeStyle)) {
458             // 2. Otherwise, if the combined duration is less than or equal to 0s, or if the current value of the property in the running transition
459             //    cannot be interpolated with the value of the property in the after-change style, then implementations must cancel the running transition.
460             cancelDeclarativeAnimation(*previouslyRunningTransition);
461         } else if (CSSPropertyAnimation::propertiesEqual(property, &previouslyRunningTransition->reversingAdjustedStartStyle(), &afterChangeStyle)) {
462             // 3. Otherwise, if the reversing-adjusted start value of the running transition is the same as the value of the property in the after-change
463             //    style (see the section on reversing of transitions for why these case exists), implementations must cancel the running transition
464             cancelDeclarativeAnimation(*previouslyRunningTransition);
465
466             // and start a new transition whose:
467             //   - reversing-adjusted start value is the end value of the running transition,
468             //   - reversing shortening factor is the absolute value, clamped to the range [0, 1], of the sum of:
469             //       1. the output of the timing function of the old transition at the time of the style change event, times the reversing shortening factor of the old transition
470             //       2. 1 minus the reversing shortening factor of the old transition.
471             //   - start time is the time of the style change event plus:
472             //       1. if the matching transition delay is nonnegative, the matching transition delay, or
473             //       2. if the matching transition delay is negative, the product of the new transition’s reversing shortening factor and the matching transition delay,
474             //   - end time is the start time plus the product of the matching transition duration and the new transition’s reversing shortening factor,
475             //   - start value is the current value of the property in the running transition,
476             //   - end value is the value of the property in the after-change style
477             auto& reversingAdjustedStartStyle = previouslyRunningTransition->targetStyle();
478             double transformedProgress = 1;
479             if (auto* effect = previouslyRunningTransition->effect()) {
480                 if (auto computedTimingProgress = effect->getComputedTiming().progress)
481                     transformedProgress = *computedTimingProgress;
482             }
483             auto reversingShorteningFactor = std::max(std::min(((transformedProgress * previouslyRunningTransition->reversingShorteningFactor()) + (1 - previouslyRunningTransition->reversingShorteningFactor())), 1.0), 0.0);
484             auto delay = matchingBackingAnimation->delay() < 0 ? Seconds(matchingBackingAnimation->delay()) * reversingShorteningFactor : Seconds(matchingBackingAnimation->delay());
485             auto duration = Seconds(matchingBackingAnimation->duration()) * reversingShorteningFactor;
486
487             ensureRunningTransitionsByProperty(element).set(property, CSSTransition::create(element, property, generationTime, *matchingBackingAnimation, &previouslyRunningTransitionCurrentStyle, afterChangeStyle, delay, duration, reversingAdjustedStartStyle, reversingShorteningFactor));
488         } else {
489             // 4. Otherwise, implementations must cancel the running transition
490             cancelDeclarativeAnimation(*previouslyRunningTransition);
491
492             // and start a new transition whose:
493             //   - start time is the time of the style change event plus the matching transition delay,
494             //   - end time is the start time plus the matching transition duration,
495             //   - start value is the current value of the property in the running transition,
496             //   - end value is the value of the property in the after-change style,
497             //   - reversing-adjusted start value is the same as the start value, and
498             //   - reversing shortening factor is 1.
499             auto delay = Seconds(matchingBackingAnimation->delay());
500             auto duration = Seconds(matchingBackingAnimation->duration());
501             auto& reversingAdjustedStartStyle = currentStyle;
502             auto reversingShorteningFactor = 1;
503             ensureRunningTransitionsByProperty(element).set(property, CSSTransition::create(element, property, generationTime, *matchingBackingAnimation, &previouslyRunningTransitionCurrentStyle, afterChangeStyle, delay, duration, reversingAdjustedStartStyle, reversingShorteningFactor));
504         }
505     }
506 }
507
508 void AnimationTimeline::updateCSSTransitionsForElement(Element& element, const RenderStyle& currentStyle, const RenderStyle& afterChangeStyle)
509 {
510     // In case this element is newly getting a "display: none" we need to cancel all of its transitions and disregard new ones.
511     if (currentStyle.hasTransitions() && currentStyle.display() != DisplayType::None && afterChangeStyle.display() == DisplayType::None) {
512         if (m_elementToRunningCSSTransitionByCSSPropertyID.contains(&element)) {
513             for (const auto& cssTransitionsByCSSPropertyIDMapItem : m_elementToRunningCSSTransitionByCSSPropertyID.take(&element))
514                 cancelDeclarativeAnimation(*cssTransitionsByCSSPropertyIDMapItem.value);
515         }
516         return;
517     }
518
519     // Section 3 "Starting of transitions" from the CSS Transitions Level 1 specification.
520     // https://drafts.csswg.org/css-transitions-1/#starting
521
522     auto& runningTransitionsByProperty = ensureRunningTransitionsByProperty(element);
523
524     auto& completedTransitionsByProperty = m_elementToCompletedCSSTransitionByCSSPropertyID.ensure(&element, [] {
525         return PropertyToTransitionMap { };
526     }).iterator->value;
527
528     auto generationTime = MonotonicTime::now();
529
530     // First, let's compile the list of all CSS properties found in the current style and the after-change style.
531     bool transitionPropertiesContainAll = false;
532     HashSet<CSSPropertyID> transitionProperties;
533     compileTransitionPropertiesInStyle(currentStyle, transitionProperties, transitionPropertiesContainAll);
534     compileTransitionPropertiesInStyle(afterChangeStyle, transitionProperties, transitionPropertiesContainAll);
535
536     if (transitionPropertiesContainAll) {
537         auto numberOfProperties = CSSPropertyAnimation::getNumProperties();
538         for (int propertyIndex = 0; propertyIndex < numberOfProperties; ++propertyIndex) {
539             Optional<bool> isShorthand;
540             auto property = CSSPropertyAnimation::getPropertyAtIndex(propertyIndex, isShorthand);
541             if (isShorthand && *isShorthand)
542                 continue;
543             updateCSSTransitionsForElementAndProperty(element, property, currentStyle, afterChangeStyle, runningTransitionsByProperty, completedTransitionsByProperty, generationTime);
544         }
545         return;
546     }
547
548     for (auto property : transitionProperties)
549         updateCSSTransitionsForElementAndProperty(element, property, currentStyle, afterChangeStyle, runningTransitionsByProperty, completedTransitionsByProperty, generationTime);
550 }
551
552 void AnimationTimeline::cancelDeclarativeAnimation(DeclarativeAnimation& animation)
553 {
554     animation.cancelFromStyle();
555     removeAnimation(animation);
556     m_allAnimations.removeFirst(&animation);
557 }
558
559 } // namespace WebCore