1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "SMILAnimationController.h"
11 #include "mozilla/AutoRestore.h"
12 #include "mozilla/PresShell.h"
13 #include "mozilla/PresShellInlines.h"
14 #include "mozilla/RestyleManager.h"
15 #include "mozilla/SMILTimedElement.h"
16 #include "mozilla/dom/DocumentInlines.h"
17 #include "mozilla/dom/Element.h"
18 #include "mozilla/dom/SVGAnimationElement.h"
19 #include "nsContentUtils.h"
20 #include "nsCSSProps.h"
21 #include "nsRefreshDriver.h"
22 #include "mozilla/dom/Document.h"
23 #include "SMILCompositor.h"
24 #include "SMILCSSProperty.h"
26 using namespace mozilla::dom
;
30 //----------------------------------------------------------------------
31 // SMILAnimationController implementation
33 //----------------------------------------------------------------------
34 // ctors, dtors, factory methods
36 SMILAnimationController::SMILAnimationController(Document
* aDoc
)
38 MOZ_ASSERT(aDoc
, "need a non-null document");
40 if (nsRefreshDriver
* refreshDriver
= GetRefreshDriver()) {
41 mStartTime
= refreshDriver
->MostRecentRefresh();
43 mStartTime
= mozilla::TimeStamp::Now();
45 mCurrentSampleTime
= mStartTime
;
50 SMILAnimationController::~SMILAnimationController() {
51 NS_ASSERTION(mAnimationElementTable
.IsEmpty(),
52 "Animation controller shouldn't be tracking any animation"
53 " elements when it dies");
54 MOZ_RELEASE_ASSERT(!mRegisteredWithRefreshDriver
,
55 "Leaving stale entry in refresh driver's observer list");
58 void SMILAnimationController::Disconnect() {
59 MOZ_ASSERT(mDocument
, "disconnecting when we weren't connected...?");
60 MOZ_ASSERT(mRefCnt
.get() == 1,
61 "Expecting to disconnect when doc is sole remaining owner");
62 NS_ASSERTION(mPauseState
& SMILTimeContainer::PAUSE_PAGEHIDE
,
63 "Expecting to be paused for pagehide before disconnect");
65 StopSampling(GetRefreshDriver());
67 mDocument
= nullptr; // (raw pointer)
70 //----------------------------------------------------------------------
71 // SMILTimeContainer methods:
73 void SMILAnimationController::Pause(uint32_t aType
) {
74 SMILTimeContainer::Pause(aType
);
78 void SMILAnimationController::Resume(uint32_t aType
) {
79 bool wasPaused
= !!mPauseState
;
80 // Update mCurrentSampleTime so that calls to GetParentTime--used for
81 // calculating parent offsets--are accurate
82 mCurrentSampleTime
= mozilla::TimeStamp::Now();
84 SMILTimeContainer::Resume(aType
);
86 if (wasPaused
&& !mPauseState
) {
91 SMILTime
SMILAnimationController::GetParentTime() const {
92 return (SMILTime
)(mCurrentSampleTime
- mStartTime
).ToMilliseconds();
95 //----------------------------------------------------------------------
96 // nsARefreshObserver methods:
97 NS_IMPL_ADDREF(SMILAnimationController
)
98 NS_IMPL_RELEASE(SMILAnimationController
)
100 // nsRefreshDriver Callback function
101 void SMILAnimationController::WillRefresh(mozilla::TimeStamp aTime
) {
102 // Although we never expect aTime to go backwards, when we initialise the
103 // animation controller, if we can't get hold of a refresh driver we
104 // initialise mCurrentSampleTime to Now(). It may be possible that after
105 // doing so we get sampled by a refresh driver whose most recent refresh time
106 // predates when we were initialised, so to be safe we make sure to take the
107 // most recent time here.
108 aTime
= std::max(mCurrentSampleTime
, aTime
);
110 // Sleep detection: If the time between samples is a whole lot greater than we
111 // were expecting then we assume the computer went to sleep or someone's
112 // messing with the clock. In that case, fiddle our parent offset and use our
113 // average time between samples to calculate the new sample time. This
114 // prevents us from hanging while trying to catch up on all the missed time.
116 // Smoothing of coefficient for the average function. 0.2 should let us track
117 // the sample rate reasonably tightly without being overly affected by
118 // occasional delays.
119 static const double SAMPLE_DUR_WEIGHTING
= 0.2;
120 // If the elapsed time exceeds our expectation by this number of times we'll
121 // initiate special behaviour to basically ignore the intervening time.
122 static const double SAMPLE_DEV_THRESHOLD
= 200.0;
124 SMILTime elapsedTime
=
125 (SMILTime
)(aTime
- mCurrentSampleTime
).ToMilliseconds();
126 if (mAvgTimeBetweenSamples
== 0) {
128 mAvgTimeBetweenSamples
= elapsedTime
;
130 if (elapsedTime
> SAMPLE_DEV_THRESHOLD
* mAvgTimeBetweenSamples
) {
131 // Unexpectedly long delay between samples.
133 "Detected really long delay between samples, continuing from "
135 mParentOffset
+= elapsedTime
- mAvgTimeBetweenSamples
;
137 // Update the moving average. Due to truncation here the average will
138 // normally be a little less than it should be but that's probably ok.
139 mAvgTimeBetweenSamples
=
140 (SMILTime
)(elapsedTime
* SAMPLE_DUR_WEIGHTING
+
141 mAvgTimeBetweenSamples
* (1.0 - SAMPLE_DUR_WEIGHTING
));
143 mCurrentSampleTime
= aTime
;
148 //----------------------------------------------------------------------
149 // Animation element registration methods:
151 void SMILAnimationController::RegisterAnimationElement(
152 SVGAnimationElement
* aAnimationElement
) {
153 const bool wasEmpty
= mAnimationElementTable
.IsEmpty();
154 mAnimationElementTable
.PutEntry(aAnimationElement
);
160 void SMILAnimationController::UnregisterAnimationElement(
161 SVGAnimationElement
* aAnimationElement
) {
162 mAnimationElementTable
.RemoveEntry(aAnimationElement
);
163 if (mAnimationElementTable
.IsEmpty()) {
168 //----------------------------------------------------------------------
171 void SMILAnimationController::OnPageShow() {
172 Resume(SMILTimeContainer::PAUSE_PAGEHIDE
);
175 void SMILAnimationController::OnPageHide() {
176 Pause(SMILTimeContainer::PAUSE_PAGEHIDE
);
179 //----------------------------------------------------------------------
180 // Cycle-collection support
182 void SMILAnimationController::Traverse(
183 nsCycleCollectionTraversalCallback
* aCallback
) {
184 // Traverse last compositor table
185 if (mLastCompositorTable
) {
186 for (SMILCompositor
& compositor
: *mLastCompositorTable
) {
187 compositor
.Traverse(aCallback
);
192 void SMILAnimationController::Unlink() { mLastCompositorTable
= nullptr; }
194 //----------------------------------------------------------------------
195 // Refresh driver lifecycle related methods
197 void SMILAnimationController::NotifyRefreshDriverCreated(
198 nsRefreshDriver
* aRefreshDriver
) {
202 void SMILAnimationController::NotifyRefreshDriverDestroying(
203 nsRefreshDriver
* aRefreshDriver
) {
204 StopSampling(aRefreshDriver
);
207 //----------------------------------------------------------------------
208 // Timer-related implementation helpers
210 bool SMILAnimationController::ShouldSample() const {
211 return !mPauseState
&& !mAnimationElementTable
.IsEmpty() &&
212 !mChildContainerTable
.IsEmpty();
215 void SMILAnimationController::UpdateSampling() {
216 const bool shouldSample
= ShouldSample();
217 const bool isSampling
= mRegisteredWithRefreshDriver
;
218 if (shouldSample
== isSampling
) {
222 nsRefreshDriver
* driver
= GetRefreshDriver();
228 // We're effectively resuming from a pause so update our current sample time
229 // or else it will confuse our "average time between samples" calculations.
230 mCurrentSampleTime
= mozilla::TimeStamp::Now();
231 driver
->AddRefreshObserver(this, FlushType::Style
, "SMIL animations");
232 mRegisteredWithRefreshDriver
= true;
233 Sample(); // Run the first sample manually.
235 StopSampling(driver
);
239 void SMILAnimationController::StopSampling(nsRefreshDriver
* aRefreshDriver
) {
240 if (aRefreshDriver
&& mRegisteredWithRefreshDriver
) {
241 // NOTE: The document might already have been detached from its PresContext
242 // (and RefreshDriver), which would make GetRefreshDriver() return null.
243 MOZ_ASSERT(!GetRefreshDriver() || aRefreshDriver
== GetRefreshDriver(),
244 "Stopping sampling with wrong refresh driver");
245 aRefreshDriver
->RemoveRefreshObserver(this, FlushType::Style
);
246 mRegisteredWithRefreshDriver
= false;
250 //----------------------------------------------------------------------
251 // Sample-related methods and callbacks
253 void SMILAnimationController::DoSample() {
254 DoSample(true); // Skip unchanged time containers
257 void SMILAnimationController::DoSample(bool aSkipUnchangedContainers
) {
259 NS_ERROR("Shouldn't be sampling after document has disconnected");
262 if (mRunningSample
) {
263 NS_ERROR("Shouldn't be recursively sampling");
267 bool isStyleFlushNeeded
= mResampleNeeded
;
268 mResampleNeeded
= false;
270 nsCOMPtr
<Document
> document(mDocument
); // keeps 'this' alive too
272 // Set running sample flag -- do this before flushing styles so that when we
273 // flush styles we don't end up requesting extra samples
274 AutoRestore
<bool> autoRestoreRunningSample(mRunningSample
);
275 mRunningSample
= true;
277 // STEP 1: Bring model up to date
278 // (i) Rewind elements where necessary
279 // (ii) Run milestone samples
281 DoMilestoneSamples();
283 // STEP 2: Sample the child time containers
285 // When we sample the child time containers they will simply record the sample
286 // time in document time.
287 TimeContainerHashtable
activeContainers(mChildContainerTable
.Count());
288 for (SMILTimeContainer
* container
: mChildContainerTable
.Keys()) {
293 if (!container
->IsPausedByType(SMILTimeContainer::PAUSE_BEGIN
) &&
294 (container
->NeedsSample() || !aSkipUnchangedContainers
)) {
295 container
->ClearMilestones();
297 container
->MarkSeekFinished();
298 activeContainers
.PutEntry(container
);
302 // STEP 3: (i) Sample the timed elements AND
303 // (ii) Create a table of compositors
305 // (i) Here we sample the timed elements (fetched from the
306 // SVGAnimationElements) which determine from the active time if the
307 // element is active and what its simple time etc. is. This information is
308 // then passed to its time client (SMILAnimationFunction).
310 // (ii) During the same loop we also build up a table that contains one
311 // compositor for each animated attribute and which maps animated elements to
312 // the corresponding compositor for their target attribute.
314 // Note that this compositor table needs to be allocated on the heap so we can
315 // store it until the next sample. This lets us find out which elements were
316 // animated in sample 'n-1' but not in sample 'n' (and hence need to have
317 // their animation effects removed in sample 'n').
319 // Parts (i) and (ii) are not functionally related but we combine them here to
320 // save iterating over the animation elements twice.
322 // Create the compositor table
323 UniquePtr
<SMILCompositorTable
> currentCompositorTable(
324 new SMILCompositorTable(0));
325 nsTArray
<RefPtr
<SVGAnimationElement
>> animElems(
326 mAnimationElementTable
.Count());
328 for (SVGAnimationElement
* animElem
: mAnimationElementTable
.Keys()) {
329 SampleTimedElement(animElem
, &activeContainers
);
330 AddAnimationToCompositorTable(animElem
, currentCompositorTable
.get(),
332 animElems
.AppendElement(animElem
);
334 activeContainers
.Clear();
336 // STEP 4: Compare previous sample's compositors against this sample's.
337 // (Transfer cached base values across, & remove animation effects from
338 // no-longer-animated targets.)
339 if (mLastCompositorTable
) {
340 // * Transfer over cached base values, from last sample's compositors
341 for (SMILCompositor
& compositor
: *currentCompositorTable
) {
342 SMILCompositor
* lastCompositor
=
343 mLastCompositorTable
->GetEntry(compositor
.GetKey());
345 if (lastCompositor
) {
346 compositor
.StealCachedBaseValue(lastCompositor
);
347 if (!lastCompositor
->HasSameNumberOfAnimationFunctionsAs(compositor
)) {
348 // If we have multiple animations on the same element, they share a
349 // compositor. If an active animation ends, it will no longer be in
350 // the compositor table. We need to force compositing to ensure we
351 // render the element with any remaining frozen animations even though
352 // they would not normally trigger compositing.
353 compositor
.ToggleForceCompositing();
358 // * For each compositor in current sample's hash table, remove entry from
359 // prev sample's hash table -- we don't need to clear animation
360 // effects of those compositors, since they're still being animated.
361 for (const auto& key
: currentCompositorTable
->Keys()) {
362 mLastCompositorTable
->RemoveEntry(key
);
365 // * For each entry that remains in prev sample's hash table (i.e. for
366 // every target that's no longer animated), clear animation effects.
367 for (SMILCompositor
& compositor
: *mLastCompositorTable
) {
368 compositor
.ClearAnimationEffects();
372 // return early if there are no active animations to avoid a style flush
373 if (currentCompositorTable
->IsEmpty()) {
374 mLastCompositorTable
= nullptr;
378 if (isStyleFlushNeeded
) {
379 document
->FlushPendingNotifications(FlushType::Style
);
383 // WARNING: the above flush may have destroyed the pres shell and/or
384 // WARNING: frames and other layout related objects.
387 // STEP 5: Compose currently-animated attributes.
388 // XXXdholbert: This step traverses our animation targets in an effectively
389 // random order. For animation from/to 'inherit' values to work correctly
390 // when the inherited value is *also* being animated, we really should be
391 // traversing our animated nodes in an ancestors-first order (bug 501183)
392 bool mightHavePendingStyleUpdates
= false;
393 for (auto& compositor
: *currentCompositorTable
) {
394 compositor
.ComposeAttribute(mightHavePendingStyleUpdates
);
397 // Update last compositor table
398 mLastCompositorTable
= std::move(currentCompositorTable
);
399 mMightHavePendingStyleUpdates
= mightHavePendingStyleUpdates
;
401 NS_ASSERTION(!mResampleNeeded
, "Resample dirty flag set during sample!");
404 void SMILAnimationController::RewindElements() {
405 const bool rewindNeeded
= std::any_of(
406 mChildContainerTable
.Keys().cbegin(), mChildContainerTable
.Keys().cend(),
407 [](SMILTimeContainer
* container
) { return container
->NeedsRewind(); });
409 if (!rewindNeeded
) return;
411 for (SVGAnimationElement
* animElem
: mAnimationElementTable
.Keys()) {
412 SMILTimeContainer
* timeContainer
= animElem
->GetTimeContainer();
413 if (timeContainer
&& timeContainer
->NeedsRewind()) {
414 animElem
->TimedElement().Rewind();
418 for (SMILTimeContainer
* container
: mChildContainerTable
.Keys()) {
419 container
->ClearNeedsRewind();
423 void SMILAnimationController::DoMilestoneSamples() {
424 // We need to sample the timing model but because SMIL operates independently
425 // of the frame-rate, we can get one sample at t=0s and the next at t=10min.
427 // In between those two sample times a whole string of significant events
428 // might be expected to take place: events firing, new interdependencies
429 // between animations resolved and dissolved, etc.
431 // Furthermore, at any given time, we want to sample all the intervals that
432 // end at that time BEFORE any that begin. This behaviour is implied by SMIL's
433 // endpoint-exclusive timing model.
435 // So we have the animations (specifically the timed elements) register the
436 // next significant moment (called a milestone) in their lifetime and then we
437 // step through the model at each of these moments and sample those animations
438 // registered for those times. This way events can fire in the correct order,
439 // dependencies can be resolved etc.
441 SMILTime sampleTime
= INT64_MIN
;
444 // We want to find any milestones AT OR BEFORE the current sample time so we
445 // initialise the next milestone to the moment after (1ms after, to be
446 // precise) the current sample time and see if there are any milestones
447 // before that. Any other milestones will be dealt with in a subsequent
449 SMILMilestone
nextMilestone(GetCurrentTimeAsSMILTime() + 1, true);
450 for (SMILTimeContainer
* container
: mChildContainerTable
.Keys()) {
451 if (container
->IsPausedByType(SMILTimeContainer::PAUSE_BEGIN
)) {
454 SMILMilestone thisMilestone
;
455 bool didGetMilestone
=
456 container
->GetNextMilestoneInParentTime(thisMilestone
);
457 if (didGetMilestone
&& thisMilestone
< nextMilestone
) {
458 nextMilestone
= thisMilestone
;
462 if (nextMilestone
.mTime
> GetCurrentTimeAsSMILTime()) {
466 nsTArray
<RefPtr
<dom::SVGAnimationElement
>> elements
;
467 for (SMILTimeContainer
* container
: mChildContainerTable
.Keys()) {
468 if (container
->IsPausedByType(SMILTimeContainer::PAUSE_BEGIN
)) {
471 container
->PopMilestoneElementsAtMilestone(nextMilestone
, elements
);
474 // During the course of a sampling we don't want to actually go backwards.
475 // Due to negative offsets, early ends and the like, a timed element might
476 // register a milestone that is actually in the past. That's fine, but it's
477 // still only going to get *sampled* with whatever time we're up to and no
480 // Because we're only performing this clamping at the last moment, the
481 // animations will still all get sampled in the correct order and
482 // dependencies will be appropriately resolved.
483 sampleTime
= std::max(nextMilestone
.mTime
, sampleTime
);
485 for (RefPtr
<dom::SVGAnimationElement
>& elem
: elements
) {
486 MOZ_ASSERT(elem
, "nullptr animation element in list");
487 SMILTimeContainer
* container
= elem
->GetTimeContainer();
489 // The container may be nullptr if the element has been detached from
490 // its parent since registering a milestone.
493 SMILTimeValue containerTimeValue
=
494 container
->ParentToContainerTime(sampleTime
);
495 if (!containerTimeValue
.IsDefinite()) continue;
497 // Clamp the converted container time to non-negative values.
498 SMILTime containerTime
=
499 std::max
<SMILTime
>(0, containerTimeValue
.GetMillis());
501 if (nextMilestone
.mIsEnd
) {
502 elem
->TimedElement().SampleEndAt(containerTime
);
504 elem
->TimedElement().SampleAt(containerTime
);
511 void SMILAnimationController::SampleTimedElement(
512 SVGAnimationElement
* aElement
, TimeContainerHashtable
* aActiveContainers
) {
513 SMILTimeContainer
* timeContainer
= aElement
->GetTimeContainer();
514 if (!timeContainer
) return;
516 // We'd like to call timeContainer->NeedsSample() here and skip all timed
517 // elements that belong to paused time containers that don't need a sample,
518 // but that doesn't work because we've already called Sample() on all the time
519 // containers so the paused ones don't need a sample any more and they'll
522 // Instead we build up a hashmap of active time containers during the previous
523 // step (SampleTimeContainer) and then test here if the container for this
524 // timed element is in the list.
525 if (!aActiveContainers
->GetEntry(timeContainer
)) return;
527 SMILTime containerTime
= timeContainer
->GetCurrentTimeAsSMILTime();
529 MOZ_ASSERT(!timeContainer
->IsSeeking(),
530 "Doing a regular sample but the time container is still seeking");
531 aElement
->TimedElement().SampleAt(containerTime
);
535 void SMILAnimationController::AddAnimationToCompositorTable(
536 SVGAnimationElement
* aElement
, SMILCompositorTable
* aCompositorTable
,
537 bool& aStyleFlushNeeded
) {
538 // Add a compositor to the hash table if there's not already one there
539 SMILTargetIdentifier key
;
540 if (!GetTargetIdentifierForAnimation(aElement
, key
))
541 // Something's wrong/missing about animation's target; skip this animation
544 SMILAnimationFunction
& func
= aElement
->AnimationFunction();
546 // Only add active animation functions. If there are no active animations
547 // targeting an attribute, no compositor will be created and any previously
548 // applied animations will be cleared.
549 if (func
.IsActiveOrFrozen()) {
550 // Look up the compositor for our target, & add our animation function
551 // to its list of animation functions.
552 SMILCompositor
* result
= aCompositorTable
->PutEntry(key
);
553 aStyleFlushNeeded
|= func
.ValueNeedsReparsingEverySample();
554 result
->AddAnimationFunction(&func
);
556 } else if (func
.HasChanged()) {
557 // Look up the compositor for our target, and force it to skip the
558 // "nothing's changed so don't bother compositing" optimization for this
559 // sample. |func| is inactive, but it's probably *newly* inactive (since
560 // it's got HasChanged() == true), so we need to make sure to recompose
562 SMILCompositor
* result
= aCompositorTable
->PutEntry(key
);
563 aStyleFlushNeeded
|= func
.ValueNeedsReparsingEverySample();
564 result
->ToggleForceCompositing();
566 // We've now made sure that |func|'s inactivity will be reflected as of
567 // this sample. We need to clear its HasChanged() flag so that it won't
568 // trigger this same clause in future samples (until it changes again).
569 func
.ClearHasChanged();
573 static inline bool IsTransformAttribute(int32_t aNamespaceID
,
574 nsAtom
* aAttributeName
) {
575 return aNamespaceID
== kNameSpaceID_None
&&
576 (aAttributeName
== nsGkAtoms::transform
||
577 aAttributeName
== nsGkAtoms::patternTransform
||
578 aAttributeName
== nsGkAtoms::gradientTransform
);
581 // Helper function that, given a SVGAnimationElement, looks up its target
582 // element & target attribute and populates a SMILTargetIdentifier
585 bool SMILAnimationController::GetTargetIdentifierForAnimation(
586 SVGAnimationElement
* aAnimElem
, SMILTargetIdentifier
& aResult
) {
587 // Look up target (animated) element
588 Element
* targetElem
= aAnimElem
->GetTargetElementContent();
590 // Animation has no target elem -- skip it.
593 // Look up target (animated) attribute
594 // SMILANIM section 3.1, attributeName may
595 // have an XMLNS prefix to indicate the XML namespace.
596 RefPtr
<nsAtom
> attributeName
;
597 int32_t attributeNamespaceID
;
598 if (!aAnimElem
->GetTargetAttributeName(&attributeNamespaceID
,
599 getter_AddRefs(attributeName
)))
600 // Animation has no target attr -- skip it.
603 // animateTransform can only animate transforms, conversely transforms
604 // can only be animated by animateTransform
605 if (IsTransformAttribute(attributeNamespaceID
, attributeName
) !=
606 (aAnimElem
->IsSVGElement(nsGkAtoms::animateTransform
)))
610 aResult
.mElement
= targetElem
;
611 aResult
.mAttributeName
= attributeName
;
612 aResult
.mAttributeNamespaceID
= attributeNamespaceID
;
617 bool SMILAnimationController::PreTraverse() {
618 return PreTraverseInSubtree(nullptr);
621 bool SMILAnimationController::PreTraverseInSubtree(Element
* aRoot
) {
622 MOZ_ASSERT(NS_IsMainThread());
624 if (!mMightHavePendingStyleUpdates
) {
628 nsPresContext
* context
= mDocument
->GetPresContext();
633 bool foundElementsNeedingRestyle
= false;
634 for (SVGAnimationElement
* animElement
: mAnimationElementTable
.Keys()) {
635 SMILTargetIdentifier key
;
636 if (!GetTargetIdentifierForAnimation(animElement
, key
)) {
637 // Something's wrong/missing about animation's target; skip this animation
641 // Ignore restyles that aren't in the flattened tree subtree rooted at
643 if (aRoot
&& !nsContentUtils::ContentIsFlattenedTreeDescendantOf(
644 key
.mElement
, aRoot
)) {
648 context
->RestyleManager()->PostRestyleEventForAnimations(
649 key
.mElement
, PseudoStyleType::NotPseudo
, RestyleHint::RESTYLE_SMIL
);
651 foundElementsNeedingRestyle
= true;
654 // Only clear the mMightHavePendingStyleUpdates flag if we definitely posted
657 mMightHavePendingStyleUpdates
= false;
660 return foundElementsNeedingRestyle
;
663 //----------------------------------------------------------------------
664 // Add/remove child time containers
666 nsresult
SMILAnimationController::AddChild(SMILTimeContainer
& aChild
) {
667 const bool wasEmpty
= mChildContainerTable
.IsEmpty();
668 TimeContainerPtrKey
* key
= mChildContainerTable
.PutEntry(&aChild
);
669 NS_ENSURE_TRUE(key
, NS_ERROR_OUT_OF_MEMORY
);
676 void SMILAnimationController::RemoveChild(SMILTimeContainer
& aChild
) {
677 mChildContainerTable
.RemoveEntry(&aChild
);
678 if (mChildContainerTable
.IsEmpty()) {
684 nsRefreshDriver
* SMILAnimationController::GetRefreshDriver() {
686 NS_ERROR("Requesting refresh driver after document has disconnected!");
690 nsPresContext
* context
= mDocument
->GetPresContext();
691 return context
? context
->RefreshDriver() : nullptr;
694 void SMILAnimationController::FlagDocumentNeedsFlush() {
695 if (PresShell
* presShell
= mDocument
->GetPresShell()) {
696 presShell
->SetNeedStyleFlush();
700 } // namespace mozilla