#include "config.h"
#include "core/animation/css/CSSAnimations.h"
-#include "StylePropertyShorthand.h"
+#include "core/StylePropertyShorthand.h"
#include "core/animation/ActiveAnimations.h"
-#include "core/animation/DocumentTimeline.h"
-#include "core/animation/KeyframeAnimationEffect.h"
+#include "core/animation/AnimationTimeline.h"
+#include "core/animation/CompositorAnimations.h"
+#include "core/animation/KeyframeEffectModel.h"
+#include "core/animation/LegacyStyleInterpolation.h"
#include "core/animation/css/CSSAnimatableValueFactory.h"
+#include "core/animation/css/CSSPropertyEquality.h"
#include "core/css/CSSKeyframeRule.h"
+#include "core/css/CSSPropertyMetadata.h"
+#include "core/css/resolver/CSSToStyleMap.h"
#include "core/css/resolver/StyleResolver.h"
#include "core/dom/Element.h"
-#include "core/events/ThreadLocalEventNames.h"
+#include "core/dom/PseudoElement.h"
+#include "core/dom/StyleEngine.h"
#include "core/events/TransitionEvent.h"
#include "core/events/WebKitAnimationEvent.h"
-#include "core/frame/animation/CSSPropertyAnimation.h"
-#include "core/platform/animation/CSSAnimationDataList.h"
-#include "core/platform/animation/TimingFunction.h"
+#include "core/frame/UseCounter.h"
+#include "core/rendering/RenderLayer.h"
+#include "core/rendering/RenderObject.h"
+#include "core/rendering/style/KeyframeList.h"
+#include "platform/animation/TimingFunction.h"
+#include "public/platform/Platform.h"
+#include "wtf/BitArray.h"
#include "wtf/HashSet.h"
-namespace WebCore {
-
-struct CandidateTransition {
- CandidateTransition(PassRefPtr<AnimatableValue> from, PassRefPtr<AnimatableValue> to, const CSSAnimationData* anim)
- : from(from)
- , to(to)
- , anim(anim)
- {
- }
- CandidateTransition() { } // The HashMap calls the default ctor
- RefPtr<AnimatableValue> from;
- RefPtr<AnimatableValue> to;
- const CSSAnimationData* anim;
-};
-typedef HashMap<CSSPropertyID, CandidateTransition> CandidateTransitionMap;
+namespace blink {
namespace {
-bool isEarlierPhase(TimedItem::Phase target, TimedItem::Phase reference)
+CSSPropertyID propertyForAnimation(CSSPropertyID property)
{
- ASSERT(target != TimedItem::PhaseNone);
- ASSERT(reference != TimedItem::PhaseNone);
- return target < reference;
+ switch (property) {
+ case CSSPropertyWebkitPerspective:
+ return CSSPropertyPerspective;
+ case CSSPropertyWebkitTransform:
+ return CSSPropertyTransform;
+ case CSSPropertyWebkitPerspectiveOriginX:
+ case CSSPropertyWebkitPerspectiveOriginY:
+ return CSSPropertyPerspectiveOrigin;
+ case CSSPropertyWebkitTransformOriginX:
+ case CSSPropertyWebkitTransformOriginY:
+ case CSSPropertyWebkitTransformOriginZ:
+ return CSSPropertyTransformOrigin;
+ default:
+ break;
+ }
+ return property;
}
-bool isLaterPhase(TimedItem::Phase target, TimedItem::Phase reference)
+static void resolveKeyframes(StyleResolver* resolver, Element* element, const Element& parentElement, const RenderStyle& style, RenderStyle* parentStyle, const AtomicString& name, TimingFunction* defaultTimingFunction,
+ AnimatableValueKeyframeVector& keyframes)
{
- ASSERT(target != TimedItem::PhaseNone);
- ASSERT(reference != TimedItem::PhaseNone);
- return target > reference;
-}
+ // When the element is null, use its parent for scoping purposes.
+ const Element* elementForScoping = element ? element : &parentElement;
+ const StyleRuleKeyframes* keyframesRule = CSSAnimations::matchScopedKeyframesRule(resolver, elementForScoping, name.impl());
+ if (!keyframesRule)
+ return;
-// Returns the default timing function.
-const PassRefPtr<TimingFunction> timingFromAnimationData(const CSSAnimationData* animationData, Timing& timing, bool& isPaused)
-{
- if (animationData->isDelaySet())
- timing.startDelay = animationData->delay();
- if (animationData->isDurationSet()) {
- timing.iterationDuration = animationData->duration();
- timing.hasIterationDuration = true;
- }
- if (animationData->isIterationCountSet()) {
- if (animationData->iterationCount() == CSSAnimationData::IterationCountInfinite)
- timing.iterationCount = std::numeric_limits<double>::infinity();
- else
- timing.iterationCount = animationData->iterationCount();
- }
- if (animationData->isFillModeSet()) {
- switch (animationData->fillMode()) {
- case AnimationFillModeForwards:
- timing.fillMode = Timing::FillModeForwards;
- break;
- case AnimationFillModeBackwards:
- timing.fillMode = Timing::FillModeBackwards;
- break;
- case AnimationFillModeBoth:
- timing.fillMode = Timing::FillModeBoth;
- break;
- case AnimationFillModeNone:
- timing.fillMode = Timing::FillModeNone;
- break;
- default:
- ASSERT_NOT_REACHED();
+ const WillBeHeapVector<RefPtrWillBeMember<StyleKeyframe> >& styleKeyframes = keyframesRule->keyframes();
+ if (styleKeyframes.isEmpty())
+ return;
+
+ // Construct and populate the style for each keyframe
+ PropertySet specifiedPropertiesForUseCounter;
+ for (size_t i = 0; i < styleKeyframes.size(); ++i) {
+ const StyleKeyframe* styleKeyframe = styleKeyframes[i].get();
+ // It's OK to pass a null element here.
+ RefPtr<RenderStyle> keyframeStyle = resolver->styleForKeyframe(element, style, parentStyle, styleKeyframe, name);
+ RefPtrWillBeRawPtr<AnimatableValueKeyframe> keyframe = AnimatableValueKeyframe::create();
+ const Vector<double>& offsets = styleKeyframe->keys();
+ ASSERT(!offsets.isEmpty());
+ keyframe->setOffset(offsets[0]);
+ keyframe->setEasing(defaultTimingFunction);
+ const StylePropertySet& properties = styleKeyframe->properties();
+ for (unsigned j = 0; j < properties.propertyCount(); j++) {
+ specifiedPropertiesForUseCounter.add(properties.propertyAt(j).id());
+ CSSPropertyID property = propertyForAnimation(properties.propertyAt(j).id());
+ if (property == CSSPropertyWebkitAnimationTimingFunction || property == CSSPropertyAnimationTimingFunction) {
+ CSSValue* value = properties.propertyAt(j).value();
+ RefPtr<TimingFunction> timingFunction;
+ if (value->isInheritedValue() && parentStyle->animations())
+ timingFunction = parentStyle->animations()->timingFunctionList()[0];
+ else if (value->isInheritedValue() || value->isInitialValue())
+ timingFunction = CSSTimingData::initialTimingFunction();
+ else
+ timingFunction = CSSToStyleMap::mapAnimationTimingFunction(toCSSValueList(value)->item(0));
+ keyframe->setEasing(timingFunction.release());
+ } else if (CSSPropertyMetadata::isAnimatableProperty(property)) {
+ keyframe->setPropertyValue(property, CSSAnimatableValueFactory::create(property, *keyframeStyle).get());
+ }
}
- } else {
- timing.fillMode = Timing::FillModeNone;
- }
- if (animationData->isDirectionSet()) {
- switch (animationData->direction()) {
- case CSSAnimationData::AnimationDirectionNormal:
- timing.direction = Timing::PlaybackDirectionNormal;
- break;
- case CSSAnimationData::AnimationDirectionAlternate:
- timing.direction = Timing::PlaybackDirectionAlternate;
- break;
- case CSSAnimationData::AnimationDirectionReverse:
- timing.direction = Timing::PlaybackDirectionReverse;
- break;
- case CSSAnimationData::AnimationDirectionAlternateReverse:
- timing.direction = Timing::PlaybackDirectionAlternateReverse;
- break;
- default:
- ASSERT_NOT_REACHED();
+ keyframes.append(keyframe);
+ // The last keyframe specified at a given offset is used.
+ for (size_t j = 1; j < offsets.size(); ++j) {
+ keyframes.append(toAnimatableValueKeyframe(keyframe->cloneWithOffset(offsets[j]).get()));
}
}
- isPaused = animationData->isPlayStateSet() && animationData->playState() == AnimPlayStatePaused;
- return animationData->isTimingFunctionSet() ? animationData->timingFunction() : CSSAnimationData::initialAnimationTimingFunction();
-}
+ ASSERT(!keyframes.isEmpty());
-void calculateCandidateTransitionForProperty(const CSSAnimationData* anim, CSSPropertyID id, const RenderStyle* oldStyle, const RenderStyle* newStyle, CandidateTransitionMap& candidateMap)
-{
- if (!CSSPropertyAnimation::propertiesEqual(id, oldStyle, newStyle)) {
- 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 CandidateTransition.
- if (from->usesNonDefaultInterpolationWith(to.get()))
- candidateMap.add(id, CandidateTransition(from.release(), to.release(), anim));
+ for (PropertySet::const_iterator iter = specifiedPropertiesForUseCounter.begin(); iter != specifiedPropertiesForUseCounter.end(); ++iter) {
+ const CSSPropertyID property = *iter;
+ ASSERT(property != CSSPropertyInvalid);
+ blink::Platform::current()->histogramSparse("WebCore.Animation.CSSProperties", UseCounter::mapCSSPropertyIdToCSSSampleIdForHistogram(property));
}
-}
-
-void computeCandidateTransitions(const RenderStyle* oldStyle, const RenderStyle* newStyle, CandidateTransitionMap& candidateMap, 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()) {
- if (!CSSAnimations::isAnimatableProperty(anim->property()))
+ // Remove duplicate keyframes. In CSS the last keyframe at a given offset takes priority.
+ std::stable_sort(keyframes.begin(), keyframes.end(), Keyframe::compareOffsets);
+ size_t targetIndex = 0;
+ for (size_t i = 1; i < keyframes.size(); i++) {
+ if (keyframes[i]->offset() != keyframes[targetIndex]->offset())
+ targetIndex++;
+ if (targetIndex != i)
+ keyframes[targetIndex] = keyframes[i];
+ }
+ keyframes.shrink(targetIndex + 1);
+
+ // Add 0% and 100% keyframes if absent.
+ RefPtrWillBeRawPtr<AnimatableValueKeyframe> startKeyframe = keyframes[0];
+ if (startKeyframe->offset()) {
+ startKeyframe = AnimatableValueKeyframe::create();
+ startKeyframe->setOffset(0);
+ startKeyframe->setEasing(defaultTimingFunction);
+ keyframes.prepend(startKeyframe);
+ }
+ RefPtrWillBeRawPtr<AnimatableValueKeyframe> endKeyframe = keyframes[keyframes.size() - 1];
+ if (endKeyframe->offset() != 1) {
+ endKeyframe = AnimatableValueKeyframe::create();
+ endKeyframe->setOffset(1);
+ endKeyframe->setEasing(defaultTimingFunction);
+ keyframes.append(endKeyframe);
+ }
+ ASSERT(keyframes.size() >= 2);
+ ASSERT(!keyframes.first()->offset());
+ ASSERT(keyframes.last()->offset() == 1);
+
+ // Snapshot current property values for 0% and 100% if missing.
+ PropertySet allProperties;
+ size_t numKeyframes = keyframes.size();
+ for (size_t i = 0; i < numKeyframes; i++) {
+ const PropertySet& keyframeProperties = keyframes[i]->properties();
+ for (PropertySet::const_iterator iter = keyframeProperties.begin(); iter != keyframeProperties.end(); ++iter)
+ allProperties.add(*iter);
+ }
+ const PropertySet& startKeyframeProperties = startKeyframe->properties();
+ const PropertySet& endKeyframeProperties = endKeyframe->properties();
+ bool missingStartValues = startKeyframeProperties.size() < allProperties.size();
+ bool missingEndValues = endKeyframeProperties.size() < allProperties.size();
+ if (missingStartValues || missingEndValues) {
+ for (PropertySet::const_iterator iter = allProperties.begin(); iter != allProperties.end(); ++iter) {
+ const CSSPropertyID property = *iter;
+ bool startNeedsValue = missingStartValues && !startKeyframeProperties.contains(property);
+ bool endNeedsValue = missingEndValues && !endKeyframeProperties.contains(property);
+ if (!startNeedsValue && !endNeedsValue)
continue;
- listedProperties.add(anim->property());
- calculateCandidateTransitionForProperty(anim, anim->property(), oldStyle, newStyle, candidateMap);
- } else {
- for (unsigned i = 0; i < propertyList.length(); ++i) {
- CSSPropertyID id = propertyList.properties()[i];
- if (!animateAll && !CSSAnimations::isAnimatableProperty(id))
- continue;
- listedProperties.add(id);
- calculateCandidateTransitionForProperty(anim, id, oldStyle, newStyle, candidateMap);
- }
+ RefPtrWillBeRawPtr<AnimatableValue> snapshotValue = CSSAnimatableValueFactory::create(property, style);
+ if (startNeedsValue)
+ startKeyframe->setPropertyValue(property, snapshotValue.get());
+ if (endNeedsValue)
+ endKeyframe->setPropertyValue(property, snapshotValue.get());
}
}
+ ASSERT(startKeyframe->properties().size() == allProperties.size());
+ ASSERT(endKeyframe->properties().size() == allProperties.size());
}
} // namespace
-CSSAnimationUpdateScope::CSSAnimationUpdateScope(Element* target)
- : m_target(target)
+const StyleRuleKeyframes* CSSAnimations::matchScopedKeyframesRule(StyleResolver* resolver, const Element* element, const StringImpl* animationName)
{
- if (!m_target)
- return;
- // It's possible than an update was created outside an update scope. That's harmless
- // but we must clear it now to avoid applying it if an updated replacement is not
- // created in this scope.
- if (ActiveAnimations* activeAnimations = m_target->activeAnimations())
- activeAnimations->cssAnimations().setPendingUpdate(nullptr);
+ // FIXME: This is all implementation detail of style resolver, CSSAnimations shouldn't be reaching into any of it.
+ if (resolver->document().styleEngine()->hasOnlyScopedResolverForDocument())
+ return element->document().scopedStyleResolver()->keyframeStylesForAnimation(animationName);
+
+ WillBeHeapVector<RawPtrWillBeMember<ScopedStyleResolver>, 8> stack;
+ resolver->styleTreeResolveScopedKeyframesRules(element, stack);
+ if (stack.isEmpty())
+ return 0;
+
+ for (size_t i = 0; i < stack.size(); ++i) {
+ if (const StyleRuleKeyframes* keyframesRule = stack.at(i)->keyframeStylesForAnimation(animationName))
+ return keyframesRule;
+ }
+ return 0;
}
-CSSAnimationUpdateScope::~CSSAnimationUpdateScope()
+CSSAnimations::CSSAnimations()
{
- if (!m_target)
- return;
- if (ActiveAnimations* activeAnimations = m_target->activeAnimations())
- activeAnimations->cssAnimations().maybeApplyPendingUpdate(m_target);
}
-PassOwnPtr<CSSAnimationUpdate> CSSAnimations::calculateUpdate(Element* element, const RenderStyle* style, StyleResolver* resolver)
+PassOwnPtrWillBeRawPtr<CSSAnimationUpdate> CSSAnimations::calculateUpdate(Element* element, const Element& parentElement, const RenderStyle& style, RenderStyle* parentStyle, StyleResolver* resolver)
{
- ASSERT(RuntimeEnabledFeatures::webAnimationsCSSEnabled());
- OwnPtr<CSSAnimationUpdate> update = adoptPtr(new CSSAnimationUpdate());
- calculateAnimationUpdate(update.get(), element, style, resolver);
- calculateAnimationCompositableValues(update.get(), element);
+ OwnPtrWillBeRawPtr<CSSAnimationUpdate> update = adoptPtrWillBeNoop(new CSSAnimationUpdate());
+ calculateAnimationUpdate(update.get(), element, parentElement, style, parentStyle, resolver);
+ calculateAnimationActiveInterpolations(update.get(), element, parentElement.document().timeline().currentTimeInternal());
calculateTransitionUpdate(update.get(), element, style);
- calculateTransitionCompositableValues(update.get(), element);
+ calculateTransitionActiveInterpolations(update.get(), element, parentElement.document().timeline().currentTimeInternal());
return update->isEmpty() ? nullptr : update.release();
}
-void CSSAnimations::calculateAnimationUpdate(CSSAnimationUpdate* update, Element* element, const RenderStyle* style, StyleResolver* resolver)
+void CSSAnimations::calculateAnimationUpdate(CSSAnimationUpdate* update, Element* element, const Element& parentElement, const RenderStyle& style, RenderStyle* parentStyle, StyleResolver* resolver)
{
- const CSSAnimationDataList* animationDataList = style->animations();
- const CSSAnimations* cssAnimations = element->activeAnimations() ? &element->activeAnimations()->cssAnimations() : 0;
+ const ActiveAnimations* activeAnimations = element ? element->activeAnimations() : 0;
+
+#if !ENABLE(ASSERT)
+ // If we're in an animation style change, no animations can have started, been cancelled or changed play state.
+ // When ASSERT is enabled, we verify this optimization.
+ if (activeAnimations && activeAnimations->isAnimationStyleChange())
+ return;
+#endif
+
+ const CSSAnimationData* animationData = 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)
inactive.add(iter->key);
- if (style->display() != NONE) {
- for (size_t i = 0; animationDataList && i < animationDataList->size(); ++i) {
- const CSSAnimationData* animationData = animationDataList->animation(i);
- if (animationData->isNoneAnimation())
+ if (style.display() != NONE) {
+ for (size_t i = 0; animationData && i < animationData->nameList().size(); ++i) {
+ AtomicString animationName(animationData->nameList()[i]);
+ if (animationName == CSSAnimationData::initialName())
continue;
- ASSERT(animationData->isValidAnimation());
- AtomicString animationName(animationData->name());
+
+ bool isPaused = CSSTimingData::getRepeated(animationData->playStateList(), i) == AnimPlayStatePaused;
// Keyframes and animation properties are snapshotted when the
// animation starts, so we don't need to track changes to these,
AnimationMap::const_iterator existing(cssAnimations->m_animations.find(animationName));
if (existing != cssAnimations->m_animations.end()) {
inactive.remove(animationName);
- const HashSet<RefPtr<Player> >& players = existing->value;
- ASSERT(!players.isEmpty());
- bool isFirstPlayerPaused = (*players.begin())->paused();
-#ifndef NDEBUG
- for (HashSet<RefPtr<Player> >::const_iterator iter = players.begin(); iter != players.end(); ++iter)
- ASSERT((*iter)->paused() == isFirstPlayerPaused);
-#endif
- if ((animationData->playState() == AnimPlayStatePaused) != isFirstPlayerPaused)
+ AnimationPlayer* player = existing->value.get();
+ if (isPaused != player->paused()) {
+ ASSERT(!activeAnimations || !activeAnimations->isAnimationStyleChange());
update->toggleAnimationPaused(animationName);
+ }
continue;
}
}
- Timing timing;
- bool isPaused;
- RefPtr<TimingFunction> defaultTimingFunction = timingFromAnimationData(animationData, timing, isPaused);
- Vector<std::pair<KeyframeAnimationEffect::KeyframeVector, RefPtr<TimingFunction> > > keyframesAndTimingFunctions;
- resolver->resolveKeyframes(element, style, animationName, defaultTimingFunction.get(), keyframesAndTimingFunctions);
- if (!keyframesAndTimingFunctions.isEmpty()) {
- HashSet<RefPtr<InertAnimation> > animations;
- for (size_t j = 0; j < keyframesAndTimingFunctions.size(); ++j) {
- ASSERT(!keyframesAndTimingFunctions[j].first.isEmpty());
- timing.timingFunction = keyframesAndTimingFunctions[j].second;
- // FIXME: crbug.com/268791 - Keyframes are already normalized, perhaps there should be a flag on KeyframeAnimationEffect to skip normalization.
- animations.add(InertAnimation::create(KeyframeAnimationEffect::create(keyframesAndTimingFunctions[j].first), timing, isPaused));
- }
- update->startAnimation(animationName, animations);
+ Timing timing = animationData->convertToTiming(i);
+ RefPtr<TimingFunction> keyframeTimingFunction = timing.timingFunction;
+ timing.timingFunction = Timing::defaults().timingFunction;
+ AnimatableValueKeyframeVector resolvedKeyframes;
+ resolveKeyframes(resolver, element, parentElement, style, parentStyle, animationName, keyframeTimingFunction.get(), resolvedKeyframes);
+ if (!resolvedKeyframes.isEmpty()) {
+ ASSERT(!activeAnimations || !activeAnimations->isAnimationStyleChange());
+ update->startAnimation(animationName, InertAnimation::create(AnimatableValueKeyframeEffectModel::create(resolvedKeyframes), timing, isPaused));
}
}
}
- for (HashSet<AtomicString>::const_iterator iter = inactive.begin(); iter != inactive.end(); ++iter)
- update->cancelAnimation(*iter, cssAnimations->m_animations.get(*iter));
+ ASSERT(inactive.isEmpty() || cssAnimations);
+ for (HashSet<AtomicString>::const_iterator iter = inactive.begin(); iter != inactive.end(); ++iter) {
+ ASSERT(!activeAnimations || !activeAnimations->isAnimationStyleChange());
+ update->cancelAnimation(*iter, *cssAnimations->m_animations.get(*iter));
+ }
}
void CSSAnimations::maybeApplyPendingUpdate(Element* element)
{
- if (!element->renderer())
- m_pendingUpdate = nullptr;
-
if (!m_pendingUpdate) {
- m_previousCompositableValuesForAnimations.clear();
+ m_previousActiveInterpolationsForAnimations.clear();
return;
}
- OwnPtr<CSSAnimationUpdate> update = m_pendingUpdate.release();
+ OwnPtrWillBeRawPtr<CSSAnimationUpdate> update = m_pendingUpdate.release();
+
+ m_previousActiveInterpolationsForAnimations.swap(update->activeInterpolationsForAnimations());
- m_previousCompositableValuesForAnimations.swap(update->compositableValuesForAnimations());
+ // FIXME: cancelling, pausing, unpausing animations all query compositingState, which is not necessarily up to date here
+ // since we call this from recalc style.
+ // https://code.google.com/p/chromium/issues/detail?id=339847
+ DisableCompositingQueryAsserts disabler;
for (Vector<AtomicString>::const_iterator iter = update->cancelledAnimationNames().begin(); iter != update->cancelledAnimationNames().end(); ++iter) {
- const HashSet<RefPtr<Player> >& players = m_animations.take(*iter);
- for (HashSet<RefPtr<Player> >::const_iterator iter = players.begin(); iter != players.end(); ++iter)
- (*iter)->cancel();
+ RefPtr<AnimationPlayer> player = m_animations.take(*iter);
+ player->cancel();
+ player->update(TimingUpdateOnDemand);
}
for (Vector<AtomicString>::const_iterator iter = update->animationsWithPauseToggled().begin(); iter != update->animationsWithPauseToggled().end(); ++iter) {
- const HashSet<RefPtr<Player> >& players = m_animations.get(*iter);
- ASSERT(!players.isEmpty());
- bool isFirstPlayerPaused = (*players.begin())->paused();
- for (HashSet<RefPtr<Player> >::const_iterator iter = players.begin(); iter != players.end(); ++iter) {
- Player* player = iter->get();
- ASSERT(player->paused() == isFirstPlayerPaused);
- player->setPaused(!isFirstPlayerPaused);
- }
+ AnimationPlayer* player = m_animations.get(*iter);
+ if (player->paused())
+ player->unpause();
+ else
+ player->pause();
+ if (player->outdated())
+ player->update(TimingUpdateOnDemand);
}
- for (Vector<CSSAnimationUpdate::NewAnimation>::const_iterator iter = update->newAnimations().begin(); iter != update->newAnimations().end(); ++iter) {
- 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();
- // The event delegate is set on the the first animation only. We
- // rely on the behavior of OwnPtr::release() to achieve this.
- RefPtr<Animation> animation = Animation::create(element, inertAnimation->effect(), inertAnimation->specified(), Animation::DefaultPriority, eventDelegate.release());
- RefPtr<Player> player = element->document().timeline()->play(animation.get());
- player->setPaused(inertAnimation->paused());
- players.add(player.release());
- }
- m_animations.set(iter->name, players);
+ for (WillBeHeapVector<CSSAnimationUpdate::NewAnimation>::const_iterator iter = update->newAnimations().begin(); iter != update->newAnimations().end(); ++iter) {
+ const InertAnimation* inertAnimation = iter->animation.get();
+ OwnPtrWillBeRawPtr<AnimationEventDelegate> eventDelegate = adoptPtrWillBeNoop(new AnimationEventDelegate(element, iter->name));
+ RefPtrWillBeRawPtr<Animation> animation = Animation::create(element, inertAnimation->effect(), inertAnimation->specifiedTiming(), Animation::DefaultPriority, eventDelegate.release());
+ RefPtrWillBeRawPtr<AnimationPlayer> player = element->document().timeline().createAnimationPlayer(animation.get());
+ element->document().compositorPendingAnimations().add(player.get());
+ if (inertAnimation->paused())
+ player->pause();
+ player->update(TimingUpdateOnDemand);
+ m_animations.set(iter->name, player.get());
}
+ // Transitions that are run on the compositor only update main-thread state
+ // lazily. However, we need the new state to know what the from state shoud
+ // be when transitions are retargeted. Instead of triggering complete style
+ // recalculation, we find these cases by searching for new transitions that
+ // have matching cancelled animation property IDs on the compositor.
+ WillBeHeapHashMap<CSSPropertyID, std::pair<RefPtrWillBeMember<Animation>, double> > retargetedCompositorTransitions;
for (HashSet<CSSPropertyID>::iterator iter = update->cancelledTransitions().begin(); iter != update->cancelledTransitions().end(); ++iter) {
- ASSERT(m_transitions.contains(*iter));
- m_transitions.take(*iter).transition->player()->cancel();
+ CSSPropertyID id = *iter;
+ ASSERT(m_transitions.contains(id));
+
+ RefPtrWillBeRawPtr<AnimationPlayer> player = m_transitions.take(id).player;
+ Animation* animation = toAnimation(player->source());
+ if (animation->hasActiveAnimationsOnCompositor(id) && update->newTransitions().find(id) != update->newTransitions().end())
+ retargetedCompositorTransitions.add(id, std::pair<RefPtrWillBeMember<Animation>, double>(animation, player->startTimeInternal()));
+ player->cancel();
+ player->update(TimingUpdateOnDemand);
}
- for (size_t i = 0; i < update->newTransitions().size(); ++i) {
- const CSSAnimationUpdate::NewTransition& newTransition = update->newTransitions()[i];
+ for (CSSAnimationUpdate::NewTransitionMap::const_iterator iter = update->newTransitions().begin(); iter != update->newTransitions().end(); ++iter) {
+ const CSSAnimationUpdate::NewTransition& newTransition = iter->value;
RunningTransition runningTransition;
runningTransition.from = newTransition.from;
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(), Animation::TransitionPriority, eventDelegate.release());
- element->document().transitionTimeline()->play(transition.get());
- runningTransition.transition = transition.get();
+ OwnPtrWillBeRawPtr<TransitionEventDelegate> eventDelegate = adoptPtrWillBeNoop(new TransitionEventDelegate(element, newTransition.eventId));
+
+ RefPtrWillBeRawPtr<AnimationEffect> effect = inertAnimation->effect();
+
+ if (retargetedCompositorTransitions.contains(id)) {
+ const std::pair<RefPtrWillBeMember<Animation>, double>& oldTransition = retargetedCompositorTransitions.get(id);
+ RefPtrWillBeRawPtr<Animation> oldAnimation = oldTransition.first;
+ double oldStartTime = oldTransition.second;
+ double inheritedTime = isNull(oldStartTime) ? 0 : element->document().timeline().currentTimeInternal() - oldStartTime;
+
+ AnimatableValueKeyframeEffectModel* oldEffect = toAnimatableValueKeyframeEffectModel(inertAnimation->effect());
+ const KeyframeVector& frames = oldEffect->getFrames();
+
+ AnimatableValueKeyframeVector newFrames;
+ newFrames.append(toAnimatableValueKeyframe(frames[0]->clone().get()));
+ newFrames.append(toAnimatableValueKeyframe(frames[1]->clone().get()));
+
+ newFrames[0]->clearPropertyValue(id);
+ RefPtrWillBeRawPtr<InertAnimation> inertAnimationForSampling = InertAnimation::create(oldAnimation->effect(), oldAnimation->specifiedTiming(), false);
+ OwnPtrWillBeRawPtr<WillBeHeapVector<RefPtrWillBeMember<Interpolation> > > sample = inertAnimationForSampling->sample(inheritedTime);
+ ASSERT(sample->size() == 1);
+ newFrames[0]->setPropertyValue(id, toLegacyStyleInterpolation(sample->at(0).get())->currentValue());
+
+ effect = AnimatableValueKeyframeEffectModel::create(newFrames);
+ }
+
+ RefPtrWillBeRawPtr<Animation> transition = Animation::create(element, effect, inertAnimation->specifiedTiming(), Animation::TransitionPriority, eventDelegate.release());
+ RefPtrWillBeRawPtr<AnimationPlayer> player = element->document().timeline().createAnimationPlayer(transition.get());
+ element->document().compositorPendingAnimations().add(player.get());
+ player->update(TimingUpdateOnDemand);
+ runningTransition.player = player;
m_transitions.set(id, runningTransition);
+ ASSERT(id != CSSPropertyInvalid);
+ blink::Platform::current()->histogramSparse("WebCore.Animation.CSSProperties", UseCounter::mapCSSPropertyIdToCSSSampleIdForHistogram(id));
}
}
-void CSSAnimations::calculateTransitionUpdateForProperty(CSSAnimationUpdate* update, CSSPropertyID id, const CandidateTransition& newTransition, const TransitionMap* existingTransitions)
+void CSSAnimations::calculateTransitionUpdateForProperty(CSSPropertyID id, CSSPropertyID eventId, const CSSTransitionData& transitionData, size_t transitionIndex, const RenderStyle& oldStyle, const RenderStyle& style, const TransitionMap* activeTransitions, CSSAnimationUpdate* update, const Element* element)
{
- if (existingTransitions) {
- TransitionMap::const_iterator existingTransitionIter = existingTransitions->find(id);
-
- if (existingTransitionIter != existingTransitions->end() && !update->cancelledTransitions().contains(id)) {
- const AnimatableValue* existingTo = existingTransitionIter->value.to;
- if (newTransition.to->equals(existingTo))
+ RefPtrWillBeRawPtr<AnimatableValue> to = nullptr;
+ if (activeTransitions) {
+ TransitionMap::const_iterator activeTransitionIter = activeTransitions->find(id);
+ if (activeTransitionIter != activeTransitions->end()) {
+ to = CSSAnimatableValueFactory::create(id, style);
+ const AnimatableValue* activeTo = activeTransitionIter->value.to;
+ if (to->equals(activeTo))
return;
update->cancelTransition(id);
+ ASSERT(!element->activeAnimations() || !element->activeAnimations()->isAnimationStyleChange());
}
}
- KeyframeAnimationEffect::KeyframeVector keyframes;
+ if (CSSPropertyEquality::propertiesEqual(id, oldStyle, style))
+ return;
+ if (!to)
+ to = CSSAnimatableValueFactory::create(id, style);
+
+ RefPtrWillBeRawPtr<AnimatableValue> from = CSSAnimatableValueFactory::create(id, oldStyle);
+ // If we have multiple transitions on the same property, we will use the
+ // last one since we iterate over them in order.
+ if (AnimatableValue::usesDefaultInterpolation(to.get(), from.get()))
+ return;
+
+ Timing timing = transitionData.convertToTiming(transitionIndex);
+ if (timing.startDelay + timing.iterationDuration <= 0)
+ return;
+
+ AnimatableValueKeyframeVector keyframes;
- RefPtr<Keyframe> startKeyframe = Keyframe::create();
- startKeyframe->setPropertyValue(id, newTransition.from.get());
+ RefPtrWillBeRawPtr<AnimatableValueKeyframe> startKeyframe = AnimatableValueKeyframe::create();
+ startKeyframe->setPropertyValue(id, from.get());
startKeyframe->setOffset(0);
+ startKeyframe->setEasing(timing.timingFunction.release());
+ timing.timingFunction = LinearTimingFunction::shared();
keyframes.append(startKeyframe);
- RefPtr<Keyframe> endKeyframe = Keyframe::create();
- endKeyframe->setPropertyValue(id, newTransition.to.get());
+ RefPtrWillBeRawPtr<AnimatableValueKeyframe> endKeyframe = AnimatableValueKeyframe::create();
+ endKeyframe->setPropertyValue(id, to.get());
endKeyframe->setOffset(1);
keyframes.append(endKeyframe);
- RefPtr<KeyframeAnimationEffect> effect = KeyframeAnimationEffect::create(keyframes);
-
- Timing timing;
- bool isPaused;
- RefPtr<TimingFunction> timingFunction = timingFromAnimationData(newTransition.anim, timing, isPaused);
- ASSERT(!isPaused);
- timing.timingFunction = timingFunction;
- // Note that the backwards part is required for delay to work.
- timing.fillMode = Timing::FillModeBoth;
-
- update->startTransition(id, newTransition.from.get(), newTransition.to.get(), InertAnimation::create(effect, timing, isPaused));
+ RefPtrWillBeRawPtr<AnimatableValueKeyframeEffectModel> effect = AnimatableValueKeyframeEffectModel::create(keyframes);
+ update->startTransition(id, eventId, from.get(), to.get(), InertAnimation::create(effect, timing, false));
+ ASSERT(!element->activeAnimations() || !element->activeAnimations()->isAnimationStyleChange());
}
-void CSSAnimations::calculateTransitionUpdate(CSSAnimationUpdate* update, const Element* element, const RenderStyle* style)
+void CSSAnimations::calculateTransitionUpdate(CSSAnimationUpdate* update, const Element* element, const RenderStyle& style)
{
+ if (!element)
+ return;
+
ActiveAnimations* activeAnimations = element->activeAnimations();
- const TransitionMap* transitions = activeAnimations ? &activeAnimations->cssAnimations().m_transitions : 0;
-
- HashSet<CSSPropertyID> listedProperties;
- if (style->display() != NONE && element->renderer() && element->renderer()->style()) {
- CandidateTransitionMap candidateMap;
- computeCandidateTransitions(element->renderer()->style(), style, candidateMap, listedProperties);
- for (CandidateTransitionMap::const_iterator iter = candidateMap.begin(); iter != candidateMap.end(); ++iter) {
- // FIXME: We should transition if an !important property changes even when an animation is running,
- // but this is a bit hard to do with the current applyMatchedProperties system.
- if (!update->compositableValuesForAnimations().contains(iter->key)
- && (!activeAnimations || !activeAnimations->cssAnimations().m_previousCompositableValuesForAnimations.contains(iter->key)))
- calculateTransitionUpdateForProperty(update, iter->key, iter->value, transitions);
+ const TransitionMap* activeTransitions = activeAnimations ? &activeAnimations->cssAnimations().m_transitions : 0;
+ const CSSTransitionData* transitionData = style.transitions();
+
+#if ENABLE(ASSERT)
+ // In debug builds we verify that it would have been safe to avoid populating and testing listedProperties if the style recalc is due to animation.
+ const bool animationStyleRecalc = false;
+#else
+ // In release builds we avoid the cost of checking for new and interrupted transitions if the style recalc is due to animation.
+ const bool animationStyleRecalc = activeAnimations && activeAnimations->isAnimationStyleChange();
+#endif
+
+ BitArray<numCSSProperties> listedProperties;
+ bool anyTransitionHadTransitionAll = false;
+ const RenderObject* renderer = element->renderer();
+ if (!animationStyleRecalc && style.display() != NONE && renderer && renderer->style() && transitionData) {
+ const RenderStyle& oldStyle = *renderer->style();
+
+ for (size_t i = 0; i < transitionData->propertyList().size(); ++i) {
+ const CSSTransitionData::TransitionProperty& transitionProperty = transitionData->propertyList()[i];
+ CSSTransitionData::TransitionPropertyType mode = transitionProperty.propertyType;
+ CSSPropertyID property = transitionProperty.propertyId;
+ if (mode == CSSTransitionData::TransitionNone || mode == CSSTransitionData::TransitionUnknown)
+ continue;
+
+ bool animateAll = mode == CSSTransitionData::TransitionAll;
+ ASSERT(animateAll || mode == CSSTransitionData::TransitionSingleProperty);
+ if (animateAll)
+ anyTransitionHadTransitionAll = true;
+ const StylePropertyShorthand& propertyList = animateAll ? CSSAnimations::animatableProperties() : shorthandForProperty(property);
+ // If not a shorthand we only execute one iteration of this loop, and refer to the property directly.
+ for (unsigned j = 0; !j || j < propertyList.length(); ++j) {
+ CSSPropertyID id = propertyList.length() ? propertyList.properties()[j] : property;
+ CSSPropertyID eventId = id;
+
+ if (!animateAll) {
+ id = propertyForAnimation(id);
+ if (CSSPropertyMetadata::isAnimatableProperty(id))
+ listedProperties.set(id);
+ else
+ continue;
+ }
+
+ // FIXME: We should transition if an !important property changes even when an animation is running,
+ // but this is a bit hard to do with the current applyMatchedProperties system.
+ if (!update->activeInterpolationsForAnimations().contains(id)
+ && (!activeAnimations || !activeAnimations->cssAnimations().m_previousActiveInterpolationsForAnimations.contains(id))) {
+ calculateTransitionUpdateForProperty(id, eventId, *transitionData, i, oldStyle, style, activeTransitions, update, element);
+ }
+ }
}
}
- if (transitions) {
- for (TransitionMap::const_iterator iter = transitions->begin(); iter != transitions->end(); ++iter) {
- const TimedItem* timedItem = iter->value.transition;
+ if (activeTransitions) {
+ for (TransitionMap::const_iterator iter = activeTransitions->begin(); iter != activeTransitions->end(); ++iter) {
+ const AnimationPlayer& player = *iter->value.player;
CSSPropertyID id = iter->key;
- if (timedItem->phase() == TimedItem::PhaseAfter || !listedProperties.contains(id))
+ if (player.finishedInternal() || (!anyTransitionHadTransitionAll && !animationStyleRecalc && !listedProperties.get(id))) {
+ // TODO: Figure out why this fails on Chrome OS login page. crbug.com/365507
+ // ASSERT(player.finishedInternal() || !(activeAnimations && activeAnimations->isAnimationStyleChange()));
update->cancelTransition(id);
+ }
}
}
}
void CSSAnimations::cancel()
{
for (AnimationMap::iterator iter = m_animations.begin(); iter != m_animations.end(); ++iter) {
- const HashSet<RefPtr<Player> >& players = iter->value;
- for (HashSet<RefPtr<Player> >::const_iterator animationsIter = players.begin(); animationsIter != players.end(); ++animationsIter)
- (*animationsIter)->cancel();
+ iter->value->cancel();
+ iter->value->update(TimingUpdateOnDemand);
}
- for (TransitionMap::iterator iter = m_transitions.begin(); iter != m_transitions.end(); ++iter)
- iter->value.transition->player()->cancel();
+ for (TransitionMap::iterator iter = m_transitions.begin(); iter != m_transitions.end(); ++iter) {
+ iter->value.player->cancel();
+ iter->value.player->update(TimingUpdateOnDemand);
+ }
m_animations.clear();
m_transitions.clear();
m_pendingUpdate = nullptr;
}
-void CSSAnimations::calculateAnimationCompositableValues(CSSAnimationUpdate* update, const Element* element)
+void CSSAnimations::calculateAnimationActiveInterpolations(CSSAnimationUpdate* update, const Element* element, double timelineCurrentTime)
{
- ActiveAnimations* activeAnimations = element->activeAnimations();
+ ActiveAnimations* activeAnimations = element ? element->activeAnimations() : 0;
AnimationStack* animationStack = activeAnimations ? &activeAnimations->defaultStack() : 0;
- if (update->newAnimations().isEmpty() && update->cancelledAnimationPlayers().isEmpty()) {
- AnimationEffect::CompositableValueMap compositableValuesForAnimations(AnimationStack::compositableValues(animationStack, 0, 0, Animation::DefaultPriority));
- update->adoptCompositableValuesForAnimations(compositableValuesForAnimations);
+ if (update->newAnimations().isEmpty() && update->cancelledAnimationAnimationPlayers().isEmpty()) {
+ WillBeHeapHashMap<CSSPropertyID, RefPtrWillBeMember<Interpolation> > activeInterpolationsForAnimations(AnimationStack::activeInterpolations(animationStack, 0, 0, Animation::DefaultPriority, timelineCurrentTime));
+ update->adoptActiveInterpolationsForAnimations(activeInterpolationsForAnimations);
+ return;
}
- Vector<InertAnimation*> newAnimations;
+ WillBeHeapVector<RawPtrWillBeMember<InertAnimation> > newAnimations;
for (size_t i = 0; i < update->newAnimations().size(); ++i) {
- HashSet<RefPtr<InertAnimation> > animations = update->newAnimations()[i].animations;
- for (HashSet<RefPtr<InertAnimation> >::const_iterator animationsIter = animations.begin(); animationsIter != animations.end(); ++animationsIter)
- newAnimations.append(animationsIter->get());
+ newAnimations.append(update->newAnimations()[i].animation.get());
}
- AnimationEffect::CompositableValueMap compositableValuesForAnimations(AnimationStack::compositableValues(animationStack, &newAnimations, &update->cancelledAnimationPlayers(), Animation::DefaultPriority));
- update->adoptCompositableValuesForAnimations(compositableValuesForAnimations);
+ WillBeHeapHashMap<CSSPropertyID, RefPtrWillBeMember<Interpolation> > activeInterpolationsForAnimations(AnimationStack::activeInterpolations(animationStack, &newAnimations, &update->cancelledAnimationAnimationPlayers(), Animation::DefaultPriority, timelineCurrentTime));
+ update->adoptActiveInterpolationsForAnimations(activeInterpolationsForAnimations);
}
-void CSSAnimations::calculateTransitionCompositableValues(CSSAnimationUpdate* update, const Element* element)
+void CSSAnimations::calculateTransitionActiveInterpolations(CSSAnimationUpdate* update, const Element* element, double timelineCurrentTime)
{
- ActiveAnimations* activeAnimations = element->activeAnimations();
+ ActiveAnimations* activeAnimations = element ? element->activeAnimations() : 0;
AnimationStack* animationStack = activeAnimations ? &activeAnimations->defaultStack() : 0;
- AnimationEffect::CompositableValueMap compositableValuesForTransitions;
+ WillBeHeapHashMap<CSSPropertyID, RefPtrWillBeMember<Interpolation> > activeInterpolationsForTransitions;
if (update->newTransitions().isEmpty() && update->cancelledTransitions().isEmpty()) {
- compositableValuesForTransitions = AnimationStack::compositableValues(animationStack, 0, 0, Animation::TransitionPriority);
+ activeInterpolationsForTransitions = AnimationStack::activeInterpolations(animationStack, 0, 0, Animation::TransitionPriority, timelineCurrentTime);
} else {
- Vector<InertAnimation*> newTransitions;
- for (size_t i = 0; i < update->newTransitions().size(); ++i)
- newTransitions.append(update->newTransitions()[i].animation.get());
+ WillBeHeapVector<RawPtrWillBeMember<InertAnimation> > newTransitions;
+ for (CSSAnimationUpdate::NewTransitionMap::const_iterator iter = update->newTransitions().begin(); iter != update->newTransitions().end(); ++iter)
+ newTransitions.append(iter->value.animation.get());
- HashSet<const Player*> cancelledPlayers;
+ WillBeHeapHashSet<RawPtrWillBeMember<const AnimationPlayer> > cancelledAnimationPlayers;
if (!update->cancelledTransitions().isEmpty()) {
+ ASSERT(activeAnimations);
const TransitionMap& transitionMap = activeAnimations->cssAnimations().m_transitions;
for (HashSet<CSSPropertyID>::iterator iter = update->cancelledTransitions().begin(); iter != update->cancelledTransitions().end(); ++iter) {
ASSERT(transitionMap.contains(*iter));
- cancelledPlayers.add(transitionMap.get(*iter).transition->player());
+ cancelledAnimationPlayers.add(transitionMap.get(*iter).player.get());
}
}
- compositableValuesForTransitions = AnimationStack::compositableValues(animationStack, &newTransitions, &cancelledPlayers, Animation::TransitionPriority);
+ activeInterpolationsForTransitions = AnimationStack::activeInterpolations(animationStack, &newTransitions, &cancelledAnimationPlayers, Animation::TransitionPriority, timelineCurrentTime);
}
// Properties being animated by animations don't get values from transitions applied.
- if (!update->compositableValuesForAnimations().isEmpty() && !compositableValuesForTransitions.isEmpty()) {
- for (AnimationEffect::CompositableValueMap::const_iterator iter = update->compositableValuesForAnimations().begin(); iter != update->compositableValuesForAnimations().end(); ++iter)
- compositableValuesForTransitions.remove(iter->key);
+ if (!update->activeInterpolationsForAnimations().isEmpty() && !activeInterpolationsForTransitions.isEmpty()) {
+ for (WillBeHeapHashMap<CSSPropertyID, RefPtrWillBeMember<Interpolation> >::const_iterator iter = update->activeInterpolationsForAnimations().begin(); iter != update->activeInterpolationsForAnimations().end(); ++iter)
+ activeInterpolationsForTransitions.remove(iter->key);
}
- update->adoptCompositableValuesForTransitions(compositableValuesForTransitions);
+ update->adoptActiveInterpolationsForTransitions(activeInterpolationsForTransitions);
}
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));
+ if (m_target->document().hasListenerType(listenerType)) {
+ RefPtrWillBeRawPtr<WebKitAnimationEvent> event = WebKitAnimationEvent::create(eventName, m_name, elapsedTime);
+ event->setTarget(m_target);
+ m_target->document().enqueueAnimationFrameEvent(event);
+ }
}
-void CSSAnimations::AnimationEventDelegate::onEventCondition(const TimedItem* timedItem, bool isFirstSample, TimedItem::Phase previousPhase, double previousIteration)
+void CSSAnimations::AnimationEventDelegate::onEventCondition(const AnimationNode* animationNode)
{
- // 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();
- const double currentIteration = timedItem->currentIteration();
-
- // Note that the elapsedTime is measured from when the animation starts playing.
- if (!isFirstSample && previousPhase == TimedItem::PhaseActive && currentPhase == TimedItem::PhaseActive && previousIteration != currentIteration) {
- ASSERT(!isNull(previousIteration));
- ASSERT(!isNull(currentIteration));
+ const AnimationNode::Phase currentPhase = animationNode->phase();
+ const double currentIteration = animationNode->currentIteration();
+
+ if (m_previousPhase != currentPhase
+ && (currentPhase == AnimationNode::PhaseActive || currentPhase == AnimationNode::PhaseAfter)
+ && (m_previousPhase == AnimationNode::PhaseNone || m_previousPhase == AnimationNode::PhaseBefore)) {
+ // The spec states that the elapsed time should be
+ // 'delay < 0 ? -delay : 0', but we always use 0 to match the existing
+ // implementation. See crbug.com/279611
+ maybeDispatch(Document::ANIMATIONSTART_LISTENER, EventTypeNames::animationstart, 0);
+ }
+
+ if (currentPhase == AnimationNode::PhaseActive && m_previousPhase == currentPhase && m_previousIteration != currentIteration) {
// We fire only a single event for all iterations thast terminate
// between a single pair of samples. See http://crbug.com/275263. For
// compatibility with the existing implementation, this event uses
// the elapsedTime for the first iteration in question.
- ASSERT(timedItem->specified().hasIterationDuration);
- const double elapsedTime = timedItem->specified().iterationDuration * (previousIteration + 1);
+ ASSERT(!std::isnan(animationNode->specifiedTiming().iterationDuration));
+ const double elapsedTime = animationNode->specifiedTiming().iterationDuration * (m_previousIteration + 1);
maybeDispatch(Document::ANIMATIONITERATION_LISTENER, EventTypeNames::animationiteration, elapsedTime);
- return;
}
- if ((isFirstSample || previousPhase == TimedItem::PhaseBefore) && isLaterPhase(currentPhase, TimedItem::PhaseBefore)) {
- ASSERT(timedItem->specified().startDelay > 0 || isFirstSample);
- // The spec states that the elapsed time should be
- // 'delay < 0 ? -delay : 0', but we always use 0 to match the existing
- // implementation. See crbug.com/279611
- maybeDispatch(Document::ANIMATIONSTART_LISTENER, EventTypeNames::animationstart, 0);
- }
- if ((isFirstSample || isEarlierPhase(previousPhase, TimedItem::PhaseAfter)) && currentPhase == TimedItem::PhaseAfter)
- maybeDispatch(Document::ANIMATIONEND_LISTENER, EventTypeNames::animationend, timedItem->activeDuration());
+
+ if (currentPhase == AnimationNode::PhaseAfter && m_previousPhase != AnimationNode::PhaseAfter)
+ maybeDispatch(Document::ANIMATIONEND_LISTENER, EventTypeNames::animationend, animationNode->activeDurationInternal());
+
+ m_previousPhase = currentPhase;
+ m_previousIteration = currentIteration;
+}
+
+void CSSAnimations::AnimationEventDelegate::trace(Visitor* visitor)
+{
+ visitor->trace(m_target);
+ AnimationNode::EventDelegate::trace(visitor);
}
-void CSSAnimations::TransitionEventDelegate::onEventCondition(const TimedItem* timedItem, bool isFirstSample, TimedItem::Phase previousPhase, double previousIteration)
+void CSSAnimations::TransitionEventDelegate::onEventCondition(const AnimationNode* animationNode)
{
- // 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) && m_target->document().hasListenerType(Document::TRANSITIONEND_LISTENER)) {
+ const AnimationNode::Phase currentPhase = animationNode->phase();
+ if (currentPhase == AnimationNode::PhaseAfter && currentPhase != m_previousPhase && m_target->document().hasListenerType(Document::TRANSITIONEND_LISTENER)) {
String propertyName = getPropertyNameString(m_property);
- const Timing& timing = timedItem->specified();
+ const Timing& timing = animationNode->specifiedTiming();
double elapsedTime = timing.iterationDuration;
const AtomicString& eventType = EventTypeNames::transitionend;
String pseudoElement = PseudoElement::pseudoElementNameForEvents(m_target->pseudoId());
- m_target->document().transitionTimeline()->addEventToDispatch(m_target, TransitionEvent::create(eventType, propertyName, elapsedTime, pseudoElement));
+ RefPtrWillBeRawPtr<TransitionEvent> event = TransitionEvent::create(eventType, propertyName, elapsedTime, pseudoElement);
+ event->setTarget(m_target);
+ m_target->document().enqueueAnimationFrameEvent(event);
}
-}
+ m_previousPhase = currentPhase;
+}
-bool CSSAnimations::isAnimatableProperty(CSSPropertyID property)
+void CSSAnimations::TransitionEventDelegate::trace(Visitor* visitor)
{
- switch (property) {
- case CSSPropertyBackgroundColor:
- case CSSPropertyBackgroundImage:
- case CSSPropertyBackgroundPositionX:
- case CSSPropertyBackgroundPositionY:
- case CSSPropertyBackgroundSize:
- case CSSPropertyBaselineShift:
- case CSSPropertyBorderBottomColor:
- case CSSPropertyBorderBottomLeftRadius:
- case CSSPropertyBorderBottomRightRadius:
- case CSSPropertyBorderBottomWidth:
- case CSSPropertyBorderImageOutset:
- case CSSPropertyBorderImageSlice:
- case CSSPropertyBorderImageSource:
- case CSSPropertyBorderImageWidth:
- case CSSPropertyBorderLeftColor:
- case CSSPropertyBorderLeftWidth:
- case CSSPropertyBorderRightColor:
- case CSSPropertyBorderRightWidth:
- case CSSPropertyBorderTopColor:
- case CSSPropertyBorderTopLeftRadius:
- case CSSPropertyBorderTopRightRadius:
- case CSSPropertyBorderTopWidth:
- case CSSPropertyBottom:
- case CSSPropertyBoxShadow:
- case CSSPropertyClip:
- case CSSPropertyColor:
- case CSSPropertyFill:
- case CSSPropertyFillOpacity:
- case CSSPropertyFlexBasis:
- case CSSPropertyFlexGrow:
- case CSSPropertyFlexShrink:
- case CSSPropertyFloodColor:
- case CSSPropertyFloodOpacity:
- case CSSPropertyFontSize:
- case CSSPropertyHeight:
- case CSSPropertyKerning:
- case CSSPropertyLeft:
- case CSSPropertyLetterSpacing:
- case CSSPropertyLightingColor:
- case CSSPropertyLineHeight:
- case CSSPropertyListStyleImage:
- case CSSPropertyMarginBottom:
- case CSSPropertyMarginLeft:
- case CSSPropertyMarginRight:
- case CSSPropertyMarginTop:
- case CSSPropertyMaxHeight:
- case CSSPropertyMaxWidth:
- case CSSPropertyMinHeight:
- case CSSPropertyMinWidth:
- case CSSPropertyObjectPosition:
- case CSSPropertyOpacity:
- case CSSPropertyOrphans:
- case CSSPropertyOutlineColor:
- case CSSPropertyOutlineOffset:
- case CSSPropertyOutlineWidth:
- case CSSPropertyPaddingBottom:
- case CSSPropertyPaddingLeft:
- case CSSPropertyPaddingRight:
- case CSSPropertyPaddingTop:
- case CSSPropertyRight:
- case CSSPropertyStopColor:
- case CSSPropertyStopOpacity:
- case CSSPropertyStroke:
- case CSSPropertyStrokeDasharray:
- case CSSPropertyStrokeDashoffset:
- case CSSPropertyStrokeMiterlimit:
- case CSSPropertyStrokeOpacity:
- case CSSPropertyStrokeWidth:
- case CSSPropertyTextDecorationColor:
- case CSSPropertyTextIndent:
- case CSSPropertyTextShadow:
- case CSSPropertyTop:
- case CSSPropertyVisibility:
- case CSSPropertyWebkitBackgroundSize:
- case CSSPropertyWebkitBorderHorizontalSpacing:
- case CSSPropertyWebkitBorderVerticalSpacing:
- case CSSPropertyWebkitBoxShadow:
- case CSSPropertyWebkitClipPath:
- case CSSPropertyWebkitColumnCount:
- case CSSPropertyWebkitColumnGap:
- case CSSPropertyWebkitColumnRuleColor:
- case CSSPropertyWebkitColumnRuleWidth:
- case CSSPropertyWebkitColumnWidth:
- case CSSPropertyWebkitFilter:
- case CSSPropertyWebkitMaskBoxImageOutset:
- case CSSPropertyWebkitMaskBoxImageSlice:
- case CSSPropertyWebkitMaskBoxImageSource:
- case CSSPropertyWebkitMaskBoxImageWidth:
- case CSSPropertyWebkitMaskImage:
- case CSSPropertyWebkitMaskPositionX:
- case CSSPropertyWebkitMaskPositionY:
- case CSSPropertyWebkitMaskSize:
- case CSSPropertyWebkitPerspective:
- case CSSPropertyWebkitPerspectiveOriginX:
- case CSSPropertyWebkitPerspectiveOriginY:
- case CSSPropertyShapeInside:
- case CSSPropertyShapeOutside:
- case CSSPropertyShapeMargin:
- case CSSPropertyWebkitTextStrokeColor:
- case CSSPropertyWebkitTransform:
- case CSSPropertyWebkitTransformOriginX:
- case CSSPropertyWebkitTransformOriginY:
- case CSSPropertyWebkitTransformOriginZ:
- case CSSPropertyWidows:
- case CSSPropertyWidth:
- case CSSPropertyWordSpacing:
- case CSSPropertyZIndex:
- case CSSPropertyZoom:
- return true;
- // FIXME: Shorthands should not be present in this list, but
- // CSSPropertyAnimation implements animation of these shorthands
- // directly and makes use of this method.
- case CSSPropertyFlex:
- case CSSPropertyWebkitMaskBoxImage:
- return !RuntimeEnabledFeatures::webAnimationsCSSEnabled();
- default:
- return false;
- }
+ visitor->trace(m_target);
+ AnimationNode::EventDelegate::trace(visitor);
}
const StylePropertyShorthand& CSSAnimations::animatableProperties()
if (properties.isEmpty()) {
for (int i = firstCSSProperty; i < lastCSSProperty; ++i) {
CSSPropertyID id = convertToCSSPropertyID(i);
- if (isAnimatableProperty(id))
+ if (CSSPropertyMetadata::isAnimatableProperty(id))
properties.append(id);
}
propertyShorthand = StylePropertyShorthand(CSSPropertyInvalid, properties.begin(), properties.size());
return propertyShorthand;
}
-} // namespace WebCore
+// Animation properties are not allowed to be affected by Web Animations.
+// http://dev.w3.org/fxtf/web-animations/#not-animatable
+bool CSSAnimations::isAllowedAnimation(CSSPropertyID property)
+{
+ switch (property) {
+ case CSSPropertyAnimation:
+ case CSSPropertyAnimationDelay:
+ case CSSPropertyAnimationDirection:
+ case CSSPropertyAnimationDuration:
+ case CSSPropertyAnimationFillMode:
+ case CSSPropertyAnimationIterationCount:
+ case CSSPropertyAnimationName:
+ case CSSPropertyAnimationPlayState:
+ case CSSPropertyAnimationTimingFunction:
+ case CSSPropertyDisplay:
+ case CSSPropertyTransition:
+ case CSSPropertyTransitionDelay:
+ case CSSPropertyTransitionDuration:
+ case CSSPropertyTransitionProperty:
+ case CSSPropertyTransitionTimingFunction:
+ case CSSPropertyWebkitAnimation:
+ case CSSPropertyWebkitAnimationDelay:
+ case CSSPropertyWebkitAnimationDirection:
+ case CSSPropertyWebkitAnimationDuration:
+ case CSSPropertyWebkitAnimationFillMode:
+ case CSSPropertyWebkitAnimationIterationCount:
+ case CSSPropertyWebkitAnimationName:
+ case CSSPropertyWebkitAnimationPlayState:
+ case CSSPropertyWebkitAnimationTimingFunction:
+ case CSSPropertyWebkitTransition:
+ case CSSPropertyWebkitTransitionDelay:
+ case CSSPropertyWebkitTransitionDuration:
+ case CSSPropertyWebkitTransitionProperty:
+ case CSSPropertyWebkitTransitionTimingFunction:
+ return false;
+ default:
+ return true;
+ }
+}
+
+void CSSAnimations::trace(Visitor* visitor)
+{
+#if ENABLE(OILPAN)
+ visitor->trace(m_transitions);
+ visitor->trace(m_pendingUpdate);
+ visitor->trace(m_animations);
+ visitor->trace(m_previousActiveInterpolationsForAnimations);
+#endif
+}
+
+void CSSAnimationUpdate::trace(Visitor* visitor)
+{
+#if ENABLE(OILPAN)
+ visitor->trace(m_newTransitions);
+ visitor->trace(m_activeInterpolationsForAnimations);
+ visitor->trace(m_activeInterpolationsForTransitions);
+ visitor->trace(m_newAnimations);
+ visitor->trace(m_cancelledAnimationPlayers);
+#endif
+}
+
+} // namespace blink