Home | History | Annotate | Download | only in animation
      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