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