void AnimationBase::updateStateMachine(AnimationStateInput input, double param) { if (!m_compositeAnimation) return; // If we get AnimationStateInput::RestartAnimation then we force a new animation, regardless of state. if (input == AnimationStateInput::MakeNew) { if (m_animationState == AnimationState::StartWaitStyleAvailable) m_compositeAnimation->animationController().removeFromAnimationsWaitingForStyle(this); LOG(Animations, "%p AnimationState %s -> New", this, nameForState(m_animationState)); m_animationState = AnimationState::New; m_startTime = 0; m_pauseTime = -1; m_requestedStartTime = 0; m_nextIterationDuration = -1; endAnimation(); return; } if (input == AnimationStateInput::RestartAnimation) { if (m_animationState == AnimationState::StartWaitStyleAvailable) m_compositeAnimation->animationController().removeFromAnimationsWaitingForStyle(this); LOG(Animations, "%p AnimationState %s -> New", this, nameForState(m_animationState)); m_animationState = AnimationState::New; m_startTime = 0; m_pauseTime = -1; m_requestedStartTime = 0; m_nextIterationDuration = -1; endAnimation(); if (!paused()) updateStateMachine(AnimationStateInput::StartAnimation, -1); return; } if (input == AnimationStateInput::EndAnimation) { if (m_animationState == AnimationState::StartWaitStyleAvailable) m_compositeAnimation->animationController().removeFromAnimationsWaitingForStyle(this); LOG(Animations, "%p AnimationState %s -> Done", this, nameForState(m_animationState)); m_animationState = AnimationState::Done; endAnimation(); return; } if (input == AnimationStateInput::PauseOverride) { if (m_animationState == AnimationState::StartWaitResponse) { // If we are in AnimationState::StartWaitResponse, the animation will get canceled before // we get a response, so move to the next state. endAnimation(); updateStateMachine(AnimationStateInput::StartTimeSet, beginAnimationUpdateTime()); } return; } if (input == AnimationStateInput::ResumeOverride) { if (m_animationState == AnimationState::Looping || m_animationState == AnimationState::Ending) { // Start the animation startAnimation(beginAnimationUpdateTime() - m_startTime); } return; } // Execute state machine switch (m_animationState) { case AnimationState::New: ASSERT(input == AnimationStateInput::StartAnimation || input == AnimationStateInput::PlayStateRunning || input == AnimationStateInput::PlayStatePaused); if (input == AnimationStateInput::StartAnimation || input == AnimationStateInput::PlayStateRunning) { m_requestedStartTime = beginAnimationUpdateTime(); LOG(Animations, "%p AnimationState %s -> StartWaitTimer", this, nameForState(m_animationState)); m_animationState = AnimationState::StartWaitTimer; } else { // We are pausing before we even started. LOG(Animations, "%p AnimationState %s -> AnimationState::PausedNew", this, nameForState(m_animationState)); m_animationState = AnimationState::PausedNew; } #if ENABLE(CSS_ANIMATIONS_LEVEL_2) if (m_animation->trigger() && m_animation->trigger()->isScrollAnimationTrigger()) m_compositeAnimation->animationController().addToAnimationsDependentOnScroll(this); #endif break; case AnimationState::StartWaitTimer: ASSERT(input == AnimationStateInput::StartTimerFired || input == AnimationStateInput::PlayStatePaused); if (input == AnimationStateInput::StartTimerFired) { ASSERT(param >= 0); // Start timer has fired, tell the animation to start and wait for it to respond with start time LOG(Animations, "%p AnimationState %s -> StartWaitStyleAvailable (time is %f)", this, nameForState(m_animationState), param); m_animationState = AnimationState::StartWaitStyleAvailable; m_compositeAnimation->animationController().addToAnimationsWaitingForStyle(this); // Trigger a render so we can start the animation if (m_object && m_object->element()) m_compositeAnimation->animationController().addElementChangeToDispatch(*m_object->element()); } else { ASSERT(!paused()); // We're waiting for the start timer to fire and we got a pause. Cancel the timer, pause and wait m_pauseTime = beginAnimationUpdateTime(); LOG(Animations, "%p AnimationState %s -> PausedWaitTimer", this, nameForState(m_animationState)); m_animationState = AnimationState::PausedWaitTimer; } break; case AnimationState::StartWaitStyleAvailable: ASSERT(input == AnimationStateInput::StyleAvailable || input == AnimationStateInput::PlayStatePaused); if (input == AnimationStateInput::StyleAvailable) { // Start timer has fired, tell the animation to start and wait for it to respond with start time LOG(Animations, "%p AnimationState %s -> StartWaitResponse (time is %f)", this, nameForState(m_animationState), param); m_animationState = AnimationState::StartWaitResponse; overrideAnimations(); // Start the animation if (overridden()) { // We won't try to start accelerated animations if we are overridden and // just move on to the next state. LOG(Animations, "%p AnimationState %s -> StartWaitResponse", this, nameForState(m_animationState)); m_animationState = AnimationState::StartWaitResponse; m_isAccelerated = false; updateStateMachine(AnimationStateInput::StartTimeSet, beginAnimationUpdateTime()); } else { double timeOffset = 0; // If the value for 'animation-delay' is negative then the animation appears to have started in the past. if (m_animation->delay() < 0) timeOffset = -m_animation->delay(); bool started = startAnimation(timeOffset); m_compositeAnimation->animationController().addToAnimationsWaitingForStartTimeResponse(this, started); m_isAccelerated = started; } } else { // We're waiting for the style to be available and we got a pause. Pause and wait m_pauseTime = beginAnimationUpdateTime(); LOG(Animations, "%p AnimationState %s -> PausedWaitStyleAvailable", this, nameForState(m_animationState)); m_animationState = AnimationState::PausedWaitStyleAvailable; } break; case AnimationState::StartWaitResponse: ASSERT(input == AnimationStateInput::StartTimeSet || input == AnimationStateInput::PlayStatePaused); if (input == AnimationStateInput::StartTimeSet) { ASSERT(param > -0.001); // Sometimes Core Animation gives us a beginTime slightly into the future. LOG(Animations, "%p AnimationState %s -> StartTimeSet (time is %f)", this, nameForState(m_animationState), param); // We have a start time, set it, unless the startTime is already set if (m_startTime <= 0) { m_startTime = param; // If the value for 'animation-delay' is negative then the animation appears to have started in the past. if (m_animation->delay() < 0) m_startTime += m_animation->delay(); } // Now that we know the start time, fire the start event. onAnimationStart(0); // The elapsedTime is 0. // Decide whether to go into looping or ending state goIntoEndingOrLoopingState(); // Dispatch updateStyleIfNeeded so we can start the animation if (m_object && m_object->element()) m_compositeAnimation->animationController().addElementChangeToDispatch(*m_object->element()); } else { // We are pausing while waiting for a start response. Cancel the animation and wait. When // we unpause, we will act as though the start timer just fired m_pauseTime = beginAnimationUpdateTime(); pauseAnimation(beginAnimationUpdateTime() - m_startTime); LOG(Animations, "%p AnimationState %s -> PausedWaitResponse", this, nameForState(m_animationState)); m_animationState = AnimationState::PausedWaitResponse; } break; case AnimationState::Looping: ASSERT(input == AnimationStateInput::LoopTimerFired || input == AnimationStateInput::PlayStatePaused); if (input == AnimationStateInput::LoopTimerFired) { ASSERT(param >= 0); LOG(Animations, "%p AnimationState %s -> LoopTimerFired (time is %f)", this, nameForState(m_animationState), param); // Loop timer fired, loop again or end. onAnimationIteration(param); // Decide whether to go into looping or ending state goIntoEndingOrLoopingState(); } else { // We are pausing while running. Cancel the animation and wait m_pauseTime = beginAnimationUpdateTime(); pauseAnimation(beginAnimationUpdateTime() - m_startTime); LOG(Animations, "%p AnimationState %s -> PausedRun", this, nameForState(m_animationState)); m_animationState = AnimationState::PausedRun; } break; case AnimationState::Ending: #if !LOG_DISABLED if (input != AnimationStateInput::EndTimerFired && input != AnimationStateInput::PlayStatePaused) LOG_ERROR("State is AnimationState::Ending, but input is not AnimationStateInput::EndTimerFired or AnimationStateInput::PlayStatePaused. It is %s.", nameForStateInput(input)); #endif if (input == AnimationStateInput::EndTimerFired) { ASSERT(param >= 0); // End timer fired, finish up onAnimationEnd(param); LOG(Animations, "%p AnimationState %s -> Done (time is %f)", this, nameForState(m_animationState), param); m_animationState = AnimationState::Done; if (m_object) { if (m_animation->fillsForwards()) { LOG(Animations, "%p AnimationState %s -> FillingForwards", this, nameForState(m_animationState)); m_animationState = AnimationState::FillingForwards; } else resumeOverriddenAnimations(); // Fire off another style change so we can set the final value if (m_object->element()) m_compositeAnimation->animationController().addElementChangeToDispatch(*m_object->element()); } } else { // We are pausing while running. Cancel the animation and wait m_pauseTime = beginAnimationUpdateTime(); pauseAnimation(beginAnimationUpdateTime() - m_startTime); LOG(Animations, "%p AnimationState %s -> PausedRun", this, nameForState(m_animationState)); m_animationState = AnimationState::PausedRun; } // |this| may be deleted here break; case AnimationState::PausedWaitTimer: ASSERT(input == AnimationStateInput::PlayStateRunning); ASSERT(paused()); // Update the times m_startTime += beginAnimationUpdateTime() - m_pauseTime; m_pauseTime = -1; // we were waiting for the start timer to fire, go back and wait again LOG(Animations, "%p AnimationState %s -> New", this, nameForState(m_animationState)); m_animationState = AnimationState::New; updateStateMachine(AnimationStateInput::StartAnimation, 0); break; case AnimationState::PausedNew: case AnimationState::PausedWaitResponse: case AnimationState::PausedWaitStyleAvailable: case AnimationState::PausedRun: // We treat these two cases the same. The only difference is that, when we are in // AnimationState::PausedWaitResponse, we don't yet have a valid startTime, so we send 0 to startAnimation. // When the AnimationStateInput::StartTimeSet comes in and we were in AnimationState::PausedRun, we will notice // that we have already set the startTime and will ignore it. ASSERT(input == AnimationStateInput::PlayStateRunning || input == AnimationStateInput::StartTimeSet || input == AnimationStateInput::StyleAvailable || input == AnimationStateInput::StartAnimation); ASSERT(paused()); if (input == AnimationStateInput::PlayStateRunning) { if (m_animationState == AnimationState::PausedNew) { // We were paused before we even started, and now we're supposed // to start, so jump back to the New state and reset. LOG(Animations, "%p AnimationState %s -> AnimationState::New", this, nameForState(m_animationState)); m_animationState = AnimationState::New; updateStateMachine(input, param); break; } // Update the times if (m_animationState == AnimationState::PausedRun) m_startTime += beginAnimationUpdateTime() - m_pauseTime; else m_startTime = 0; m_pauseTime = -1; if (m_animationState == AnimationState::PausedWaitStyleAvailable) { LOG(Animations, "%p AnimationState %s -> StartWaitStyleAvailable", this, nameForState(m_animationState)); m_animationState = AnimationState::StartWaitStyleAvailable; } else { // We were either running or waiting for a begin time response from the animation. // Either way we need to restart the animation (possibly with an offset if we // had already been running) and wait for it to start. LOG(Animations, "%p AnimationState %s -> StartWaitResponse", this, nameForState(m_animationState)); m_animationState = AnimationState::StartWaitResponse; // Start the animation if (overridden()) { // We won't try to start accelerated animations if we are overridden and // just move on to the next state. updateStateMachine(AnimationStateInput::StartTimeSet, beginAnimationUpdateTime()); m_isAccelerated = true; } else { bool started = startAnimation(beginAnimationUpdateTime() - m_startTime); m_compositeAnimation->animationController().addToAnimationsWaitingForStartTimeResponse(this, started); m_isAccelerated = started; } } break; } if (input == AnimationStateInput::StartTimeSet) { ASSERT(m_animationState == AnimationState::PausedWaitResponse); // We are paused but we got the callback that notifies us that an accelerated animation started. // We ignore the start time and just move into the paused-run state. LOG(Animations, "%p AnimationState %s -> PausedRun (time is %f)", this, nameForState(m_animationState), param); m_animationState = AnimationState::PausedRun; ASSERT(m_startTime == 0); m_startTime = param; m_pauseTime += m_startTime; break; } ASSERT(m_animationState == AnimationState::PausedWaitStyleAvailable); // We are paused but we got the callback that notifies us that style has been updated. // We move to the AnimationState::PausedWaitResponse state LOG(Animations, "%p AnimationState %s -> PausedWaitResponse", this, nameForState(m_animationState)); m_animationState = AnimationState::PausedWaitResponse; overrideAnimations(); break; case AnimationState::FillingForwards: case AnimationState::Done: // We're done. Stay in this state until we are deleted break; } }
KeyframeAnimation::~KeyframeAnimation() { // Do the cleanup here instead of in the base class so the specialized methods get called if (!postActive()) updateStateMachine(AnimationStateInputEndAnimation, -1); }
void KeyframeAnimation::animate(CompositeAnimation* animation, RenderObject* renderer, const RenderStyle* currentStyle, const RenderStyle* targetStyle, RefPtr<RenderStyle>& animatedStyle) { // If we have not yet started, we will not have a valid start time, so just start the animation if needed. if (isNew() && m_animation->playState() == AnimPlayStatePlaying) updateStateMachine(AnimationStateInputStartAnimation, -1); // If we get this far and the animation is done, it means we are cleaning up a just finished animation. // If so, we need to send back the targetStyle. if (postActive()) { if (!animatedStyle) animatedStyle = const_cast<RenderStyle*>(targetStyle); return; } // If we are waiting for the start timer, we don't want to change the style yet. // Special case - if the delay time is 0, then we do want to set the first frame of the // animation right away. This avoids a flash when the animation starts. if (waitingToStart() && m_animation->delay() > 0) return; // FIXME: we need to be more efficient about determining which keyframes we are animating between. // We should cache the last pair or something. // Find the first key double elapsedTime = (m_startTime > 0) ? ((!paused() ? currentTime() : m_pauseTime) - m_startTime) : 0; if (elapsedTime < 0) elapsedTime = 0; double t = m_animation->duration() ? (elapsedTime / m_animation->duration()) : 1; int i = static_cast<int>(t); t -= i; if (m_animation->direction() && (i & 1)) t = 1 - t; const RenderStyle* fromStyle = 0; const RenderStyle* toStyle = 0; double scale = 1; double offset = 0; Vector<KeyframeValue>::const_iterator endKeyframes = m_keyframes.endKeyframes(); for (Vector<KeyframeValue>::const_iterator it = m_keyframes.beginKeyframes(); it != endKeyframes; ++it) { if (t < it->key()) { // The first key should always be 0, so we should never succeed on the first key if (!fromStyle) break; scale = 1.0 / (it->key() - offset); toStyle = it->style(); break; } offset = it->key(); fromStyle = it->style(); } // If either style is 0 we have an invalid case, just stop the animation. if (!fromStyle || !toStyle) { updateStateMachine(AnimationStateInputEndAnimation, -1); return; } // Run a cycle of animation. // We know we will need a new render style, so make one if needed. if (!animatedStyle) animatedStyle = RenderStyle::clone(targetStyle); const TimingFunction* timingFunction = 0; if (fromStyle->animations() && fromStyle->animations()->size() > 0) timingFunction = &(fromStyle->animations()->animation(0)->timingFunction()); double prog = progress(scale, offset, timingFunction); HashSet<int>::const_iterator endProperties = m_keyframes.endProperties(); for (HashSet<int>::const_iterator it = m_keyframes.beginProperties(); it != endProperties; ++it) { if (blendProperties(this, *it, animatedStyle.get(), fromStyle, toStyle, prog)) setAnimating(); } }
void KeyframeAnimation::animate(CompositeAnimation*, RenderObject*, const RenderStyle*, RenderStyle* targetStyle, RefPtr<RenderStyle>& animatedStyle) { // Fire the start timeout if needed fireAnimationEventsIfNeeded(); // If we have not yet started, we will not have a valid start time, so just start the animation if needed. if (isNew() && m_animation->playState() == AnimPlayStatePlaying) updateStateMachine(AnimationStateInputStartAnimation, -1); // If we get this far and the animation is done, it means we are cleaning up a just finished animation. // If so, we need to send back the targetStyle. if (postActive()) { if (!animatedStyle) animatedStyle = const_cast<RenderStyle*>(targetStyle); return; } // If we are waiting for the start timer, we don't want to change the style yet. // Special case 1 - if the delay time is 0, then we do want to set the first frame of the // animation right away. This avoids a flash when the animation starts. // Special case 2 - if there is a backwards fill mode, then we want to continue // through to the style blend so that we get the fromStyle. if (waitingToStart() && m_animation->delay() > 0 && !m_animation->fillsBackwards()) return; // If we have no keyframes, don't animate. if (!m_keyframes.size()) { updateStateMachine(AnimationStateInputEndAnimation, -1); return; } // Run a cycle of animation. // We know we will need a new render style, so make one if needed. if (!animatedStyle) animatedStyle = RenderStyle::clone(targetStyle); // FIXME: we need to be more efficient about determining which keyframes we are animating between. // We should cache the last pair or something. HashSet<int>::const_iterator endProperties = m_keyframes.endProperties(); for (HashSet<int>::const_iterator it = m_keyframes.beginProperties(); it != endProperties; ++it) { int property = *it; // Get the from/to styles and progress between const RenderStyle* fromStyle = 0; const RenderStyle* toStyle = 0; double progress = 0.0; fetchIntervalEndpointsForProperty(property, fromStyle, toStyle, progress); bool needsAnim = blendProperties(this, property, animatedStyle.get(), fromStyle, toStyle, progress); if (needsAnim) setAnimating(); else { #if USE(ACCELERATED_COMPOSITING) // If we are running an accelerated animation, set a flag in the style // to indicate it. This can be used to make sure we get an updated // style for hit testing, etc. animatedStyle->setIsRunningAcceleratedAnimation(); #endif } } }