blob: 0a6aff9d71b4c5c58cf8d7b7cfada5b5c87ebfe9 [file] [log] [blame]
/*
* 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