no bug - Bumping Firefox l10n changesets r=release a=l10n-bump DONTBUILD CLOSED TREE
[gecko.git] / dom / smil / SMILAnimationController.cpp
blob99abc07b58efafb63bcb8859304a04d51c29b5f0
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 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
280 RewindElements();
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()) {
289 if (!container) {
290 continue;
293 if (!container->IsPausedByType(SMILTimeContainer::PAUSE_BEGIN) &&
294 (container->NeedsSample() || !aSkipUnchangedContainers)) {
295 container->ClearMilestones();
296 container->Sample();
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(),
331 isStyleFlushNeeded);
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;
375 return;
378 if (isStyleFlushNeeded) {
379 document->FlushPendingNotifications(FlushType::Style);
382 // WARNING:
383 // WARNING: the above flush may have destroyed the pres shell and/or
384 // WARNING: frames and other layout related objects.
385 // WARNING:
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;
443 while (true) {
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
448 // sample.
449 SMILMilestone nextMilestone(GetCurrentTimeAsSMILTime() + 1, true);
450 for (SMILTimeContainer* container : mChildContainerTable.Keys()) {
451 if (container->IsPausedByType(SMILTimeContainer::PAUSE_BEGIN)) {
452 continue;
454 SMILMilestone thisMilestone;
455 bool didGetMilestone =
456 container->GetNextMilestoneInParentTime(thisMilestone);
457 if (didGetMilestone && thisMilestone < nextMilestone) {
458 nextMilestone = thisMilestone;
462 if (nextMilestone.mTime > GetCurrentTimeAsSMILTime()) {
463 break;
466 nsTArray<RefPtr<dom::SVGAnimationElement>> elements;
467 for (SMILTimeContainer* container : mChildContainerTable.Keys()) {
468 if (container->IsPausedByType(SMILTimeContainer::PAUSE_BEGIN)) {
469 continue;
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
478 // earlier.
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();
488 if (!container)
489 // The container may be nullptr if the element has been detached from
490 // its parent since registering a milestone.
491 continue;
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);
503 } else {
504 elem->TimedElement().SampleAt(containerTime);
510 /*static*/
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
520 // return false.
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);
534 /*static*/
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
542 return;
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
561 // its target.
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
583 // for this target.
584 /*static*/
585 bool SMILAnimationController::GetTargetIdentifierForAnimation(
586 SVGAnimationElement* aAnimElem, SMILTargetIdentifier& aResult) {
587 // Look up target (animated) element
588 Element* targetElem = aAnimElem->GetTargetElementContent();
589 if (!targetElem)
590 // Animation has no target elem -- skip it.
591 return false;
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.
601 return false;
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)))
607 return false;
609 // Construct the key
610 aResult.mElement = targetElem;
611 aResult.mAttributeName = attributeName;
612 aResult.mAttributeNamespaceID = attributeNamespaceID;
614 return true;
617 bool SMILAnimationController::PreTraverse() {
618 return PreTraverseInSubtree(nullptr);
621 bool SMILAnimationController::PreTraverseInSubtree(Element* aRoot) {
622 MOZ_ASSERT(NS_IsMainThread());
624 if (!mMightHavePendingStyleUpdates) {
625 return false;
628 nsPresContext* context = mDocument->GetPresContext();
629 if (!context) {
630 return false;
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
638 continue;
641 // Ignore restyles that aren't in the flattened tree subtree rooted at
642 // aRoot.
643 if (aRoot && !nsContentUtils::ContentIsFlattenedTreeDescendantOf(
644 key.mElement, aRoot)) {
645 continue;
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
655 // all restyles.
656 if (!aRoot) {
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);
670 if (wasEmpty) {
671 UpdateSampling();
673 return NS_OK;
676 void SMILAnimationController::RemoveChild(SMILTimeContainer& aChild) {
677 mChildContainerTable.RemoveEntry(&aChild);
678 if (mChildContainerTable.IsEmpty()) {
679 UpdateSampling();
683 // Helper method
684 nsRefreshDriver* SMILAnimationController::GetRefreshDriver() {
685 if (!mDocument) {
686 NS_ERROR("Requesting refresh driver after document has disconnected!");
687 return nullptr;
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