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