| 1 | /* |
| 2 | * Copyright (C) 2007, 2008, 2009 Apple Inc. All rights reserved. |
| 3 | * |
| 4 | * Redistribution and use in source and binary forms, with or without |
| 5 | * modification, are permitted provided that the following conditions |
| 6 | * are met: |
| 7 | * |
| 8 | * 1. Redistributions of source code must retain the above copyright |
| 9 | * notice, this list of conditions and the following disclaimer. |
| 10 | * 2. Redistributions in binary form must reproduce the above copyright |
| 11 | * notice, this list of conditions and the following disclaimer in the |
| 12 | * documentation and/or other materials provided with the distribution. |
| 13 | * 3. Neither the name of Apple Inc. ("Apple") nor the names of |
| 14 | * its contributors may be used to endorse or promote products derived |
| 15 | * from this software without specific prior written permission. |
| 16 | * |
| 17 | * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY |
| 18 | * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED |
| 19 | * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE |
| 20 | * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY |
| 21 | * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES |
| 22 | * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| 23 | * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND |
| 24 | * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
| 25 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF |
| 26 | * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| 27 | */ |
| 28 | |
| 29 | #include "config.h" |
| 30 | #include "AnimationBase.h" |
| 31 | |
| 32 | #include "CSSAnimationControllerPrivate.h" |
| 33 | #include "CSSPrimitiveValue.h" |
| 34 | #include "CSSPropertyAnimation.h" |
| 35 | #include "CompositeAnimation.h" |
| 36 | #include "Document.h" |
| 37 | #include "FloatConversion.h" |
| 38 | #include "GeometryUtilities.h" |
| 39 | #include "Logging.h" |
| 40 | #include "RenderBox.h" |
| 41 | #include "RenderStyle.h" |
| 42 | #include "RenderView.h" |
| 43 | #include <algorithm> |
| 44 | #include <wtf/Ref.h> |
| 45 | |
| 46 | namespace WebCore { |
| 47 | |
| 48 | AnimationBase::AnimationBase(const Animation& animation, Element& element, CompositeAnimation& compositeAnimation) |
| 49 | : m_element(&element) |
| 50 | , m_compositeAnimation(&compositeAnimation) |
| 51 | , m_animation(const_cast<Animation&>(animation)) |
| 52 | { |
| 53 | // Compute the total duration |
| 54 | if (m_animation->iterationCount() > 0) |
| 55 | m_totalDuration = m_animation->duration() * m_animation->iterationCount(); |
| 56 | } |
| 57 | |
| 58 | AnimationBase::~AnimationBase() = default; |
| 59 | |
| 60 | const RenderStyle& AnimationBase::currentStyle() const |
| 61 | { |
| 62 | if (auto* renderer = this->renderer()) |
| 63 | return renderer->style(); |
| 64 | return unanimatedStyle(); |
| 65 | } |
| 66 | |
| 67 | RenderElement* AnimationBase::renderer() const |
| 68 | { |
| 69 | return m_element ? m_element->renderer() : nullptr; |
| 70 | } |
| 71 | |
| 72 | void AnimationBase::clear() |
| 73 | { |
| 74 | endAnimation(); |
| 75 | m_element = nullptr; |
| 76 | m_compositeAnimation = nullptr; |
| 77 | } |
| 78 | |
| 79 | void AnimationBase::setNeedsStyleRecalc(Element* element) |
| 80 | { |
| 81 | if (!element || element->document().renderTreeBeingDestroyed()) |
| 82 | return; |
| 83 | |
| 84 | ASSERT(element->document().pageCacheState() == Document::NotInPageCache); |
| 85 | element->invalidateStyle(); |
| 86 | } |
| 87 | |
| 88 | double AnimationBase::duration() const |
| 89 | { |
| 90 | return m_animation->duration(); |
| 91 | } |
| 92 | |
| 93 | bool AnimationBase::playStatePlaying() const |
| 94 | { |
| 95 | return m_animation->playState() == AnimationPlayState::Playing; |
| 96 | } |
| 97 | |
| 98 | bool AnimationBase::animationsMatch(const Animation& animation) const |
| 99 | { |
| 100 | return m_animation->animationsMatch(animation); |
| 101 | } |
| 102 | |
| 103 | #if !LOG_DISABLED |
| 104 | static const char* nameForState(AnimationBase::AnimationState state) |
| 105 | { |
| 106 | switch (state) { |
| 107 | case AnimationBase::AnimationState::New: return "New" ; |
| 108 | case AnimationBase::AnimationState::StartWaitTimer: return "StartWaitTimer" ; |
| 109 | case AnimationBase::AnimationState::StartWaitStyleAvailable: return "StartWaitStyleAvailable" ; |
| 110 | case AnimationBase::AnimationState::StartWaitResponse: return "StartWaitResponse" ; |
| 111 | case AnimationBase::AnimationState::Looping: return "Looping" ; |
| 112 | case AnimationBase::AnimationState::Ending: return "Ending" ; |
| 113 | case AnimationBase::AnimationState::PausedNew: return "PausedNew" ; |
| 114 | case AnimationBase::AnimationState::PausedWaitTimer: return "PausedWaitTimer" ; |
| 115 | case AnimationBase::AnimationState::PausedWaitStyleAvailable: return "PausedWaitStyleAvailable" ; |
| 116 | case AnimationBase::AnimationState::PausedWaitResponse: return "PausedWaitResponse" ; |
| 117 | case AnimationBase::AnimationState::PausedRun: return "PausedRun" ; |
| 118 | case AnimationBase::AnimationState::Done: return "Done" ; |
| 119 | case AnimationBase::AnimationState::FillingForwards: return "FillingForwards" ; |
| 120 | } |
| 121 | return "" ; |
| 122 | } |
| 123 | |
| 124 | static const char* nameForStateInput(AnimationBase::AnimationStateInput input) |
| 125 | { |
| 126 | switch (input) { |
| 127 | case AnimationBase::AnimationStateInput::MakeNew: return "MakeNew" ; |
| 128 | case AnimationBase::AnimationStateInput::StartAnimation: return "StartAnimation" ; |
| 129 | case AnimationBase::AnimationStateInput::RestartAnimation: return "RestartAnimation" ; |
| 130 | case AnimationBase::AnimationStateInput::StartTimerFired: return "StartTimerFired" ; |
| 131 | case AnimationBase::AnimationStateInput::StyleAvailable: return "StyleAvailable" ; |
| 132 | case AnimationBase::AnimationStateInput::StartTimeSet: return "StartTimeSet" ; |
| 133 | case AnimationBase::AnimationStateInput::LoopTimerFired: return "LoopTimerFired" ; |
| 134 | case AnimationBase::AnimationStateInput::EndTimerFired: return "EndTimerFired" ; |
| 135 | case AnimationBase::AnimationStateInput::PauseOverride: return "PauseOverride" ; |
| 136 | case AnimationBase::AnimationStateInput::ResumeOverride: return "ResumeOverride" ; |
| 137 | case AnimationBase::AnimationStateInput::PlayStateRunning: return "PlayStateRunning" ; |
| 138 | case AnimationBase::AnimationStateInput::PlayStatePaused: return "PlayStatePaused" ; |
| 139 | case AnimationBase::AnimationStateInput::EndAnimation: return "EndAnimation" ; |
| 140 | } |
| 141 | return "" ; |
| 142 | } |
| 143 | #endif |
| 144 | |
| 145 | void AnimationBase::updateStateMachine(AnimationStateInput input, double param) |
| 146 | { |
| 147 | if (!m_compositeAnimation) |
| 148 | return; |
| 149 | |
| 150 | // If we get AnimationStateInput::RestartAnimation then we force a new animation, regardless of state. |
| 151 | if (input == AnimationStateInput::MakeNew) { |
| 152 | if (m_animationState == AnimationState::StartWaitStyleAvailable) |
| 153 | m_compositeAnimation->animationController().removeFromAnimationsWaitingForStyle(*this); |
| 154 | LOG(Animations, "%p AnimationState %s -> New" , this, nameForState(m_animationState)); |
| 155 | m_animationState = AnimationState::New; |
| 156 | m_startTime = WTF::nullopt; |
| 157 | m_pauseTime = WTF::nullopt; |
| 158 | m_requestedStartTime = 0; |
| 159 | m_nextIterationDuration = WTF::nullopt; |
| 160 | endAnimation(); |
| 161 | return; |
| 162 | } |
| 163 | |
| 164 | if (input == AnimationStateInput::RestartAnimation) { |
| 165 | if (m_animationState == AnimationState::StartWaitStyleAvailable) |
| 166 | m_compositeAnimation->animationController().removeFromAnimationsWaitingForStyle(*this); |
| 167 | LOG(Animations, "%p AnimationState %s -> New" , this, nameForState(m_animationState)); |
| 168 | m_animationState = AnimationState::New; |
| 169 | m_startTime = WTF::nullopt; |
| 170 | m_pauseTime = WTF::nullopt; |
| 171 | m_requestedStartTime = 0; |
| 172 | m_nextIterationDuration = WTF::nullopt; |
| 173 | endAnimation(); |
| 174 | |
| 175 | if (!paused()) |
| 176 | updateStateMachine(AnimationStateInput::StartAnimation, -1); |
| 177 | return; |
| 178 | } |
| 179 | |
| 180 | if (input == AnimationStateInput::EndAnimation) { |
| 181 | if (m_animationState == AnimationState::StartWaitStyleAvailable) |
| 182 | m_compositeAnimation->animationController().removeFromAnimationsWaitingForStyle(*this); |
| 183 | LOG(Animations, "%p AnimationState %s -> Done" , this, nameForState(m_animationState)); |
| 184 | m_animationState = AnimationState::Done; |
| 185 | endAnimation(); |
| 186 | return; |
| 187 | } |
| 188 | |
| 189 | if (input == AnimationStateInput::PauseOverride) { |
| 190 | if (m_animationState == AnimationState::StartWaitResponse) { |
| 191 | // If we are in AnimationState::StartWaitResponse, the animation will get canceled before |
| 192 | // we get a response, so move to the next state. |
| 193 | endAnimation(); |
| 194 | updateStateMachine(AnimationStateInput::StartTimeSet, beginAnimationUpdateTime()); |
| 195 | } |
| 196 | return; |
| 197 | } |
| 198 | |
| 199 | if (input == AnimationStateInput::ResumeOverride) { |
| 200 | if (m_animationState == AnimationState::Looping || m_animationState == AnimationState::Ending) { |
| 201 | // Start the animation |
| 202 | startAnimation(beginAnimationUpdateTime() - m_startTime.valueOr(0)); |
| 203 | } |
| 204 | return; |
| 205 | } |
| 206 | |
| 207 | // Execute state machine |
| 208 | switch (m_animationState) { |
| 209 | case AnimationState::New: |
| 210 | ASSERT(input == AnimationStateInput::StartAnimation || input == AnimationStateInput::PlayStateRunning || input == AnimationStateInput::PlayStatePaused); |
| 211 | |
| 212 | if (input == AnimationStateInput::StartAnimation || input == AnimationStateInput::PlayStateRunning) { |
| 213 | m_requestedStartTime = beginAnimationUpdateTime(); |
| 214 | LOG(Animations, "%p AnimationState %s -> StartWaitTimer" , this, nameForState(m_animationState)); |
| 215 | m_animationState = AnimationState::StartWaitTimer; |
| 216 | } else { |
| 217 | // We are pausing before we even started. |
| 218 | LOG(Animations, "%p AnimationState %s -> AnimationState::PausedNew" , this, nameForState(m_animationState)); |
| 219 | m_animationState = AnimationState::PausedNew; |
| 220 | m_pauseTime = WTF::nullopt; |
| 221 | } |
| 222 | |
| 223 | break; |
| 224 | case AnimationState::StartWaitTimer: |
| 225 | ASSERT(input == AnimationStateInput::StartTimerFired || input == AnimationStateInput::PlayStatePaused); |
| 226 | |
| 227 | if (input == AnimationStateInput::StartTimerFired) { |
| 228 | ASSERT(param >= 0); |
| 229 | // Start timer has fired, tell the animation to start and wait for it to respond with start time |
| 230 | LOG(Animations, "%p AnimationState %s -> StartWaitStyleAvailable (time is %f)" , this, nameForState(m_animationState), param); |
| 231 | m_animationState = AnimationState::StartWaitStyleAvailable; |
| 232 | m_compositeAnimation->animationController().addToAnimationsWaitingForStyle(*this); |
| 233 | |
| 234 | // Trigger a render so we can start the animation |
| 235 | if (m_element) |
| 236 | m_compositeAnimation->animationController().addElementChangeToDispatch(*m_element); |
| 237 | } else { |
| 238 | ASSERT(!paused()); |
| 239 | // We're waiting for the start timer to fire and we got a pause. Cancel the timer, pause and wait |
| 240 | m_pauseTime = beginAnimationUpdateTime(); |
| 241 | LOG(Animations, "%p AnimationState %s -> PausedWaitTimer" , this, nameForState(m_animationState)); |
| 242 | m_animationState = AnimationState::PausedWaitTimer; |
| 243 | } |
| 244 | break; |
| 245 | case AnimationState::StartWaitStyleAvailable: |
| 246 | ASSERT(input == AnimationStateInput::StyleAvailable || input == AnimationStateInput::PlayStatePaused); |
| 247 | |
| 248 | if (input == AnimationStateInput::StyleAvailable) { |
| 249 | // Start timer has fired, tell the animation to start and wait for it to respond with start time |
| 250 | LOG(Animations, "%p AnimationState %s -> StartWaitResponse (time is %f)" , this, nameForState(m_animationState), param); |
| 251 | m_animationState = AnimationState::StartWaitResponse; |
| 252 | |
| 253 | overrideAnimations(); |
| 254 | |
| 255 | // Start the animation |
| 256 | if (overridden()) { |
| 257 | // We won't try to start accelerated animations if we are overridden and |
| 258 | // just move on to the next state. |
| 259 | LOG(Animations, "%p AnimationState %s -> StartWaitResponse" , this, nameForState(m_animationState)); |
| 260 | m_animationState = AnimationState::StartWaitResponse; |
| 261 | m_isAccelerated = false; |
| 262 | updateStateMachine(AnimationStateInput::StartTimeSet, beginAnimationUpdateTime()); |
| 263 | } else { |
| 264 | double timeOffset = 0; |
| 265 | // If the value for 'animation-delay' is negative then the animation appears to have started in the past. |
| 266 | if (m_animation->delay() < 0) |
| 267 | timeOffset = -m_animation->delay(); |
| 268 | bool started = startAnimation(timeOffset); |
| 269 | |
| 270 | m_compositeAnimation->animationController().addToAnimationsWaitingForStartTimeResponse(*this, started); |
| 271 | m_isAccelerated = started; |
| 272 | } |
| 273 | } else { |
| 274 | // We're waiting for the style to be available and we got a pause. Pause and wait |
| 275 | m_pauseTime = beginAnimationUpdateTime(); |
| 276 | LOG(Animations, "%p AnimationState %s -> PausedWaitStyleAvailable" , this, nameForState(m_animationState)); |
| 277 | m_animationState = AnimationState::PausedWaitStyleAvailable; |
| 278 | } |
| 279 | break; |
| 280 | case AnimationState::StartWaitResponse: |
| 281 | ASSERT(input == AnimationStateInput::StartTimeSet || input == AnimationStateInput::PlayStatePaused); |
| 282 | |
| 283 | if (input == AnimationStateInput::StartTimeSet) { |
| 284 | ASSERT(param > -0.001); // Sometimes Core Animation gives us a beginTime slightly into the future. |
| 285 | LOG(Animations, "%p AnimationState %s -> StartTimeSet (time is %f)" , this, nameForState(m_animationState), param); |
| 286 | |
| 287 | // We have a start time, set it, unless the startTime is already set |
| 288 | if (!m_startTime) { |
| 289 | m_startTime = param; |
| 290 | // If the value for 'animation-delay' is negative then the animation appears to have started in the past. |
| 291 | if (m_animation->delay() < 0) |
| 292 | m_startTime = m_startTime.value() + m_animation->delay(); |
| 293 | } |
| 294 | |
| 295 | // Now that we know the start time, fire the start event. |
| 296 | onAnimationStart(0); // The elapsedTime is 0. |
| 297 | |
| 298 | // Decide whether to go into looping or ending state |
| 299 | goIntoEndingOrLoopingState(); |
| 300 | |
| 301 | // Dispatch updateStyleIfNeeded so we can start the animation |
| 302 | if (m_element) |
| 303 | m_compositeAnimation->animationController().addElementChangeToDispatch(*m_element); |
| 304 | } else { |
| 305 | // We are pausing while waiting for a start response. Cancel the animation and wait. When |
| 306 | // we unpause, we will act as though the start timer just fired |
| 307 | m_pauseTime = beginAnimationUpdateTime(); |
| 308 | pauseAnimation(beginAnimationUpdateTime() - m_startTime.valueOr(0)); |
| 309 | LOG(Animations, "%p AnimationState %s -> PausedWaitResponse" , this, nameForState(m_animationState)); |
| 310 | m_animationState = AnimationState::PausedWaitResponse; |
| 311 | } |
| 312 | break; |
| 313 | case AnimationState::Looping: |
| 314 | ASSERT(input == AnimationStateInput::LoopTimerFired || input == AnimationStateInput::PlayStatePaused); |
| 315 | |
| 316 | if (input == AnimationStateInput::LoopTimerFired) { |
| 317 | ASSERT(param >= 0); |
| 318 | LOG(Animations, "%p AnimationState %s -> LoopTimerFired (time is %f)" , this, nameForState(m_animationState), param); |
| 319 | |
| 320 | // Loop timer fired, loop again or end. |
| 321 | onAnimationIteration(param); |
| 322 | |
| 323 | // Decide whether to go into looping or ending state |
| 324 | goIntoEndingOrLoopingState(); |
| 325 | } else { |
| 326 | // We are pausing while running. Cancel the animation and wait |
| 327 | m_pauseTime = beginAnimationUpdateTime(); |
| 328 | pauseAnimation(beginAnimationUpdateTime() - m_startTime.valueOr(0)); |
| 329 | LOG(Animations, "%p AnimationState %s -> PausedRun" , this, nameForState(m_animationState)); |
| 330 | m_animationState = AnimationState::PausedRun; |
| 331 | } |
| 332 | break; |
| 333 | case AnimationState::Ending: |
| 334 | #if !LOG_DISABLED |
| 335 | if (input != AnimationStateInput::EndTimerFired && input != AnimationStateInput::PlayStatePaused) |
| 336 | LOG_ERROR("State is AnimationState::Ending, but input is not AnimationStateInput::EndTimerFired or AnimationStateInput::PlayStatePaused. It is %s." , nameForStateInput(input)); |
| 337 | #endif |
| 338 | if (input == AnimationStateInput::EndTimerFired) { |
| 339 | ASSERT(param >= 0); |
| 340 | // End timer fired, finish up |
| 341 | onAnimationEnd(param); |
| 342 | |
| 343 | LOG(Animations, "%p AnimationState %s -> Done (time is %f)" , this, nameForState(m_animationState), param); |
| 344 | m_animationState = AnimationState::Done; |
| 345 | |
| 346 | if (m_element) { |
| 347 | if (m_animation->fillsForwards()) { |
| 348 | LOG(Animations, "%p AnimationState %s -> FillingForwards" , this, nameForState(m_animationState)); |
| 349 | m_animationState = AnimationState::FillingForwards; |
| 350 | } else |
| 351 | resumeOverriddenAnimations(); |
| 352 | |
| 353 | // Fire off another style change so we can set the final value |
| 354 | if (m_element) |
| 355 | m_compositeAnimation->animationController().addElementChangeToDispatch(*m_element); |
| 356 | } |
| 357 | } else { |
| 358 | // We are pausing while running. Cancel the animation and wait |
| 359 | m_pauseTime = beginAnimationUpdateTime(); |
| 360 | pauseAnimation(beginAnimationUpdateTime() - m_startTime.valueOr(0)); |
| 361 | LOG(Animations, "%p AnimationState %s -> PausedRun" , this, nameForState(m_animationState)); |
| 362 | m_animationState = AnimationState::PausedRun; |
| 363 | } |
| 364 | // |this| may be deleted here |
| 365 | break; |
| 366 | case AnimationState::PausedWaitTimer: |
| 367 | ASSERT(input == AnimationStateInput::PlayStateRunning); |
| 368 | ASSERT(paused()); |
| 369 | // Update the times |
| 370 | m_startTime = m_startTime.valueOr(0) + beginAnimationUpdateTime() - m_pauseTime.valueOr(0); |
| 371 | m_pauseTime = WTF::nullopt; |
| 372 | |
| 373 | // we were waiting for the start timer to fire, go back and wait again |
| 374 | LOG(Animations, "%p AnimationState %s -> New" , this, nameForState(m_animationState)); |
| 375 | m_animationState = AnimationState::New; |
| 376 | updateStateMachine(AnimationStateInput::StartAnimation, 0); |
| 377 | break; |
| 378 | case AnimationState::PausedNew: |
| 379 | case AnimationState::PausedWaitResponse: |
| 380 | case AnimationState::PausedWaitStyleAvailable: |
| 381 | case AnimationState::PausedRun: |
| 382 | // We treat these two cases the same. The only difference is that, when we are in |
| 383 | // AnimationState::PausedWaitResponse, we don't yet have a valid startTime, so we send 0 to startAnimation. |
| 384 | // When the AnimationStateInput::StartTimeSet comes in and we were in AnimationState::PausedRun, we will notice |
| 385 | // that we have already set the startTime and will ignore it. |
| 386 | ASSERT(input == AnimationStateInput::PlayStatePaused || input == AnimationStateInput::PlayStateRunning || input == AnimationStateInput::StartTimeSet || input == AnimationStateInput::StyleAvailable); |
| 387 | ASSERT(paused()); |
| 388 | |
| 389 | if (input == AnimationStateInput::PlayStateRunning) { |
| 390 | if (m_animationState == AnimationState::PausedNew) { |
| 391 | // We were paused before we even started, and now we're supposed |
| 392 | // to start, so jump back to the New state and reset. |
| 393 | LOG(Animations, "%p AnimationState %s -> AnimationState::New" , this, nameForState(m_animationState)); |
| 394 | m_animationState = AnimationState::New; |
| 395 | m_pauseTime = WTF::nullopt; |
| 396 | updateStateMachine(input, param); |
| 397 | break; |
| 398 | } |
| 399 | |
| 400 | // Update the times |
| 401 | if (m_animationState == AnimationState::PausedRun) |
| 402 | m_startTime = m_startTime.valueOr(0) + beginAnimationUpdateTime() - m_pauseTime.valueOr(0); |
| 403 | else |
| 404 | m_startTime = 0; |
| 405 | |
| 406 | m_pauseTime = WTF::nullopt; |
| 407 | |
| 408 | if (m_animationState == AnimationState::PausedWaitStyleAvailable) { |
| 409 | LOG(Animations, "%p AnimationState %s -> StartWaitStyleAvailable" , this, nameForState(m_animationState)); |
| 410 | m_animationState = AnimationState::StartWaitStyleAvailable; |
| 411 | } else { |
| 412 | // We were either running or waiting for a begin time response from the animation. |
| 413 | // Either way we need to restart the animation (possibly with an offset if we |
| 414 | // had already been running) and wait for it to start. |
| 415 | LOG(Animations, "%p AnimationState %s -> StartWaitResponse" , this, nameForState(m_animationState)); |
| 416 | m_animationState = AnimationState::StartWaitResponse; |
| 417 | |
| 418 | // Start the animation |
| 419 | if (overridden()) { |
| 420 | // We won't try to start accelerated animations if we are overridden and |
| 421 | // just move on to the next state. |
| 422 | updateStateMachine(AnimationStateInput::StartTimeSet, beginAnimationUpdateTime()); |
| 423 | m_isAccelerated = true; |
| 424 | } else { |
| 425 | bool started = startAnimation(beginAnimationUpdateTime() - m_startTime.valueOr(0)); |
| 426 | m_compositeAnimation->animationController().addToAnimationsWaitingForStartTimeResponse(*this, started); |
| 427 | m_isAccelerated = started; |
| 428 | } |
| 429 | } |
| 430 | break; |
| 431 | } |
| 432 | |
| 433 | if (input == AnimationStateInput::StartTimeSet) { |
| 434 | ASSERT(m_animationState == AnimationState::PausedWaitResponse); |
| 435 | |
| 436 | // We are paused but we got the callback that notifies us that an accelerated animation started. |
| 437 | // We ignore the start time and just move into the paused-run state. |
| 438 | LOG(Animations, "%p AnimationState %s -> PausedRun (time is %f)" , this, nameForState(m_animationState), param); |
| 439 | m_animationState = AnimationState::PausedRun; |
| 440 | ASSERT(!m_startTime); |
| 441 | m_startTime = param; |
| 442 | m_pauseTime = m_pauseTime.valueOr(0) + param; |
| 443 | break; |
| 444 | } |
| 445 | |
| 446 | ASSERT(m_animationState == AnimationState::PausedNew || m_animationState == AnimationState::PausedWaitStyleAvailable); |
| 447 | |
| 448 | if (input == AnimationStateInput::PlayStatePaused) |
| 449 | break; |
| 450 | |
| 451 | ASSERT(input == AnimationStateInput::StyleAvailable); |
| 452 | |
| 453 | // We are paused but we got the callback that notifies us that style has been updated. |
| 454 | // We move to the AnimationState::PausedWaitResponse state |
| 455 | LOG(Animations, "%p AnimationState %s -> PausedWaitResponse" , this, nameForState(m_animationState)); |
| 456 | m_animationState = AnimationState::PausedWaitResponse; |
| 457 | overrideAnimations(); |
| 458 | break; |
| 459 | case AnimationState::FillingForwards: |
| 460 | case AnimationState::Done: |
| 461 | // We're done. Stay in this state until we are deleted |
| 462 | break; |
| 463 | } |
| 464 | } |
| 465 | |
| 466 | void AnimationBase::fireAnimationEventsIfNeeded() |
| 467 | { |
| 468 | if (!m_compositeAnimation) |
| 469 | return; |
| 470 | |
| 471 | // If we are waiting for the delay time to expire and it has, go to the next state |
| 472 | if (m_animationState != AnimationState::StartWaitTimer && m_animationState != AnimationState::Looping && m_animationState != AnimationState::Ending) |
| 473 | return; |
| 474 | |
| 475 | // We have to make sure to keep a ref to the this pointer, because it could get destroyed |
| 476 | // during an animation callback that might get called. Since the owner is a CompositeAnimation |
| 477 | // and it ref counts this object, we will keep a ref to that instead. That way the AnimationBase |
| 478 | // can still access the resources of its CompositeAnimation as needed. |
| 479 | Ref<AnimationBase> protectedThis(*this); |
| 480 | Ref<CompositeAnimation> protectCompositeAnimation(*m_compositeAnimation); |
| 481 | |
| 482 | // Check for start timeout |
| 483 | if (m_animationState == AnimationState::StartWaitTimer) { |
| 484 | if (beginAnimationUpdateTime() - m_requestedStartTime >= m_animation->delay()) |
| 485 | updateStateMachine(AnimationStateInput::StartTimerFired, 0); |
| 486 | return; |
| 487 | } |
| 488 | |
| 489 | double elapsedDuration = beginAnimationUpdateTime() - m_startTime.valueOr(0); |
| 490 | |
| 491 | // FIXME: we need to ensure that elapsedDuration is never < 0. If it is, this suggests that |
| 492 | // we had a recalcStyle() outside of beginAnimationUpdate()/endAnimationUpdate(). |
| 493 | // Also check in getTimeToNextEvent(). |
| 494 | elapsedDuration = std::max(elapsedDuration, 0.0); |
| 495 | |
| 496 | // Check for end timeout |
| 497 | if (m_totalDuration && elapsedDuration >= m_totalDuration.value()) { |
| 498 | // We may still be in AnimationState::Looping if we've managed to skip a |
| 499 | // whole iteration, in which case we should jump to the end state. |
| 500 | LOG(Animations, "%p AnimationState %s -> Ending" , this, nameForState(m_animationState)); |
| 501 | m_animationState = AnimationState::Ending; |
| 502 | |
| 503 | // Fire an end event |
| 504 | updateStateMachine(AnimationStateInput::EndTimerFired, m_totalDuration.value()); |
| 505 | } else { |
| 506 | // Check for iteration timeout |
| 507 | if (!m_nextIterationDuration) { |
| 508 | // Hasn't been set yet, set it |
| 509 | double durationLeft = m_animation->duration() - fmod(elapsedDuration, m_animation->duration()); |
| 510 | m_nextIterationDuration = elapsedDuration + durationLeft; |
| 511 | } |
| 512 | |
| 513 | if (elapsedDuration >= m_nextIterationDuration) { |
| 514 | // Set to the next iteration |
| 515 | double previous = m_nextIterationDuration.value(); |
| 516 | double durationLeft = m_animation->duration() - fmod(elapsedDuration, m_animation->duration()); |
| 517 | m_nextIterationDuration = elapsedDuration + durationLeft; |
| 518 | |
| 519 | // Send the event |
| 520 | updateStateMachine(AnimationStateInput::LoopTimerFired, previous); |
| 521 | } |
| 522 | } |
| 523 | } |
| 524 | |
| 525 | void AnimationBase::updatePlayState(AnimationPlayState playState) |
| 526 | { |
| 527 | if (!m_compositeAnimation) |
| 528 | return; |
| 529 | |
| 530 | // When we get here, we can have one of 4 desired states: running, paused, suspended, paused & suspended. |
| 531 | // The state machine can be in one of two states: running, paused. |
| 532 | // Set the state machine to the desired state. |
| 533 | bool pause = playState == AnimationPlayState::Paused || m_compositeAnimation->isSuspended(); |
| 534 | |
| 535 | if (pause == paused() && !isNew()) |
| 536 | return; |
| 537 | |
| 538 | updateStateMachine(pause ? AnimationStateInput::PlayStatePaused : AnimationStateInput::PlayStateRunning, -1); |
| 539 | } |
| 540 | |
| 541 | Optional<Seconds> AnimationBase::timeToNextService() |
| 542 | { |
| 543 | // Returns the time at which next service is required. WTF::nullopt means no service is required. 0 means |
| 544 | // service is required now, and > 0 means service is required that many seconds in the future. |
| 545 | if (paused() || isNew() || postActive() || fillingForwards()) |
| 546 | return WTF::nullopt; |
| 547 | |
| 548 | if (m_animationState == AnimationState::StartWaitTimer) { |
| 549 | double timeFromNow = m_animation->delay() - (beginAnimationUpdateTime() - m_requestedStartTime); |
| 550 | return std::max(Seconds { timeFromNow }, 0_s); |
| 551 | } |
| 552 | |
| 553 | fireAnimationEventsIfNeeded(); |
| 554 | |
| 555 | // In all other cases, we need service right away. |
| 556 | return 0_s; |
| 557 | } |
| 558 | |
| 559 | // Compute the fractional time, taking into account direction. |
| 560 | // There is no need to worry about iterations, we assume that we would have |
| 561 | // short circuited above if we were done. |
| 562 | |
| 563 | double AnimationBase::fractionalTime(double scale, double elapsedTime, double offset) const |
| 564 | { |
| 565 | double fractionalTime = m_animation->duration() ? (elapsedTime / m_animation->duration()) : 1; |
| 566 | // FIXME: startTime can be before the current animation "frame" time. This is to sync with the frame time |
| 567 | // concept in AnimationTimeController. So we need to somehow sync the two. Until then, the possible |
| 568 | // error is small and will probably not be noticeable. Until we fix this, remove the assert. |
| 569 | // https://bugs.webkit.org/show_bug.cgi?id=52037 |
| 570 | // ASSERT(fractionalTime >= 0); |
| 571 | if (fractionalTime < 0) |
| 572 | fractionalTime = 0; |
| 573 | |
| 574 | int integralTime = static_cast<int>(fractionalTime); |
| 575 | const int integralIterationCount = static_cast<int>(m_animation->iterationCount()); |
| 576 | const bool iterationCountHasFractional = m_animation->iterationCount() - integralIterationCount; |
| 577 | if (m_animation->iterationCount() != Animation::IterationCountInfinite && !iterationCountHasFractional) |
| 578 | integralTime = std::min(integralTime, integralIterationCount - 1); |
| 579 | |
| 580 | fractionalTime -= integralTime; |
| 581 | |
| 582 | if (((m_animation->direction() == Animation::AnimationDirectionAlternate) && (integralTime & 1)) |
| 583 | || ((m_animation->direction() == Animation::AnimationDirectionAlternateReverse) && !(integralTime & 1)) |
| 584 | || m_animation->direction() == Animation::AnimationDirectionReverse) |
| 585 | fractionalTime = 1 - fractionalTime; |
| 586 | |
| 587 | if (scale != 1 || offset) |
| 588 | fractionalTime = (fractionalTime - offset) * scale; |
| 589 | |
| 590 | return fractionalTime; |
| 591 | } |
| 592 | |
| 593 | double AnimationBase::progress(double scale, double offset, const TimingFunction* timingFunction) const |
| 594 | { |
| 595 | if (preActive()) |
| 596 | return 0; |
| 597 | |
| 598 | if (postActive()) |
| 599 | return 1; |
| 600 | |
| 601 | double elapsedTime = getElapsedTime(); |
| 602 | |
| 603 | double duration = m_animation->duration(); |
| 604 | if (m_animation->iterationCount() > 0) |
| 605 | duration *= m_animation->iterationCount(); |
| 606 | |
| 607 | if (fillingForwards()) |
| 608 | elapsedTime = duration; |
| 609 | |
| 610 | double fractionalTime = this->fractionalTime(scale, elapsedTime, offset); |
| 611 | |
| 612 | if (m_animation->iterationCount() > 0 && elapsedTime >= duration) { |
| 613 | if (WTF::isIntegral(fractionalTime)) |
| 614 | return fractionalTime; |
| 615 | } |
| 616 | |
| 617 | if (!timingFunction) |
| 618 | timingFunction = m_animation->timingFunction(); |
| 619 | |
| 620 | return timingFunction->transformTime(fractionalTime, m_animation->duration()); |
| 621 | } |
| 622 | |
| 623 | void AnimationBase::getTimeToNextEvent(Seconds& time, bool& isLooping) const |
| 624 | { |
| 625 | // Decide when the end or loop event needs to fire |
| 626 | const double elapsedDuration = std::max(beginAnimationUpdateTime() - m_startTime.valueOr(0), 0.0); |
| 627 | double durationLeft = 0; |
| 628 | double nextIterationTime = m_totalDuration.valueOr(0); |
| 629 | |
| 630 | if (!m_totalDuration || elapsedDuration < m_totalDuration.value()) { |
| 631 | durationLeft = m_animation->duration() > 0 ? (m_animation->duration() - fmod(elapsedDuration, m_animation->duration())) : 0; |
| 632 | nextIterationTime = elapsedDuration + durationLeft; |
| 633 | } |
| 634 | |
| 635 | if (!m_totalDuration || nextIterationTime < m_totalDuration.value()) { |
| 636 | // We are not at the end yet |
| 637 | ASSERT(nextIterationTime > 0); |
| 638 | isLooping = true; |
| 639 | } else { |
| 640 | // We are at the end |
| 641 | isLooping = false; |
| 642 | } |
| 643 | |
| 644 | time = Seconds { durationLeft }; |
| 645 | } |
| 646 | |
| 647 | void AnimationBase::goIntoEndingOrLoopingState() |
| 648 | { |
| 649 | Seconds t; |
| 650 | bool isLooping; |
| 651 | getTimeToNextEvent(t, isLooping); |
| 652 | LOG(Animations, "%p AnimationState %s -> %s" , this, nameForState(m_animationState), isLooping ? "Looping" : "Ending" ); |
| 653 | m_animationState = isLooping ? AnimationState::Looping : AnimationState::Ending; |
| 654 | } |
| 655 | |
| 656 | void AnimationBase::freezeAtTime(double t) |
| 657 | { |
| 658 | if (!m_compositeAnimation) |
| 659 | return; |
| 660 | |
| 661 | if (!m_startTime) { |
| 662 | // If we haven't started yet, make it as if we started. |
| 663 | LOG(Animations, "%p AnimationState %s -> StartWaitResponse" , this, nameForState(m_animationState)); |
| 664 | m_animationState = AnimationState::StartWaitResponse; |
| 665 | onAnimationStartResponse(MonotonicTime::now()); |
| 666 | } |
| 667 | |
| 668 | ASSERT(m_startTime); // If m_startTime is zero, we haven't started yet, so we'll get a bad pause time. |
| 669 | if (t <= m_animation->delay()) |
| 670 | m_pauseTime = m_startTime.valueOr(0); |
| 671 | else |
| 672 | m_pauseTime = m_startTime.valueOr(0) + t - m_animation->delay(); |
| 673 | |
| 674 | if (auto* renderer = this->renderer()) |
| 675 | renderer->suspendAnimations(MonotonicTime::fromRawSeconds(m_pauseTime.value())); |
| 676 | } |
| 677 | |
| 678 | double AnimationBase::beginAnimationUpdateTime() const |
| 679 | { |
| 680 | if (!m_compositeAnimation) |
| 681 | return 0; |
| 682 | |
| 683 | return m_compositeAnimation->animationController().beginAnimationUpdateTime().secondsSinceEpoch().seconds(); |
| 684 | } |
| 685 | |
| 686 | double AnimationBase::getElapsedTime() const |
| 687 | { |
| 688 | if (paused()) { |
| 689 | double delayOffset = (!m_startTime && m_animation->delay() < 0) ? m_animation->delay() : 0; |
| 690 | return m_pauseTime.valueOr(0) - m_startTime.valueOr(0) - delayOffset; |
| 691 | } |
| 692 | |
| 693 | if (!m_startTime) |
| 694 | return 0; |
| 695 | |
| 696 | if (postActive() || fillingForwards()) |
| 697 | return m_totalDuration.valueOr(0); |
| 698 | |
| 699 | return beginAnimationUpdateTime() - m_startTime.valueOr(0); |
| 700 | } |
| 701 | |
| 702 | void AnimationBase::setElapsedTime(double time) |
| 703 | { |
| 704 | // FIXME: implement this method |
| 705 | UNUSED_PARAM(time); |
| 706 | } |
| 707 | |
| 708 | void AnimationBase::play() |
| 709 | { |
| 710 | // FIXME: implement this method |
| 711 | } |
| 712 | |
| 713 | void AnimationBase::pause() |
| 714 | { |
| 715 | // FIXME: implement this method |
| 716 | } |
| 717 | |
| 718 | static bool containsRotation(const Vector<RefPtr<TransformOperation>>& operations) |
| 719 | { |
| 720 | for (const auto& operation : operations) { |
| 721 | if (operation->type() == TransformOperation::ROTATE) |
| 722 | return true; |
| 723 | } |
| 724 | return false; |
| 725 | } |
| 726 | |
| 727 | bool AnimationBase::computeTransformedExtentViaTransformList(const FloatRect& rendererBox, const RenderStyle& style, LayoutRect& bounds) const |
| 728 | { |
| 729 | FloatRect floatBounds = bounds; |
| 730 | FloatPoint transformOrigin; |
| 731 | |
| 732 | bool applyTransformOrigin = containsRotation(style.transform().operations()) || style.transform().affectedByTransformOrigin(); |
| 733 | if (applyTransformOrigin) { |
| 734 | transformOrigin.setX(rendererBox.x() + floatValueForLength(style.transformOriginX(), rendererBox.width())); |
| 735 | transformOrigin.setY(rendererBox.y() + floatValueForLength(style.transformOriginY(), rendererBox.height())); |
| 736 | // Ignore transformOriginZ because we'll bail if we encounter any 3D transforms. |
| 737 | |
| 738 | floatBounds.moveBy(-transformOrigin); |
| 739 | } |
| 740 | |
| 741 | for (const auto& operation : style.transform().operations()) { |
| 742 | if (operation->type() == TransformOperation::ROTATE) { |
| 743 | // For now, just treat this as a full rotation. This could take angle into account to reduce inflation. |
| 744 | floatBounds = boundsOfRotatingRect(floatBounds); |
| 745 | } else { |
| 746 | TransformationMatrix transform; |
| 747 | operation->apply(transform, rendererBox.size()); |
| 748 | if (!transform.isAffine()) |
| 749 | return false; |
| 750 | |
| 751 | if (operation->type() == TransformOperation::MATRIX || operation->type() == TransformOperation::MATRIX_3D) { |
| 752 | TransformationMatrix::Decomposed2Type toDecomp; |
| 753 | transform.decompose2(toDecomp); |
| 754 | // Any rotation prevents us from using a simple start/end rect union. |
| 755 | if (toDecomp.angle) |
| 756 | return false; |
| 757 | } |
| 758 | |
| 759 | floatBounds = transform.mapRect(floatBounds); |
| 760 | } |
| 761 | } |
| 762 | |
| 763 | if (applyTransformOrigin) |
| 764 | floatBounds.moveBy(transformOrigin); |
| 765 | |
| 766 | bounds = LayoutRect(floatBounds); |
| 767 | return true; |
| 768 | } |
| 769 | |
| 770 | bool AnimationBase::computeTransformedExtentViaMatrix(const FloatRect& rendererBox, const RenderStyle& style, LayoutRect& bounds) const |
| 771 | { |
| 772 | TransformationMatrix transform; |
| 773 | style.applyTransform(transform, rendererBox, RenderStyle::IncludeTransformOrigin); |
| 774 | if (!transform.isAffine()) |
| 775 | return false; |
| 776 | |
| 777 | TransformationMatrix::Decomposed2Type fromDecomp; |
| 778 | transform.decompose2(fromDecomp); |
| 779 | // Any rotation prevents us from using a simple start/end rect union. |
| 780 | if (fromDecomp.angle) |
| 781 | return false; |
| 782 | |
| 783 | bounds = LayoutRect(transform.mapRect(bounds)); |
| 784 | return true; |
| 785 | |
| 786 | } |
| 787 | |
| 788 | } // namespace WebCore |
| 789 | |