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 Computer, 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 "core/frame/animation/AnimationBase.h" 31 32 #include "core/frame/animation/AnimationControllerPrivate.h" 33 #include "core/frame/animation/CompositeAnimation.h" 34 #include "core/platform/animation/TimingFunction.h" 35 #include "core/rendering/RenderBox.h" 36 #include "platform/animation/AnimationUtilities.h" 37 #include <algorithm> 38 39 using namespace std; 40 41 namespace WebCore { 42 43 AnimationBase::AnimationBase(const CSSAnimationData* transition, RenderObject& renderer, CompositeAnimation* compAnim) 44 : m_animState(AnimationStateNew) 45 , m_isAccelerated(false) 46 , m_transformFunctionListValid(false) 47 , m_filterFunctionListsMatch(false) 48 , m_startTime(0) 49 , m_pauseTime(-1) 50 , m_requestedStartTime(0) 51 , m_totalDuration(-1) 52 , m_nextIterationDuration(-1) 53 , m_object(&renderer) 54 , m_animation(const_cast<CSSAnimationData*>(transition)) 55 , m_compAnim(compAnim) 56 { 57 // Compute the total duration 58 if (m_animation->iterationCount() > 0) 59 m_totalDuration = m_animation->duration() * m_animation->iterationCount(); 60 } 61 62 void AnimationBase::setNeedsStyleRecalc(Node* node) 63 { 64 if (node) 65 node->setNeedsStyleRecalc(LocalStyleChange); 66 } 67 68 double AnimationBase::duration() const 69 { 70 return m_animation->duration(); 71 } 72 73 bool AnimationBase::playStatePlaying() const 74 { 75 return m_animation->playState() == AnimPlayStatePlaying; 76 } 77 78 void AnimationBase::updateStateMachine(AnimStateInput input, double param) 79 { 80 if (!m_compAnim) 81 return; 82 83 // If we get AnimationStateInputRestartAnimation then we force a new animation, regardless of state. 84 if (input == AnimationStateInputMakeNew) { 85 if (m_animState == AnimationStateStartWaitStyleAvailable) 86 m_compAnim->animationController()->removeFromAnimationsWaitingForStyle(this); 87 m_animState = AnimationStateNew; 88 m_startTime = 0; 89 m_pauseTime = -1; 90 m_requestedStartTime = 0; 91 m_nextIterationDuration = -1; 92 endAnimation(); 93 return; 94 } 95 96 if (input == AnimationStateInputRestartAnimation) { 97 if (m_animState == AnimationStateStartWaitStyleAvailable) 98 m_compAnim->animationController()->removeFromAnimationsWaitingForStyle(this); 99 m_animState = AnimationStateNew; 100 m_startTime = 0; 101 m_pauseTime = -1; 102 m_requestedStartTime = 0; 103 m_nextIterationDuration = -1; 104 endAnimation(); 105 106 if (!paused()) 107 updateStateMachine(AnimationStateInputStartAnimation, -1); 108 return; 109 } 110 111 if (input == AnimationStateInputEndAnimation) { 112 if (m_animState == AnimationStateStartWaitStyleAvailable) 113 m_compAnim->animationController()->removeFromAnimationsWaitingForStyle(this); 114 m_animState = AnimationStateDone; 115 endAnimation(); 116 return; 117 } 118 119 if (input == AnimationStateInputPauseOverride) { 120 if (m_animState == AnimationStateStartWaitResponse) { 121 // If we are in AnimationStateStartWaitResponse, the animation will get canceled before 122 // we get a response, so move to the next state. 123 endAnimation(); 124 updateStateMachine(AnimationStateInputStartTimeSet, beginAnimationUpdateTime()); 125 } 126 return; 127 } 128 129 if (input == AnimationStateInputResumeOverride) { 130 if (m_animState == AnimationStateLooping || m_animState == AnimationStateEnding) { 131 // Start the animation 132 startAnimation(beginAnimationUpdateTime() - m_startTime); 133 } 134 return; 135 } 136 137 // Execute state machine 138 switch (m_animState) { 139 case AnimationStateNew: 140 ASSERT(input == AnimationStateInputStartAnimation || input == AnimationStateInputPlayStateRunning || input == AnimationStateInputPlayStatePaused); 141 if (input == AnimationStateInputStartAnimation || input == AnimationStateInputPlayStateRunning) { 142 m_requestedStartTime = beginAnimationUpdateTime(); 143 m_animState = AnimationStateStartWaitTimer; 144 } 145 break; 146 case AnimationStateStartWaitTimer: 147 ASSERT(input == AnimationStateInputStartTimerFired || input == AnimationStateInputPlayStatePaused); 148 149 if (input == AnimationStateInputStartTimerFired) { 150 ASSERT(param >= 0); 151 // Start timer has fired, tell the animation to start and wait for it to respond with start time 152 m_animState = AnimationStateStartWaitStyleAvailable; 153 m_compAnim->animationController()->addToAnimationsWaitingForStyle(this); 154 155 // Trigger a render so we can start the animation 156 if (m_object) 157 m_compAnim->animationController()->addNodeChangeToDispatch(m_object->node()); 158 } else { 159 ASSERT(!paused()); 160 // We're waiting for the start timer to fire and we got a pause. Cancel the timer, pause and wait 161 m_pauseTime = beginAnimationUpdateTime(); 162 m_animState = AnimationStatePausedWaitTimer; 163 } 164 break; 165 case AnimationStateStartWaitStyleAvailable: 166 ASSERT(input == AnimationStateInputStyleAvailable || input == AnimationStateInputPlayStatePaused); 167 168 if (input == AnimationStateInputStyleAvailable) { 169 // Start timer has fired, tell the animation to start and wait for it to respond with start time 170 m_animState = AnimationStateStartWaitResponse; 171 172 overrideAnimations(); 173 174 // Start the animation 175 if (overridden()) { 176 m_animState = AnimationStateStartWaitResponse; 177 updateStateMachine(AnimationStateInputStartTimeSet, beginAnimationUpdateTime()); 178 } else { 179 double timeOffset = 0; 180 // If the value for 'animation-delay' is negative then the animation appears to have started in the past. 181 if (m_animation->delay() < 0) 182 timeOffset = -m_animation->delay(); 183 startAnimation(timeOffset); 184 m_compAnim->animationController()->addToAnimationsWaitingForStartTimeResponse(this, isAccelerated()); 185 } 186 } else { 187 // We're waiting for the style to be available and we got a pause. Pause and wait 188 m_pauseTime = beginAnimationUpdateTime(); 189 m_animState = AnimationStatePausedWaitStyleAvailable; 190 } 191 break; 192 case AnimationStateStartWaitResponse: 193 ASSERT(input == AnimationStateInputStartTimeSet || input == AnimationStateInputPlayStatePaused); 194 195 if (input == AnimationStateInputStartTimeSet) { 196 ASSERT(param >= 0); 197 // We have a start time, set it, unless the startTime is already set 198 if (m_startTime <= 0) { 199 m_startTime = param; 200 // If the value for 'animation-delay' is negative then the animation appears to have started in the past. 201 if (m_animation->delay() < 0) 202 m_startTime += m_animation->delay(); 203 } 204 205 // Now that we know the start time, fire the start event. 206 onAnimationStart(0); // The elapsedTime is 0. 207 208 // Decide whether to go into looping or ending state 209 goIntoEndingOrLoopingState(); 210 211 // Dispatch updateStyleIfNeeded so we can start the animation 212 if (m_object) 213 m_compAnim->animationController()->addNodeChangeToDispatch(m_object->node()); 214 } else { 215 // We are pausing while waiting for a start response. Cancel the animation and wait. When 216 // we unpause, we will act as though the start timer just fired 217 m_pauseTime = beginAnimationUpdateTime(); 218 pauseAnimation(beginAnimationUpdateTime() - m_startTime); 219 m_animState = AnimationStatePausedWaitResponse; 220 } 221 break; 222 case AnimationStateLooping: 223 ASSERT(input == AnimationStateInputLoopTimerFired || input == AnimationStateInputPlayStatePaused); 224 225 if (input == AnimationStateInputLoopTimerFired) { 226 ASSERT(param >= 0); 227 // Loop timer fired, loop again or end. 228 onAnimationIteration(param); 229 230 // Decide whether to go into looping or ending state 231 goIntoEndingOrLoopingState(); 232 } else { 233 // We are pausing while running. Cancel the animation and wait 234 m_pauseTime = beginAnimationUpdateTime(); 235 pauseAnimation(beginAnimationUpdateTime() - m_startTime); 236 m_animState = AnimationStatePausedRun; 237 } 238 break; 239 case AnimationStateEnding: 240 #if !LOG_DISABLED 241 if (input != AnimationStateInputEndTimerFired && input != AnimationStateInputPlayStatePaused) 242 WTF_LOG_ERROR("State is AnimationStateEnding, but input is not AnimationStateInputEndTimerFired or AnimationStateInputPlayStatePaused. It is %d.", input); 243 #endif 244 if (input == AnimationStateInputEndTimerFired) { 245 246 ASSERT(param >= 0); 247 // End timer fired, finish up 248 onAnimationEnd(param); 249 250 m_animState = AnimationStateDone; 251 252 if (m_object) { 253 if (m_animation->fillsForwards()) 254 m_animState = AnimationStateFillingForwards; 255 else 256 resumeOverriddenAnimations(); 257 258 // Fire off another style change so we can set the final value 259 m_compAnim->animationController()->addNodeChangeToDispatch(m_object->node()); 260 } 261 } else { 262 // We are pausing while running. Cancel the animation and wait 263 m_pauseTime = beginAnimationUpdateTime(); 264 pauseAnimation(beginAnimationUpdateTime() - m_startTime); 265 m_animState = AnimationStatePausedRun; 266 } 267 // |this| may be deleted here 268 break; 269 case AnimationStatePausedWaitTimer: 270 ASSERT(input == AnimationStateInputPlayStateRunning); 271 ASSERT(paused()); 272 // Update the times 273 m_startTime += beginAnimationUpdateTime() - m_pauseTime; 274 m_pauseTime = -1; 275 276 // we were waiting for the start timer to fire, go back and wait again 277 m_animState = AnimationStateNew; 278 updateStateMachine(AnimationStateInputStartAnimation, 0); 279 break; 280 case AnimationStatePausedWaitResponse: 281 case AnimationStatePausedWaitStyleAvailable: 282 case AnimationStatePausedRun: 283 // We treat these two cases the same. The only difference is that, when we are in 284 // AnimationStatePausedWaitResponse, we don't yet have a valid startTime, so we send 0 to startAnimation. 285 // When the AnimationStateInputStartTimeSet comes in and we were in AnimationStatePausedRun, we will notice 286 // that we have already set the startTime and will ignore it. 287 ASSERT(input == AnimationStateInputPlayStateRunning || input == AnimationStateInputStartTimeSet || input == AnimationStateInputStyleAvailable); 288 ASSERT(paused()); 289 290 if (input == AnimationStateInputPlayStateRunning) { 291 // Update the times 292 if (m_animState == AnimationStatePausedRun) 293 m_startTime += beginAnimationUpdateTime() - m_pauseTime; 294 else 295 m_startTime = 0; 296 m_pauseTime = -1; 297 298 if (m_animState == AnimationStatePausedWaitStyleAvailable) 299 m_animState = AnimationStateStartWaitStyleAvailable; 300 else { 301 // We were either running or waiting for a begin time response from the animation. 302 // Either way we need to restart the animation (possibly with an offset if we 303 // had already been running) and wait for it to start. 304 m_animState = AnimationStateStartWaitResponse; 305 306 // Start the animation 307 if (overridden()) { 308 updateStateMachine(AnimationStateInputStartTimeSet, beginAnimationUpdateTime()); 309 } else { 310 startAnimation(beginAnimationUpdateTime() - m_startTime); 311 m_compAnim->animationController()->addToAnimationsWaitingForStartTimeResponse(this, isAccelerated()); 312 } 313 } 314 break; 315 } 316 317 if (input == AnimationStateInputStartTimeSet) { 318 ASSERT(m_animState == AnimationStatePausedWaitResponse); 319 320 // We are paused but we got the callback that notifies us that an accelerated animation started. 321 // We ignore the start time and just move into the paused-run state. 322 m_animState = AnimationStatePausedRun; 323 ASSERT(m_startTime == 0); 324 m_startTime = param; 325 m_pauseTime += m_startTime; 326 break; 327 } 328 329 ASSERT(m_animState == AnimationStatePausedWaitStyleAvailable); 330 // We are paused but we got the callback that notifies us that style has been updated. 331 // We move to the AnimationStatePausedWaitResponse state 332 m_animState = AnimationStatePausedWaitResponse; 333 overrideAnimations(); 334 break; 335 case AnimationStateFillingForwards: 336 case AnimationStateDone: 337 // We're done. Stay in this state until we are deleted 338 break; 339 } 340 } 341 342 void AnimationBase::fireAnimationEventsIfNeeded() 343 { 344 if (!m_compAnim) 345 return; 346 347 // If we are waiting for the delay time to expire and it has, go to the next state 348 if (m_animState != AnimationStateStartWaitTimer && m_animState != AnimationStateLooping && m_animState != AnimationStateEnding) 349 return; 350 351 // We have to make sure to keep a ref to the this pointer, because it could get destroyed 352 // during an animation callback that might get called. Since the owner is a CompositeAnimation 353 // and it ref counts this object, we will keep a ref to that instead. That way the AnimationBase 354 // can still access the resources of its CompositeAnimation as needed. 355 RefPtr<AnimationBase> protector(this); 356 RefPtr<CompositeAnimation> compProtector(m_compAnim); 357 358 // Check for start timeout 359 if (m_animState == AnimationStateStartWaitTimer) { 360 if (beginAnimationUpdateTime() - m_requestedStartTime >= m_animation->delay()) 361 updateStateMachine(AnimationStateInputStartTimerFired, 0); 362 return; 363 } 364 365 double elapsedDuration = getElapsedTime(); 366 367 // Check for end timeout 368 if (m_totalDuration >= 0 && elapsedDuration >= m_totalDuration) { 369 // We may still be in AnimationStateLooping if we've managed to skip a 370 // whole iteration, in which case we should jump to the end state. 371 m_animState = AnimationStateEnding; 372 373 // Fire an end event 374 updateStateMachine(AnimationStateInputEndTimerFired, m_totalDuration); 375 } else { 376 // Check for iteration timeout 377 if (m_nextIterationDuration < 0) { 378 // Hasn't been set yet, set it 379 double durationLeft = m_animation->duration() - fmod(elapsedDuration, m_animation->duration()); 380 m_nextIterationDuration = elapsedDuration + durationLeft; 381 } 382 383 if (elapsedDuration >= m_nextIterationDuration) { 384 // Set to the next iteration 385 double previous = m_nextIterationDuration; 386 double durationLeft = m_animation->duration() - fmod(elapsedDuration, m_animation->duration()); 387 m_nextIterationDuration = elapsedDuration + durationLeft; 388 389 // Send the event 390 updateStateMachine(AnimationStateInputLoopTimerFired, previous); 391 } 392 } 393 } 394 395 void AnimationBase::updatePlayState(EAnimPlayState playState) 396 { 397 if (!m_compAnim) 398 return; 399 400 // Set the state machine to the desired state. 401 bool pause = playState == AnimPlayStatePaused; 402 403 if (pause == paused() && !isNew()) 404 return; 405 406 updateStateMachine(pause ? AnimationStateInputPlayStatePaused : AnimationStateInputPlayStateRunning, -1); 407 } 408 409 double AnimationBase::timeToNextService() 410 { 411 // Returns the time at which next service is required. -1 means no service is required. 0 means 412 // service is required now, and > 0 means service is required that many seconds in the future. 413 if (paused() || isNew() || m_animState == AnimationStateFillingForwards) 414 return -1; 415 416 if (m_animState == AnimationStateStartWaitTimer) { 417 double timeFromNow = m_animation->delay() - (beginAnimationUpdateTime() - m_requestedStartTime); 418 return max(timeFromNow, 0.0); 419 } 420 421 fireAnimationEventsIfNeeded(); 422 423 // In all other cases, we need service right away. 424 return 0; 425 } 426 427 // Compute the fractional time, taking into account direction. 428 // There is no need to worry about iterations, we assume that we would have 429 // short circuited above if we were done. 430 431 double AnimationBase::fractionalTime(double scale, double elapsedTime, double offset) const 432 { 433 double fractionalTime = m_animation->duration() ? (elapsedTime / m_animation->duration()) : 1; 434 // FIXME: startTime can be before the current animation "frame" time. This is to sync with the frame time 435 // concept in AnimationTimeController. So we need to somehow sync the two. Until then, the possible 436 // error is small and will probably not be noticeable. Until we fix this, remove the assert. 437 // https://bugs.webkit.org/show_bug.cgi?id=52037 438 // ASSERT(fractionalTime >= 0); 439 if (fractionalTime < 0) 440 fractionalTime = 0; 441 442 int integralTime = static_cast<int>(fractionalTime); 443 const int integralIterationCount = static_cast<int>(m_animation->iterationCount()); 444 const bool iterationCountHasFractional = m_animation->iterationCount() - integralIterationCount; 445 if (m_animation->iterationCount() != CSSAnimationData::IterationCountInfinite && !iterationCountHasFractional) 446 integralTime = min(integralTime, integralIterationCount - 1); 447 448 fractionalTime -= integralTime; 449 450 // Thie method can be called with an elapsedTime which very slightly 451 // exceeds the end of the animation. In this case, clamp the 452 // fractionalTime. 453 if (fractionalTime > 1) 454 fractionalTime = 1; 455 ASSERT(fractionalTime >= 0 && fractionalTime <= 1); 456 457 if (((m_animation->direction() == CSSAnimationData::AnimationDirectionAlternate) && (integralTime & 1)) 458 || ((m_animation->direction() == CSSAnimationData::AnimationDirectionAlternateReverse) && !(integralTime & 1)) 459 || m_animation->direction() == CSSAnimationData::AnimationDirectionReverse) 460 fractionalTime = 1 - fractionalTime; 461 462 fractionalTime -= offset; 463 // Note that if fractionalTime == 0 here, scale may be infinity, but in 464 // this case we don't need to apply scale anyway. 465 if (scale != 1.0 && fractionalTime) { 466 ASSERT(scale >= 0 && !std::isinf(scale)); 467 fractionalTime *= scale; 468 } 469 470 ASSERT(fractionalTime >= 0 && fractionalTime <= 1); 471 return fractionalTime; 472 } 473 474 double AnimationBase::progress(double scale, double offset, const TimingFunction* timingFunction) const 475 { 476 if (preActive()) 477 return 0; 478 479 double dur = m_animation->duration(); 480 if (m_animation->iterationCount() > 0) 481 dur *= m_animation->iterationCount(); 482 483 if (postActive() || !m_animation->duration()) 484 return 1.0; 485 486 double elapsedTime = getElapsedTime(); 487 if (m_animation->iterationCount() > 0 && elapsedTime >= dur) { 488 const int integralIterationCount = static_cast<int>(m_animation->iterationCount()); 489 const bool iterationCountHasFractional = m_animation->iterationCount() - integralIterationCount; 490 return (integralIterationCount % 2 || iterationCountHasFractional) ? 1.0 : 0.0; 491 } 492 493 const double fractionalTime = this->fractionalTime(scale, elapsedTime, offset); 494 495 if (!timingFunction) 496 timingFunction = m_animation->timingFunction(); 497 498 return timingFunction->evaluate(fractionalTime, accuracyForDuration(m_animation->duration())); 499 } 500 501 void AnimationBase::getTimeToNextEvent(double& time, bool& isLooping) const 502 { 503 if (postActive()) { 504 time = -1; 505 isLooping = false; 506 return; 507 } 508 509 // Decide when the end or loop event needs to fire 510 const double elapsedDuration = getElapsedTime(); 511 double durationLeft = 0; 512 double nextIterationTime = m_totalDuration; 513 514 if (m_totalDuration < 0 || elapsedDuration < m_totalDuration) { 515 durationLeft = m_animation->duration() > 0 ? (m_animation->duration() - fmod(elapsedDuration, m_animation->duration())) : 0; 516 nextIterationTime = elapsedDuration + durationLeft; 517 } 518 519 if (m_totalDuration < 0 || nextIterationTime < m_totalDuration) { 520 // We are not at the end yet 521 ASSERT(m_totalDuration < 0 || nextIterationTime > 0); 522 isLooping = true; 523 } else { 524 // We are at the end 525 isLooping = false; 526 } 527 528 time = durationLeft; 529 } 530 531 void AnimationBase::goIntoEndingOrLoopingState() 532 { 533 double t; 534 bool isLooping; 535 getTimeToNextEvent(t, isLooping); 536 m_animState = isLooping ? AnimationStateLooping : AnimationStateEnding; 537 } 538 539 void AnimationBase::freezeAtTime(double t) 540 { 541 if (!m_compAnim) 542 return; 543 544 if (!m_startTime) { 545 // If we haven't started yet, make it as if we started. 546 m_animState = AnimationStateStartWaitResponse; 547 onAnimationStartResponse(beginAnimationUpdateTime()); 548 } 549 550 ASSERT(m_startTime); // if m_startTime is zero, we haven't started yet, so we'll get a bad pause time. 551 if (t <= m_animation->delay()) 552 m_pauseTime = m_startTime; 553 else 554 m_pauseTime = m_startTime + t - m_animation->delay(); 555 556 // It is possible that m_isAccelerated is true and m_object->compositingState() is NotComposited, because of style change. 557 // So, both conditions need to be checked. 558 if (m_object && m_object->compositingState() == PaintsIntoOwnBacking && isAccelerated()) 559 pauseAnimation(t); 560 } 561 562 double AnimationBase::beginAnimationUpdateTime() const 563 { 564 if (!m_compAnim) 565 return 0; 566 567 return m_compAnim->animationController()->beginAnimationUpdateTime(); 568 } 569 570 double AnimationBase::getElapsedTime() const 571 { 572 ASSERT(!postActive()); 573 if (paused()) 574 return m_pauseTime - m_startTime; 575 if (m_startTime <= 0) 576 return 0; 577 578 double elapsedTime = beginAnimationUpdateTime() - m_startTime; 579 // It's possible for the start time to be ahead of the last update time 580 // if the compositor has just sent notification for the start of an 581 // accelerated animation. 582 return max(elapsedTime, 0.0); 583 } 584 585 } // namespace WebCore 586