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 "core/svg/animation/SMILTimeContainer.h" 28 29 #include "core/dom/ElementTraversal.h" 30 #include "core/svg/SVGSVGElement.h" 31 #include "core/svg/animation/SVGSMILElement.h" 32 #include "wtf/CurrentTime.h" 33 34 using namespace std; 35 36 namespace WebCore { 37 38 static const double animationFrameDelay = 0.025; 39 40 SMILTimeContainer::SMILTimeContainer(SVGSVGElement* owner) 41 : m_beginTime(0) 42 , m_pauseTime(0) 43 , m_resumeTime(0) 44 , m_accumulatedActiveTime(0) 45 , m_presetStartTime(0) 46 , m_documentOrderIndexesDirty(false) 47 , m_timer(this, &SMILTimeContainer::timerFired) 48 , m_ownerSVGElement(owner) 49 #ifndef NDEBUG 50 , m_preventScheduledAnimationsChanges(false) 51 #endif 52 { 53 } 54 55 SMILTimeContainer::~SMILTimeContainer() 56 { 57 #ifndef NDEBUG 58 ASSERT(!m_preventScheduledAnimationsChanges); 59 #endif 60 } 61 62 void SMILTimeContainer::schedule(SVGSMILElement* animation, SVGElement* target, const QualifiedName& attributeName) 63 { 64 ASSERT(animation->timeContainer() == this); 65 ASSERT(target); 66 ASSERT(animation->hasValidAttributeName()); 67 68 #ifndef NDEBUG 69 ASSERT(!m_preventScheduledAnimationsChanges); 70 #endif 71 72 ElementAttributePair key(target, attributeName); 73 OwnPtr<AnimationsVector>& scheduled = m_scheduledAnimations.add(key, nullptr).iterator->value; 74 if (!scheduled) 75 scheduled = adoptPtr(new AnimationsVector); 76 ASSERT(!scheduled->contains(animation)); 77 scheduled->append(animation); 78 79 SMILTime nextFireTime = animation->nextProgressTime(); 80 if (nextFireTime.isFinite()) 81 notifyIntervalsChanged(); 82 } 83 84 void SMILTimeContainer::unschedule(SVGSMILElement* animation, SVGElement* target, const QualifiedName& attributeName) 85 { 86 ASSERT(animation->timeContainer() == this); 87 88 #ifndef NDEBUG 89 ASSERT(!m_preventScheduledAnimationsChanges); 90 #endif 91 92 ElementAttributePair key(target, attributeName); 93 AnimationsVector* scheduled = m_scheduledAnimations.get(key); 94 ASSERT(scheduled); 95 size_t idx = scheduled->find(animation); 96 ASSERT(idx != kNotFound); 97 scheduled->remove(idx); 98 } 99 100 void SMILTimeContainer::notifyIntervalsChanged() 101 { 102 // Schedule updateAnimations() to be called asynchronously so multiple intervals 103 // can change with updateAnimations() only called once at the end. 104 startTimer(0); 105 } 106 107 SMILTime SMILTimeContainer::elapsed() const 108 { 109 if (!m_beginTime) 110 return 0; 111 112 if (isPaused()) 113 return m_accumulatedActiveTime; 114 115 return currentTime() + m_accumulatedActiveTime - lastResumeTime(); 116 } 117 118 bool SMILTimeContainer::isActive() const 119 { 120 return m_beginTime && !isPaused(); 121 } 122 123 bool SMILTimeContainer::isPaused() const 124 { 125 return m_pauseTime; 126 } 127 128 bool SMILTimeContainer::isStarted() const 129 { 130 return m_beginTime; 131 } 132 133 void SMILTimeContainer::begin() 134 { 135 ASSERT(!m_beginTime); 136 double now = currentTime(); 137 138 // If 'm_presetStartTime' is set, the timeline was modified via setElapsed() before the document began. 139 // In this case pass on 'seekToTime=true' to updateAnimations(). 140 m_beginTime = now - m_presetStartTime; 141 updateAnimations(SMILTime(m_presetStartTime), m_presetStartTime ? true : false); 142 m_presetStartTime = 0; 143 144 if (m_pauseTime) { 145 m_pauseTime = now; 146 m_timer.stop(); 147 } 148 } 149 150 void SMILTimeContainer::pause() 151 { 152 ASSERT(!isPaused()); 153 m_pauseTime = currentTime(); 154 155 if (m_beginTime) { 156 m_accumulatedActiveTime += m_pauseTime - lastResumeTime(); 157 m_timer.stop(); 158 } 159 m_resumeTime = 0; 160 } 161 162 void SMILTimeContainer::resume() 163 { 164 ASSERT(isPaused()); 165 m_resumeTime = currentTime(); 166 167 m_pauseTime = 0; 168 startTimer(0); 169 } 170 171 void SMILTimeContainer::setElapsed(SMILTime time) 172 { 173 // If the documment didn't begin yet, record a new start time, we'll seek to once its possible. 174 if (!m_beginTime) { 175 m_presetStartTime = time.value(); 176 return; 177 } 178 179 if (m_beginTime) 180 m_timer.stop(); 181 182 double now = currentTime(); 183 m_beginTime = now - time.value(); 184 m_resumeTime = 0; 185 if (m_pauseTime) { 186 m_pauseTime = now; 187 m_accumulatedActiveTime = time.value(); 188 } else { 189 m_accumulatedActiveTime = 0; 190 } 191 192 #ifndef NDEBUG 193 m_preventScheduledAnimationsChanges = true; 194 #endif 195 GroupedAnimationsMap::iterator end = m_scheduledAnimations.end(); 196 for (GroupedAnimationsMap::iterator it = m_scheduledAnimations.begin(); it != end; ++it) { 197 AnimationsVector* scheduled = it->value.get(); 198 unsigned size = scheduled->size(); 199 for (unsigned n = 0; n < size; n++) 200 scheduled->at(n)->reset(); 201 } 202 #ifndef NDEBUG 203 m_preventScheduledAnimationsChanges = false; 204 #endif 205 206 updateAnimations(time, true); 207 } 208 209 void SMILTimeContainer::startTimer(SMILTime fireTime, SMILTime minimumDelay) 210 { 211 if (!m_beginTime || isPaused()) 212 return; 213 214 if (!fireTime.isFinite()) 215 return; 216 217 SMILTime delay = max(fireTime - elapsed(), minimumDelay); 218 m_timer.startOneShot(delay.value()); 219 } 220 221 void SMILTimeContainer::timerFired(Timer<SMILTimeContainer>*) 222 { 223 ASSERT(m_beginTime); 224 ASSERT(!m_pauseTime); 225 updateAnimations(elapsed()); 226 } 227 228 void SMILTimeContainer::updateDocumentOrderIndexes() 229 { 230 unsigned timingElementCount = 0; 231 for (Element* element = m_ownerSVGElement; element; element = ElementTraversal::next(*element, m_ownerSVGElement)) { 232 if (isSVGSMILElement(*element)) 233 toSVGSMILElement(element)->setDocumentOrderIndex(timingElementCount++); 234 } 235 m_documentOrderIndexesDirty = false; 236 } 237 238 struct PriorityCompare { 239 PriorityCompare(SMILTime elapsed) : m_elapsed(elapsed) {} 240 bool operator()(SVGSMILElement* a, SVGSMILElement* b) 241 { 242 // FIXME: This should also consider possible timing relations between the elements. 243 SMILTime aBegin = a->intervalBegin(); 244 SMILTime bBegin = b->intervalBegin(); 245 // Frozen elements need to be prioritized based on their previous interval. 246 aBegin = a->isFrozen() && m_elapsed < aBegin ? a->previousIntervalBegin() : aBegin; 247 bBegin = b->isFrozen() && m_elapsed < bBegin ? b->previousIntervalBegin() : bBegin; 248 if (aBegin == bBegin) 249 return a->documentOrderIndex() < b->documentOrderIndex(); 250 return aBegin < bBegin; 251 } 252 SMILTime m_elapsed; 253 }; 254 255 void SMILTimeContainer::sortByPriority(Vector<SVGSMILElement*>& smilElements, SMILTime elapsed) 256 { 257 if (m_documentOrderIndexesDirty) 258 updateDocumentOrderIndexes(); 259 std::sort(smilElements.begin(), smilElements.end(), PriorityCompare(elapsed)); 260 } 261 262 void SMILTimeContainer::updateAnimations(SMILTime elapsed, bool seekToTime) 263 { 264 SMILTime earliestFireTime = SMILTime::unresolved(); 265 266 #ifndef NDEBUG 267 // This boolean will catch any attempts to schedule/unschedule scheduledAnimations during this critical section. 268 // Similarly, any elements removed will unschedule themselves, so this will catch modification of animationsToApply. 269 m_preventScheduledAnimationsChanges = true; 270 #endif 271 272 AnimationsVector animationsToApply; 273 GroupedAnimationsMap::iterator end = m_scheduledAnimations.end(); 274 for (GroupedAnimationsMap::iterator it = m_scheduledAnimations.begin(); it != end; ++it) { 275 AnimationsVector* scheduled = it->value.get(); 276 277 // Sort according to priority. Elements with later begin time have higher priority. 278 // In case of a tie, document order decides. 279 // FIXME: This should also consider timing relationships between the elements. Dependents 280 // have higher priority. 281 sortByPriority(*scheduled, elapsed); 282 283 SVGSMILElement* resultElement = 0; 284 unsigned size = scheduled->size(); 285 for (unsigned n = 0; n < size; n++) { 286 SVGSMILElement* animation = scheduled->at(n); 287 ASSERT(animation->timeContainer() == this); 288 ASSERT(animation->targetElement()); 289 ASSERT(animation->hasValidAttributeName()); 290 291 // Results are accumulated to the first animation that animates and contributes to a particular element/attribute pair. 292 // FIXME: we should ensure that resultElement is of an appropriate type. 293 if (!resultElement) { 294 if (!animation->hasValidAttributeType()) 295 continue; 296 resultElement = animation; 297 } 298 299 // This will calculate the contribution from the animation and add it to the resultsElement. 300 if (!animation->progress(elapsed, resultElement, seekToTime) && resultElement == animation) 301 resultElement = 0; 302 303 SMILTime nextFireTime = animation->nextProgressTime(); 304 if (nextFireTime.isFinite()) 305 earliestFireTime = min(nextFireTime, earliestFireTime); 306 } 307 308 if (resultElement) 309 animationsToApply.append(resultElement); 310 } 311 312 unsigned animationsToApplySize = animationsToApply.size(); 313 if (!animationsToApplySize) { 314 #ifndef NDEBUG 315 m_preventScheduledAnimationsChanges = false; 316 #endif 317 startTimer(earliestFireTime, animationFrameDelay); 318 return; 319 } 320 321 // Apply results to target elements. 322 for (unsigned i = 0; i < animationsToApplySize; ++i) 323 animationsToApply[i]->applyResultsToTarget(); 324 325 #ifndef NDEBUG 326 m_preventScheduledAnimationsChanges = false; 327 #endif 328 329 startTimer(earliestFireTime, animationFrameDelay); 330 } 331 332 } 333