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