#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 "SpringSolver.h"
#include "UnitBezier.h"
#include <algorithm>
#include <wtf/CurrentTime.h>
#include <wtf/Ref.h>
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 std::min(1.0, (floor(numSteps * t) + 1) / numSteps);
return floor(numSteps * t) / numSteps;
}
static inline double solveSpringFunction(double mass, double stiffness, double damping, double initialVelocity, double t, double duration)
{
SpringSolver solver(mass, stiffness, damping, initialVelocity);
return solver.solve(t * duration);
}
AnimationBase::AnimationBase(const Animation& animation, RenderElement* renderer, CompositeAnimation* compositeAnimation)
: m_object(renderer)
, m_compositeAnimation(compositeAnimation)
, m_animation(const_cast<Animation&>(animation))
{
if (m_animation->iterationCount() > 0)
m_totalDuration = m_animation->duration() * m_animation->iterationCount();
}
void AnimationBase::setNeedsStyleRecalc(Element* element)
{
if (!element || element->document().renderTreeBeingDestroyed())
return;
ASSERT(element->document().pageCacheState() == Document::NotInPageCache);
element->invalidateStyleAndLayerComposition();
}
double AnimationBase::duration() const
{
return m_animation->duration();
}
bool AnimationBase::playStatePlaying() const
{
return m_animation->playState() == AnimPlayStatePlaying;
}
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 (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 = std::nullopt;
m_pauseTime = std::nullopt;
m_requestedStartTime = 0;
m_nextIterationDuration = std::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 = std::nullopt;
m_pauseTime = std::nullopt;
m_requestedStartTime = 0;
m_nextIterationDuration = std::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) {
endAnimation();
updateStateMachine(AnimationStateInput::StartTimeSet, beginAnimationUpdateTime());
}
return;
}
if (input == AnimationStateInput::ResumeOverride) {
if (m_animationState == AnimationState::Looping || m_animationState == AnimationState::Ending) {
startAnimation(beginAnimationUpdateTime() - m_startTime.value_or(0));
}
return;
}
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 {
LOG(Animations, "%p AnimationState %s -> AnimationState::PausedNew", this, nameForState(m_animationState));
m_animationState = AnimationState::PausedNew;
m_pauseTime = std::nullopt;
}
#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);
LOG(Animations, "%p AnimationState %s -> StartWaitStyleAvailable (time is %f)", this, nameForState(m_animationState), param);
m_animationState = AnimationState::StartWaitStyleAvailable;
m_compositeAnimation->animationController().addToAnimationsWaitingForStyle(this);
if (m_object && m_object->element())
m_compositeAnimation->animationController().addElementChangeToDispatch(*m_object->element());
} else {
ASSERT(!paused());
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) {
LOG(Animations, "%p AnimationState %s -> StartWaitResponse (time is %f)", this, nameForState(m_animationState), param);
m_animationState = AnimationState::StartWaitResponse;
overrideAnimations();
if (overridden()) {
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 (m_animation->delay() < 0)
timeOffset = -m_animation->delay();
bool started = startAnimation(timeOffset);
m_compositeAnimation->animationController().addToAnimationsWaitingForStartTimeResponse(this, started);
m_isAccelerated = started;
}
} else {
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); LOG(Animations, "%p AnimationState %s -> StartTimeSet (time is %f)", this, nameForState(m_animationState), param);
if (!m_startTime) {
m_startTime = param;
if (m_animation->delay() < 0)
m_startTime = m_startTime.value() + m_animation->delay();
}
onAnimationStart(0);
goIntoEndingOrLoopingState();
if (m_object && m_object->element())
m_compositeAnimation->animationController().addElementChangeToDispatch(*m_object->element());
} else {
m_pauseTime = beginAnimationUpdateTime();
pauseAnimation(beginAnimationUpdateTime() - m_startTime.value_or(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);
onAnimationIteration(param);
goIntoEndingOrLoopingState();
} else {
m_pauseTime = beginAnimationUpdateTime();
pauseAnimation(beginAnimationUpdateTime() - m_startTime.value_or(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);
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();
if (m_object->element())
m_compositeAnimation->animationController().addElementChangeToDispatch(*m_object->element());
}
} else {
m_pauseTime = beginAnimationUpdateTime();
pauseAnimation(beginAnimationUpdateTime() - m_startTime.value_or(0));
LOG(Animations, "%p AnimationState %s -> PausedRun", this, nameForState(m_animationState));
m_animationState = AnimationState::PausedRun;
}
break;
case AnimationState::PausedWaitTimer:
ASSERT(input == AnimationStateInput::PlayStateRunning);
ASSERT(paused());
m_startTime = m_startTime.value() + beginAnimationUpdateTime() - m_pauseTime.value_or(0);
m_pauseTime = std::nullopt;
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:
ASSERT(input == AnimationStateInput::PlayStatePaused || input == AnimationStateInput::PlayStateRunning || input == AnimationStateInput::StartTimeSet || input == AnimationStateInput::StyleAvailable || input == AnimationStateInput::StartAnimation);
ASSERT(paused());
if (input == AnimationStateInput::PlayStateRunning) {
if (m_animationState == AnimationState::PausedNew) {
LOG(Animations, "%p AnimationState %s -> AnimationState::New", this, nameForState(m_animationState));
m_animationState = AnimationState::New;
m_pauseTime = std::nullopt;
updateStateMachine(input, param);
break;
}
if (m_animationState == AnimationState::PausedRun)
m_startTime = m_startTime.value() + beginAnimationUpdateTime() - m_pauseTime.value_or(0);
else
m_startTime = 0;
m_pauseTime = std::nullopt;
if (m_animationState == AnimationState::PausedWaitStyleAvailable) {
LOG(Animations, "%p AnimationState %s -> StartWaitStyleAvailable", this, nameForState(m_animationState));
m_animationState = AnimationState::StartWaitStyleAvailable;
} else {
LOG(Animations, "%p AnimationState %s -> StartWaitResponse", this, nameForState(m_animationState));
m_animationState = AnimationState::StartWaitResponse;
if (overridden()) {
updateStateMachine(AnimationStateInput::StartTimeSet, beginAnimationUpdateTime());
m_isAccelerated = true;
} else {
bool started = startAnimation(beginAnimationUpdateTime() - m_startTime.value_or(0));
m_compositeAnimation->animationController().addToAnimationsWaitingForStartTimeResponse(this, started);
m_isAccelerated = started;
}
}
break;
}
if (input == AnimationStateInput::StartTimeSet) {
ASSERT(m_animationState == AnimationState::PausedWaitResponse);
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.value_or(0) + param;
break;
}
ASSERT(m_animationState == AnimationState::PausedNew || m_animationState == AnimationState::PausedWaitStyleAvailable);
LOG(Animations, "%p AnimationState %s -> PausedWaitResponse", this, nameForState(m_animationState));
m_animationState = AnimationState::PausedWaitResponse;
overrideAnimations();
break;
case AnimationState::FillingForwards:
case AnimationState::Done:
break;
}
}
void AnimationBase::fireAnimationEventsIfNeeded()
{
if (!m_compositeAnimation)
return;
if (m_animationState != AnimationState::StartWaitTimer && m_animationState != AnimationState::Looping && m_animationState != AnimationState::Ending)
return;
Ref<AnimationBase> protectedThis(*this);
Ref<CompositeAnimation> protectCompositeAnimation(*m_compositeAnimation);
if (m_animationState == AnimationState::StartWaitTimer) {
#if ENABLE(CSS_ANIMATIONS_LEVEL_2)
if (m_animation->trigger() && m_animation->trigger()->isScrollAnimationTrigger()) {
if (m_object) {
float offset = m_compositeAnimation->animationController().scrollPosition();
auto& scrollTrigger = downcast<ScrollAnimationTrigger>(*m_animation->trigger());
if (offset > scrollTrigger.startValue().value())
updateStateMachine(AnimationStateInput::StartTimerFired, 0);
}
return;
}
#endif
if (beginAnimationUpdateTime() - m_requestedStartTime >= m_animation->delay())
updateStateMachine(AnimationStateInput::StartTimerFired, 0);
return;
}
double elapsedDuration = beginAnimationUpdateTime() - m_startTime.value_or(0);
#if ENABLE(CSS_ANIMATIONS_LEVEL_2)
if (m_animation->trigger() && m_animation->trigger()->isScrollAnimationTrigger())
elapsedDuration = getElapsedTime();
#endif
elapsedDuration = std::max(elapsedDuration, 0.0);
if (m_totalDuration && elapsedDuration >= m_totalDuration.value()) {
LOG(Animations, "%p AnimationState %s -> Ending", this, nameForState(m_animationState));
m_animationState = AnimationState::Ending;
updateStateMachine(AnimationStateInput::EndTimerFired, m_totalDuration.value());
} else {
if (!m_nextIterationDuration) {
double durationLeft = m_animation->duration() - fmod(elapsedDuration, m_animation->duration());
m_nextIterationDuration = elapsedDuration + durationLeft;
}
if (elapsedDuration >= m_nextIterationDuration) {
double previous = m_nextIterationDuration.value();
double durationLeft = m_animation->duration() - fmod(elapsedDuration, m_animation->duration());
m_nextIterationDuration = elapsedDuration + durationLeft;
updateStateMachine(AnimationStateInput::LoopTimerFired, previous);
}
}
}
void AnimationBase::updatePlayState(EAnimPlayState playState)
{
if (!m_compositeAnimation)
return;
bool pause = playState == AnimPlayStatePaused || m_compositeAnimation->isSuspended();
if (pause == paused() && !isNew())
return;
updateStateMachine(pause ? AnimationStateInput::PlayStatePaused : AnimationStateInput::PlayStateRunning, -1);
}
std::optional<Seconds> AnimationBase::timeToNextService()
{
if (paused() || isNew() || postActive() || fillingForwards())
return std::nullopt;
if (m_animationState == AnimationState::StartWaitTimer) {
#if ENABLE(CSS_ANIMATIONS_LEVEL_2)
if (m_animation->trigger()->isScrollAnimationTrigger()) {
if (m_object) {
float currentScrollPosition = m_object->view().frameView().scrollPositionForFixedPosition().y().toFloat();
auto& scrollTrigger = downcast<ScrollAnimationTrigger>(*m_animation->trigger());
if (currentScrollPosition >= scrollTrigger.startValue().value() && (!scrollTrigger.hasEndValue() || currentScrollPosition <= scrollTrigger.endValue().value()))
return 0_s;
}
return std::nullopt;
}
#endif
double timeFromNow = m_animation->delay() - (beginAnimationUpdateTime() - m_requestedStartTime);
return std::max(Seconds { timeFromNow }, 0_s);
}
fireAnimationEventsIfNeeded();
return 0_s;
}
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 = 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();
switch (timingFunction->type()) {
case TimingFunction::CubicBezierFunction: {
auto& function = *static_cast<const CubicBezierTimingFunction*>(timingFunction);
return solveCubicBezierFunction(function.x1(), function.y1(), function.x2(), function.y2(), fractionalTime, m_animation->duration());
}
case TimingFunction::StepsFunction: {
auto& function = *static_cast<const StepsTimingFunction*>(timingFunction);
return solveStepsFunction(function.numberOfSteps(), function.stepAtStart(), fractionalTime);
}
case TimingFunction::SpringFunction: {
auto& function = *static_cast<const SpringTimingFunction*>(timingFunction);
return solveSpringFunction(function.mass(), function.stiffness(), function.damping(), function.initialVelocity(), fractionalTime, m_animation->duration());
}
case TimingFunction::LinearFunction:
return fractionalTime;
}
ASSERT_NOT_REACHED();
return 0;
}
void AnimationBase::getTimeToNextEvent(Seconds& time, bool& isLooping) const
{
const double elapsedDuration = std::max(beginAnimationUpdateTime() - m_startTime.value_or(0), 0.0);
double durationLeft = 0;
double nextIterationTime = m_totalDuration.value_or(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()) {
ASSERT(nextIterationTime > 0);
isLooping = true;
} else {
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) {
LOG(Animations, "%p AnimationState %s -> StartWaitResponse", this, nameForState(m_animationState));
m_animationState = AnimationState::StartWaitResponse;
onAnimationStartResponse(monotonicallyIncreasingTime());
}
ASSERT(m_startTime); if (t <= m_animation->delay())
m_pauseTime = m_startTime.value_or(0);
else
m_pauseTime = m_startTime.value_or(0) + t - m_animation->delay();
if (m_object && m_object->isComposited())
downcast<RenderBoxModelObject>(*m_object).suspendAnimations(m_pauseTime.value());
}
double AnimationBase::beginAnimationUpdateTime() const
{
if (!m_compositeAnimation)
return 0;
return m_compositeAnimation->animationController().beginAnimationUpdateTime();
}
double AnimationBase::getElapsedTime() const
{
#if ENABLE(CSS_ANIMATIONS_LEVEL_2)
if (m_animation->trigger() && m_animation->trigger()->isScrollAnimationTrigger()) {
auto& scrollTrigger = downcast<ScrollAnimationTrigger>(*m_animation->trigger());
if (scrollTrigger.hasEndValue() && m_object) {
float offset = m_compositeAnimation->animationController().scrollPosition();
float startValue = scrollTrigger.startValue().value();
if (offset < startValue)
return 0;
float endValue = scrollTrigger.endValue().value();
if (offset > endValue)
return m_animation->duration();
return m_animation->duration() * (offset - startValue) / (endValue - startValue);
}
}
#endif
if (paused()) {
double delayOffset = (!m_startTime && m_animation->delay() < 0) ? m_animation->delay() : 0;
return m_pauseTime.value_or(0) - m_startTime.value_or(0) - delayOffset;
}
if (!m_startTime)
return 0;
if (postActive() || fillingForwards())
return m_totalDuration.value_or(0);
return beginAnimationUpdateTime() - m_startTime.value_or(0);
}
void AnimationBase::setElapsedTime(double time)
{
UNUSED_PARAM(time);
}
void AnimationBase::play()
{
}
void AnimationBase::pause()
{
}
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()));
floatBounds.moveBy(-transformOrigin);
}
for (const auto& operation : style.transform().operations()) {
if (operation->type() == TransformOperation::ROTATE) {
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);
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);
if (fromDecomp.angle)
return false;
bounds = LayoutRect(transform.mapRect(bounds));
return true;
}
}