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

Unified Diff: Source/core/animation/Animation.cpp

Issue 1113173003: Web Animations: Update naming to reflect spec changes (Closed) Base URL: svn://svn.chromium.org/blink/trunk
Patch Set: No, really. Created 5 years, 7 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « Source/core/animation/Animation.h ('k') | Source/core/animation/Animation.idl » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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
« no previous file with comments | « Source/core/animation/Animation.h ('k') | Source/core/animation/Animation.idl » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698