OLD | NEW |
| (Empty) |
1 /* | |
2 * Copyright (C) 2007, 2012 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/KeyframeAnimation.h" | |
31 | |
32 #include "CSSPropertyNames.h" | |
33 #include "core/css/resolver/StyleResolver.h" | |
34 #include "core/events/ThreadLocalEventNames.h" | |
35 #include "core/frame/UseCounter.h" | |
36 #include "core/frame/animation/AnimationControllerPrivate.h" | |
37 #include "core/frame/animation/CSSPropertyAnimation.h" | |
38 #include "core/frame/animation/CompositeAnimation.h" | |
39 #include "core/rendering/RenderBoxModelObject.h" | |
40 #include "core/rendering/style/RenderStyle.h" | |
41 #include "public/platform/Platform.h" | |
42 | |
43 using namespace std; | |
44 | |
45 namespace WebCore { | |
46 | |
47 KeyframeAnimation::KeyframeAnimation(const CSSAnimationData* animation, RenderOb
ject& renderer, int index, CompositeAnimation* compAnim, RenderStyle& unanimated
Style) | |
48 : AnimationBase(animation, renderer, compAnim) | |
49 , m_keyframes(renderer, animation->name()) | |
50 , m_index(index) | |
51 , m_startEventDispatched(false) | |
52 , m_unanimatedStyle(unanimatedStyle) | |
53 { | |
54 // Get the keyframe RenderStyles | |
55 if (m_object && m_object->node() && m_object->node()->isElementNode()) | |
56 m_object->document().ensureStyleResolver().keyframeStylesForAnimation(to
Element(m_object->node()), unanimatedStyle, m_keyframes); | |
57 | |
58 // Update the m_transformFunctionListValid flag based on whether the functio
n lists in the keyframes match. | |
59 validateTransformFunctionList(); | |
60 checkForMatchingFilterFunctionLists(); | |
61 HashSet<CSSPropertyID>::const_iterator endProperties = m_keyframes.endProper
ties(); | |
62 for (HashSet<CSSPropertyID>::const_iterator it = m_keyframes.beginProperties
(); it != endProperties; ++it) | |
63 blink::Platform::current()->histogramSparse("WebCore.Animation.CSSProper
ties", UseCounter::mapCSSPropertyIdToCSSSampleIdForHistogram(*it)); | |
64 } | |
65 | |
66 KeyframeAnimation::~KeyframeAnimation() | |
67 { | |
68 // Make sure to tell the renderer that we are ending. This will make sure an
y accelerated animations are removed. | |
69 if (!postActive()) | |
70 endAnimation(); | |
71 } | |
72 | |
73 void KeyframeAnimation::fetchIntervalEndpointsForProperty(CSSPropertyID property
, const RenderStyle*& fromStyle, const RenderStyle*& toStyle, double& prog) cons
t | |
74 { | |
75 // Find the first key | |
76 double elapsedTime = getElapsedTime(); | |
77 if (m_animation->duration() && m_animation->iterationCount() != CSSAnimation
Data::IterationCountInfinite) | |
78 elapsedTime = min(elapsedTime, m_animation->duration() * m_animation->it
erationCount()); | |
79 | |
80 const double fractionalTime = this->fractionalTime(1, elapsedTime, 0); | |
81 | |
82 size_t numKeyframes = m_keyframes.size(); | |
83 if (!numKeyframes) | |
84 return; | |
85 | |
86 ASSERT(!m_keyframes[0].key()); | |
87 ASSERT(m_keyframes[m_keyframes.size() - 1].key() == 1); | |
88 | |
89 size_t currentIndex = 0; | |
90 size_t firstIndex = 0; | |
91 size_t lastIndex = numKeyframes - 1; | |
92 size_t distance = numKeyframes; | |
93 | |
94 // Find keyframe that is closest to elapsed time. | |
95 while (distance > 1) { | |
96 currentIndex = (lastIndex + firstIndex) >> 1; | |
97 double key = m_keyframes[currentIndex].key(); | |
98 distance = lastIndex - currentIndex; | |
99 | |
100 if (key < fractionalTime) { | |
101 if (distance < 2) | |
102 currentIndex++; | |
103 firstIndex = currentIndex; | |
104 } else { | |
105 lastIndex = currentIndex; | |
106 } | |
107 } | |
108 | |
109 int prevIndex = -1; | |
110 int nextIndex = -1; | |
111 | |
112 // Iterate forward to find next keyframe that is used to animate CSS propert
y. | |
113 for (size_t i = currentIndex; i < numKeyframes; ++i) { | |
114 const KeyframeValue& keyFrame = m_keyframes[i]; | |
115 if (keyFrame.key() > fractionalTime && keyFrame.containsProperty(propert
y)) { | |
116 nextIndex = i; | |
117 break; | |
118 } | |
119 } | |
120 | |
121 // Iterate backward to find previous keyframe. | |
122 for (int i = currentIndex; i >= 0; --i) { | |
123 const KeyframeValue& keyFrame = m_keyframes[i]; | |
124 if (keyFrame.key() <= fractionalTime && keyFrame.containsProperty(proper
ty)) { | |
125 prevIndex = i; | |
126 break; | |
127 } | |
128 } | |
129 | |
130 double scale = 1; | |
131 double offset = 0; | |
132 | |
133 if (prevIndex == -1) | |
134 prevIndex = 0; | |
135 | |
136 if (nextIndex == -1) | |
137 nextIndex = numKeyframes - 1; | |
138 | |
139 const KeyframeValue& prevKeyframe = m_keyframes[prevIndex]; | |
140 const KeyframeValue& nextKeyframe = m_keyframes[nextIndex]; | |
141 | |
142 fromStyle = prevKeyframe.style(); | |
143 toStyle = nextKeyframe.style(); | |
144 | |
145 offset = prevKeyframe.key(); | |
146 scale = 1.0 / (nextKeyframe.key() - prevKeyframe.key()); | |
147 // A scale of infinity is handled in AnimationBase::fractionalTime(). | |
148 ASSERT(scale >= 0 && (!std::isinf(scale) || prevIndex == nextIndex)); | |
149 | |
150 prog = progress(scale, offset, KeyframeValue::timingFunction(*prevKeyframe.s
tyle())); | |
151 } | |
152 | |
153 void KeyframeAnimation::animate(CompositeAnimation*, RenderObject*, const Render
Style*, RenderStyle* targetStyle, RefPtr<RenderStyle>& animatedStyle) | |
154 { | |
155 // Fire the start timeout if needed | |
156 fireAnimationEventsIfNeeded(); | |
157 | |
158 // If we have not yet started, we will not have a valid start time, so just
start the animation if needed. | |
159 if (isNew() && m_animation->playState() == AnimPlayStatePlaying) | |
160 updateStateMachine(AnimationStateInputStartAnimation, -1); | |
161 | |
162 // If we get this far and the animation is done, it means we are cleaning up
a just finished animation. | |
163 // If so, we need to send back the targetStyle. | |
164 if (postActive()) { | |
165 if (!animatedStyle) | |
166 animatedStyle = const_cast<RenderStyle*>(targetStyle); | |
167 return; | |
168 } | |
169 | |
170 // If we are waiting for the start timer, we don't want to change the style
yet. | |
171 // Special case 1 - if the delay time is 0, then we do want to set the first
frame of the | |
172 // animation right away. This avoids a flash when the animation starts. | |
173 // Special case 2 - if there is a backwards fill mode, then we want to conti
nue | |
174 // through to the style blend so that we get the fromStyle. | |
175 if (waitingToStart() && m_animation->delay() > 0 && !m_animation->fillsBackw
ards()) | |
176 return; | |
177 | |
178 // If we have no keyframes, don't animate. | |
179 if (!m_keyframes.size()) { | |
180 updateStateMachine(AnimationStateInputEndAnimation, -1); | |
181 return; | |
182 } | |
183 | |
184 // Run a cycle of animation. | |
185 // We know we will need a new render style, so make one if needed. | |
186 if (!animatedStyle) | |
187 animatedStyle = RenderStyle::clone(targetStyle); | |
188 | |
189 // FIXME: we need to be more efficient about determining which keyframes we
are animating between. | |
190 // We should cache the last pair or something. | |
191 HashSet<CSSPropertyID>::const_iterator endProperties = m_keyframes.endProper
ties(); | |
192 for (HashSet<CSSPropertyID>::const_iterator it = m_keyframes.beginProperties
(); it != endProperties; ++it) { | |
193 // Get the from/to styles and progress between | |
194 const RenderStyle* fromStyle = 0; | |
195 const RenderStyle* toStyle = 0; | |
196 double progress = 0.0; | |
197 fetchIntervalEndpointsForProperty(*it, fromStyle, toStyle, progress); | |
198 | |
199 bool needsAnim = CSSPropertyAnimation::blendProperties(this, *it, animat
edStyle.get(), fromStyle, toStyle, progress); | |
200 if (!needsAnim) | |
201 // If we are running an accelerated animation, set a flag in the sty
le | |
202 // to indicate it. This can be used to make sure we get an updated | |
203 // style for hit testing, etc. | |
204 animatedStyle->setIsRunningAcceleratedAnimation(); | |
205 } | |
206 } | |
207 | |
208 void KeyframeAnimation::getAnimatedStyle(RefPtr<RenderStyle>& animatedStyle) | |
209 { | |
210 // If we're in the delay phase and we're not backwards filling, tell the cal
ler | |
211 // to use the current style. | |
212 if (waitingToStart() && m_animation->delay() > 0 && !m_animation->fillsBackw
ards()) | |
213 return; | |
214 | |
215 if (!m_keyframes.size()) | |
216 return; | |
217 | |
218 if (!animatedStyle) | |
219 animatedStyle = RenderStyle::clone(m_object->style()); | |
220 | |
221 HashSet<CSSPropertyID>::const_iterator endProperties = m_keyframes.endProper
ties(); | |
222 for (HashSet<CSSPropertyID>::const_iterator it = m_keyframes.beginProperties
(); it != endProperties; ++it) { | |
223 // Get the from/to styles and progress between | |
224 const RenderStyle* fromStyle = 0; | |
225 const RenderStyle* toStyle = 0; | |
226 double progress = 0.0; | |
227 fetchIntervalEndpointsForProperty(*it, fromStyle, toStyle, progress); | |
228 | |
229 CSSPropertyAnimation::blendProperties(this, *it, animatedStyle.get(), fr
omStyle, toStyle, progress); | |
230 } | |
231 } | |
232 | |
233 bool KeyframeAnimation::hasAnimationForProperty(CSSPropertyID property) const | |
234 { | |
235 return m_keyframes.containsProperty(property); | |
236 } | |
237 | |
238 void KeyframeAnimation::startAnimation(double timeOffset) | |
239 { | |
240 if (m_object && m_object->compositingState() == PaintsIntoOwnBacking) | |
241 m_isAccelerated = toRenderBoxModelObject(m_object)->startAnimation(timeO
ffset, m_animation.get(), m_keyframes); | |
242 } | |
243 | |
244 void KeyframeAnimation::pauseAnimation(double timeOffset) | |
245 { | |
246 if (!m_object) | |
247 return; | |
248 | |
249 if (m_object && m_object->compositingState() == PaintsIntoOwnBacking && isAc
celerated()) | |
250 toRenderBoxModelObject(m_object)->animationPaused(timeOffset, m_keyframe
s.animationName()); | |
251 | |
252 // Restore the original (unanimated) style | |
253 if (!paused()) | |
254 setNeedsStyleRecalc(m_object->node()); | |
255 } | |
256 | |
257 void KeyframeAnimation::endAnimation() | |
258 { | |
259 if (!m_object) | |
260 return; | |
261 | |
262 if (m_object && m_object->compositingState() == PaintsIntoOwnBacking && isAc
celerated()) | |
263 toRenderBoxModelObject(m_object)->animationFinished(m_keyframes.animatio
nName()); | |
264 m_isAccelerated = false; | |
265 | |
266 // Restore the original (unanimated) style | |
267 if (!paused()) | |
268 setNeedsStyleRecalc(m_object->node()); | |
269 } | |
270 | |
271 bool KeyframeAnimation::shouldSendEventForListener(Document::ListenerType listen
erType) const | |
272 { | |
273 return m_object->document().hasListenerType(listenerType); | |
274 } | |
275 | |
276 void KeyframeAnimation::onAnimationStart(double elapsedTime) | |
277 { | |
278 sendAnimationEvent(EventTypeNames::animationstart, elapsedTime); | |
279 } | |
280 | |
281 void KeyframeAnimation::onAnimationIteration(double elapsedTime) | |
282 { | |
283 sendAnimationEvent(EventTypeNames::animationiteration, elapsedTime); | |
284 } | |
285 | |
286 void KeyframeAnimation::onAnimationEnd(double elapsedTime) | |
287 { | |
288 sendAnimationEvent(EventTypeNames::animationend, elapsedTime); | |
289 // End the animation if we don't fill forwards. Forward filling | |
290 // animations are ended properly in the class destructor. | |
291 if (!m_animation->fillsForwards()) | |
292 endAnimation(); | |
293 } | |
294 | |
295 bool KeyframeAnimation::sendAnimationEvent(const AtomicString& eventType, double
elapsedTime) | |
296 { | |
297 Document::ListenerType listenerType; | |
298 if (eventType == EventTypeNames::animationiteration) | |
299 listenerType = Document::ANIMATIONITERATION_LISTENER; | |
300 else if (eventType == EventTypeNames::animationend) | |
301 listenerType = Document::ANIMATIONEND_LISTENER; | |
302 else { | |
303 ASSERT(eventType == EventTypeNames::animationstart); | |
304 if (m_startEventDispatched) | |
305 return false; | |
306 m_startEventDispatched = true; | |
307 listenerType = Document::ANIMATIONSTART_LISTENER; | |
308 } | |
309 | |
310 if (shouldSendEventForListener(listenerType)) { | |
311 // Dispatch the event | |
312 RefPtr<Element> element; | |
313 if (m_object->node() && m_object->node()->isElementNode()) | |
314 element = toElement(m_object->node()); | |
315 | |
316 if (!element) | |
317 return false; | |
318 | |
319 // Schedule event handling | |
320 m_compAnim->animationController()->addEventToDispatch(element, eventType
, m_keyframes.animationName(), elapsedTime); | |
321 | |
322 // Restore the original (unanimated) style | |
323 if (eventType == EventTypeNames::animationend && element->renderer()) | |
324 setNeedsStyleRecalc(element.get()); | |
325 | |
326 return true; // Did dispatch an event | |
327 } | |
328 | |
329 return false; // Did not dispatch an event | |
330 } | |
331 | |
332 void KeyframeAnimation::overrideAnimations() | |
333 { | |
334 // This will override implicit animations that match the properties in the k
eyframe animation | |
335 HashSet<CSSPropertyID>::const_iterator end = m_keyframes.endProperties(); | |
336 for (HashSet<CSSPropertyID>::const_iterator it = m_keyframes.beginProperties
(); it != end; ++it) | |
337 compositeAnimation()->overrideImplicitAnimations(*it); | |
338 } | |
339 | |
340 void KeyframeAnimation::resumeOverriddenAnimations() | |
341 { | |
342 // This will resume overridden implicit animations | |
343 HashSet<CSSPropertyID>::const_iterator end = m_keyframes.endProperties(); | |
344 for (HashSet<CSSPropertyID>::const_iterator it = m_keyframes.beginProperties
(); it != end; ++it) | |
345 compositeAnimation()->resumeOverriddenImplicitAnimations(*it); | |
346 } | |
347 | |
348 bool KeyframeAnimation::affectsProperty(CSSPropertyID property) const | |
349 { | |
350 return m_keyframes.containsProperty(property); | |
351 } | |
352 | |
353 void KeyframeAnimation::validateTransformFunctionList() | |
354 { | |
355 m_transformFunctionListValid = false; | |
356 | |
357 if (m_keyframes.size() < 2 || !m_keyframes.containsProperty(CSSPropertyWebki
tTransform)) | |
358 return; | |
359 | |
360 // Empty transforms match anything, so find the first non-empty entry as the
reference | |
361 size_t numKeyframes = m_keyframes.size(); | |
362 size_t firstNonEmptyTransformKeyframeIndex = numKeyframes; | |
363 | |
364 for (size_t i = 0; i < numKeyframes; ++i) { | |
365 const KeyframeValue& currentKeyframe = m_keyframes[i]; | |
366 if (currentKeyframe.style()->transform().operations().size()) { | |
367 firstNonEmptyTransformKeyframeIndex = i; | |
368 break; | |
369 } | |
370 } | |
371 | |
372 if (firstNonEmptyTransformKeyframeIndex == numKeyframes) | |
373 return; | |
374 | |
375 const TransformOperations* firstVal = &m_keyframes[firstNonEmptyTransformKey
frameIndex].style()->transform(); | |
376 | |
377 // See if the keyframes are valid | |
378 for (size_t i = firstNonEmptyTransformKeyframeIndex + 1; i < numKeyframes; +
+i) { | |
379 const KeyframeValue& currentKeyframe = m_keyframes[i]; | |
380 const TransformOperations* val = ¤tKeyframe.style()->transform(); | |
381 | |
382 // An emtpy transform list matches anything. | |
383 if (val->operations().isEmpty()) | |
384 continue; | |
385 | |
386 if (!firstVal->operationsMatch(*val)) | |
387 return; | |
388 } | |
389 | |
390 // Keyframes are valid | |
391 m_transformFunctionListValid = true; | |
392 } | |
393 | |
394 void KeyframeAnimation::checkForMatchingFilterFunctionLists() | |
395 { | |
396 m_filterFunctionListsMatch = false; | |
397 | |
398 if (m_keyframes.size() < 2 || !m_keyframes.containsProperty(CSSPropertyWebki
tFilter)) | |
399 return; | |
400 | |
401 // Empty filters match anything, so find the first non-empty entry as the re
ference | |
402 size_t numKeyframes = m_keyframes.size(); | |
403 size_t firstNonEmptyFilterKeyframeIndex = numKeyframes; | |
404 | |
405 for (size_t i = 0; i < numKeyframes; ++i) { | |
406 const KeyframeValue& currentKeyframe = m_keyframes[i]; | |
407 if (currentKeyframe.style()->filter().operations().size()) { | |
408 firstNonEmptyFilterKeyframeIndex = i; | |
409 break; | |
410 } | |
411 } | |
412 | |
413 if (firstNonEmptyFilterKeyframeIndex == numKeyframes) | |
414 return; | |
415 | |
416 const FilterOperations* firstVal = &m_keyframes[firstNonEmptyFilterKeyframeI
ndex].style()->filter(); | |
417 | |
418 for (size_t i = firstNonEmptyFilterKeyframeIndex + 1; i < numKeyframes; ++i)
{ | |
419 const KeyframeValue& currentKeyframe = m_keyframes[i]; | |
420 const FilterOperations* val = ¤tKeyframe.style()->filter(); | |
421 | |
422 if (!firstVal->canInterpolateWith(*val)) | |
423 return; | |
424 } | |
425 | |
426 m_filterFunctionListsMatch = true; | |
427 } | |
428 | |
429 double KeyframeAnimation::timeToNextService() | |
430 { | |
431 double t = AnimationBase::timeToNextService(); | |
432 if (t != 0 || preActive()) | |
433 return t; | |
434 | |
435 // A return value of 0 means we need service. But if we only have accelerate
d animations we | |
436 // only need service at the end of the transition | |
437 HashSet<CSSPropertyID>::const_iterator endProperties = m_keyframes.endProper
ties(); | |
438 bool acceleratedPropertiesOnly = true; | |
439 | |
440 for (HashSet<CSSPropertyID>::const_iterator it = m_keyframes.beginProperties
(); it != endProperties; ++it) { | |
441 if (!CSSPropertyAnimation::animationOfPropertyIsAccelerated(*it) || !isA
ccelerated()) { | |
442 acceleratedPropertiesOnly = false; | |
443 break; | |
444 } | |
445 } | |
446 | |
447 if (acceleratedPropertiesOnly) { | |
448 bool isLooping; | |
449 getTimeToNextEvent(t, isLooping); | |
450 } | |
451 | |
452 return t; | |
453 } | |
454 | |
455 } // namespace WebCore | |
OLD | NEW |