Bug 1890844 - Tiny cleanup of classes implementing nsDOMCSSDeclaration r=layout-revie...
[gecko.git] / dom / smil / SMILAnimationController.cpp
blobbe180e96f6587d72cae81fd04979d603895ad188
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"
9 #include <algorithm>
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;
28 namespace mozilla {
30 //----------------------------------------------------------------------
31 // SMILAnimationController implementation
33 //----------------------------------------------------------------------
34 // ctors, dtors, factory methods
36 SMILAnimationController::SMILAnimationController(Document* aDoc)
37 : mDocument(aDoc) {
38 MOZ_ASSERT(aDoc, "need a non-null document");
40 if (nsRefreshDriver* refreshDriver = GetRefreshDriver()) {
41 mStartTime = refreshDriver->MostRecentRefresh();
42 } else {
43 mStartTime = mozilla::TimeStamp::Now();
45 mCurrentSampleTime = mStartTime;
47 Begin();
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);
75 UpdateSampling();
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) {
87 UpdateSampling();
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) {
127 // First sample.
128 mAvgTimeBetweenSamples = elapsedTime;
129 } else {
130 if (elapsedTime > SAMPLE_DEV_THRESHOLD * mAvgTimeBetweenSamples) {
131 // Unexpectedly long delay between samples.
132 NS_WARNING(
133 "Detected really long delay between samples, continuing from "
134 "previous sample");
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;
145 Sample();
148 //----------------------------------------------------------------------
149 // Animation element registration methods:
151 void SMILAnimationController::RegisterAnimationElement(
152 SVGAnimationElement* aAnimationElement) {
153 const bool wasEmpty = mAnimationElementTable.IsEmpty();
154 mAnimationElementTable.PutEntry(aAnimationElement);
155 if (wasEmpty) {
156 UpdateSampling();
160 void SMILAnimationController::UnregisterAnimationElement(
161 SVGAnimationElement* aAnimationElement) {
162 mAnimationElementTable.RemoveEntry(aAnimationElement);
163 if (mAnimationElementTable.IsEmpty()) {
164 UpdateSampling();
168 //----------------------------------------------------------------------
169 // Page show/hide
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) {
199 UpdateSampling();
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) {
219 return;
222 nsRefreshDriver* driver = GetRefreshDriver();
223 if (!driver) {
224 return;
227 if (shouldSample) {
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.
234 } else {
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) {
258 if (!mDocument) {
259 NS_ERROR("Shouldn't be sampling after document has disconnected");
260 return;
262 if (mRunningSample) {
263 NS_ERROR("Shouldn't be recursively sampling");
264 return;
267 mResampleNeeded = false;
269 // Set running sample flag -- do this before flushing styles so that when we
270 // flush styles we don't end up requesting extra samples
271 AutoRestore<bool> autoRestoreRunningSample(mRunningSample);
272 mRunningSample = true;
274 // STEP 1: Bring model up to date
275 // (i) Rewind elements where necessary
276 // (ii) Run milestone samples
277 RewindElements();
278 DoMilestoneSamples();
280 // STEP 2: Sample the child time containers
282 // When we sample the child time containers they will simply record the sample
283 // time in document time.
284 TimeContainerHashtable activeContainers(mChildContainerTable.Count());
285 for (SMILTimeContainer* container : mChildContainerTable.Keys()) {
286 if (!container) {
287 continue;
290 if (!container->IsPausedByType(SMILTimeContainer::PAUSE_BEGIN) &&
291 (container->NeedsSample() || !aSkipUnchangedContainers)) {
292 container->ClearMilestones();
293 container->Sample();
294 container->MarkSeekFinished();
295 activeContainers.PutEntry(container);
299 // STEP 3: (i) Sample the timed elements AND
300 // (ii) Create a table of compositors
302 // (i) Here we sample the timed elements (fetched from the
303 // SVGAnimationElements) which determine from the active time if the
304 // element is active and what its simple time etc. is. This information is
305 // then passed to its time client (SMILAnimationFunction).
307 // (ii) During the same loop we also build up a table that contains one
308 // compositor for each animated attribute and which maps animated elements to
309 // the corresponding compositor for their target attribute.
311 // Note that this compositor table needs to be allocated on the heap so we can
312 // store it until the next sample. This lets us find out which elements were
313 // animated in sample 'n-1' but not in sample 'n' (and hence need to have
314 // their animation effects removed in sample 'n').
316 // Parts (i) and (ii) are not functionally related but we combine them here to
317 // save iterating over the animation elements twice.
319 // Create the compositor table
320 UniquePtr<SMILCompositorTable> currentCompositorTable(
321 new SMILCompositorTable(0));
322 nsTArray<RefPtr<SVGAnimationElement>> animElems(
323 mAnimationElementTable.Count());
325 for (SVGAnimationElement* animElem : mAnimationElementTable.Keys()) {
326 SampleTimedElement(animElem, &activeContainers);
327 AddAnimationToCompositorTable(animElem, currentCompositorTable.get());
328 animElems.AppendElement(animElem);
330 activeContainers.Clear();
332 // STEP 4: Compare previous sample's compositors against this sample's.
333 // (Transfer cached base values across, & remove animation effects from
334 // no-longer-animated targets.)
335 if (mLastCompositorTable) {
336 // * Transfer over cached base values, from last sample's compositors
337 for (SMILCompositor& compositor : *currentCompositorTable) {
338 SMILCompositor* lastCompositor =
339 mLastCompositorTable->GetEntry(compositor.GetKey());
341 if (lastCompositor) {
342 compositor.StealCachedBaseValue(lastCompositor);
343 if (!lastCompositor->HasSameNumberOfAnimationFunctionsAs(compositor)) {
344 // If we have multiple animations on the same element, they share a
345 // compositor. If an active animation ends, it will no longer be in
346 // the compositor table. We need to force compositing to ensure we
347 // render the element with any remaining frozen animations even though
348 // they would not normally trigger compositing.
349 compositor.ToggleForceCompositing();
354 // * For each compositor in current sample's hash table, remove entry from
355 // prev sample's hash table -- we don't need to clear animation
356 // effects of those compositors, since they're still being animated.
357 for (const auto& key : currentCompositorTable->Keys()) {
358 mLastCompositorTable->RemoveEntry(key);
361 // * For each entry that remains in prev sample's hash table (i.e. for
362 // every target that's no longer animated), clear animation effects.
363 for (SMILCompositor& compositor : *mLastCompositorTable) {
364 compositor.ClearAnimationEffects();
368 // return early if there are no active animations to avoid a style flush
369 if (currentCompositorTable->IsEmpty()) {
370 mLastCompositorTable = nullptr;
371 return;
374 // STEP 5: Compose currently-animated attributes.
375 // XXXdholbert: This step traverses our animation targets in an effectively
376 // random order. For animation from/to 'inherit' values to work correctly
377 // when the inherited value is *also* being animated, we really should be
378 // traversing our animated nodes in an ancestors-first order (bug 501183)
379 bool mightHavePendingStyleUpdates = false;
380 for (auto& compositor : *currentCompositorTable) {
381 compositor.ComposeAttribute(mightHavePendingStyleUpdates);
384 // Update last compositor table
385 mLastCompositorTable = std::move(currentCompositorTable);
386 mMightHavePendingStyleUpdates = mightHavePendingStyleUpdates;
388 NS_ASSERTION(!mResampleNeeded, "Resample dirty flag set during sample!");
391 void SMILAnimationController::RewindElements() {
392 const bool rewindNeeded = std::any_of(
393 mChildContainerTable.Keys().cbegin(), mChildContainerTable.Keys().cend(),
394 [](SMILTimeContainer* container) { return container->NeedsRewind(); });
396 if (!rewindNeeded) return;
398 for (SVGAnimationElement* animElem : mAnimationElementTable.Keys()) {
399 SMILTimeContainer* timeContainer = animElem->GetTimeContainer();
400 if (timeContainer && timeContainer->NeedsRewind()) {
401 animElem->TimedElement().Rewind();
405 for (SMILTimeContainer* container : mChildContainerTable.Keys()) {
406 container->ClearNeedsRewind();
410 void SMILAnimationController::DoMilestoneSamples() {
411 // We need to sample the timing model but because SMIL operates independently
412 // of the frame-rate, we can get one sample at t=0s and the next at t=10min.
414 // In between those two sample times a whole string of significant events
415 // might be expected to take place: events firing, new interdependencies
416 // between animations resolved and dissolved, etc.
418 // Furthermore, at any given time, we want to sample all the intervals that
419 // end at that time BEFORE any that begin. This behaviour is implied by SMIL's
420 // endpoint-exclusive timing model.
422 // So we have the animations (specifically the timed elements) register the
423 // next significant moment (called a milestone) in their lifetime and then we
424 // step through the model at each of these moments and sample those animations
425 // registered for those times. This way events can fire in the correct order,
426 // dependencies can be resolved etc.
428 SMILTime sampleTime = INT64_MIN;
430 while (true) {
431 // We want to find any milestones AT OR BEFORE the current sample time so we
432 // initialise the next milestone to the moment after (1ms after, to be
433 // precise) the current sample time and see if there are any milestones
434 // before that. Any other milestones will be dealt with in a subsequent
435 // sample.
436 SMILMilestone nextMilestone(GetCurrentTimeAsSMILTime() + 1, true);
437 for (SMILTimeContainer* container : mChildContainerTable.Keys()) {
438 if (container->IsPausedByType(SMILTimeContainer::PAUSE_BEGIN)) {
439 continue;
441 SMILMilestone thisMilestone;
442 bool didGetMilestone =
443 container->GetNextMilestoneInParentTime(thisMilestone);
444 if (didGetMilestone && thisMilestone < nextMilestone) {
445 nextMilestone = thisMilestone;
449 if (nextMilestone.mTime > GetCurrentTimeAsSMILTime()) {
450 break;
453 nsTArray<RefPtr<dom::SVGAnimationElement>> elements;
454 for (SMILTimeContainer* container : mChildContainerTable.Keys()) {
455 if (container->IsPausedByType(SMILTimeContainer::PAUSE_BEGIN)) {
456 continue;
458 container->PopMilestoneElementsAtMilestone(nextMilestone, elements);
461 // During the course of a sampling we don't want to actually go backwards.
462 // Due to negative offsets, early ends and the like, a timed element might
463 // register a milestone that is actually in the past. That's fine, but it's
464 // still only going to get *sampled* with whatever time we're up to and no
465 // earlier.
467 // Because we're only performing this clamping at the last moment, the
468 // animations will still all get sampled in the correct order and
469 // dependencies will be appropriately resolved.
470 sampleTime = std::max(nextMilestone.mTime, sampleTime);
472 for (RefPtr<dom::SVGAnimationElement>& elem : elements) {
473 MOZ_ASSERT(elem, "nullptr animation element in list");
474 SMILTimeContainer* container = elem->GetTimeContainer();
475 if (!container)
476 // The container may be nullptr if the element has been detached from
477 // its parent since registering a milestone.
478 continue;
480 SMILTimeValue containerTimeValue =
481 container->ParentToContainerTime(sampleTime);
482 if (!containerTimeValue.IsDefinite()) continue;
484 // Clamp the converted container time to non-negative values.
485 SMILTime containerTime =
486 std::max<SMILTime>(0, containerTimeValue.GetMillis());
488 if (nextMilestone.mIsEnd) {
489 elem->TimedElement().SampleEndAt(containerTime);
490 } else {
491 elem->TimedElement().SampleAt(containerTime);
497 /*static*/
498 void SMILAnimationController::SampleTimedElement(
499 SVGAnimationElement* aElement, TimeContainerHashtable* aActiveContainers) {
500 SMILTimeContainer* timeContainer = aElement->GetTimeContainer();
501 if (!timeContainer) return;
503 // We'd like to call timeContainer->NeedsSample() here and skip all timed
504 // elements that belong to paused time containers that don't need a sample,
505 // but that doesn't work because we've already called Sample() on all the time
506 // containers so the paused ones don't need a sample any more and they'll
507 // return false.
509 // Instead we build up a hashmap of active time containers during the previous
510 // step (SampleTimeContainer) and then test here if the container for this
511 // timed element is in the list.
512 if (!aActiveContainers->GetEntry(timeContainer)) return;
514 SMILTime containerTime = timeContainer->GetCurrentTimeAsSMILTime();
516 MOZ_ASSERT(!timeContainer->IsSeeking(),
517 "Doing a regular sample but the time container is still seeking");
518 aElement->TimedElement().SampleAt(containerTime);
521 /*static*/
522 void SMILAnimationController::AddAnimationToCompositorTable(
523 SVGAnimationElement* aElement, SMILCompositorTable* aCompositorTable) {
524 // Add a compositor to the hash table if there's not already one there
525 SMILTargetIdentifier key;
526 if (!GetTargetIdentifierForAnimation(aElement, key))
527 // Something's wrong/missing about animation's target; skip this animation
528 return;
530 SMILAnimationFunction& func = aElement->AnimationFunction();
532 // Only add active animation functions. If there are no active animations
533 // targeting an attribute, no compositor will be created and any previously
534 // applied animations will be cleared.
535 if (func.IsActiveOrFrozen()) {
536 // Look up the compositor for our target, & add our animation function
537 // to its list of animation functions.
538 SMILCompositor* result = aCompositorTable->PutEntry(key);
539 result->AddAnimationFunction(&func);
541 } else if (func.HasChanged()) {
542 // Look up the compositor for our target, and force it to skip the
543 // "nothing's changed so don't bother compositing" optimization for this
544 // sample. |func| is inactive, but it's probably *newly* inactive (since
545 // it's got HasChanged() == true), so we need to make sure to recompose
546 // its target.
547 SMILCompositor* result = aCompositorTable->PutEntry(key);
548 result->ToggleForceCompositing();
550 // We've now made sure that |func|'s inactivity will be reflected as of
551 // this sample. We need to clear its HasChanged() flag so that it won't
552 // trigger this same clause in future samples (until it changes again).
553 func.ClearHasChanged();
557 static inline bool IsTransformAttribute(int32_t aNamespaceID,
558 nsAtom* aAttributeName) {
559 return aNamespaceID == kNameSpaceID_None &&
560 (aAttributeName == nsGkAtoms::transform ||
561 aAttributeName == nsGkAtoms::patternTransform ||
562 aAttributeName == nsGkAtoms::gradientTransform);
565 // Helper function that, given a SVGAnimationElement, looks up its target
566 // element & target attribute and populates a SMILTargetIdentifier
567 // for this target.
568 /*static*/
569 bool SMILAnimationController::GetTargetIdentifierForAnimation(
570 SVGAnimationElement* aAnimElem, SMILTargetIdentifier& aResult) {
571 // Look up target (animated) element
572 Element* targetElem = aAnimElem->GetTargetElementContent();
573 if (!targetElem)
574 // Animation has no target elem -- skip it.
575 return false;
577 // Look up target (animated) attribute
578 // SMILANIM section 3.1, attributeName may
579 // have an XMLNS prefix to indicate the XML namespace.
580 RefPtr<nsAtom> attributeName;
581 int32_t attributeNamespaceID;
582 if (!aAnimElem->GetTargetAttributeName(&attributeNamespaceID,
583 getter_AddRefs(attributeName)))
584 // Animation has no target attr -- skip it.
585 return false;
587 // animateTransform can only animate transforms, conversely transforms
588 // can only be animated by animateTransform
589 if (IsTransformAttribute(attributeNamespaceID, attributeName) !=
590 (aAnimElem->IsSVGElement(nsGkAtoms::animateTransform)))
591 return false;
593 // Construct the key
594 aResult.mElement = targetElem;
595 aResult.mAttributeName = attributeName;
596 aResult.mAttributeNamespaceID = attributeNamespaceID;
598 return true;
601 void SMILAnimationController::PreTraverse() { PreTraverseInSubtree(nullptr); }
603 void SMILAnimationController::PreTraverseInSubtree(Element* aRoot) {
604 MOZ_ASSERT(NS_IsMainThread());
606 if (!mMightHavePendingStyleUpdates) {
607 return;
610 nsPresContext* context = mDocument->GetPresContext();
611 if (!context) {
612 return;
615 for (SVGAnimationElement* animElement : mAnimationElementTable.Keys()) {
616 SMILTargetIdentifier key;
617 if (!GetTargetIdentifierForAnimation(animElement, key)) {
618 // Something's wrong/missing about animation's target; skip this animation
619 continue;
622 // Ignore restyles that aren't in the flattened tree subtree rooted at
623 // aRoot.
624 if (aRoot && !nsContentUtils::ContentIsFlattenedTreeDescendantOf(
625 key.mElement, aRoot)) {
626 continue;
629 context->RestyleManager()->PostRestyleEventForAnimations(
630 key.mElement, PseudoStyleType::NotPseudo, RestyleHint::RESTYLE_SMIL);
633 // Only clear the mMightHavePendingStyleUpdates flag if we definitely posted
634 // all restyles.
635 if (!aRoot) {
636 mMightHavePendingStyleUpdates = false;
640 //----------------------------------------------------------------------
641 // Add/remove child time containers
643 nsresult SMILAnimationController::AddChild(SMILTimeContainer& aChild) {
644 const bool wasEmpty = mChildContainerTable.IsEmpty();
645 TimeContainerPtrKey* key = mChildContainerTable.PutEntry(&aChild);
646 NS_ENSURE_TRUE(key, NS_ERROR_OUT_OF_MEMORY);
647 if (wasEmpty) {
648 UpdateSampling();
650 return NS_OK;
653 void SMILAnimationController::RemoveChild(SMILTimeContainer& aChild) {
654 mChildContainerTable.RemoveEntry(&aChild);
655 if (mChildContainerTable.IsEmpty()) {
656 UpdateSampling();
660 // Helper method
661 nsRefreshDriver* SMILAnimationController::GetRefreshDriver() {
662 if (!mDocument) {
663 NS_ERROR("Requesting refresh driver after document has disconnected!");
664 return nullptr;
667 nsPresContext* context = mDocument->GetPresContext();
668 return context ? context->RefreshDriver() : nullptr;
671 void SMILAnimationController::FlagDocumentNeedsFlush() {
672 if (PresShell* presShell = mDocument->GetPresShell()) {
673 presShell->SetNeedStyleFlush();
677 } // namespace mozilla