AnimationTimeline.cpp [plain text]
#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(ClassType classType)
: m_classType(classType)
{
}
AnimationTimeline::~AnimationTimeline()
{
}
void AnimationTimeline::forgetAnimation(WebAnimation* animation)
{
m_allAnimations.remove(animation);
}
void AnimationTimeline::animationTimingDidChange(WebAnimation& animation)
{
if (m_animations.add(&animation)) {
m_allAnimations.add(&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);
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) {
Vector<RefPtr<WebAnimation>> sortedCSSTransitions;
sortedCSSTransitions.appendRange(cssTransitions.begin(), cssTransitions.end());
std::sort(sortedCSSTransitions.begin(), sortedCSSTransitions.end(), [](auto& lhs, auto& rhs) {
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);
animations.appendRange(webAnimations.begin(), webAnimations.end());
}
return animations;
}
void AnimationTimeline::elementWasRemoved(Element& element)
{
for (auto& animation : animationsForElement(element))
animation->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)
{
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;
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());
}
}
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)) {
if (auto cssAnimation = cssAnimationsByName.get(name))
cssAnimation->setBackingAnimation(currentAnimation);
} else if (shouldConsiderAnimation(element, currentAnimation)) {
cssAnimationsByName.set(name, CSSAnimation::create(element, currentAnimation, currentStyle, afterChangeStyle));
}
namesOfPreviousAnimations.remove(name);
}
}
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)
{
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;
}
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;
}
}
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) {
completedTransitionsByProperty.remove(property);
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)) {
completedTransitionsByProperty.remove(property);
}
bool hasRunningTransition = runningTransitionsByProperty.contains(property);
if ((hasRunningTransition || completedTransitionsByProperty.contains(property)) && !matchingBackingAnimation) {
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();
if (CSSPropertyAnimation::propertiesEqual(property, &previouslyRunningTransitionCurrentStyle, &afterChangeStyle) || !CSSPropertyAnimation::canPropertyBeInterpolated(property, ¤tStyle, &afterChangeStyle)) {
cancelDeclarativeAnimation(*previouslyRunningTransition);
} else if (transitionCombinedDuration(matchingBackingAnimation) <= 0.0 || !CSSPropertyAnimation::canPropertyBeInterpolated(property, &previouslyRunningTransitionCurrentStyle, &afterChangeStyle)) {
cancelDeclarativeAnimation(*previouslyRunningTransition);
} else if (CSSPropertyAnimation::propertiesEqual(property, &previouslyRunningTransition->reversingAdjustedStartStyle(), &afterChangeStyle)) {
cancelDeclarativeAnimation(*previouslyRunningTransition);
auto& reversingAdjustedStartStyle = previouslyRunningTransition->targetStyle();
double transformedProgress = 1;
if (auto* effect = previouslyRunningTransition->effect())
transformedProgress = effect->iterationProgress().valueOr(transformedProgress);
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 {
cancelDeclarativeAnimation(*previouslyRunningTransition);
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.remove(&animation);
}
}