Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(375)

Unified Diff: Source/core/animation/css/CSSAnimations.cpp

Issue 26382004: Web Animations CSS: Implement CSS Transitions backed on Web Animations model (Closed) Base URL: https://chromium.googlesource.com/chromium/blink@master
Patch Set: fix logic for elapsedTime in event Created 7 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: Source/core/animation/css/CSSAnimations.cpp
diff --git a/Source/core/animation/css/CSSAnimations.cpp b/Source/core/animation/css/CSSAnimations.cpp
index 0e3a97b540249600f8f4856c1c7b2c1147685d97..533aec599ece9d58b10c6255cb2022195b0918a6 100644
--- a/Source/core/animation/css/CSSAnimations.cpp
+++ b/Source/core/animation/css/CSSAnimations.cpp
@@ -31,14 +31,19 @@
#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/EventNames.h"
+#include "core/events/TransitionEvent.h"
#include "core/events/WebKitAnimationEvent.h"
+// FIXME: Remove this once all properties are supported
+#include "core/page/animation/CSSPropertyAnimation.h"
#include "core/platform/animation/CSSAnimationDataList.h"
#include "core/platform/animation/TimingFunction.h"
#include "wtf/HashSet.h"
@@ -143,18 +148,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());
dstockwell 2013/10/08 21:02:59 where did this display != NONE check go?
Timothy Loh 2013/10/09 01:33:07 Looks redundant - if display is none and cssAnimat
+ ASSERT(RuntimeEnabledFeatures::webAnimationsCSSEnabled());
+ OwnPtr<CSSAnimationUpdate> update = adoptPtr(new CSSAnimationUpdate());
+ updateAnimationUpdate(update.get(), element, style, resolver);
dstockwell 2013/10/08 21:02:59 I think calculateAnimationUpdate and calculateTran
Timothy Loh 2013/10/09 01:33:07 Done.
+ updateTransitionUpdate(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::updateAnimationUpdate(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 +190,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 +202,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 +225,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 +236,205 @@ void CSSAnimations::maybeApplyPendingUpdate(Element* element)
}
m_animations.set(iter->name, players);
}
+
+ Vector<CSSPropertyID> removedTransitions;
+ for (TransitionMap::iterator iter = m_transitions.begin(); iter != m_transitions.end(); ++iter) {
dstockwell 2013/10/08 21:02:59 Why are we iterating over the transition map rathe
Timothy Loh 2013/10/09 01:33:07 Because I'm silly :(
+ Player* player = iter->value.get();
+ CSSPropertyID id = iter->key;
+ if (update->endedTransitions().contains(id)) {
+ player->cancel();
+ removedTransitions.append(id);
+ }
+ }
+ for (int i = 0; i < removedTransitions.size(); ++i)
dstockwell 2013/10/08 21:02:59 Should this be size_t?
Timothy Loh 2013/10/09 01:33:07 Loop removed (changed to loop over cancelled trans
+ m_transitions.remove(removedTransitions[i]);
Steve Block 2013/10/09 00:57:12 Why do you use two passes to remove the cancelled
Timothy Loh 2013/10/09 01:33:07 Doug had the same comment. Fixed.
+
+ HashMap<CSSPropertyID, RefPtr<InertAnimation> >::const_iterator iter;
+ for (iter = update->newTransitions().begin(); iter != update->newTransitions().end(); ++iter) {
+ CSSPropertyID id = iter->key;
+ OwnPtr<TransitionEventDelegate> eventDelegate = adoptPtr(new TransitionEventDelegate(element, id));
+ RefPtr<Animation> transition = Animation::create(element, iter->value->effect(), iter->value->specified(), eventDelegate.release());
+ // FIXME: We'll probably want a separate DocumentTimeline for transitions, or
+ // at least to not have transitions also added to the default AnimationStack.
+ Player* player = element->document().timeline()->play(transition.get()).get();
+ m_transitions.set(id, player);
+ }
+}
+
+namespace {
Steve Block 2013/10/09 00:57:12 You should merge this with the anonymous namespace
Timothy Loh 2013/10/09 07:39:23 Done. Also moved the existing function timingFromA
+
+struct TransitionDetails {
+ TransitionDetails(PassRefPtr<AnimatableValue> from, PassRefPtr<AnimatableValue> to, const CSSAnimationData* anim)
+ : from(from), to(to), anim(anim)
dstockwell 2013/10/08 21:02:59 place to and anim on separate lines for consistenc
Timothy Loh 2013/10/09 01:33:07 Done.
+ {
+ }
+ TransitionDetails() { } // The HashMap calls the default ctor
+ RefPtr<AnimatableValue> from;
+ RefPtr<AnimatableValue> to;
+ const CSSAnimationData* anim;
+};
+typedef HashMap<CSSPropertyID, TransitionDetails> StyleChange;
+
+void updateIfPropertyChanged(const CSSAnimationData* anim, CSSPropertyID id, const RenderStyle* oldStyle, const RenderStyle* newStyle, StyleChange& styleChange)
+{
+ RefPtr<AnimatableValue> from = CSSAnimatableValueFactory::create(id, oldStyle);
dstockwell 2013/10/08 21:02:59 If there's already a transition running on this pr
+ 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;
+
+ if (mode == CSSAnimationData::AnimateAll) {
+ const Vector<CSSPropertyID>& animatableProperties = CSSAnimations::animatableProperties();
+ for (unsigned i = 0; i < animatableProperties.size(); ++i) {
+ CSSPropertyID id = animatableProperties[i];
+ listedProperties.add(id);
+ // FIXME: We haven't yet added support for all animatable properties,
+ // so this check lets us transition 'all' without failing to pull
+ // values out of the RenderStyles.
+ if (CSSPropertyAnimation::propertiesEqual(id, oldStyle, newStyle))
dstockwell 2013/10/08 21:02:59 Could we just skip over the specific properties th
Steve Block 2013/10/09 00:57:12 I don't see how testing for equality means we skip
Timothy Loh 2013/10/09 01:33:07 Not sure what you mean here. The check is just so
Timothy Loh 2013/10/09 07:39:23 OK. Will take this out for now. This takes it from
+ continue;
+ updateIfPropertyChanged(anim, id, oldStyle, newStyle, styleChange);
+ }
+ continue;
+ }
+
dstockwell 2013/10/08 21:02:59 ASSERT mode == AnimateSingleProperty
Timothy Loh 2013/10/09 01:33:07 Done.
+ const StylePropertyShorthand& propertyList = 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 (!CSSAnimations::isAnimatableProperty(id))
+ continue;
+ listedProperties.add(id);
+ updateIfPropertyChanged(anim, id, oldStyle, newStyle, styleChange);
Steve Block 2013/10/09 00:57:12 Can you merge this block with the AnimateAll case
Timothy Loh 2013/10/09 07:39:23 OK, I changed animatableProperties to return a con
+ }
+ }
+ }
+}
+
+} // namespace
+
+void CSSAnimations::updateTransitionUpdate(CSSAnimationUpdate* update, CSSPropertyID id, const AnimatableValue* from, const AnimatableValue* to, const CSSAnimationData* anim, const TransitionMap* transitions)
dstockwell 2013/10/08 21:02:59 calculateTransitionUpdateForProperty
Timothy Loh 2013/10/09 01:33:07 Done.
+{
+ // 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;
+
+ if (transitions) {
+ TransitionMap::const_iterator existingTransitionIter = transitions->find(id);
+
+ if (existingTransitionIter != transitions->end() && !update->endedTransitions().contains(id)) {
+ const Animation* animation = static_cast<const Animation*>(existingTransitionIter->value->source());
dstockwell 2013/10/08 21:02:59 I don't like reaching in here to pull out the targ
Steve Block 2013/10/09 00:57:12 Agreed
Timothy Loh 2013/10/09 07:39:23 OK. Plumbing these explicitly through now. Also ch
+ const KeyframeAnimationEffect* existingEffect = static_cast<const KeyframeAnimationEffect*>(animation->effect());
+
+ ASSERT(existingEffect->getFrames().size() == 2);
+ const AnimatableValue* existingTo = existingEffect->getFrames()[1]->propertyValue(id);
+
+ 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, InertAnimation::create(effect, timing));
+}
+
+void CSSAnimations::updateTransitionUpdate(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()) {
dstockwell 2013/10/08 21:02:59 if display is none, there's should be nothing to d
Timothy Loh 2013/10/09 01:33:07 For animations, it looks like we'll set all the ru
+ StyleChange styleChange;
+ computeStyleChange(element->renderer()->style(), style, styleChange, listedProperties);
+ for (StyleChange::const_iterator iter = styleChange.begin(); iter != styleChange.end(); ++iter) {
+ CSSPropertyID id = iter->key;
+ AnimatableValue* from = iter->value.from.get();
+ AnimatableValue* to = iter->value.to.get();
+ const CSSAnimationData* anim = iter->value.anim;
+ updateTransitionUpdate(update, id, from, to, anim, transitions);
+ }
+ }
+
+ if (transitions) {
+ for (TransitionMap::const_iterator iter = transitions->begin(); iter != transitions->end(); ++iter) {
+ const TimedItem* timedItem = iter->value->source();
+ bool isReversed = timedItem->specified().playbackRate < 0;
dstockwell 2013/10/08 21:02:59 Reversing doesn't seem to be implemented, can we r
Timothy Loh 2013/10/09 01:33:07 Whoops, I pulled most of it out for a separate pat
+ TimedItem::Phase end = isReversed ? TimedItem::PhaseBefore : TimedItem::PhaseAfter;
+ if (timedItem->phase() == end || !listedProperties.contains(iter->key))
+ update->endTransition(iter->key);
+ }
+ }
+}
+
+
+AnimationEffect::CompositableValueMap CSSAnimations::compositableValuesForTransitions(const CSSAnimations* cssAnimations, const CSSAnimationUpdate* update)
dstockwell 2013/10/08 21:02:59 If the transitions are being added to the document
Timothy Loh 2013/10/09 07:39:23 This mean that frame 0 will show the end state, si
+{
+ // FIXME: Transitions shouldn't apply values for properties with a running CSS Animation.
+ AnimationEffect::CompositableValueMap result;
+
+ if (cssAnimations) {
+ const TransitionMap& transitions = cssAnimations->m_transitions;
+ for (TransitionMap::const_iterator iter = transitions.begin(); iter != transitions.end(); ++iter) {
+ CSSPropertyID id = iter->key;
+ if (update && update->endedTransitions().contains(id))
+ continue;
+ Player* player = iter->value.get();
+ Animation* animation = static_cast<Animation*>(player->source());
+ ASSERT(animation && animation->compositableValues());
+ const AnimationEffect::CompositableValueMap* compositableValues = animation->compositableValues();
+ ASSERT(compositableValues->size() == 1 && compositableValues->contains(id));
+ result.set(id, compositableValues->get(id));
+ }
+ }
+
+ if (update) {
+ HashMap<CSSPropertyID, RefPtr<InertAnimation> >::const_iterator iter;
dstockwell 2013/10/08 21:02:59 Put this on the next line
Timothy Loh 2013/10/09 01:33:07 Makes the line a bit long but OK.
+ for (iter = update->newTransitions().begin(); iter != update->newTransitions().end(); ++iter) {
+ CSSPropertyID id = iter->key;
+ InertAnimation* animation = iter->value.get();
+ OwnPtr<AnimationEffect::CompositableValueMap> compositableValues = animation->sample();
+ ASSERT(compositableValues->size() == 1 && compositableValues->contains(id));
+ result.set(id, compositableValues->get(id));
+ }
+ }
+
+ return result;
}
void CSSAnimations::cancel()
@@ -244,17 +445,20 @@ void CSSAnimations::cancel()
(*animationsIter)->cancel();
}
+ for (TransitionMap::iterator iter = m_transitions.begin(); iter != m_transitions.end(); ++iter)
+ iter->value->cancel();
+
m_animations.clear();
dstockwell 2013/10/08 21:02:59 m_transitions.clear()
Timothy Loh 2013/10/09 01:33:07 Done.
m_pendingUpdate = nullptr;
}
-void CSSAnimations::EventDelegate::maybeDispatch(Document::ListenerType listenerType, AtomicString& eventName, double elapsedTime)
+void CSSAnimations::AnimationEventDelegate::maybeDispatch(Document::ListenerType listenerType, 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 +491,26 @@ void CSSAnimations::EventDelegate::onEventCondition(const TimedItem* timedItem,
maybeDispatch(Document::ANIMATIONEND_LISTENER, eventNames().animationendEvent, 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::PhaseActive || previousPhase == currentPhase)
dstockwell 2013/10/08 21:02:59 Shouldn't we only be firing when currentPhase beco
Timothy Loh 2013/10/09 01:33:07 Tweaked condition slightly.
+ return;
+ if (m_target->document().hasListenerType(Document::TRANSITIONEND_LISTENER)) {
+ String propertyName = getPropertyNameString(m_id);
+ const Timing& timing = timedItem->specified();
+ double elapsedTime = timing.iterationDuration;
+ const AtomicString& eventType = eventNames().transitionendEvent;
+ 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) {
@@ -398,4 +622,17 @@ bool CSSAnimations::isAnimatableProperty(CSSPropertyID property)
}
}
+const Vector<CSSPropertyID>& CSSAnimations::animatableProperties()
+{
+ DEFINE_STATIC_LOCAL(Vector<CSSPropertyID>, properties, ());
+ if (properties.isEmpty()) {
+ for (int i = firstCSSProperty; i < lastCSSProperty; ++i) {
+ CSSPropertyID id = convertToCSSPropertyID(i);
+ if (isAnimatableProperty(id))
+ properties.append(id);
+ }
+ }
+ return properties;
+}
+
} // namespace WebCore

Powered by Google App Engine
This is Rietveld 408576698