399ccb49793924e891e0b1771aa6a68adcecb331
[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& animation : m_scheduledAnimations.values()) {
191         for (auto& element : *animation)
192             element->reset();
193     }
194 #ifndef NDEBUG
195     m_preventScheduledAnimationsChanges = false;
196 #endif
197
198     updateAnimations(time, true);
199 }
200
201 void SMILTimeContainer::startTimer(SMILTime fireTime, SMILTime minimumDelay)
202 {
203     if (!m_beginTime || isPaused())
204         return;
205
206     if (!fireTime.isFinite())
207         return;
208
209     SMILTime delay = std::max(fireTime - elapsed(), minimumDelay);
210     m_timer.startOneShot(delay.value());
211 }
212
213 void SMILTimeContainer::timerFired()
214 {
215     ASSERT(m_beginTime);
216     ASSERT(!m_pauseTime);
217     updateAnimations(elapsed());
218 }
219
220 void SMILTimeContainer::updateDocumentOrderIndexes()
221 {
222     unsigned timingElementCount = 0;
223
224     for (auto& smilElement : descendantsOfType<SVGSMILElement>(*m_ownerSVGElement))
225         smilElement.setDocumentOrderIndex(timingElementCount++);
226
227     m_documentOrderIndexesDirty = false;
228 }
229
230 struct PriorityCompare {
231     PriorityCompare(SMILTime elapsed) : m_elapsed(elapsed) {}
232     bool operator()(SVGSMILElement* a, SVGSMILElement* b)
233     {
234         // FIXME: This should also consider possible timing relations between the elements.
235         SMILTime aBegin = a->intervalBegin();
236         SMILTime bBegin = b->intervalBegin();
237         // Frozen elements need to be prioritized based on their previous interval.
238         aBegin = a->isFrozen() && m_elapsed < aBegin ? a->previousIntervalBegin() : aBegin;
239         bBegin = b->isFrozen() && m_elapsed < bBegin ? b->previousIntervalBegin() : bBegin;
240         if (aBegin == bBegin)
241             return a->documentOrderIndex() < b->documentOrderIndex();
242         return aBegin < bBegin;
243     }
244     SMILTime m_elapsed;
245 };
246
247 void SMILTimeContainer::sortByPriority(Vector<SVGSMILElement*>& smilElements, SMILTime elapsed)
248 {
249     if (m_documentOrderIndexesDirty)
250         updateDocumentOrderIndexes();
251     std::sort(smilElements.begin(), smilElements.end(), PriorityCompare(elapsed));
252 }
253
254 void SMILTimeContainer::updateAnimations(SMILTime elapsed, bool seekToTime)
255 {
256     SMILTime earliestFireTime = SMILTime::unresolved();
257
258 #ifndef NDEBUG
259     // This boolean will catch any attempts to schedule/unschedule scheduledAnimations during this critical section.
260     // Similarly, any elements removed will unschedule themselves, so this will catch modification of animationsToApply.
261     m_preventScheduledAnimationsChanges = true;
262 #endif
263
264     AnimationsVector animationsToApply;
265     for (auto& it : m_scheduledAnimations) {
266         AnimationsVector* scheduled = it.value.get();
267
268         // Sort according to priority. Elements with later begin time have higher priority.
269         // In case of a tie, document order decides. 
270         // FIXME: This should also consider timing relationships between the elements. Dependents
271         // have higher priority.
272         sortByPriority(*scheduled, elapsed);
273
274         SVGSMILElement* resultElement = 0;
275         for (auto& animation : *scheduled) {
276             ASSERT(animation->timeContainer() == this);
277             ASSERT(animation->targetElement());
278             ASSERT(animation->hasValidAttributeName());
279
280             // Results are accumulated to the first animation that animates and contributes to a particular element/attribute pair.
281             if (!resultElement) {
282                 if (!animation->hasValidAttributeType())
283                     continue;
284                 resultElement = animation;
285             }
286
287             // This will calculate the contribution from the animation and add it to the resultsElement.
288             if (!animation->progress(elapsed, resultElement, seekToTime) && resultElement == animation)
289                 resultElement = 0;
290
291             SMILTime nextFireTime = animation->nextProgressTime();
292             if (nextFireTime.isFinite())
293                 earliestFireTime = std::min(nextFireTime, earliestFireTime);
294         }
295
296         if (resultElement)
297             animationsToApply.append(resultElement);
298     }
299
300     if (animationsToApply.isEmpty()) {
301 #ifndef NDEBUG
302         m_preventScheduledAnimationsChanges = false;
303 #endif
304         startTimer(earliestFireTime, animationFrameDelay);
305         return;
306     }
307
308     // Apply results to target elements.
309     for (auto& animation : animationsToApply)
310         animation->applyResultsToTarget();
311
312 #ifndef NDEBUG
313     m_preventScheduledAnimationsChanges = false;
314 #endif
315
316     startTimer(earliestFireTime, animationFrameDelay);
317 }
318
319 }