2 * Copyright (C) 2008 Apple Inc. All Rights Reserved.
4 * Redistribution and use in source and binary forms, with or without
5 * modification, are permitted provided that the following conditions
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.
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.
27 #include "SMILTimeContainer.h"
29 #include "CSSComputedStyleDeclaration.h"
30 #include "CSSParser.h"
32 #include "SVGAnimationElement.h"
33 #include "SVGSMILElement.h"
34 #include "SVGSVGElement.h"
35 #include "SystemTime.h"
41 static const double animationFrameDelay
= 0.025;
43 SMILTimeContainer::SMILTimeContainer(SVGSVGElement
* owner
)
46 , m_accumulatedPauseTime(0)
47 , m_documentOrderIndexesDirty(false)
48 , m_timer(this, &SMILTimeContainer::timerFired
)
49 , m_ownerSVGElement(owner
)
53 #if !ENABLE(SVG_ANIMATION)
54 void SMILTimeContainer::begin() {}
55 void SMILTimeContainer::pause() {}
56 void SMILTimeContainer::resume() {}
57 SMILTime
SMILTimeContainer::elapsed() const { return 0; }
58 bool SMILTimeContainer::isPaused() const { return false; }
59 void SMILTimeContainer::timerFired(Timer
<SMILTimeContainer
>*) {}
62 void SMILTimeContainer::schedule(SVGSMILElement
* animation
)
64 ASSERT(animation
->timeContainer() == this);
65 SMILTime nextFireTime
= animation
->nextProgressTime();
66 if (!nextFireTime
.isFinite())
68 m_scheduledAnimations
.add(animation
);
72 void SMILTimeContainer::unschedule(SVGSMILElement
* animation
)
74 ASSERT(animation
->timeContainer() == this);
76 m_scheduledAnimations
.remove(animation
);
79 SMILTime
SMILTimeContainer::elapsed() const
83 return currentTime() - m_beginTime
- m_accumulatedPauseTime
;
86 bool SMILTimeContainer::isActive() const
88 return m_beginTime
&& !isPaused();
91 bool SMILTimeContainer::isPaused() const
96 void SMILTimeContainer::begin()
99 m_beginTime
= currentTime();
103 void SMILTimeContainer::pause()
108 m_pauseTime
= currentTime();
112 void SMILTimeContainer::resume()
117 m_accumulatedPauseTime
+= currentTime() - m_pauseTime
;
122 void SMILTimeContainer::startTimer(SMILTime fireTime
, SMILTime minimumDelay
)
124 if (!m_beginTime
|| isPaused())
127 if (!fireTime
.isFinite())
130 SMILTime delay
= max(fireTime
- elapsed(), minimumDelay
);
131 m_timer
.startOneShot(delay
.value());
134 void SMILTimeContainer::timerFired(Timer
<SMILTimeContainer
>*)
137 ASSERT(!m_pauseTime
);
138 SMILTime elapsed
= this->elapsed();
139 updateAnimations(elapsed
);
142 void SMILTimeContainer::updateDocumentOrderIndexes()
144 unsigned timingElementCount
= 0;
145 for (Node
* node
= m_ownerSVGElement
; node
; node
= node
->traverseNextNode(m_ownerSVGElement
)) {
146 if (SVGSMILElement::isSMILElement(node
))
147 static_cast<SVGSMILElement
*>(node
)->setDocumentOrderIndex(timingElementCount
++);
149 m_documentOrderIndexesDirty
= false;
152 struct PriorityCompare
{
153 PriorityCompare(SMILTime elapsed
) : m_elapsed(elapsed
) {}
154 bool operator()(SVGSMILElement
* a
, SVGSMILElement
* b
)
156 // FIXME: This should also consider possible timing relations between the elements.
157 SMILTime aBegin
= a
->intervalBegin();
158 SMILTime bBegin
= b
->intervalBegin();
159 // Frozen elements need to be prioritized based on their previous interval.
160 aBegin
= a
->isFrozen() && m_elapsed
< aBegin
? a
->previousIntervalBegin() : aBegin
;
161 bBegin
= b
->isFrozen() && m_elapsed
< bBegin
? b
->previousIntervalBegin() : bBegin
;
162 if (aBegin
== bBegin
)
163 return a
->documentOrderIndex() < b
->documentOrderIndex();
164 return aBegin
< bBegin
;
169 void SMILTimeContainer::sortByPriority(Vector
<SVGSMILElement
*>& smilElements
, SMILTime elapsed
)
171 if (m_documentOrderIndexesDirty
)
172 updateDocumentOrderIndexes();
173 std::sort(smilElements
.begin(), smilElements
.end(), PriorityCompare(elapsed
));
176 static bool applyOrderSortFunction(SVGSMILElement
* a
, SVGSMILElement
* b
)
178 if (!a
->hasTagName(SVGNames::animateTransformTag
) && b
->hasTagName(SVGNames::animateTransformTag
))
183 static void sortByApplyOrder(Vector
<SVGSMILElement
*>& smilElements
)
185 std::sort(smilElements
.begin(), smilElements
.end(), applyOrderSortFunction
);
188 String
SMILTimeContainer::baseValueFor(ElementAttributePair key
)
190 // FIXME: We wouldn't need to do this if we were keeping base values around properly in DOM.
191 // Currently animation overwrites them so we need to save them somewhere.
192 BaseValueMap::iterator it
= m_savedBaseValues
.find(key
);
193 if (it
!= m_savedBaseValues
.end())
196 SVGElement
* target
= key
.first
;
197 String attributeName
= key
.second
;
199 ASSERT(!attributeName
.isEmpty());
201 if (SVGAnimationElement::attributeIsCSS(attributeName
)) {
202 CSSComputedStyleDeclaration
computedStyle(target
);
203 baseValue
= computedStyle
.getPropertyValue(cssPropertyID(attributeName
));
205 baseValue
= target
->getAttribute(attributeName
);
206 m_savedBaseValues
.add(key
, baseValue
);
210 void SMILTimeContainer::updateAnimations(SMILTime elapsed
)
212 SMILTime earliersFireTime
= SMILTime::unresolved();
214 Vector
<SVGSMILElement
*> toAnimate
;
215 copyToVector(m_scheduledAnimations
, toAnimate
);
217 // Sort according to priority. Elements with later begin time have higher priority.
218 // In case of a tie, document order decides.
219 // FIXME: This should also consider timing relationships between the elements. Dependents
220 // have higher priority.
221 sortByPriority(toAnimate
, elapsed
);
223 // Calculate animation contributions.
224 typedef HashMap
<ElementAttributePair
, SVGSMILElement
*> ResultElementMap
;
225 ResultElementMap resultsElements
;
226 for (unsigned n
= 0; n
< toAnimate
.size(); ++n
) {
227 SVGSMILElement
* animation
= toAnimate
[n
];
228 ASSERT(animation
->timeContainer() == this);
230 SVGElement
* targetElement
= animation
->targetElement();
233 String attributeName
= animation
->attributeName();
234 if (attributeName
.isEmpty()) {
235 if (animation
->hasTagName(SVGNames::animateMotionTag
))
236 attributeName
= SVGNames::animateMotionTag
.localName();
241 // Results are accumulated to the first animation that animates a particular element/attribute pair.
242 ElementAttributePair
key(targetElement
, attributeName
);
243 SVGSMILElement
* resultElement
= resultsElements
.get(key
);
244 if (!resultElement
) {
245 resultElement
= animation
;
246 resultElement
->resetToBaseValue(baseValueFor(key
));
247 resultsElements
.add(key
, resultElement
);
250 // This will calculate the contribution from the animation and add it to the resultsElement.
251 animation
->progress(elapsed
, resultElement
);
253 SMILTime nextFireTime
= animation
->nextProgressTime();
254 if (nextFireTime
.isFinite())
255 earliersFireTime
= min(nextFireTime
, earliersFireTime
);
256 else if (!animation
->isContributing(elapsed
)) {
257 m_scheduledAnimations
.remove(animation
);
258 if (m_scheduledAnimations
.isEmpty())
259 m_savedBaseValues
.clear();
263 Vector
<SVGSMILElement
*> animationsToApply
;
264 ResultElementMap::iterator end
= resultsElements
.end();
265 for (ResultElementMap::iterator it
= resultsElements
.begin(); it
!= end
; ++it
)
266 animationsToApply
.append(it
->second
);
268 // Sort <animateTranform> to be the last one to be applied. <animate> may change transform attribute as
269 // well (directly or indirectly by modifying <use> x/y) and this way transforms combine properly.
270 sortByApplyOrder(animationsToApply
);
272 // Apply results to target elements.
273 for (unsigned n
= 0; n
< animationsToApply
.size(); ++n
)
274 animationsToApply
[n
]->applyResultsToTarget();
276 startTimer(earliersFireTime
, animationFrameDelay
);
278 Document::updateDocumentsRendering();