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/animation/AnimationClock.h" 30 #include "core/animation/AnimationTimeline.h" 31 #include "core/dom/ElementTraversal.h" 32 #include "core/frame/FrameView.h" 33 #include "core/svg/SVGSVGElement.h" 34 #include "core/svg/animation/SVGSMILElement.h" 35 36 namespace blink { 37 38 static const double initialFrameDelay = 0.025; 39 40 #if !ENABLE(OILPAN) 41 // Every entry-point that calls updateAnimations() should instantiate a 42 // DiscardScope to prevent deletion of the ownerElement (and hence itself.) 43 class DiscardScope { 44 public: 45 explicit DiscardScope(SVGSVGElement& timeContainerOwner) : m_discardScopeElement(&timeContainerOwner) { } 46 47 private: 48 RefPtr<SVGSVGElement> m_discardScopeElement; 49 }; 50 #endif 51 52 SMILTimeContainer::SMILTimeContainer(SVGSVGElement& owner) 53 : m_beginTime(0) 54 , m_pauseTime(0) 55 , m_resumeTime(0) 56 , m_accumulatedActiveTime(0) 57 , m_presetStartTime(0) 58 , m_frameSchedulingState(Idle) 59 , m_documentOrderIndexesDirty(false) 60 , m_wakeupTimer(this, &SMILTimeContainer::wakeupTimerFired) 61 , m_ownerSVGElement(owner) 62 #if ENABLE(ASSERT) 63 , m_preventScheduledAnimationsChanges(false) 64 #endif 65 { 66 } 67 68 SMILTimeContainer::~SMILTimeContainer() 69 { 70 cancelAnimationFrame(); 71 ASSERT(!m_wakeupTimer.isActive()); 72 #if ENABLE(ASSERT) 73 ASSERT(!m_preventScheduledAnimationsChanges); 74 #endif 75 } 76 77 void SMILTimeContainer::schedule(SVGSMILElement* animation, SVGElement* target, const QualifiedName& attributeName) 78 { 79 ASSERT(animation->timeContainer() == this); 80 ASSERT(target); 81 ASSERT(animation->hasValidAttributeName()); 82 83 #if ENABLE(ASSERT) 84 ASSERT(!m_preventScheduledAnimationsChanges); 85 #endif 86 87 ElementAttributePair key(target, attributeName); 88 OwnPtrWillBeMember<AnimationsLinkedHashSet>& scheduled = m_scheduledAnimations.add(key, nullptr).storedValue->value; 89 if (!scheduled) 90 scheduled = adoptPtrWillBeNoop(new AnimationsLinkedHashSet); 91 ASSERT(!scheduled->contains(animation)); 92 scheduled->add(animation); 93 94 SMILTime nextFireTime = animation->nextProgressTime(); 95 if (nextFireTime.isFinite()) 96 notifyIntervalsChanged(); 97 } 98 99 void SMILTimeContainer::unschedule(SVGSMILElement* animation, SVGElement* target, const QualifiedName& attributeName) 100 { 101 ASSERT(animation->timeContainer() == this); 102 103 #if ENABLE(ASSERT) 104 ASSERT(!m_preventScheduledAnimationsChanges); 105 #endif 106 107 ElementAttributePair key(target, attributeName); 108 GroupedAnimationsMap::iterator it = m_scheduledAnimations.find(key); 109 ASSERT(it != m_scheduledAnimations.end()); 110 AnimationsLinkedHashSet* scheduled = it->value.get(); 111 ASSERT(scheduled); 112 AnimationsLinkedHashSet::iterator itAnimation = scheduled->find(animation); 113 ASSERT(itAnimation != scheduled->end()); 114 scheduled->remove(itAnimation); 115 116 if (scheduled->isEmpty()) 117 m_scheduledAnimations.remove(it); 118 } 119 120 bool SMILTimeContainer::hasAnimations() const 121 { 122 return !m_scheduledAnimations.isEmpty(); 123 } 124 125 bool SMILTimeContainer::hasPendingSynchronization() const 126 { 127 return m_frameSchedulingState == SynchronizeAnimations && m_wakeupTimer.isActive() && !m_wakeupTimer.nextFireInterval(); 128 } 129 130 void SMILTimeContainer::notifyIntervalsChanged() 131 { 132 if (!isStarted()) 133 return; 134 // Schedule updateAnimations() to be called asynchronously so multiple intervals 135 // can change with updateAnimations() only called once at the end. 136 if (hasPendingSynchronization()) 137 return; 138 cancelAnimationFrame(); 139 scheduleWakeUp(0, SynchronizeAnimations); 140 } 141 142 SMILTime SMILTimeContainer::elapsed() const 143 { 144 if (!m_beginTime) 145 return 0; 146 147 if (isPaused()) 148 return m_accumulatedActiveTime; 149 150 return currentTime() + m_accumulatedActiveTime - lastResumeTime(); 151 } 152 153 bool SMILTimeContainer::isPaused() const 154 { 155 return m_pauseTime; 156 } 157 158 bool SMILTimeContainer::isStarted() const 159 { 160 return m_beginTime; 161 } 162 163 void SMILTimeContainer::begin() 164 { 165 RELEASE_ASSERT(!m_beginTime); 166 double now = currentTime(); 167 168 // If 'm_presetStartTime' is set, the timeline was modified via setElapsed() before the document began. 169 // In this case pass on 'seekToTime=true' to updateAnimations(). 170 m_beginTime = now - m_presetStartTime; 171 #if !ENABLE(OILPAN) 172 DiscardScope discardScope(m_ownerSVGElement); 173 #endif 174 SMILTime earliestFireTime = updateAnimations(SMILTime(m_presetStartTime), m_presetStartTime ? true : false); 175 m_presetStartTime = 0; 176 177 if (m_pauseTime) { 178 m_pauseTime = now; 179 // If updateAnimations() caused new syncbase instance to be generated, 180 // we don't want to cancel those. Excepting that, no frame should've 181 // been scheduled at this point. 182 ASSERT(m_frameSchedulingState == Idle || m_frameSchedulingState == SynchronizeAnimations); 183 } else if (!hasPendingSynchronization()) { 184 ASSERT(isTimelineRunning()); 185 // If the timeline is running, and there's pending animation updates, 186 // always perform the first update after the timeline was started using 187 // the wake-up mechanism. 188 if (earliestFireTime.isFinite()) { 189 SMILTime delay = earliestFireTime - elapsed(); 190 scheduleWakeUp(std::max(initialFrameDelay, delay.value()), SynchronizeAnimations); 191 } 192 } 193 } 194 195 void SMILTimeContainer::pause() 196 { 197 ASSERT(!isPaused()); 198 m_pauseTime = currentTime(); 199 200 if (m_beginTime) { 201 m_accumulatedActiveTime += m_pauseTime - lastResumeTime(); 202 cancelAnimationFrame(); 203 } 204 m_resumeTime = 0; 205 } 206 207 void SMILTimeContainer::resume() 208 { 209 ASSERT(isPaused()); 210 m_resumeTime = currentTime(); 211 212 m_pauseTime = 0; 213 scheduleWakeUp(0, SynchronizeAnimations); 214 } 215 216 void SMILTimeContainer::setElapsed(SMILTime time) 217 { 218 // If the documment didn't begin yet, record a new start time, we'll seek to once its possible. 219 if (!m_beginTime) { 220 m_presetStartTime = time.value(); 221 return; 222 } 223 224 cancelAnimationFrame(); 225 226 double now = currentTime(); 227 m_beginTime = now - time.value(); 228 m_resumeTime = 0; 229 if (m_pauseTime) { 230 m_pauseTime = now; 231 m_accumulatedActiveTime = time.value(); 232 } else { 233 m_accumulatedActiveTime = 0; 234 } 235 236 #if ENABLE(ASSERT) 237 m_preventScheduledAnimationsChanges = true; 238 #endif 239 GroupedAnimationsMap::iterator end = m_scheduledAnimations.end(); 240 for (GroupedAnimationsMap::iterator it = m_scheduledAnimations.begin(); it != end; ++it) { 241 if (!it->key.first) 242 continue; 243 244 AnimationsLinkedHashSet* scheduled = it->value.get(); 245 for (AnimationsLinkedHashSet::const_iterator itAnimation = scheduled->begin(), itAnimationEnd = scheduled->end(); itAnimation != itAnimationEnd; ++itAnimation) 246 (*itAnimation)->reset(); 247 } 248 #if ENABLE(ASSERT) 249 m_preventScheduledAnimationsChanges = false; 250 #endif 251 252 updateAnimationsAndScheduleFrameIfNeeded(time, true); 253 } 254 255 bool SMILTimeContainer::isTimelineRunning() const 256 { 257 return m_beginTime && !isPaused(); 258 } 259 260 void SMILTimeContainer::scheduleAnimationFrame(SMILTime fireTime) 261 { 262 ASSERT(isTimelineRunning() && fireTime.isFinite()); 263 ASSERT(!m_wakeupTimer.isActive()); 264 265 SMILTime delay = fireTime - elapsed(); 266 if (delay.value() < AnimationTimeline::s_minimumDelay) { 267 serviceOnNextFrame(); 268 } else { 269 scheduleWakeUp(delay.value() - AnimationTimeline::s_minimumDelay, FutureAnimationFrame); 270 } 271 } 272 273 void SMILTimeContainer::cancelAnimationFrame() 274 { 275 m_frameSchedulingState = Idle; 276 m_wakeupTimer.stop(); 277 } 278 279 void SMILTimeContainer::scheduleWakeUp(double delayTime, FrameSchedulingState frameSchedulingState) 280 { 281 ASSERT(frameSchedulingState == SynchronizeAnimations || frameSchedulingState == FutureAnimationFrame); 282 m_wakeupTimer.startOneShot(delayTime, FROM_HERE); 283 m_frameSchedulingState = frameSchedulingState; 284 } 285 286 void SMILTimeContainer::wakeupTimerFired(Timer<SMILTimeContainer>*) 287 { 288 ASSERT(m_frameSchedulingState == SynchronizeAnimations || m_frameSchedulingState == FutureAnimationFrame); 289 if (m_frameSchedulingState == FutureAnimationFrame) { 290 ASSERT(isTimelineRunning()); 291 m_frameSchedulingState = Idle; 292 serviceOnNextFrame(); 293 } else { 294 m_frameSchedulingState = Idle; 295 updateAnimationsAndScheduleFrameIfNeeded(elapsed()); 296 } 297 } 298 299 void SMILTimeContainer::updateDocumentOrderIndexes() 300 { 301 unsigned timingElementCount = 0; 302 for (SVGSMILElement* element = Traversal<SVGSMILElement>::firstWithin(m_ownerSVGElement); element; element = Traversal<SVGSMILElement>::next(*element, &m_ownerSVGElement)) 303 element->setDocumentOrderIndex(timingElementCount++); 304 m_documentOrderIndexesDirty = false; 305 } 306 307 struct PriorityCompare { 308 PriorityCompare(SMILTime elapsed) : m_elapsed(elapsed) {} 309 bool operator()(const RefPtrWillBeMember<SVGSMILElement>& a, const RefPtrWillBeMember<SVGSMILElement>& b) 310 { 311 // FIXME: This should also consider possible timing relations between the elements. 312 SMILTime aBegin = a->intervalBegin(); 313 SMILTime bBegin = b->intervalBegin(); 314 // Frozen elements need to be prioritized based on their previous interval. 315 aBegin = a->isFrozen() && m_elapsed < aBegin ? a->previousIntervalBegin() : aBegin; 316 bBegin = b->isFrozen() && m_elapsed < bBegin ? b->previousIntervalBegin() : bBegin; 317 if (aBegin == bBegin) 318 return a->documentOrderIndex() < b->documentOrderIndex(); 319 return aBegin < bBegin; 320 } 321 SMILTime m_elapsed; 322 }; 323 324 Document& SMILTimeContainer::document() const 325 { 326 return m_ownerSVGElement.document(); 327 } 328 329 double SMILTimeContainer::currentTime() const 330 { 331 return document().animationClock().currentTime(); 332 } 333 334 void SMILTimeContainer::serviceOnNextFrame() 335 { 336 if (document().view()) { 337 document().view()->scheduleAnimation(); 338 m_frameSchedulingState = AnimationFrame; 339 } 340 } 341 342 void SMILTimeContainer::serviceAnimations(double monotonicAnimationStartTime) 343 { 344 if (m_frameSchedulingState != AnimationFrame) 345 return; 346 347 m_frameSchedulingState = Idle; 348 updateAnimationsAndScheduleFrameIfNeeded(elapsed()); 349 } 350 351 void SMILTimeContainer::updateAnimationsAndScheduleFrameIfNeeded(SMILTime elapsed, bool seekToTime) 352 { 353 #if !ENABLE(OILPAN) 354 DiscardScope discardScope(m_ownerSVGElement); 355 #endif 356 SMILTime earliestFireTime = updateAnimations(elapsed, seekToTime); 357 // If updateAnimations() ended up triggering a synchronization (most likely 358 // via syncbases), then give that priority. 359 if (hasPendingSynchronization()) 360 return; 361 362 if (!isTimelineRunning()) 363 return; 364 365 if (!earliestFireTime.isFinite()) 366 return; 367 368 scheduleAnimationFrame(earliestFireTime); 369 } 370 371 SMILTime SMILTimeContainer::updateAnimations(SMILTime elapsed, bool seekToTime) 372 { 373 SMILTime earliestFireTime = SMILTime::unresolved(); 374 375 #if ENABLE(ASSERT) 376 // This boolean will catch any attempts to schedule/unschedule scheduledAnimations during this critical section. 377 // Similarly, any elements removed will unschedule themselves, so this will catch modification of animationsToApply. 378 m_preventScheduledAnimationsChanges = true; 379 #endif 380 381 if (m_documentOrderIndexesDirty) 382 updateDocumentOrderIndexes(); 383 384 WillBeHeapHashSet<ElementAttributePair> invalidKeys; 385 typedef WillBeHeapVector<RefPtrWillBeMember<SVGSMILElement> > AnimationsVector; 386 AnimationsVector animationsToApply; 387 for (GroupedAnimationsMap::iterator it = m_scheduledAnimations.begin(), end = m_scheduledAnimations.end(); it != end; ++it) { 388 if (!it->key.first || it->value->isEmpty()) { 389 invalidKeys.add(it->key); 390 continue; 391 } 392 393 AnimationsLinkedHashSet* scheduled = it->value.get(); 394 395 // Sort according to priority. Elements with later begin time have higher priority. 396 // In case of a tie, document order decides. 397 // FIXME: This should also consider timing relationships between the elements. Dependents 398 // have higher priority. 399 AnimationsVector scheduledAnimations; 400 copyToVector(*scheduled, scheduledAnimations); 401 std::sort(scheduledAnimations.begin(), scheduledAnimations.end(), PriorityCompare(elapsed)); 402 403 SVGSMILElement* resultElement = 0; 404 for (AnimationsVector::const_iterator itAnimation = scheduledAnimations.begin(), itAnimationEnd = scheduledAnimations.end(); itAnimation != itAnimationEnd; ++itAnimation) { 405 SVGSMILElement* animation = itAnimation->get(); 406 ASSERT(animation->timeContainer() == this); 407 ASSERT(animation->targetElement()); 408 ASSERT(animation->hasValidAttributeName()); 409 410 // Results are accumulated to the first animation that animates and contributes to a particular element/attribute pair. 411 // FIXME: we should ensure that resultElement is of an appropriate type. 412 if (!resultElement) { 413 if (!animation->hasValidAttributeType()) 414 continue; 415 resultElement = animation; 416 } 417 418 // This will calculate the contribution from the animation and add it to the resultsElement. 419 if (!animation->progress(elapsed, resultElement, seekToTime) && resultElement == animation) 420 resultElement = 0; 421 422 SMILTime nextFireTime = animation->nextProgressTime(); 423 if (nextFireTime.isFinite()) 424 earliestFireTime = std::min(nextFireTime, earliestFireTime); 425 } 426 427 if (resultElement) 428 animationsToApply.append(resultElement); 429 } 430 m_scheduledAnimations.removeAll(invalidKeys); 431 432 std::sort(animationsToApply.begin(), animationsToApply.end(), PriorityCompare(elapsed)); 433 434 unsigned animationsToApplySize = animationsToApply.size(); 435 if (!animationsToApplySize) { 436 #if ENABLE(ASSERT) 437 m_preventScheduledAnimationsChanges = false; 438 #endif 439 return earliestFireTime; 440 } 441 442 // Apply results to target elements. 443 for (unsigned i = 0; i < animationsToApplySize; ++i) 444 animationsToApply[i]->applyResultsToTarget(); 445 446 #if ENABLE(ASSERT) 447 m_preventScheduledAnimationsChanges = false; 448 #endif 449 450 for (unsigned i = 0; i < animationsToApplySize; ++i) { 451 if (animationsToApply[i]->inDocument() && animationsToApply[i]->isSVGDiscardElement()) { 452 RefPtrWillBeRawPtr<SVGSMILElement> animDiscard = animationsToApply[i]; 453 RefPtrWillBeRawPtr<SVGElement> targetElement = animDiscard->targetElement(); 454 if (targetElement && targetElement->inDocument()) { 455 targetElement->remove(IGNORE_EXCEPTION); 456 ASSERT(!targetElement->inDocument()); 457 } 458 459 if (animDiscard->inDocument()) { 460 animDiscard->remove(IGNORE_EXCEPTION); 461 ASSERT(!animDiscard->inDocument()); 462 } 463 } 464 } 465 return earliestFireTime; 466 } 467 468 void SMILTimeContainer::trace(Visitor* visitor) 469 { 470 #if ENABLE(OILPAN) 471 visitor->trace(m_scheduledAnimations); 472 #endif 473 } 474 475 } 476