diff -r 000000000000 -r 4f2f89ce4247 WebCore/svg/animation/SMILTimeContainer.cpp --- /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 + +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*) {} +#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*) +{ + 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(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& 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& 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 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 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 animationsToApply; + ResultElementMap::iterator end = resultsElements.end(); + for (ResultElementMap::iterator it = resultsElements.begin(); it != end; ++it) + animationsToApply.append(it->second); + + // Sort to be the last one to be applied. may change transform attribute as + // well (directly or indirectly by modifying 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)