File indexing completed on 2024-05-12 11:51:52

0001 /*
0002  * Copyright (C) 2008 Apple Inc. All Rights Reserved.
0003  *
0004  * Redistribution and use in source and binary forms, with or without
0005  * modification, are permitted provided that the following conditions
0006  * are met:
0007  * 1. Redistributions of source code must retain the above copyright
0008  *    notice, this list of conditions and the following disclaimer.
0009  * 2. Redistributions in binary form must reproduce the above copyright
0010  *    notice, this list of conditions and the following disclaimer in the
0011  *    documentation and/or other materials provided with the distribution.
0012  *
0013  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
0014  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
0015  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
0016  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
0017  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
0018  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
0019  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
0020  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
0021  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
0022  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
0023  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
0024  */
0025 
0026 #include "SMILTimeContainer.h"
0027 
0028 #include "CSSComputedStyleDeclaration.h"
0029 #include "CSSParser.h"
0030 #include "Document.h"
0031 #include "SVGAnimationElement.h"
0032 #include "SVGSMILElement.h"
0033 #include "SVGSVGElement.h"
0034 #include "SystemTime.h"
0035 
0036 using namespace std;
0037 
0038 namespace WebCore
0039 {
0040 
0041 static const double animationFrameDelay = 0.025;
0042 
0043 SMILTimeContainer::SMILTimeContainer(SVGSVGElement *owner)
0044     : m_beginTime(0)
0045     , m_pauseTime(0)
0046     , m_accumulatedPauseTime(0)
0047     , m_documentOrderIndexesDirty(false)
0048     , m_timer(this, &SMILTimeContainer::timerFired)
0049     , m_ownerSVGElement(owner)
0050 {
0051 }
0052 
0053 #if !ENABLE(SVG_ANIMATION)
0054 void SMILTimeContainer::begin() {}
0055 void SMILTimeContainer::pause() {}
0056 void SMILTimeContainer::resume() {}
0057 SMILTime SMILTimeContainer::elapsed() const
0058 {
0059     return 0;
0060 }
0061 bool SMILTimeContainer::isPaused() const
0062 {
0063     return false;
0064 }
0065 void SMILTimeContainer::timerFired(Timer<SMILTimeContainer> *) {}
0066 #else
0067 
0068 void SMILTimeContainer::schedule(SVGSMILElement *animation)
0069 {
0070     ASSERT(animation->timeContainer() == this);
0071     SMILTime nextFireTime = animation->nextProgressTime();
0072     if (!nextFireTime.isFinite()) {
0073         return;
0074     }
0075     m_scheduledAnimations.add(animation);
0076     startTimer(0);
0077 }
0078 
0079 void SMILTimeContainer::unschedule(SVGSMILElement *animation)
0080 {
0081     ASSERT(animation->timeContainer() == this);
0082 
0083     m_scheduledAnimations.remove(animation);
0084 }
0085 
0086 SMILTime SMILTimeContainer::elapsed() const
0087 {
0088     if (!m_beginTime) {
0089         return 0;
0090     }
0091     return currentTime() - m_beginTime - m_accumulatedPauseTime;
0092 }
0093 
0094 bool SMILTimeContainer::isActive() const
0095 {
0096     return m_beginTime && !isPaused();
0097 }
0098 
0099 bool SMILTimeContainer::isPaused() const
0100 {
0101     return m_pauseTime;
0102 }
0103 
0104 void SMILTimeContainer::begin()
0105 {
0106     ASSERT(!m_beginTime);
0107     m_beginTime = currentTime();
0108     updateAnimations(0);
0109 }
0110 
0111 void SMILTimeContainer::pause()
0112 {
0113     if (!m_beginTime) {
0114         return;
0115     }
0116     ASSERT(!isPaused());
0117     m_pauseTime = currentTime();
0118     m_timer.stop();
0119 }
0120 
0121 void SMILTimeContainer::resume()
0122 {
0123     if (!m_beginTime) {
0124         return;
0125     }
0126     ASSERT(isPaused());
0127     m_accumulatedPauseTime += currentTime() - m_pauseTime;
0128     m_pauseTime = 0;
0129     startTimer(0);
0130 }
0131 
0132 void SMILTimeContainer::startTimer(SMILTime fireTime, SMILTime minimumDelay)
0133 {
0134     if (!m_beginTime || isPaused()) {
0135         return;
0136     }
0137 
0138     if (!fireTime.isFinite()) {
0139         return;
0140     }
0141 
0142     SMILTime delay = max(fireTime - elapsed(), minimumDelay);
0143     m_timer.startOneShot(delay.value());
0144 }
0145 
0146 void SMILTimeContainer::timerFired(Timer<SMILTimeContainer> *)
0147 {
0148     ASSERT(m_beginTime);
0149     ASSERT(!m_pauseTime);
0150     SMILTime elapsed = this->elapsed();
0151     updateAnimations(elapsed);
0152 }
0153 
0154 void SMILTimeContainer::updateDocumentOrderIndexes()
0155 {
0156     unsigned timingElementCount = 0;
0157     for (Node *node = m_ownerSVGElement; node; node = node->traverseNextNode(m_ownerSVGElement)) {
0158         if (SVGSMILElement::isSMILElement(node)) {
0159             static_cast<SVGSMILElement *>(node)->setDocumentOrderIndex(timingElementCount++);
0160         }
0161     }
0162     m_documentOrderIndexesDirty = false;
0163 }
0164 
0165 struct PriorityCompare {
0166     PriorityCompare(SMILTime elapsed) : m_elapsed(elapsed) {}
0167     bool operator()(SVGSMILElement *a, SVGSMILElement *b)
0168     {
0169         // FIXME: This should also consider possible timing relations between the elements.
0170         SMILTime aBegin = a->intervalBegin();
0171         SMILTime bBegin = b->intervalBegin();
0172         // Frozen elements need to be prioritized based on their previous interval.
0173         aBegin = a->isFrozen() && m_elapsed < aBegin ? a->previousIntervalBegin() : aBegin;
0174         bBegin = b->isFrozen() && m_elapsed < bBegin ? b->previousIntervalBegin() : bBegin;
0175         if (aBegin == bBegin) {
0176             return a->documentOrderIndex() < b->documentOrderIndex();
0177         }
0178         return aBegin < bBegin;
0179     }
0180     SMILTime m_elapsed;
0181 };
0182 
0183 void SMILTimeContainer::sortByPriority(Vector<SVGSMILElement *> &smilElements, SMILTime elapsed)
0184 {
0185     if (m_documentOrderIndexesDirty) {
0186         updateDocumentOrderIndexes();
0187     }
0188     std::sort(smilElements.begin(), smilElements.end(), PriorityCompare(elapsed));
0189 }
0190 
0191 static bool applyOrderSortFunction(SVGSMILElement *a, SVGSMILElement *b)
0192 {
0193     if (!a->hasTagName(SVGNames::animateTransformTag) && b->hasTagName(SVGNames::animateTransformTag)) {
0194         return true;
0195     }
0196     return false;
0197 }
0198 
0199 static void sortByApplyOrder(Vector<SVGSMILElement *> &smilElements)
0200 {
0201     std::sort(smilElements.begin(), smilElements.end(), applyOrderSortFunction);
0202 }
0203 
0204 String SMILTimeContainer::baseValueFor(ElementAttributePair key)
0205 {
0206     // FIXME: We wouldn't need to do this if we were keeping base values around properly in DOM.
0207     // Currently animation overwrites them so we need to save them somewhere.
0208     BaseValueMap::iterator it = m_savedBaseValues.find(key);
0209     if (it != m_savedBaseValues.end()) {
0210         return it->second;
0211     }
0212 
0213     SVGElement *target = key.first;
0214     String attributeName = key.second;
0215     ASSERT(target);
0216     ASSERT(!attributeName.isEmpty());
0217     String baseValue;
0218     if (SVGAnimationElement::attributeIsCSS(attributeName)) {
0219         CSSComputedStyleDeclaration computedStyle(target);
0220         baseValue = computedStyle.getPropertyValue(cssPropertyID(attributeName));
0221     } else {
0222         baseValue = target->getAttribute(attributeName);
0223     }
0224     m_savedBaseValues.add(key, baseValue);
0225     return baseValue;
0226 }
0227 
0228 void SMILTimeContainer::updateAnimations(SMILTime elapsed)
0229 {
0230     SMILTime earliersFireTime = SMILTime::unresolved();
0231 
0232     Vector<SVGSMILElement *> toAnimate;
0233     copyToVector(m_scheduledAnimations, toAnimate);
0234 
0235     // Sort according to priority. Elements with later begin time have higher priority.
0236     // In case of a tie, document order decides.
0237     // FIXME: This should also consider timing relationships between the elements. Dependents
0238     // have higher priority.
0239     sortByPriority(toAnimate, elapsed);
0240 
0241     // Calculate animation contributions.
0242     typedef HashMap<ElementAttributePair, SVGSMILElement *> ResultElementMap;
0243     ResultElementMap resultsElements;
0244     for (unsigned n = 0; n < toAnimate.size(); ++n) {
0245         SVGSMILElement *animation = toAnimate[n];
0246         ASSERT(animation->timeContainer() == this);
0247 
0248         SVGElement *targetElement = animation->targetElement();
0249         if (!targetElement) {
0250             continue;
0251         }
0252         String attributeName = animation->attributeName();
0253         if (attributeName.isEmpty()) {
0254             if (animation->hasTagName(SVGNames::animateMotionTag)) {
0255                 attributeName = SVGNames::animateMotionTag.localName();
0256             } else {
0257                 continue;
0258             }
0259         }
0260 
0261         // Results are accumulated to the first animation that animates a particular element/attribute pair.
0262         ElementAttributePair key(targetElement, attributeName);
0263         SVGSMILElement *resultElement = resultsElements.get(key);
0264         if (!resultElement) {
0265             resultElement = animation;
0266             resultElement->resetToBaseValue(baseValueFor(key));
0267             resultsElements.add(key, resultElement);
0268         }
0269 
0270         // This will calculate the contribution from the animation and add it to the resultsElement.
0271         animation->progress(elapsed, resultElement);
0272 
0273         SMILTime nextFireTime = animation->nextProgressTime();
0274         if (nextFireTime.isFinite()) {
0275             earliersFireTime = min(nextFireTime, earliersFireTime);
0276         } else if (!animation->isContributing(elapsed)) {
0277             m_scheduledAnimations.remove(animation);
0278             if (m_scheduledAnimations.isEmpty()) {
0279                 m_savedBaseValues.clear();
0280             }
0281         }
0282     }
0283 
0284     Vector<SVGSMILElement *> animationsToApply;
0285     ResultElementMap::iterator end = resultsElements.end();
0286     for (ResultElementMap::iterator it = resultsElements.begin(); it != end; ++it) {
0287         animationsToApply.append(it->second);
0288     }
0289 
0290     // Sort <animateTranform> to be the last one to be applied. <animate> may change transform attribute as
0291     // well (directly or indirectly by modifying <use> x/y) and this way transforms combine properly.
0292     sortByApplyOrder(animationsToApply);
0293 
0294     // Apply results to target elements.
0295     for (unsigned n = 0; n < animationsToApply.size(); ++n) {
0296         animationsToApply[n]->applyResultsToTarget();
0297     }
0298 
0299     startTimer(earliersFireTime, animationFrameDelay);
0300 
0301     Document::updateDocumentsRendering();
0302 }
0303 
0304 #endif
0305 }
0306