| /* |
| * Copyright (C) 2007, 2008, 2009 Apple 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: |
| * |
| * 1. Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * 2. 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. |
| * 3. Neither the name of Apple Inc. ("Apple") 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 APPLE AND ITS 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 APPLE OR ITS 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 "AnimationBase.h" |
| |
| #include "CSSAnimationControllerPrivate.h" |
| #include "CSSPrimitiveValue.h" |
| #include "CSSPropertyAnimation.h" |
| #include "CompositeAnimation.h" |
| #include "Document.h" |
| #include "FloatConversion.h" |
| #include "GeometryUtilities.h" |
| #include "Logging.h" |
| #include "RenderBox.h" |
| #include "RenderStyle.h" |
| #include "RenderView.h" |
| #include <algorithm> |
| #include <wtf/Ref.h> |
| |
| namespace WebCore { |
| |
| AnimationBase::AnimationBase(const Animation& animation, Element& element, CompositeAnimation& compositeAnimation) |
| : m_element(&element) |
| , m_compositeAnimation(&compositeAnimation) |
| , m_animation(const_cast<Animation&>(animation)) |
| { |
| // Compute the total duration |
| if (m_animation->iterationCount() > 0) |
| m_totalDuration = m_animation->duration() * m_animation->iterationCount(); |
| } |
| |
| AnimationBase::~AnimationBase() = default; |
| |
| const RenderStyle& AnimationBase::currentStyle() const |
| { |
| if (auto* renderer = this->renderer()) |
| return renderer->style(); |
| return unanimatedStyle(); |
| } |
| |
| RenderElement* AnimationBase::renderer() const |
| { |
| return m_element ? m_element->renderer() : nullptr; |
| } |
| |
| void AnimationBase::clear() |
| { |
| endAnimation(); |
| m_element = nullptr; |
| m_compositeAnimation = nullptr; |
| } |
| |
| void AnimationBase::setNeedsStyleRecalc(Element* element) |
| { |
| if (!element || element->document().renderTreeBeingDestroyed()) |
| return; |
| |
| ASSERT(element->document().backForwardCacheState() == Document::NotInBackForwardCache); |
| element->invalidateStyle(); |
| } |
| |
| double AnimationBase::duration() const |
| { |
| return m_animation->duration(); |
| } |
| |
| bool AnimationBase::playStatePlaying() const |
| { |
| return m_animation->playState() == AnimationPlayState::Playing; |
| } |
| |
| bool AnimationBase::animationsMatch(const Animation& animation) const |
| { |
| return m_animation->animationsMatch(animation); |
| } |
| |
| #if !LOG_DISABLED |
| static const char* nameForState(AnimationBase::AnimationState state) |
| { |
| switch (state) { |
| case AnimationBase::AnimationState::New: return "New"; |
| case AnimationBase::AnimationState::StartWaitTimer: return "StartWaitTimer"; |
| case AnimationBase::AnimationState::StartWaitStyleAvailable: return "StartWaitStyleAvailable"; |
| case AnimationBase::AnimationState::StartWaitResponse: return "StartWaitResponse"; |
| case AnimationBase::AnimationState::Looping: return "Looping"; |
| case AnimationBase::AnimationState::Ending: return "Ending"; |
| case AnimationBase::AnimationState::PausedNew: return "PausedNew"; |
| case AnimationBase::AnimationState::PausedWaitTimer: return "PausedWaitTimer"; |
| case AnimationBase::AnimationState::PausedWaitStyleAvailable: return "PausedWaitStyleAvailable"; |
| case AnimationBase::AnimationState::PausedWaitResponse: return "PausedWaitResponse"; |
| case AnimationBase::AnimationState::PausedRun: return "PausedRun"; |
| case AnimationBase::AnimationState::Done: return "Done"; |
| case AnimationBase::AnimationState::FillingForwards: return "FillingForwards"; |
| } |
| return ""; |
| } |
| |
| static const char* nameForStateInput(AnimationBase::AnimationStateInput input) |
| { |
| switch (input) { |
| case AnimationBase::AnimationStateInput::MakeNew: return "MakeNew"; |
| case AnimationBase::AnimationStateInput::StartAnimation: return "StartAnimation"; |
| case AnimationBase::AnimationStateInput::RestartAnimation: return "RestartAnimation"; |
| case AnimationBase::AnimationStateInput::StartTimerFired: return "StartTimerFired"; |
| case AnimationBase::AnimationStateInput::StyleAvailable: return "StyleAvailable"; |
| case AnimationBase::AnimationStateInput::StartTimeSet: return "StartTimeSet"; |
| case AnimationBase::AnimationStateInput::LoopTimerFired: return "LoopTimerFired"; |
| case AnimationBase::AnimationStateInput::EndTimerFired: return "EndTimerFired"; |
| case AnimationBase::AnimationStateInput::PauseOverride: return "PauseOverride"; |
| case AnimationBase::AnimationStateInput::ResumeOverride: return "ResumeOverride"; |
| case AnimationBase::AnimationStateInput::PlayStateRunning: return "PlayStateRunning"; |
| case AnimationBase::AnimationStateInput::PlayStatePaused: return "PlayStatePaused"; |
| case AnimationBase::AnimationStateInput::EndAnimation: return "EndAnimation"; |
| } |
| return ""; |
| } |
| #endif |
| |
| 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 = WTF::nullopt; |
| m_pauseTime = WTF::nullopt; |
| m_requestedStartTime = 0; |
| m_nextIterationDuration = WTF::nullopt; |
| 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 = WTF::nullopt; |
| m_pauseTime = WTF::nullopt; |
| m_requestedStartTime = 0; |
| m_nextIterationDuration = WTF::nullopt; |
| 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.valueOr(0)); |
| } |
| 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; |
| m_pauseTime = WTF::nullopt; |
| } |
| |
| 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_element) |
| m_compositeAnimation->animationController().addElementChangeToDispatch(*m_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) { |
| 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_startTime.value() + 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_element) |
| m_compositeAnimation->animationController().addElementChangeToDispatch(*m_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.valueOr(0)); |
| 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.valueOr(0)); |
| 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_element) { |
| 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_element) |
| m_compositeAnimation->animationController().addElementChangeToDispatch(*m_element); |
| } |
| } else { |
| // We are pausing while running. Cancel the animation and wait |
| m_pauseTime = beginAnimationUpdateTime(); |
| pauseAnimation(beginAnimationUpdateTime() - m_startTime.valueOr(0)); |
| 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 = m_startTime.valueOr(0) + beginAnimationUpdateTime() - m_pauseTime.valueOr(0); |
| m_pauseTime = WTF::nullopt; |
| |
| // 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::PlayStatePaused || input == AnimationStateInput::PlayStateRunning || input == AnimationStateInput::StartTimeSet || input == AnimationStateInput::StyleAvailable); |
| 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; |
| m_pauseTime = WTF::nullopt; |
| updateStateMachine(input, param); |
| break; |
| } |
| |
| // Update the times |
| if (m_animationState == AnimationState::PausedRun) |
| m_startTime = m_startTime.valueOr(0) + beginAnimationUpdateTime() - m_pauseTime.valueOr(0); |
| else |
| m_startTime = 0; |
| |
| m_pauseTime = WTF::nullopt; |
| |
| 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.valueOr(0)); |
| 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); |
| m_startTime = param; |
| m_pauseTime = m_pauseTime.valueOr(0) + param; |
| break; |
| } |
| |
| ASSERT(m_animationState == AnimationState::PausedNew || m_animationState == AnimationState::PausedWaitStyleAvailable); |
| |
| if (input == AnimationStateInput::PlayStatePaused) |
| break; |
| |
| ASSERT(input == AnimationStateInput::StyleAvailable); |
| |
| // 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; |
| } |
| } |
| |
| void AnimationBase::fireAnimationEventsIfNeeded() |
| { |
| if (!m_compositeAnimation) |
| return; |
| |
| // If we are waiting for the delay time to expire and it has, go to the next state |
| if (m_animationState != AnimationState::StartWaitTimer && m_animationState != AnimationState::Looping && m_animationState != AnimationState::Ending) |
| return; |
| |
| // We have to make sure to keep a ref to the this pointer, because it could get destroyed |
| // during an animation callback that might get called. Since the owner is a CompositeAnimation |
| // and it ref counts this object, we will keep a ref to that instead. That way the AnimationBase |
| // can still access the resources of its CompositeAnimation as needed. |
| Ref<AnimationBase> protectedThis(*this); |
| Ref<CompositeAnimation> protectCompositeAnimation(*m_compositeAnimation); |
| |
| // Check for start timeout |
| if (m_animationState == AnimationState::StartWaitTimer) { |
| if (beginAnimationUpdateTime() - m_requestedStartTime >= m_animation->delay()) |
| updateStateMachine(AnimationStateInput::StartTimerFired, 0); |
| return; |
| } |
| |
| double elapsedDuration = beginAnimationUpdateTime() - m_startTime.valueOr(0); |
| |
| // FIXME: we need to ensure that elapsedDuration is never < 0. If it is, this suggests that |
| // we had a recalcStyle() outside of beginAnimationUpdate()/endAnimationUpdate(). |
| // Also check in getTimeToNextEvent(). |
| elapsedDuration = std::max(elapsedDuration, 0.0); |
| |
| // Check for end timeout |
| if (m_totalDuration && elapsedDuration >= m_totalDuration.value()) { |
| // We may still be in AnimationState::Looping if we've managed to skip a |
| // whole iteration, in which case we should jump to the end state. |
| LOG(Animations, "%p AnimationState %s -> Ending", this, nameForState(m_animationState)); |
| m_animationState = AnimationState::Ending; |
| |
| // Fire an end event |
| updateStateMachine(AnimationStateInput::EndTimerFired, m_totalDuration.value()); |
| } else { |
| // Check for iteration timeout |
| if (!m_nextIterationDuration) { |
| // Hasn't been set yet, set it |
| double durationLeft = m_animation->duration() - fmod(elapsedDuration, m_animation->duration()); |
| m_nextIterationDuration = elapsedDuration + durationLeft; |
| } |
| |
| if (elapsedDuration >= m_nextIterationDuration) { |
| // Set to the next iteration |
| double previous = m_nextIterationDuration.value(); |
| double durationLeft = m_animation->duration() - fmod(elapsedDuration, m_animation->duration()); |
| m_nextIterationDuration = elapsedDuration + durationLeft; |
| |
| // Send the event |
| updateStateMachine(AnimationStateInput::LoopTimerFired, previous); |
| } |
| } |
| } |
| |
| void AnimationBase::updatePlayState(AnimationPlayState playState) |
| { |
| if (!m_compositeAnimation) |
| return; |
| |
| // When we get here, we can have one of 4 desired states: running, paused, suspended, paused & suspended. |
| // The state machine can be in one of two states: running, paused. |
| // Set the state machine to the desired state. |
| bool pause = playState == AnimationPlayState::Paused || m_compositeAnimation->isSuspended(); |
| |
| if (pause == paused() && !isNew()) |
| return; |
| |
| updateStateMachine(pause ? AnimationStateInput::PlayStatePaused : AnimationStateInput::PlayStateRunning, -1); |
| } |
| |
| Optional<Seconds> AnimationBase::timeToNextService() |
| { |
| // Returns the time at which next service is required. WTF::nullopt means no service is required. 0 means |
| // service is required now, and > 0 means service is required that many seconds in the future. |
| if (paused() || isNew() || postActive() || fillingForwards()) |
| return WTF::nullopt; |
| |
| if (m_animationState == AnimationState::StartWaitTimer) { |
| double timeFromNow = m_animation->delay() - (beginAnimationUpdateTime() - m_requestedStartTime); |
| return std::max(Seconds { timeFromNow }, 0_s); |
| } |
| |
| fireAnimationEventsIfNeeded(); |
| |
| // In all other cases, we need service right away. |
| return 0_s; |
| } |
| |
| // Compute the fractional time, taking into account direction. |
| // There is no need to worry about iterations, we assume that we would have |
| // short circuited above if we were done. |
| |
| double AnimationBase::fractionalTime(double scale, double elapsedTime, double offset) const |
| { |
| double fractionalTime = m_animation->duration() ? (elapsedTime / m_animation->duration()) : 1; |
| // FIXME: startTime can be before the current animation "frame" time. This is to sync with the frame time |
| // concept in AnimationTimeController. So we need to somehow sync the two. Until then, the possible |
| // error is small and will probably not be noticeable. Until we fix this, remove the assert. |
| // https://bugs.webkit.org/show_bug.cgi?id=52037 |
| // ASSERT(fractionalTime >= 0); |
| 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 = std::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* timingFunction) const |
| { |
| if (preActive()) |
| return 0; |
| |
| if (postActive()) |
| return 1; |
| |
| double elapsedTime = getElapsedTime(); |
| |
| double duration = m_animation->duration(); |
| if (m_animation->iterationCount() > 0) |
| duration *= m_animation->iterationCount(); |
| |
| if (fillingForwards()) |
| elapsedTime = duration; |
| |
| double fractionalTime = this->fractionalTime(scale, elapsedTime, offset); |
| |
| if (m_animation->iterationCount() > 0 && elapsedTime >= duration) { |
| if (WTF::isIntegral(fractionalTime)) |
| return fractionalTime; |
| } |
| |
| if (!timingFunction) |
| timingFunction = m_animation->timingFunction(); |
| |
| return timingFunction->transformTime(fractionalTime, m_animation->duration()); |
| } |
| |
| void AnimationBase::getTimeToNextEvent(Seconds& time, bool& isLooping) const |
| { |
| // Decide when the end or loop event needs to fire |
| const double elapsedDuration = std::max(beginAnimationUpdateTime() - m_startTime.valueOr(0), 0.0); |
| double durationLeft = 0; |
| double nextIterationTime = m_totalDuration.valueOr(0); |
| |
| if (!m_totalDuration || elapsedDuration < m_totalDuration.value()) { |
| durationLeft = m_animation->duration() > 0 ? (m_animation->duration() - fmod(elapsedDuration, m_animation->duration())) : 0; |
| nextIterationTime = elapsedDuration + durationLeft; |
| } |
| |
| if (!m_totalDuration || nextIterationTime < m_totalDuration.value()) { |
| // We are not at the end yet |
| ASSERT(nextIterationTime > 0); |
| isLooping = true; |
| } else { |
| // We are at the end |
| isLooping = false; |
| } |
| |
| time = Seconds { durationLeft }; |
| } |
| |
| void AnimationBase::goIntoEndingOrLoopingState() |
| { |
| Seconds t; |
| bool isLooping; |
| getTimeToNextEvent(t, isLooping); |
| LOG(Animations, "%p AnimationState %s -> %s", this, nameForState(m_animationState), isLooping ? "Looping" : "Ending"); |
| m_animationState = isLooping ? AnimationState::Looping : AnimationState::Ending; |
| } |
| |
| void AnimationBase::freezeAtTime(double t) |
| { |
| if (!m_compositeAnimation) |
| return; |
| |
| if (!m_startTime) { |
| // If we haven't started yet, make it as if we started. |
| LOG(Animations, "%p AnimationState %s -> StartWaitResponse", this, nameForState(m_animationState)); |
| m_animationState = AnimationState::StartWaitResponse; |
| onAnimationStartResponse(MonotonicTime::now()); |
| } |
| |
| ASSERT(m_startTime); // If m_startTime is zero, we haven't started yet, so we'll get a bad pause time. |
| if (t <= m_animation->delay()) |
| m_pauseTime = m_startTime.valueOr(0); |
| else |
| m_pauseTime = m_startTime.valueOr(0) + t - m_animation->delay(); |
| |
| if (auto* renderer = this->renderer()) |
| renderer->suspendAnimations(MonotonicTime::fromRawSeconds(m_pauseTime.value())); |
| } |
| |
| double AnimationBase::beginAnimationUpdateTime() const |
| { |
| if (!m_compositeAnimation) |
| return 0; |
| |
| return m_compositeAnimation->animationController().beginAnimationUpdateTime().secondsSinceEpoch().seconds(); |
| } |
| |
| double AnimationBase::getElapsedTime() const |
| { |
| if (paused()) { |
| double delayOffset = (!m_startTime && m_animation->delay() < 0) ? m_animation->delay() : 0; |
| return m_pauseTime.valueOr(0) - m_startTime.valueOr(0) - delayOffset; |
| } |
| |
| if (!m_startTime) |
| return 0; |
| |
| if (postActive() || fillingForwards()) |
| return m_totalDuration.valueOr(0); |
| |
| return beginAnimationUpdateTime() - m_startTime.valueOr(0); |
| } |
| |
| void AnimationBase::setElapsedTime(double time) |
| { |
| // FIXME: implement this method |
| UNUSED_PARAM(time); |
| } |
| |
| void AnimationBase::play() |
| { |
| // FIXME: implement this method |
| } |
| |
| void AnimationBase::pause() |
| { |
| // FIXME: implement this method |
| } |
| |
| static bool containsRotation(const Vector<RefPtr<TransformOperation>>& operations) |
| { |
| for (const auto& operation : operations) { |
| if (operation->type() == TransformOperation::ROTATE) |
| return true; |
| } |
| return false; |
| } |
| |
| bool AnimationBase::computeTransformedExtentViaTransformList(const FloatRect& rendererBox, const RenderStyle& style, LayoutRect& bounds) const |
| { |
| FloatRect floatBounds = bounds; |
| FloatPoint transformOrigin; |
| |
| bool applyTransformOrigin = containsRotation(style.transform().operations()) || style.transform().affectedByTransformOrigin(); |
| if (applyTransformOrigin) { |
| transformOrigin.setX(rendererBox.x() + floatValueForLength(style.transformOriginX(), rendererBox.width())); |
| transformOrigin.setY(rendererBox.y() + floatValueForLength(style.transformOriginY(), rendererBox.height())); |
| // Ignore transformOriginZ because we'll bail if we encounter any 3D transforms. |
| |
| floatBounds.moveBy(-transformOrigin); |
| } |
| |
| for (const auto& operation : style.transform().operations()) { |
| if (operation->type() == TransformOperation::ROTATE) { |
| // For now, just treat this as a full rotation. This could take angle into account to reduce inflation. |
| floatBounds = boundsOfRotatingRect(floatBounds); |
| } else { |
| TransformationMatrix transform; |
| operation->apply(transform, rendererBox.size()); |
| if (!transform.isAffine()) |
| return false; |
| |
| if (operation->type() == TransformOperation::MATRIX || operation->type() == TransformOperation::MATRIX_3D) { |
| TransformationMatrix::Decomposed2Type toDecomp; |
| transform.decompose2(toDecomp); |
| // Any rotation prevents us from using a simple start/end rect union. |
| if (toDecomp.angle) |
| return false; |
| } |
| |
| floatBounds = transform.mapRect(floatBounds); |
| } |
| } |
| |
| if (applyTransformOrigin) |
| floatBounds.moveBy(transformOrigin); |
| |
| bounds = LayoutRect(floatBounds); |
| return true; |
| } |
| |
| bool AnimationBase::computeTransformedExtentViaMatrix(const FloatRect& rendererBox, const RenderStyle& style, LayoutRect& bounds) const |
| { |
| TransformationMatrix transform; |
| style.applyTransform(transform, rendererBox, RenderStyle::IncludeTransformOrigin); |
| if (!transform.isAffine()) |
| return false; |
| |
| TransformationMatrix::Decomposed2Type fromDecomp; |
| transform.decompose2(fromDecomp); |
| // Any rotation prevents us from using a simple start/end rect union. |
| if (fromDecomp.angle) |
| return false; |
| |
| bounds = LayoutRect(transform.mapRect(bounds)); |
| return true; |
| |
| } |
| |
| } // namespace WebCore |