1/* 2 * Copyright (C) 2013 Google 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 are 6 * met: 7 * 8 * * Redistributions of source code must retain the above copyright 9 * notice, this list of conditions and the following disclaimer. 10 * * Redistributions in binary form must reproduce the above 11 * copyright notice, this list of conditions and the following disclaimer 12 * in the documentation and/or other materials provided with the 13 * distribution. 14 * * Neither the name of Google Inc. nor the names of its 15 * contributors may be used to endorse or promote products derived from 16 * this software without specific prior written permission. 17 * 18 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 19 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 20 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 21 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 22 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 23 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 24 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 25 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 26 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 27 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 28 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 29 */ 30 31#include "config.h" 32#include "core/animation/css/CSSAnimations.h" 33 34#include "core/animation/DocumentTimeline.h" 35#include "core/animation/KeyframeAnimationEffect.h" 36#include "core/css/CSSKeyframeRule.h" 37#include "core/css/resolver/StyleResolver.h" 38#include "core/dom/AnimationEvent.h" 39#include "core/dom/Element.h" 40#include "core/dom/EventNames.h" 41#include "core/platform/animation/CSSAnimationDataList.h" 42#include "core/platform/animation/TimingFunction.h" 43#include "wtf/HashSet.h" 44 45namespace WebCore { 46 47void timingFromAnimationData(const CSSAnimationData* animationData, Timing& timing) 48{ 49 if (animationData->isDelaySet()) 50 timing.startDelay = animationData->delay(); 51 if (animationData->isDurationSet()) { 52 timing.iterationDuration = animationData->duration(); 53 timing.hasIterationDuration = true; 54 } 55 if (animationData->isIterationCountSet()) { 56 if (animationData->iterationCount() == CSSAnimationData::IterationCountInfinite) 57 timing.iterationCount = std::numeric_limits<double>::infinity(); 58 else 59 timing.iterationCount = animationData->iterationCount(); 60 } 61 if (animationData->isTimingFunctionSet()) { 62 if (!animationData->timingFunction()->isLinearTimingFunction()) 63 timing.timingFunction = animationData->timingFunction(); 64 } else { 65 // CSS default is ease, default in model is linear. 66 timing.timingFunction = CubicBezierTimingFunction::preset(CubicBezierTimingFunction::Ease); 67 } 68 if (animationData->isFillModeSet()) { 69 switch (animationData->fillMode()) { 70 case AnimationFillModeForwards: 71 timing.fillMode = Timing::FillModeForwards; 72 break; 73 case AnimationFillModeBackwards: 74 timing.fillMode = Timing::FillModeBackwards; 75 break; 76 case AnimationFillModeBoth: 77 timing.fillMode = Timing::FillModeBoth; 78 break; 79 case AnimationFillModeNone: 80 timing.fillMode = Timing::FillModeNone; 81 break; 82 default: 83 ASSERT_NOT_REACHED(); 84 } 85 } 86 if (animationData->isDirectionSet()) { 87 switch (animationData->direction()) { 88 case CSSAnimationData::AnimationDirectionNormal: 89 timing.direction = Timing::PlaybackDirectionNormal; 90 break; 91 case CSSAnimationData::AnimationDirectionAlternate: 92 timing.direction = Timing::PlaybackDirectionAlternate; 93 break; 94 case CSSAnimationData::AnimationDirectionReverse: 95 timing.direction = Timing::PlaybackDirectionReverse; 96 break; 97 case CSSAnimationData::AnimationDirectionAlternateReverse: 98 timing.direction = Timing::PlaybackDirectionAlternateReverse; 99 break; 100 default: 101 ASSERT_NOT_REACHED(); 102 } 103 } 104} 105 106bool CSSAnimations::needsUpdate(const Element* element, const RenderStyle* style) 107{ 108 ActiveAnimations* activeAnimations = element->activeAnimations(); 109 const CSSAnimationDataList* animations = style->animations(); 110 const CSSAnimations* cssAnimations = activeAnimations ? activeAnimations->cssAnimations() : 0; 111 EDisplay display = style->display(); 112 return (display != NONE && animations && animations->size()) || (cssAnimations && !cssAnimations->isEmpty()); 113} 114 115PassOwnPtr<CSSAnimationUpdate> CSSAnimations::calculateUpdate(const Element* element, EDisplay display, const CSSAnimations* cssAnimations, const CSSAnimationDataList* animationDataList, StyleResolver* resolver) 116{ 117 OwnPtr<CSSAnimationUpdate> update; 118 HashSet<StringImpl*> inactive; 119 if (cssAnimations) 120 for (AnimationMap::const_iterator iter = cssAnimations->m_animations.begin(); iter != cssAnimations->m_animations.end(); ++iter) 121 inactive.add(iter->key); 122 123 RefPtr<MutableStylePropertySet> newStyles; 124 if (display != NONE) { 125 for (size_t i = 0; animationDataList && i < animationDataList->size(); ++i) { 126 const CSSAnimationData* animationData = animationDataList->animation(i); 127 if (animationData->isNoneAnimation()) 128 continue; 129 ASSERT(animationData->isValidAnimation()); 130 AtomicString animationName(animationData->name()); 131 132 if (cssAnimations) { 133 AnimationMap::const_iterator existing(cssAnimations->m_animations.find(animationName.impl())); 134 if (existing != cssAnimations->m_animations.end()) { 135 inactive.remove(animationName.impl()); 136 continue; 137 } 138 } 139 140 // If there's a delay, no styles will apply yet. 141 if (animationData->isDelaySet() && animationData->delay()) { 142 RELEASE_ASSERT_WITH_MESSAGE(animationData->delay() > 0, "Web Animations not yet implemented: Negative delay"); 143 continue; 144 } 145 146 const StylePropertySet* keyframeStyles = resolver->firstKeyframeStyles(element, animationName.impl()); 147 if (keyframeStyles) { 148 if (!update) 149 update = adoptPtr(new CSSAnimationUpdate()); 150 update->addStyles(keyframeStyles); 151 } 152 } 153 } 154 155 if (!inactive.isEmpty() && !update) 156 update = adoptPtr(new CSSAnimationUpdate()); 157 for (HashSet<StringImpl*>::const_iterator iter = inactive.begin(); iter != inactive.end(); ++iter) 158 update->cancel(cssAnimations->m_animations.get(*iter)); 159 160 return update.release(); 161} 162 163void CSSAnimations::update(Element* element, const RenderStyle* style) 164{ 165 const CSSAnimationDataList* animationDataList = style->animations(); 166 HashSet<StringImpl*> inactive; 167 for (AnimationMap::const_iterator iter = m_animations.begin(); iter != m_animations.end(); ++iter) 168 inactive.add(iter->key); 169 170 if (style->display() != NONE) { 171 for (size_t i = 0; animationDataList && i < animationDataList->size(); ++i) { 172 const CSSAnimationData* animationData = animationDataList->animation(i); 173 if (animationData->isNoneAnimation()) 174 continue; 175 ASSERT(animationData->isValidAnimation()); 176 AtomicString animationName(animationData->name()); 177 178 AnimationMap::const_iterator existing(m_animations.find(animationName.impl())); 179 if (existing != m_animations.end()) { 180 bool paused = animationData->playState() == AnimPlayStatePaused; 181 existing->value->setPaused(paused); 182 inactive.remove(animationName.impl()); 183 continue; 184 } 185 186 KeyframeAnimationEffect::KeyframeVector keyframes; 187 element->document()->styleResolver()->resolveKeyframes(element, style, animationName.impl(), keyframes); 188 if (!keyframes.isEmpty()) { 189 Timing timing; 190 timingFromAnimationData(animationData, timing); 191 OwnPtr<CSSAnimations::EventDelegate> eventDelegate = adoptPtr(new EventDelegate(element, animationName)); 192 // FIXME: crbug.com/268791 - Keyframes are already normalized, perhaps there should be a flag on KeyframeAnimationEffect to skip normalization. 193 m_animations.set(animationName.impl(), element->document()->timeline()->play( 194 Animation::create(element, KeyframeAnimationEffect::create(keyframes), timing, eventDelegate.release()).get()).get()); 195 } 196 } 197 } 198 199 for (HashSet<StringImpl*>::const_iterator iter = inactive.begin(); iter != inactive.end(); ++iter) 200 m_animations.take(*iter)->cancel(); 201} 202 203void CSSAnimations::cancel() 204{ 205 for (AnimationMap::iterator iter = m_animations.begin(); iter != m_animations.end(); ++iter) 206 iter->value->cancel(); 207 208 m_animations.clear(); 209} 210 211void CSSAnimations::EventDelegate::maybeDispatch(Document::ListenerType listenerType, AtomicString& eventName, double elapsedTime) 212{ 213 if (m_target->document()->hasListenerType(listenerType)) 214 m_target->document()->timeline()->addEventToDispatch(m_target, AnimationEvent::create(eventName, m_name, elapsedTime)); 215} 216 217void CSSAnimations::EventDelegate::onEventCondition(bool wasInPlay, bool isInPlay, double previousIteration, double currentIteration) 218{ 219 // Events for a single document are queued and dispatched as a group at 220 // the end of DocumentTimeline::serviceAnimations. 221 // FIXME: Events which are queued outside of serviceAnimations should 222 // trigger a timer to dispatch when control is released. 223 // FIXME: Receive TimedItem as param in order to produce correct elapsed time value. 224 double elapsedTime = 0; 225 if (!wasInPlay && isInPlay) { 226 maybeDispatch(Document::ANIMATIONSTART_LISTENER, eventNames().webkitAnimationStartEvent, elapsedTime); 227 return; 228 } 229 if (wasInPlay && isInPlay && currentIteration != previousIteration) { 230 maybeDispatch(Document::ANIMATIONITERATION_LISTENER, eventNames().webkitAnimationIterationEvent, elapsedTime); 231 return; 232 } 233 if (wasInPlay && !isInPlay) { 234 maybeDispatch(Document::ANIMATIONEND_LISTENER, eventNames().webkitAnimationEndEvent, elapsedTime); 235 return; 236 } 237} 238 239} // namespace WebCore 240