| 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
|
|
|