--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/WebCore/svg/animation/SMILTimeContainer.cpp Fri Sep 17 09:02:29 2010 +0300
@@ -0,0 +1,319 @@
+/*
+ * Copyright (C) 2008 Apple Inc. All Rights Reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+ * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
+ * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "config.h"
+#include "SMILTimeContainer.h"
+
+#if ENABLE(SVG)
+
+#include "CSSComputedStyleDeclaration.h"
+#include "CSSParser.h"
+#include "Document.h"
+#include "SVGAnimationElement.h"
+#include "SVGSMILElement.h"
+#include "SVGSVGElement.h"
+#include <wtf/CurrentTime.h>
+
+using namespace std;
+
+namespace WebCore {
+
+static const double animationFrameDelay = 0.025;
+
+SMILTimeContainer::SMILTimeContainer(SVGSVGElement* owner)
+ : m_beginTime(0)
+ , m_pauseTime(0)
+ , m_accumulatedPauseTime(0)
+ , m_nextManualSampleTime(0)
+ , m_documentOrderIndexesDirty(false)
+ , m_timer(this, &SMILTimeContainer::timerFired)
+ , m_ownerSVGElement(owner)
+{
+}
+
+#if !ENABLE(SVG_ANIMATION)
+void SMILTimeContainer::begin() {}
+void SMILTimeContainer::pause() {}
+void SMILTimeContainer::resume() {}
+SMILTime SMILTimeContainer::elapsed() const { return 0; }
+bool SMILTimeContainer::isPaused() const { return false; }
+void SMILTimeContainer::timerFired(Timer<SMILTimeContainer>*) {}
+#else
+
+void SMILTimeContainer::schedule(SVGSMILElement* animation)
+{
+ ASSERT(animation->timeContainer() == this);
+ SMILTime nextFireTime = animation->nextProgressTime();
+ if (!nextFireTime.isFinite())
+ return;
+ m_scheduledAnimations.add(animation);
+ startTimer(0);
+}
+
+void SMILTimeContainer::unschedule(SVGSMILElement* animation)
+{
+ ASSERT(animation->timeContainer() == this);
+
+ m_scheduledAnimations.remove(animation);
+}
+
+SMILTime SMILTimeContainer::elapsed() const
+{
+ if (!m_beginTime)
+ return 0;
+ return currentTime() - m_beginTime - m_accumulatedPauseTime;
+}
+
+bool SMILTimeContainer::isActive() const
+{
+ return m_beginTime && !isPaused();
+}
+
+bool SMILTimeContainer::isPaused() const
+{
+ return m_pauseTime;
+}
+
+void SMILTimeContainer::begin()
+{
+ ASSERT(!m_beginTime);
+ m_beginTime = currentTime();
+ updateAnimations(0);
+}
+
+void SMILTimeContainer::pause()
+{
+ if (!m_beginTime)
+ return;
+ ASSERT(!isPaused());
+ m_pauseTime = currentTime();
+ m_timer.stop();
+}
+
+void SMILTimeContainer::resume()
+{
+ if (!m_beginTime)
+ return;
+ ASSERT(isPaused());
+ m_accumulatedPauseTime += currentTime() - m_pauseTime;
+ m_pauseTime = 0;
+ startTimer(0);
+}
+
+void SMILTimeContainer::startTimer(SMILTime fireTime, SMILTime minimumDelay)
+{
+ if (!m_beginTime || isPaused())
+ return;
+
+ if (!fireTime.isFinite())
+ return;
+
+ SMILTime delay = max(fireTime - elapsed(), minimumDelay);
+ m_timer.startOneShot(delay.value());
+}
+
+void SMILTimeContainer::timerFired(Timer<SMILTimeContainer>*)
+{
+ ASSERT(m_beginTime);
+ ASSERT(!m_pauseTime);
+ SMILTime elapsed = this->elapsed();
+ updateAnimations(elapsed);
+}
+
+void SMILTimeContainer::updateDocumentOrderIndexes()
+{
+ unsigned timingElementCount = 0;
+ for (Node* node = m_ownerSVGElement; node; node = node->traverseNextNode(m_ownerSVGElement)) {
+ if (SVGSMILElement::isSMILElement(node))
+ static_cast<SVGSMILElement*>(node)->setDocumentOrderIndex(timingElementCount++);
+ }
+ m_documentOrderIndexesDirty = false;
+}
+
+struct PriorityCompare {
+ PriorityCompare(SMILTime elapsed) : m_elapsed(elapsed) {}
+ bool operator()(SVGSMILElement* a, SVGSMILElement* b)
+ {
+ // FIXME: This should also consider possible timing relations between the elements.
+ SMILTime aBegin = a->intervalBegin();
+ SMILTime bBegin = b->intervalBegin();
+ // Frozen elements need to be prioritized based on their previous interval.
+ aBegin = a->isFrozen() && m_elapsed < aBegin ? a->previousIntervalBegin() : aBegin;
+ bBegin = b->isFrozen() && m_elapsed < bBegin ? b->previousIntervalBegin() : bBegin;
+ if (aBegin == bBegin)
+ return a->documentOrderIndex() < b->documentOrderIndex();
+ return aBegin < bBegin;
+ }
+ SMILTime m_elapsed;
+};
+
+void SMILTimeContainer::sortByPriority(Vector<SVGSMILElement*>& smilElements, SMILTime elapsed)
+{
+ if (m_documentOrderIndexesDirty)
+ updateDocumentOrderIndexes();
+ std::sort(smilElements.begin(), smilElements.end(), PriorityCompare(elapsed));
+}
+
+static bool applyOrderSortFunction(SVGSMILElement* a, SVGSMILElement* b)
+{
+ if (!a->hasTagName(SVGNames::animateTransformTag) && b->hasTagName(SVGNames::animateTransformTag))
+ return true;
+ return false;
+}
+
+static void sortByApplyOrder(Vector<SVGSMILElement*>& smilElements)
+{
+ std::sort(smilElements.begin(), smilElements.end(), applyOrderSortFunction);
+}
+
+String SMILTimeContainer::baseValueFor(ElementAttributePair key)
+{
+ // FIXME: We wouldn't need to do this if we were keeping base values around properly in DOM.
+ // Currently animation overwrites them so we need to save them somewhere.
+ BaseValueMap::iterator it = m_savedBaseValues.find(key);
+ if (it != m_savedBaseValues.end())
+ return it->second;
+
+ SVGElement* target = key.first;
+ String attributeName = key.second;
+ ASSERT(target);
+ ASSERT(!attributeName.isEmpty());
+ String baseValue;
+ if (SVGAnimationElement::attributeIsCSS(attributeName))
+ baseValue = computedStyle(target)->getPropertyValue(cssPropertyID(attributeName));
+ else
+ baseValue = target->getAttribute(attributeName);
+ m_savedBaseValues.add(key, baseValue);
+ return baseValue;
+}
+
+void SMILTimeContainer::sampleAnimationAtTime(const String& elementId, double newTime)
+{
+ ASSERT(m_beginTime);
+ ASSERT(!isPaused());
+
+ // Fast-forward to the time DRT wants to sample
+ m_timer.stop();
+ m_nextSamplingTarget = elementId;
+ m_nextManualSampleTime = newTime;
+
+ updateAnimations(elapsed());
+}
+
+void SMILTimeContainer::updateAnimations(SMILTime elapsed)
+{
+ SMILTime earliersFireTime = SMILTime::unresolved();
+
+ Vector<SVGSMILElement*> toAnimate;
+ copyToVector(m_scheduledAnimations, toAnimate);
+
+ if (m_nextManualSampleTime) {
+ SMILTime samplingDiff;
+ for (unsigned n = 0; n < toAnimate.size(); ++n) {
+ SVGSMILElement* animation = toAnimate[n];
+ ASSERT(animation->timeContainer() == this);
+
+ SVGElement* targetElement = animation->targetElement();
+ // FIXME: This should probably be using getIdAttribute instead of idForStyleResolution.
+ if (!targetElement || !targetElement->hasID() || targetElement->idForStyleResolution() != m_nextSamplingTarget)
+ continue;
+
+ samplingDiff = animation->intervalBegin();
+ break;
+ }
+
+ elapsed = SMILTime(m_nextManualSampleTime) + samplingDiff;
+ m_nextManualSampleTime = 0;
+ }
+
+ // Sort according to priority. Elements with later begin time have higher priority.
+ // In case of a tie, document order decides.
+ // FIXME: This should also consider timing relationships between the elements. Dependents
+ // have higher priority.
+ sortByPriority(toAnimate, elapsed);
+
+ // Calculate animation contributions.
+ typedef HashMap<ElementAttributePair, SVGSMILElement*> ResultElementMap;
+ ResultElementMap resultsElements;
+ for (unsigned n = 0; n < toAnimate.size(); ++n) {
+ SVGSMILElement* animation = toAnimate[n];
+ ASSERT(animation->timeContainer() == this);
+
+ SVGElement* targetElement = animation->targetElement();
+ if (!targetElement)
+ continue;
+ String attributeName = animation->attributeName();
+ if (attributeName.isEmpty()) {
+ if (animation->hasTagName(SVGNames::animateMotionTag))
+ attributeName = SVGNames::animateMotionTag.localName();
+ else
+ continue;
+ }
+
+ // Results are accumulated to the first animation that animates a particular element/attribute pair.
+ ElementAttributePair key(targetElement, attributeName);
+ SVGSMILElement* resultElement = resultsElements.get(key);
+ if (!resultElement) {
+ resultElement = animation;
+ resultElement->resetToBaseValue(baseValueFor(key));
+ resultsElements.add(key, resultElement);
+ }
+
+ // This will calculate the contribution from the animation and add it to the resultsElement.
+ animation->progress(elapsed, resultElement);
+
+ SMILTime nextFireTime = animation->nextProgressTime();
+ if (nextFireTime.isFinite())
+ earliersFireTime = min(nextFireTime, earliersFireTime);
+ else if (!animation->isContributing(elapsed)) {
+ m_scheduledAnimations.remove(animation);
+ if (m_scheduledAnimations.isEmpty())
+ m_savedBaseValues.clear();
+ }
+ }
+
+ Vector<SVGSMILElement*> animationsToApply;
+ ResultElementMap::iterator end = resultsElements.end();
+ for (ResultElementMap::iterator it = resultsElements.begin(); it != end; ++it)
+ animationsToApply.append(it->second);
+
+ // Sort <animateTranform> to be the last one to be applied. <animate> may change transform attribute as
+ // well (directly or indirectly by modifying <use> x/y) and this way transforms combine properly.
+ sortByApplyOrder(animationsToApply);
+
+ // Apply results to target elements.
+ for (unsigned n = 0; n < animationsToApply.size(); ++n)
+ animationsToApply[n]->applyResultsToTarget();
+
+ startTimer(earliersFireTime, animationFrameDelay);
+
+ Document::updateStyleForAllDocuments();
+}
+
+#endif
+
+}
+
+#endif // ENABLE(SVG)