| /* |
| * 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 "SystemTime.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_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::updateAnimations(SMILTime elapsed) |
| { |
| SMILTime earliersFireTime = SMILTime::unresolved(); |
| |
| Vector<SVGSMILElement*> toAnimate; |
| copyToVector(m_scheduledAnimations, toAnimate); |
| |
| // 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::updateDocumentsRendering(); |
| } |
| |
| #endif |
| |
| } |
| |
| #endif // ENABLE(SVG) |