• 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 "SMILTimeContainer.h"
28 
29 #if ENABLE(SVG)
30 
31 #include "CSSComputedStyleDeclaration.h"
32 #include "CSSParser.h"
33 #include "Document.h"
34 #include "SVGAnimationElement.h"
35 #include "SVGSMILElement.h"
36 #include "SVGSVGElement.h"
37 #include <wtf/CurrentTime.h>
38 
39 using namespace std;
40 
41 namespace WebCore {
42 
43 static const double animationFrameDelay = 0.025;
44 
SMILTimeContainer(SVGSVGElement * owner)45 SMILTimeContainer::SMILTimeContainer(SVGSVGElement* owner)
46     : m_beginTime(0)
47     , m_pauseTime(0)
48     , m_accumulatedPauseTime(0)
49     , m_nextManualSampleTime(0)
50     , m_documentOrderIndexesDirty(false)
51     , m_timer(this, &SMILTimeContainer::timerFired)
52     , m_ownerSVGElement(owner)
53 {
54 }
55 
56 #if !ENABLE(SVG_ANIMATION)
begin()57 void SMILTimeContainer::begin() {}
pause()58 void SMILTimeContainer::pause() {}
resume()59 void SMILTimeContainer::resume() {}
elapsed() const60 SMILTime SMILTimeContainer::elapsed() const { return 0; }
isPaused() const61 bool SMILTimeContainer::isPaused() const { return false; }
timerFired(Timer<SMILTimeContainer> *)62 void SMILTimeContainer::timerFired(Timer<SMILTimeContainer>*) {}
63 #else
64 
schedule(SVGSMILElement * animation)65 void SMILTimeContainer::schedule(SVGSMILElement* animation)
66 {
67     ASSERT(animation->timeContainer() == this);
68     SMILTime nextFireTime = animation->nextProgressTime();
69     if (!nextFireTime.isFinite())
70         return;
71     m_scheduledAnimations.add(animation);
72     startTimer(0);
73 }
74 
unschedule(SVGSMILElement * animation)75 void SMILTimeContainer::unschedule(SVGSMILElement* animation)
76 {
77     ASSERT(animation->timeContainer() == this);
78 
79     m_scheduledAnimations.remove(animation);
80 }
81 
elapsed() const82 SMILTime SMILTimeContainer::elapsed() const
83 {
84     if (!m_beginTime)
85         return 0;
86     return currentTime() - m_beginTime - m_accumulatedPauseTime;
87 }
88 
isActive() const89 bool SMILTimeContainer::isActive() const
90 {
91     return m_beginTime && !isPaused();
92 }
93 
isPaused() const94 bool SMILTimeContainer::isPaused() const
95 {
96     return m_pauseTime;
97 }
98 
begin()99 void SMILTimeContainer::begin()
100 {
101     ASSERT(!m_beginTime);
102     m_beginTime = currentTime();
103     updateAnimations(0);
104 }
105 
pause()106 void SMILTimeContainer::pause()
107 {
108     if (!m_beginTime)
109         return;
110     ASSERT(!isPaused());
111     m_pauseTime = currentTime();
112     m_timer.stop();
113 }
114 
resume()115 void SMILTimeContainer::resume()
116 {
117     if (!m_beginTime)
118         return;
119     ASSERT(isPaused());
120     m_accumulatedPauseTime += currentTime() - m_pauseTime;
121     m_pauseTime = 0;
122     startTimer(0);
123 }
124 
startTimer(SMILTime fireTime,SMILTime minimumDelay)125 void SMILTimeContainer::startTimer(SMILTime fireTime, SMILTime minimumDelay)
126 {
127     if (!m_beginTime || isPaused())
128         return;
129 
130     if (!fireTime.isFinite())
131         return;
132 
133     SMILTime delay = max(fireTime - elapsed(), minimumDelay);
134     m_timer.startOneShot(delay.value());
135 }
136 
timerFired(Timer<SMILTimeContainer> *)137 void SMILTimeContainer::timerFired(Timer<SMILTimeContainer>*)
138 {
139     ASSERT(m_beginTime);
140     ASSERT(!m_pauseTime);
141     SMILTime elapsed = this->elapsed();
142     updateAnimations(elapsed);
143 }
144 
updateDocumentOrderIndexes()145 void SMILTimeContainer::updateDocumentOrderIndexes()
146 {
147     unsigned timingElementCount = 0;
148     for (Node* node = m_ownerSVGElement; node; node = node->traverseNextNode(m_ownerSVGElement)) {
149         if (SVGSMILElement::isSMILElement(node))
150             static_cast<SVGSMILElement*>(node)->setDocumentOrderIndex(timingElementCount++);
151     }
152     m_documentOrderIndexesDirty = false;
153 }
154 
155 struct PriorityCompare {
PriorityCompareWebCore::PriorityCompare156     PriorityCompare(SMILTime elapsed) : m_elapsed(elapsed) {}
operator ()WebCore::PriorityCompare157     bool operator()(SVGSMILElement* a, SVGSMILElement* b)
158     {
159         // FIXME: This should also consider possible timing relations between the elements.
160         SMILTime aBegin = a->intervalBegin();
161         SMILTime bBegin = b->intervalBegin();
162         // Frozen elements need to be prioritized based on their previous interval.
163         aBegin = a->isFrozen() && m_elapsed < aBegin ? a->previousIntervalBegin() : aBegin;
164         bBegin = b->isFrozen() && m_elapsed < bBegin ? b->previousIntervalBegin() : bBegin;
165         if (aBegin == bBegin)
166             return a->documentOrderIndex() < b->documentOrderIndex();
167         return aBegin < bBegin;
168     }
169     SMILTime m_elapsed;
170 };
171 
sortByPriority(Vector<SVGSMILElement * > & smilElements,SMILTime elapsed)172 void SMILTimeContainer::sortByPriority(Vector<SVGSMILElement*>& smilElements, SMILTime elapsed)
173 {
174     if (m_documentOrderIndexesDirty)
175         updateDocumentOrderIndexes();
176     std::sort(smilElements.begin(), smilElements.end(), PriorityCompare(elapsed));
177 }
178 
applyOrderSortFunction(SVGSMILElement * a,SVGSMILElement * b)179 static bool applyOrderSortFunction(SVGSMILElement* a, SVGSMILElement* b)
180 {
181     if (!a->hasTagName(SVGNames::animateTransformTag) && b->hasTagName(SVGNames::animateTransformTag))
182         return true;
183     return false;
184 }
185 
sortByApplyOrder(Vector<SVGSMILElement * > & smilElements)186 static void sortByApplyOrder(Vector<SVGSMILElement*>& smilElements)
187 {
188     std::sort(smilElements.begin(), smilElements.end(), applyOrderSortFunction);
189 }
190 
baseValueFor(ElementAttributePair key)191 String SMILTimeContainer::baseValueFor(ElementAttributePair key)
192 {
193     // FIXME: We wouldn't need to do this if we were keeping base values around properly in DOM.
194     // Currently animation overwrites them so we need to save them somewhere.
195     BaseValueMap::iterator it = m_savedBaseValues.find(key);
196     if (it != m_savedBaseValues.end())
197         return it->second;
198 
199     SVGElement* target = key.first;
200     String attributeName = key.second;
201     ASSERT(target);
202     ASSERT(!attributeName.isEmpty());
203     String baseValue;
204     if (SVGAnimationElement::attributeIsCSS(attributeName))
205         baseValue = computedStyle(target)->getPropertyValue(cssPropertyID(attributeName));
206     else
207         baseValue = target->getAttribute(attributeName);
208     m_savedBaseValues.add(key, baseValue);
209     return baseValue;
210 }
211 
sampleAnimationAtTime(const String & elementId,double newTime)212 void SMILTimeContainer::sampleAnimationAtTime(const String& elementId, double newTime)
213 {
214     ASSERT(m_beginTime);
215     ASSERT(!isPaused());
216 
217     // Fast-forward to the time DRT wants to sample
218     m_timer.stop();
219     m_nextSamplingTarget = elementId;
220     m_nextManualSampleTime = newTime;
221 
222     updateAnimations(elapsed());
223 }
224 
updateAnimations(SMILTime elapsed)225 void SMILTimeContainer::updateAnimations(SMILTime elapsed)
226 {
227     SMILTime earliersFireTime = SMILTime::unresolved();
228 
229     Vector<SVGSMILElement*> toAnimate;
230     copyToVector(m_scheduledAnimations, toAnimate);
231 
232     if (m_nextManualSampleTime) {
233         SMILTime samplingDiff;
234         for (unsigned n = 0; n < toAnimate.size(); ++n) {
235             SVGSMILElement* animation = toAnimate[n];
236             ASSERT(animation->timeContainer() == this);
237 
238             SVGElement* targetElement = animation->targetElement();
239             if (!targetElement || targetElement->getIDAttribute() != m_nextSamplingTarget)
240                 continue;
241 
242             samplingDiff = animation->intervalBegin();
243             break;
244         }
245 
246         elapsed = SMILTime(m_nextManualSampleTime) + samplingDiff;
247         m_nextManualSampleTime = 0;
248     }
249 
250     // Sort according to priority. Elements with later begin time have higher priority.
251     // In case of a tie, document order decides.
252     // FIXME: This should also consider timing relationships between the elements. Dependents
253     // have higher priority.
254     sortByPriority(toAnimate, elapsed);
255 
256     // Calculate animation contributions.
257     typedef HashMap<ElementAttributePair, SVGSMILElement*> ResultElementMap;
258     ResultElementMap resultsElements;
259     for (unsigned n = 0; n < toAnimate.size(); ++n) {
260         SVGSMILElement* animation = toAnimate[n];
261         ASSERT(animation->timeContainer() == this);
262 
263         SVGElement* targetElement = animation->targetElement();
264         if (!targetElement)
265             continue;
266         String attributeName = animation->attributeName();
267         if (attributeName.isEmpty()) {
268             if (animation->hasTagName(SVGNames::animateMotionTag))
269                 attributeName = SVGNames::animateMotionTag.localName();
270             else
271                 continue;
272         }
273 
274         // Results are accumulated to the first animation that animates a particular element/attribute pair.
275         ElementAttributePair key(targetElement, attributeName);
276         SVGSMILElement* resultElement = resultsElements.get(key);
277         if (!resultElement) {
278             resultElement = animation;
279             resultElement->resetToBaseValue(baseValueFor(key));
280             resultsElements.add(key, resultElement);
281         }
282 
283         // This will calculate the contribution from the animation and add it to the resultsElement.
284         animation->progress(elapsed, resultElement);
285 
286         SMILTime nextFireTime = animation->nextProgressTime();
287         if (nextFireTime.isFinite())
288             earliersFireTime = min(nextFireTime, earliersFireTime);
289         else if (!animation->isContributing(elapsed)) {
290             m_scheduledAnimations.remove(animation);
291             if (m_scheduledAnimations.isEmpty())
292                 m_savedBaseValues.clear();
293         }
294     }
295 
296     Vector<SVGSMILElement*> animationsToApply;
297     ResultElementMap::iterator end = resultsElements.end();
298     for (ResultElementMap::iterator it = resultsElements.begin(); it != end; ++it)
299         animationsToApply.append(it->second);
300 
301     // Sort <animateTranform> to be the last one to be applied. <animate> may change transform attribute as
302     // well (directly or indirectly by modifying <use> x/y) and this way transforms combine properly.
303     sortByApplyOrder(animationsToApply);
304 
305     // Apply results to target elements.
306     for (unsigned n = 0; n < animationsToApply.size(); ++n)
307         animationsToApply[n]->applyResultsToTarget();
308 
309     startTimer(earliersFireTime, animationFrameDelay);
310 
311     Document::updateStyleForAllDocuments();
312 }
313 
314 #endif
315 
316 }
317 
318 #endif // ENABLE(SVG)
319