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 "SMILTimeContainer.h"
28
29#include "Document.h"
30#include "ElementIterator.h"
31#include "SVGNames.h"
32#include "SVGSMILElement.h"
33#include "SVGSVGElement.h"
34#include <wtf/CurrentTime.h>
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_accumulatedActiveTime(0)
44    , m_resumeTime(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    std::unique_ptr<AnimationsVector>& scheduled = m_scheduledAnimations.add(key, nullptr).iterator->value;
74    if (!scheduled)
75        scheduled = std::make_unique<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 != notFound);
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    if (isPaused())
112        return m_accumulatedActiveTime;
113    return monotonicallyIncreasingTime() + m_accumulatedActiveTime - m_resumeTime;
114}
115
116bool SMILTimeContainer::isActive() const
117{
118    return m_beginTime && !isPaused();
119}
120
121bool SMILTimeContainer::isPaused() const
122{
123    return m_pauseTime;
124}
125
126bool SMILTimeContainer::isStarted() const
127{
128    return m_beginTime;
129}
130
131void SMILTimeContainer::begin()
132{
133    ASSERT(!m_beginTime);
134    double now = monotonicallyIncreasingTime();
135
136    // If 'm_presetStartTime' is set, the timeline was modified via setElapsed() before the document began.
137    // In this case pass on 'seekToTime=true' to updateAnimations().
138    m_beginTime = m_resumeTime = now - m_presetStartTime;
139    updateAnimations(SMILTime(m_presetStartTime), m_presetStartTime ? true : false);
140    m_presetStartTime = 0;
141
142    if (m_pauseTime) {
143        m_pauseTime = now;
144        m_timer.stop();
145    }
146}
147
148void SMILTimeContainer::pause()
149{
150    ASSERT(!isPaused());
151
152    m_pauseTime = monotonicallyIncreasingTime();
153    if (m_beginTime) {
154        m_accumulatedActiveTime += m_pauseTime - m_resumeTime;
155        m_timer.stop();
156    }
157}
158
159void SMILTimeContainer::resume()
160{
161    ASSERT(isPaused());
162
163    m_resumeTime = monotonicallyIncreasingTime();
164    m_pauseTime = 0;
165    startTimer(0);
166}
167
168void SMILTimeContainer::setElapsed(SMILTime time)
169{
170    // If the documment didn't begin yet, record a new start time, we'll seek to once its possible.
171    if (!m_beginTime) {
172        m_presetStartTime = time.value();
173        return;
174    }
175
176    if (m_beginTime)
177        m_timer.stop();
178
179    double now = monotonicallyIncreasingTime();
180    m_beginTime = now - time.value();
181
182    if (m_pauseTime) {
183        m_resumeTime = m_pauseTime = now;
184        m_accumulatedActiveTime = time.value();
185    } else
186        m_resumeTime = m_beginTime;
187
188#ifndef NDEBUG
189    m_preventScheduledAnimationsChanges = true;
190#endif
191    GroupedAnimationsMap::iterator end = m_scheduledAnimations.end();
192    for (GroupedAnimationsMap::iterator it = m_scheduledAnimations.begin(); it != end; ++it) {
193        AnimationsVector* scheduled = it->value.get();
194        unsigned size = scheduled->size();
195        for (unsigned n = 0; n < size; n++)
196            scheduled->at(n)->reset();
197    }
198#ifndef NDEBUG
199    m_preventScheduledAnimationsChanges = false;
200#endif
201
202    updateAnimations(time, true);
203}
204
205void SMILTimeContainer::startTimer(SMILTime fireTime, SMILTime minimumDelay)
206{
207    if (!m_beginTime || isPaused())
208        return;
209
210    if (!fireTime.isFinite())
211        return;
212
213    SMILTime delay = std::max(fireTime - elapsed(), minimumDelay);
214    m_timer.startOneShot(delay.value());
215}
216
217void SMILTimeContainer::timerFired(Timer<SMILTimeContainer>*)
218{
219    ASSERT(m_beginTime);
220    ASSERT(!m_pauseTime);
221    updateAnimations(elapsed());
222}
223
224void SMILTimeContainer::updateDocumentOrderIndexes()
225{
226    unsigned timingElementCount = 0;
227
228    for (auto& smilElement : descendantsOfType<SVGSMILElement>(*m_ownerSVGElement))
229        smilElement.setDocumentOrderIndex(timingElementCount++);
230
231    m_documentOrderIndexesDirty = false;
232}
233
234struct PriorityCompare {
235    PriorityCompare(SMILTime elapsed) : m_elapsed(elapsed) {}
236    bool operator()(SVGSMILElement* a, SVGSMILElement* b)
237    {
238        // FIXME: This should also consider possible timing relations between the elements.
239        SMILTime aBegin = a->intervalBegin();
240        SMILTime bBegin = b->intervalBegin();
241        // Frozen elements need to be prioritized based on their previous interval.
242        aBegin = a->isFrozen() && m_elapsed < aBegin ? a->previousIntervalBegin() : aBegin;
243        bBegin = b->isFrozen() && m_elapsed < bBegin ? b->previousIntervalBegin() : bBegin;
244        if (aBegin == bBegin)
245            return a->documentOrderIndex() < b->documentOrderIndex();
246        return aBegin < bBegin;
247    }
248    SMILTime m_elapsed;
249};
250
251void SMILTimeContainer::sortByPriority(Vector<SVGSMILElement*>& smilElements, SMILTime elapsed)
252{
253    if (m_documentOrderIndexesDirty)
254        updateDocumentOrderIndexes();
255    std::sort(smilElements.begin(), smilElements.end(), PriorityCompare(elapsed));
256}
257
258void SMILTimeContainer::updateAnimations(SMILTime elapsed, bool seekToTime)
259{
260    SMILTime earliestFireTime = SMILTime::unresolved();
261
262#ifndef NDEBUG
263    // This boolean will catch any attempts to schedule/unschedule scheduledAnimations during this critical section.
264    // Similarly, any elements removed will unschedule themselves, so this will catch modification of animationsToApply.
265    m_preventScheduledAnimationsChanges = true;
266#endif
267
268    AnimationsVector animationsToApply;
269    GroupedAnimationsMap::iterator end = m_scheduledAnimations.end();
270    for (GroupedAnimationsMap::iterator it = m_scheduledAnimations.begin(); it != end; ++it) {
271        AnimationsVector* scheduled = it->value.get();
272
273        // Sort according to priority. Elements with later begin time have higher priority.
274        // In case of a tie, document order decides.
275        // FIXME: This should also consider timing relationships between the elements. Dependents
276        // have higher priority.
277        sortByPriority(*scheduled, elapsed);
278
279        SVGSMILElement* resultElement = 0;
280        unsigned size = scheduled->size();
281        for (unsigned n = 0; n < size; n++) {
282            SVGSMILElement* animation = scheduled->at(n);
283            ASSERT(animation->timeContainer() == this);
284            ASSERT(animation->targetElement());
285            ASSERT(animation->hasValidAttributeName());
286
287            // Results are accumulated to the first animation that animates and contributes to a particular element/attribute pair.
288            if (!resultElement) {
289                if (!animation->hasValidAttributeType())
290                    continue;
291                resultElement = animation;
292            }
293
294            // This will calculate the contribution from the animation and add it to the resultsElement.
295            if (!animation->progress(elapsed, resultElement, seekToTime) && resultElement == animation)
296                resultElement = 0;
297
298            SMILTime nextFireTime = animation->nextProgressTime();
299            if (nextFireTime.isFinite())
300                earliestFireTime = std::min(nextFireTime, earliestFireTime);
301        }
302
303        if (resultElement)
304            animationsToApply.append(resultElement);
305    }
306
307    unsigned animationsToApplySize = animationsToApply.size();
308    if (!animationsToApplySize) {
309#ifndef NDEBUG
310        m_preventScheduledAnimationsChanges = false;
311#endif
312        startTimer(earliestFireTime, animationFrameDelay);
313        return;
314    }
315
316    // Apply results to target elements.
317    for (unsigned i = 0; i < animationsToApplySize; ++i)
318        animationsToApply[i]->applyResultsToTarget();
319
320#ifndef NDEBUG
321    m_preventScheduledAnimationsChanges = false;
322#endif
323
324    startTimer(earliestFireTime, animationFrameDelay);
325}
326
327}
328