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