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