Index: Source/core/animation/Animation.cpp |
diff --git a/Source/core/animation/Animation.cpp b/Source/core/animation/Animation.cpp |
index 1d829a2102657783b14fea11664c07f80e8b1720..aa1ddae606f38a8801d42447d6c71c56ce99bfa3 100644 |
--- a/Source/core/animation/Animation.cpp |
+++ b/Source/core/animation/Animation.cpp |
@@ -31,311 +31,1002 @@ |
#include "config.h" |
#include "core/animation/Animation.h" |
-#include "bindings/core/v8/Dictionary.h" |
-#include "bindings/core/v8/ExceptionState.h" |
-#include "core/animation/AnimationPlayer.h" |
#include "core/animation/AnimationTimeline.h" |
-#include "core/animation/AnimationTimingProperties.h" |
-#include "core/animation/CompositorAnimations.h" |
-#include "core/animation/ElementAnimations.h" |
-#include "core/animation/Interpolation.h" |
-#include "core/animation/KeyframeEffectModel.h" |
-#include "core/animation/PropertyHandle.h" |
-#include "core/dom/Element.h" |
-#include "core/dom/NodeComputedStyle.h" |
+#include "core/animation/KeyframeEffect.h" |
+#include "core/dom/Document.h" |
+#include "core/dom/ExceptionCode.h" |
+#include "core/events/AnimationPlayerEvent.h" |
#include "core/frame/UseCounter.h" |
-#include "core/paint/DeprecatedPaintLayer.h" |
-#include "core/svg/SVGElement.h" |
+#include "core/inspector/InspectorInstrumentation.h" |
+#include "core/inspector/InspectorTraceEvents.h" |
+#include "platform/RuntimeEnabledFeatures.h" |
+#include "platform/TraceEvent.h" |
+#include "public/platform/Platform.h" |
+#include "public/platform/WebCompositorAnimationPlayer.h" |
+#include "public/platform/WebCompositorSupport.h" |
+#include "wtf/MathExtras.h" |
namespace blink { |
-PassRefPtrWillBeRawPtr<Animation> Animation::create(Element* target, PassRefPtrWillBeRawPtr<AnimationEffect> effect, const Timing& timing, Priority priority, PassOwnPtrWillBeRawPtr<EventDelegate> eventDelegate) |
+namespace { |
+ |
+static unsigned nextSequenceNumber() |
{ |
- return adoptRefWillBeNoop(new Animation(target, effect, timing, priority, eventDelegate)); |
+ static unsigned next = 0; |
+ return ++next; |
} |
-PassRefPtrWillBeRawPtr<Animation> Animation::create(Element* element, const Vector<Dictionary>& keyframeDictionaryVector, double duration, ExceptionState& exceptionState) |
-{ |
- ASSERT(RuntimeEnabledFeatures::webAnimationsAPIEnabled()); |
- if (element) |
- UseCounter::count(element->document(), UseCounter::AnimationConstructorKeyframeListEffectObjectTiming); |
- return create(element, EffectInput::convert(element, keyframeDictionaryVector, exceptionState), TimingInput::convert(duration)); |
} |
-PassRefPtrWillBeRawPtr<Animation> Animation::create(Element* element, const Vector<Dictionary>& keyframeDictionaryVector, const AnimationTimingProperties& timingInput, ExceptionState& exceptionState) |
+ |
+PassRefPtrWillBeRawPtr<Animation> Animation::create(AnimationEffect* source, AnimationTimeline* timeline) |
{ |
- ASSERT(RuntimeEnabledFeatures::webAnimationsAPIEnabled()); |
- if (element) |
- UseCounter::count(element->document(), UseCounter::AnimationConstructorKeyframeListEffectObjectTiming); |
- return create(element, EffectInput::convert(element, keyframeDictionaryVector, exceptionState), TimingInput::convert(timingInput)); |
+ if (!timeline) { |
+ // FIXME: Support creating animations without a timeline. |
+ return nullptr; |
+ } |
+ |
+ RefPtrWillBeRawPtr<Animation> animation = adoptRefWillBeNoop(new Animation(timeline->document()->contextDocument().get(), *timeline, source)); |
+ animation->suspendIfNeeded(); |
+ |
+ if (timeline) { |
+ timeline->animationAttached(*animation); |
+ animation->attachCompositorTimeline(); |
+ } |
+ |
+ return animation.release(); |
} |
-PassRefPtrWillBeRawPtr<Animation> Animation::create(Element* element, const Vector<Dictionary>& keyframeDictionaryVector, ExceptionState& exceptionState) |
+ |
+Animation::Animation(ExecutionContext* executionContext, AnimationTimeline& timeline, AnimationEffect* content) |
+ : ActiveDOMObject(executionContext) |
+ , m_playState(Idle) |
+ , m_playbackRate(1) |
+ , m_startTime(nullValue()) |
+ , m_holdTime(0) |
+ , m_sequenceNumber(nextSequenceNumber()) |
+ , m_content(content) |
+ , m_timeline(&timeline) |
+ , m_paused(false) |
+ , m_held(true) |
+ , m_isPausedForTesting(false) |
+ , m_outdated(false) |
+ , m_finished(true) |
+ , m_compositorState(nullptr) |
+ , m_compositorPending(false) |
+ , m_compositorGroup(0) |
+ , m_currentTimePending(false) |
+ , m_stateIsBeingUpdated(false) |
{ |
- ASSERT(RuntimeEnabledFeatures::webAnimationsAPIEnabled()); |
- if (element) |
- UseCounter::count(element->document(), UseCounter::AnimationConstructorKeyframeListEffectNoTiming); |
- return create(element, EffectInput::convert(element, keyframeDictionaryVector, exceptionState), Timing()); |
+ if (m_content) { |
+ if (m_content->animation()) { |
+ m_content->animation()->cancel(); |
+ m_content->animation()->setSource(0); |
+ } |
+ m_content->attach(this); |
+ } |
} |
-Animation::Animation(Element* target, PassRefPtrWillBeRawPtr<AnimationEffect> effect, const Timing& timing, Priority priority, PassOwnPtrWillBeRawPtr<EventDelegate> eventDelegate) |
- : AnimationNode(timing, eventDelegate) |
- , m_target(target) |
- , m_effect(effect) |
- , m_sampledEffect(nullptr) |
- , m_priority(priority) |
+Animation::~Animation() |
{ |
#if !ENABLE(OILPAN) |
- if (m_target) |
- m_target->ensureElementAnimations().addAnimation(this); |
+ if (m_content) |
+ m_content->detach(); |
+ if (m_timeline) |
+ m_timeline->animationDestroyed(this); |
#endif |
+ |
+ destroyCompositorPlayer(); |
} |
-Animation::~Animation() |
-{ |
#if !ENABLE(OILPAN) |
- if (m_target) |
- m_target->elementAnimations()->notifyAnimationDestroyed(this); |
+void Animation::detachFromTimeline() |
+{ |
+ dispose(); |
+ m_timeline = nullptr; |
+} |
#endif |
+ |
+double Animation::sourceEnd() const |
+{ |
+ return m_content ? m_content->endTimeInternal() : 0; |
} |
-void Animation::attach(AnimationPlayer* player) |
+bool Animation::limited(double currentTime) const |
{ |
- if (m_target) { |
- m_target->ensureElementAnimations().players().add(player); |
- m_target->setNeedsAnimationStyleRecalc(); |
- } |
- AnimationNode::attach(player); |
+ return (m_playbackRate < 0 && currentTime <= 0) || (m_playbackRate > 0 && currentTime >= sourceEnd()); |
} |
-void Animation::detach() |
+void Animation::setCurrentTime(double newCurrentTime) |
{ |
- if (m_target) |
- m_target->elementAnimations()->players().remove(player()); |
- if (m_sampledEffect) |
- clearEffects(); |
- AnimationNode::detach(); |
+ PlayStateUpdateScope updateScope(*this, TimingUpdateOnDemand); |
+ |
+ m_currentTimePending = false; |
+ setCurrentTimeInternal(newCurrentTime / 1000, TimingUpdateOnDemand); |
+ |
+ if (calculatePlayState() == Finished) |
+ m_startTime = calculateStartTime(newCurrentTime); |
} |
-void Animation::specifiedTimingChanged() |
+void Animation::setCurrentTimeInternal(double newCurrentTime, TimingUpdateReason reason) |
{ |
- if (player()) { |
- // FIXME: Needs to consider groups when added. |
- ASSERT(player()->source() == this); |
- player()->setCompositorPending(true); |
+ ASSERT(std::isfinite(newCurrentTime)); |
+ |
+ bool oldHeld = m_held; |
+ bool outdated = false; |
+ bool isLimited = limited(newCurrentTime); |
+ m_held = m_paused || !m_playbackRate || isLimited || std::isnan(m_startTime); |
+ if (m_held) { |
+ if (!oldHeld || m_holdTime != newCurrentTime) |
+ outdated = true; |
+ m_holdTime = newCurrentTime; |
+ if (m_paused || !m_playbackRate) { |
+ m_startTime = nullValue(); |
+ } else if (isLimited && std::isnan(m_startTime) && reason == TimingUpdateForAnimationFrame) { |
+ m_startTime = calculateStartTime(newCurrentTime); |
+ } |
+ } else { |
+ m_holdTime = nullValue(); |
+ m_startTime = calculateStartTime(newCurrentTime); |
+ m_finished = false; |
+ outdated = true; |
} |
+ |
+ if (outdated) { |
+ setOutdated(); |
+ } |
+} |
+ |
+// Update timing to reflect updated animation clock due to tick |
+void Animation::updateCurrentTimingState(TimingUpdateReason reason) |
+{ |
+ if (m_held) { |
+ double newCurrentTime = m_holdTime; |
+ if (playStateInternal() == Finished && !isNull(m_startTime) && m_timeline) { |
+ // Add hystersis due to floating point error accumulation |
+ if (!limited(calculateCurrentTime() + 0.001 * m_playbackRate)) { |
+ // The current time became unlimited, eg. due to a backwards |
+ // seek of the timeline. |
+ newCurrentTime = calculateCurrentTime(); |
+ } else if (!limited(m_holdTime)) { |
+ // The hold time became unlimited, eg. due to the source content |
+ // becoming longer. |
+ newCurrentTime = clampTo<double>(calculateCurrentTime(), 0, sourceEnd()); |
+ } |
+ } |
+ setCurrentTimeInternal(newCurrentTime, reason); |
+ } else if (limited(calculateCurrentTime())) { |
+ m_held = true; |
+ m_holdTime = m_playbackRate < 0 ? 0 : sourceEnd(); |
+ } |
+} |
+ |
+double Animation::startTime(bool& isNull) const |
+{ |
+ double result = startTime(); |
+ isNull = std::isnan(result); |
+ return result; |
+} |
+ |
+double Animation::startTime() const |
+{ |
+ return m_startTime * 1000; |
} |
-static AnimationStack& ensureAnimationStack(Element* element) |
+double Animation::currentTime(bool& isNull) |
{ |
- return element->ensureElementAnimations().defaultStack(); |
+ double result = currentTime(); |
+ isNull = std::isnan(result); |
+ return result; |
} |
-void Animation::applyEffects() |
+double Animation::currentTime() |
{ |
- ASSERT(isInEffect()); |
- ASSERT(player()); |
- if (!m_target || !m_effect) |
+ PlayStateUpdateScope updateScope(*this, TimingUpdateOnDemand); |
+ |
+ if (m_currentTimePending || playStateInternal() == Idle) |
+ return std::numeric_limits<double>::quiet_NaN(); |
+ |
+ return currentTimeInternal() * 1000; |
+} |
+ |
+double Animation::currentTimeInternal() const |
+{ |
+ double result = m_held ? m_holdTime : calculateCurrentTime(); |
+#if ENABLE(ASSERT) |
+ const_cast<Animation*>(this)->updateCurrentTimingState(TimingUpdateOnDemand); |
+ ASSERT(result == (m_held ? m_holdTime : calculateCurrentTime())); |
+#endif |
+ return result; |
+} |
+ |
+double Animation::unlimitedCurrentTimeInternal() const |
+{ |
+#if ENABLE(ASSERT) |
+ currentTimeInternal(); |
+#endif |
+ return playStateInternal() == Paused || isNull(m_startTime) |
+ ? currentTimeInternal() |
+ : calculateCurrentTime(); |
+} |
+ |
+void Animation::preCommit(int compositorGroup, bool startOnCompositor) |
+{ |
+ PlayStateUpdateScope updateScope(*this, TimingUpdateOnDemand, DoNotSetCompositorPending); |
+ |
+ bool softChange = m_compositorState && (paused() || m_compositorState->playbackRate != m_playbackRate); |
+ bool hardChange = m_compositorState && (m_compositorState->sourceChanged || m_compositorState->startTime != m_startTime); |
+ |
+ // FIXME: softChange && !hardChange should generate a Pause/ThenStart, |
+ // not a Cancel, but we can't communicate these to the compositor yet. |
+ |
+ bool changed = softChange || hardChange; |
+ bool shouldCancel = (!playing() && m_compositorState) || changed; |
+ bool shouldStart = playing() && (!m_compositorState || changed); |
+ |
+ if (shouldCancel) { |
+ cancelAnimationOnCompositor(); |
+ m_compositorState = nullptr; |
+ } |
+ |
+ if (m_compositorState && m_compositorState->pendingAction == Start) { |
+ // Still waiting for a start time. |
return; |
+ } |
- // Cancel composited animation of transform if a motion path has been introduced on the element. |
- if (m_target->computedStyle() |
- && m_target->computedStyle()->hasMotionPath() |
- && player()->hasActiveAnimationsOnCompositor() |
- && player()->affects(*m_target, CSSPropertyTransform)) { |
- player()->cancelAnimationOnCompositor(); |
- } |
- |
- double iteration = currentIteration(); |
- ASSERT(iteration >= 0); |
- OwnPtrWillBeRawPtr<WillBeHeapVector<RefPtrWillBeMember<Interpolation>>> interpolations = m_sampledEffect ? m_sampledEffect->mutableInterpolations() : nullptr; |
- // FIXME: Handle iteration values which overflow int. |
- m_effect->sample(static_cast<int>(iteration), timeFraction(), iterationDuration(), interpolations); |
- if (m_sampledEffect) { |
- m_sampledEffect->setInterpolations(interpolations.release()); |
- } else if (interpolations && !interpolations->isEmpty()) { |
- OwnPtrWillBeRawPtr<SampledEffect> sampledEffect = SampledEffect::create(this, interpolations.release()); |
- m_sampledEffect = sampledEffect.get(); |
- ensureAnimationStack(m_target).add(sampledEffect.release()); |
- } else { |
+ ASSERT(!m_compositorState || !std::isnan(m_compositorState->startTime)); |
+ |
+ if (!shouldStart) { |
+ m_currentTimePending = false; |
+ } |
+ |
+ if (shouldStart) { |
+ m_compositorGroup = compositorGroup; |
+ if (startOnCompositor) { |
+ if (isCandidateForAnimationOnCompositor()) |
+ createCompositorPlayer(); |
+ |
+ if (maybeStartAnimationOnCompositor()) |
+ m_compositorState = adoptPtr(new CompositorState(*this)); |
+ else |
+ cancelIncompatibleAnimationsOnCompositor(); |
+ } |
+ } |
+} |
+ |
+void Animation::postCommit(double timelineTime) |
+{ |
+ PlayStateUpdateScope updateScope(*this, TimingUpdateOnDemand, DoNotSetCompositorPending); |
+ |
+ m_compositorPending = false; |
+ |
+ if (!m_compositorState || m_compositorState->pendingAction == None) |
return; |
+ |
+ switch (m_compositorState->pendingAction) { |
+ case Start: |
+ if (!std::isnan(m_compositorState->startTime)) { |
+ ASSERT(m_startTime == m_compositorState->startTime); |
+ m_compositorState->pendingAction = None; |
+ } |
+ break; |
+ case Pause: |
+ case PauseThenStart: |
+ ASSERT(std::isnan(m_startTime)); |
+ m_compositorState->pendingAction = None; |
+ setCurrentTimeInternal((timelineTime - m_compositorState->startTime) * m_playbackRate, TimingUpdateForAnimationFrame); |
+ m_currentTimePending = false; |
+ break; |
+ default: |
+ ASSERT_NOT_REACHED(); |
+ } |
+} |
+ |
+void Animation::notifyCompositorStartTime(double timelineTime) |
+{ |
+ PlayStateUpdateScope updateScope(*this, TimingUpdateOnDemand, DoNotSetCompositorPending); |
+ |
+ if (m_compositorState) { |
+ ASSERT(m_compositorState->pendingAction == Start); |
+ ASSERT(std::isnan(m_compositorState->startTime)); |
+ |
+ double initialCompositorHoldTime = m_compositorState->holdTime; |
+ m_compositorState->pendingAction = None; |
+ m_compositorState->startTime = timelineTime + currentTimeInternal() / -m_playbackRate; |
+ |
+ if (m_startTime == timelineTime) { |
+ // The start time was set to the incoming compositor start time. |
+ // Unlikely, but possible. |
+ // FIXME: Depending on what changed above this might still be pending. |
+ // Maybe... |
+ m_currentTimePending = false; |
+ return; |
+ } |
+ |
+ if (!std::isnan(m_startTime) || currentTimeInternal() != initialCompositorHoldTime) { |
+ // A new start time or current time was set while starting. |
+ setCompositorPending(true); |
+ return; |
+ } |
} |
- m_target->setNeedsAnimationStyleRecalc(); |
- if (m_target->isSVGElement()) |
- m_sampledEffect->applySVGUpdate(toSVGElement(*m_target)); |
+ notifyStartTime(timelineTime); |
+} |
+ |
+void Animation::notifyStartTime(double timelineTime) |
+{ |
+ if (playing()) { |
+ ASSERT(std::isnan(m_startTime)); |
+ ASSERT(m_held); |
+ |
+ if (m_playbackRate == 0) { |
+ setStartTimeInternal(timelineTime); |
+ } else { |
+ setStartTimeInternal(timelineTime + currentTimeInternal() / -m_playbackRate); |
+ } |
+ |
+ // FIXME: This avoids marking this animation as outdated needlessly when a start time |
+ // is notified, but we should refactor how outdating works to avoid this. |
+ m_outdated = false; |
+ |
+ m_currentTimePending = false; |
+ } |
} |
-void Animation::clearEffects() |
+bool Animation::affects(const Element& element, CSSPropertyID property) const |
{ |
- ASSERT(player()); |
- ASSERT(m_sampledEffect); |
+ if (!m_content || !m_content->isAnimation()) |
+ return false; |
- m_sampledEffect->clear(); |
- m_sampledEffect = nullptr; |
- restartAnimationOnCompositor(); |
- m_target->setNeedsAnimationStyleRecalc(); |
- invalidate(); |
+ const KeyframeEffect* effect = toKeyframeEffect(m_content.get()); |
+ return (effect->target() == &element) && effect->affects(PropertyHandle(property)); |
} |
-void Animation::updateChildrenAndEffects() const |
+double Animation::calculateStartTime(double currentTime) const |
{ |
- if (!m_effect) |
+ return m_timeline->effectiveTime() - currentTime / m_playbackRate; |
+} |
+ |
+double Animation::calculateCurrentTime() const |
+{ |
+ if (isNull(m_startTime) || !m_timeline) |
+ return 0; |
+ return (m_timeline->effectiveTime() - m_startTime) * m_playbackRate; |
+} |
+ |
+void Animation::setStartTime(double startTime) |
+{ |
+ PlayStateUpdateScope updateScope(*this, TimingUpdateOnDemand); |
+ |
+ if (m_paused || playStateInternal() == Idle) |
+ return; |
+ if (startTime == m_startTime) |
return; |
- if (isInEffect()) |
- const_cast<Animation*>(this)->applyEffects(); |
- else if (m_sampledEffect) |
- const_cast<Animation*>(this)->clearEffects(); |
+ |
+ m_currentTimePending = false; |
+ setStartTimeInternal(startTime / 1000); |
} |
-double Animation::calculateTimeToEffectChange(bool forwards, double localTime, double timeToNextIteration) const |
+void Animation::setStartTimeInternal(double newStartTime) |
{ |
- const double start = startTimeInternal() + specifiedTiming().startDelay; |
- const double end = start + activeDurationInternal(); |
+ ASSERT(!m_paused); |
+ ASSERT(std::isfinite(newStartTime)); |
+ ASSERT(newStartTime != m_startTime); |
- switch (phase()) { |
- case PhaseNone: |
- return std::numeric_limits<double>::infinity(); |
- case PhaseBefore: |
- ASSERT(start >= localTime); |
- return forwards |
- ? start - localTime |
- : std::numeric_limits<double>::infinity(); |
- case PhaseActive: |
- if (forwards) { |
- // Need service to apply fill / fire events. |
- const double timeToEnd = end - localTime; |
- if (requiresIterationEvents()) { |
- return std::min(timeToEnd, timeToNextIteration); |
- } |
- return timeToEnd; |
+ bool hadStartTime = hasStartTime(); |
+ double previousCurrentTime = currentTimeInternal(); |
+ m_startTime = newStartTime; |
+ if (m_held && m_playbackRate) { |
+ // If held, the start time would still be derrived from the hold time. |
+ // Force a new, limited, current time. |
+ m_held = false; |
+ double currentTime = calculateCurrentTime(); |
+ if (m_playbackRate > 0 && currentTime > sourceEnd()) { |
+ currentTime = sourceEnd(); |
+ } else if (m_playbackRate < 0 && currentTime < 0) { |
+ currentTime = 0; |
} |
- return 0; |
- case PhaseAfter: |
- ASSERT(localTime >= end); |
- // If this Animation is still in effect then it will need to update |
- // when its parent goes out of effect. We have no way of knowing when |
- // that will be, however, so the parent will need to supply it. |
- return forwards |
- ? std::numeric_limits<double>::infinity() |
- : localTime - end; |
+ setCurrentTimeInternal(currentTime, TimingUpdateOnDemand); |
+ } |
+ updateCurrentTimingState(TimingUpdateOnDemand); |
+ double newCurrentTime = currentTimeInternal(); |
+ |
+ if (previousCurrentTime != newCurrentTime) { |
+ setOutdated(); |
+ } else if (!hadStartTime && m_timeline) { |
+ // Even though this animation is not outdated, time to effect change is |
+ // infinity until start time is set. |
+ m_timeline->wake(); |
+ } |
+} |
+ |
+void Animation::setSource(AnimationEffect* newSource) |
+{ |
+ if (m_content == newSource) |
+ return; |
+ PlayStateUpdateScope updateScope(*this, TimingUpdateOnDemand, SetCompositorPendingWithSourceChanged); |
+ |
+ double storedCurrentTime = currentTimeInternal(); |
+ if (m_content) |
+ m_content->detach(); |
+ m_content = newSource; |
+ if (newSource) { |
+ // FIXME: This logic needs to be updated once groups are implemented |
+ if (newSource->animation()) { |
+ newSource->animation()->cancel(); |
+ newSource->animation()->setSource(0); |
+ } |
+ newSource->attach(this); |
+ setOutdated(); |
+ } |
+ setCurrentTimeInternal(storedCurrentTime, TimingUpdateOnDemand); |
+} |
+ |
+const char* Animation::playStateString(AnimationPlayState playState) |
+{ |
+ switch (playState) { |
+ case Idle: |
+ return "idle"; |
+ case Pending: |
+ return "pending"; |
+ case Running: |
+ return "running"; |
+ case Paused: |
+ return "paused"; |
+ case Finished: |
+ return "finished"; |
default: |
ASSERT_NOT_REACHED(); |
- return std::numeric_limits<double>::infinity(); |
+ return ""; |
} |
} |
-#if !ENABLE(OILPAN) |
-void Animation::notifyElementDestroyed() |
-{ |
- // If our player is kept alive just by the sampledEffect, we might get our |
- // destructor called when we call SampledEffect::clear(), so we need to |
- // clear m_sampledEffect first. |
- m_target = nullptr; |
- clearEventDelegate(); |
- SampledEffect* sampledEffect = m_sampledEffect; |
- m_sampledEffect = nullptr; |
- if (sampledEffect) |
- sampledEffect->clear(); |
+Animation::AnimationPlayState Animation::playStateInternal() const |
+{ |
+ return m_playState; |
} |
-#endif |
-bool Animation::isCandidateForAnimationOnCompositor(double playerPlaybackRate) const |
+Animation::AnimationPlayState Animation::calculatePlayState() |
{ |
- if (!effect() |
- || !m_target |
- || (m_target->computedStyle() && m_target->computedStyle()->hasMotionPath())) |
- return false; |
+ if (m_playState == Idle) |
+ return Idle; |
+ if (m_currentTimePending || (isNull(m_startTime) && !m_paused && m_playbackRate != 0)) |
+ return Pending; |
+ if (m_paused) |
+ return Paused; |
+ if (limited()) |
+ return Finished; |
+ return Running; |
+} |
+ |
+void Animation::pause() |
+{ |
+ if (m_paused) |
+ return; |
+ |
+ PlayStateUpdateScope updateScope(*this, TimingUpdateOnDemand); |
- return CompositorAnimations::instance()->isCandidateForAnimationOnCompositor(specifiedTiming(), *m_target, player(), *effect(), playerPlaybackRate); |
+ if (playing()) { |
+ m_currentTimePending = true; |
+ } |
+ m_paused = true; |
+ setCurrentTimeInternal(currentTimeInternal(), TimingUpdateOnDemand); |
} |
-bool Animation::maybeStartAnimationOnCompositor(int group, double startTime, double currentTime, double playerPlaybackRate) |
+void Animation::unpause() |
{ |
- ASSERT(!hasActiveAnimationsOnCompositor()); |
- if (!isCandidateForAnimationOnCompositor(playerPlaybackRate)) |
- return false; |
- if (!CompositorAnimations::instance()->canStartAnimationOnCompositor(*m_target)) |
- return false; |
- if (!CompositorAnimations::instance()->startAnimationOnCompositor(*m_target, group, startTime, currentTime, specifiedTiming(), *player(), *effect(), m_compositorAnimationIds, playerPlaybackRate)) |
- return false; |
- ASSERT(!m_compositorAnimationIds.isEmpty()); |
- return true; |
+ if (!m_paused) |
+ return; |
+ |
+ PlayStateUpdateScope updateScope(*this, TimingUpdateOnDemand); |
+ |
+ m_currentTimePending = true; |
+ unpauseInternal(); |
+} |
+ |
+void Animation::unpauseInternal() |
+{ |
+ if (!m_paused) |
+ return; |
+ m_paused = false; |
+ setCurrentTimeInternal(currentTimeInternal(), TimingUpdateOnDemand); |
+} |
+ |
+void Animation::play() |
+{ |
+ PlayStateUpdateScope updateScope(*this, TimingUpdateOnDemand); |
+ |
+ if (!playing()) |
+ m_startTime = nullValue(); |
+ |
+ if (playStateInternal() == Idle) { |
+ // We may not go into the pending state, but setting it to something other |
+ // than Idle here will force an update. |
+ ASSERT(isNull(m_startTime)); |
+ m_playState = Pending; |
+ m_held = true; |
+ m_holdTime = 0; |
+ } |
+ |
+ m_finished = false; |
+ unpauseInternal(); |
+ if (!m_content) |
+ return; |
+ double currentTime = this->currentTimeInternal(); |
+ if (m_playbackRate > 0 && (currentTime < 0 || currentTime >= sourceEnd())) { |
+ m_startTime = nullValue(); |
+ setCurrentTimeInternal(0, TimingUpdateOnDemand); |
+ } else if (m_playbackRate < 0 && (currentTime <= 0 || currentTime > sourceEnd())) { |
+ m_startTime = nullValue(); |
+ setCurrentTimeInternal(sourceEnd(), TimingUpdateOnDemand); |
+ } |
+} |
+ |
+void Animation::reverse() |
+{ |
+ if (!m_playbackRate) { |
+ return; |
+ } |
+ |
+ setPlaybackRateInternal(-m_playbackRate); |
+ play(); |
} |
-bool Animation::hasActiveAnimationsOnCompositor() const |
+void Animation::finish(ExceptionState& exceptionState) |
{ |
- return !m_compositorAnimationIds.isEmpty(); |
+ PlayStateUpdateScope updateScope(*this, TimingUpdateOnDemand); |
+ |
+ if (!m_playbackRate || playStateInternal() == Idle) { |
+ return; |
+ } |
+ if (m_playbackRate > 0 && sourceEnd() == std::numeric_limits<double>::infinity()) { |
+ exceptionState.throwDOMException(InvalidStateError, "Animation has source content whose end time is infinity."); |
+ return; |
+ } |
+ |
+ double newCurrentTime = m_playbackRate < 0 ? 0 : sourceEnd(); |
+ setCurrentTimeInternal(newCurrentTime, TimingUpdateOnDemand); |
+ if (!paused()) { |
+ m_startTime = calculateStartTime(newCurrentTime); |
+ } |
+ |
+ m_currentTimePending = false; |
+ ASSERT(playStateInternal() != Idle); |
+ ASSERT(limited()); |
+} |
+ |
+ScriptPromise Animation::finished(ScriptState* scriptState) |
+{ |
+ if (!m_finishedPromise) { |
+ m_finishedPromise = new AnimationPromise(scriptState->executionContext(), this, AnimationPromise::Finished); |
+ if (playStateInternal() == Finished) |
+ m_finishedPromise->resolve(this); |
+ } |
+ return m_finishedPromise->promise(scriptState->world()); |
+} |
+ |
+ScriptPromise Animation::ready(ScriptState* scriptState) |
+{ |
+ if (!m_readyPromise) { |
+ m_readyPromise = new AnimationPromise(scriptState->executionContext(), this, AnimationPromise::Ready); |
+ if (playStateInternal() != Pending) |
+ m_readyPromise->resolve(this); |
+ } |
+ return m_readyPromise->promise(scriptState->world()); |
+} |
+ |
+const AtomicString& Animation::interfaceName() const |
+{ |
+ return EventTargetNames::AnimationPlayer; |
+} |
+ |
+ExecutionContext* Animation::executionContext() const |
+{ |
+ return ActiveDOMObject::executionContext(); |
+} |
+ |
+bool Animation::hasPendingActivity() const |
+{ |
+ return m_pendingFinishedEvent || (!m_finished && hasEventListeners(EventTypeNames::finish)); |
+} |
+ |
+void Animation::stop() |
+{ |
+ PlayStateUpdateScope updateScope(*this, TimingUpdateOnDemand); |
+ |
+ m_finished = true; |
+ m_pendingFinishedEvent = nullptr; |
+} |
+ |
+bool Animation::dispatchEvent(PassRefPtrWillBeRawPtr<Event> event) |
+{ |
+ if (m_pendingFinishedEvent == event) |
+ m_pendingFinishedEvent = nullptr; |
+ return EventTargetWithInlineData::dispatchEvent(event); |
+} |
+ |
+double Animation::playbackRate() const |
+{ |
+ return m_playbackRate; |
+} |
+ |
+void Animation::setPlaybackRate(double playbackRate) |
+{ |
+ if (playbackRate == m_playbackRate) |
+ return; |
+ |
+ PlayStateUpdateScope updateScope(*this, TimingUpdateOnDemand); |
+ |
+ setPlaybackRateInternal(playbackRate); |
+} |
+ |
+void Animation::setPlaybackRateInternal(double playbackRate) |
+{ |
+ ASSERT(std::isfinite(playbackRate)); |
+ ASSERT(playbackRate != m_playbackRate); |
+ |
+ if (!limited() && !paused() && hasStartTime()) |
+ m_currentTimePending = true; |
+ |
+ double storedCurrentTime = currentTimeInternal(); |
+ if ((m_playbackRate < 0 && playbackRate >= 0) || (m_playbackRate > 0 && playbackRate <= 0)) |
+ m_finished = false; |
+ |
+ m_playbackRate = playbackRate; |
+ m_startTime = std::numeric_limits<double>::quiet_NaN(); |
+ setCurrentTimeInternal(storedCurrentTime, TimingUpdateOnDemand); |
} |
-bool Animation::hasActiveAnimationsOnCompositor(CSSPropertyID property) const |
+void Animation::setOutdated() |
{ |
- return hasActiveAnimationsOnCompositor() && affects(PropertyHandle(property)); |
+ m_outdated = true; |
+ if (m_timeline) |
+ m_timeline->setOutdatedAnimation(this); |
} |
-bool Animation::affects(PropertyHandle property) const |
+bool Animation::canStartAnimationOnCompositor() const |
{ |
- return m_effect && m_effect->affects(property); |
+ // FIXME: Timeline playback rates should be compositable |
+ if (m_playbackRate == 0 || (std::isinf(sourceEnd()) && m_playbackRate < 0) || (timeline() && timeline()->playbackRate() != 1)) |
+ return false; |
+ |
+ return m_timeline && m_content && m_content->isAnimation() && playing(); |
} |
-bool Animation::cancelAnimationOnCompositor() |
+bool Animation::isCandidateForAnimationOnCompositor() const |
{ |
- // FIXME: cancelAnimationOnCompositor is called from withins style recalc. |
- // This queries compositingState, which is not necessarily up to date. |
- // https://code.google.com/p/chromium/issues/detail?id=339847 |
- DisableCompositingQueryAsserts disabler; |
- if (!hasActiveAnimationsOnCompositor()) |
+ if (!canStartAnimationOnCompositor()) |
return false; |
- if (!m_target || !m_target->layoutObject()) |
+ |
+ return toKeyframeEffect(m_content.get())->isCandidateForAnimationOnCompositor(m_playbackRate); |
+} |
+ |
+bool Animation::maybeStartAnimationOnCompositor() |
+{ |
+ if (!canStartAnimationOnCompositor()) |
return false; |
- ASSERT(player()); |
- for (const auto& compositorAnimationId : m_compositorAnimationIds) |
- CompositorAnimations::instance()->cancelAnimationOnCompositor(*m_target, *player(), compositorAnimationId); |
- m_compositorAnimationIds.clear(); |
- return true; |
+ |
+ bool reversed = m_playbackRate < 0; |
+ |
+ double startTime = timeline()->zeroTime() + startTimeInternal(); |
+ if (reversed) { |
+ startTime -= sourceEnd() / fabs(m_playbackRate); |
+ } |
+ |
+ double timeOffset = 0; |
+ if (std::isnan(startTime)) { |
+ timeOffset = reversed ? sourceEnd() - currentTimeInternal() : currentTimeInternal(); |
+ timeOffset = timeOffset / fabs(m_playbackRate); |
+ } |
+ ASSERT(m_compositorGroup != 0); |
+ return toKeyframeEffect(m_content.get())->maybeStartAnimationOnCompositor(m_compositorGroup, startTime, timeOffset, m_playbackRate); |
+} |
+ |
+void Animation::setCompositorPending(bool sourceChanged) |
+{ |
+ // FIXME: KeyframeEffect could notify this directly? |
+ if (!hasActiveAnimationsOnCompositor()) { |
+ destroyCompositorPlayer(); |
+ m_compositorState.release(); |
+ } |
+ if (sourceChanged && m_compositorState) { |
+ m_compositorState->sourceChanged = true; |
+ } |
+ if (m_compositorPending || m_isPausedForTesting) { |
+ return; |
+ } |
+ |
+ if (sourceChanged || !m_compositorState |
+ || !playing() || m_compositorState->playbackRate != m_playbackRate |
+ || m_compositorState->startTime != m_startTime) { |
+ m_compositorPending = true; |
+ ASSERT(timeline()); |
+ ASSERT(timeline()->document()); |
+ timeline()->document()->compositorPendingAnimations().add(this); |
+ } |
+} |
+ |
+void Animation::cancelAnimationOnCompositor() |
+{ |
+ if (hasActiveAnimationsOnCompositor()) |
+ toKeyframeEffect(m_content.get())->cancelAnimationOnCompositor(); |
+ |
+ destroyCompositorPlayer(); |
} |
void Animation::restartAnimationOnCompositor() |
{ |
- if (cancelAnimationOnCompositor()) |
- player()->setCompositorPending(true); |
+ if (hasActiveAnimationsOnCompositor()) |
+ toKeyframeEffect(m_content.get())->restartAnimationOnCompositor(); |
} |
void Animation::cancelIncompatibleAnimationsOnCompositor() |
{ |
- if (m_target && player() && effect()) |
- CompositorAnimations::instance()->cancelIncompatibleAnimationsOnCompositor(*m_target, *player(), *effect()); |
+ if (m_content && m_content->isAnimation()) |
+ toKeyframeEffect(m_content.get())->cancelIncompatibleAnimationsOnCompositor(); |
} |
-void Animation::pauseAnimationForTestingOnCompositor(double pauseTime) |
+bool Animation::hasActiveAnimationsOnCompositor() |
{ |
- ASSERT(hasActiveAnimationsOnCompositor()); |
- if (!m_target || !m_target->layoutObject()) |
- return; |
- ASSERT(player()); |
- for (const auto& compositorAnimationId : m_compositorAnimationIds) |
- CompositorAnimations::instance()->pauseAnimationForTestingOnCompositor(*m_target, *player(), compositorAnimationId, pauseTime); |
+ if (!m_content || !m_content->isAnimation()) |
+ return false; |
+ |
+ return toKeyframeEffect(m_content.get())->hasActiveAnimationsOnCompositor(); |
} |
-bool Animation::canAttachCompositedLayers() const |
+bool Animation::update(TimingUpdateReason reason) |
{ |
- if (!m_target || !player()) |
+ if (!m_timeline) |
return false; |
- return CompositorAnimations::instance()->canAttachCompositedLayers(*m_target, *player()); |
+ PlayStateUpdateScope updateScope(*this, reason, DoNotSetCompositorPending); |
+ |
+ m_outdated = false; |
+ bool idle = playStateInternal() == Idle; |
+ |
+ if (m_content) { |
+ double inheritedTime = idle || isNull(m_timeline->currentTimeInternal()) ? nullValue() : currentTimeInternal(); |
+ // Special case for end-exclusivity when playing backwards. |
+ if (inheritedTime == 0 && m_playbackRate < 0) |
+ inheritedTime = -1; |
+ m_content->updateInheritedTime(inheritedTime, reason); |
+ } |
+ |
+ if ((idle || limited()) && !m_finished) { |
+ if (reason == TimingUpdateForAnimationFrame && (idle || hasStartTime())) { |
+ const AtomicString& eventType = EventTypeNames::finish; |
+ if (executionContext() && hasEventListeners(eventType)) { |
+ double eventCurrentTime = currentTimeInternal() * 1000; |
+ m_pendingFinishedEvent = AnimationPlayerEvent::create(eventType, eventCurrentTime, timeline()->currentTime()); |
+ m_pendingFinishedEvent->setTarget(this); |
+ m_pendingFinishedEvent->setCurrentTarget(this); |
+ m_timeline->document()->enqueueAnimationFrameEvent(m_pendingFinishedEvent); |
+ } |
+ m_finished = true; |
+ } |
+ } |
+ ASSERT(!m_outdated); |
+ return !m_finished; |
+} |
+ |
+double Animation::timeToEffectChange() |
+{ |
+ ASSERT(!m_outdated); |
+ if (m_held || !hasStartTime()) |
+ return std::numeric_limits<double>::infinity(); |
+ if (!m_content) |
+ return -currentTimeInternal() / m_playbackRate; |
+ double result = m_playbackRate > 0 |
+ ? m_content->timeToForwardsEffectChange() / m_playbackRate |
+ : m_content->timeToReverseEffectChange() / -m_playbackRate; |
+ return !hasActiveAnimationsOnCompositor() && m_content->phase() == AnimationEffect::PhaseActive |
+ ? 0 |
+ : result; |
+} |
+ |
+void Animation::cancel() |
+{ |
+ PlayStateUpdateScope updateScope(*this, TimingUpdateOnDemand); |
+ |
+ if (playStateInternal() == Idle) |
+ return; |
+ |
+ m_holdTime = currentTimeInternal(); |
+ m_held = true; |
+ // TODO |
+ m_playState = Idle; |
+ m_startTime = nullValue(); |
+ m_currentTimePending = false; |
+ |
+ InspectorInstrumentation::didCancelAnimation(timeline()->document(), this); |
+} |
+ |
+void Animation::beginUpdatingState() |
+{ |
+ // Nested calls are not allowed! |
+ ASSERT(!m_stateIsBeingUpdated); |
+ m_stateIsBeingUpdated = true; |
+} |
+ |
+void Animation::endUpdatingState() |
+{ |
+ ASSERT(m_stateIsBeingUpdated); |
+ m_stateIsBeingUpdated = false; |
+} |
+ |
+void Animation::createCompositorPlayer() |
+{ |
+ if (RuntimeEnabledFeatures::compositorAnimationTimelinesEnabled() && !m_compositorPlayer && Platform::current()->compositorSupport()) { |
+ m_compositorPlayer = adoptPtr(Platform::current()->compositorSupport()->createAnimationPlayer()); |
+ ASSERT(m_compositorPlayer); |
+ m_compositorPlayer->setAnimationDelegate(this); |
+ attachCompositorTimeline(); |
+ } |
+ |
+ attachCompositedLayers(); |
+} |
+ |
+void Animation::destroyCompositorPlayer() |
+{ |
+ detachCompositedLayers(); |
+ |
+ if (m_compositorPlayer) { |
+ detachCompositorTimeline(); |
+ m_compositorPlayer->setAnimationDelegate(nullptr); |
+ } |
+ m_compositorPlayer.clear(); |
+} |
+ |
+void Animation::attachCompositorTimeline() |
+{ |
+ if (m_compositorPlayer) { |
+ WebCompositorAnimationTimeline* timeline = m_timeline ? m_timeline->compositorTimeline() : nullptr; |
+ if (timeline) |
+ timeline->playerAttached(*this); |
+ } |
+} |
+ |
+void Animation::detachCompositorTimeline() |
+{ |
+ if (m_compositorPlayer) { |
+ WebCompositorAnimationTimeline* timeline = m_timeline ? m_timeline->compositorTimeline() : nullptr; |
+ if (timeline) |
+ timeline->playerDestroyed(*this); |
+ } |
} |
void Animation::attachCompositedLayers() |
{ |
- ASSERT(m_target); |
- ASSERT(player()); |
- CompositorAnimations::instance()->attachCompositedLayers(*m_target, *player()); |
+ if (!RuntimeEnabledFeatures::compositorAnimationTimelinesEnabled() || !m_compositorPlayer) |
+ return; |
+ |
+ ASSERT(m_content); |
+ ASSERT(m_content->isAnimation()); |
+ |
+ if (toKeyframeEffect(m_content.get())->canAttachCompositedLayers()) |
+ toKeyframeEffect(m_content.get())->attachCompositedLayers(); |
+} |
+ |
+void Animation::detachCompositedLayers() |
+{ |
+ if (m_compositorPlayer && m_compositorPlayer->isLayerAttached()) |
+ m_compositorPlayer->detachLayer(); |
+} |
+ |
+void Animation::notifyAnimationStarted(double monotonicTime, int group) |
+{ |
+ ASSERT(RuntimeEnabledFeatures::compositorAnimationTimelinesEnabled()); |
+ timeline()->document()->compositorPendingAnimations().notifyCompositorAnimationStarted(monotonicTime, group); |
+} |
+ |
+Animation::PlayStateUpdateScope::PlayStateUpdateScope(Animation& animation, TimingUpdateReason reason, CompositorPendingChange compositorPendingChange) |
+ : m_animation(animation) |
+ , m_initialPlayState(m_animation->playStateInternal()) |
+ , m_compositorPendingChange(compositorPendingChange) |
+{ |
+ m_animation->beginUpdatingState(); |
+ m_animation->updateCurrentTimingState(reason); |
+} |
+ |
+Animation::PlayStateUpdateScope::~PlayStateUpdateScope() |
+{ |
+ AnimationPlayState oldPlayState = m_initialPlayState; |
+ AnimationPlayState newPlayState = m_animation->calculatePlayState(); |
+ |
+ m_animation->m_playState = newPlayState; |
+ if (oldPlayState != newPlayState) { |
+ bool wasActive = oldPlayState == Pending || oldPlayState == Running; |
+ bool isActive = newPlayState == Pending || newPlayState == Running; |
+ if (!wasActive && isActive) |
+ TRACE_EVENT_NESTABLE_ASYNC_BEGIN1("blink.animations," TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), "KeyframeEffect", m_animation, "data", InspectorAnimationEvent::data(*m_animation)); |
+ else if (wasActive && !isActive) |
+ TRACE_EVENT_NESTABLE_ASYNC_END1("blink.animations," TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), "KeyframeEffect", m_animation, "endData", InspectorAnimationStateEvent::data(*m_animation)); |
+ else |
+ TRACE_EVENT_NESTABLE_ASYNC_INSTANT1("blink.animations," TRACE_DISABLED_BY_DEFAULT("devtools.timeline"), "KeyframeEffect", m_animation, "data", InspectorAnimationStateEvent::data(*m_animation)); |
+ } |
+ |
+ // Ordering is important, the ready promise should resolve/reject before |
+ // the finished promise. |
+ if (m_animation->m_readyPromise && newPlayState != oldPlayState) { |
+ if (newPlayState == Idle) { |
+ if (m_animation->m_readyPromise->state() == AnimationPromise::Pending) { |
+ m_animation->m_readyPromise->reject(DOMException::create(AbortError)); |
+ } |
+ m_animation->m_readyPromise->reset(); |
+ m_animation->m_readyPromise->resolve(m_animation); |
+ } else if (oldPlayState == Pending) { |
+ m_animation->m_readyPromise->resolve(m_animation); |
+ } else if (newPlayState == Pending) { |
+ ASSERT(m_animation->m_readyPromise->state() != AnimationPromise::Pending); |
+ m_animation->m_readyPromise->reset(); |
+ } |
+ } |
+ |
+ if (m_animation->m_finishedPromise && newPlayState != oldPlayState) { |
+ if (newPlayState == Idle) { |
+ if (m_animation->m_finishedPromise->state() == AnimationPromise::Pending) { |
+ m_animation->m_finishedPromise->reject(DOMException::create(AbortError)); |
+ } |
+ m_animation->m_finishedPromise->reset(); |
+ } else if (newPlayState == Finished) { |
+ m_animation->m_finishedPromise->resolve(m_animation); |
+ } else if (oldPlayState == Finished) { |
+ m_animation->m_finishedPromise->reset(); |
+ } |
+ } |
+ |
+ if (oldPlayState != newPlayState && (oldPlayState == Idle || newPlayState == Idle)) { |
+ m_animation->setOutdated(); |
+ } |
+ |
+#if ENABLE(ASSERT) |
+ // Verify that current time is up to date. |
+ m_animation->currentTimeInternal(); |
+#endif |
+ |
+ switch (m_compositorPendingChange) { |
+ case SetCompositorPending: |
+ m_animation->setCompositorPending(); |
+ break; |
+ case SetCompositorPendingWithSourceChanged: |
+ m_animation->setCompositorPending(true); |
+ break; |
+ case DoNotSetCompositorPending: |
+ break; |
+ default: |
+ ASSERT_NOT_REACHED(); |
+ break; |
+ } |
+ m_animation->endUpdatingState(); |
+ |
+ if (oldPlayState != newPlayState && newPlayState == Running) |
+ InspectorInstrumentation::didCreateAnimation(m_animation->timeline()->document(), m_animation); |
+} |
+ |
+bool Animation::addEventListener(const AtomicString& eventType, PassRefPtr<EventListener> listener, bool useCapture) |
+{ |
+ if (eventType == EventTypeNames::finish) |
+ UseCounter::count(executionContext(), UseCounter::AnimationFinishEvent); |
+ return EventTargetWithInlineData::addEventListener(eventType, listener, useCapture); |
+} |
+ |
+void Animation::pauseForTesting(double pauseTime) |
+{ |
+ RELEASE_ASSERT(!paused()); |
+ setCurrentTimeInternal(pauseTime, TimingUpdateOnDemand); |
+ if (hasActiveAnimationsOnCompositor()) |
+ toKeyframeEffect(m_content.get())->pauseAnimationForTestingOnCompositor(currentTimeInternal()); |
+ m_isPausedForTesting = true; |
+ pause(); |
} |
DEFINE_TRACE(Animation) |
{ |
- visitor->trace(m_target); |
- visitor->trace(m_effect); |
- visitor->trace(m_sampledEffect); |
- AnimationNode::trace(visitor); |
+ visitor->trace(m_content); |
+ visitor->trace(m_timeline); |
+ visitor->trace(m_pendingFinishedEvent); |
+ visitor->trace(m_finishedPromise); |
+ visitor->trace(m_readyPromise); |
+ EventTargetWithInlineData::trace(visitor); |
+ ActiveDOMObject::trace(visitor); |
} |
-} // namespace blink |
+} // namespace |