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 #include "CSSComputedStyleDeclaration.h"
31 #include "CSSParser.h"
32 #include "Document.h"
33 #include "SVGAnimationElement.h"
34 #include "SVGNames.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* targetElement = key.first;
200 QualifiedName attributeName = key.second;
201 ASSERT(targetElement);
202 ASSERT(attributeName != anyQName());
203 String baseValue;
204 if (SVGAnimationElement::isTargetAttributeCSSProperty(targetElement, attributeName))
205 baseValue = computedStyle(targetElement)->getPropertyValue(cssPropertyID(attributeName.localName()));
206 else
207 baseValue = targetElement->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 // FIXME: This should probably be using getIdAttribute instead of idForStyleResolution.
240 if (!targetElement || !targetElement->hasID() || targetElement->idForStyleResolution() != m_nextSamplingTarget)
241 continue;
242
243 samplingDiff = animation->intervalBegin();
244 break;
245 }
246
247 elapsed = SMILTime(m_nextManualSampleTime) + samplingDiff;
248 m_nextManualSampleTime = 0;
249 }
250
251 // Sort according to priority. Elements with later begin time have higher priority.
252 // In case of a tie, document order decides.
253 // FIXME: This should also consider timing relationships between the elements. Dependents
254 // have higher priority.
255 sortByPriority(toAnimate, elapsed);
256
257 // Calculate animation contributions.
258 typedef HashMap<ElementAttributePair, RefPtr<SVGSMILElement> > ResultElementMap;
259 ResultElementMap resultsElements;
260 for (unsigned n = 0; n < toAnimate.size(); ++n) {
261 SVGSMILElement* animation = toAnimate[n];
262 ASSERT(animation->timeContainer() == this);
263
264 SVGElement* targetElement = animation->targetElement();
265 if (!targetElement)
266 continue;
267
268 QualifiedName attributeName = animation->attributeName();
269 if (attributeName == anyQName()) {
270 if (animation->hasTagName(SVGNames::animateMotionTag))
271 attributeName = SVGNames::animateMotionTag;
272 else
273 continue;
274 }
275
276 // Results are accumulated to the first animation that animates a particular element/attribute pair.
277 ElementAttributePair key(targetElement, attributeName);
278 SVGSMILElement* resultElement = resultsElements.get(key).get();
279 if (!resultElement) {
280 if (!animation->hasValidAttributeType())
281 continue;
282 resultElement = animation;
283 resultElement->resetToBaseValue(baseValueFor(key));
284 resultsElements.add(key, resultElement);
285 }
286
287 // This will calculate the contribution from the animation and add it to the resultsElement.
288 animation->progress(elapsed, resultElement);
289
290 SMILTime nextFireTime = animation->nextProgressTime();
291 if (nextFireTime.isFinite())
292 earliersFireTime = min(nextFireTime, earliersFireTime);
293 else if (!animation->isContributing(elapsed)) {
294 m_scheduledAnimations.remove(animation);
295 if (m_scheduledAnimations.isEmpty())
296 m_savedBaseValues.clear();
297 }
298 }
299
300 Vector<SVGSMILElement*> animationsToApply;
301 ResultElementMap::iterator end = resultsElements.end();
302 for (ResultElementMap::iterator it = resultsElements.begin(); it != end; ++it)
303 animationsToApply.append(it->second.get());
304
305 // Sort <animateTranform> to be the last one to be applied. <animate> may change transform attribute as
306 // well (directly or indirectly by modifying <use> x/y) and this way transforms combine properly.
307 sortByApplyOrder(animationsToApply);
308
309 // Apply results to target elements.
310 for (unsigned n = 0; n < animationsToApply.size(); ++n)
311 animationsToApply[n]->applyResultsToTarget();
312
313 startTimer(earliersFireTime, animationFrameDelay);
314
315 Document::updateStyleForAllDocuments();
316 }
317
318 #endif
319
320 }
321
322 #endif // ENABLE(SVG)
323