#include "config.h"
#include "AnimationBase.h"
#include "AnimationControllerPrivate.h"
#include "CSSPrimitiveValue.h"
#include "CSSPropertyAnimation.h"
#include "CompositeAnimation.h"
#include "Document.h"
#include "EventNames.h"
#include "FloatConversion.h"
#include "RenderBox.h"
#include "RenderStyle.h"
#include "UnitBezier.h"
#include <algorithm>
#include <wtf/CurrentTime.h>
using namespace std;
namespace WebCore {
static inline double solveEpsilon(double duration)
{
return 1.0 / (200.0 * duration);
}
static inline double solveCubicBezierFunction(double p1x, double p1y, double p2x, double p2y, double t, double duration)
{
UnitBezier bezier(p1x, p1y, p2x, p2y);
return bezier.solve(t, solveEpsilon(duration));
}
static inline double solveStepsFunction(int numSteps, bool stepAtStart, double t)
{
if (stepAtStart)
return min(1.0, (floor(numSteps * t) + 1) / numSteps);
return floor(numSteps * t) / numSteps;
}
AnimationBase::AnimationBase(const Animation* transition, RenderObject* renderer, CompositeAnimation* compAnim)
: m_animState(AnimationStateNew)
, m_isAnimating(false)
, m_isAccelerated(false)
, m_transformFunctionListValid(false)
#if ENABLE(CSS_FILTERS)
, m_filterFunctionListsMatch(false)
#endif
, m_startTime(0)
, m_pauseTime(-1)
, m_requestedStartTime(0)
, m_totalDuration(-1)
, m_nextIterationDuration(-1)
, m_object(renderer)
, m_animation(const_cast<Animation*>(transition))
, m_compAnim(compAnim)
{
if (m_animation->iterationCount() > 0)
m_totalDuration = m_animation->duration() * m_animation->iterationCount();
}
void AnimationBase::setNeedsStyleRecalc(Node* node)
{
ASSERT(!node || (node->document() && !node->document()->inPageCache()));
if (node)
node->setNeedsStyleRecalc(SyntheticStyleChange);
}
double AnimationBase::duration() const
{
return m_animation->duration();
}
bool AnimationBase::playStatePlaying() const
{
return m_animation->playState() == AnimPlayStatePlaying;
}
bool AnimationBase::animationsMatch(const Animation* anim) const
{
return m_animation->animationsMatch(anim);
}
void AnimationBase::updateStateMachine(AnimStateInput input, double param)
{
if (!m_compAnim)
return;
if (input == AnimationStateInputMakeNew) {
if (m_animState == AnimationStateStartWaitStyleAvailable)
m_compAnim->animationController()->removeFromAnimationsWaitingForStyle(this);
m_animState = AnimationStateNew;
m_startTime = 0;
m_pauseTime = -1;
m_requestedStartTime = 0;
m_nextIterationDuration = -1;
endAnimation();
return;
}
if (input == AnimationStateInputRestartAnimation) {
if (m_animState == AnimationStateStartWaitStyleAvailable)
m_compAnim->animationController()->removeFromAnimationsWaitingForStyle(this);
m_animState = AnimationStateNew;
m_startTime = 0;
m_pauseTime = -1;
m_requestedStartTime = 0;
m_nextIterationDuration = -1;
endAnimation();
if (!paused())
updateStateMachine(AnimationStateInputStartAnimation, -1);
return;
}
if (input == AnimationStateInputEndAnimation) {
if (m_animState == AnimationStateStartWaitStyleAvailable)
m_compAnim->animationController()->removeFromAnimationsWaitingForStyle(this);
m_animState = AnimationStateDone;
endAnimation();
return;
}
if (input == AnimationStateInputPauseOverride) {
if (m_animState == AnimationStateStartWaitResponse) {
endAnimation();
updateStateMachine(AnimationStateInputStartTimeSet, beginAnimationUpdateTime());
}
return;
}
if (input == AnimationStateInputResumeOverride) {
if (m_animState == AnimationStateLooping || m_animState == AnimationStateEnding) {
startAnimation(beginAnimationUpdateTime() - m_startTime);
}
return;
}
switch (m_animState) {
case AnimationStateNew:
ASSERT(input == AnimationStateInputStartAnimation || input == AnimationStateInputPlayStateRunning || input == AnimationStateInputPlayStatePaused);
if (input == AnimationStateInputStartAnimation || input == AnimationStateInputPlayStateRunning) {
m_requestedStartTime = beginAnimationUpdateTime();
m_animState = AnimationStateStartWaitTimer;
}
break;
case AnimationStateStartWaitTimer:
ASSERT(input == AnimationStateInputStartTimerFired || input == AnimationStateInputPlayStatePaused);
if (input == AnimationStateInputStartTimerFired) {
ASSERT(param >= 0);
m_animState = AnimationStateStartWaitStyleAvailable;
m_compAnim->animationController()->addToAnimationsWaitingForStyle(this);
if (m_object)
m_compAnim->animationController()->addNodeChangeToDispatch(m_object->node());
} else {
ASSERT(!paused());
m_pauseTime = beginAnimationUpdateTime();
m_animState = AnimationStatePausedWaitTimer;
}
break;
case AnimationStateStartWaitStyleAvailable:
ASSERT(input == AnimationStateInputStyleAvailable || input == AnimationStateInputPlayStatePaused);
if (input == AnimationStateInputStyleAvailable) {
m_animState = AnimationStateStartWaitResponse;
overrideAnimations();
if (overridden()) {
m_animState = AnimationStateStartWaitResponse;
m_isAccelerated = false;
updateStateMachine(AnimationStateInputStartTimeSet, beginAnimationUpdateTime());
} else {
double timeOffset = 0;
if (m_animation->delay() < 0)
timeOffset = -m_animation->delay();
bool started = startAnimation(timeOffset);
m_compAnim->animationController()->addToAnimationsWaitingForStartTimeResponse(this, started);
m_isAccelerated = started;
}
} else {
m_pauseTime = beginAnimationUpdateTime();
m_animState = AnimationStatePausedWaitStyleAvailable;
}
break;
case AnimationStateStartWaitResponse:
ASSERT(input == AnimationStateInputStartTimeSet || input == AnimationStateInputPlayStatePaused);
if (input == AnimationStateInputStartTimeSet) {
ASSERT(param >= 0);
if (m_startTime <= 0) {
m_startTime = param;
if (m_animation->delay() < 0)
m_startTime += m_animation->delay();
}
onAnimationStart(0);
goIntoEndingOrLoopingState();
if (m_object)
m_compAnim->animationController()->addNodeChangeToDispatch(m_object->node());
} else {
m_pauseTime = beginAnimationUpdateTime();
pauseAnimation(beginAnimationUpdateTime() - m_startTime);
m_animState = AnimationStatePausedWaitResponse;
}
break;
case AnimationStateLooping:
ASSERT(input == AnimationStateInputLoopTimerFired || input == AnimationStateInputPlayStatePaused);
if (input == AnimationStateInputLoopTimerFired) {
ASSERT(param >= 0);
onAnimationIteration(param);
goIntoEndingOrLoopingState();
} else {
m_pauseTime = beginAnimationUpdateTime();
pauseAnimation(beginAnimationUpdateTime() - m_startTime);
m_animState = AnimationStatePausedRun;
}
break;
case AnimationStateEnding:
#if !LOG_DISABLED
if (input != AnimationStateInputEndTimerFired && input != AnimationStateInputPlayStatePaused)
LOG_ERROR("State is AnimationStateEnding, but input is not AnimationStateInputEndTimerFired or AnimationStateInputPlayStatePaused. It is %d.", input);
#endif
if (input == AnimationStateInputEndTimerFired) {
ASSERT(param >= 0);
onAnimationEnd(param);
m_animState = AnimationStateDone;
if (m_object) {
if (m_animation->fillsForwards())
m_animState = AnimationStateFillingForwards;
else
resumeOverriddenAnimations();
m_compAnim->animationController()->addNodeChangeToDispatch(m_object->node());
}
} else {
m_pauseTime = beginAnimationUpdateTime();
pauseAnimation(beginAnimationUpdateTime() - m_startTime);
m_animState = AnimationStatePausedRun;
}
break;
case AnimationStatePausedWaitTimer:
ASSERT(input == AnimationStateInputPlayStateRunning);
ASSERT(paused());
m_startTime += beginAnimationUpdateTime() - m_pauseTime;
m_pauseTime = -1;
m_animState = AnimationStateNew;
updateStateMachine(AnimationStateInputStartAnimation, 0);
break;
case AnimationStatePausedWaitResponse:
case AnimationStatePausedWaitStyleAvailable:
case AnimationStatePausedRun:
ASSERT(input == AnimationStateInputPlayStateRunning || input == AnimationStateInputStartTimeSet || input == AnimationStateInputStyleAvailable);
ASSERT(paused());
if (input == AnimationStateInputPlayStateRunning) {
if (m_animState == AnimationStatePausedRun)
m_startTime += beginAnimationUpdateTime() - m_pauseTime;
else
m_startTime = 0;
m_pauseTime = -1;
if (m_animState == AnimationStatePausedWaitStyleAvailable)
m_animState = AnimationStateStartWaitStyleAvailable;
else {
m_animState = AnimationStateStartWaitResponse;
if (overridden()) {
updateStateMachine(AnimationStateInputStartTimeSet, beginAnimationUpdateTime());
m_isAccelerated = true;
} else {
bool started = startAnimation(beginAnimationUpdateTime() - m_startTime);
m_compAnim->animationController()->addToAnimationsWaitingForStartTimeResponse(this, started);
m_isAccelerated = started;
}
}
break;
}
if (input == AnimationStateInputStartTimeSet) {
ASSERT(m_animState == AnimationStatePausedWaitResponse);
m_animState = AnimationStatePausedRun;
ASSERT(m_startTime == 0);
m_startTime = param;
m_pauseTime += m_startTime;
break;
}
ASSERT(m_animState == AnimationStatePausedWaitStyleAvailable);
m_animState = AnimationStatePausedWaitResponse;
overrideAnimations();
break;
case AnimationStateFillingForwards:
case AnimationStateDone:
break;
}
}
void AnimationBase::fireAnimationEventsIfNeeded()
{
if (!m_compAnim)
return;
if (m_animState != AnimationStateStartWaitTimer && m_animState != AnimationStateLooping && m_animState != AnimationStateEnding)
return;
RefPtr<AnimationBase> protector(this);
RefPtr<CompositeAnimation> compProtector(m_compAnim);
if (m_animState == AnimationStateStartWaitTimer) {
if (beginAnimationUpdateTime() - m_requestedStartTime >= m_animation->delay())
updateStateMachine(AnimationStateInputStartTimerFired, 0);
return;
}
double elapsedDuration = beginAnimationUpdateTime() - m_startTime;
elapsedDuration = max(elapsedDuration, 0.0);
if (m_totalDuration >= 0 && elapsedDuration >= m_totalDuration) {
m_animState = AnimationStateEnding;
updateStateMachine(AnimationStateInputEndTimerFired, m_totalDuration);
} else {
if (m_nextIterationDuration < 0) {
double durationLeft = m_animation->duration() - fmod(elapsedDuration, m_animation->duration());
m_nextIterationDuration = elapsedDuration + durationLeft;
}
if (elapsedDuration >= m_nextIterationDuration) {
double previous = m_nextIterationDuration;
double durationLeft = m_animation->duration() - fmod(elapsedDuration, m_animation->duration());
m_nextIterationDuration = elapsedDuration + durationLeft;
updateStateMachine(AnimationStateInputLoopTimerFired, previous);
}
}
}
void AnimationBase::updatePlayState(EAnimPlayState playState)
{
if (!m_compAnim)
return;
bool pause = playState == AnimPlayStatePaused || m_compAnim->suspended();
if (pause == paused() && !isNew())
return;
updateStateMachine(pause ? AnimationStateInputPlayStatePaused : AnimationStateInputPlayStateRunning, -1);
}
double AnimationBase::timeToNextService()
{
if (paused() || isNew() || m_animState == AnimationStateFillingForwards)
return -1;
if (m_animState == AnimationStateStartWaitTimer) {
double timeFromNow = m_animation->delay() - (beginAnimationUpdateTime() - m_requestedStartTime);
return max(timeFromNow, 0.0);
}
fireAnimationEventsIfNeeded();
return 0;
}
double AnimationBase::fractionalTime(double scale, double elapsedTime, double offset) const
{
double fractionalTime = m_animation->duration() ? (elapsedTime / m_animation->duration()) : 1;
if (fractionalTime < 0)
fractionalTime = 0;
int integralTime = static_cast<int>(fractionalTime);
const int integralIterationCount = static_cast<int>(m_animation->iterationCount());
const bool iterationCountHasFractional = m_animation->iterationCount() - integralIterationCount;
if (m_animation->iterationCount() != Animation::IterationCountInfinite && !iterationCountHasFractional)
integralTime = min(integralTime, integralIterationCount - 1);
fractionalTime -= integralTime;
if (((m_animation->direction() == Animation::AnimationDirectionAlternate) && (integralTime & 1))
|| ((m_animation->direction() == Animation::AnimationDirectionAlternateReverse) && !(integralTime & 1))
|| m_animation->direction() == Animation::AnimationDirectionReverse)
fractionalTime = 1 - fractionalTime;
if (scale != 1 || offset)
fractionalTime = (fractionalTime - offset) * scale;
return fractionalTime;
}
double AnimationBase::progress(double scale, double offset, const TimingFunction* tf) const
{
if (preActive())
return 0;
double elapsedTime = getElapsedTime();
double dur = m_animation->duration();
if (m_animation->iterationCount() > 0)
dur *= m_animation->iterationCount();
if (postActive() || !m_animation->duration())
return 1.0;
if (m_animation->iterationCount() > 0 && elapsedTime >= dur) {
const int integralIterationCount = static_cast<int>(m_animation->iterationCount());
const bool iterationCountHasFractional = m_animation->iterationCount() - integralIterationCount;
return (integralIterationCount % 2 || iterationCountHasFractional) ? 1.0 : 0.0;
}
const double fractionalTime = this->fractionalTime(scale, elapsedTime, offset);
if (!tf)
tf = m_animation->timingFunction().get();
if (tf->isCubicBezierTimingFunction()) {
const CubicBezierTimingFunction* ctf = static_cast<const CubicBezierTimingFunction*>(tf);
return solveCubicBezierFunction(ctf->x1(),
ctf->y1(),
ctf->x2(),
ctf->y2(),
fractionalTime, m_animation->duration());
} else if (tf->isStepsTimingFunction()) {
const StepsTimingFunction* stf = static_cast<const StepsTimingFunction*>(tf);
return solveStepsFunction(stf->numberOfSteps(), stf->stepAtStart(), fractionalTime);
} else
return fractionalTime;
}
void AnimationBase::getTimeToNextEvent(double& time, bool& isLooping) const
{
const double elapsedDuration = max(beginAnimationUpdateTime() - m_startTime, 0.0);
double durationLeft = 0;
double nextIterationTime = m_totalDuration;
if (m_totalDuration < 0 || elapsedDuration < m_totalDuration) {
durationLeft = m_animation->duration() > 0 ? (m_animation->duration() - fmod(elapsedDuration, m_animation->duration())) : 0;
nextIterationTime = elapsedDuration + durationLeft;
}
if (m_totalDuration < 0 || nextIterationTime < m_totalDuration) {
ASSERT(nextIterationTime > 0);
isLooping = true;
} else {
isLooping = false;
}
time = durationLeft;
}
void AnimationBase::goIntoEndingOrLoopingState()
{
double t;
bool isLooping;
getTimeToNextEvent(t, isLooping);
m_animState = isLooping ? AnimationStateLooping : AnimationStateEnding;
}
void AnimationBase::freezeAtTime(double t)
{
if (!m_compAnim)
return;
if (!m_startTime) {
m_animState = AnimationStateStartWaitResponse;
onAnimationStartResponse(currentTime());
}
ASSERT(m_startTime); if (t <= m_animation->delay())
m_pauseTime = m_startTime;
else
m_pauseTime = m_startTime + t - m_animation->delay();
#if USE(ACCELERATED_COMPOSITING)
if (m_object && m_object->isComposited())
toRenderBoxModelObject(m_object)->suspendAnimations(m_pauseTime);
#endif
}
double AnimationBase::beginAnimationUpdateTime() const
{
if (!m_compAnim)
return 0;
return m_compAnim->animationController()->beginAnimationUpdateTime();
}
double AnimationBase::getElapsedTime() const
{
if (paused())
return m_pauseTime - m_startTime;
if (m_startTime <= 0)
return 0;
if (postActive())
return 1;
return beginAnimationUpdateTime() - m_startTime;
}
void AnimationBase::setElapsedTime(double time)
{
UNUSED_PARAM(time);
}
void AnimationBase::play()
{
}
void AnimationBase::pause()
{
}
}