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#include "Document.h" 30#include "ElementIterator.h" 31#include "SVGNames.h" 32#include "SVGSMILElement.h" 33#include "SVGSVGElement.h" 34#include <wtf/CurrentTime.h> 35 36namespace WebCore { 37 38static const double animationFrameDelay = 0.025; 39 40SMILTimeContainer::SMILTimeContainer(SVGSVGElement* owner) 41 : m_beginTime(0) 42 , m_pauseTime(0) 43 , m_accumulatedActiveTime(0) 44 , m_resumeTime(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 55SMILTimeContainer::~SMILTimeContainer() 56{ 57#ifndef NDEBUG 58 ASSERT(!m_preventScheduledAnimationsChanges); 59#endif 60} 61 62void 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 std::unique_ptr<AnimationsVector>& scheduled = m_scheduledAnimations.add(key, nullptr).iterator->value; 74 if (!scheduled) 75 scheduled = std::make_unique<AnimationsVector>(); 76 ASSERT(!scheduled->contains(animation)); 77 scheduled->append(animation); 78 79 SMILTime nextFireTime = animation->nextProgressTime(); 80 if (nextFireTime.isFinite()) 81 notifyIntervalsChanged(); 82} 83 84void 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 != notFound); 97 scheduled->remove(idx); 98} 99 100void 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 107SMILTime SMILTimeContainer::elapsed() const 108{ 109 if (!m_beginTime) 110 return 0; 111 if (isPaused()) 112 return m_accumulatedActiveTime; 113 return monotonicallyIncreasingTime() + m_accumulatedActiveTime - m_resumeTime; 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 = monotonicallyIncreasingTime(); 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 = m_resumeTime = 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 152 m_pauseTime = monotonicallyIncreasingTime(); 153 if (m_beginTime) { 154 m_accumulatedActiveTime += m_pauseTime - m_resumeTime; 155 m_timer.stop(); 156 } 157} 158 159void SMILTimeContainer::resume() 160{ 161 ASSERT(isPaused()); 162 163 m_resumeTime = monotonicallyIncreasingTime(); 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 = monotonicallyIncreasingTime(); 180 m_beginTime = now - time.value(); 181 182 if (m_pauseTime) { 183 m_resumeTime = m_pauseTime = now; 184 m_accumulatedActiveTime = time.value(); 185 } else 186 m_resumeTime = m_beginTime; 187 188#ifndef NDEBUG 189 m_preventScheduledAnimationsChanges = true; 190#endif 191 GroupedAnimationsMap::iterator end = m_scheduledAnimations.end(); 192 for (GroupedAnimationsMap::iterator it = m_scheduledAnimations.begin(); it != end; ++it) { 193 AnimationsVector* scheduled = it->value.get(); 194 unsigned size = scheduled->size(); 195 for (unsigned n = 0; n < size; n++) 196 scheduled->at(n)->reset(); 197 } 198#ifndef NDEBUG 199 m_preventScheduledAnimationsChanges = false; 200#endif 201 202 updateAnimations(time, true); 203} 204 205void SMILTimeContainer::startTimer(SMILTime fireTime, SMILTime minimumDelay) 206{ 207 if (!m_beginTime || isPaused()) 208 return; 209 210 if (!fireTime.isFinite()) 211 return; 212 213 SMILTime delay = std::max(fireTime - elapsed(), minimumDelay); 214 m_timer.startOneShot(delay.value()); 215} 216 217void SMILTimeContainer::timerFired(Timer<SMILTimeContainer>*) 218{ 219 ASSERT(m_beginTime); 220 ASSERT(!m_pauseTime); 221 updateAnimations(elapsed()); 222} 223 224void SMILTimeContainer::updateDocumentOrderIndexes() 225{ 226 unsigned timingElementCount = 0; 227 228 for (auto& smilElement : descendantsOfType<SVGSMILElement>(*m_ownerSVGElement)) 229 smilElement.setDocumentOrderIndex(timingElementCount++); 230 231 m_documentOrderIndexesDirty = false; 232} 233 234struct PriorityCompare { 235 PriorityCompare(SMILTime elapsed) : m_elapsed(elapsed) {} 236 bool operator()(SVGSMILElement* a, SVGSMILElement* b) 237 { 238 // FIXME: This should also consider possible timing relations between the elements. 239 SMILTime aBegin = a->intervalBegin(); 240 SMILTime bBegin = b->intervalBegin(); 241 // Frozen elements need to be prioritized based on their previous interval. 242 aBegin = a->isFrozen() && m_elapsed < aBegin ? a->previousIntervalBegin() : aBegin; 243 bBegin = b->isFrozen() && m_elapsed < bBegin ? b->previousIntervalBegin() : bBegin; 244 if (aBegin == bBegin) 245 return a->documentOrderIndex() < b->documentOrderIndex(); 246 return aBegin < bBegin; 247 } 248 SMILTime m_elapsed; 249}; 250 251void SMILTimeContainer::sortByPriority(Vector<SVGSMILElement*>& smilElements, SMILTime elapsed) 252{ 253 if (m_documentOrderIndexesDirty) 254 updateDocumentOrderIndexes(); 255 std::sort(smilElements.begin(), smilElements.end(), PriorityCompare(elapsed)); 256} 257 258void SMILTimeContainer::updateAnimations(SMILTime elapsed, bool seekToTime) 259{ 260 SMILTime earliestFireTime = SMILTime::unresolved(); 261 262#ifndef NDEBUG 263 // This boolean will catch any attempts to schedule/unschedule scheduledAnimations during this critical section. 264 // Similarly, any elements removed will unschedule themselves, so this will catch modification of animationsToApply. 265 m_preventScheduledAnimationsChanges = true; 266#endif 267 268 AnimationsVector animationsToApply; 269 GroupedAnimationsMap::iterator end = m_scheduledAnimations.end(); 270 for (GroupedAnimationsMap::iterator it = m_scheduledAnimations.begin(); it != end; ++it) { 271 AnimationsVector* scheduled = it->value.get(); 272 273 // Sort according to priority. Elements with later begin time have higher priority. 274 // In case of a tie, document order decides. 275 // FIXME: This should also consider timing relationships between the elements. Dependents 276 // have higher priority. 277 sortByPriority(*scheduled, elapsed); 278 279 SVGSMILElement* resultElement = 0; 280 unsigned size = scheduled->size(); 281 for (unsigned n = 0; n < size; n++) { 282 SVGSMILElement* animation = scheduled->at(n); 283 ASSERT(animation->timeContainer() == this); 284 ASSERT(animation->targetElement()); 285 ASSERT(animation->hasValidAttributeName()); 286 287 // Results are accumulated to the first animation that animates and contributes to a particular element/attribute pair. 288 if (!resultElement) { 289 if (!animation->hasValidAttributeType()) 290 continue; 291 resultElement = animation; 292 } 293 294 // This will calculate the contribution from the animation and add it to the resultsElement. 295 if (!animation->progress(elapsed, resultElement, seekToTime) && resultElement == animation) 296 resultElement = 0; 297 298 SMILTime nextFireTime = animation->nextProgressTime(); 299 if (nextFireTime.isFinite()) 300 earliestFireTime = std::min(nextFireTime, earliestFireTime); 301 } 302 303 if (resultElement) 304 animationsToApply.append(resultElement); 305 } 306 307 unsigned animationsToApplySize = animationsToApply.size(); 308 if (!animationsToApplySize) { 309#ifndef NDEBUG 310 m_preventScheduledAnimationsChanges = false; 311#endif 312 startTimer(earliestFireTime, animationFrameDelay); 313 return; 314 } 315 316 // Apply results to target elements. 317 for (unsigned i = 0; i < animationsToApplySize; ++i) 318 animationsToApply[i]->applyResultsToTarget(); 319 320#ifndef NDEBUG 321 m_preventScheduledAnimationsChanges = false; 322#endif 323 324 startTimer(earliestFireTime, animationFrameDelay); 325} 326 327} 328