[SVG] Cleanup iterator code in SMILTimeContainer using range-based loop
[WebKit-https.git] / Source / WebCore / svg / animation / SMILTimeContainer.cpp
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
36 namespace WebCore {
37
38 static const double animationFrameDelay = 0.025;
39
40 SMILTimeContainer::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
55 SMILTimeContainer::~SMILTimeContainer()
56 {
57 #ifndef NDEBUG
58     ASSERT(!m_preventScheduledAnimationsChanges);
59 #endif
60 }
61
62 void 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
84 void 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
100 void 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
107 SMILTime 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
116 bool SMILTimeContainer::isActive() const
117 {
118     return m_beginTime && !isPaused();
119 }
120
121 bool SMILTimeContainer::isPaused() const
122 {
123     return m_pauseTime;
124 }
125
126 bool SMILTimeContainer::isStarted() const
127 {
128     return m_beginTime;
129 }
130
131 void 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
148 void 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
159 void SMILTimeContainer::resume()
160 {
161     ASSERT(isPaused());
162
163     m_resumeTime = monotonicallyIncreasingTime();
164     m_pauseTime = 0;
165     startTimer(0);
166 }
167
168 void 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     for (auto& it : m_scheduledAnimations) {
192         AnimationsVector* scheduled = it.value.get();
193         unsigned size = scheduled->size();
194         for (unsigned n = 0; n < size; n++)
195             scheduled->at(n)->reset();
196     }
197 #ifndef NDEBUG
198     m_preventScheduledAnimationsChanges = false;
199 #endif
200
201     updateAnimations(time, true);
202 }
203
204 void SMILTimeContainer::startTimer(SMILTime fireTime, SMILTime minimumDelay)
205 {
206     if (!m_beginTime || isPaused())
207         return;
208
209     if (!fireTime.isFinite())
210         return;
211
212     SMILTime delay = std::max(fireTime - elapsed(), minimumDelay);
213     m_timer.startOneShot(delay.value());
214 }
215
216 void SMILTimeContainer::timerFired(Timer<SMILTimeContainer>*)
217 {
218     ASSERT(m_beginTime);
219     ASSERT(!m_pauseTime);
220     updateAnimations(elapsed());
221 }
222
223 void SMILTimeContainer::updateDocumentOrderIndexes()
224 {
225     unsigned timingElementCount = 0;
226
227     for (auto& smilElement : descendantsOfType<SVGSMILElement>(*m_ownerSVGElement))
228         smilElement.setDocumentOrderIndex(timingElementCount++);
229
230     m_documentOrderIndexesDirty = false;
231 }
232
233 struct PriorityCompare {
234     PriorityCompare(SMILTime elapsed) : m_elapsed(elapsed) {}
235     bool operator()(SVGSMILElement* a, SVGSMILElement* b)
236     {
237         // FIXME: This should also consider possible timing relations between the elements.
238         SMILTime aBegin = a->intervalBegin();
239         SMILTime bBegin = b->intervalBegin();
240         // Frozen elements need to be prioritized based on their previous interval.
241         aBegin = a->isFrozen() && m_elapsed < aBegin ? a->previousIntervalBegin() : aBegin;
242         bBegin = b->isFrozen() && m_elapsed < bBegin ? b->previousIntervalBegin() : bBegin;
243         if (aBegin == bBegin)
244             return a->documentOrderIndex() < b->documentOrderIndex();
245         return aBegin < bBegin;
246     }
247     SMILTime m_elapsed;
248 };
249
250 void SMILTimeContainer::sortByPriority(Vector<SVGSMILElement*>& smilElements, SMILTime elapsed)
251 {
252     if (m_documentOrderIndexesDirty)
253         updateDocumentOrderIndexes();
254     std::sort(smilElements.begin(), smilElements.end(), PriorityCompare(elapsed));
255 }
256
257 void SMILTimeContainer::updateAnimations(SMILTime elapsed, bool seekToTime)
258 {
259     SMILTime earliestFireTime = SMILTime::unresolved();
260
261 #ifndef NDEBUG
262     // This boolean will catch any attempts to schedule/unschedule scheduledAnimations during this critical section.
263     // Similarly, any elements removed will unschedule themselves, so this will catch modification of animationsToApply.
264     m_preventScheduledAnimationsChanges = true;
265 #endif
266
267     AnimationsVector animationsToApply;
268     for (auto& it : m_scheduledAnimations) {
269         AnimationsVector* scheduled = it.value.get();
270
271         // Sort according to priority. Elements with later begin time have higher priority.
272         // In case of a tie, document order decides. 
273         // FIXME: This should also consider timing relationships between the elements. Dependents
274         // have higher priority.
275         sortByPriority(*scheduled, elapsed);
276
277         SVGSMILElement* resultElement = 0;
278         unsigned size = scheduled->size();
279         for (unsigned n = 0; n < size; n++) {
280             SVGSMILElement* animation = scheduled->at(n);
281             ASSERT(animation->timeContainer() == this);
282             ASSERT(animation->targetElement());
283             ASSERT(animation->hasValidAttributeName());
284
285             // Results are accumulated to the first animation that animates and contributes to a particular element/attribute pair.
286             if (!resultElement) {
287                 if (!animation->hasValidAttributeType())
288                     continue;
289                 resultElement = animation;
290             }
291
292             // This will calculate the contribution from the animation and add it to the resultsElement.
293             if (!animation->progress(elapsed, resultElement, seekToTime) && resultElement == animation)
294                 resultElement = 0;
295
296             SMILTime nextFireTime = animation->nextProgressTime();
297             if (nextFireTime.isFinite())
298                 earliestFireTime = std::min(nextFireTime, earliestFireTime);
299         }
300
301         if (resultElement)
302             animationsToApply.append(resultElement);
303     }
304
305     unsigned animationsToApplySize = animationsToApply.size();
306     if (!animationsToApplySize) {
307 #ifndef NDEBUG
308         m_preventScheduledAnimationsChanges = false;
309 #endif
310         startTimer(earliestFireTime, animationFrameDelay);
311         return;
312     }
313
314     // Apply results to target elements.
315     for (unsigned i = 0; i < animationsToApplySize; ++i)
316         animationsToApply[i]->applyResultsToTarget();
317
318 #ifndef NDEBUG
319     m_preventScheduledAnimationsChanges = false;
320 #endif
321
322     startTimer(earliestFireTime, animationFrameDelay);
323 }
324
325 }