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