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 "core/svg/animation/SMILTimeContainer.h"
28
29 #include "core/dom/ElementTraversal.h"
30 #include "core/svg/SVGSVGElement.h"
31 #include "core/svg/animation/SVGSMILElement.h"
32 #include "wtf/CurrentTime.h"
33
34 using namespace std;
35
36 namespace WebCore {
37
38 static const double animationFrameDelay = 0.025;
39
SMILTimeContainer(SVGSVGElement * owner)40 SMILTimeContainer::SMILTimeContainer(SVGSVGElement* owner)
41 : m_beginTime(0)
42 , m_pauseTime(0)
43 , m_resumeTime(0)
44 , m_accumulatedActiveTime(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
~SMILTimeContainer()55 SMILTimeContainer::~SMILTimeContainer()
56 {
57 #ifndef NDEBUG
58 ASSERT(!m_preventScheduledAnimationsChanges);
59 #endif
60 }
61
schedule(SVGSMILElement * animation,SVGElement * target,const QualifiedName & attributeName)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 OwnPtr<AnimationsVector>& scheduled = m_scheduledAnimations.add(key, nullptr).iterator->value;
74 if (!scheduled)
75 scheduled = adoptPtr(new AnimationsVector);
76 ASSERT(!scheduled->contains(animation));
77 scheduled->append(animation);
78
79 SMILTime nextFireTime = animation->nextProgressTime();
80 if (nextFireTime.isFinite())
81 notifyIntervalsChanged();
82 }
83
unschedule(SVGSMILElement * animation,SVGElement * target,const QualifiedName & attributeName)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 != kNotFound);
97 scheduled->remove(idx);
98 }
99
notifyIntervalsChanged()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
elapsed() const107 SMILTime SMILTimeContainer::elapsed() const
108 {
109 if (!m_beginTime)
110 return 0;
111
112 if (isPaused())
113 return m_accumulatedActiveTime;
114
115 return currentTime() + m_accumulatedActiveTime - lastResumeTime();
116 }
117
isActive() const118 bool SMILTimeContainer::isActive() const
119 {
120 return m_beginTime && !isPaused();
121 }
122
isPaused() const123 bool SMILTimeContainer::isPaused() const
124 {
125 return m_pauseTime;
126 }
127
isStarted() const128 bool SMILTimeContainer::isStarted() const
129 {
130 return m_beginTime;
131 }
132
begin()133 void SMILTimeContainer::begin()
134 {
135 ASSERT(!m_beginTime);
136 double now = currentTime();
137
138 // If 'm_presetStartTime' is set, the timeline was modified via setElapsed() before the document began.
139 // In this case pass on 'seekToTime=true' to updateAnimations().
140 m_beginTime = now - m_presetStartTime;
141 updateAnimations(SMILTime(m_presetStartTime), m_presetStartTime ? true : false);
142 m_presetStartTime = 0;
143
144 if (m_pauseTime) {
145 m_pauseTime = now;
146 m_timer.stop();
147 }
148 }
149
pause()150 void SMILTimeContainer::pause()
151 {
152 ASSERT(!isPaused());
153 m_pauseTime = currentTime();
154
155 if (m_beginTime) {
156 m_accumulatedActiveTime += m_pauseTime - lastResumeTime();
157 m_timer.stop();
158 }
159 m_resumeTime = 0;
160 }
161
resume()162 void SMILTimeContainer::resume()
163 {
164 ASSERT(isPaused());
165 m_resumeTime = currentTime();
166
167 m_pauseTime = 0;
168 startTimer(0);
169 }
170
setElapsed(SMILTime time)171 void SMILTimeContainer::setElapsed(SMILTime time)
172 {
173 // If the documment didn't begin yet, record a new start time, we'll seek to once its possible.
174 if (!m_beginTime) {
175 m_presetStartTime = time.value();
176 return;
177 }
178
179 if (m_beginTime)
180 m_timer.stop();
181
182 double now = currentTime();
183 m_beginTime = now - time.value();
184 m_resumeTime = 0;
185 if (m_pauseTime) {
186 m_pauseTime = now;
187 m_accumulatedActiveTime = time.value();
188 } else {
189 m_accumulatedActiveTime = 0;
190 }
191
192 #ifndef NDEBUG
193 m_preventScheduledAnimationsChanges = true;
194 #endif
195 GroupedAnimationsMap::iterator end = m_scheduledAnimations.end();
196 for (GroupedAnimationsMap::iterator it = m_scheduledAnimations.begin(); it != end; ++it) {
197 AnimationsVector* scheduled = it->value.get();
198 unsigned size = scheduled->size();
199 for (unsigned n = 0; n < size; n++)
200 scheduled->at(n)->reset();
201 }
202 #ifndef NDEBUG
203 m_preventScheduledAnimationsChanges = false;
204 #endif
205
206 updateAnimations(time, true);
207 }
208
startTimer(SMILTime fireTime,SMILTime minimumDelay)209 void SMILTimeContainer::startTimer(SMILTime fireTime, SMILTime minimumDelay)
210 {
211 if (!m_beginTime || isPaused())
212 return;
213
214 if (!fireTime.isFinite())
215 return;
216
217 SMILTime delay = max(fireTime - elapsed(), minimumDelay);
218 m_timer.startOneShot(delay.value());
219 }
220
timerFired(Timer<SMILTimeContainer> *)221 void SMILTimeContainer::timerFired(Timer<SMILTimeContainer>*)
222 {
223 ASSERT(m_beginTime);
224 ASSERT(!m_pauseTime);
225 updateAnimations(elapsed());
226 }
227
updateDocumentOrderIndexes()228 void SMILTimeContainer::updateDocumentOrderIndexes()
229 {
230 unsigned timingElementCount = 0;
231 for (Element* element = m_ownerSVGElement; element; element = ElementTraversal::next(*element, m_ownerSVGElement)) {
232 if (isSVGSMILElement(*element))
233 toSVGSMILElement(element)->setDocumentOrderIndex(timingElementCount++);
234 }
235 m_documentOrderIndexesDirty = false;
236 }
237
238 struct PriorityCompare {
PriorityCompareWebCore::PriorityCompare239 PriorityCompare(SMILTime elapsed) : m_elapsed(elapsed) {}
operator ()WebCore::PriorityCompare240 bool operator()(SVGSMILElement* a, SVGSMILElement* b)
241 {
242 // FIXME: This should also consider possible timing relations between the elements.
243 SMILTime aBegin = a->intervalBegin();
244 SMILTime bBegin = b->intervalBegin();
245 // Frozen elements need to be prioritized based on their previous interval.
246 aBegin = a->isFrozen() && m_elapsed < aBegin ? a->previousIntervalBegin() : aBegin;
247 bBegin = b->isFrozen() && m_elapsed < bBegin ? b->previousIntervalBegin() : bBegin;
248 if (aBegin == bBegin)
249 return a->documentOrderIndex() < b->documentOrderIndex();
250 return aBegin < bBegin;
251 }
252 SMILTime m_elapsed;
253 };
254
sortByPriority(Vector<SVGSMILElement * > & smilElements,SMILTime elapsed)255 void SMILTimeContainer::sortByPriority(Vector<SVGSMILElement*>& smilElements, SMILTime elapsed)
256 {
257 if (m_documentOrderIndexesDirty)
258 updateDocumentOrderIndexes();
259 std::sort(smilElements.begin(), smilElements.end(), PriorityCompare(elapsed));
260 }
261
updateAnimations(SMILTime elapsed,bool seekToTime)262 void SMILTimeContainer::updateAnimations(SMILTime elapsed, bool seekToTime)
263 {
264 SMILTime earliestFireTime = SMILTime::unresolved();
265
266 #ifndef NDEBUG
267 // This boolean will catch any attempts to schedule/unschedule scheduledAnimations during this critical section.
268 // Similarly, any elements removed will unschedule themselves, so this will catch modification of animationsToApply.
269 m_preventScheduledAnimationsChanges = true;
270 #endif
271
272 AnimationsVector animationsToApply;
273 GroupedAnimationsMap::iterator end = m_scheduledAnimations.end();
274 for (GroupedAnimationsMap::iterator it = m_scheduledAnimations.begin(); it != end; ++it) {
275 AnimationsVector* scheduled = it->value.get();
276
277 // Sort according to priority. Elements with later begin time have higher priority.
278 // In case of a tie, document order decides.
279 // FIXME: This should also consider timing relationships between the elements. Dependents
280 // have higher priority.
281 sortByPriority(*scheduled, elapsed);
282
283 SVGSMILElement* resultElement = 0;
284 unsigned size = scheduled->size();
285 for (unsigned n = 0; n < size; n++) {
286 SVGSMILElement* animation = scheduled->at(n);
287 ASSERT(animation->timeContainer() == this);
288 ASSERT(animation->targetElement());
289 ASSERT(animation->hasValidAttributeName());
290
291 // Results are accumulated to the first animation that animates and contributes to a particular element/attribute pair.
292 // FIXME: we should ensure that resultElement is of an appropriate type.
293 if (!resultElement) {
294 if (!animation->hasValidAttributeType())
295 continue;
296 resultElement = animation;
297 }
298
299 // This will calculate the contribution from the animation and add it to the resultsElement.
300 if (!animation->progress(elapsed, resultElement, seekToTime) && resultElement == animation)
301 resultElement = 0;
302
303 SMILTime nextFireTime = animation->nextProgressTime();
304 if (nextFireTime.isFinite())
305 earliestFireTime = min(nextFireTime, earliestFireTime);
306 }
307
308 if (resultElement)
309 animationsToApply.append(resultElement);
310 }
311
312 unsigned animationsToApplySize = animationsToApply.size();
313 if (!animationsToApplySize) {
314 #ifndef NDEBUG
315 m_preventScheduledAnimationsChanges = false;
316 #endif
317 startTimer(earliestFireTime, animationFrameDelay);
318 return;
319 }
320
321 // Apply results to target elements.
322 for (unsigned i = 0; i < animationsToApplySize; ++i)
323 animationsToApply[i]->applyResultsToTarget();
324
325 #ifndef NDEBUG
326 m_preventScheduledAnimationsChanges = false;
327 #endif
328
329 startTimer(earliestFireTime, animationFrameDelay);
330 }
331
332 }
333