Chromium Code Reviews| Index: Source/core/animation/css/CSSAnimations.cpp |
| diff --git a/Source/core/animation/css/CSSAnimations.cpp b/Source/core/animation/css/CSSAnimations.cpp |
| index 97ed93de6e40f9e9712348a13d94ad5be4184f6a..15384058642905120dae3bfe540cb35bc15a79b2 100644 |
| --- a/Source/core/animation/css/CSSAnimations.cpp |
| +++ b/Source/core/animation/css/CSSAnimations.cpp |
| @@ -31,21 +31,24 @@ |
| #include "config.h" |
| #include "core/animation/css/CSSAnimations.h" |
| +#include "StylePropertyShorthand.h" |
| #include "core/animation/ActiveAnimations.h" |
| #include "core/animation/DocumentTimeline.h" |
| #include "core/animation/KeyframeAnimationEffect.h" |
| +#include "core/animation/css/CSSAnimatableValueFactory.h" |
| #include "core/css/CSSKeyframeRule.h" |
| #include "core/css/resolver/StyleResolver.h" |
| #include "core/dom/Element.h" |
| #include "core/events/ThreadLocalEventNames.h" |
| +#include "core/events/TransitionEvent.h" |
| #include "core/events/WebKitAnimationEvent.h" |
| #include "core/platform/animation/CSSAnimationDataList.h" |
| #include "core/platform/animation/TimingFunction.h" |
| #include "wtf/HashSet.h" |
| -namespace { |
| +namespace WebCore { |
| -using namespace WebCore; |
| +namespace { |
| bool isEarlierPhase(TimedItem::Phase target, TimedItem::Phase reference) |
| { |
| @@ -61,10 +64,6 @@ bool isLaterPhase(TimedItem::Phase target, TimedItem::Phase reference) |
| return target > reference; |
| } |
| -} // namespace |
| - |
| -namespace WebCore { |
| - |
| // Returns the default timing function. |
| const PassRefPtr<TimingFunction> timingFromAnimationData(const CSSAnimationData* animationData, Timing& timing) |
| { |
| @@ -119,6 +118,62 @@ const PassRefPtr<TimingFunction> timingFromAnimationData(const CSSAnimationData* |
| return animationData->isTimingFunctionSet() ? animationData->timingFunction() : CSSAnimationData::initialAnimationTimingFunction(); |
| } |
| +struct TransitionDetails { |
|
dstockwell
2013/10/09 21:03:51
CandidateTransition?
Timothy Loh
2013/10/10 02:10:19
Done.
|
| + TransitionDetails(PassRefPtr<AnimatableValue> from, PassRefPtr<AnimatableValue> to, const CSSAnimationData* anim) |
| + : from(from) |
| + , to(to) |
| + , anim(anim) |
| + { |
| + } |
| + TransitionDetails() { } // The HashMap calls the default ctor |
| + RefPtr<AnimatableValue> from; |
| + RefPtr<AnimatableValue> to; |
| + const CSSAnimationData* anim; |
| +}; |
| +typedef HashMap<CSSPropertyID, TransitionDetails> StyleChange; |
|
dstockwell
2013/10/09 21:03:51
CandidateTransitionMap
Timothy Loh
2013/10/10 02:10:19
Done.
|
| + |
| +void updateIfPropertyChanged(const CSSAnimationData* anim, CSSPropertyID id, const RenderStyle* oldStyle, const RenderStyle* newStyle, StyleChange& styleChange) |
| +{ |
| + RefPtr<AnimatableValue> from = CSSAnimatableValueFactory::create(id, oldStyle); |
| + RefPtr<AnimatableValue> to = CSSAnimatableValueFactory::create(id, newStyle); |
| + // If we have multiple transitions on the same property, we will use the |
| + // last one since we iterate over them in order and this will override |
| + // a previously set TransitionDetails. |
| + if (!from->equals(to.get())) |
| + styleChange.add(id, TransitionDetails(from, to, anim)); |
| +} |
| + |
| +void computeStyleChange(const RenderStyle* oldStyle, const RenderStyle* newStyle, StyleChange& styleChange, HashSet<CSSPropertyID>& listedProperties) |
| +{ |
| + if (!newStyle->transitions()) |
| + return; |
| + |
| + for (size_t i = 0; i < newStyle->transitions()->size(); ++i) { |
| + const CSSAnimationData* anim = newStyle->transitions()->animation(i); |
| + CSSAnimationData::AnimationMode mode = anim->animationMode(); |
| + if (anim->duration() + anim->delay() <= 0 || mode == CSSAnimationData::AnimateNone) |
| + continue; |
| + |
| + bool animateAll = mode == CSSAnimationData::AnimateAll; |
| + ASSERT(animateAll || mode == CSSAnimationData::AnimateSingleProperty); |
| + const StylePropertyShorthand& propertyList = animateAll ? CSSAnimations::animatableProperties() : shorthandForProperty(anim->property()); |
| + if (!propertyList.length()) { |
| + listedProperties.add(anim->property()); |
| + updateIfPropertyChanged(anim, anim->property(), oldStyle, newStyle, styleChange); |
| + } else { |
| + for (unsigned i = 0; i < propertyList.length(); ++i) { |
| + CSSPropertyID id = propertyList.properties()[i]; |
| + if (!animateAll && !CSSAnimations::isAnimatableProperty(id)) |
| + continue; |
| + listedProperties.add(id); |
| + updateIfPropertyChanged(anim, id, oldStyle, newStyle, styleChange); |
| + } |
| + } |
| + } |
| +} |
| + |
| +} // namespace |
| + |
| CSSAnimationUpdateScope::CSSAnimationUpdateScope(Element* target) |
| : m_target(target) |
| { |
| @@ -143,18 +198,21 @@ CSSAnimationUpdateScope::~CSSAnimationUpdateScope() |
| cssAnimations->maybeApplyPendingUpdate(m_target); |
| } |
| -bool CSSAnimations::needsUpdate(const Element* element, const RenderStyle* style) |
| +PassOwnPtr<CSSAnimationUpdate> CSSAnimations::calculateUpdate(Element* element, const RenderStyle* style, StyleResolver* resolver) |
| { |
| - ActiveAnimations* activeAnimations = element->activeAnimations(); |
| - const CSSAnimationDataList* animations = style->animations(); |
| - const CSSAnimations* cssAnimations = activeAnimations ? activeAnimations->cssAnimations() : 0; |
| - EDisplay display = style->display(); |
| - return (display != NONE && animations && animations->size()) || (cssAnimations && !cssAnimations->isEmpty()); |
| + ASSERT(RuntimeEnabledFeatures::webAnimationsCSSEnabled()); |
| + OwnPtr<CSSAnimationUpdate> update = adoptPtr(new CSSAnimationUpdate()); |
| + calculateAnimationUpdate(update.get(), element, style, resolver); |
| + calculateTransitionUpdate(update.get(), element, style); |
| + return update->isEmpty() ? nullptr : update.release(); |
| } |
| -PassOwnPtr<CSSAnimationUpdate> CSSAnimations::calculateUpdate(Element* element, const RenderStyle* style, const CSSAnimations* cssAnimations, const CSSAnimationDataList* animationDataList, StyleResolver* resolver) |
| +void CSSAnimations::calculateAnimationUpdate(CSSAnimationUpdate* update, Element* element, const RenderStyle* style, StyleResolver* resolver) |
| { |
| - OwnPtr<CSSAnimationUpdate> update; |
| + ActiveAnimations* activeAnimations = element->activeAnimations(); |
| + const CSSAnimationDataList* animationDataList = style->animations(); |
| + const CSSAnimations* cssAnimations = activeAnimations ? activeAnimations->cssAnimations() : 0; |
| + |
| HashSet<AtomicString> inactive; |
| if (cssAnimations) |
| for (AnimationMap::const_iterator iter = cssAnimations->m_animations.begin(); iter != cssAnimations->m_animations.end(); ++iter) |
| @@ -182,8 +240,6 @@ PassOwnPtr<CSSAnimationUpdate> CSSAnimations::calculateUpdate(Element* element, |
| Vector<std::pair<KeyframeAnimationEffect::KeyframeVector, RefPtr<TimingFunction> > > keyframesAndTimingFunctions; |
| resolver->resolveKeyframes(element, style, animationName, defaultTimingFunction.get(), keyframesAndTimingFunctions); |
| if (!keyframesAndTimingFunctions.isEmpty()) { |
| - if (!update) |
| - update = adoptPtr(new CSSAnimationUpdate()); |
| HashSet<RefPtr<InertAnimation> > animations; |
| for (size_t j = 0; j < keyframesAndTimingFunctions.size(); ++j) { |
| ASSERT(!keyframesAndTimingFunctions[j].first.isEmpty()); |
| @@ -196,12 +252,8 @@ PassOwnPtr<CSSAnimationUpdate> CSSAnimations::calculateUpdate(Element* element, |
| } |
| } |
| - if (!inactive.isEmpty() && !update) |
| - update = adoptPtr(new CSSAnimationUpdate()); |
| for (HashSet<AtomicString>::const_iterator iter = inactive.begin(); iter != inactive.end(); ++iter) |
| update->cancelAnimation(*iter, cssAnimations->m_animations.get(*iter)); |
| - |
| - return update.release(); |
| } |
| void CSSAnimations::maybeApplyPendingUpdate(Element* element) |
| @@ -223,7 +275,7 @@ void CSSAnimations::maybeApplyPendingUpdate(Element* element) |
| // FIXME: Apply updates to play-state. |
| for (Vector<CSSAnimationUpdate::NewAnimation>::const_iterator iter = update->newAnimations().begin(); iter != update->newAnimations().end(); ++iter) { |
| - OwnPtr<CSSAnimations::EventDelegate> eventDelegate = adoptPtr(new EventDelegate(element, iter->name)); |
| + OwnPtr<AnimationEventDelegate> eventDelegate = adoptPtr(new AnimationEventDelegate(element, iter->name)); |
| HashSet<RefPtr<Player> > players; |
| for (HashSet<RefPtr<InertAnimation> >::const_iterator animationsIter = iter->animations.begin(); animationsIter != iter->animations.end(); ++animationsIter) { |
| const InertAnimation* inertAnimation = animationsIter->get(); |
| @@ -234,6 +286,93 @@ void CSSAnimations::maybeApplyPendingUpdate(Element* element) |
| } |
| m_animations.set(iter->name, players); |
| } |
| + |
| + for (HashSet<CSSPropertyID>::iterator iter = update->endedTransitions().begin(); iter != update->endedTransitions().end(); ++iter) { |
| + ASSERT(m_transitions.contains(*iter)); |
| + m_transitions.take(*iter).player->cancel(); |
| + } |
| + |
| + for (size_t i = 0; i < update->newTransitions().size(); ++i) { |
| + const CSSAnimationUpdate::NewTransition& newTransition = update->newTransitions()[i]; |
| + |
| + RunningTransition runningTransition; |
| + runningTransition.from = newTransition.from; |
| + runningTransition.to = newTransition.to; |
| + |
| + CSSPropertyID id = newTransition.id; |
| + InertAnimation* inertAnimation = newTransition.animation.get(); |
| + OwnPtr<TransitionEventDelegate> eventDelegate = adoptPtr(new TransitionEventDelegate(element, id)); |
| + RefPtr<Animation> transition = Animation::create(element, inertAnimation->effect(), inertAnimation->specified(), eventDelegate.release()); |
| + // FIXME: Transitions need to be added to a separate timeline. |
| + runningTransition.player = element->document().timeline()->play(transition.get()); |
| + m_transitions.set(id, runningTransition); |
| + } |
| +} |
| + |
| +void CSSAnimations::calculateTransitionUpdateForProperty(CSSAnimationUpdate* update, CSSPropertyID id, const AnimatableValue* from, const AnimatableValue* to, const CSSAnimationData* anim, const TransitionMap* transitions) |
| +{ |
| + // FIXME: Skip the rest of this if there is a running animation on this property |
| + |
| + Timing timing; |
| + RefPtr<TimingFunction> timingFunction = timingFromAnimationData(anim, timing); |
| + timing.timingFunction = timingFunction; |
| + timing.fillMode = Timing::FillModeBoth; |
|
dstockwell
2013/10/09 21:03:51
We should discuss this, I'm not sure why this is n
Timothy Loh
2013/10/10 02:10:19
We still need at least FillModeBackwards so delay
|
| + |
| + if (transitions) { |
| + TransitionMap::const_iterator existingTransitionIter = transitions->find(id); |
| + |
| + if (existingTransitionIter != transitions->end() && !update->endedTransitions().contains(id)) { |
| + const AnimatableValue* existingTo = existingTransitionIter->value.to; |
| + if (existingTo->equals(to)) |
| + return; |
| + update->endTransition(id); |
| + } |
| + } |
| + |
| + KeyframeAnimationEffect::KeyframeVector keyframes; |
| + |
| + RefPtr<Keyframe> startKeyframe = Keyframe::create(); |
| + startKeyframe->setPropertyValue(id, from); |
| + startKeyframe->setOffset(0); |
| + keyframes.append(startKeyframe); |
| + |
| + RefPtr<Keyframe> endKeyframe = Keyframe::create(); |
| + endKeyframe->setPropertyValue(id, to); |
| + endKeyframe->setOffset(1); |
| + keyframes.append(endKeyframe); |
| + |
| + RefPtr<KeyframeAnimationEffect> effect = KeyframeAnimationEffect::create(keyframes); |
| + |
| + update->startTransition(id, from, to, InertAnimation::create(effect, timing)); |
| +} |
| + |
| +void CSSAnimations::calculateTransitionUpdate(CSSAnimationUpdate* update, const Element* element, const RenderStyle* style) |
| +{ |
| + ActiveAnimations* activeAnimations = element->activeAnimations(); |
| + const CSSAnimations* cssAnimations = activeAnimations ? activeAnimations->cssAnimations() : 0; |
| + const TransitionMap* transitions = cssAnimations ? &cssAnimations->m_transitions : 0; |
| + |
| + HashSet<CSSPropertyID> listedProperties; |
| + if (style->display() != NONE && element->renderer() && element->renderer()->style()) { |
| + StyleChange styleChange; |
| + computeStyleChange(element->renderer()->style(), style, styleChange, listedProperties); |
| + for (StyleChange::const_iterator iter = styleChange.begin(); iter != styleChange.end(); ++iter) { |
| + CSSPropertyID id = iter->key; |
|
dstockwell
2013/10/09 21:03:51
Inline these, there are no line limits in blink.
Timothy Loh
2013/10/10 02:10:19
Done.
|
| + AnimatableValue* from = iter->value.from.get(); |
| + AnimatableValue* to = iter->value.to.get(); |
| + const CSSAnimationData* anim = iter->value.anim; |
| + calculateTransitionUpdateForProperty(update, id, from, to, anim, transitions); |
| + } |
| + } |
| + |
| + if (transitions) { |
| + for (TransitionMap::const_iterator iter = transitions->begin(); iter != transitions->end(); ++iter) { |
| + const TimedItem* timedItem = iter->value.player->source(); |
| + CSSPropertyID id = iter->key; |
| + if (timedItem->phase() == TimedItem::PhaseAfter || !listedProperties.contains(id)) |
| + update->endTransition(id); |
| + } |
| + } |
| } |
| void CSSAnimations::cancel() |
| @@ -244,17 +383,21 @@ void CSSAnimations::cancel() |
| (*animationsIter)->cancel(); |
| } |
| + for (TransitionMap::iterator iter = m_transitions.begin(); iter != m_transitions.end(); ++iter) |
| + iter->value.player->cancel(); |
| + |
| m_animations.clear(); |
| + m_transitions.clear(); |
| m_pendingUpdate = nullptr; |
| } |
| -void CSSAnimations::EventDelegate::maybeDispatch(Document::ListenerType listenerType, const AtomicString& eventName, double elapsedTime) |
| +void CSSAnimations::AnimationEventDelegate::maybeDispatch(Document::ListenerType listenerType, const AtomicString& eventName, double elapsedTime) |
| { |
| if (m_target->document().hasListenerType(listenerType)) |
| m_target->document().timeline()->addEventToDispatch(m_target, WebKitAnimationEvent::create(eventName, m_name, elapsedTime)); |
| } |
| -void CSSAnimations::EventDelegate::onEventCondition(const TimedItem* timedItem, bool isFirstSample, TimedItem::Phase previousPhase, double previousIteration) |
| +void CSSAnimations::AnimationEventDelegate::onEventCondition(const TimedItem* timedItem, bool isFirstSample, TimedItem::Phase previousPhase, double previousIteration) |
| { |
| // Events for a single document are queued and dispatched as a group at |
| // the end of DocumentTimeline::serviceAnimations. |
| @@ -287,6 +430,26 @@ void CSSAnimations::EventDelegate::onEventCondition(const TimedItem* timedItem, |
| maybeDispatch(Document::ANIMATIONEND_LISTENER, EventNames::animationend, timedItem->activeDuration()); |
| } |
| +void CSSAnimations::TransitionEventDelegate::onEventCondition(const TimedItem* timedItem, bool isFirstSample, TimedItem::Phase previousPhase, double previousIteration) |
| +{ |
| + // Events for a single document are queued and dispatched as a group at |
| + // the end of DocumentTimeline::serviceAnimations. |
| + // FIXME: Events which are queued outside of serviceAnimations should |
| + // trigger a timer to dispatch when control is released. |
| + const TimedItem::Phase currentPhase = timedItem->phase(); |
| + if (currentPhase == TimedItem::PhaseAfter && (isFirstSample || previousPhase != currentPhase)) { |
| + if (m_target->document().hasListenerType(Document::TRANSITIONEND_LISTENER)) { |
| + String propertyName = getPropertyNameString(m_property); |
| + const Timing& timing = timedItem->specified(); |
| + double elapsedTime = timing.iterationDuration; |
| + const AtomicString& eventType = EventNames::transitionend; |
| + String pseudoElement = PseudoElement::pseudoElementNameForEvents(m_target->pseudoId()); |
| + m_target->document().timeline()->addEventToDispatch(m_target, TransitionEvent::create(eventType, propertyName, elapsedTime, pseudoElement)); |
| + } |
| + } |
| +} |
| + |
| + |
| bool CSSAnimations::isAnimatableProperty(CSSPropertyID property) |
| { |
| switch (property) { |
| @@ -404,4 +567,19 @@ bool CSSAnimations::isAnimatableProperty(CSSPropertyID property) |
| } |
| } |
| +const StylePropertyShorthand& CSSAnimations::animatableProperties() |
| +{ |
| + DEFINE_STATIC_LOCAL(Vector<CSSPropertyID>, properties, ()); |
| + DEFINE_STATIC_LOCAL(StylePropertyShorthand, propertyShorthand, ()); |
| + if (properties.isEmpty()) { |
| + for (int i = firstCSSProperty; i < lastCSSProperty; ++i) { |
| + CSSPropertyID id = convertToCSSPropertyID(i); |
| + if (isAnimatableProperty(id)) |
| + properties.append(id); |
| + } |
| + propertyShorthand = StylePropertyShorthand(CSSPropertyInvalid, properties.begin(), properties.size()); |
| + } |
| + return propertyShorthand; |
| +} |
| + |
| } // namespace WebCore |