Home | History | Annotate | Download | only in animation
      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/frame/animation/AnimationController.h"
     31 
     32 #include "core/dom/PseudoElement.h"
     33 #include "core/events/ThreadLocalEventNames.h"
     34 #include "core/events/TransitionEvent.h"
     35 #include "core/events/WebKitAnimationEvent.h"
     36 #include "core/frame/Frame.h"
     37 #include "core/frame/FrameView.h"
     38 #include "core/page/Page.h"
     39 #include "core/frame/animation/AnimationBase.h"
     40 #include "core/frame/animation/AnimationControllerPrivate.h"
     41 #include "core/frame/animation/CSSPropertyAnimation.h"
     42 #include "core/frame/animation/CompositeAnimation.h"
     43 #include "core/rendering/RenderView.h"
     44 #include "wtf/CurrentTime.h"
     45 
     46 namespace WebCore {
     47 
     48 static const double cBeginAnimationUpdateTimeNotSet = -1;
     49 
     50 AnimationControllerPrivate::AnimationControllerPrivate(Frame* frame)
     51     : m_animationTimer(this, &AnimationControllerPrivate::animationTimerFired)
     52     , m_updateStyleIfNeededDispatcher(this, &AnimationControllerPrivate::updateStyleIfNeededDispatcherFired)
     53     , m_frame(frame)
     54     , m_beginAnimationUpdateTime(cBeginAnimationUpdateTimeNotSet)
     55     , m_animationsWaitingForStyle()
     56     , m_animationsWaitingForStartTimeResponse()
     57     , m_animationsWaitingForAsyncStartNotification()
     58 {
     59 }
     60 
     61 AnimationControllerPrivate::~AnimationControllerPrivate()
     62 {
     63 }
     64 
     65 PassRefPtr<CompositeAnimation> AnimationControllerPrivate::accessCompositeAnimation(RenderObject& renderer)
     66 {
     67     RefPtr<CompositeAnimation> animation = m_compositeAnimations.get(&renderer);
     68     if (!animation) {
     69         animation = CompositeAnimation::create(this);
     70         m_compositeAnimations.set(&renderer, animation);
     71     }
     72     return animation;
     73 }
     74 
     75 bool AnimationControllerPrivate::clear(RenderObject* renderer)
     76 {
     77     // Return false if we didn't do anything.
     78     PassRefPtr<CompositeAnimation> animation = m_compositeAnimations.take(renderer);
     79     if (!animation)
     80         return false;
     81     animation->clearRenderer();
     82     return true;
     83 }
     84 
     85 void AnimationControllerPrivate::updateAnimations(double& timeToNextService, double& timeToNextEvent, SetNeedsStyleRecalc callSetNeedsStyleRecalc/* = DoNotCallSetNeedsStyleRecalc*/)
     86 {
     87     double minTimeToNextService = -1;
     88     double minTimeToNextEvent = -1;
     89     bool updateStyleNeeded = false;
     90 
     91     RenderObjectAnimationMap::const_iterator animationsEnd = m_compositeAnimations.end();
     92     for (RenderObjectAnimationMap::const_iterator it = m_compositeAnimations.begin(); it != animationsEnd; ++it) {
     93         CompositeAnimation* compAnim = it->value.get();
     94         if (compAnim->hasAnimations()) {
     95             double t = compAnim->timeToNextService();
     96             if (t != -1 && (t < minTimeToNextService || minTimeToNextService == -1))
     97                 minTimeToNextService = t;
     98             double nextEvent = compAnim->timeToNextEvent();
     99             if (nextEvent != -1 && (nextEvent < minTimeToNextEvent || minTimeToNextEvent == -1))
    100                 minTimeToNextEvent = nextEvent;
    101             if (callSetNeedsStyleRecalc == CallSetNeedsStyleRecalc) {
    102                 if (!t) {
    103                     Node* node = it->key->node();
    104                     node->setNeedsStyleRecalc(LocalStyleChange, StyleChangeFromRenderer);
    105                     updateStyleNeeded = true;
    106                 }
    107             } else if (!minTimeToNextService && !minTimeToNextEvent) {
    108                 // Found the minimum values and do not need to mark for style recalc.
    109                 break;
    110             }
    111         }
    112     }
    113 
    114     if (updateStyleNeeded)
    115         m_frame->document()->updateStyleIfNeeded();
    116 
    117     timeToNextService = minTimeToNextService;
    118     timeToNextEvent = minTimeToNextEvent;
    119 }
    120 
    121 void AnimationControllerPrivate::scheduleServiceForRenderer(RenderObject& renderer)
    122 {
    123     double timeToNextService = -1;
    124     double timeToNextEvent = -1;
    125 
    126     RefPtr<CompositeAnimation> compAnim = m_compositeAnimations.get(&renderer);
    127     if (compAnim->hasAnimations()) {
    128         timeToNextService = compAnim->timeToNextService();
    129         timeToNextEvent = compAnim->timeToNextEvent();
    130     }
    131 
    132     if (timeToNextService >= 0)
    133         scheduleService(timeToNextService, timeToNextEvent);
    134 }
    135 
    136 void AnimationControllerPrivate::scheduleService()
    137 {
    138     double timeToNextService = -1;
    139     double timeToNextEvent = -1;
    140     updateAnimations(timeToNextService, timeToNextEvent, DoNotCallSetNeedsStyleRecalc);
    141     scheduleService(timeToNextService, timeToNextEvent);
    142 }
    143 
    144 void AnimationControllerPrivate::scheduleService(double timeToNextService, double timeToNextEvent)
    145 {
    146     if (!m_frame->page())
    147         return;
    148 
    149     bool visible = m_frame->page()->visibilityState() == WebCore::PageVisibilityStateVisible;
    150 
    151     // This std::max to 1 second limits how often we service animations on background tabs.
    152     // Without this, plus and gmail were recalculating style as every 200ms or even more
    153     // often, burning CPU needlessly for background tabs.
    154     // FIXME: Do we want to fire events at all on background tabs?
    155     if (!visible)
    156         timeToNextService = std::max(timeToNextEvent, 1.0);
    157 
    158     if (visible && !timeToNextService) {
    159         m_frame->document()->view()->scheduleAnimation();
    160         if (m_animationTimer.isActive())
    161             m_animationTimer.stop();
    162         return;
    163     }
    164 
    165     if (timeToNextService < 0) {
    166         if (m_animationTimer.isActive())
    167             m_animationTimer.stop();
    168         return;
    169     }
    170 
    171     if (m_animationTimer.isActive() && m_animationTimer.nextFireInterval() <= timeToNextService)
    172         return;
    173 
    174     m_animationTimer.startOneShot(timeToNextService);
    175 }
    176 
    177 void AnimationControllerPrivate::updateStyleIfNeededDispatcherFired(Timer<AnimationControllerPrivate>*)
    178 {
    179     fireEventsAndUpdateStyle();
    180 }
    181 
    182 void AnimationControllerPrivate::fireEventsAndUpdateStyle()
    183 {
    184     // Protect the frame from getting destroyed in the event handler
    185     RefPtr<Frame> protector = m_frame;
    186 
    187     bool updateStyle = !m_eventsToDispatch.isEmpty() || !m_nodeChangesToDispatch.isEmpty();
    188 
    189     // fire all the events
    190     Vector<EventToDispatch> eventsToDispatch = m_eventsToDispatch;
    191     m_eventsToDispatch.clear();
    192     Vector<EventToDispatch>::const_iterator eventsToDispatchEnd = eventsToDispatch.end();
    193     for (Vector<EventToDispatch>::const_iterator it = eventsToDispatch.begin(); it != eventsToDispatchEnd; ++it) {
    194         Element* element = it->element.get();
    195         if (it->eventType == EventTypeNames::transitionend)
    196             element->dispatchEvent(TransitionEvent::create(it->eventType, it->name, it->elapsedTime, PseudoElement::pseudoElementNameForEvents(element->pseudoId())));
    197         else
    198             element->dispatchEvent(WebKitAnimationEvent::create(it->eventType, it->name, it->elapsedTime));
    199     }
    200 
    201     // call setChanged on all the elements
    202     Vector<RefPtr<Node> >::const_iterator nodeChangesToDispatchEnd = m_nodeChangesToDispatch.end();
    203     for (Vector<RefPtr<Node> >::const_iterator it = m_nodeChangesToDispatch.begin(); it != nodeChangesToDispatchEnd; ++it)
    204         (*it)->setNeedsStyleRecalc(LocalStyleChange, StyleChangeFromRenderer);
    205 
    206     m_nodeChangesToDispatch.clear();
    207 
    208     if (updateStyle && m_frame)
    209         m_frame->document()->updateStyleIfNeeded();
    210 }
    211 
    212 void AnimationControllerPrivate::startUpdateStyleIfNeededDispatcher()
    213 {
    214     if (!m_updateStyleIfNeededDispatcher.isActive())
    215         m_updateStyleIfNeededDispatcher.startOneShot(0);
    216 }
    217 
    218 void AnimationControllerPrivate::addEventToDispatch(PassRefPtr<Element> element, const AtomicString& eventType, const String& name, double elapsedTime)
    219 {
    220     m_eventsToDispatch.grow(m_eventsToDispatch.size()+1);
    221     EventToDispatch& event = m_eventsToDispatch[m_eventsToDispatch.size()-1];
    222     event.element = element;
    223     event.eventType = eventType;
    224     event.name = name;
    225     event.elapsedTime = elapsedTime;
    226 
    227     startUpdateStyleIfNeededDispatcher();
    228 }
    229 
    230 void AnimationControllerPrivate::addNodeChangeToDispatch(PassRefPtr<Node> node)
    231 {
    232     if (!node)
    233         return;
    234 
    235     m_nodeChangesToDispatch.append(node);
    236     startUpdateStyleIfNeededDispatcher();
    237 }
    238 
    239 void AnimationControllerPrivate::serviceAnimations()
    240 {
    241     double timeToNextService = -1;
    242     double timeToNextEvent = -1;
    243     updateAnimations(timeToNextService, timeToNextEvent, CallSetNeedsStyleRecalc);
    244     scheduleService(timeToNextService, timeToNextEvent);
    245 
    246     // Fire events right away, to avoid a flash of unanimated style after an animation completes, and before
    247     // the 'end' event fires.
    248     fireEventsAndUpdateStyle();
    249 }
    250 
    251 void AnimationControllerPrivate::animationTimerFired(Timer<AnimationControllerPrivate>*)
    252 {
    253     // Make sure animationUpdateTime is updated, so that it is current even if no
    254     // styleChange has happened (e.g. accelerated animations)
    255     setBeginAnimationUpdateTime(cBeginAnimationUpdateTimeNotSet);
    256     serviceAnimations();
    257 }
    258 
    259 bool AnimationControllerPrivate::isRunningAnimationOnRenderer(RenderObject* renderer, CSSPropertyID property, bool isRunningNow) const
    260 {
    261     RefPtr<CompositeAnimation> animation = m_compositeAnimations.get(renderer);
    262     if (!animation)
    263         return false;
    264 
    265     return animation->isAnimatingProperty(property, false, isRunningNow);
    266 }
    267 
    268 bool AnimationControllerPrivate::isRunningAcceleratableAnimationOnRenderer(RenderObject *renderer) const
    269 {
    270     RefPtr<CompositeAnimation> animation = m_compositeAnimations.get(renderer);
    271     if (!animation)
    272         return false;
    273 
    274     bool acceleratedOnly = false;
    275     bool isRunningNow = true;
    276     return animation->isAnimatingProperty(CSSPropertyOpacity, acceleratedOnly, isRunningNow)
    277         || animation->isAnimatingProperty(CSSPropertyWebkitTransform, acceleratedOnly, isRunningNow)
    278         || animation->isAnimatingProperty(CSSPropertyWebkitFilter, acceleratedOnly, isRunningNow);
    279 }
    280 
    281 bool AnimationControllerPrivate::isRunningAcceleratedAnimationOnRenderer(RenderObject* renderer, CSSPropertyID property, bool isRunningNow) const
    282 {
    283     RefPtr<CompositeAnimation> animation = m_compositeAnimations.get(renderer);
    284     if (!animation)
    285         return false;
    286 
    287     return animation->isAnimatingProperty(property, true, isRunningNow);
    288 }
    289 
    290 void AnimationControllerPrivate::pauseAnimationsForTesting(double t)
    291 {
    292     RenderObjectAnimationMap::const_iterator animationsEnd = m_compositeAnimations.end();
    293     for (RenderObjectAnimationMap::const_iterator it = m_compositeAnimations.begin(); it != animationsEnd; ++it) {
    294         it->value->pauseAnimationsForTesting(t);
    295         it->key->node()->setNeedsStyleRecalc(LocalStyleChange, StyleChangeFromRenderer);
    296     }
    297 }
    298 
    299 double AnimationControllerPrivate::beginAnimationUpdateTime()
    300 {
    301     if (m_beginAnimationUpdateTime == cBeginAnimationUpdateTimeNotSet)
    302         m_beginAnimationUpdateTime = currentTime();
    303     return m_beginAnimationUpdateTime;
    304 }
    305 
    306 void AnimationControllerPrivate::endAnimationUpdate()
    307 {
    308     styleAvailable();
    309     if (m_animationsWaitingForAsyncStartNotification.isEmpty())
    310         startTimeResponse(beginAnimationUpdateTime());
    311 }
    312 
    313 void AnimationControllerPrivate::receivedStartTimeResponse(double time)
    314 {
    315     startTimeResponse(time);
    316 }
    317 
    318 PassRefPtr<RenderStyle> AnimationControllerPrivate::getAnimatedStyleForRenderer(RenderObject* renderer)
    319 {
    320     if (!renderer)
    321         return 0;
    322 
    323     RefPtr<CompositeAnimation> rendererAnimations = m_compositeAnimations.get(renderer);
    324     if (!rendererAnimations)
    325         return renderer->style();
    326 
    327     RefPtr<RenderStyle> animatingStyle = rendererAnimations->getAnimatedStyle();
    328     if (!animatingStyle)
    329         animatingStyle = renderer->style();
    330 
    331     return animatingStyle.release();
    332 }
    333 
    334 unsigned AnimationControllerPrivate::numberOfActiveAnimations(Document* document) const
    335 {
    336     unsigned count = 0;
    337 
    338     RenderObjectAnimationMap::const_iterator animationsEnd = m_compositeAnimations.end();
    339     for (RenderObjectAnimationMap::const_iterator it = m_compositeAnimations.begin(); it != animationsEnd; ++it) {
    340         RenderObject* renderer = it->key;
    341         CompositeAnimation* compAnim = it->value.get();
    342         if (renderer->document() == document)
    343             count += compAnim->numberOfActiveAnimations();
    344     }
    345 
    346     return count;
    347 }
    348 
    349 void AnimationControllerPrivate::addToAnimationsWaitingForStyle(AnimationBase* animation)
    350 {
    351     // Make sure this animation is not in the start time waiters
    352     m_animationsWaitingForStartTimeResponse.remove(animation);
    353     m_animationsWaitingForAsyncStartNotification.remove(animation);
    354 
    355     m_animationsWaitingForStyle.add(animation);
    356 }
    357 
    358 void AnimationControllerPrivate::removeFromAnimationsWaitingForStyle(AnimationBase* animationToRemove)
    359 {
    360     m_animationsWaitingForStyle.remove(animationToRemove);
    361 }
    362 
    363 void AnimationControllerPrivate::styleAvailable()
    364 {
    365     // Go through list of waiters and send them on their way
    366     WaitingAnimationsSet::const_iterator it = m_animationsWaitingForStyle.begin();
    367     WaitingAnimationsSet::const_iterator end = m_animationsWaitingForStyle.end();
    368     for (; it != end; ++it)
    369         (*it)->styleAvailable();
    370 
    371     m_animationsWaitingForStyle.clear();
    372 }
    373 
    374 void AnimationControllerPrivate::addToAnimationsWaitingForStartTimeResponse(AnimationBase* animation, bool willGetResponse)
    375 {
    376     // If willGetResponse is true, it means this animation is actually waiting for a response
    377     // (which will come in as a call to notifyAnimationStarted()).
    378     // In that case we don't need to add it to this list. We just set a waitingForAResponse flag
    379     // which says we are waiting for the response. If willGetResponse is false, this animation
    380     // is not waiting for a response for itself, but rather for a notifyXXXStarted() call for
    381     // another animation to which it will sync.
    382     //
    383     // When endAnimationUpdate() is called we check to see if the waitingForAResponse flag is
    384     // true. If so, we just return and will do our work when the first notifyXXXStarted() call
    385     // comes in. If it is false, we will not be getting a notifyXXXStarted() call, so we will
    386     // do our work right away. In both cases we call the onAnimationStartResponse() method
    387     // on each animation. In the first case we send in the time we got from notifyXXXStarted().
    388     // In the second case, we just pass in the beginAnimationUpdateTime().
    389     //
    390     // This will synchronize all software and accelerated animations started in the same
    391     // updateStyleIfNeeded cycle.
    392     //
    393 
    394     if (willGetResponse)
    395         m_animationsWaitingForAsyncStartNotification.add(animation);
    396 
    397     m_animationsWaitingForStartTimeResponse.add(animation);
    398 }
    399 
    400 void AnimationControllerPrivate::removeFromAnimationsWaitingForStartTimeResponse(AnimationBase* animationToRemove)
    401 {
    402     m_animationsWaitingForStartTimeResponse.remove(animationToRemove);
    403     m_animationsWaitingForAsyncStartNotification.remove(animationToRemove);
    404 }
    405 
    406 void AnimationControllerPrivate::startTimeResponse(double time)
    407 {
    408     // Go through list of waiters and send them on their way
    409 
    410     WaitingAnimationsSet::const_iterator it = m_animationsWaitingForStartTimeResponse.begin();
    411     WaitingAnimationsSet::const_iterator end = m_animationsWaitingForStartTimeResponse.end();
    412     for (; it != end; ++it)
    413         (*it)->onAnimationStartResponse(time);
    414 
    415     m_animationsWaitingForStartTimeResponse.clear();
    416     m_animationsWaitingForAsyncStartNotification.clear();
    417 }
    418 
    419 void AnimationControllerPrivate::animationWillBeRemoved(AnimationBase* animation)
    420 {
    421     removeFromAnimationsWaitingForStyle(animation);
    422     removeFromAnimationsWaitingForStartTimeResponse(animation);
    423 }
    424 
    425 AnimationController::AnimationController(Frame* frame)
    426     : m_data(adoptPtr(new AnimationControllerPrivate(frame)))
    427     , m_beginAnimationUpdateCount(0)
    428 {
    429 }
    430 
    431 AnimationController::~AnimationController()
    432 {
    433 }
    434 
    435 void AnimationController::cancelAnimations(RenderObject* renderer)
    436 {
    437     if (!m_data->hasAnimations())
    438         return;
    439 
    440     if (m_data->clear(renderer)) {
    441         if (Node* node = renderer->node())
    442             node->setNeedsStyleRecalc(LocalStyleChange, StyleChangeFromRenderer);
    443     }
    444 }
    445 
    446 PassRefPtr<RenderStyle> AnimationController::updateAnimations(RenderObject& renderer, RenderStyle& newStyle)
    447 {
    448     RenderStyle* oldStyle = renderer.style();
    449 
    450     if ((!oldStyle || (!oldStyle->animations() && !oldStyle->transitions())) && (!newStyle.animations() && !newStyle.transitions()))
    451         return PassRefPtr<RenderStyle>(newStyle);
    452 
    453     // Don't run transitions when printing.
    454     if (renderer.view()->document().printing())
    455         return PassRefPtr<RenderStyle>(newStyle);
    456 
    457     // Fetch our current set of implicit animations from a hashtable.  We then compare them
    458     // against the animations in the style and make sure we're in sync.  If destination values
    459     // have changed, we reset the animation.  We then do a blend to get new values and we return
    460     // a new style.
    461 
    462     // We don't support anonymous pseudo elements like :first-line or :first-letter.
    463     ASSERT(renderer.node());
    464 
    465     RefPtr<CompositeAnimation> rendererAnimations = m_data->accessCompositeAnimation(renderer);
    466     RefPtr<RenderStyle> blendedStyle = rendererAnimations->animate(renderer, oldStyle, newStyle);
    467 
    468     if (renderer.parent() || newStyle.animations() || (oldStyle && oldStyle->animations())) {
    469         m_data->scheduleServiceForRenderer(renderer);
    470     }
    471 
    472     if (blendedStyle != &newStyle) {
    473         // If the animations/transitions change opacity or transform, we need to update
    474         // the style to impose the stacking rules. Note that this is also
    475         // done in StyleResolver::adjustRenderStyle().
    476         if (blendedStyle->hasAutoZIndex() && (blendedStyle->opacity() < 1.0f || blendedStyle->hasTransform()))
    477             blendedStyle->setZIndex(0);
    478     }
    479     return blendedStyle.release();
    480 }
    481 
    482 PassRefPtr<RenderStyle> AnimationController::getAnimatedStyleForRenderer(RenderObject* renderer)
    483 {
    484     return m_data->getAnimatedStyleForRenderer(renderer);
    485 }
    486 
    487 void AnimationController::notifyAnimationStarted(RenderObject*, double startTime)
    488 {
    489     m_data->receivedStartTimeResponse(startTime);
    490 }
    491 
    492 void AnimationController::pauseAnimationsForTesting(double t)
    493 {
    494     m_data->pauseAnimationsForTesting(t);
    495 }
    496 
    497 unsigned AnimationController::numberOfActiveAnimations(Document* document) const
    498 {
    499     return m_data->numberOfActiveAnimations(document);
    500 }
    501 
    502 bool AnimationController::isRunningAnimationOnRenderer(RenderObject* renderer, CSSPropertyID property, bool isRunningNow) const
    503 {
    504     return m_data->isRunningAnimationOnRenderer(renderer, property, isRunningNow);
    505 }
    506 
    507 bool AnimationController::isRunningAcceleratableAnimationOnRenderer(RenderObject* renderer) const
    508 {
    509     return m_data->isRunningAcceleratableAnimationOnRenderer(renderer);
    510 }
    511 
    512 bool AnimationController::isRunningAcceleratedAnimationOnRenderer(RenderObject* renderer, CSSPropertyID property, bool isRunningNow) const
    513 {
    514     return m_data->isRunningAcceleratedAnimationOnRenderer(renderer, property, isRunningNow);
    515 }
    516 
    517 void AnimationController::serviceAnimations()
    518 {
    519     m_data->serviceAnimations();
    520 }
    521 
    522 void AnimationController::beginAnimationUpdate()
    523 {
    524     if (!m_beginAnimationUpdateCount)
    525         m_data->setBeginAnimationUpdateTime(cBeginAnimationUpdateTimeNotSet);
    526     ++m_beginAnimationUpdateCount;
    527 }
    528 
    529 void AnimationController::endAnimationUpdate()
    530 {
    531     ASSERT(m_beginAnimationUpdateCount > 0);
    532     --m_beginAnimationUpdateCount;
    533     if (!m_beginAnimationUpdateCount)
    534         m_data->endAnimationUpdate();
    535 }
    536 
    537 bool AnimationController::supportsAcceleratedAnimationOfProperty(CSSPropertyID property)
    538 {
    539     return CSSPropertyAnimation::animationOfPropertyIsAccelerated(property);
    540 }
    541 
    542 } // namespace WebCore
    543