• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 "core/svg/animation/SMILTimeContainer.h"
28 
29 #include "core/animation/AnimationClock.h"
30 #include "core/animation/AnimationTimeline.h"
31 #include "core/dom/ElementTraversal.h"
32 #include "core/frame/FrameView.h"
33 #include "core/svg/SVGSVGElement.h"
34 #include "core/svg/animation/SVGSMILElement.h"
35 
36 namespace WebCore {
37 
38 static const double initialFrameDelay = 0.025;
39 
40 #if !ENABLE(OILPAN)
41 // Every entry-point that calls updateAnimations() should instantiate a
42 // DiscardScope to prevent deletion of the ownerElement (and hence itself.)
43 class DiscardScope {
44 public:
DiscardScope(SVGSVGElement & timeContainerOwner)45     explicit DiscardScope(SVGSVGElement& timeContainerOwner) : m_discardScopeElement(&timeContainerOwner) { }
46 
47 private:
48     RefPtr<SVGSVGElement> m_discardScopeElement;
49 };
50 #endif
51 
SMILTimeContainer(SVGSVGElement & owner)52 SMILTimeContainer::SMILTimeContainer(SVGSVGElement& owner)
53     : m_beginTime(0)
54     , m_pauseTime(0)
55     , m_resumeTime(0)
56     , m_accumulatedActiveTime(0)
57     , m_presetStartTime(0)
58     , m_frameSchedulingState(Idle)
59     , m_documentOrderIndexesDirty(false)
60     , m_wakeupTimer(this, &SMILTimeContainer::wakeupTimerFired)
61     , m_ownerSVGElement(owner)
62 #ifndef NDEBUG
63     , m_preventScheduledAnimationsChanges(false)
64 #endif
65 {
66 }
67 
~SMILTimeContainer()68 SMILTimeContainer::~SMILTimeContainer()
69 {
70     cancelAnimationFrame();
71     ASSERT(!m_wakeupTimer.isActive());
72 #ifndef NDEBUG
73     ASSERT(!m_preventScheduledAnimationsChanges);
74 #endif
75 }
76 
schedule(SVGSMILElement * animation,SVGElement * target,const QualifiedName & attributeName)77 void SMILTimeContainer::schedule(SVGSMILElement* animation, SVGElement* target, const QualifiedName& attributeName)
78 {
79     ASSERT(animation->timeContainer() == this);
80     ASSERT(target);
81     ASSERT(animation->hasValidAttributeName());
82 
83 #ifndef NDEBUG
84     ASSERT(!m_preventScheduledAnimationsChanges);
85 #endif
86 
87     ElementAttributePair key(target, attributeName);
88     OwnPtrWillBeMember<AnimationsLinkedHashSet>& scheduled = m_scheduledAnimations.add(key, nullptr).storedValue->value;
89     if (!scheduled)
90         scheduled = adoptPtrWillBeNoop(new AnimationsLinkedHashSet);
91     ASSERT(!scheduled->contains(animation));
92     scheduled->add(animation);
93 
94     SMILTime nextFireTime = animation->nextProgressTime();
95     if (nextFireTime.isFinite())
96         notifyIntervalsChanged();
97 }
98 
unschedule(SVGSMILElement * animation,SVGElement * target,const QualifiedName & attributeName)99 void SMILTimeContainer::unschedule(SVGSMILElement* animation, SVGElement* target, const QualifiedName& attributeName)
100 {
101     ASSERT(animation->timeContainer() == this);
102 
103 #ifndef NDEBUG
104     ASSERT(!m_preventScheduledAnimationsChanges);
105 #endif
106 
107     ElementAttributePair key(target, attributeName);
108     GroupedAnimationsMap::iterator it = m_scheduledAnimations.find(key);
109     ASSERT(it != m_scheduledAnimations.end());
110     AnimationsLinkedHashSet* scheduled = it->value.get();
111     ASSERT(scheduled);
112     AnimationsLinkedHashSet::iterator itAnimation = scheduled->find(animation);
113     ASSERT(itAnimation != scheduled->end());
114     scheduled->remove(itAnimation);
115 
116     if (scheduled->isEmpty())
117         m_scheduledAnimations.remove(it);
118 }
119 
hasAnimations() const120 bool SMILTimeContainer::hasAnimations() const
121 {
122     return !m_scheduledAnimations.isEmpty();
123 }
124 
hasPendingSynchronization() const125 bool SMILTimeContainer::hasPendingSynchronization() const
126 {
127     return m_frameSchedulingState == SynchronizeAnimations && m_wakeupTimer.isActive() && !m_wakeupTimer.nextFireInterval();
128 }
129 
notifyIntervalsChanged()130 void SMILTimeContainer::notifyIntervalsChanged()
131 {
132     if (!isStarted())
133         return;
134     // Schedule updateAnimations() to be called asynchronously so multiple intervals
135     // can change with updateAnimations() only called once at the end.
136     if (hasPendingSynchronization())
137         return;
138     cancelAnimationFrame();
139     scheduleWakeUp(0, SynchronizeAnimations);
140 }
141 
elapsed() const142 SMILTime SMILTimeContainer::elapsed() const
143 {
144     if (!m_beginTime)
145         return 0;
146 
147     if (isPaused())
148         return m_accumulatedActiveTime;
149 
150     return currentTime() + m_accumulatedActiveTime - lastResumeTime();
151 }
152 
isPaused() const153 bool SMILTimeContainer::isPaused() const
154 {
155     return m_pauseTime;
156 }
157 
isStarted() const158 bool SMILTimeContainer::isStarted() const
159 {
160     return m_beginTime;
161 }
162 
begin()163 void SMILTimeContainer::begin()
164 {
165     RELEASE_ASSERT(!m_beginTime);
166     double now = currentTime();
167 
168     // If 'm_presetStartTime' is set, the timeline was modified via setElapsed() before the document began.
169     // In this case pass on 'seekToTime=true' to updateAnimations().
170     m_beginTime = now - m_presetStartTime;
171 #if !ENABLE(OILPAN)
172     DiscardScope discardScope(m_ownerSVGElement);
173 #endif
174     SMILTime earliestFireTime = updateAnimations(SMILTime(m_presetStartTime), m_presetStartTime ? true : false);
175     m_presetStartTime = 0;
176 
177     if (m_pauseTime) {
178         m_pauseTime = now;
179         // If updateAnimations() caused new syncbase instance to be generated,
180         // we don't want to cancel those. Excepting that, no frame should've
181         // been scheduled at this point.
182         ASSERT(m_frameSchedulingState == Idle || m_frameSchedulingState == SynchronizeAnimations);
183     } else if (!hasPendingSynchronization()) {
184         ASSERT(isTimelineRunning());
185         // If the timeline is running, and there's pending animation updates,
186         // always perform the first update after the timeline was started using
187         // the wake-up mechanism.
188         if (earliestFireTime.isFinite()) {
189             SMILTime delay = earliestFireTime - elapsed();
190             scheduleWakeUp(std::max(initialFrameDelay, delay.value()), SynchronizeAnimations);
191         }
192     }
193 }
194 
pause()195 void SMILTimeContainer::pause()
196 {
197     ASSERT(!isPaused());
198     m_pauseTime = currentTime();
199 
200     if (m_beginTime) {
201         m_accumulatedActiveTime += m_pauseTime - lastResumeTime();
202         cancelAnimationFrame();
203     }
204     m_resumeTime = 0;
205 }
206 
resume()207 void SMILTimeContainer::resume()
208 {
209     ASSERT(isPaused());
210     m_resumeTime = currentTime();
211 
212     m_pauseTime = 0;
213     scheduleWakeUp(0, SynchronizeAnimations);
214 }
215 
setElapsed(SMILTime time)216 void SMILTimeContainer::setElapsed(SMILTime time)
217 {
218     // If the documment didn't begin yet, record a new start time, we'll seek to once its possible.
219     if (!m_beginTime) {
220         m_presetStartTime = time.value();
221         return;
222     }
223 
224     cancelAnimationFrame();
225 
226     double now = currentTime();
227     m_beginTime = now - time.value();
228     m_resumeTime = 0;
229     if (m_pauseTime) {
230         m_pauseTime = now;
231         m_accumulatedActiveTime = time.value();
232     } else {
233         m_accumulatedActiveTime = 0;
234     }
235 
236 #ifndef NDEBUG
237     m_preventScheduledAnimationsChanges = true;
238 #endif
239     GroupedAnimationsMap::iterator end = m_scheduledAnimations.end();
240     for (GroupedAnimationsMap::iterator it = m_scheduledAnimations.begin(); it != end; ++it) {
241         if (!it->key.first)
242             continue;
243 
244         AnimationsLinkedHashSet* scheduled = it->value.get();
245         for (AnimationsLinkedHashSet::const_iterator itAnimation = scheduled->begin(), itAnimationEnd = scheduled->end(); itAnimation != itAnimationEnd; ++itAnimation)
246             (*itAnimation)->reset();
247     }
248 #ifndef NDEBUG
249     m_preventScheduledAnimationsChanges = false;
250 #endif
251 
252     updateAnimationsAndScheduleFrameIfNeeded(time, true);
253 }
254 
isTimelineRunning() const255 bool SMILTimeContainer::isTimelineRunning() const
256 {
257     return m_beginTime && !isPaused();
258 }
259 
scheduleAnimationFrame(SMILTime fireTime)260 void SMILTimeContainer::scheduleAnimationFrame(SMILTime fireTime)
261 {
262     ASSERT(isTimelineRunning() && fireTime.isFinite());
263     ASSERT(!m_wakeupTimer.isActive());
264 
265     SMILTime delay = fireTime - elapsed();
266     if (delay.value() < AnimationTimeline::s_minimumDelay) {
267         serviceOnNextFrame();
268     } else {
269         scheduleWakeUp(delay.value() - AnimationTimeline::s_minimumDelay, FutureAnimationFrame);
270     }
271 }
272 
cancelAnimationFrame()273 void SMILTimeContainer::cancelAnimationFrame()
274 {
275     m_frameSchedulingState = Idle;
276     m_wakeupTimer.stop();
277 }
278 
scheduleWakeUp(double delayTime,FrameSchedulingState frameSchedulingState)279 void SMILTimeContainer::scheduleWakeUp(double delayTime, FrameSchedulingState frameSchedulingState)
280 {
281     ASSERT(frameSchedulingState == SynchronizeAnimations || frameSchedulingState == FutureAnimationFrame);
282     m_wakeupTimer.startOneShot(delayTime, FROM_HERE);
283     m_frameSchedulingState = frameSchedulingState;
284 }
285 
wakeupTimerFired(Timer<SMILTimeContainer> *)286 void SMILTimeContainer::wakeupTimerFired(Timer<SMILTimeContainer>*)
287 {
288     ASSERT(m_frameSchedulingState == SynchronizeAnimations || m_frameSchedulingState == FutureAnimationFrame);
289     if (m_frameSchedulingState == FutureAnimationFrame) {
290         ASSERT(isTimelineRunning());
291         m_frameSchedulingState = Idle;
292         serviceOnNextFrame();
293     } else {
294         m_frameSchedulingState = Idle;
295         updateAnimationsAndScheduleFrameIfNeeded(elapsed());
296     }
297 }
298 
updateDocumentOrderIndexes()299 void SMILTimeContainer::updateDocumentOrderIndexes()
300 {
301     unsigned timingElementCount = 0;
302     for (SVGSMILElement* element = Traversal<SVGSMILElement>::firstWithin(m_ownerSVGElement); element; element = Traversal<SVGSMILElement>::next(*element, &m_ownerSVGElement))
303         element->setDocumentOrderIndex(timingElementCount++);
304     m_documentOrderIndexesDirty = false;
305 }
306 
307 struct PriorityCompare {
PriorityCompareWebCore::PriorityCompare308     PriorityCompare(SMILTime elapsed) : m_elapsed(elapsed) {}
operator ()WebCore::PriorityCompare309     bool operator()(const RefPtrWillBeMember<SVGSMILElement>& a, const RefPtrWillBeMember<SVGSMILElement>& b)
310     {
311         // FIXME: This should also consider possible timing relations between the elements.
312         SMILTime aBegin = a->intervalBegin();
313         SMILTime bBegin = b->intervalBegin();
314         // Frozen elements need to be prioritized based on their previous interval.
315         aBegin = a->isFrozen() && m_elapsed < aBegin ? a->previousIntervalBegin() : aBegin;
316         bBegin = b->isFrozen() && m_elapsed < bBegin ? b->previousIntervalBegin() : bBegin;
317         if (aBegin == bBegin)
318             return a->documentOrderIndex() < b->documentOrderIndex();
319         return aBegin < bBegin;
320     }
321     SMILTime m_elapsed;
322 };
323 
document() const324 Document& SMILTimeContainer::document() const
325 {
326     return m_ownerSVGElement.document();
327 }
328 
currentTime() const329 double SMILTimeContainer::currentTime() const
330 {
331     return document().animationClock().currentTime();
332 }
333 
serviceOnNextFrame()334 void SMILTimeContainer::serviceOnNextFrame()
335 {
336     if (document().view()) {
337         document().view()->scheduleAnimation();
338         m_frameSchedulingState = AnimationFrame;
339     }
340 }
341 
serviceAnimations(double monotonicAnimationStartTime)342 void SMILTimeContainer::serviceAnimations(double monotonicAnimationStartTime)
343 {
344     if (m_frameSchedulingState != AnimationFrame)
345         return;
346 
347     m_frameSchedulingState = Idle;
348     updateAnimationsAndScheduleFrameIfNeeded(elapsed());
349 }
350 
updateAnimationsAndScheduleFrameIfNeeded(SMILTime elapsed,bool seekToTime)351 void SMILTimeContainer::updateAnimationsAndScheduleFrameIfNeeded(SMILTime elapsed, bool seekToTime)
352 {
353 #if !ENABLE(OILPAN)
354     DiscardScope discardScope(m_ownerSVGElement);
355 #endif
356     SMILTime earliestFireTime = updateAnimations(elapsed, seekToTime);
357     // If updateAnimations() ended up triggering a synchronization (most likely
358     // via syncbases), then give that priority.
359     if (hasPendingSynchronization())
360         return;
361 
362     if (!isTimelineRunning())
363         return;
364 
365     if (!earliestFireTime.isFinite())
366         return;
367 
368     scheduleAnimationFrame(earliestFireTime);
369 }
370 
updateAnimations(SMILTime elapsed,bool seekToTime)371 SMILTime SMILTimeContainer::updateAnimations(SMILTime elapsed, bool seekToTime)
372 {
373     SMILTime earliestFireTime = SMILTime::unresolved();
374 
375 #ifndef NDEBUG
376     // This boolean will catch any attempts to schedule/unschedule scheduledAnimations during this critical section.
377     // Similarly, any elements removed will unschedule themselves, so this will catch modification of animationsToApply.
378     m_preventScheduledAnimationsChanges = true;
379 #endif
380 
381     if (m_documentOrderIndexesDirty)
382         updateDocumentOrderIndexes();
383 
384     WillBeHeapHashSet<ElementAttributePair> invalidKeys;
385     typedef WillBeHeapVector<RefPtrWillBeMember<SVGSMILElement> > AnimationsVector;
386     AnimationsVector animationsToApply;
387     for (GroupedAnimationsMap::iterator it = m_scheduledAnimations.begin(), end = m_scheduledAnimations.end(); it != end; ++it) {
388         if (!it->key.first || it->value->isEmpty()) {
389             invalidKeys.add(it->key);
390             continue;
391         }
392 
393         AnimationsLinkedHashSet* scheduled = it->value.get();
394 
395         // Sort according to priority. Elements with later begin time have higher priority.
396         // In case of a tie, document order decides.
397         // FIXME: This should also consider timing relationships between the elements. Dependents
398         // have higher priority.
399         AnimationsVector scheduledAnimations;
400         copyToVector(*scheduled, scheduledAnimations);
401         std::sort(scheduledAnimations.begin(), scheduledAnimations.end(), PriorityCompare(elapsed));
402 
403         SVGSMILElement* resultElement = 0;
404         for (AnimationsVector::const_iterator itAnimation = scheduledAnimations.begin(), itAnimationEnd = scheduledAnimations.end(); itAnimation != itAnimationEnd; ++itAnimation) {
405             SVGSMILElement* animation = itAnimation->get();
406             ASSERT(animation->timeContainer() == this);
407             ASSERT(animation->targetElement());
408             ASSERT(animation->hasValidAttributeName());
409 
410             // Results are accumulated to the first animation that animates and contributes to a particular element/attribute pair.
411             // FIXME: we should ensure that resultElement is of an appropriate type.
412             if (!resultElement) {
413                 if (!animation->hasValidAttributeType())
414                     continue;
415                 resultElement = animation;
416             }
417 
418             // This will calculate the contribution from the animation and add it to the resultsElement.
419             if (!animation->progress(elapsed, resultElement, seekToTime) && resultElement == animation)
420                 resultElement = 0;
421 
422             SMILTime nextFireTime = animation->nextProgressTime();
423             if (nextFireTime.isFinite())
424                 earliestFireTime = std::min(nextFireTime, earliestFireTime);
425         }
426 
427         if (resultElement)
428             animationsToApply.append(resultElement);
429     }
430     m_scheduledAnimations.removeAll(invalidKeys);
431 
432     std::sort(animationsToApply.begin(), animationsToApply.end(), PriorityCompare(elapsed));
433 
434     unsigned animationsToApplySize = animationsToApply.size();
435     if (!animationsToApplySize) {
436 #ifndef NDEBUG
437         m_preventScheduledAnimationsChanges = false;
438 #endif
439         return earliestFireTime;
440     }
441 
442     // Apply results to target elements.
443     for (unsigned i = 0; i < animationsToApplySize; ++i)
444         animationsToApply[i]->applyResultsToTarget();
445 
446 #ifndef NDEBUG
447     m_preventScheduledAnimationsChanges = false;
448 #endif
449 
450     for (unsigned i = 0; i < animationsToApplySize; ++i) {
451         if (animationsToApply[i]->inDocument() && animationsToApply[i]->isSVGDiscardElement()) {
452             RefPtrWillBeRawPtr<SVGSMILElement> animDiscard = animationsToApply[i];
453             RefPtrWillBeRawPtr<SVGElement> targetElement = animDiscard->targetElement();
454             if (targetElement && targetElement->inDocument()) {
455                 targetElement->remove(IGNORE_EXCEPTION);
456                 ASSERT(!targetElement->inDocument());
457             }
458 
459             if (animDiscard->inDocument()) {
460                 animDiscard->remove(IGNORE_EXCEPTION);
461                 ASSERT(!animDiscard->inDocument());
462             }
463         }
464     }
465     return earliestFireTime;
466 }
467 
trace(Visitor * visitor)468 void SMILTimeContainer::trace(Visitor* visitor)
469 {
470     visitor->trace(m_scheduledAnimations);
471 }
472 
473 }
474