| /* |
| * Copyright (C) 2021 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 "Styleable.h" |
| |
| #include "Animation.h" |
| #include "AnimationEffect.h" |
| #include "AnimationList.h" |
| #include "AnimationTimeline.h" |
| #include "CSSAnimation.h" |
| #include "CSSPropertyAnimation.h" |
| #include "CSSTransition.h" |
| #include "DeclarativeAnimation.h" |
| #include "Document.h" |
| #include "DocumentTimeline.h" |
| #include "Element.h" |
| #include "KeyframeEffect.h" |
| #include "KeyframeEffectStack.h" |
| #include "RenderElement.h" |
| #include "RenderListItem.h" |
| #include "RenderListMarker.h" |
| #include "RenderStyle.h" |
| #include "StylePropertyShorthand.h" |
| #include "StyleResolver.h" |
| #include "StyleScope.h" |
| #include "WebAnimation.h" |
| #include "WebAnimationUtilities.h" |
| |
| namespace WebCore { |
| |
| const std::optional<const Styleable> Styleable::fromRenderer(const RenderElement& renderer) |
| { |
| switch (renderer.style().styleType()) { |
| case PseudoId::Backdrop: |
| for (auto& topLayerElement : renderer.document().topLayerElements()) { |
| if (topLayerElement->renderer() && topLayerElement->renderer()->backdropRenderer() == &renderer) |
| return Styleable(topLayerElement.get(), PseudoId::Backdrop); |
| } |
| break; |
| case PseudoId::Marker: |
| if (auto* ancestor = renderer.parent()) { |
| while (ancestor && !ancestor->element()) |
| ancestor = ancestor->parent(); |
| ASSERT(is<RenderListItem>(ancestor)); |
| ASSERT(downcast<RenderListItem>(ancestor)->markerRenderer() == &renderer); |
| return Styleable(*ancestor->element(), PseudoId::Marker); |
| } |
| break; |
| case PseudoId::After: |
| case PseudoId::Before: |
| case PseudoId::None: |
| if (auto* element = renderer.element()) |
| return fromElement(*element); |
| break; |
| default: |
| return std::nullopt; |
| } |
| |
| return std::nullopt; |
| } |
| |
| RenderElement* Styleable::renderer() const |
| { |
| switch (pseudoId) { |
| case PseudoId::After: |
| if (auto* afterPseudoElement = element.afterPseudoElement()) |
| return afterPseudoElement->renderer(); |
| break; |
| case PseudoId::Backdrop: |
| if (auto* hostRenderer = element.renderer()) |
| return hostRenderer->backdropRenderer().get(); |
| break; |
| case PseudoId::Before: |
| if (auto* beforePseudoElement = element.beforePseudoElement()) |
| return beforePseudoElement->renderer(); |
| break; |
| case PseudoId::Marker: |
| if (is<RenderListItem>(element.renderer())) { |
| auto* markerRenderer = downcast<RenderListItem>(*element.renderer()).markerRenderer(); |
| if (markerRenderer && markerRenderer->style().hasContent()) |
| return markerRenderer; |
| } |
| break; |
| case PseudoId::None: |
| return element.renderer(); |
| default: |
| return nullptr; |
| } |
| |
| return nullptr; |
| } |
| |
| void Styleable::animationWasAdded(WebAnimation& animation) const |
| { |
| ensureAnimations().add(&animation); |
| } |
| |
| static inline bool removeCSSTransitionFromMap(CSSTransition& transition, PropertyToTransitionMap& cssTransitionsByProperty) |
| { |
| auto transitionIterator = cssTransitionsByProperty.find(transition.property()); |
| if (transitionIterator == cssTransitionsByProperty.end() || transitionIterator->value != &transition) |
| return false; |
| |
| cssTransitionsByProperty.remove(transitionIterator); |
| return true; |
| } |
| |
| void Styleable::removeDeclarativeAnimationFromListsForOwningElement(WebAnimation& animation) const |
| { |
| ASSERT(is<DeclarativeAnimation>(animation)); |
| |
| if (is<CSSTransition>(animation)) { |
| auto& transition = downcast<CSSTransition>(animation); |
| if (!removeCSSTransitionFromMap(transition, ensureRunningTransitionsByProperty())) |
| removeCSSTransitionFromMap(transition, ensureCompletedTransitionsByProperty()); |
| } |
| } |
| |
| void Styleable::animationWasRemoved(WebAnimation& animation) const |
| { |
| ensureAnimations().remove(&animation); |
| |
| // Now, if we're dealing with a CSS Transition, we remove it from the m_elementToRunningCSSTransitionByCSSPropertyID map. |
| // We don't need to do this for CSS Animations because their timing can be set via CSS to end, which would cause this |
| // function to be called, but they should remain associated with their owning element until this is changed via a call |
| // to the JS API or changing the target element's animation-name property. |
| if (is<CSSTransition>(animation)) |
| removeDeclarativeAnimationFromListsForOwningElement(animation); |
| } |
| |
| static void removeCSSAnimationCreatedByMarkup(const Styleable& styleable, CSSAnimation& cssAnimation) |
| { |
| styleable.animationsCreatedByMarkup().remove(&cssAnimation); |
| |
| if (!styleable.hasKeyframeEffects()) |
| return; |
| |
| auto& keyframeEffectStack = styleable.ensureKeyframeEffectStack(); |
| auto* cssAnimationList = keyframeEffectStack.cssAnimationList(); |
| if (!cssAnimationList || cssAnimationList->isEmpty()) |
| return; |
| |
| auto& backingAnimation = cssAnimation.backingAnimation(); |
| for (size_t i = 0; i < cssAnimationList->size(); ++i) { |
| if (cssAnimationList->animation(i) == backingAnimation) { |
| // It is important we do not make a clone of the Animation references contained |
| // within cssAnimationList since sorting animations in compareCSSAnimations() |
| // makes pointer comparisons to distinguish between backing animations of various |
| // CSSAnimation objects. |
| auto newAnimationList = cssAnimationList->shallowCopy(); |
| newAnimationList->remove(i); |
| keyframeEffectStack.setCSSAnimationList(WTFMove(newAnimationList)); |
| return; |
| } |
| } |
| } |
| |
| void Styleable::elementWasRemoved() const |
| { |
| cancelDeclarativeAnimations(); |
| } |
| |
| void Styleable::willChangeRenderer() const |
| { |
| if (auto* animations = this->animations()) { |
| for (const auto& animation : *animations) |
| animation->willChangeRenderer(); |
| } |
| } |
| |
| void Styleable::cancelDeclarativeAnimations() const |
| { |
| if (auto* animations = this->animations()) { |
| for (auto& animation : *animations) { |
| if (is<DeclarativeAnimation>(animation)) { |
| if (is<CSSAnimation>(animation)) |
| removeCSSAnimationCreatedByMarkup(*this, downcast<CSSAnimation>(*animation)); |
| downcast<DeclarativeAnimation>(*animation).cancelFromStyle(); |
| } |
| } |
| } |
| } |
| |
| static bool keyframesRuleExistsForAnimation(Element& element, const Animation& animation, const String& animationName) |
| { |
| auto* styleScope = Style::Scope::forOrdinal(element, animation.nameStyleScopeOrdinal()); |
| return styleScope && styleScope->resolver().isAnimationNameValid(animationName); |
| } |
| |
| bool Styleable::animationListContainsNewlyValidAnimation(const AnimationList& animations) const |
| { |
| auto& keyframeEffectStack = ensureKeyframeEffectStack(); |
| if (!keyframeEffectStack.hasInvalidCSSAnimationNames()) |
| return false; |
| |
| for (auto& animation : animations) { |
| auto& name = animation->name().string; |
| if (name != "none" && !name.isEmpty() && keyframeEffectStack.containsInvalidCSSAnimationName(name) && keyframesRuleExistsForAnimation(element, animation.get(), name)) |
| return true; |
| } |
| |
| return false; |
| } |
| |
| void Styleable::updateCSSAnimations(const RenderStyle* currentStyle, const RenderStyle& newStyle, const Style::ResolutionContext& resolutionContext) const |
| { |
| auto& keyframeEffectStack = ensureKeyframeEffectStack(); |
| |
| // 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->display() != DisplayType::None && newStyle.display() == DisplayType::None) { |
| for (auto& cssAnimation : animationsCreatedByMarkup()) |
| cssAnimation->cancelFromStyle(); |
| keyframeEffectStack.setCSSAnimationList(nullptr); |
| return; |
| } |
| |
| auto* currentAnimationList = newStyle.animations(); |
| auto* previousAnimationList = keyframeEffectStack.cssAnimationList(); |
| if (!element.hasPendingKeyframesUpdate(pseudoId) && previousAnimationList && !previousAnimationList->isEmpty() && newStyle.hasAnimations() && *(previousAnimationList) == *(newStyle.animations()) && !animationListContainsNewlyValidAnimation(*newStyle.animations())) |
| return; |
| |
| CSSAnimationCollection newAnimations; |
| auto& previousAnimations = animationsCreatedByMarkup(); |
| |
| keyframeEffectStack.clearInvalidCSSAnimationNames(); |
| |
| // https://www.w3.org/TR/css-animations-1/#animations |
| // The same @keyframes rule name may be repeated within an animation-name. Changes to the animation-name update existing |
| // animations by iterating over the new list of animations from last to first, and, for each animation, finding the last |
| // matching animation in the list of existing animations. If a match is found, the existing animation is updated using the |
| // animation properties corresponding to its position in the new list of animations, whilst maintaining its current playback |
| // time as described above. The matching animation is removed from the existing list of animations such that it will not match |
| // twice. If a match is not found, a new animation is created. As a result, updating animation-name from ‘a’ to ‘a, a’ will |
| // cause the existing animation for ‘a’ to become the second animation in the list and a new animation will be created for the |
| // first item in the list. |
| if (currentAnimationList) { |
| for (auto& currentAnimation : makeReversedRange(*currentAnimationList)) { |
| if (!currentAnimation->isValidAnimation()) |
| continue; |
| |
| auto& animationName = currentAnimation->name().string; |
| if (animationName == "none" || animationName.isEmpty()) |
| continue; |
| |
| if (!keyframesRuleExistsForAnimation(element, currentAnimation.get(), animationName)) { |
| keyframeEffectStack.addInvalidCSSAnimationName(animationName); |
| continue; |
| } |
| |
| bool foundMatchingAnimation = false; |
| for (auto& previousAnimation : previousAnimations) { |
| if (previousAnimation->animationName() == animationName) { |
| // Timing properties or play state may have changed so we need to update the backing animation with |
| // the Animation found in the current style. |
| previousAnimation->setBackingAnimation(currentAnimation.get()); |
| // Keyframes may have been cleared if the @keyframes rules was changed since |
| // the last style update, so we must ensure keyframes are picked up. |
| previousAnimation->updateKeyframesIfNeeded(currentStyle, newStyle, resolutionContext); |
| newAnimations.add(previousAnimation); |
| // Remove the matched animation from the list of previous animations so we may not match it again. |
| previousAnimations.remove(previousAnimation); |
| foundMatchingAnimation = true; |
| break; |
| } |
| } |
| |
| if (!foundMatchingAnimation) |
| newAnimations.add(CSSAnimation::create(*this, currentAnimation.get(), currentStyle, newStyle, resolutionContext)); |
| } |
| } |
| |
| // Any animation found in previousAnimations but not found in newAnimations is not longer current and should be canceled. |
| for (auto& previousAnimation : previousAnimations) { |
| if (!newAnimations.contains(previousAnimation)) { |
| if (previousAnimation->owningElement()) |
| previousAnimation->cancelFromStyle(); |
| } |
| } |
| |
| setAnimationsCreatedByMarkup(WTFMove(newAnimations)); |
| |
| keyframeEffectStack.setCSSAnimationList(currentAnimationList); |
| |
| element.cssAnimationsDidUpdate(pseudoId); |
| } |
| |
| static KeyframeEffect* keyframeEffectForElementAndProperty(const Styleable& styleable, CSSPropertyID property) |
| { |
| if (auto* keyframeEffectStack = styleable.keyframeEffectStack()) { |
| auto effects = keyframeEffectStack->sortedEffects(); |
| for (const auto& effect : makeReversedRange(effects)) { |
| if (effect->animatesProperty(property)) |
| return effect.get(); |
| } |
| } |
| |
| return nullptr; |
| } |
| |
| static bool propertyInStyleMatchesValueForTransitionInMap(CSSPropertyID property, const RenderStyle& style, 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) |
| { |
| if (transition.isPropertyFilled()) |
| return false; |
| |
| auto mode = transition.property().mode; |
| if (mode == Animation::TransitionMode::None || mode == Animation::TransitionMode::UnknownProperty) |
| return false; |
| if (mode == Animation::TransitionMode::SingleProperty) { |
| auto transitionProperty = transition.property().id; |
| if (transitionProperty != property) { |
| for (auto longhand : shorthandForProperty(transitionProperty)) { |
| if (longhand == property) |
| return true; |
| } |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| static void compileTransitionPropertiesInStyle(const RenderStyle& style, HashSet<CSSPropertyID>& transitionProperties, bool& transitionPropertiesContainAll) |
| { |
| if (transitionPropertiesContainAll) |
| return; |
| |
| auto* transitions = style.transitions(); |
| if (!transitions) |
| return; |
| |
| for (const auto& animation : *transitions) { |
| auto mode = animation->property().mode; |
| if (mode == Animation::TransitionMode::SingleProperty) { |
| auto property = animation->property().id; |
| if (isShorthandCSSProperty(property)) { |
| for (auto longhand : shorthandForProperty(property)) |
| transitionProperties.add(longhand); |
| } else if (property != CSSPropertyInvalid) |
| transitionProperties.add(property); |
| } else if (mode == Animation::TransitionMode::All) { |
| transitionPropertiesContainAll = true; |
| return; |
| } |
| } |
| } |
| |
| static void updateCSSTransitionsForStyleableAndProperty(const Styleable& styleable, CSSPropertyID property, const RenderStyle& currentStyle, const RenderStyle& newStyle, const MonotonicTime generationTime) |
| { |
| auto* keyframeEffect = keyframeEffectForElementAndProperty(styleable, property); |
| auto* animation = keyframeEffect ? keyframeEffect->animation() : nullptr; |
| |
| bool isDeclarative = false; |
| if (is<DeclarativeAnimation>(animation)) { |
| if (auto owningElement = downcast<DeclarativeAnimation>(*animation).owningElement()) |
| isDeclarative = *owningElement == styleable; |
| } |
| |
| if (animation && !isDeclarative) |
| return; |
| |
| const Animation* matchingBackingAnimation = nullptr; |
| if (auto* transitions = newStyle.transitions()) { |
| for (auto& backingAnimation : *transitions) { |
| if (transitionMatchesProperty(backingAnimation.get(), property)) |
| matchingBackingAnimation = backingAnimation.ptr(); |
| } |
| } |
| |
| // A CSS Transition might have completed since the last time animations were updated so we must |
| // update the running and completed transitions membership in that case. |
| if (is<CSSTransition>(animation) && matchingBackingAnimation && styleable.hasRunningTransitionForProperty(property) && animation->playState() == WebAnimation::PlayState::Finished) { |
| styleable.ensureCompletedTransitionsByProperty().set(property, styleable.ensureRunningTransitionsByProperty().take(property)); |
| animation = nullptr; |
| } |
| |
| // 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 beforeChangeStyle = [&]() -> const RenderStyle { |
| if (auto* lastStyleChangeEventStyle = styleable.lastStyleChangeEventStyle()) { |
| auto style = RenderStyle::clone(*lastStyleChangeEventStyle); |
| if (auto* keyframeEffectStack = styleable.keyframeEffectStack()) { |
| for (const auto& effect : keyframeEffectStack->sortedEffects()) { |
| auto* effectAnimation = effect->animation(); |
| bool shouldUseTimelineTimeAtCreation = is<CSSTransition>(effectAnimation) && (!effectAnimation->startTime() || *effectAnimation->startTime() == styleable.element.document().timeline().currentTime()); |
| effectAnimation->resolve(style, { nullptr }, shouldUseTimelineTimeAtCreation ? downcast<CSSTransition>(*effectAnimation).timelineTimeAtCreation() : std::nullopt); |
| } |
| } |
| return style; |
| } |
| return RenderStyle::clone(currentStyle); |
| }(); |
| |
| // https://drafts.csswg.org/css-transitions-1/#after-change-style |
| // Likewise, define the after-change style as the computed values of all properties on the element based on the information known at the start |
| // of that style change event, but using the computed values of the animation-* properties from the before-change style, excluding any styles |
| // from CSS Transitions in the computation, and inheriting from the after-change style of the parent. Note that this means the after-change |
| // style does not differ from the before-change style due to newly created or canceled CSS Animations. |
| auto afterChangeStyle = [&]() -> const RenderStyle { |
| if (is<CSSAnimation>(animation) && animation->isRelevant()) { |
| auto animatedStyle = RenderStyle::clone(newStyle); |
| animation->resolve(animatedStyle, { nullptr }); |
| return animatedStyle; |
| } |
| |
| return RenderStyle::clone(newStyle); |
| }(); |
| |
| if (!styleable.hasRunningTransitionForProperty(property) |
| && !CSSPropertyAnimation::propertiesEqual(property, beforeChangeStyle, afterChangeStyle) |
| && CSSPropertyAnimation::canPropertyBeInterpolated(property, beforeChangeStyle, afterChangeStyle) |
| && !propertyInStyleMatchesValueForTransitionInMap(property, afterChangeStyle, styleable.ensureCompletedTransitionsByProperty()) |
| && 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 |
| styleable.ensureCompletedTransitionsByProperty().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; |
| styleable.ensureRunningTransitionsByProperty().set(property, CSSTransition::create(styleable, property, generationTime, *matchingBackingAnimation, &beforeChangeStyle, afterChangeStyle, delay, duration, reversingAdjustedStartStyle, reversingShorteningFactor)); |
| } else if (styleable.hasCompletedTransitionForProperty(property) && !propertyInStyleMatchesValueForTransitionInMap(property, afterChangeStyle, styleable.ensureCompletedTransitionsByProperty())) { |
| // 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. |
| styleable.ensureCompletedTransitionsByProperty().remove(property); |
| } |
| |
| bool hasRunningTransition = styleable.hasRunningTransitionForProperty(property); |
| if ((hasRunningTransition || styleable.hasCompletedTransitionForProperty(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) |
| styleable.ensureRunningTransitionsByProperty().take(property)->cancel(); |
| else |
| styleable.ensureCompletedTransitionsByProperty().remove(property); |
| } |
| |
| if (matchingBackingAnimation && styleable.hasRunningTransitionForProperty(property) && !propertyInStyleMatchesValueForTransitionInMap(property, afterChangeStyle, styleable.ensureRunningTransitionsByProperty())) { |
| auto previouslyRunningTransition = styleable.ensureRunningTransitionsByProperty().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, currentStyle, 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. |
| previouslyRunningTransition->cancelFromStyle(); |
| } 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. |
| previouslyRunningTransition->cancelFromStyle(); |
| } 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 |
| // 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; |
| |
| previouslyRunningTransition->cancelFromStyle(); |
| styleable.ensureRunningTransitionsByProperty().set(property, CSSTransition::create(styleable, property, generationTime, *matchingBackingAnimation, &beforeChangeStyle, afterChangeStyle, delay, duration, reversingAdjustedStartStyle, reversingShorteningFactor)); |
| } else { |
| // 4. Otherwise, implementations must cancel the running transition 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; |
| previouslyRunningTransition->cancelFromStyle(); |
| styleable.ensureRunningTransitionsByProperty().set(property, CSSTransition::create(styleable, property, generationTime, *matchingBackingAnimation, &previouslyRunningTransitionCurrentStyle, afterChangeStyle, delay, duration, reversingAdjustedStartStyle, reversingShorteningFactor)); |
| } |
| } |
| } |
| |
| void Styleable::updateCSSTransitions(const RenderStyle& currentStyle, const RenderStyle& newStyle) const |
| { |
| // 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 && newStyle.display() == DisplayType::None) { |
| if (hasRunningTransitions()) { |
| auto runningTransitions = ensureRunningTransitionsByProperty(); |
| for (const auto& cssTransitionsByCSSPropertyIDMapItem : runningTransitions) |
| cssTransitionsByCSSPropertyIDMapItem.value->cancelFromStyle(); |
| } |
| return; |
| } |
| |
| // Section 3 "Starting of transitions" from the CSS Transitions Level 1 specification. |
| // https://drafts.csswg.org/css-transitions-1/#starting |
| |
| auto generationTime = MonotonicTime::now(); |
| |
| // First, let's compile the list of all CSS properties found in the current style and the after-change style. |
| bool transitionPropertiesContainAll = false; |
| HashSet<CSSPropertyID> transitionProperties; |
| compileTransitionPropertiesInStyle(currentStyle, transitionProperties, transitionPropertiesContainAll); |
| compileTransitionPropertiesInStyle(newStyle, transitionProperties, transitionPropertiesContainAll); |
| |
| if (transitionPropertiesContainAll) { |
| auto numberOfProperties = CSSPropertyAnimation::getNumProperties(); |
| for (int propertyIndex = 0; propertyIndex < numberOfProperties; ++propertyIndex) { |
| std::optional<bool> isShorthand; |
| auto property = CSSPropertyAnimation::getPropertyAtIndex(propertyIndex, isShorthand); |
| if (isShorthand && *isShorthand) |
| continue; |
| updateCSSTransitionsForStyleableAndProperty(*this, property, currentStyle, newStyle, generationTime); |
| } |
| return; |
| } |
| |
| for (auto property : transitionProperties) |
| updateCSSTransitionsForStyleableAndProperty(*this, property, currentStyle, newStyle, generationTime); |
| } |
| |
| } // namespace WebCore |