• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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