Backed out changeset 58dbd2146e24 (bug 944961) for bustage.
[gecko.git] / content / smil / nsSMILAnimationController.cpp
blob9d9d1c15712c02ef3bb8c00805b0cc37f738c0ab
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 #include "nsSMILAnimationController.h"
7 #include "nsSMILCompositor.h"
8 #include "nsSMILCSSProperty.h"
9 #include "nsCSSProps.h"
10 #include "nsITimer.h"
11 #include "mozilla/dom/Element.h"
12 #include "nsIDocument.h"
13 #include "mozilla/dom/SVGAnimationElement.h"
14 #include "nsSMILTimedElement.h"
15 #include <algorithm>
16 #include "mozilla/AutoRestore.h"
18 using namespace mozilla;
19 using namespace mozilla::dom;
21 //----------------------------------------------------------------------
22 // nsSMILAnimationController implementation
24 //----------------------------------------------------------------------
25 // ctors, dtors, factory methods
27 nsSMILAnimationController::nsSMILAnimationController(nsIDocument* aDoc)
28 : mAvgTimeBetweenSamples(0),
29 mResampleNeeded(false),
30 mDeferredStartSampling(false),
31 mRunningSample(false),
32 mDocument(aDoc)
34 NS_ABORT_IF_FALSE(aDoc, "need a non-null document");
36 nsRefreshDriver* refreshDriver = GetRefreshDriver();
37 if (refreshDriver) {
38 mStartTime = refreshDriver->MostRecentRefresh();
39 } else {
40 mStartTime = mozilla::TimeStamp::Now();
42 mCurrentSampleTime = mStartTime;
44 Begin();
47 nsSMILAnimationController::~nsSMILAnimationController()
49 NS_ASSERTION(mAnimationElementTable.Count() == 0,
50 "Animation controller shouldn't be tracking any animation"
51 " elements when it dies");
54 void
55 nsSMILAnimationController::Disconnect()
57 NS_ABORT_IF_FALSE(mDocument, "disconnecting when we weren't connected...?");
58 NS_ABORT_IF_FALSE(mRefCnt.get() == 1,
59 "Expecting to disconnect when doc is sole remaining owner");
60 NS_ASSERTION(mPauseState & nsSMILTimeContainer::PAUSE_PAGEHIDE,
61 "Expecting to be paused for pagehide before disconnect");
63 StopSampling(GetRefreshDriver());
65 mDocument = nullptr; // (raw pointer)
68 //----------------------------------------------------------------------
69 // nsSMILTimeContainer methods:
71 void
72 nsSMILAnimationController::Pause(uint32_t aType)
74 nsSMILTimeContainer::Pause(aType);
76 if (mPauseState) {
77 mDeferredStartSampling = false;
78 StopSampling(GetRefreshDriver());
82 void
83 nsSMILAnimationController::Resume(uint32_t aType)
85 bool wasPaused = (mPauseState != 0);
86 // Update mCurrentSampleTime so that calls to GetParentTime--used for
87 // calculating parent offsets--are accurate
88 mCurrentSampleTime = mozilla::TimeStamp::Now();
90 nsSMILTimeContainer::Resume(aType);
92 if (wasPaused && !mPauseState && mChildContainerTable.Count()) {
93 Sample(); // Run the first sample manually
94 MaybeStartSampling(GetRefreshDriver());
98 nsSMILTime
99 nsSMILAnimationController::GetParentTime() const
101 return (nsSMILTime)(mCurrentSampleTime - mStartTime).ToMilliseconds();
104 //----------------------------------------------------------------------
105 // nsARefreshObserver methods:
106 NS_IMPL_ADDREF(nsSMILAnimationController)
107 NS_IMPL_RELEASE(nsSMILAnimationController)
109 // nsRefreshDriver Callback function
110 void
111 nsSMILAnimationController::WillRefresh(mozilla::TimeStamp aTime)
113 // Although we never expect aTime to go backwards, when we initialise the
114 // animation controller, if we can't get hold of a refresh driver we
115 // initialise mCurrentSampleTime to Now(). It may be possible that after
116 // doing so we get sampled by a refresh driver whose most recent refresh time
117 // predates when we were initialised, so to be safe we make sure to take the
118 // most recent time here.
119 aTime = std::max(mCurrentSampleTime, aTime);
121 // Sleep detection: If the time between samples is a whole lot greater than we
122 // were expecting then we assume the computer went to sleep or someone's
123 // messing with the clock. In that case, fiddle our parent offset and use our
124 // average time between samples to calculate the new sample time. This
125 // prevents us from hanging while trying to catch up on all the missed time.
127 // Smoothing of coefficient for the average function. 0.2 should let us track
128 // the sample rate reasonably tightly without being overly affected by
129 // occasional delays.
130 static const double SAMPLE_DUR_WEIGHTING = 0.2;
131 // If the elapsed time exceeds our expectation by this number of times we'll
132 // initiate special behaviour to basically ignore the intervening time.
133 static const double SAMPLE_DEV_THRESHOLD = 200.0;
135 nsSMILTime elapsedTime =
136 (nsSMILTime)(aTime - mCurrentSampleTime).ToMilliseconds();
137 if (mAvgTimeBetweenSamples == 0) {
138 // First sample.
139 mAvgTimeBetweenSamples = elapsedTime;
140 } else {
141 if (elapsedTime > SAMPLE_DEV_THRESHOLD * mAvgTimeBetweenSamples) {
142 // Unexpectedly long delay between samples.
143 NS_WARNING("Detected really long delay between samples, continuing from "
144 "previous sample");
145 mParentOffset += elapsedTime - mAvgTimeBetweenSamples;
147 // Update the moving average. Due to truncation here the average will
148 // normally be a little less than it should be but that's probably ok.
149 mAvgTimeBetweenSamples =
150 (nsSMILTime)(elapsedTime * SAMPLE_DUR_WEIGHTING +
151 mAvgTimeBetweenSamples * (1.0 - SAMPLE_DUR_WEIGHTING));
153 mCurrentSampleTime = aTime;
155 Sample();
158 //----------------------------------------------------------------------
159 // Animation element registration methods:
161 void
162 nsSMILAnimationController::RegisterAnimationElement(
163 SVGAnimationElement* aAnimationElement)
165 mAnimationElementTable.PutEntry(aAnimationElement);
166 if (mDeferredStartSampling) {
167 mDeferredStartSampling = false;
168 if (mChildContainerTable.Count()) {
169 // mAnimationElementTable was empty, but now we've added its 1st element
170 NS_ABORT_IF_FALSE(mAnimationElementTable.Count() == 1,
171 "we shouldn't have deferred sampling if we already had "
172 "animations registered");
173 StartSampling(GetRefreshDriver());
174 Sample(); // Run the first sample manually
175 } // else, don't sample until a time container is registered (via AddChild)
179 void
180 nsSMILAnimationController::UnregisterAnimationElement(
181 SVGAnimationElement* aAnimationElement)
183 mAnimationElementTable.RemoveEntry(aAnimationElement);
186 //----------------------------------------------------------------------
187 // Page show/hide
189 void
190 nsSMILAnimationController::OnPageShow()
192 Resume(nsSMILTimeContainer::PAUSE_PAGEHIDE);
195 void
196 nsSMILAnimationController::OnPageHide()
198 Pause(nsSMILTimeContainer::PAUSE_PAGEHIDE);
201 //----------------------------------------------------------------------
202 // Cycle-collection support
204 void
205 nsSMILAnimationController::Traverse(
206 nsCycleCollectionTraversalCallback* aCallback)
208 // Traverse last compositor table
209 if (mLastCompositorTable) {
210 mLastCompositorTable->EnumerateEntries(CompositorTableEntryTraverse,
211 aCallback);
215 /*static*/ PLDHashOperator
216 nsSMILAnimationController::CompositorTableEntryTraverse(
217 nsSMILCompositor* aCompositor,
218 void* aArg)
220 nsCycleCollectionTraversalCallback* cb =
221 static_cast<nsCycleCollectionTraversalCallback*>(aArg);
222 aCompositor->Traverse(cb);
223 return PL_DHASH_NEXT;
226 void
227 nsSMILAnimationController::Unlink()
229 mLastCompositorTable = nullptr;
232 //----------------------------------------------------------------------
233 // Refresh driver lifecycle related methods
235 void
236 nsSMILAnimationController::NotifyRefreshDriverCreated(
237 nsRefreshDriver* aRefreshDriver)
239 if (!mPauseState) {
240 MaybeStartSampling(aRefreshDriver);
244 void
245 nsSMILAnimationController::NotifyRefreshDriverDestroying(
246 nsRefreshDriver* aRefreshDriver)
248 if (!mPauseState && !mDeferredStartSampling) {
249 StopSampling(aRefreshDriver);
253 //----------------------------------------------------------------------
254 // Timer-related implementation helpers
256 void
257 nsSMILAnimationController::StartSampling(nsRefreshDriver* aRefreshDriver)
259 NS_ASSERTION(mPauseState == 0, "Starting sampling but controller is paused");
260 NS_ASSERTION(!mDeferredStartSampling,
261 "Started sampling but the deferred start flag is still set");
262 if (aRefreshDriver) {
263 NS_ABORT_IF_FALSE(!GetRefreshDriver() ||
264 aRefreshDriver == GetRefreshDriver(),
265 "Starting sampling with wrong refresh driver");
266 // We're effectively resuming from a pause so update our current sample time
267 // or else it will confuse our "average time between samples" calculations.
268 mCurrentSampleTime = mozilla::TimeStamp::Now();
269 aRefreshDriver->AddRefreshObserver(this, Flush_Style);
273 void
274 nsSMILAnimationController::StopSampling(nsRefreshDriver* aRefreshDriver)
276 if (aRefreshDriver) {
277 // NOTE: The document might already have been detached from its PresContext
278 // (and RefreshDriver), which would make GetRefreshDriverForDoc return null.
279 NS_ABORT_IF_FALSE(!GetRefreshDriver() ||
280 aRefreshDriver == GetRefreshDriver(),
281 "Stopping sampling with wrong refresh driver");
282 aRefreshDriver->RemoveRefreshObserver(this, Flush_Style);
286 void
287 nsSMILAnimationController::MaybeStartSampling(nsRefreshDriver* aRefreshDriver)
289 if (mDeferredStartSampling) {
290 // We've received earlier 'MaybeStartSampling' calls, and we're
291 // deferring until we get a registered animation.
292 return;
295 if (mAnimationElementTable.Count()) {
296 StartSampling(aRefreshDriver);
297 } else {
298 mDeferredStartSampling = true;
302 //----------------------------------------------------------------------
303 // Sample-related methods and callbacks
305 PLDHashOperator
306 TransferCachedBaseValue(nsSMILCompositor* aCompositor,
307 void* aData)
309 nsSMILCompositorTable* lastCompositorTable =
310 static_cast<nsSMILCompositorTable*>(aData);
311 nsSMILCompositor* lastCompositor =
312 lastCompositorTable->GetEntry(aCompositor->GetKey());
314 if (lastCompositor) {
315 aCompositor->StealCachedBaseValue(lastCompositor);
318 return PL_DHASH_NEXT;
321 PLDHashOperator
322 RemoveCompositorFromTable(nsSMILCompositor* aCompositor,
323 void* aData)
325 nsSMILCompositorTable* lastCompositorTable =
326 static_cast<nsSMILCompositorTable*>(aData);
327 lastCompositorTable->RemoveEntry(aCompositor->GetKey());
328 return PL_DHASH_NEXT;
331 PLDHashOperator
332 DoClearAnimationEffects(nsSMILCompositor* aCompositor,
333 void* /*aData*/)
335 aCompositor->ClearAnimationEffects();
336 return PL_DHASH_NEXT;
339 PLDHashOperator
340 DoComposeAttribute(nsSMILCompositor* aCompositor,
341 void* /*aData*/)
343 aCompositor->ComposeAttribute();
344 return PL_DHASH_NEXT;
347 void
348 nsSMILAnimationController::DoSample()
350 DoSample(true); // Skip unchanged time containers
353 void
354 nsSMILAnimationController::DoSample(bool aSkipUnchangedContainers)
356 if (!mDocument) {
357 NS_ERROR("Shouldn't be sampling after document has disconnected");
358 return;
360 if (mRunningSample) {
361 NS_ERROR("Shouldn't be recursively sampling");
362 return;
365 mResampleNeeded = false;
366 // Set running sample flag -- do this before flushing styles so that when we
367 // flush styles we don't end up requesting extra samples
368 AutoRestore<bool> autoRestoreRunningSample(mRunningSample);
369 mRunningSample = true;
371 // STEP 1: Bring model up to date
372 // (i) Rewind elements where necessary
373 // (ii) Run milestone samples
374 RewindElements();
375 DoMilestoneSamples();
377 // STEP 2: Sample the child time containers
379 // When we sample the child time containers they will simply record the sample
380 // time in document time.
381 TimeContainerHashtable activeContainers(mChildContainerTable.Count());
382 SampleTimeContainerParams tcParams = { &activeContainers,
383 aSkipUnchangedContainers };
384 mChildContainerTable.EnumerateEntries(SampleTimeContainer, &tcParams);
386 // STEP 3: (i) Sample the timed elements AND
387 // (ii) Create a table of compositors
389 // (i) Here we sample the timed elements (fetched from the
390 // SVGAnimationElements) which determine from the active time if the
391 // element is active and what its simple time etc. is. This information is
392 // then passed to its time client (nsSMILAnimationFunction).
394 // (ii) During the same loop we also build up a table that contains one
395 // compositor for each animated attribute and which maps animated elements to
396 // the corresponding compositor for their target attribute.
398 // Note that this compositor table needs to be allocated on the heap so we can
399 // store it until the next sample. This lets us find out which elements were
400 // animated in sample 'n-1' but not in sample 'n' (and hence need to have
401 // their animation effects removed in sample 'n').
403 // Parts (i) and (ii) are not functionally related but we combine them here to
404 // save iterating over the animation elements twice.
406 // Create the compositor table
407 nsAutoPtr<nsSMILCompositorTable>
408 currentCompositorTable(new nsSMILCompositorTable(0));
410 SampleAnimationParams saParams = { &activeContainers,
411 currentCompositorTable };
412 mAnimationElementTable.EnumerateEntries(SampleAnimation,
413 &saParams);
414 activeContainers.Clear();
416 // STEP 4: Compare previous sample's compositors against this sample's.
417 // (Transfer cached base values across, & remove animation effects from
418 // no-longer-animated targets.)
419 if (mLastCompositorTable) {
420 // * Transfer over cached base values, from last sample's compositors
421 currentCompositorTable->EnumerateEntries(TransferCachedBaseValue,
422 mLastCompositorTable);
424 // * For each compositor in current sample's hash table, remove entry from
425 // prev sample's hash table -- we don't need to clear animation
426 // effects of those compositors, since they're still being animated.
427 currentCompositorTable->EnumerateEntries(RemoveCompositorFromTable,
428 mLastCompositorTable);
430 // * For each entry that remains in prev sample's hash table (i.e. for
431 // every target that's no longer animated), clear animation effects.
432 mLastCompositorTable->EnumerateEntries(DoClearAnimationEffects, nullptr);
435 // return early if there are no active animations to avoid a style flush
436 if (currentCompositorTable->Count() == 0) {
437 mLastCompositorTable = nullptr;
438 return;
441 nsCOMPtr<nsIDocument> kungFuDeathGrip(mDocument); // keeps 'this' alive too
442 mDocument->FlushPendingNotifications(Flush_Style);
444 // WARNING:
445 // WARNING: the above flush may have destroyed the pres shell and/or
446 // WARNING: frames and other layout related objects.
447 // WARNING:
449 // STEP 5: Compose currently-animated attributes.
450 // XXXdholbert: This step traverses our animation targets in an effectively
451 // random order. For animation from/to 'inherit' values to work correctly
452 // when the inherited value is *also* being animated, we really should be
453 // traversing our animated nodes in an ancestors-first order (bug 501183)
454 currentCompositorTable->EnumerateEntries(DoComposeAttribute, nullptr);
456 // Update last compositor table
457 mLastCompositorTable = currentCompositorTable.forget();
459 NS_ASSERTION(!mResampleNeeded, "Resample dirty flag set during sample!");
462 void
463 nsSMILAnimationController::RewindElements()
465 bool rewindNeeded = false;
466 mChildContainerTable.EnumerateEntries(RewindNeeded, &rewindNeeded);
467 if (!rewindNeeded)
468 return;
470 mAnimationElementTable.EnumerateEntries(RewindAnimation, nullptr);
471 mChildContainerTable.EnumerateEntries(ClearRewindNeeded, nullptr);
474 /*static*/ PLDHashOperator
475 nsSMILAnimationController::RewindNeeded(TimeContainerPtrKey* aKey,
476 void* aData)
478 NS_ABORT_IF_FALSE(aData,
479 "Null data pointer during time container enumeration");
480 bool* rewindNeeded = static_cast<bool*>(aData);
482 nsSMILTimeContainer* container = aKey->GetKey();
483 if (container->NeedsRewind()) {
484 *rewindNeeded = true;
485 return PL_DHASH_STOP;
488 return PL_DHASH_NEXT;
491 /*static*/ PLDHashOperator
492 nsSMILAnimationController::RewindAnimation(AnimationElementPtrKey* aKey,
493 void* aData)
495 SVGAnimationElement* animElem = aKey->GetKey();
496 nsSMILTimeContainer* timeContainer = animElem->GetTimeContainer();
497 if (timeContainer && timeContainer->NeedsRewind()) {
498 animElem->TimedElement().Rewind();
501 return PL_DHASH_NEXT;
504 /*static*/ PLDHashOperator
505 nsSMILAnimationController::ClearRewindNeeded(TimeContainerPtrKey* aKey,
506 void* aData)
508 aKey->GetKey()->ClearNeedsRewind();
509 return PL_DHASH_NEXT;
512 void
513 nsSMILAnimationController::DoMilestoneSamples()
515 // We need to sample the timing model but because SMIL operates independently
516 // of the frame-rate, we can get one sample at t=0s and the next at t=10min.
518 // In between those two sample times a whole string of significant events
519 // might be expected to take place: events firing, new interdependencies
520 // between animations resolved and dissolved, etc.
522 // Furthermore, at any given time, we want to sample all the intervals that
523 // end at that time BEFORE any that begin. This behaviour is implied by SMIL's
524 // endpoint-exclusive timing model.
526 // So we have the animations (specifically the timed elements) register the
527 // next significant moment (called a milestone) in their lifetime and then we
528 // step through the model at each of these moments and sample those animations
529 // registered for those times. This way events can fire in the correct order,
530 // dependencies can be resolved etc.
532 nsSMILTime sampleTime = INT64_MIN;
534 while (true) {
535 // We want to find any milestones AT OR BEFORE the current sample time so we
536 // initialise the next milestone to the moment after (1ms after, to be
537 // precise) the current sample time and see if there are any milestones
538 // before that. Any other milestones will be dealt with in a subsequent
539 // sample.
540 nsSMILMilestone nextMilestone(GetCurrentTime() + 1, true);
541 mChildContainerTable.EnumerateEntries(GetNextMilestone, &nextMilestone);
543 if (nextMilestone.mTime > GetCurrentTime()) {
544 break;
547 GetMilestoneElementsParams params;
548 params.mMilestone = nextMilestone;
549 mChildContainerTable.EnumerateEntries(GetMilestoneElements, &params);
550 uint32_t length = params.mElements.Length();
552 // During the course of a sampling we don't want to actually go backwards.
553 // Due to negative offsets, early ends and the like, a timed element might
554 // register a milestone that is actually in the past. That's fine, but it's
555 // still only going to get *sampled* with whatever time we're up to and no
556 // earlier.
558 // Because we're only performing this clamping at the last moment, the
559 // animations will still all get sampled in the correct order and
560 // dependencies will be appropriately resolved.
561 sampleTime = std::max(nextMilestone.mTime, sampleTime);
563 for (uint32_t i = 0; i < length; ++i) {
564 SVGAnimationElement* elem = params.mElements[i].get();
565 NS_ABORT_IF_FALSE(elem, "nullptr animation element in list");
566 nsSMILTimeContainer* container = elem->GetTimeContainer();
567 if (!container)
568 // The container may be nullptr if the element has been detached from its
569 // parent since registering a milestone.
570 continue;
572 nsSMILTimeValue containerTimeValue =
573 container->ParentToContainerTime(sampleTime);
574 if (!containerTimeValue.IsDefinite())
575 continue;
577 // Clamp the converted container time to non-negative values.
578 nsSMILTime containerTime = std::max<nsSMILTime>(0, containerTimeValue.GetMillis());
580 if (nextMilestone.mIsEnd) {
581 elem->TimedElement().SampleEndAt(containerTime);
582 } else {
583 elem->TimedElement().SampleAt(containerTime);
589 /*static*/ PLDHashOperator
590 nsSMILAnimationController::GetNextMilestone(TimeContainerPtrKey* aKey,
591 void* aData)
593 NS_ABORT_IF_FALSE(aKey, "Null hash key for time container hash table");
594 NS_ABORT_IF_FALSE(aKey->GetKey(), "Null time container key in hash table");
595 NS_ABORT_IF_FALSE(aData,
596 "Null data pointer during time container enumeration");
598 nsSMILMilestone* nextMilestone = static_cast<nsSMILMilestone*>(aData);
600 nsSMILTimeContainer* container = aKey->GetKey();
601 if (container->IsPausedByType(nsSMILTimeContainer::PAUSE_BEGIN))
602 return PL_DHASH_NEXT;
604 nsSMILMilestone thisMilestone;
605 bool didGetMilestone =
606 container->GetNextMilestoneInParentTime(thisMilestone);
607 if (didGetMilestone && thisMilestone < *nextMilestone) {
608 *nextMilestone = thisMilestone;
611 return PL_DHASH_NEXT;
614 /*static*/ PLDHashOperator
615 nsSMILAnimationController::GetMilestoneElements(TimeContainerPtrKey* aKey,
616 void* aData)
618 NS_ABORT_IF_FALSE(aKey, "Null hash key for time container hash table");
619 NS_ABORT_IF_FALSE(aKey->GetKey(), "Null time container key in hash table");
620 NS_ABORT_IF_FALSE(aData,
621 "Null data pointer during time container enumeration");
623 GetMilestoneElementsParams* params =
624 static_cast<GetMilestoneElementsParams*>(aData);
626 nsSMILTimeContainer* container = aKey->GetKey();
627 if (container->IsPausedByType(nsSMILTimeContainer::PAUSE_BEGIN))
628 return PL_DHASH_NEXT;
630 container->PopMilestoneElementsAtMilestone(params->mMilestone,
631 params->mElements);
633 return PL_DHASH_NEXT;
636 /*static*/ PLDHashOperator
637 nsSMILAnimationController::SampleTimeContainer(TimeContainerPtrKey* aKey,
638 void* aData)
640 NS_ENSURE_TRUE(aKey, PL_DHASH_NEXT);
641 NS_ENSURE_TRUE(aKey->GetKey(), PL_DHASH_NEXT);
642 NS_ENSURE_TRUE(aData, PL_DHASH_NEXT);
644 SampleTimeContainerParams* params =
645 static_cast<SampleTimeContainerParams*>(aData);
647 nsSMILTimeContainer* container = aKey->GetKey();
648 if (!container->IsPausedByType(nsSMILTimeContainer::PAUSE_BEGIN) &&
649 (container->NeedsSample() || !params->mSkipUnchangedContainers)) {
650 container->ClearMilestones();
651 container->Sample();
652 container->MarkSeekFinished();
653 params->mActiveContainers->PutEntry(container);
656 return PL_DHASH_NEXT;
659 /*static*/ PLDHashOperator
660 nsSMILAnimationController::SampleAnimation(AnimationElementPtrKey* aKey,
661 void* aData)
663 NS_ENSURE_TRUE(aKey, PL_DHASH_NEXT);
664 NS_ENSURE_TRUE(aKey->GetKey(), PL_DHASH_NEXT);
665 NS_ENSURE_TRUE(aData, PL_DHASH_NEXT);
667 SVGAnimationElement* animElem = aKey->GetKey();
668 if (animElem->PassesConditionalProcessingTests()) {
669 SampleAnimationParams* params = static_cast<SampleAnimationParams*>(aData);
671 SampleTimedElement(animElem, params->mActiveContainers);
672 AddAnimationToCompositorTable(animElem, params->mCompositorTable);
675 return PL_DHASH_NEXT;
678 /*static*/ void
679 nsSMILAnimationController::SampleTimedElement(
680 SVGAnimationElement* aElement, TimeContainerHashtable* aActiveContainers)
682 nsSMILTimeContainer* timeContainer = aElement->GetTimeContainer();
683 if (!timeContainer)
684 return;
686 // We'd like to call timeContainer->NeedsSample() here and skip all timed
687 // elements that belong to paused time containers that don't need a sample,
688 // but that doesn't work because we've already called Sample() on all the time
689 // containers so the paused ones don't need a sample any more and they'll
690 // return false.
692 // Instead we build up a hashmap of active time containers during the previous
693 // step (SampleTimeContainer) and then test here if the container for this
694 // timed element is in the list.
695 if (!aActiveContainers->GetEntry(timeContainer))
696 return;
698 nsSMILTime containerTime = timeContainer->GetCurrentTime();
700 NS_ABORT_IF_FALSE(!timeContainer->IsSeeking(),
701 "Doing a regular sample but the time container is still seeking");
702 aElement->TimedElement().SampleAt(containerTime);
705 /*static*/ void
706 nsSMILAnimationController::AddAnimationToCompositorTable(
707 SVGAnimationElement* aElement, nsSMILCompositorTable* aCompositorTable)
709 // Add a compositor to the hash table if there's not already one there
710 nsSMILTargetIdentifier key;
711 if (!GetTargetIdentifierForAnimation(aElement, key))
712 // Something's wrong/missing about animation's target; skip this animation
713 return;
715 nsSMILAnimationFunction& func = aElement->AnimationFunction();
717 // Only add active animation functions. If there are no active animations
718 // targeting an attribute, no compositor will be created and any previously
719 // applied animations will be cleared.
720 if (func.IsActiveOrFrozen()) {
721 // Look up the compositor for our target, & add our animation function
722 // to its list of animation functions.
723 nsSMILCompositor* result = aCompositorTable->PutEntry(key);
724 result->AddAnimationFunction(&func);
726 } else if (func.HasChanged()) {
727 // Look up the compositor for our target, and force it to skip the
728 // "nothing's changed so don't bother compositing" optimization for this
729 // sample. |func| is inactive, but it's probably *newly* inactive (since
730 // it's got HasChanged() == true), so we need to make sure to recompose
731 // its target.
732 nsSMILCompositor* result = aCompositorTable->PutEntry(key);
733 result->ToggleForceCompositing();
735 // We've now made sure that |func|'s inactivity will be reflected as of
736 // this sample. We need to clear its HasChanged() flag so that it won't
737 // trigger this same clause in future samples (until it changes again).
738 func.ClearHasChanged();
742 static inline bool
743 IsTransformAttribute(int32_t aNamespaceID, nsIAtom *aAttributeName)
745 return aNamespaceID == kNameSpaceID_None &&
746 (aAttributeName == nsGkAtoms::transform ||
747 aAttributeName == nsGkAtoms::patternTransform ||
748 aAttributeName == nsGkAtoms::gradientTransform);
751 // Helper function that, given a SVGAnimationElement, looks up its target
752 // element & target attribute and populates a nsSMILTargetIdentifier
753 // for this target.
754 /*static*/ bool
755 nsSMILAnimationController::GetTargetIdentifierForAnimation(
756 SVGAnimationElement* aAnimElem, nsSMILTargetIdentifier& aResult)
758 // Look up target (animated) element
759 Element* targetElem = aAnimElem->GetTargetElementContent();
760 if (!targetElem)
761 // Animation has no target elem -- skip it.
762 return false;
764 // Look up target (animated) attribute
765 // SMILANIM section 3.1, attributeName may
766 // have an XMLNS prefix to indicate the XML namespace.
767 nsCOMPtr<nsIAtom> attributeName;
768 int32_t attributeNamespaceID;
769 if (!aAnimElem->GetTargetAttributeName(&attributeNamespaceID,
770 getter_AddRefs(attributeName)))
771 // Animation has no target attr -- skip it.
772 return false;
774 // animateTransform can only animate transforms, conversely transforms
775 // can only be animated by animateTransform
776 if (IsTransformAttribute(attributeNamespaceID, attributeName) !=
777 (aAnimElem->Tag() == nsGkAtoms::animateTransform))
778 return false;
780 // Look up target (animated) attribute-type
781 nsSMILTargetAttrType attributeType = aAnimElem->GetTargetAttributeType();
783 // Check if an 'auto' attributeType refers to a CSS property or XML attribute.
784 // Note that SMIL requires we search for CSS properties first. So if they
785 // overlap, 'auto' = 'CSS'. (SMILANIM 3.1)
786 bool isCSS = false;
787 if (attributeType == eSMILTargetAttrType_auto) {
788 if (attributeNamespaceID == kNameSpaceID_None) {
789 // width/height are special as they may be attributes or for
790 // outer-<svg> elements, mapped into style.
791 if (attributeName == nsGkAtoms::width ||
792 attributeName == nsGkAtoms::height) {
793 isCSS = targetElem->GetNameSpaceID() != kNameSpaceID_SVG;
794 } else {
795 nsCSSProperty prop =
796 nsCSSProps::LookupProperty(nsDependentAtomString(attributeName),
797 nsCSSProps::eEnabled);
798 isCSS = nsSMILCSSProperty::IsPropertyAnimatable(prop);
801 } else {
802 isCSS = (attributeType == eSMILTargetAttrType_CSS);
805 // Construct the key
806 aResult.mElement = targetElem;
807 aResult.mAttributeName = attributeName;
808 aResult.mAttributeNamespaceID = attributeNamespaceID;
809 aResult.mIsCSS = isCSS;
811 return true;
814 //----------------------------------------------------------------------
815 // Add/remove child time containers
817 nsresult
818 nsSMILAnimationController::AddChild(nsSMILTimeContainer& aChild)
820 TimeContainerPtrKey* key = mChildContainerTable.PutEntry(&aChild);
821 NS_ENSURE_TRUE(key, NS_ERROR_OUT_OF_MEMORY);
823 if (!mPauseState && mChildContainerTable.Count() == 1) {
824 MaybeStartSampling(GetRefreshDriver());
825 Sample(); // Run the first sample manually
828 return NS_OK;
831 void
832 nsSMILAnimationController::RemoveChild(nsSMILTimeContainer& aChild)
834 mChildContainerTable.RemoveEntry(&aChild);
836 if (!mPauseState && mChildContainerTable.Count() == 0) {
837 StopSampling(GetRefreshDriver());
841 // Helper method
842 nsRefreshDriver*
843 nsSMILAnimationController::GetRefreshDriver()
845 if (!mDocument) {
846 NS_ERROR("Requesting refresh driver after document has disconnected!");
847 return nullptr;
850 nsIPresShell* shell = mDocument->GetShell();
851 if (!shell) {
852 return nullptr;
855 nsPresContext* context = shell->GetPresContext();
856 return context ? context->RefreshDriver() : nullptr;
859 void
860 nsSMILAnimationController::FlagDocumentNeedsFlush()
862 mDocument->SetNeedStyleFlush();