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/dom/NodeTraversal.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 != notFound);
     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 (SVGSMILElement::isSMILElement(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             if (!resultElement) {
    293                 if (!animation->hasValidAttributeType())
    294                     continue;
    295                 resultElement = animation;
    296             }
    297 
    298             // This will calculate the contribution from the animation and add it to the resultsElement.
    299             if (!animation->progress(elapsed, resultElement, seekToTime) && resultElement == animation)
    300                 resultElement = 0;
    301 
    302             SMILTime nextFireTime = animation->nextProgressTime();
    303             if (nextFireTime.isFinite())
    304                 earliestFireTime = min(nextFireTime, earliestFireTime);
    305         }
    306 
    307         if (resultElement)
    308             animationsToApply.append(resultElement);
    309     }
    310 
    311     unsigned animationsToApplySize = animationsToApply.size();
    312     if (!animationsToApplySize) {
    313 #ifndef NDEBUG
    314         m_preventScheduledAnimationsChanges = false;
    315 #endif
    316         startTimer(earliestFireTime, animationFrameDelay);
    317         return;
    318     }
    319 
    320     // Apply results to target elements.
    321     for (unsigned i = 0; i < animationsToApplySize; ++i)
    322         animationsToApply[i]->applyResultsToTarget();
    323 
    324 #ifndef NDEBUG
    325     m_preventScheduledAnimationsChanges = false;
    326 #endif
    327 
    328     startTimer(earliestFireTime, animationFrameDelay);
    329 }
    330 
    331 }
    332