/* * Copyright (C) 2007 Apple Inc. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of Apple Computer, Inc. ("Apple") nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "config.h" #include "AnimationController.h" #include "CompositeAnimation.h" #include "Frame.h" #include "Timer.h" namespace WebCore { static const double cAnimationTimerDelay = 0.025; class AnimationControllerPrivate { public: AnimationControllerPrivate(Frame*); ~AnimationControllerPrivate(); CompositeAnimation* accessCompositeAnimation(RenderObject*); bool clear(RenderObject*); void animationTimerFired(Timer*); void updateAnimationTimer(); void updateRenderingDispatcherFired(Timer*); void startUpdateRenderingDispatcher(); bool hasAnimations() const { return !m_compositeAnimations.isEmpty(); } void suspendAnimations(Document*); void resumeAnimations(Document*); void styleAvailable(); bool isAnimatingPropertyOnRenderer(RenderObject*, int property, bool isRunningNow) const; private: typedef HashMap RenderObjectAnimationMap; RenderObjectAnimationMap m_compositeAnimations; Timer m_animationTimer; Timer m_updateRenderingDispatcher; Frame* m_frame; }; AnimationControllerPrivate::AnimationControllerPrivate(Frame* frame) : m_animationTimer(this, &AnimationControllerPrivate::animationTimerFired) , m_updateRenderingDispatcher(this, &AnimationControllerPrivate::updateRenderingDispatcherFired) , m_frame(frame) { } AnimationControllerPrivate::~AnimationControllerPrivate() { deleteAllValues(m_compositeAnimations); } CompositeAnimation* AnimationControllerPrivate::accessCompositeAnimation(RenderObject* renderer) { CompositeAnimation* animation = m_compositeAnimations.get(renderer); if (!animation) { animation = new CompositeAnimation(m_frame->animation()); m_compositeAnimations.set(renderer, animation); } return animation; } bool AnimationControllerPrivate::clear(RenderObject* renderer) { // Return false if we didn't do anything OR we are suspended (so we don't try to // do a setChanged() when suspended). CompositeAnimation* animation = m_compositeAnimations.take(renderer); if (!animation) return false; animation->resetTransitions(renderer); bool wasSuspended = animation->isSuspended(); delete animation; return !wasSuspended; } void AnimationControllerPrivate::styleAvailable() { RenderObjectAnimationMap::const_iterator animationsEnd = m_compositeAnimations.end(); for (RenderObjectAnimationMap::const_iterator it = m_compositeAnimations.begin(); it != animationsEnd; ++it) it->second->styleAvailable(); } void AnimationControllerPrivate::updateAnimationTimer() { bool isAnimating = false; RenderObjectAnimationMap::const_iterator animationsEnd = m_compositeAnimations.end(); for (RenderObjectAnimationMap::const_iterator it = m_compositeAnimations.begin(); it != animationsEnd; ++it) { CompositeAnimation* compAnim = it->second; if (!compAnim->isSuspended() && compAnim->isAnimating()) { isAnimating = true; break; } } if (isAnimating) { if (!m_animationTimer.isActive()) m_animationTimer.startRepeating(cAnimationTimerDelay); } else if (m_animationTimer.isActive()) m_animationTimer.stop(); } void AnimationControllerPrivate::updateRenderingDispatcherFired(Timer*) { if (m_frame && m_frame->document()) m_frame->document()->updateRendering(); } void AnimationControllerPrivate::startUpdateRenderingDispatcher() { if (!m_updateRenderingDispatcher.isActive()) m_updateRenderingDispatcher.startOneShot(0); } void AnimationControllerPrivate::animationTimerFired(Timer* timer) { // When the timer fires, all we do is call setChanged on all DOM nodes with running animations and then do an immediate // updateRendering. It will then call back to us with new information. bool isAnimating = false; RenderObjectAnimationMap::const_iterator animationsEnd = m_compositeAnimations.end(); for (RenderObjectAnimationMap::const_iterator it = m_compositeAnimations.begin(); it != animationsEnd; ++it) { CompositeAnimation* compAnim = it->second; if (!compAnim->isSuspended() && compAnim->isAnimating()) { isAnimating = true; compAnim->setAnimating(false); Node* node = it->first->element(); ASSERT(!node || (node->document() && !node->document()->inPageCache())); node->setChanged(AnimationStyleChange); } } m_frame->document()->updateRendering(); updateAnimationTimer(); } bool AnimationControllerPrivate::isAnimatingPropertyOnRenderer(RenderObject* renderer, int property, bool isRunningNow) const { CompositeAnimation* animation = m_compositeAnimations.get(renderer); if (!animation) return false; return animation->isAnimatingProperty(property, isRunningNow); } void AnimationControllerPrivate::suspendAnimations(Document* document) { RenderObjectAnimationMap::const_iterator animationsEnd = m_compositeAnimations.end(); for (RenderObjectAnimationMap::const_iterator it = m_compositeAnimations.begin(); it != animationsEnd; ++it) { RenderObject* renderer = it->first; CompositeAnimation* compAnim = it->second; if (renderer->document() == document) compAnim->suspendAnimations(); } updateAnimationTimer(); } void AnimationControllerPrivate::resumeAnimations(Document* document) { RenderObjectAnimationMap::const_iterator animationsEnd = m_compositeAnimations.end(); for (RenderObjectAnimationMap::const_iterator it = m_compositeAnimations.begin(); it != animationsEnd; ++it) { RenderObject* renderer = it->first; CompositeAnimation* compAnim = it->second; if (renderer->document() == document) compAnim->resumeAnimations(); } updateAnimationTimer(); } AnimationController::AnimationController(Frame* frame) : m_data(new AnimationControllerPrivate(frame)) , m_numStyleAvailableWaiters(0) { } AnimationController::~AnimationController() { delete m_data; } void AnimationController::cancelAnimations(RenderObject* renderer) { if (!m_data->hasAnimations()) return; if (m_data->clear(renderer)) { Node* node = renderer->element(); ASSERT(!node || (node->document() && !node->document()->inPageCache())); node->setChanged(AnimationStyleChange); } } PassRefPtr AnimationController::updateAnimations(RenderObject* renderer, RenderStyle* newStyle) { // Don't do anything if we're in the cache if (!renderer->document() || renderer->document()->inPageCache()) return newStyle; RenderStyle* oldStyle = renderer->style(); if ((!oldStyle || (!oldStyle->animations() && !oldStyle->transitions())) && (!newStyle->animations() && !newStyle->transitions())) return newStyle; // Fetch our current set of implicit animations from a hashtable. We then compare them // against the animations in the style and make sure we're in sync. If destination values // have changed, we reset the animation. We then do a blend to get new values and we return // a new style. ASSERT(renderer->element()); // FIXME: We do not animate generated content yet. CompositeAnimation* rendererAnimations = m_data->accessCompositeAnimation(renderer); RefPtr blendedStyle = rendererAnimations->animate(renderer, oldStyle, newStyle); m_data->updateAnimationTimer(); if (blendedStyle != newStyle) { // If the animations/transitions change opacity or transform, we neeed to update // the style to impose the stacking rules. Note that this is also // done in CSSStyleSelector::adjustRenderStyle(). if (blendedStyle->hasAutoZIndex() && (blendedStyle->opacity() < 1.0f || blendedStyle->hasTransform())) blendedStyle->setZIndex(0); } return blendedStyle.release(); } void AnimationController::setAnimationStartTime(RenderObject* renderer, double t) { CompositeAnimation* rendererAnimations = m_data->accessCompositeAnimation(renderer); rendererAnimations->setAnimationStartTime(t); } void AnimationController::setTransitionStartTime(RenderObject* renderer, int property, double t) { CompositeAnimation* rendererAnimations = m_data->accessCompositeAnimation(renderer); rendererAnimations->setTransitionStartTime(property, t); } bool AnimationController::isAnimatingPropertyOnRenderer(RenderObject* renderer, int property, bool isRunningNow) const { return m_data->isAnimatingPropertyOnRenderer(renderer, property, isRunningNow); } void AnimationController::suspendAnimations(Document* document) { m_data->suspendAnimations(document); } void AnimationController::resumeAnimations(Document* document) { m_data->resumeAnimations(document); } void AnimationController::startUpdateRenderingDispatcher() { m_data->startUpdateRenderingDispatcher(); } void AnimationController::styleAvailable() { if (!m_numStyleAvailableWaiters) return; m_data->styleAvailable(); } } // namespace WebCore