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 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 "AnimationController.h"
31
32#include "AnimationBase.h"
33#include "AnimationControllerPrivate.h"
34#include "CSSParser.h"
35#include "CSSPropertyAnimation.h"
36#include "CompositeAnimation.h"
37#include "EventNames.h"
38#include "Frame.h"
39#include "FrameView.h"
40#include "Logging.h"
41#include "PseudoElement.h"
42#include "RenderView.h"
43#include "TransitionEvent.h"
44#include "WebKitAnimationEvent.h"
45#include "WebKitTransitionEvent.h"
46#include <wtf/CurrentTime.h>
47
48namespace WebCore {
49
50static const double cAnimationTimerDelay = 0.025;
51static const double cBeginAnimationUpdateTimeNotSet = -1;
52
53AnimationControllerPrivate::AnimationControllerPrivate(Frame& frame)
54    : m_animationTimer(this, &AnimationControllerPrivate::animationTimerFired)
55    , m_updateStyleIfNeededDispatcher(this, &AnimationControllerPrivate::updateStyleIfNeededDispatcherFired)
56    , m_frame(frame)
57    , m_beginAnimationUpdateTime(cBeginAnimationUpdateTimeNotSet)
58    , m_animationsWaitingForStyle()
59    , m_animationsWaitingForStartTimeResponse()
60    , m_waitingForAsyncStartNotification(false)
61    , m_isSuspended(false)
62    , m_allowsNewAnimationsWhileSuspended(false)
63{
64}
65
66AnimationControllerPrivate::~AnimationControllerPrivate()
67{
68}
69
70CompositeAnimation& AnimationControllerPrivate::ensureCompositeAnimation(RenderElement* renderer)
71{
72    auto result = m_compositeAnimations.add(renderer, nullptr);
73    if (result.isNewEntry)
74        result.iterator->value = CompositeAnimation::create(this);
75    return *result.iterator->value;
76}
77
78bool AnimationControllerPrivate::clear(RenderElement* renderer)
79{
80    // Return false if we didn't do anything OR we are suspended (so we don't try to
81    // do a setNeedsStyleRecalc() when suspended).
82    RefPtr<CompositeAnimation> animation = m_compositeAnimations.take(renderer);
83    if (!animation)
84        return false;
85    animation->clearRenderer();
86    return animation->isSuspended();
87}
88
89double AnimationControllerPrivate::updateAnimations(SetChanged callSetChanged/* = DoNotCallSetChanged*/)
90{
91    double timeToNextService = -1;
92    bool calledSetChanged = false;
93
94    auto end = m_compositeAnimations.end();
95    for (auto it = m_compositeAnimations.begin(); it != end; ++it) {
96        CompositeAnimation& animation = *it->value;
97        if (!animation.isSuspended() && animation.hasAnimations()) {
98            double t = animation.timeToNextService();
99            if (t != -1 && (t < timeToNextService || timeToNextService == -1))
100                timeToNextService = t;
101            if (!timeToNextService) {
102                if (callSetChanged != CallSetChanged)
103                    break;
104                Element* element = it->key->element();
105                ASSERT(element);
106                ASSERT(!element->document().inPageCache());
107                element->setNeedsStyleRecalc(SyntheticStyleChange);
108                calledSetChanged = true;
109            }
110        }
111    }
112
113    if (calledSetChanged)
114        m_frame.document()->updateStyleIfNeeded();
115
116    return timeToNextService;
117}
118
119void AnimationControllerPrivate::updateAnimationTimerForRenderer(RenderElement* renderer)
120{
121    double timeToNextService = 0;
122
123    const CompositeAnimation* compositeAnimation = m_compositeAnimations.get(renderer);
124    if (!compositeAnimation->isSuspended() && compositeAnimation->hasAnimations())
125        timeToNextService = compositeAnimation->timeToNextService();
126
127    if (m_animationTimer.isActive() && (m_animationTimer.repeatInterval() || m_animationTimer.nextFireInterval() <= timeToNextService))
128        return;
129
130    m_animationTimer.startOneShot(timeToNextService);
131}
132
133void AnimationControllerPrivate::updateAnimationTimer(SetChanged callSetChanged/* = DoNotCallSetChanged*/)
134{
135    double timeToNextService = updateAnimations(callSetChanged);
136
137    LOG(Animations, "updateAnimationTimer: timeToNextService is %.2f", timeToNextService);
138
139    // If we want service immediately, we start a repeating timer to reduce the overhead of starting
140    if (!timeToNextService) {
141        if (!m_animationTimer.isActive() || m_animationTimer.repeatInterval() == 0)
142            m_animationTimer.startRepeating(cAnimationTimerDelay);
143        return;
144    }
145
146    // If we don't need service, we want to make sure the timer is no longer running
147    if (timeToNextService < 0) {
148        if (m_animationTimer.isActive())
149            m_animationTimer.stop();
150        return;
151    }
152
153    // Otherwise, we want to start a one-shot timer so we get here again
154    m_animationTimer.startOneShot(timeToNextService);
155}
156
157void AnimationControllerPrivate::updateStyleIfNeededDispatcherFired(Timer<AnimationControllerPrivate>&)
158{
159    fireEventsAndUpdateStyle();
160}
161
162void AnimationControllerPrivate::fireEventsAndUpdateStyle()
163{
164    // Protect the frame from getting destroyed in the event handler
165    Ref<Frame> protector(m_frame);
166
167    bool updateStyle = !m_eventsToDispatch.isEmpty() || !m_elementChangesToDispatch.isEmpty();
168
169    // fire all the events
170    Vector<EventToDispatch> eventsToDispatch = WTF::move(m_eventsToDispatch);
171    Vector<EventToDispatch>::const_iterator eventsToDispatchEnd = eventsToDispatch.end();
172    for (Vector<EventToDispatch>::const_iterator it = eventsToDispatch.begin(); it != eventsToDispatchEnd; ++it) {
173        Element* element = it->element.get();
174        if (it->eventType == eventNames().transitionendEvent)
175            element->dispatchEvent(TransitionEvent::create(it->eventType, it->name, it->elapsedTime, PseudoElement::pseudoElementNameForEvents(element->pseudoId())));
176        else
177            element->dispatchEvent(WebKitAnimationEvent::create(it->eventType, it->name, it->elapsedTime));
178    }
179
180    for (unsigned i = 0, size = m_elementChangesToDispatch.size(); i < size; ++i)
181        m_elementChangesToDispatch[i]->setNeedsStyleRecalc(SyntheticStyleChange);
182
183    m_elementChangesToDispatch.clear();
184
185    if (updateStyle)
186        m_frame.document()->updateStyleIfNeeded();
187}
188
189void AnimationControllerPrivate::startUpdateStyleIfNeededDispatcher()
190{
191    if (!m_updateStyleIfNeededDispatcher.isActive())
192        m_updateStyleIfNeededDispatcher.startOneShot(0);
193}
194
195void AnimationControllerPrivate::addEventToDispatch(PassRefPtr<Element> element, const AtomicString& eventType, const String& name, double elapsedTime)
196{
197    m_eventsToDispatch.grow(m_eventsToDispatch.size()+1);
198    EventToDispatch& event = m_eventsToDispatch[m_eventsToDispatch.size()-1];
199    event.element = element;
200    event.eventType = eventType;
201    event.name = name;
202    event.elapsedTime = elapsedTime;
203
204    startUpdateStyleIfNeededDispatcher();
205}
206
207void AnimationControllerPrivate::addElementChangeToDispatch(PassRef<Element> element)
208{
209    m_elementChangesToDispatch.append(WTF::move(element));
210    ASSERT(!m_elementChangesToDispatch.last()->document().inPageCache());
211    startUpdateStyleIfNeededDispatcher();
212}
213
214#if ENABLE(REQUEST_ANIMATION_FRAME)
215void AnimationControllerPrivate::animationFrameCallbackFired()
216{
217    double timeToNextService = updateAnimations(CallSetChanged);
218
219    if (timeToNextService >= 0)
220        m_frame.document()->view()->scheduleAnimation();
221}
222#endif
223
224void AnimationControllerPrivate::animationTimerFired(Timer<AnimationControllerPrivate>&)
225{
226    // Make sure animationUpdateTime is updated, so that it is current even if no
227    // styleChange has happened (e.g. accelerated animations)
228    setBeginAnimationUpdateTime(cBeginAnimationUpdateTimeNotSet);
229
230    // When the timer fires, all we do is call setChanged on all DOM nodes with running animations and then do an immediate
231    // updateStyleIfNeeded.  It will then call back to us with new information.
232    updateAnimationTimer(CallSetChanged);
233
234    // Fire events right away, to avoid a flash of unanimated style after an animation completes, and before
235    // the 'end' event fires.
236    fireEventsAndUpdateStyle();
237}
238
239bool AnimationControllerPrivate::isRunningAnimationOnRenderer(RenderElement* renderer, CSSPropertyID property, AnimationBase::RunningState runningState) const
240{
241    const CompositeAnimation* animation = m_compositeAnimations.get(renderer);
242    return animation && animation->isAnimatingProperty(property, false, runningState);
243}
244
245bool AnimationControllerPrivate::isRunningAcceleratedAnimationOnRenderer(RenderElement* renderer, CSSPropertyID property, AnimationBase::RunningState runningState) const
246{
247    const CompositeAnimation* animation = m_compositeAnimations.get(renderer);
248    return animation && animation->isAnimatingProperty(property, true, runningState);
249}
250
251void AnimationControllerPrivate::suspendAnimations()
252{
253    if (isSuspended())
254        return;
255
256    suspendAnimationsForDocument(m_frame.document());
257
258    // Traverse subframes
259    for (Frame* child = m_frame.tree().firstChild(); child; child = child->tree().nextSibling())
260        child->animation().suspendAnimations();
261
262    m_isSuspended = true;
263}
264
265void AnimationControllerPrivate::resumeAnimations()
266{
267    if (!isSuspended())
268        return;
269
270    resumeAnimationsForDocument(m_frame.document());
271
272    // Traverse subframes
273    for (Frame* child = m_frame.tree().firstChild(); child; child = child->tree().nextSibling())
274        child->animation().resumeAnimations();
275
276    m_isSuspended = false;
277}
278
279void AnimationControllerPrivate::suspendAnimationsForDocument(Document* document)
280{
281    setBeginAnimationUpdateTime(cBeginAnimationUpdateTimeNotSet);
282
283    for (auto it = m_compositeAnimations.begin(), end = m_compositeAnimations.end(); it != end; ++it) {
284        if (&it->key->document() == document)
285            it->value->suspendAnimations();
286    }
287
288    updateAnimationTimer();
289}
290
291void AnimationControllerPrivate::resumeAnimationsForDocument(Document* document)
292{
293    setBeginAnimationUpdateTime(cBeginAnimationUpdateTimeNotSet);
294
295    for (auto it = m_compositeAnimations.begin(), end = m_compositeAnimations.end(); it != end; ++it) {
296        if (&it->key->document() == document)
297            it->value->resumeAnimations();
298    }
299
300    updateAnimationTimer();
301}
302
303void AnimationControllerPrivate::startAnimationsIfNotSuspended(Document* document)
304{
305    if (!isSuspended() || allowsNewAnimationsWhileSuspended())
306        resumeAnimationsForDocument(document);
307}
308
309void AnimationControllerPrivate::setAllowsNewAnimationsWhileSuspended(bool allowed)
310{
311    m_allowsNewAnimationsWhileSuspended = allowed;
312}
313
314bool AnimationControllerPrivate::pauseAnimationAtTime(RenderElement* renderer, const AtomicString& name, double t)
315{
316    if (!renderer)
317        return false;
318
319    CompositeAnimation& compositeAnimation = ensureCompositeAnimation(renderer);
320    if (compositeAnimation.pauseAnimationAtTime(name, t)) {
321        renderer->element()->setNeedsStyleRecalc(SyntheticStyleChange);
322        startUpdateStyleIfNeededDispatcher();
323        return true;
324    }
325
326    return false;
327}
328
329bool AnimationControllerPrivate::pauseTransitionAtTime(RenderElement* renderer, const String& property, double t)
330{
331    if (!renderer)
332        return false;
333
334    CompositeAnimation& compositeAnimation = ensureCompositeAnimation(renderer);
335    if (compositeAnimation.pauseTransitionAtTime(cssPropertyID(property), t)) {
336        renderer->element()->setNeedsStyleRecalc(SyntheticStyleChange);
337        startUpdateStyleIfNeededDispatcher();
338        return true;
339    }
340
341    return false;
342}
343
344double AnimationControllerPrivate::beginAnimationUpdateTime()
345{
346    if (m_beginAnimationUpdateTime == cBeginAnimationUpdateTimeNotSet)
347        m_beginAnimationUpdateTime = monotonicallyIncreasingTime();
348    return m_beginAnimationUpdateTime;
349}
350
351void AnimationControllerPrivate::endAnimationUpdate()
352{
353    styleAvailable();
354    if (!m_waitingForAsyncStartNotification)
355        startTimeResponse(beginAnimationUpdateTime());
356}
357
358void AnimationControllerPrivate::receivedStartTimeResponse(double time)
359{
360    m_waitingForAsyncStartNotification = false;
361    startTimeResponse(time);
362}
363
364PassRefPtr<RenderStyle> AnimationControllerPrivate::getAnimatedStyleForRenderer(RenderElement* renderer)
365{
366    if (!renderer)
367        return 0;
368
369    const CompositeAnimation* rendererAnimations = m_compositeAnimations.get(renderer);
370    if (!rendererAnimations)
371        return &renderer->style();
372
373    RefPtr<RenderStyle> animatingStyle = rendererAnimations->getAnimatedStyle();
374    if (!animatingStyle)
375        animatingStyle = &renderer->style();
376
377    return animatingStyle.release();
378}
379
380unsigned AnimationControllerPrivate::numberOfActiveAnimations(Document* document) const
381{
382    unsigned count = 0;
383
384    for (auto it = m_compositeAnimations.begin(), end = m_compositeAnimations.end(); it != end; ++it) {
385        if (&it->key->document() == document)
386            count += it->value->numberOfActiveAnimations();
387    }
388
389    return count;
390}
391
392void AnimationControllerPrivate::addToAnimationsWaitingForStyle(AnimationBase* animation)
393{
394    // Make sure this animation is not in the start time waiters
395    m_animationsWaitingForStartTimeResponse.remove(animation);
396
397    m_animationsWaitingForStyle.add(animation);
398}
399
400void AnimationControllerPrivate::removeFromAnimationsWaitingForStyle(AnimationBase* animationToRemove)
401{
402    m_animationsWaitingForStyle.remove(animationToRemove);
403}
404
405void AnimationControllerPrivate::styleAvailable()
406{
407    // Go through list of waiters and send them on their way
408    for (const auto& waitingAnimation : m_animationsWaitingForStyle)
409        waitingAnimation->styleAvailable();
410
411    m_animationsWaitingForStyle.clear();
412}
413
414void AnimationControllerPrivate::addToAnimationsWaitingForStartTimeResponse(AnimationBase* animation, bool willGetResponse)
415{
416    // If willGetResponse is true, it means this animation is actually waiting for a response
417    // (which will come in as a call to notifyAnimationStarted()).
418    // In that case we don't need to add it to this list. We just set a waitingForAResponse flag
419    // which says we are waiting for the response. If willGetResponse is false, this animation
420    // is not waiting for a response for itself, but rather for a notifyXXXStarted() call for
421    // another animation to which it will sync.
422    //
423    // When endAnimationUpdate() is called we check to see if the waitingForAResponse flag is
424    // true. If so, we just return and will do our work when the first notifyXXXStarted() call
425    // comes in. If it is false, we will not be getting a notifyXXXStarted() call, so we will
426    // do our work right away. In both cases we call the onAnimationStartResponse() method
427    // on each animation. In the first case we send in the time we got from notifyXXXStarted().
428    // In the second case, we just pass in the beginAnimationUpdateTime().
429    //
430    // This will synchronize all software and accelerated animations started in the same
431    // updateStyleIfNeeded cycle.
432    //
433
434    if (willGetResponse)
435        m_waitingForAsyncStartNotification = true;
436
437    m_animationsWaitingForStartTimeResponse.add(animation);
438}
439
440void AnimationControllerPrivate::removeFromAnimationsWaitingForStartTimeResponse(AnimationBase* animationToRemove)
441{
442    m_animationsWaitingForStartTimeResponse.remove(animationToRemove);
443
444    if (m_animationsWaitingForStartTimeResponse.isEmpty())
445        m_waitingForAsyncStartNotification = false;
446}
447
448void AnimationControllerPrivate::startTimeResponse(double time)
449{
450    // Go through list of waiters and send them on their way
451
452    for (const auto& animation : m_animationsWaitingForStartTimeResponse)
453        animation->onAnimationStartResponse(time);
454
455    m_animationsWaitingForStartTimeResponse.clear();
456    m_waitingForAsyncStartNotification = false;
457}
458
459void AnimationControllerPrivate::animationWillBeRemoved(AnimationBase* animation)
460{
461    removeFromAnimationsWaitingForStyle(animation);
462    removeFromAnimationsWaitingForStartTimeResponse(animation);
463}
464
465AnimationController::AnimationController(Frame& frame)
466    : m_data(std::make_unique<AnimationControllerPrivate>(frame))
467    , m_beginAnimationUpdateCount(0)
468{
469}
470
471AnimationController::~AnimationController()
472{
473}
474
475void AnimationController::cancelAnimations(RenderElement* renderer)
476{
477    if (!m_data->hasAnimations())
478        return;
479
480    if (m_data->clear(renderer)) {
481        Element* element = renderer->element();
482        ASSERT(!element || !element->document().inPageCache());
483        if (element)
484            element->setNeedsStyleRecalc(SyntheticStyleChange);
485    }
486}
487
488PassRef<RenderStyle> AnimationController::updateAnimations(RenderElement& renderer, PassRef<RenderStyle> newStyle)
489{
490    // Don't do anything if we're in the cache
491    if (renderer.document().inPageCache())
492        return newStyle;
493
494    RenderStyle* oldStyle = renderer.hasInitializedStyle() ? &renderer.style() : nullptr;
495
496    if ((!oldStyle || (!oldStyle->animations() && !oldStyle->transitions())) && (!newStyle.get().animations() && !newStyle.get().transitions()))
497        return newStyle;
498
499    // Don't run transitions when printing.
500    if (renderer.view().printing())
501        return newStyle;
502
503    // Fetch our current set of implicit animations from a hashtable.  We then compare them
504    // against the animations in the style and make sure we're in sync.  If destination values
505    // have changed, we reset the animation.  We then do a blend to get new values and we return
506    // a new style.
507
508    // We don't support anonymous pseudo elements like :first-line or :first-letter.
509    ASSERT(renderer.element());
510
511    Ref<RenderStyle> newStyleBeforeAnimation(WTF::move(newStyle));
512
513    CompositeAnimation& rendererAnimations = m_data->ensureCompositeAnimation(&renderer);
514    auto blendedStyle = rendererAnimations.animate(renderer, oldStyle, newStyleBeforeAnimation.get());
515
516    if (renderer.parent() || newStyleBeforeAnimation->animations() || (oldStyle && oldStyle->animations())) {
517        m_data->updateAnimationTimerForRenderer(&renderer);
518#if ENABLE(REQUEST_ANIMATION_FRAME)
519        renderer.view().frameView().scheduleAnimation();
520#endif
521    }
522
523    if (&blendedStyle.get() != &newStyleBeforeAnimation.get()) {
524        // If the animations/transitions change opacity or transform, we need to update
525        // the style to impose the stacking rules. Note that this is also
526        // done in StyleResolver::adjustRenderStyle().
527        if (blendedStyle.get().hasAutoZIndex() && (blendedStyle.get().opacity() < 1.0f || blendedStyle.get().hasTransform()))
528            blendedStyle.get().setZIndex(0);
529    }
530    return blendedStyle;
531}
532
533PassRefPtr<RenderStyle> AnimationController::getAnimatedStyleForRenderer(RenderElement* renderer)
534{
535    return m_data->getAnimatedStyleForRenderer(renderer);
536}
537
538void AnimationController::notifyAnimationStarted(RenderElement*, double startTime)
539{
540    m_data->receivedStartTimeResponse(startTime);
541}
542
543bool AnimationController::pauseAnimationAtTime(RenderElement* renderer, const AtomicString& name, double t)
544{
545    return m_data->pauseAnimationAtTime(renderer, name, t);
546}
547
548unsigned AnimationController::numberOfActiveAnimations(Document* document) const
549{
550    return m_data->numberOfActiveAnimations(document);
551}
552
553bool AnimationController::pauseTransitionAtTime(RenderElement* renderer, const String& property, double t)
554{
555    return m_data->pauseTransitionAtTime(renderer, property, t);
556}
557
558bool AnimationController::isRunningAnimationOnRenderer(RenderElement* renderer, CSSPropertyID property, AnimationBase::RunningState runningState) const
559{
560    return m_data->isRunningAnimationOnRenderer(renderer, property, runningState);
561}
562
563bool AnimationController::isRunningAcceleratedAnimationOnRenderer(RenderElement* renderer, CSSPropertyID property, AnimationBase::RunningState runningState) const
564{
565    return m_data->isRunningAcceleratedAnimationOnRenderer(renderer, property, runningState);
566}
567
568bool AnimationController::isSuspended() const
569{
570    return m_data->isSuspended();
571}
572
573void AnimationController::suspendAnimations()
574{
575    LOG(Animations, "controller is suspending animations");
576    m_data->suspendAnimations();
577}
578
579void AnimationController::resumeAnimations()
580{
581    LOG(Animations, "controller is resuming animations");
582    m_data->resumeAnimations();
583}
584
585bool AnimationController::allowsNewAnimationsWhileSuspended() const
586{
587    return m_data->allowsNewAnimationsWhileSuspended();
588}
589
590void AnimationController::setAllowsNewAnimationsWhileSuspended(bool allowed)
591{
592    m_data->setAllowsNewAnimationsWhileSuspended(allowed);
593}
594
595#if ENABLE(REQUEST_ANIMATION_FRAME)
596void AnimationController::serviceAnimations()
597{
598    m_data->animationFrameCallbackFired();
599}
600#endif
601
602void AnimationController::suspendAnimationsForDocument(Document* document)
603{
604    LOG(Animations, "suspending animations for document %p", document);
605    m_data->suspendAnimationsForDocument(document);
606}
607
608void AnimationController::resumeAnimationsForDocument(Document* document)
609{
610    LOG(Animations, "resuming animations for document %p", document);
611    m_data->resumeAnimationsForDocument(document);
612}
613
614void AnimationController::startAnimationsIfNotSuspended(Document* document)
615{
616    LOG(Animations, "animations may start for document %p", document);
617    m_data->startAnimationsIfNotSuspended(document);
618}
619
620void AnimationController::beginAnimationUpdate()
621{
622    if (!m_beginAnimationUpdateCount)
623        m_data->setBeginAnimationUpdateTime(cBeginAnimationUpdateTimeNotSet);
624    ++m_beginAnimationUpdateCount;
625}
626
627void AnimationController::endAnimationUpdate()
628{
629    ASSERT(m_beginAnimationUpdateCount > 0);
630    --m_beginAnimationUpdateCount;
631    if (!m_beginAnimationUpdateCount)
632        m_data->endAnimationUpdate();
633}
634
635bool AnimationController::supportsAcceleratedAnimationOfProperty(CSSPropertyID property)
636{
637    return CSSPropertyAnimation::animationOfPropertyIsAccelerated(property);
638}
639
640} // namespace WebCore
641