4156f189d921c594ae8afdbd4144410cf758065d
[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 "Page.h"
32 #include "SVGSMILElement.h"
33 #include "SVGSVGElement.h"
34 #include <wtf/CurrentTime.h>
35
36 namespace WebCore {
37
38 static const Seconds SMILAnimationFrameDelay { 1_s / 60. };
39 static const Seconds SMILAnimationFrameThrottledDelay { 1_s / 30. };
40
41 SMILTimeContainer::SMILTimeContainer(SVGSVGElement& owner)
42     : m_timer(*this, &SMILTimeContainer::timerFired)
43     , m_ownerSVGElement(owner)
44 {
45 }
46
47 SMILTimeContainer::~SMILTimeContainer()
48 {
49 #ifndef NDEBUG
50     ASSERT(!m_preventScheduledAnimationsChanges);
51 #endif
52 }
53
54 void SMILTimeContainer::schedule(SVGSMILElement* animation, SVGElement* target, const QualifiedName& attributeName)
55 {
56     ASSERT(animation->timeContainer() == this);
57     ASSERT(target);
58     ASSERT(animation->hasValidAttributeName());
59
60 #ifndef NDEBUG
61     ASSERT(!m_preventScheduledAnimationsChanges);
62 #endif
63
64     ElementAttributePair key(target, attributeName);
65     std::unique_ptr<AnimationsVector>& scheduled = m_scheduledAnimations.add(key, nullptr).iterator->value;
66     if (!scheduled)
67         scheduled = std::make_unique<AnimationsVector>();
68     ASSERT(!scheduled->contains(animation));
69     scheduled->append(animation);
70
71     SMILTime nextFireTime = animation->nextProgressTime();
72     if (nextFireTime.isFinite())
73         notifyIntervalsChanged();
74 }
75
76 void SMILTimeContainer::unschedule(SVGSMILElement* animation, SVGElement* target, const QualifiedName& attributeName)
77 {
78     ASSERT(animation->timeContainer() == this);
79
80 #ifndef NDEBUG
81     ASSERT(!m_preventScheduledAnimationsChanges);
82 #endif
83
84     ElementAttributePair key(target, attributeName);
85     AnimationsVector* scheduled = m_scheduledAnimations.get(key);
86     ASSERT(scheduled);
87     bool removed = scheduled->removeFirst(animation);
88     ASSERT_UNUSED(removed, removed);
89 }
90
91 void SMILTimeContainer::notifyIntervalsChanged()
92 {
93     // Schedule updateAnimations() to be called asynchronously so multiple intervals
94     // can change with updateAnimations() only called once at the end.
95     startTimer(elapsed(), 0);
96 }
97
98 Seconds SMILTimeContainer::animationFrameDelay() const
99 {
100     auto* page = m_ownerSVGElement.document().page();
101     if (!page)
102         return SMILAnimationFrameDelay;
103     return page->isLowPowerModeEnabled() ? SMILAnimationFrameThrottledDelay : SMILAnimationFrameDelay;
104 }
105
106 SMILTime SMILTimeContainer::elapsed() const
107 {
108     if (!m_beginTime)
109         return 0_s;
110     if (isPaused())
111         return m_accumulatedActiveTime;
112     return MonotonicTime::now() + 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     MonotonicTime now = MonotonicTime::now();
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_s;
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 = MonotonicTime::now();
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 = MonotonicTime::now();
163     m_pauseTime = MonotonicTime();
164     startTimer(elapsed(), 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 = Seconds(time.value());
172         return;
173     }
174
175     if (m_beginTime)
176         m_timer.stop();
177
178     MonotonicTime now = MonotonicTime::now();
179     m_beginTime = now - Seconds { time.value() };
180
181     if (m_pauseTime) {
182         m_resumeTime = m_pauseTime = now;
183         m_accumulatedActiveTime = Seconds(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 elapsed, 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(1_s * 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         for (auto* animation : *scheduled) {
268             if (!animation->hasConditionsConnected())
269                 animation->connectConditions();
270         }
271     }
272     
273     for (auto& it : m_scheduledAnimations) {
274         AnimationsVector* scheduled = it.value.get();
275
276         // Sort according to priority. Elements with later begin time have higher priority.
277         // In case of a tie, document order decides. 
278         // FIXME: This should also consider timing relationships between the elements. Dependents
279         // have higher priority.
280         sortByPriority(*scheduled, elapsed);
281
282         RefPtr<SVGSMILElement> resultElement;
283         for (auto& animation : *scheduled) {
284             ASSERT(animation->timeContainer() == this);
285             ASSERT(animation->targetElement());
286             ASSERT(animation->hasValidAttributeName());
287
288             // Results are accumulated to the first animation that animates and contributes to a particular element/attribute pair.
289             if (!resultElement) {
290                 if (!animation->hasValidAttributeType())
291                     continue;
292                 resultElement = animation;
293             }
294
295             // This will calculate the contribution from the animation and add it to the resultsElement.
296             if (!animation->progress(elapsed, resultElement.get(), seekToTime) && resultElement == animation)
297                 resultElement = nullptr;
298
299             SMILTime nextFireTime = animation->nextProgressTime();
300             if (nextFireTime.isFinite())
301                 earliestFireTime = std::min(nextFireTime, earliestFireTime);
302         }
303
304         if (resultElement)
305             animationsToApply.append(resultElement.get());
306     }
307
308     if (animationsToApply.isEmpty()) {
309 #ifndef NDEBUG
310         m_preventScheduledAnimationsChanges = false;
311 #endif
312         startTimer(elapsed, earliestFireTime, animationFrameDelay());
313         return;
314     }
315
316     // Apply results to target elements.
317     for (auto& animation : animationsToApply)
318         animation->applyResultsToTarget();
319
320 #ifndef NDEBUG
321     m_preventScheduledAnimationsChanges = false;
322 #endif
323
324     startTimer(elapsed, earliestFireTime, animationFrameDelay());
325 }
326
327 }