1/*
2 * Copyright (C) 2008 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 * 1. Redistributions of source code must retain the above copyright
8 *    notice, this list of conditions and the following disclaimer.
9 * 2. Redistributions in binary form must reproduce the above copyright
10 *    notice, this list of conditions and the following disclaimer in the
11 *    documentation and/or other materials provided with the distribution.
12 *
13 * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24 */
25
26#include "config.h"
27#include "core/svg/animation/SMILTimeContainer.h"
28
29#include "core/dom/ElementTraversal.h"
30#include "core/svg/SVGSVGElement.h"
31#include "core/svg/animation/SVGSMILElement.h"
32#include "wtf/CurrentTime.h"
33
34using namespace std;
35
36namespace WebCore {
37
38static const double animationFrameDelay = 0.025;
39
40SMILTimeContainer::SMILTimeContainer(SVGSVGElement* owner)
41    : m_beginTime(0)
42    , m_pauseTime(0)
43    , m_resumeTime(0)
44    , m_accumulatedActiveTime(0)
45    , m_presetStartTime(0)
46    , m_documentOrderIndexesDirty(false)
47    , m_timer(this, &SMILTimeContainer::timerFired)
48    , m_ownerSVGElement(owner)
49#ifndef NDEBUG
50    , m_preventScheduledAnimationsChanges(false)
51#endif
52{
53}
54
55SMILTimeContainer::~SMILTimeContainer()
56{
57#ifndef NDEBUG
58    ASSERT(!m_preventScheduledAnimationsChanges);
59#endif
60}
61
62void SMILTimeContainer::schedule(SVGSMILElement* animation, SVGElement* target, const QualifiedName& attributeName)
63{
64    ASSERT(animation->timeContainer() == this);
65    ASSERT(target);
66    ASSERT(animation->hasValidAttributeName());
67
68#ifndef NDEBUG
69    ASSERT(!m_preventScheduledAnimationsChanges);
70#endif
71
72    ElementAttributePair key(target, attributeName);
73    OwnPtr<AnimationsVector>& scheduled = m_scheduledAnimations.add(key, nullptr).iterator->value;
74    if (!scheduled)
75        scheduled = adoptPtr(new AnimationsVector);
76    ASSERT(!scheduled->contains(animation));
77    scheduled->append(animation);
78
79    SMILTime nextFireTime = animation->nextProgressTime();
80    if (nextFireTime.isFinite())
81        notifyIntervalsChanged();
82}
83
84void SMILTimeContainer::unschedule(SVGSMILElement* animation, SVGElement* target, const QualifiedName& attributeName)
85{
86    ASSERT(animation->timeContainer() == this);
87
88#ifndef NDEBUG
89    ASSERT(!m_preventScheduledAnimationsChanges);
90#endif
91
92    ElementAttributePair key(target, attributeName);
93    AnimationsVector* scheduled = m_scheduledAnimations.get(key);
94    ASSERT(scheduled);
95    size_t idx = scheduled->find(animation);
96    ASSERT(idx != kNotFound);
97    scheduled->remove(idx);
98}
99
100void SMILTimeContainer::notifyIntervalsChanged()
101{
102    // Schedule updateAnimations() to be called asynchronously so multiple intervals
103    // can change with updateAnimations() only called once at the end.
104    startTimer(0);
105}
106
107SMILTime SMILTimeContainer::elapsed() const
108{
109    if (!m_beginTime)
110        return 0;
111
112    if (isPaused())
113        return m_accumulatedActiveTime;
114
115    return currentTime() + m_accumulatedActiveTime - lastResumeTime();
116}
117
118bool SMILTimeContainer::isActive() const
119{
120    return m_beginTime && !isPaused();
121}
122
123bool SMILTimeContainer::isPaused() const
124{
125    return m_pauseTime;
126}
127
128bool SMILTimeContainer::isStarted() const
129{
130    return m_beginTime;
131}
132
133void SMILTimeContainer::begin()
134{
135    ASSERT(!m_beginTime);
136    double now = currentTime();
137
138    // If 'm_presetStartTime' is set, the timeline was modified via setElapsed() before the document began.
139    // In this case pass on 'seekToTime=true' to updateAnimations().
140    m_beginTime = now - m_presetStartTime;
141    updateAnimations(SMILTime(m_presetStartTime), m_presetStartTime ? true : false);
142    m_presetStartTime = 0;
143
144    if (m_pauseTime) {
145        m_pauseTime = now;
146        m_timer.stop();
147    }
148}
149
150void SMILTimeContainer::pause()
151{
152    ASSERT(!isPaused());
153    m_pauseTime = currentTime();
154
155    if (m_beginTime) {
156        m_accumulatedActiveTime += m_pauseTime - lastResumeTime();
157        m_timer.stop();
158    }
159    m_resumeTime = 0;
160}
161
162void SMILTimeContainer::resume()
163{
164    ASSERT(isPaused());
165    m_resumeTime = currentTime();
166
167    m_pauseTime = 0;
168    startTimer(0);
169}
170
171void SMILTimeContainer::setElapsed(SMILTime time)
172{
173    // If the documment didn't begin yet, record a new start time, we'll seek to once its possible.
174    if (!m_beginTime) {
175        m_presetStartTime = time.value();
176        return;
177    }
178
179    if (m_beginTime)
180        m_timer.stop();
181
182    double now = currentTime();
183    m_beginTime = now - time.value();
184    m_resumeTime = 0;
185    if (m_pauseTime) {
186        m_pauseTime = now;
187        m_accumulatedActiveTime = time.value();
188    } else {
189        m_accumulatedActiveTime = 0;
190    }
191
192#ifndef NDEBUG
193    m_preventScheduledAnimationsChanges = true;
194#endif
195    GroupedAnimationsMap::iterator end = m_scheduledAnimations.end();
196    for (GroupedAnimationsMap::iterator it = m_scheduledAnimations.begin(); it != end; ++it) {
197        AnimationsVector* scheduled = it->value.get();
198        unsigned size = scheduled->size();
199        for (unsigned n = 0; n < size; n++)
200            scheduled->at(n)->reset();
201    }
202#ifndef NDEBUG
203    m_preventScheduledAnimationsChanges = false;
204#endif
205
206    updateAnimations(time, true);
207}
208
209void SMILTimeContainer::startTimer(SMILTime fireTime, SMILTime minimumDelay)
210{
211    if (!m_beginTime || isPaused())
212        return;
213
214    if (!fireTime.isFinite())
215        return;
216
217    SMILTime delay = max(fireTime - elapsed(), minimumDelay);
218    m_timer.startOneShot(delay.value());
219}
220
221void SMILTimeContainer::timerFired(Timer<SMILTimeContainer>*)
222{
223    ASSERT(m_beginTime);
224    ASSERT(!m_pauseTime);
225    updateAnimations(elapsed());
226}
227
228void SMILTimeContainer::updateDocumentOrderIndexes()
229{
230    unsigned timingElementCount = 0;
231    for (Element* element = m_ownerSVGElement; element; element = ElementTraversal::next(*element, m_ownerSVGElement)) {
232        if (isSVGSMILElement(*element))
233            toSVGSMILElement(element)->setDocumentOrderIndex(timingElementCount++);
234    }
235    m_documentOrderIndexesDirty = false;
236}
237
238struct PriorityCompare {
239    PriorityCompare(SMILTime elapsed) : m_elapsed(elapsed) {}
240    bool operator()(SVGSMILElement* a, SVGSMILElement* b)
241    {
242        // FIXME: This should also consider possible timing relations between the elements.
243        SMILTime aBegin = a->intervalBegin();
244        SMILTime bBegin = b->intervalBegin();
245        // Frozen elements need to be prioritized based on their previous interval.
246        aBegin = a->isFrozen() && m_elapsed < aBegin ? a->previousIntervalBegin() : aBegin;
247        bBegin = b->isFrozen() && m_elapsed < bBegin ? b->previousIntervalBegin() : bBegin;
248        if (aBegin == bBegin)
249            return a->documentOrderIndex() < b->documentOrderIndex();
250        return aBegin < bBegin;
251    }
252    SMILTime m_elapsed;
253};
254
255void SMILTimeContainer::sortByPriority(Vector<SVGSMILElement*>& smilElements, SMILTime elapsed)
256{
257    if (m_documentOrderIndexesDirty)
258        updateDocumentOrderIndexes();
259    std::sort(smilElements.begin(), smilElements.end(), PriorityCompare(elapsed));
260}
261
262void SMILTimeContainer::updateAnimations(SMILTime elapsed, bool seekToTime)
263{
264    SMILTime earliestFireTime = SMILTime::unresolved();
265
266#ifndef NDEBUG
267    // This boolean will catch any attempts to schedule/unschedule scheduledAnimations during this critical section.
268    // Similarly, any elements removed will unschedule themselves, so this will catch modification of animationsToApply.
269    m_preventScheduledAnimationsChanges = true;
270#endif
271
272    AnimationsVector animationsToApply;
273    GroupedAnimationsMap::iterator end = m_scheduledAnimations.end();
274    for (GroupedAnimationsMap::iterator it = m_scheduledAnimations.begin(); it != end; ++it) {
275        AnimationsVector* scheduled = it->value.get();
276
277        // Sort according to priority. Elements with later begin time have higher priority.
278        // In case of a tie, document order decides.
279        // FIXME: This should also consider timing relationships between the elements. Dependents
280        // have higher priority.
281        sortByPriority(*scheduled, elapsed);
282
283        SVGSMILElement* resultElement = 0;
284        unsigned size = scheduled->size();
285        for (unsigned n = 0; n < size; n++) {
286            SVGSMILElement* animation = scheduled->at(n);
287            ASSERT(animation->timeContainer() == this);
288            ASSERT(animation->targetElement());
289            ASSERT(animation->hasValidAttributeName());
290
291            // Results are accumulated to the first animation that animates and contributes to a particular element/attribute pair.
292            // FIXME: we should ensure that resultElement is of an appropriate type.
293            if (!resultElement) {
294                if (!animation->hasValidAttributeType())
295                    continue;
296                resultElement = animation;
297            }
298
299            // This will calculate the contribution from the animation and add it to the resultsElement.
300            if (!animation->progress(elapsed, resultElement, seekToTime) && resultElement == animation)
301                resultElement = 0;
302
303            SMILTime nextFireTime = animation->nextProgressTime();
304            if (nextFireTime.isFinite())
305                earliestFireTime = min(nextFireTime, earliestFireTime);
306        }
307
308        if (resultElement)
309            animationsToApply.append(resultElement);
310    }
311
312    unsigned animationsToApplySize = animationsToApply.size();
313    if (!animationsToApplySize) {
314#ifndef NDEBUG
315        m_preventScheduledAnimationsChanges = false;
316#endif
317        startTimer(earliestFireTime, animationFrameDelay);
318        return;
319    }
320
321    // Apply results to target elements.
322    for (unsigned i = 0; i < animationsToApplySize; ++i)
323        animationsToApply[i]->applyResultsToTarget();
324
325#ifndef NDEBUG
326    m_preventScheduledAnimationsChanges = false;
327#endif
328
329    startTimer(earliestFireTime, animationFrameDelay);
330}
331
332}
333