| /* |
| * Copyright (C) Canon Inc. 2016 |
| * Copyright (C) 2017 Apple Inc. All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or without |
| * modification, are permitted provided that the following conditions |
| * are met: |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. Redistributions in binary form must reproduce the above copyright |
| * notice, this list of conditions and the following disclaimer in the |
| * documentation and/or other materials provided with the distribution. |
| * |
| * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY |
| * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE |
| * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR |
| * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, |
| * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, |
| * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR |
| * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY |
| * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
| * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| #include "config.h" |
| #include "AnimationTimeline.h" |
| |
| #include "Animation.h" |
| #include "AnimationEffect.h" |
| #include "AnimationList.h" |
| #include "CSSAnimation.h" |
| #include "CSSPropertyAnimation.h" |
| #include "CSSTransition.h" |
| #include "DocumentTimeline.h" |
| #include "Element.h" |
| #include "KeyframeEffect.h" |
| #include "RenderStyle.h" |
| #include "RenderView.h" |
| #include "StylePropertyShorthand.h" |
| #include "StyleResolver.h" |
| #include "WebAnimationUtilities.h" |
| #include <wtf/text/TextStream.h> |
| #include <wtf/text/WTFString.h> |
| |
| namespace WebCore { |
| |
| AnimationTimeline::AnimationTimeline() |
| { |
| } |
| |
| AnimationTimeline::~AnimationTimeline() |
| { |
| } |
| |
| void AnimationTimeline::forgetAnimation(WebAnimation* animation) |
| { |
| m_allAnimations.removeFirst(animation); |
| } |
| |
| void AnimationTimeline::animationTimingDidChange(WebAnimation& animation) |
| { |
| if (m_animations.add(&animation)) { |
| animation.setGlobalPosition(m_allAnimations.size()); |
| m_allAnimations.append(makeWeakPtr(&animation)); |
| auto* timeline = animation.timeline(); |
| if (timeline && timeline != this) |
| timeline->removeAnimation(animation); |
| } |
| } |
| |
| void AnimationTimeline::removeAnimation(WebAnimation& animation) |
| { |
| ASSERT(!animation.timeline() || animation.timeline() == this); |
| m_animations.remove(&animation); |
| if (is<KeyframeEffect>(animation.effect())) { |
| if (auto* target = downcast<KeyframeEffect>(animation.effect())->target()) |
| animationWasRemovedFromElement(animation, *target); |
| } |
| } |
| |
| Optional<double> AnimationTimeline::bindingsCurrentTime() |
| { |
| auto time = currentTime(); |
| if (!time) |
| return WTF::nullopt; |
| return secondsToWebAnimationsAPITime(*time); |
| } |
| |
| void AnimationTimeline::animationWasAddedToElement(WebAnimation& animation, Element& element) |
| { |
| [&] () -> ElementToAnimationsMap& { |
| if (is<CSSTransition>(animation) && downcast<CSSTransition>(animation).owningElement()) |
| return m_elementToCSSTransitionsMap; |
| if (is<CSSAnimation>(animation) && downcast<CSSAnimation>(animation).owningElement()) |
| return m_elementToCSSAnimationsMap; |
| return m_elementToAnimationsMap; |
| }().ensure(&element, [] { |
| return ListHashSet<RefPtr<WebAnimation>> { }; |
| }).iterator->value.add(&animation); |
| } |
| |
| static inline bool removeCSSTransitionFromMap(CSSTransition& transition, Element& element, HashMap<Element*, AnimationTimeline::PropertyToTransitionMap>& map) |
| { |
| auto iterator = map.find(&element); |
| if (iterator == map.end()) |
| return false; |
| |
| auto& cssTransitionsByProperty = iterator->value; |
| |
| auto transitionIterator = cssTransitionsByProperty.find(transition.property()); |
| if (transitionIterator == cssTransitionsByProperty.end() || transitionIterator->value != &transition) |
| return false; |
| |
| cssTransitionsByProperty.remove(transitionIterator); |
| |
| if (cssTransitionsByProperty.isEmpty()) |
| map.remove(&element); |
| return true; |
| } |
| |
| static inline void removeAnimationFromMapForElement(WebAnimation& animation, AnimationTimeline::ElementToAnimationsMap& map, Element& element) |
| { |
| auto iterator = map.find(&element); |
| if (iterator == map.end()) |
| return; |
| |
| auto& animations = iterator->value; |
| animations.remove(&animation); |
| if (!animations.size()) |
| map.remove(iterator); |
| } |
| |
| void AnimationTimeline::animationWasRemovedFromElement(WebAnimation& animation, Element& element) |
| { |
| removeAnimationFromMapForElement(animation, m_elementToCSSTransitionsMap, element); |
| removeAnimationFromMapForElement(animation, m_elementToCSSAnimationsMap, element); |
| removeAnimationFromMapForElement(animation, m_elementToAnimationsMap, element); |
| |
| // Now, if we're dealing with a declarative animation, we remove it from either the m_elementToCSSAnimationByName |
| // or the m_elementToRunningCSSTransitionByCSSPropertyID map, whichever is relevant to this type of animation. |
| if (is<DeclarativeAnimation>(animation)) |
| removeDeclarativeAnimationFromListsForOwningElement(animation, element); |
| } |
| |
| void AnimationTimeline::removeDeclarativeAnimationFromListsForOwningElement(WebAnimation& animation, Element& element) |
| { |
| ASSERT(is<DeclarativeAnimation>(animation)); |
| |
| if (is<CSSAnimation>(animation)) { |
| auto iterator = m_elementToCSSAnimationByName.find(&element); |
| if (iterator != m_elementToCSSAnimationByName.end()) { |
| auto& cssAnimationsByName = iterator->value; |
| auto& name = downcast<CSSAnimation>(animation).animationName(); |
| cssAnimationsByName.remove(name); |
| if (cssAnimationsByName.isEmpty()) |
| m_elementToCSSAnimationByName.remove(&element); |
| } |
| } else if (is<CSSTransition>(animation)) { |
| auto& transition = downcast<CSSTransition>(animation); |
| if (!removeCSSTransitionFromMap(transition, element, m_elementToRunningCSSTransitionByCSSPropertyID)) |
| removeCSSTransitionFromMap(transition, element, m_elementToCompletedCSSTransitionByCSSPropertyID); |
| } |
| } |
| |
| Vector<RefPtr<WebAnimation>> AnimationTimeline::animationsForElement(Element& element, Ordering ordering) const |
| { |
| Vector<RefPtr<WebAnimation>> animations; |
| if (m_elementToCSSTransitionsMap.contains(&element)) { |
| const auto& cssTransitions = m_elementToCSSTransitionsMap.get(&element); |
| if (ordering == Ordering::Sorted) { |
| auto sortedCSSTransitions = copyToVector(cssTransitions); |
| std::sort(sortedCSSTransitions.begin(), sortedCSSTransitions.end(), [](auto& lhs, auto& rhs) { |
| // Sort transitions first by their generation time, and then by transition-property. |
| // https://drafts.csswg.org/css-transitions-2/#animation-composite-order |
| auto* lhsTransition = downcast<CSSTransition>(lhs.get()); |
| auto* rhsTransition = downcast<CSSTransition>(rhs.get()); |
| if (lhsTransition->generationTime() != rhsTransition->generationTime()) |
| return lhsTransition->generationTime() < rhsTransition->generationTime(); |
| return lhsTransition->transitionProperty().utf8() < rhsTransition->transitionProperty().utf8(); |
| }); |
| animations.appendVector(sortedCSSTransitions); |
| } else |
| animations.appendRange(cssTransitions.begin(), cssTransitions.end()); |
| } |
| if (m_elementToCSSAnimationsMap.contains(&element)) { |
| const auto& cssAnimations = m_elementToCSSAnimationsMap.get(&element); |
| animations.appendRange(cssAnimations.begin(), cssAnimations.end()); |
| } |
| if (m_elementToAnimationsMap.contains(&element)) { |
| const auto& webAnimations = m_elementToAnimationsMap.get(&element); |
| if (ordering == Ordering::Sorted) { |
| auto sortedWebAnimations = copyToVector(webAnimations); |
| std::sort(sortedWebAnimations.begin(), sortedWebAnimations.end(), [](auto& lha, auto& rha) { |
| return lha->globalPosition() < rha->globalPosition(); |
| }); |
| animations.appendVector(sortedWebAnimations); |
| } else |
| animations.appendRange(webAnimations.begin(), webAnimations.end()); |
| } |
| return animations; |
| } |
| |
| void AnimationTimeline::elementWasRemoved(Element& element) |
| { |
| for (auto& cssTransition : m_elementToCSSTransitionsMap.get(&element)) |
| cssTransition->cancel(WebAnimation::Silently::Yes); |
| for (auto& cssAnimation : m_elementToCSSAnimationsMap.get(&element)) |
| cssAnimation->cancel(WebAnimation::Silently::Yes); |
| } |
| |
| void AnimationTimeline::removeAnimationsForElement(Element& element) |
| { |
| for (auto& animation : animationsForElement(element)) |
| animation->remove(); |
| } |
| |
| void AnimationTimeline::cancelDeclarativeAnimationsForElement(Element& element) |
| { |
| for (auto& cssTransition : m_elementToCSSTransitionsMap.get(&element)) |
| cssTransition->cancel(); |
| for (auto& cssAnimation : m_elementToCSSAnimationsMap.get(&element)) |
| cssAnimation->cancel(); |
| } |
| |
| static bool shouldConsiderAnimation(Element& element, const Animation& animation) |
| { |
| if (!animation.isValidAnimation()) |
| return false; |
| |
| static NeverDestroyed<const String> animationNameNone(MAKE_STATIC_STRING_IMPL("none")); |
| |
| auto& name = animation.name(); |
| if (name == animationNameNone || name.isEmpty()) |
| return false; |
| |
| if (auto* styleScope = Style::Scope::forOrdinal(element, animation.nameStyleScopeOrdinal())) |
| return styleScope->resolver().isAnimationNameValid(name); |
| |
| return false; |
| } |
| |
| void AnimationTimeline::updateCSSAnimationsForElement(Element& element, const RenderStyle* currentStyle, const RenderStyle& afterChangeStyle) |
| { |
| // In case this element is newly getting a "display: none" we need to cancel all of its animations and disregard new ones. |
| if (currentStyle && currentStyle->hasAnimations() && currentStyle->display() != DisplayType::None && afterChangeStyle.display() == DisplayType::None) { |
| if (m_elementToCSSAnimationByName.contains(&element)) { |
| for (const auto& cssAnimationsByNameMapItem : m_elementToCSSAnimationByName.take(&element)) |
| cancelDeclarativeAnimation(*cssAnimationsByNameMapItem.value); |
| } |
| return; |
| } |
| |
| if (currentStyle && currentStyle->hasAnimations() && afterChangeStyle.hasAnimations() && *(currentStyle->animations()) == *(afterChangeStyle.animations())) |
| return; |
| |
| // First, compile the list of animation names that were applied to this element up to this point. |
| HashSet<String> namesOfPreviousAnimations; |
| if (currentStyle && currentStyle->hasAnimations()) { |
| auto* previousAnimations = currentStyle->animations(); |
| for (size_t i = 0; i < previousAnimations->size(); ++i) { |
| auto& previousAnimation = previousAnimations->animation(i); |
| if (shouldConsiderAnimation(element, previousAnimation)) |
| namesOfPreviousAnimations.add(previousAnimation.name()); |
| } |
| } |
| |
| // Create or get the CSSAnimations by animation name map for this element. |
| auto& cssAnimationsByName = m_elementToCSSAnimationByName.ensure(&element, [] { |
| return HashMap<String, RefPtr<CSSAnimation>> { }; |
| }).iterator->value; |
| |
| if (auto* currentAnimations = afterChangeStyle.animations()) { |
| for (size_t i = 0; i < currentAnimations->size(); ++i) { |
| auto& currentAnimation = currentAnimations->animation(i); |
| auto& name = currentAnimation.name(); |
| if (namesOfPreviousAnimations.contains(name)) { |
| // We've found the name of this animation in our list of previous animations, this means we've already |
| // created a CSSAnimation object for it and need to ensure that this CSSAnimation is backed by the current |
| // animation object for this animation name. |
| if (auto cssAnimation = cssAnimationsByName.get(name)) |
| cssAnimation->setBackingAnimation(currentAnimation); |
| } else if (shouldConsiderAnimation(element, currentAnimation)) { |
| // Otherwise we are dealing with a new animation name and must create a CSSAnimation for it. |
| cssAnimationsByName.set(name, CSSAnimation::create(element, currentAnimation, currentStyle, afterChangeStyle)); |
| } |
| // Remove the name of this animation from our list since it's now known to be current. |
| namesOfPreviousAnimations.remove(name); |
| } |
| } |
| |
| // The animations names left in namesOfPreviousAnimations are now known to no longer apply so we need to |
| // remove the CSSAnimation object created for them. |
| for (const auto& nameOfAnimationToRemove : namesOfPreviousAnimations) { |
| if (auto animation = cssAnimationsByName.take(nameOfAnimationToRemove)) |
| cancelDeclarativeAnimation(*animation); |
| } |
| } |
| |
| RefPtr<WebAnimation> AnimationTimeline::cssAnimationForElementAndProperty(Element& element, CSSPropertyID property) |
| { |
| RefPtr<WebAnimation> matchingAnimation; |
| for (const auto& animation : m_elementToCSSAnimationsMap.get(&element)) { |
| auto* effect = animation->effect(); |
| if (is<KeyframeEffect>(effect) && downcast<KeyframeEffect>(effect)->animatedProperties().contains(property)) |
| matchingAnimation = animation; |
| } |
| return matchingAnimation; |
| } |
| |
| static bool propertyInStyleMatchesValueForTransitionInMap(CSSPropertyID property, const RenderStyle& style, AnimationTimeline::PropertyToTransitionMap& transitions) |
| { |
| if (auto* transition = transitions.get(property)) { |
| if (CSSPropertyAnimation::propertiesEqual(property, &style, &transition->targetStyle())) |
| return true; |
| } |
| return false; |
| } |
| |
| static double transitionCombinedDuration(const Animation* transition) |
| { |
| return std::max(0.0, transition->duration()) + transition->delay(); |
| } |
| |
| static bool transitionMatchesProperty(const Animation& transition, CSSPropertyID property) |
| { |
| auto mode = transition.animationMode(); |
| if (mode == Animation::AnimateNone || mode == Animation::AnimateUnknownProperty) |
| return false; |
| if (mode == Animation::AnimateSingleProperty) { |
| auto transitionProperty = transition.property(); |
| if (transitionProperty != property) { |
| auto shorthand = shorthandForProperty(transitionProperty); |
| for (size_t i = 0; i < shorthand.length(); ++i) { |
| if (shorthand.properties()[i] == property) |
| return true; |
| } |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| AnimationTimeline::PropertyToTransitionMap& AnimationTimeline::ensureRunningTransitionsByProperty(Element& element) |
| { |
| return m_elementToRunningCSSTransitionByCSSPropertyID.ensure(&element, [] { |
| return PropertyToTransitionMap { }; |
| }).iterator->value; |
| } |
| |
| void AnimationTimeline::updateCSSTransitionsForElement(Element& element, const RenderStyle& currentStyle, const RenderStyle& afterChangeStyle) |
| { |
| // In case this element is newly getting a "display: none" we need to cancel all of its transitions and disregard new ones. |
| if (currentStyle.hasTransitions() && currentStyle.display() != DisplayType::None && afterChangeStyle.display() == DisplayType::None) { |
| if (m_elementToRunningCSSTransitionByCSSPropertyID.contains(&element)) { |
| for (const auto& cssTransitionsByCSSPropertyIDMapItem : m_elementToRunningCSSTransitionByCSSPropertyID.take(&element)) |
| cancelDeclarativeAnimation(*cssTransitionsByCSSPropertyIDMapItem.value); |
| } |
| return; |
| } |
| |
| // Section 3 "Starting of transitions" from the CSS Transitions Level 1 specification. |
| // https://drafts.csswg.org/css-transitions-1/#starting |
| |
| auto& runningTransitionsByProperty = ensureRunningTransitionsByProperty(element); |
| |
| auto& completedTransitionsByProperty = m_elementToCompletedCSSTransitionByCSSPropertyID.ensure(&element, [] { |
| return PropertyToTransitionMap { }; |
| }).iterator->value; |
| |
| auto generationTime = MonotonicTime::now(); |
| |
| auto numberOfProperties = CSSPropertyAnimation::getNumProperties(); |
| for (int propertyIndex = 0; propertyIndex < numberOfProperties; ++propertyIndex) { |
| Optional<bool> isShorthand; |
| auto property = CSSPropertyAnimation::getPropertyAtIndex(propertyIndex, isShorthand); |
| if (isShorthand && *isShorthand) |
| continue; |
| |
| const Animation* matchingBackingAnimation = nullptr; |
| if (auto* transitions = afterChangeStyle.transitions()) { |
| for (size_t i = 0; i < transitions->size(); ++i) { |
| auto& backingAnimation = transitions->animation(i); |
| if (transitionMatchesProperty(backingAnimation, property)) |
| matchingBackingAnimation = &backingAnimation; |
| } |
| } |
| |
| // https://drafts.csswg.org/css-transitions-1/#before-change-style |
| // Define the before-change style as the computed values of all properties on the element as of the previous style change event, except with |
| // any styles derived from declarative animations such as CSS Transitions, CSS Animations, and SMIL Animations updated to the current time. |
| auto existingAnimation = cssAnimationForElementAndProperty(element, property); |
| const auto& beforeChangeStyle = existingAnimation ? downcast<CSSAnimation>(existingAnimation.get())->unanimatedStyle() : currentStyle; |
| |
| if (!runningTransitionsByProperty.contains(property) |
| && !CSSPropertyAnimation::propertiesEqual(property, &beforeChangeStyle, &afterChangeStyle) |
| && CSSPropertyAnimation::canPropertyBeInterpolated(property, &beforeChangeStyle, &afterChangeStyle) |
| && !propertyInStyleMatchesValueForTransitionInMap(property, afterChangeStyle, completedTransitionsByProperty) |
| && matchingBackingAnimation && transitionCombinedDuration(matchingBackingAnimation) > 0) { |
| // 1. If all of the following are true: |
| // - the element does not have a running transition for the property, |
| // - the before-change style is different from and can be interpolated with the after-change style for that property, |
| // - 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, |
| // - there is a matching transition-property value, and |
| // - the combined duration is greater than 0s, |
| |
| // then implementations must remove the completed transition (if present) from the set of completed transitions |
| completedTransitionsByProperty.remove(property); |
| |
| // and start a transition whose: |
| // - start time is the time of the style change event plus the matching transition delay, |
| // - end time is the start time plus the matching transition duration, |
| // - start value is the value of the transitioning property in the before-change style, |
| // - end value is the value of the transitioning property in the after-change style, |
| // - reversing-adjusted start value is the same as the start value, and |
| // - reversing shortening factor is 1. |
| auto delay = Seconds(matchingBackingAnimation->delay()); |
| auto duration = Seconds(matchingBackingAnimation->duration()); |
| auto& reversingAdjustedStartStyle = beforeChangeStyle; |
| auto reversingShorteningFactor = 1; |
| runningTransitionsByProperty.set(property, CSSTransition::create(element, property, generationTime, *matchingBackingAnimation, &beforeChangeStyle, afterChangeStyle, delay, duration, reversingAdjustedStartStyle, reversingShorteningFactor)); |
| } else if (completedTransitionsByProperty.contains(property) && !propertyInStyleMatchesValueForTransitionInMap(property, afterChangeStyle, completedTransitionsByProperty)) { |
| // 2. Otherwise, if the element has a completed transition for the property and the end value of the completed transition is different from |
| // the after-change style for the property, then implementations must remove the completed transition from the set of completed transitions. |
| completedTransitionsByProperty.remove(property); |
| } |
| |
| bool hasRunningTransition = runningTransitionsByProperty.contains(property); |
| if ((hasRunningTransition || completedTransitionsByProperty.contains(property)) && !matchingBackingAnimation) { |
| // 3. If the element has a running transition or completed transition for the property, and there is not a matching transition-property |
| // value, then implementations must cancel the running transition or remove the completed transition from the set of completed transitions. |
| if (hasRunningTransition) |
| runningTransitionsByProperty.take(property)->cancel(); |
| else |
| completedTransitionsByProperty.remove(property); |
| } |
| |
| if (matchingBackingAnimation && runningTransitionsByProperty.contains(property) && !propertyInStyleMatchesValueForTransitionInMap(property, afterChangeStyle, runningTransitionsByProperty)) { |
| auto previouslyRunningTransition = runningTransitionsByProperty.take(property); |
| auto& previouslyRunningTransitionCurrentStyle = previouslyRunningTransition->currentStyle(); |
| // 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 |
| // transition is not equal to the value of the property in the after-change style, then: |
| if (CSSPropertyAnimation::propertiesEqual(property, &previouslyRunningTransitionCurrentStyle, &afterChangeStyle) || !CSSPropertyAnimation::canPropertyBeInterpolated(property, ¤tStyle, &afterChangeStyle)) { |
| // 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, |
| // or if these two values cannot be interpolated, then implementations must cancel the running transition. |
| cancelDeclarativeAnimation(*previouslyRunningTransition); |
| } else if (transitionCombinedDuration(matchingBackingAnimation) <= 0.0 || !CSSPropertyAnimation::canPropertyBeInterpolated(property, &previouslyRunningTransitionCurrentStyle, &afterChangeStyle)) { |
| // 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 |
| // cannot be interpolated with the value of the property in the after-change style, then implementations must cancel the running transition. |
| cancelDeclarativeAnimation(*previouslyRunningTransition); |
| } else if (CSSPropertyAnimation::propertiesEqual(property, &previouslyRunningTransition->reversingAdjustedStartStyle(), &afterChangeStyle)) { |
| // 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 |
| // style (see the section on reversing of transitions for why these case exists), implementations must cancel the running transition |
| cancelDeclarativeAnimation(*previouslyRunningTransition); |
| |
| // and start a new transition whose: |
| // - reversing-adjusted start value is the end value of the running transition, |
| // - reversing shortening factor is the absolute value, clamped to the range [0, 1], of the sum of: |
| // 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 |
| // 2. 1 minus the reversing shortening factor of the old transition. |
| // - start time is the time of the style change event plus: |
| // 1. if the matching transition delay is nonnegative, the matching transition delay, or |
| // 2. if the matching transition delay is negative, the product of the new transition’s reversing shortening factor and the matching transition delay, |
| // - end time is the start time plus the product of the matching transition duration and the new transition’s reversing shortening factor, |
| // - start value is the current value of the property in the running transition, |
| // - end value is the value of the property in the after-change style |
| auto& reversingAdjustedStartStyle = previouslyRunningTransition->targetStyle(); |
| double transformedProgress = 1; |
| if (auto* effect = previouslyRunningTransition->effect()) { |
| if (auto computedTimingProgress = effect->getComputedTiming().progress) |
| transformedProgress = *computedTimingProgress; |
| } |
| auto reversingShorteningFactor = std::max(std::min(((transformedProgress * previouslyRunningTransition->reversingShorteningFactor()) + (1 - previouslyRunningTransition->reversingShorteningFactor())), 1.0), 0.0); |
| auto delay = matchingBackingAnimation->delay() < 0 ? Seconds(matchingBackingAnimation->delay()) * reversingShorteningFactor : Seconds(matchingBackingAnimation->delay()); |
| auto duration = Seconds(matchingBackingAnimation->duration()) * reversingShorteningFactor; |
| |
| ensureRunningTransitionsByProperty(element).set(property, CSSTransition::create(element, property, generationTime, *matchingBackingAnimation, &previouslyRunningTransitionCurrentStyle, afterChangeStyle, delay, duration, reversingAdjustedStartStyle, reversingShorteningFactor)); |
| } else { |
| // 4. Otherwise, implementations must cancel the running transition |
| cancelDeclarativeAnimation(*previouslyRunningTransition); |
| |
| // and start a new transition whose: |
| // - start time is the time of the style change event plus the matching transition delay, |
| // - end time is the start time plus the matching transition duration, |
| // - start value is the current value of the property in the running transition, |
| // - end value is the value of the property in the after-change style, |
| // - reversing-adjusted start value is the same as the start value, and |
| // - reversing shortening factor is 1. |
| auto delay = Seconds(matchingBackingAnimation->delay()); |
| auto duration = Seconds(matchingBackingAnimation->duration()); |
| auto& reversingAdjustedStartStyle = currentStyle; |
| auto reversingShorteningFactor = 1; |
| ensureRunningTransitionsByProperty(element).set(property, CSSTransition::create(element, property, generationTime, *matchingBackingAnimation, &previouslyRunningTransitionCurrentStyle, afterChangeStyle, delay, duration, reversingAdjustedStartStyle, reversingShorteningFactor)); |
| } |
| } |
| } |
| } |
| |
| void AnimationTimeline::cancelDeclarativeAnimation(DeclarativeAnimation& animation) |
| { |
| animation.cancelFromStyle(); |
| removeAnimation(animation); |
| m_allAnimations.removeFirst(&animation); |
| } |
| |
| } // namespace WebCore |