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