initial import
[vuplus_webkit] / Source / WebCore / svg / animation / SMILTimeContainer.cpp
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
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)
56 void SMILTimeContainer::begin() {}
57 void SMILTimeContainer::pause() {}
58 void SMILTimeContainer::resume() {}
59 SMILTime SMILTimeContainer::elapsed() const { return 0; }
60 bool SMILTimeContainer::isPaused() const { return false; }
61 void SMILTimeContainer::timerFired(Timer<SMILTimeContainer>*) {}
62 #else
63     
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     
74 void SMILTimeContainer::unschedule(SVGSMILElement* animation)
75 {
76     ASSERT(animation->timeContainer() == this);
77
78     m_scheduledAnimations.remove(animation);
79 }
80
81 SMILTime SMILTimeContainer::elapsed() const
82 {
83     if (!m_beginTime)
84         return 0;
85     return currentTime() - m_beginTime - m_accumulatedPauseTime;
86 }
87     
88 bool SMILTimeContainer::isActive() const
89 {
90     return m_beginTime && !isPaused();
91 }
92     
93 bool SMILTimeContainer::isPaused() const
94 {
95     return m_pauseTime;
96 }
97
98 void SMILTimeContainer::begin()
99 {
100     ASSERT(!m_beginTime);
101     m_beginTime = currentTime();
102     updateAnimations(0);
103 }
104
105 void SMILTimeContainer::pause()
106 {
107     if (!m_beginTime)
108         return;
109     ASSERT(!isPaused());
110     m_pauseTime = currentTime();
111     m_timer.stop();
112 }
113
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
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     
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  
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 {
155     PriorityCompare(SMILTime elapsed) : m_elapsed(elapsed) {}
156     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
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     
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     
185 static void sortByApplyOrder(Vector<SVGSMILElement*>& smilElements)
186 {
187     std::sort(smilElements.begin(), smilElements.end(), applyOrderSortFunction);
188 }
189
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* targetElement = key.first;
199     QualifiedName attributeName = key.second;
200     ASSERT(targetElement);
201     ASSERT(attributeName != anyQName());
202     String baseValue;
203     if (SVGAnimationElement::isTargetAttributeCSSProperty(targetElement, attributeName))
204         baseValue = computedStyle(targetElement)->getPropertyValue(cssPropertyID(attributeName.localName()));
205     else
206         baseValue = targetElement->getAttribute(attributeName);
207     m_savedBaseValues.add(key, baseValue);
208     return baseValue;
209 }
210
211 void SMILTimeContainer::sampleAnimationAtTime(const String& elementId, double newTime)
212 {
213     ASSERT(m_beginTime);
214     ASSERT(!isPaused());
215
216     // Fast-forward to the time DRT wants to sample
217     m_timer.stop();
218
219     updateAnimations(elapsed(), newTime, elementId);
220 }
221
222 void SMILTimeContainer::updateAnimations(SMILTime elapsed, double nextManualSampleTime, const String& nextSamplingTarget)
223 {
224     SMILTime earliersFireTime = SMILTime::unresolved();
225
226     Vector<SVGSMILElement*> toAnimate;
227     copyToVector(m_scheduledAnimations, toAnimate);
228
229     if (nextManualSampleTime) {
230         SMILTime samplingDiff;
231         for (unsigned n = 0; n < toAnimate.size(); ++n) {
232             SVGSMILElement* animation = toAnimate[n];
233             ASSERT(animation->timeContainer() == this);
234
235             SVGElement* targetElement = animation->targetElement();
236             // FIXME: This should probably be using getIdAttribute instead of idForStyleResolution.
237             if (!targetElement || !targetElement->hasID() || targetElement->idForStyleResolution() != nextSamplingTarget)
238                 continue;
239
240             samplingDiff = animation->intervalBegin();
241             break;
242         }
243
244         elapsed = SMILTime(nextManualSampleTime) + samplingDiff;
245     }
246
247     // Sort according to priority. Elements with later begin time have higher priority.
248     // In case of a tie, document order decides. 
249     // FIXME: This should also consider timing relationships between the elements. Dependents
250     // have higher priority.
251     sortByPriority(toAnimate, elapsed);
252     
253     // Calculate animation contributions.
254     typedef HashMap<ElementAttributePair, RefPtr<SVGSMILElement> > ResultElementMap;
255     ResultElementMap resultsElements;
256     for (unsigned n = 0; n < toAnimate.size(); ++n) {
257         SVGSMILElement* animation = toAnimate[n];
258         ASSERT(animation->timeContainer() == this);
259
260         SVGElement* targetElement = animation->targetElement();
261         if (!targetElement)
262             continue;
263         
264         QualifiedName attributeName = animation->attributeName();
265         if (attributeName == anyQName()) {
266             if (animation->hasTagName(SVGNames::animateMotionTag))
267                 attributeName = SVGNames::animateMotionTag;
268             else
269                 continue;
270         }
271         
272         // Results are accumulated to the first animation that animates a particular element/attribute pair.
273         ElementAttributePair key(targetElement, attributeName); 
274         SVGSMILElement* resultElement = resultsElements.get(key).get();
275         if (!resultElement) {
276             if (!animation->hasValidAttributeType())
277                 continue;
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.get());
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)