WebCore/svg/animation/SMILTimeContainer.cpp
changeset 0 4f2f89ce4247
equal deleted inserted replaced
-1:000000000000 0:4f2f89ce4247
       
     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 
       
    31 #include "CSSComputedStyleDeclaration.h"
       
    32 #include "CSSParser.h"
       
    33 #include "Document.h"
       
    34 #include "SVGAnimationElement.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* target = key.first;
       
   200     String attributeName = key.second;
       
   201     ASSERT(target);
       
   202     ASSERT(!attributeName.isEmpty());
       
   203     String baseValue;
       
   204     if (SVGAnimationElement::attributeIsCSS(attributeName))
       
   205         baseValue = computedStyle(target)->getPropertyValue(cssPropertyID(attributeName));
       
   206     else
       
   207         baseValue = target->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, 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         String attributeName = animation->attributeName();
       
   268         if (attributeName.isEmpty()) {
       
   269             if (animation->hasTagName(SVGNames::animateMotionTag))
       
   270                 attributeName = SVGNames::animateMotionTag.localName();
       
   271             else
       
   272                 continue;
       
   273         }
       
   274         
       
   275         // Results are accumulated to the first animation that animates a particular element/attribute pair.
       
   276         ElementAttributePair key(targetElement, attributeName); 
       
   277         SVGSMILElement* resultElement = resultsElements.get(key);
       
   278         if (!resultElement) {
       
   279             resultElement = animation;
       
   280             resultElement->resetToBaseValue(baseValueFor(key));
       
   281             resultsElements.add(key, resultElement);
       
   282         }
       
   283 
       
   284         // This will calculate the contribution from the animation and add it to the resultsElement.
       
   285         animation->progress(elapsed, resultElement);
       
   286 
       
   287         SMILTime nextFireTime = animation->nextProgressTime();
       
   288         if (nextFireTime.isFinite())
       
   289             earliersFireTime = min(nextFireTime, earliersFireTime);
       
   290         else if (!animation->isContributing(elapsed)) {
       
   291             m_scheduledAnimations.remove(animation);
       
   292             if (m_scheduledAnimations.isEmpty())
       
   293                 m_savedBaseValues.clear();
       
   294         }
       
   295     }
       
   296     
       
   297     Vector<SVGSMILElement*> animationsToApply;
       
   298     ResultElementMap::iterator end = resultsElements.end();
       
   299     for (ResultElementMap::iterator it = resultsElements.begin(); it != end; ++it)
       
   300         animationsToApply.append(it->second);
       
   301 
       
   302     // Sort <animateTranform> to be the last one to be applied. <animate> may change transform attribute as
       
   303     // well (directly or indirectly by modifying <use> x/y) and this way transforms combine properly.
       
   304     sortByApplyOrder(animationsToApply);
       
   305     
       
   306     // Apply results to target elements.
       
   307     for (unsigned n = 0; n < animationsToApply.size(); ++n)
       
   308         animationsToApply[n]->applyResultsToTarget();
       
   309 
       
   310     startTimer(earliersFireTime, animationFrameDelay);
       
   311     
       
   312     Document::updateStyleForAllDocuments();
       
   313 }
       
   314 
       
   315 #endif
       
   316 
       
   317 }
       
   318 
       
   319 #endif // ENABLE(SVG)