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 "SMILTimeContainer.h"
     28 
     29 #if ENABLE(SVG)
     30 #include "CSSComputedStyleDeclaration.h"
     31 #include "CSSParser.h"
     32 #include "Document.h"
     33 #include "SVGAnimationElement.h"
     34 #include "SVGNames.h"
     35 #include "SVGSMILElement.h"
     36 #include "SVGSVGElement.h"
     37 #include <wtf/CurrentTime.h>
     38 
     39 using namespace std;
     40 
     41 namespace WebCore {
     42 
     43 static const double animationFrameDelay = 0.025;
     44 
     45 SMILTimeContainer::SMILTimeContainer(SVGSVGElement* owner)
     46     : m_beginTime(0)
     47     , m_pauseTime(0)
     48     , m_accumulatedPauseTime(0)
     49     , m_nextManualSampleTime(0)
     50     , m_documentOrderIndexesDirty(false)
     51     , m_timer(this, &SMILTimeContainer::timerFired)
     52     , m_ownerSVGElement(owner)
     53 {
     54 }
     55 
     56 #if !ENABLE(SVG_ANIMATION)
     57 void SMILTimeContainer::begin() {}
     58 void SMILTimeContainer::pause() {}
     59 void SMILTimeContainer::resume() {}
     60 SMILTime SMILTimeContainer::elapsed() const { return 0; }
     61 bool SMILTimeContainer::isPaused() const { return false; }
     62 void SMILTimeContainer::timerFired(Timer<SMILTimeContainer>*) {}
     63 #else
     64 
     65 void SMILTimeContainer::schedule(SVGSMILElement* animation)
     66 {
     67     ASSERT(animation->timeContainer() == this);
     68     SMILTime nextFireTime = animation->nextProgressTime();
     69     if (!nextFireTime.isFinite())
     70         return;
     71     m_scheduledAnimations.add(animation);
     72     startTimer(0);
     73 }
     74 
     75 void SMILTimeContainer::unschedule(SVGSMILElement* animation)
     76 {
     77     ASSERT(animation->timeContainer() == this);
     78 
     79     m_scheduledAnimations.remove(animation);
     80 }
     81 
     82 SMILTime SMILTimeContainer::elapsed() const
     83 {
     84     if (!m_beginTime)
     85         return 0;
     86     return currentTime() - m_beginTime - m_accumulatedPauseTime;
     87 }
     88 
     89 bool SMILTimeContainer::isActive() const
     90 {
     91     return m_beginTime && !isPaused();
     92 }
     93 
     94 bool SMILTimeContainer::isPaused() const
     95 {
     96     return m_pauseTime;
     97 }
     98 
     99 void SMILTimeContainer::begin()
    100 {
    101     ASSERT(!m_beginTime);
    102     m_beginTime = currentTime();
    103     updateAnimations(0);
    104 }
    105 
    106 void SMILTimeContainer::pause()
    107 {
    108     if (!m_beginTime)
    109         return;
    110     ASSERT(!isPaused());
    111     m_pauseTime = currentTime();
    112     m_timer.stop();
    113 }
    114 
    115 void SMILTimeContainer::resume()
    116 {
    117     if (!m_beginTime)
    118         return;
    119     ASSERT(isPaused());
    120     m_accumulatedPauseTime += currentTime() - m_pauseTime;
    121     m_pauseTime = 0;
    122     startTimer(0);
    123 }
    124 
    125 void SMILTimeContainer::startTimer(SMILTime fireTime, SMILTime minimumDelay)
    126 {
    127     if (!m_beginTime || isPaused())
    128         return;
    129 
    130     if (!fireTime.isFinite())
    131         return;
    132 
    133     SMILTime delay = max(fireTime - elapsed(), minimumDelay);
    134     m_timer.startOneShot(delay.value());
    135 }
    136 
    137 void SMILTimeContainer::timerFired(Timer<SMILTimeContainer>*)
    138 {
    139     ASSERT(m_beginTime);
    140     ASSERT(!m_pauseTime);
    141     SMILTime elapsed = this->elapsed();
    142     updateAnimations(elapsed);
    143 }
    144 
    145 void SMILTimeContainer::updateDocumentOrderIndexes()
    146 {
    147     unsigned timingElementCount = 0;
    148     for (Node* node = m_ownerSVGElement; node; node = node->traverseNextNode(m_ownerSVGElement)) {
    149         if (SVGSMILElement::isSMILElement(node))
    150             static_cast<SVGSMILElement*>(node)->setDocumentOrderIndex(timingElementCount++);
    151     }
    152     m_documentOrderIndexesDirty = false;
    153 }
    154 
    155 struct PriorityCompare {
    156     PriorityCompare(SMILTime elapsed) : m_elapsed(elapsed) {}
    157     bool operator()(SVGSMILElement* a, SVGSMILElement* b)
    158     {
    159         // FIXME: This should also consider possible timing relations between the elements.
    160         SMILTime aBegin = a->intervalBegin();
    161         SMILTime bBegin = b->intervalBegin();
    162         // Frozen elements need to be prioritized based on their previous interval.
    163         aBegin = a->isFrozen() && m_elapsed < aBegin ? a->previousIntervalBegin() : aBegin;
    164         bBegin = b->isFrozen() && m_elapsed < bBegin ? b->previousIntervalBegin() : bBegin;
    165         if (aBegin == bBegin)
    166             return a->documentOrderIndex() < b->documentOrderIndex();
    167         return aBegin < bBegin;
    168     }
    169     SMILTime m_elapsed;
    170 };
    171 
    172 void SMILTimeContainer::sortByPriority(Vector<SVGSMILElement*>& smilElements, SMILTime elapsed)
    173 {
    174     if (m_documentOrderIndexesDirty)
    175         updateDocumentOrderIndexes();
    176     std::sort(smilElements.begin(), smilElements.end(), PriorityCompare(elapsed));
    177 }
    178 
    179 static bool applyOrderSortFunction(SVGSMILElement* a, SVGSMILElement* b)
    180 {
    181     if (!a->hasTagName(SVGNames::animateTransformTag) && b->hasTagName(SVGNames::animateTransformTag))
    182         return true;
    183     return false;
    184 }
    185 
    186 static void sortByApplyOrder(Vector<SVGSMILElement*>& smilElements)
    187 {
    188     std::sort(smilElements.begin(), smilElements.end(), applyOrderSortFunction);
    189 }
    190 
    191 String SMILTimeContainer::baseValueFor(ElementAttributePair key)
    192 {
    193     // FIXME: We wouldn't need to do this if we were keeping base values around properly in DOM.
    194     // Currently animation overwrites them so we need to save them somewhere.
    195     BaseValueMap::iterator it = m_savedBaseValues.find(key);
    196     if (it != m_savedBaseValues.end())
    197         return it->second;
    198 
    199     SVGElement* targetElement = key.first;
    200     QualifiedName attributeName = key.second;
    201     ASSERT(targetElement);
    202     ASSERT(attributeName != anyQName());
    203     String baseValue;
    204     if (SVGAnimationElement::isTargetAttributeCSSProperty(targetElement, attributeName))
    205         baseValue = computedStyle(targetElement)->getPropertyValue(cssPropertyID(attributeName.localName()));
    206     else
    207         baseValue = targetElement->getAttribute(attributeName);
    208     m_savedBaseValues.add(key, baseValue);
    209     return baseValue;
    210 }
    211 
    212 void SMILTimeContainer::sampleAnimationAtTime(const String& elementId, double newTime)
    213 {
    214     ASSERT(m_beginTime);
    215     ASSERT(!isPaused());
    216 
    217     // Fast-forward to the time DRT wants to sample
    218     m_timer.stop();
    219     m_nextSamplingTarget = elementId;
    220     m_nextManualSampleTime = newTime;
    221 
    222     updateAnimations(elapsed());
    223 }
    224 
    225 void SMILTimeContainer::updateAnimations(SMILTime elapsed)
    226 {
    227     SMILTime earliersFireTime = SMILTime::unresolved();
    228 
    229     Vector<SVGSMILElement*> toAnimate;
    230     copyToVector(m_scheduledAnimations, toAnimate);
    231 
    232     if (m_nextManualSampleTime) {
    233         SMILTime samplingDiff;
    234         for (unsigned n = 0; n < toAnimate.size(); ++n) {
    235             SVGSMILElement* animation = toAnimate[n];
    236             ASSERT(animation->timeContainer() == this);
    237 
    238             SVGElement* targetElement = animation->targetElement();
    239             // FIXME: This should probably be using getIdAttribute instead of idForStyleResolution.
    240             if (!targetElement || !targetElement->hasID() || targetElement->idForStyleResolution() != m_nextSamplingTarget)
    241                 continue;
    242 
    243             samplingDiff = animation->intervalBegin();
    244             break;
    245         }
    246 
    247         elapsed = SMILTime(m_nextManualSampleTime) + samplingDiff;
    248         m_nextManualSampleTime = 0;
    249     }
    250 
    251     // Sort according to priority. Elements with later begin time have higher priority.
    252     // In case of a tie, document order decides.
    253     // FIXME: This should also consider timing relationships between the elements. Dependents
    254     // have higher priority.
    255     sortByPriority(toAnimate, elapsed);
    256 
    257     // Calculate animation contributions.
    258     typedef HashMap<ElementAttributePair, RefPtr<SVGSMILElement> > ResultElementMap;
    259     ResultElementMap resultsElements;
    260     for (unsigned n = 0; n < toAnimate.size(); ++n) {
    261         SVGSMILElement* animation = toAnimate[n];
    262         ASSERT(animation->timeContainer() == this);
    263 
    264         SVGElement* targetElement = animation->targetElement();
    265         if (!targetElement)
    266             continue;
    267 
    268         QualifiedName attributeName = animation->attributeName();
    269         if (attributeName == anyQName()) {
    270             if (animation->hasTagName(SVGNames::animateMotionTag))
    271                 attributeName = SVGNames::animateMotionTag;
    272             else
    273                 continue;
    274         }
    275 
    276         // Results are accumulated to the first animation that animates a particular element/attribute pair.
    277         ElementAttributePair key(targetElement, attributeName);
    278         SVGSMILElement* resultElement = resultsElements.get(key).get();
    279         if (!resultElement) {
    280             if (!animation->hasValidAttributeType())
    281                 continue;
    282             resultElement = animation;
    283             resultElement->resetToBaseValue(baseValueFor(key));
    284             resultsElements.add(key, resultElement);
    285         }
    286 
    287         // This will calculate the contribution from the animation and add it to the resultsElement.
    288         animation->progress(elapsed, resultElement);
    289 
    290         SMILTime nextFireTime = animation->nextProgressTime();
    291         if (nextFireTime.isFinite())
    292             earliersFireTime = min(nextFireTime, earliersFireTime);
    293         else if (!animation->isContributing(elapsed)) {
    294             m_scheduledAnimations.remove(animation);
    295             if (m_scheduledAnimations.isEmpty())
    296                 m_savedBaseValues.clear();
    297         }
    298     }
    299 
    300     Vector<SVGSMILElement*> animationsToApply;
    301     ResultElementMap::iterator end = resultsElements.end();
    302     for (ResultElementMap::iterator it = resultsElements.begin(); it != end; ++it)
    303         animationsToApply.append(it->second.get());
    304 
    305     // Sort <animateTranform> to be the last one to be applied. <animate> may change transform attribute as
    306     // well (directly or indirectly by modifying <use> x/y) and this way transforms combine properly.
    307     sortByApplyOrder(animationsToApply);
    308 
    309     // Apply results to target elements.
    310     for (unsigned n = 0; n < animationsToApply.size(); ++n)
    311         animationsToApply[n]->applyResultsToTarget();
    312 
    313     startTimer(earliersFireTime, animationFrameDelay);
    314 
    315     Document::updateStyleForAllDocuments();
    316 }
    317 
    318 #endif
    319 
    320 }
    321 
    322 #endif // ENABLE(SVG)
    323