1 /* 2 * Copyright (C) 2007 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 "CompositeAnimation.h" 31 32 #include "AnimationControllerPrivate.h" 33 #include "CSSPropertyNames.h" 34 #include "ImplicitAnimation.h" 35 #include "KeyframeAnimation.h" 36 #include "RenderObject.h" 37 #include "RenderStyle.h" 38 39 namespace WebCore { 40 41 CompositeAnimation::~CompositeAnimation() 42 { 43 // Toss the refs to all animations 44 m_transitions.clear(); 45 m_keyframeAnimations.clear(); 46 } 47 48 void CompositeAnimation::clearRenderer() 49 { 50 if (!m_transitions.isEmpty()) { 51 // Clear the renderers from all running animations, in case we are in the middle of 52 // an animation callback (see https://bugs.webkit.org/show_bug.cgi?id=22052) 53 CSSPropertyTransitionsMap::const_iterator transitionsEnd = m_transitions.end(); 54 for (CSSPropertyTransitionsMap::const_iterator it = m_transitions.begin(); it != transitionsEnd; ++it) { 55 ImplicitAnimation* transition = it->second.get(); 56 transition->clearRenderer(); 57 } 58 } 59 if (!m_keyframeAnimations.isEmpty()) { 60 m_keyframeAnimations.checkConsistency(); 61 AnimationNameMap::const_iterator animationsEnd = m_keyframeAnimations.end(); 62 for (AnimationNameMap::const_iterator it = m_keyframeAnimations.begin(); it != animationsEnd; ++it) { 63 KeyframeAnimation* anim = it->second.get(); 64 anim->clearRenderer(); 65 } 66 } 67 } 68 69 void CompositeAnimation::updateTransitions(RenderObject* renderer, RenderStyle* currentStyle, RenderStyle* targetStyle) 70 { 71 // If currentStyle is null or there are no old or new transitions, just skip it 72 if (!currentStyle || (!targetStyle->transitions() && m_transitions.isEmpty())) 73 return; 74 75 // Mark all existing transitions as no longer active. We will mark the still active ones 76 // in the next loop and then toss the ones that didn't get marked. 77 CSSPropertyTransitionsMap::const_iterator end = m_transitions.end(); 78 for (CSSPropertyTransitionsMap::const_iterator it = m_transitions.begin(); it != end; ++it) 79 it->second->setActive(false); 80 81 RefPtr<RenderStyle> modifiedCurrentStyle; 82 83 // Check to see if we need to update the active transitions 84 if (targetStyle->transitions()) { 85 for (size_t i = 0; i < targetStyle->transitions()->size(); ++i) { 86 const Animation* anim = targetStyle->transitions()->animation(i); 87 bool isActiveTransition = anim->duration() || anim->delay() > 0; 88 89 int prop = anim->property(); 90 91 if (prop == cAnimateNone) 92 continue; 93 94 bool all = prop == cAnimateAll; 95 96 // Handle both the 'all' and single property cases. For the single prop case, we make only one pass 97 // through the loop. 98 for (int propertyIndex = 0; propertyIndex < AnimationBase::getNumProperties(); ++propertyIndex) { 99 if (all) { 100 // Get the next property which is not a shorthand. 101 bool isShorthand; 102 prop = AnimationBase::getPropertyAtIndex(propertyIndex, isShorthand); 103 if (isShorthand) 104 continue; 105 } 106 107 // ImplicitAnimations are always hashed by actual properties, never cAnimateAll 108 ASSERT(prop >= firstCSSProperty && prop < (firstCSSProperty + numCSSProperties)); 109 110 // If there is a running animation for this property, the transition is overridden 111 // and we have to use the unanimatedStyle from the animation. We do the test 112 // against the unanimated style here, but we "override" the transition later. 113 RefPtr<KeyframeAnimation> keyframeAnim = getAnimationForProperty(prop); 114 RenderStyle* fromStyle = keyframeAnim ? keyframeAnim->unanimatedStyle() : currentStyle; 115 116 // See if there is a current transition for this prop 117 ImplicitAnimation* implAnim = m_transitions.get(prop).get(); 118 bool equal = true; 119 120 if (implAnim) { 121 // If we are post active don't bother setting the active flag. This will cause 122 // this animation to get removed at the end of this function. 123 if (!implAnim->postActive()) 124 implAnim->setActive(true); 125 126 // This might be a transition that is just finishing. That would be the case 127 // if it were postActive. But we still need to check for equality because 128 // it could be just finishing AND changing to a new goal state. 129 // 130 // This implAnim might also not be an already running transition. It might be 131 // newly added to the list in a previous iteration. This would happen if 132 // you have both an explicit transition-property and 'all' in the same 133 // list. In this case, the latter one overrides the earlier one, so we 134 // behave as though this is a running animation being replaced. 135 if (!implAnim->isTargetPropertyEqual(prop, targetStyle)) { 136 #if USE(ACCELERATED_COMPOSITING) 137 // For accelerated animations we need to return a new RenderStyle with the _current_ value 138 // of the property, so that restarted transitions use the correct starting point. 139 if (AnimationBase::animationOfPropertyIsAccelerated(prop) && !implAnim->isFallbackAnimating()) { 140 if (!modifiedCurrentStyle) 141 modifiedCurrentStyle = RenderStyle::clone(currentStyle); 142 143 implAnim->blendPropertyValueInStyle(prop, modifiedCurrentStyle.get()); 144 } 145 #endif 146 m_transitions.remove(prop); 147 equal = false; 148 } 149 } else { 150 // We need to start a transition if it is active and the properties don't match 151 equal = !isActiveTransition || AnimationBase::propertiesEqual(prop, fromStyle, targetStyle); 152 } 153 154 // We can be in this loop with an inactive transition (!isActiveTransition). We need 155 // to do that to check to see if we are canceling a transition. But we don't want to 156 // start one of the inactive transitions. So short circuit that here. (See 157 // <https://bugs.webkit.org/show_bug.cgi?id=24787> 158 if (!equal && isActiveTransition) { 159 // Add the new transition 160 m_transitions.set(prop, ImplicitAnimation::create(const_cast<Animation*>(anim), prop, renderer, this, modifiedCurrentStyle ? modifiedCurrentStyle.get() : fromStyle)); 161 } 162 163 // We only need one pass for the single prop case 164 if (!all) 165 break; 166 } 167 } 168 } 169 170 // Make a list of transitions to be removed 171 Vector<int> toBeRemoved; 172 end = m_transitions.end(); 173 for (CSSPropertyTransitionsMap::const_iterator it = m_transitions.begin(); it != end; ++it) { 174 ImplicitAnimation* anim = it->second.get(); 175 if (!anim->active()) 176 toBeRemoved.append(anim->animatingProperty()); 177 } 178 179 // Now remove the transitions from the list 180 for (size_t j = 0; j < toBeRemoved.size(); ++j) 181 m_transitions.remove(toBeRemoved[j]); 182 } 183 184 void CompositeAnimation::updateKeyframeAnimations(RenderObject* renderer, RenderStyle* currentStyle, RenderStyle* targetStyle) 185 { 186 // Nothing to do if we don't have any animations, and didn't have any before 187 if (m_keyframeAnimations.isEmpty() && !targetStyle->hasAnimations()) 188 return; 189 190 m_keyframeAnimations.checkConsistency(); 191 192 AnimationNameMap::const_iterator kfend = m_keyframeAnimations.end(); 193 194 if (currentStyle && currentStyle->hasAnimations() && targetStyle->hasAnimations() && *(currentStyle->animations()) == *(targetStyle->animations())) { 195 // The current and target animations are the same so we just need to toss any 196 // animation which is finished (postActive). 197 for (AnimationNameMap::const_iterator it = m_keyframeAnimations.begin(); it != kfend; ++it) { 198 if (it->second->postActive()) 199 it->second->setIndex(-1); 200 } 201 } else { 202 // Mark all existing animations as no longer active. 203 for (AnimationNameMap::const_iterator it = m_keyframeAnimations.begin(); it != kfend; ++it) 204 it->second->setIndex(-1); 205 206 // Toss the animation order map. 207 m_keyframeAnimationOrderMap.clear(); 208 209 // Now mark any still active animations as active and add any new animations. 210 if (targetStyle->animations()) { 211 int numAnims = targetStyle->animations()->size(); 212 for (int i = 0; i < numAnims; ++i) { 213 const Animation* anim = targetStyle->animations()->animation(i); 214 AtomicString animationName(anim->name()); 215 216 if (!anim->isValidAnimation()) 217 continue; 218 219 // See if there is a current animation for this name. 220 RefPtr<KeyframeAnimation> keyframeAnim = m_keyframeAnimations.get(animationName.impl()); 221 222 if (keyframeAnim) { 223 // If this animation is postActive, skip it so it gets removed at the end of this function. 224 if (keyframeAnim->postActive()) 225 continue; 226 227 // This one is still active. 228 229 // Animations match, but play states may differ. Update if needed. 230 keyframeAnim->updatePlayState(anim->playState() == AnimPlayStatePlaying); 231 232 // Set the saved animation to this new one, just in case the play state has changed. 233 keyframeAnim->setAnimation(anim); 234 keyframeAnim->setIndex(i); 235 } else if ((anim->duration() || anim->delay()) && anim->iterationCount()) { 236 keyframeAnim = KeyframeAnimation::create(const_cast<Animation*>(anim), renderer, i, this, currentStyle ? currentStyle : targetStyle); 237 m_keyframeAnimations.set(keyframeAnim->name().impl(), keyframeAnim); 238 } 239 240 // Add this to the animation order map. 241 if (keyframeAnim) 242 m_keyframeAnimationOrderMap.append(keyframeAnim->name().impl()); 243 } 244 } 245 } 246 247 // Make a list of animations to be removed. 248 Vector<AtomicStringImpl*> animsToBeRemoved; 249 kfend = m_keyframeAnimations.end(); 250 for (AnimationNameMap::const_iterator it = m_keyframeAnimations.begin(); it != kfend; ++it) { 251 KeyframeAnimation* keyframeAnim = it->second.get(); 252 if (keyframeAnim->index() < 0) 253 animsToBeRemoved.append(keyframeAnim->name().impl()); 254 } 255 256 // Now remove the animations from the list. 257 for (size_t j = 0; j < animsToBeRemoved.size(); ++j) 258 m_keyframeAnimations.remove(animsToBeRemoved[j]); 259 } 260 261 PassRefPtr<RenderStyle> CompositeAnimation::animate(RenderObject* renderer, RenderStyle* currentStyle, RenderStyle* targetStyle) 262 { 263 RefPtr<RenderStyle> resultStyle; 264 265 // We don't do any transitions if we don't have a currentStyle (on startup). 266 updateTransitions(renderer, currentStyle, targetStyle); 267 updateKeyframeAnimations(renderer, currentStyle, targetStyle); 268 m_keyframeAnimations.checkConsistency(); 269 270 if (currentStyle) { 271 // Now that we have transition objects ready, let them know about the new goal state. We want them 272 // to fill in a RenderStyle*& only if needed. 273 if (!m_transitions.isEmpty()) { 274 CSSPropertyTransitionsMap::const_iterator end = m_transitions.end(); 275 for (CSSPropertyTransitionsMap::const_iterator it = m_transitions.begin(); it != end; ++it) { 276 if (ImplicitAnimation* anim = it->second.get()) 277 anim->animate(this, renderer, currentStyle, targetStyle, resultStyle); 278 } 279 } 280 } 281 282 // Now that we have animation objects ready, let them know about the new goal state. We want them 283 // to fill in a RenderStyle*& only if needed. 284 for (Vector<AtomicStringImpl*>::const_iterator it = m_keyframeAnimationOrderMap.begin(); it != m_keyframeAnimationOrderMap.end(); ++it) { 285 RefPtr<KeyframeAnimation> keyframeAnim = m_keyframeAnimations.get(*it); 286 if (keyframeAnim) 287 keyframeAnim->animate(this, renderer, currentStyle, targetStyle, resultStyle); 288 } 289 290 return resultStyle ? resultStyle.release() : targetStyle; 291 } 292 293 PassRefPtr<RenderStyle> CompositeAnimation::getAnimatedStyle() const 294 { 295 RefPtr<RenderStyle> resultStyle; 296 CSSPropertyTransitionsMap::const_iterator end = m_transitions.end(); 297 for (CSSPropertyTransitionsMap::const_iterator it = m_transitions.begin(); it != end; ++it) { 298 if (ImplicitAnimation* implicitAnimation = it->second.get()) 299 implicitAnimation->getAnimatedStyle(resultStyle); 300 } 301 302 m_keyframeAnimations.checkConsistency(); 303 304 for (Vector<AtomicStringImpl*>::const_iterator it = m_keyframeAnimationOrderMap.begin(); it != m_keyframeAnimationOrderMap.end(); ++it) { 305 RefPtr<KeyframeAnimation> keyframeAnimation = m_keyframeAnimations.get(*it); 306 if (keyframeAnimation) 307 keyframeAnimation->getAnimatedStyle(resultStyle); 308 } 309 310 return resultStyle; 311 } 312 313 // "animating" means that something is running that requires the timer to keep firing 314 void CompositeAnimation::setAnimating(bool animating) 315 { 316 if (!m_transitions.isEmpty()) { 317 CSSPropertyTransitionsMap::const_iterator transitionsEnd = m_transitions.end(); 318 for (CSSPropertyTransitionsMap::const_iterator it = m_transitions.begin(); it != transitionsEnd; ++it) { 319 ImplicitAnimation* transition = it->second.get(); 320 transition->setAnimating(animating); 321 } 322 } 323 if (!m_keyframeAnimations.isEmpty()) { 324 m_keyframeAnimations.checkConsistency(); 325 AnimationNameMap::const_iterator animationsEnd = m_keyframeAnimations.end(); 326 for (AnimationNameMap::const_iterator it = m_keyframeAnimations.begin(); it != animationsEnd; ++it) { 327 KeyframeAnimation* anim = it->second.get(); 328 anim->setAnimating(animating); 329 } 330 } 331 } 332 333 double CompositeAnimation::timeToNextService() const 334 { 335 // Returns the time at which next service is required. -1 means no service is required. 0 means 336 // service is required now, and > 0 means service is required that many seconds in the future. 337 double minT = -1; 338 339 if (!m_transitions.isEmpty()) { 340 CSSPropertyTransitionsMap::const_iterator transitionsEnd = m_transitions.end(); 341 for (CSSPropertyTransitionsMap::const_iterator it = m_transitions.begin(); it != transitionsEnd; ++it) { 342 ImplicitAnimation* transition = it->second.get(); 343 double t = transition ? transition->timeToNextService() : -1; 344 if (t < minT || minT == -1) 345 minT = t; 346 if (minT == 0) 347 return 0; 348 } 349 } 350 if (!m_keyframeAnimations.isEmpty()) { 351 m_keyframeAnimations.checkConsistency(); 352 AnimationNameMap::const_iterator animationsEnd = m_keyframeAnimations.end(); 353 for (AnimationNameMap::const_iterator it = m_keyframeAnimations.begin(); it != animationsEnd; ++it) { 354 KeyframeAnimation* animation = it->second.get(); 355 double t = animation ? animation->timeToNextService() : -1; 356 if (t < minT || minT == -1) 357 minT = t; 358 if (minT == 0) 359 return 0; 360 } 361 } 362 363 return minT; 364 } 365 366 PassRefPtr<KeyframeAnimation> CompositeAnimation::getAnimationForProperty(int property) const 367 { 368 RefPtr<KeyframeAnimation> retval; 369 370 // We want to send back the last animation with the property if there are multiples. 371 // So we need to iterate through all animations 372 if (!m_keyframeAnimations.isEmpty()) { 373 m_keyframeAnimations.checkConsistency(); 374 AnimationNameMap::const_iterator animationsEnd = m_keyframeAnimations.end(); 375 for (AnimationNameMap::const_iterator it = m_keyframeAnimations.begin(); it != animationsEnd; ++it) { 376 RefPtr<KeyframeAnimation> anim = it->second; 377 if (anim->hasAnimationForProperty(property)) 378 retval = anim; 379 } 380 } 381 382 return retval; 383 } 384 385 void CompositeAnimation::suspendAnimations() 386 { 387 if (m_isSuspended) 388 return; 389 390 m_isSuspended = true; 391 392 if (!m_keyframeAnimations.isEmpty()) { 393 m_keyframeAnimations.checkConsistency(); 394 AnimationNameMap::const_iterator animationsEnd = m_keyframeAnimations.end(); 395 for (AnimationNameMap::const_iterator it = m_keyframeAnimations.begin(); it != animationsEnd; ++it) { 396 if (KeyframeAnimation* anim = it->second.get()) 397 anim->updatePlayState(false); 398 } 399 } 400 if (!m_transitions.isEmpty()) { 401 CSSPropertyTransitionsMap::const_iterator transitionsEnd = m_transitions.end(); 402 for (CSSPropertyTransitionsMap::const_iterator it = m_transitions.begin(); it != transitionsEnd; ++it) { 403 ImplicitAnimation* anim = it->second.get(); 404 if (anim && anim->hasStyle()) 405 anim->updatePlayState(false); 406 } 407 } 408 } 409 410 void CompositeAnimation::resumeAnimations() 411 { 412 if (!m_isSuspended) 413 return; 414 415 m_isSuspended = false; 416 417 if (!m_keyframeAnimations.isEmpty()) { 418 m_keyframeAnimations.checkConsistency(); 419 AnimationNameMap::const_iterator animationsEnd = m_keyframeAnimations.end(); 420 for (AnimationNameMap::const_iterator it = m_keyframeAnimations.begin(); it != animationsEnd; ++it) { 421 KeyframeAnimation* anim = it->second.get(); 422 if (anim && anim->playStatePlaying()) 423 anim->updatePlayState(true); 424 } 425 } 426 427 if (!m_transitions.isEmpty()) { 428 CSSPropertyTransitionsMap::const_iterator transitionsEnd = m_transitions.end(); 429 for (CSSPropertyTransitionsMap::const_iterator it = m_transitions.begin(); it != transitionsEnd; ++it) { 430 ImplicitAnimation* anim = it->second.get(); 431 if (anim && anim->hasStyle()) 432 anim->updatePlayState(true); 433 } 434 } 435 } 436 437 void CompositeAnimation::overrideImplicitAnimations(int property) 438 { 439 CSSPropertyTransitionsMap::const_iterator end = m_transitions.end(); 440 if (!m_transitions.isEmpty()) { 441 for (CSSPropertyTransitionsMap::const_iterator it = m_transitions.begin(); it != end; ++it) { 442 ImplicitAnimation* anim = it->second.get(); 443 if (anim && anim->animatingProperty() == property) 444 anim->setOverridden(true); 445 } 446 } 447 } 448 449 void CompositeAnimation::resumeOverriddenImplicitAnimations(int property) 450 { 451 if (!m_transitions.isEmpty()) { 452 CSSPropertyTransitionsMap::const_iterator end = m_transitions.end(); 453 for (CSSPropertyTransitionsMap::const_iterator it = m_transitions.begin(); it != end; ++it) { 454 ImplicitAnimation* anim = it->second.get(); 455 if (anim && anim->animatingProperty() == property) 456 anim->setOverridden(false); 457 } 458 } 459 } 460 461 bool CompositeAnimation::isAnimatingProperty(int property, bool isRunningNow) const 462 { 463 if (!m_keyframeAnimations.isEmpty()) { 464 m_keyframeAnimations.checkConsistency(); 465 AnimationNameMap::const_iterator animationsEnd = m_keyframeAnimations.end(); 466 for (AnimationNameMap::const_iterator it = m_keyframeAnimations.begin(); it != animationsEnd; ++it) { 467 KeyframeAnimation* anim = it->second.get(); 468 if (anim && anim->isAnimatingProperty(property, isRunningNow)) 469 return true; 470 } 471 } 472 473 if (!m_transitions.isEmpty()) { 474 CSSPropertyTransitionsMap::const_iterator transitionsEnd = m_transitions.end(); 475 for (CSSPropertyTransitionsMap::const_iterator it = m_transitions.begin(); it != transitionsEnd; ++it) { 476 ImplicitAnimation* anim = it->second.get(); 477 if (anim && anim->isAnimatingProperty(property, isRunningNow)) 478 return true; 479 } 480 } 481 return false; 482 } 483 484 bool CompositeAnimation::pauseAnimationAtTime(const AtomicString& name, double t) 485 { 486 if (!name) 487 return false; 488 489 m_keyframeAnimations.checkConsistency(); 490 491 RefPtr<KeyframeAnimation> keyframeAnim = m_keyframeAnimations.get(name.impl()); 492 if (!keyframeAnim || !keyframeAnim->running()) 493 return false; 494 495 int count = keyframeAnim->m_animation->iterationCount(); 496 if ((t >= 0.0) && (!count || (t <= count * keyframeAnim->duration()))) { 497 keyframeAnim->freezeAtTime(t); 498 return true; 499 } 500 501 return false; 502 } 503 504 bool CompositeAnimation::pauseTransitionAtTime(int property, double t) 505 { 506 if ((property < firstCSSProperty) || (property >= firstCSSProperty + numCSSProperties)) 507 return false; 508 509 ImplicitAnimation* implAnim = m_transitions.get(property).get(); 510 if (!implAnim || !implAnim->running()) 511 return false; 512 513 if ((t >= 0.0) && (t <= implAnim->duration())) { 514 implAnim->freezeAtTime(t); 515 return true; 516 } 517 518 return false; 519 } 520 521 unsigned CompositeAnimation::numberOfActiveAnimations() const 522 { 523 unsigned count = 0; 524 525 if (!m_keyframeAnimations.isEmpty()) { 526 m_keyframeAnimations.checkConsistency(); 527 AnimationNameMap::const_iterator animationsEnd = m_keyframeAnimations.end(); 528 for (AnimationNameMap::const_iterator it = m_keyframeAnimations.begin(); it != animationsEnd; ++it) { 529 KeyframeAnimation* anim = it->second.get(); 530 if (anim->running()) 531 ++count; 532 } 533 } 534 535 if (!m_transitions.isEmpty()) { 536 CSSPropertyTransitionsMap::const_iterator transitionsEnd = m_transitions.end(); 537 for (CSSPropertyTransitionsMap::const_iterator it = m_transitions.begin(); it != transitionsEnd; ++it) { 538 ImplicitAnimation* anim = it->second.get(); 539 if (anim->running()) 540 ++count; 541 } 542 } 543 544 return count; 545 } 546 547 } // namespace WebCore 548