blob: 0e3f917380346ec77bd233305db755d29cf46bce [file] [log] [blame]
/*
* Copyright (C) 2013 Google Inc. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions are
* met:
*
* * Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the following disclaimer
* in the documentation and/or other materials provided with the
* distribution.
* * Neither the name of Google Inc. nor the names of its
* contributors may be used to endorse or promote products derived from
* this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "config.h"
#include "core/animation/AnimationPlayer.h"
#include "core/animation/Animation.h"
#include "core/animation/AnimationTimeline.h"
#include "core/dom/Document.h"
#include "core/events/AnimationPlayerEvent.h"
#include "core/frame/UseCounter.h"
namespace blink {
namespace {
static unsigned nextSequenceNumber()
{
static unsigned next = 0;
return ++next;
}
}
PassRefPtrWillBeRawPtr<AnimationPlayer> AnimationPlayer::create(ExecutionContext* executionContext, AnimationTimeline& timeline, AnimationNode* content)
{
RefPtrWillBeRawPtr<AnimationPlayer> player = adoptRefWillBeNoop(new AnimationPlayer(executionContext, timeline, content));
timeline.document()->compositorPendingAnimations().add(player.get());
player->suspendIfNeeded();
return player.release();
}
AnimationPlayer::AnimationPlayer(ExecutionContext* executionContext, AnimationTimeline& timeline, AnimationNode* content)
: ActiveDOMObject(executionContext)
, 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(true)
, m_finished(false)
, m_compositorState(nullptr)
, m_compositorPending(true)
, m_currentTimePending(false)
{
ScriptWrappable::init(this);
if (m_content) {
if (m_content->player())
m_content->player()->cancel();
m_content->attach(this);
}
}
AnimationPlayer::~AnimationPlayer()
{
#if !ENABLE(OILPAN)
if (m_content)
m_content->detach();
if (m_timeline)
m_timeline->playerDestroyed(this);
#endif
}
double AnimationPlayer::sourceEnd() const
{
return m_content ? m_content->endTimeInternal() : 0;
}
bool AnimationPlayer::limited(double currentTime) const
{
return (m_playbackRate < 0 && currentTime <= 0) || (m_playbackRate > 0 && currentTime >= sourceEnd());
}
void AnimationPlayer::setCurrentTimeInternal(double newCurrentTime, TimingUpdateReason reason)
{
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 AnimationPlayer::updateCurrentTimingState(TimingUpdateReason reason)
{
if (m_held) {
setCurrentTimeInternal(m_holdTime, reason);
return;
}
if (!limited(calculateCurrentTime()))
return;
m_held = true;
m_holdTime = m_playbackRate < 0 ? 0 : sourceEnd();
}
double AnimationPlayer::startTime() const
{
return m_startTime * 1000;
}
double AnimationPlayer::currentTime()
{
if (m_currentTimePending)
return std::numeric_limits<double>::quiet_NaN();
return currentTimeInternal() * 1000;
}
double AnimationPlayer::currentTimeInternal()
{
updateCurrentTimingState(TimingUpdateOnDemand);
if (m_held)
return m_holdTime;
return calculateCurrentTime();
}
void AnimationPlayer::preCommit(bool startOnCompositor)
{
if (m_compositorState && m_compositorState->pendingAction == Start) {
// Still waiting for a start time.
return;
}
bool softChange = m_compositorState && (paused() || m_compositorState->playbackRate != m_playbackRate);
bool hardChange = m_compositorState && (m_compositorState->sourceChanged || (m_compositorState->startTime != m_startTime && !std::isnan(m_compositorState->startTime) && !std::isnan(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 (!shouldStart) {
m_currentTimePending = false;
}
if (shouldStart && startOnCompositor && maybeStartAnimationOnCompositor()) {
m_compositorState = adoptPtr(new CompositorState(*this));
}
}
void AnimationPlayer::postCommit(double timelineTime)
{
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 AnimationPlayer::notifyCompositorStartTime(double timelineTime)
{
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;
if (paused() || m_compositorState->playbackRate != m_playbackRate || m_compositorState->sourceChanged) {
// Paused state, playback rate, or source changed while starting.
setCompositorPending();
}
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();
return;
}
}
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 player 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;
}
}
double AnimationPlayer::calculateStartTime(double currentTime) const
{
return m_timeline->effectiveTime() - currentTime / m_playbackRate;
}
double AnimationPlayer::calculateCurrentTime() const
{
ASSERT(!m_held);
if (isNull(m_startTime) || !m_timeline)
return 0;
return (m_timeline->effectiveTime() - m_startTime) * m_playbackRate;
}
void AnimationPlayer::setCurrentTime(double newCurrentTime)
{
if (!std::isfinite(newCurrentTime))
return;
setCompositorPending();
// Setting current time while pending forces a start time.
if (m_currentTimePending) {
m_startTime = 0;
m_currentTimePending = false;
}
setCurrentTimeInternal(newCurrentTime / 1000, TimingUpdateOnDemand);
}
void AnimationPlayer::setStartTime(double startTime)
{
if (m_paused) // FIXME: Should this throw an exception?
return;
if (!std::isfinite(startTime))
return;
if (startTime == m_startTime)
return;
setCompositorPending();
m_currentTimePending = false;
setStartTimeInternal(startTime / 1000);
}
void AnimationPlayer::setStartTimeInternal(double newStartTime)
{
ASSERT(!m_paused);
ASSERT(std::isfinite(newStartTime));
ASSERT(newStartTime != m_startTime);
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;
}
setCurrentTimeInternal(currentTime, TimingUpdateOnDemand);
}
double newCurrentTime = currentTimeInternal();
if (previousCurrentTime != newCurrentTime) {
setOutdated();
} else if (!hadStartTime && m_timeline) {
// Even though this player is not outdated, time to effect change is
// infinity until start time is set.
m_timeline->wake();
}
}
void AnimationPlayer::setSource(AnimationNode* newSource)
{
if (m_content == newSource)
return;
setCompositorPending(true);
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->player())
newSource->player()->cancel();
newSource->attach(this);
setOutdated();
}
setCurrentTimeInternal(storedCurrentTime, TimingUpdateOnDemand);
}
String AnimationPlayer::playState()
{
switch (playStateInternal()) {
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 "";
}
}
AnimationPlayer::AnimationPlayState AnimationPlayer::playStateInternal()
{
// FIXME(shanestephens): Add clause for in-idle-state here.
if (m_currentTimePending || (isNull(m_startTime) && !m_paused && m_playbackRate != 0))
return Pending;
// FIXME(shanestephens): Add idle handling here.
if (m_paused)
return Paused;
if (finished())
return Finished;
return Running;
}
void AnimationPlayer::pause()
{
if (m_paused)
return;
if (playing()) {
setCompositorPending();
m_currentTimePending = true;
}
m_paused = true;
setCurrentTimeInternal(currentTimeInternal(), TimingUpdateOnDemand);
}
void AnimationPlayer::unpause()
{
if (!m_paused)
return;
setCompositorPending();
m_currentTimePending = true;
unpauseInternal();
}
void AnimationPlayer::unpauseInternal()
{
if (!m_paused)
return;
m_paused = false;
setCurrentTimeInternal(currentTimeInternal(), TimingUpdateOnDemand);
}
void AnimationPlayer::play()
{
if (!playing())
m_startTime = nullValue();
setCompositorPending();
unpauseInternal();
if (!m_content)
return;
double currentTime = this->currentTimeInternal();
if (m_playbackRate > 0 && (currentTime < 0 || currentTime >= sourceEnd()))
setCurrentTimeInternal(0, TimingUpdateOnDemand);
else if (m_playbackRate < 0 && (currentTime <= 0 || currentTime > sourceEnd()))
setCurrentTimeInternal(sourceEnd(), TimingUpdateOnDemand);
m_finished = false;
}
void AnimationPlayer::reverse()
{
if (!m_playbackRate) {
return;
}
if (m_content) {
if (m_playbackRate > 0 && currentTimeInternal() > sourceEnd()) {
setCurrentTimeInternal(sourceEnd(), TimingUpdateOnDemand);
ASSERT(finished());
} else if (m_playbackRate < 0 && currentTimeInternal() < 0) {
setCurrentTimeInternal(0, TimingUpdateOnDemand);
ASSERT(finished());
}
}
setPlaybackRate(-m_playbackRate);
unpauseInternal();
}
void AnimationPlayer::finish(ExceptionState& exceptionState)
{
if (!m_playbackRate) {
return;
}
if (m_playbackRate > 0 && sourceEnd() == std::numeric_limits<double>::infinity()) {
exceptionState.throwDOMException(InvalidStateError, "AnimationPlayer has source content whose end time is infinity.");
return;
}
if (playing()) {
setCompositorPending();
}
if (m_playbackRate < 0) {
setCurrentTimeInternal(0, TimingUpdateOnDemand);
} else {
setCurrentTimeInternal(sourceEnd(), TimingUpdateOnDemand);
}
ASSERT(finished());
}
const AtomicString& AnimationPlayer::interfaceName() const
{
return EventTargetNames::AnimationPlayer;
}
ExecutionContext* AnimationPlayer::executionContext() const
{
return ActiveDOMObject::executionContext();
}
bool AnimationPlayer::hasPendingActivity() const
{
return m_pendingFinishedEvent || (!m_finished && hasEventListeners(EventTypeNames::finish));
}
void AnimationPlayer::stop()
{
m_finished = true;
m_pendingFinishedEvent = nullptr;
}
bool AnimationPlayer::dispatchEvent(PassRefPtrWillBeRawPtr<Event> event)
{
if (m_pendingFinishedEvent == event)
m_pendingFinishedEvent = nullptr;
return EventTargetWithInlineData::dispatchEvent(event);
}
void AnimationPlayer::setPlaybackRate(double playbackRate)
{
if (!std::isfinite(playbackRate))
return;
if (playbackRate == m_playbackRate)
return;
setCompositorPending();
if (!finished() && !paused())
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);
}
void AnimationPlayer::setOutdated()
{
m_outdated = true;
if (m_timeline)
m_timeline->setOutdatedAnimationPlayer(this);
}
bool AnimationPlayer::canStartAnimationOnCompositor()
{
// FIXME: Need compositor support for playback rate != 1.
if (playbackRate() != 1)
return false;
return m_timeline && m_content && m_content->isAnimation() && playing();
}
bool AnimationPlayer::maybeStartAnimationOnCompositor()
{
if (!canStartAnimationOnCompositor())
return false;
double startTime = timeline()->zeroTime() + startTimeInternal();
double timeOffset = 0;
if (std::isnan(startTime)) {
timeOffset = currentTimeInternal();
}
return toAnimation(m_content.get())->maybeStartAnimationOnCompositor(startTime, timeOffset);
}
void AnimationPlayer::setCompositorPending(bool sourceChanged)
{
// FIXME: Animation could notify this directly?
if (!hasActiveAnimationsOnCompositor()) {
m_compositorState.release();
}
if (!m_compositorPending) {
m_compositorPending = true;
if (sourceChanged && m_compositorState)
m_compositorState->sourceChanged = true;
timeline()->document()->compositorPendingAnimations().add(this);
}
}
bool AnimationPlayer::hasActiveAnimationsOnCompositor()
{
if (!m_content || !m_content->isAnimation())
return false;
return toAnimation(m_content.get())->hasActiveAnimationsOnCompositor();
}
void AnimationPlayer::cancelAnimationOnCompositor()
{
if (hasActiveAnimationsOnCompositor())
toAnimation(m_content.get())->cancelAnimationOnCompositor();
}
bool AnimationPlayer::update(TimingUpdateReason reason)
{
if (!m_timeline)
return false;
updateCurrentTimingState(reason);
m_outdated = false;
if (m_content) {
double inheritedTime = isNull(m_timeline->currentTimeInternal()) ? nullValue() : currentTimeInternal();
m_content->updateInheritedTime(inheritedTime, reason);
}
if (finished() && !m_finished) {
if (reason == TimingUpdateForAnimationFrame && hasStartTime()) {
const AtomicString& eventType = EventTypeNames::finish;
if (executionContext() && hasEventListeners(eventType)) {
m_pendingFinishedEvent = AnimationPlayerEvent::create(eventType, currentTime(), 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 || !finished();
}
double AnimationPlayer::timeToEffectChange()
{
ASSERT(!m_outdated);
if (m_held || !hasStartTime())
return std::numeric_limits<double>::infinity();
if (!m_content)
return -currentTimeInternal() / m_playbackRate;
if (m_playbackRate > 0)
return m_content->timeToForwardsEffectChange() / m_playbackRate;
return m_content->timeToReverseEffectChange() / -m_playbackRate;
}
void AnimationPlayer::cancel()
{
setSource(0);
}
#if !ENABLE(OILPAN)
bool AnimationPlayer::canFree() const
{
ASSERT(m_content);
return hasOneRef() && m_content->isAnimation() && m_content->hasOneRef();
}
#endif
bool AnimationPlayer::addEventListener(const AtomicString& eventType, PassRefPtr<EventListener> listener, bool useCapture)
{
if (eventType == EventTypeNames::finish)
UseCounter::count(executionContext(), UseCounter::AnimationPlayerFinishEvent);
return EventTargetWithInlineData::addEventListener(eventType, listener, useCapture);
}
void AnimationPlayer::pauseForTesting(double pauseTime)
{
RELEASE_ASSERT(!paused());
setCurrentTimeInternal(pauseTime, TimingUpdateOnDemand);
if (hasActiveAnimationsOnCompositor())
toAnimation(m_content.get())->pauseAnimationForTestingOnCompositor(currentTimeInternal());
m_isPausedForTesting = true;
pause();
}
void AnimationPlayer::trace(Visitor* visitor)
{
visitor->trace(m_content);
visitor->trace(m_timeline);
visitor->trace(m_pendingFinishedEvent);
EventTargetWithInlineData::trace(visitor);
}
} // namespace