[WTF] Move currentCPUTime and sleep(Seconds) to CPUTime.h and Seconds.h respectively
[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
35 namespace WebCore {
36
37 static const Seconds SMILAnimationFrameDelay { 1_s / 60. };
38 static const Seconds SMILAnimationFrameThrottledDelay { 1_s / 30. };
39
40 SMILTimeContainer::SMILTimeContainer(SVGSVGElement& owner)
41     : m_timer(*this, &SMILTimeContainer::timerFired)
42     , m_ownerSVGElement(owner)
43 {
44 }
45
46 SMILTimeContainer::~SMILTimeContainer()
47 {
48 #ifndef NDEBUG
49     ASSERT(!m_preventScheduledAnimationsChanges);
50 #endif
51 }
52
53 void SMILTimeContainer::schedule(SVGSMILElement* animation, SVGElement* target, const QualifiedName& attributeName)
54 {
55     ASSERT(animation->timeContainer() == this);
56     ASSERT(target);
57     ASSERT(animation->hasValidAttributeName());
58
59 #ifndef NDEBUG
60     ASSERT(!m_preventScheduledAnimationsChanges);
61 #endif
62
63     ElementAttributePair key(target, attributeName);
64     std::unique_ptr<AnimationsVector>& scheduled = m_scheduledAnimations.add(key, nullptr).iterator->value;
65     if (!scheduled)
66         scheduled = std::make_unique<AnimationsVector>();
67     ASSERT(!scheduled->contains(animation));
68     scheduled->append(animation);
69
70     SMILTime nextFireTime = animation->nextProgressTime();
71     if (nextFireTime.isFinite())
72         notifyIntervalsChanged();
73 }
74
75 void SMILTimeContainer::unschedule(SVGSMILElement* animation, SVGElement* target, const QualifiedName& attributeName)
76 {
77     ASSERT(animation->timeContainer() == this);
78
79 #ifndef NDEBUG
80     ASSERT(!m_preventScheduledAnimationsChanges);
81 #endif
82
83     ElementAttributePair key(target, attributeName);
84     AnimationsVector* scheduled = m_scheduledAnimations.get(key);
85     ASSERT(scheduled);
86     bool removed = scheduled->removeFirst(animation);
87     ASSERT_UNUSED(removed, removed);
88 }
89
90 void SMILTimeContainer::notifyIntervalsChanged()
91 {
92     // Schedule updateAnimations() to be called asynchronously so multiple intervals
93     // can change with updateAnimations() only called once at the end.
94     startTimer(elapsed(), 0);
95 }
96
97 Seconds SMILTimeContainer::animationFrameDelay() const
98 {
99     auto* page = m_ownerSVGElement.document().page();
100     if (!page)
101         return SMILAnimationFrameDelay;
102     return page->isLowPowerModeEnabled() ? SMILAnimationFrameThrottledDelay : SMILAnimationFrameDelay;
103 }
104
105 SMILTime SMILTimeContainer::elapsed() const
106 {
107     if (!m_beginTime)
108         return 0_s;
109     if (isPaused())
110         return m_accumulatedActiveTime;
111     return MonotonicTime::now() + m_accumulatedActiveTime - m_resumeTime;
112 }
113
114 bool SMILTimeContainer::isActive() const
115 {
116     return !!m_beginTime && !isPaused();
117 }
118
119 bool SMILTimeContainer::isPaused() const
120 {
121     return !!m_pauseTime;
122 }
123
124 bool SMILTimeContainer::isStarted() const
125 {
126     return !!m_beginTime;
127 }
128
129 void SMILTimeContainer::begin()
130 {
131     ASSERT(!m_beginTime);
132     MonotonicTime now = MonotonicTime::now();
133
134     // If 'm_presetStartTime' is set, the timeline was modified via setElapsed() before the document began.
135     // In this case pass on 'seekToTime=true' to updateAnimations().
136     m_beginTime = m_resumeTime = now - m_presetStartTime;
137     updateAnimations(SMILTime(m_presetStartTime), m_presetStartTime ? true : false);
138     m_presetStartTime = 0_s;
139
140     if (m_pauseTime) {
141         m_pauseTime = now;
142         m_timer.stop();
143     }
144 }
145
146 void SMILTimeContainer::pause()
147 {
148     ASSERT(!isPaused());
149
150     m_pauseTime = MonotonicTime::now();
151     if (m_beginTime) {
152         m_accumulatedActiveTime += m_pauseTime - m_resumeTime;
153         m_timer.stop();
154     }
155 }
156
157 void SMILTimeContainer::resume()
158 {
159     ASSERT(isPaused());
160
161     m_resumeTime = MonotonicTime::now();
162     m_pauseTime = MonotonicTime();
163     startTimer(elapsed(), 0);
164 }
165
166 void SMILTimeContainer::setElapsed(SMILTime time)
167 {
168     // If the documment didn't begin yet, record a new start time, we'll seek to once its possible.
169     if (!m_beginTime) {
170         m_presetStartTime = Seconds(time.value());
171         return;
172     }
173
174     if (m_beginTime)
175         m_timer.stop();
176
177     MonotonicTime now = MonotonicTime::now();
178     m_beginTime = now - Seconds { time.value() };
179
180     if (m_pauseTime) {
181         m_resumeTime = m_pauseTime = now;
182         m_accumulatedActiveTime = Seconds(time.value());
183     } else
184         m_resumeTime = m_beginTime;
185
186 #ifndef NDEBUG
187     m_preventScheduledAnimationsChanges = true;
188 #endif
189     for (auto& animation : m_scheduledAnimations.values()) {
190         for (auto& element : *animation)
191             element->reset();
192     }
193 #ifndef NDEBUG
194     m_preventScheduledAnimationsChanges = false;
195 #endif
196
197     updateAnimations(time, true);
198 }
199
200 void SMILTimeContainer::startTimer(SMILTime elapsed, SMILTime fireTime, SMILTime minimumDelay)
201 {
202     if (!m_beginTime || isPaused())
203         return;
204
205     if (!fireTime.isFinite())
206         return;
207
208     SMILTime delay = std::max(fireTime - elapsed, minimumDelay);
209     m_timer.startOneShot(1_s * delay.value());
210 }
211
212 void SMILTimeContainer::timerFired()
213 {
214     ASSERT(!!m_beginTime);
215     ASSERT(!m_pauseTime);
216     updateAnimations(elapsed());
217 }
218
219 void SMILTimeContainer::updateDocumentOrderIndexes()
220 {
221     unsigned timingElementCount = 0;
222
223     for (auto& smilElement : descendantsOfType<SVGSMILElement>(m_ownerSVGElement))
224         smilElement.setDocumentOrderIndex(timingElementCount++);
225
226     m_documentOrderIndexesDirty = false;
227 }
228
229 struct PriorityCompare {
230     PriorityCompare(SMILTime elapsed) : m_elapsed(elapsed) {}
231     bool operator()(SVGSMILElement* a, SVGSMILElement* b)
232     {
233         // FIXME: This should also consider possible timing relations between the elements.
234         SMILTime aBegin = a->intervalBegin();
235         SMILTime bBegin = b->intervalBegin();
236         // Frozen elements need to be prioritized based on their previous interval.
237         aBegin = a->isFrozen() && m_elapsed < aBegin ? a->previousIntervalBegin() : aBegin;
238         bBegin = b->isFrozen() && m_elapsed < bBegin ? b->previousIntervalBegin() : bBegin;
239         if (aBegin == bBegin)
240             return a->documentOrderIndex() < b->documentOrderIndex();
241         return aBegin < bBegin;
242     }
243     SMILTime m_elapsed;
244 };
245
246 void SMILTimeContainer::sortByPriority(Vector<SVGSMILElement*>& smilElements, SMILTime elapsed)
247 {
248     if (m_documentOrderIndexesDirty)
249         updateDocumentOrderIndexes();
250     std::sort(smilElements.begin(), smilElements.end(), PriorityCompare(elapsed));
251 }
252
253 void SMILTimeContainer::updateAnimations(SMILTime elapsed, bool seekToTime)
254 {
255     SMILTime earliestFireTime = SMILTime::unresolved();
256
257 #ifndef NDEBUG
258     // This boolean will catch any attempts to schedule/unschedule scheduledAnimations during this critical section.
259     // Similarly, any elements removed will unschedule themselves, so this will catch modification of animationsToApply.
260     m_preventScheduledAnimationsChanges = true;
261 #endif
262
263     AnimationsVector animationsToApply;
264     for (auto& it : m_scheduledAnimations) {
265         AnimationsVector* scheduled = it.value.get();
266         for (auto* animation : *scheduled) {
267             if (!animation->hasConditionsConnected())
268                 animation->connectConditions();
269         }
270     }
271     
272     for (auto& it : m_scheduledAnimations) {
273         AnimationsVector* scheduled = it.value.get();
274
275         // Sort according to priority. Elements with later begin time have higher priority.
276         // In case of a tie, document order decides. 
277         // FIXME: This should also consider timing relationships between the elements. Dependents
278         // have higher priority.
279         sortByPriority(*scheduled, elapsed);
280
281         RefPtr<SVGSMILElement> resultElement;
282         for (auto& animation : *scheduled) {
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.get(), seekToTime) && resultElement == animation)
296                 resultElement = nullptr;
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.get());
305     }
306
307     if (animationsToApply.isEmpty()) {
308 #ifndef NDEBUG
309         m_preventScheduledAnimationsChanges = false;
310 #endif
311         startTimer(elapsed, earliestFireTime, animationFrameDelay());
312         return;
313     }
314
315     // Apply results to target elements.
316     for (auto& animation : animationsToApply)
317         animation->applyResultsToTarget();
318
319 #ifndef NDEBUG
320     m_preventScheduledAnimationsChanges = false;
321 #endif
322
323     startTimer(elapsed, earliestFireTime, animationFrameDelay());
324 }
325
326 }