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_nextManualSampleTime(0) 50 , m_documentOrderIndexesDirty(false) 51 , m_timer(this, &SMILTimeContainer::timerFired) 52 , m_ownerSVGElement(owner) 53 { 54 } 55 56 #if !ENABLE(SVG_ANIMATION) 57 void SMILTimeContainer::begin() {} 58 void SMILTimeContainer::pause() {} 59 void SMILTimeContainer::resume() {} 60 SMILTime SMILTimeContainer::elapsed() const { return 0; } 61 bool SMILTimeContainer::isPaused() const { return false; } 62 void SMILTimeContainer::timerFired(Timer<SMILTimeContainer>*) {} 63 #else 64 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 75 void SMILTimeContainer::unschedule(SVGSMILElement* animation) 76 { 77 ASSERT(animation->timeContainer() == this); 78 79 m_scheduledAnimations.remove(animation); 80 } 81 82 SMILTime SMILTimeContainer::elapsed() const 83 { 84 if (!m_beginTime) 85 return 0; 86 return currentTime() - m_beginTime - m_accumulatedPauseTime; 87 } 88 89 bool SMILTimeContainer::isActive() const 90 { 91 return m_beginTime && !isPaused(); 92 } 93 94 bool SMILTimeContainer::isPaused() const 95 { 96 return m_pauseTime; 97 } 98 99 void SMILTimeContainer::begin() 100 { 101 ASSERT(!m_beginTime); 102 m_beginTime = currentTime(); 103 updateAnimations(0); 104 } 105 106 void SMILTimeContainer::pause() 107 { 108 if (!m_beginTime) 109 return; 110 ASSERT(!isPaused()); 111 m_pauseTime = currentTime(); 112 m_timer.stop(); 113 } 114 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 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 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 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 { 156 PriorityCompare(SMILTime elapsed) : m_elapsed(elapsed) {} 157 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 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 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 186 static void sortByApplyOrder(Vector<SVGSMILElement*>& smilElements) 187 { 188 std::sort(smilElements.begin(), smilElements.end(), applyOrderSortFunction); 189 } 190 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 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 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