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