tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

SMILAnimationController.cpp (24211B)


      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/. */
      6 
      7 #include "SMILAnimationController.h"
      8 
      9 #include <algorithm>
     10 
     11 #include "SMILCSSProperty.h"
     12 #include "SMILCompositor.h"
     13 #include "mozilla/AutoRestore.h"
     14 #include "mozilla/PresShell.h"
     15 #include "mozilla/PresShellInlines.h"
     16 #include "mozilla/RestyleManager.h"
     17 #include "mozilla/SMILTimedElement.h"
     18 #include "mozilla/dom/Document.h"
     19 #include "mozilla/dom/DocumentInlines.h"
     20 #include "mozilla/dom/Element.h"
     21 #include "mozilla/dom/SVGAnimationElement.h"
     22 #include "nsCSSProps.h"
     23 #include "nsContentUtils.h"
     24 #include "nsRefreshDriver.h"
     25 
     26 using namespace mozilla::dom;
     27 
     28 namespace mozilla {
     29 
     30 //----------------------------------------------------------------------
     31 // SMILAnimationController implementation
     32 
     33 //----------------------------------------------------------------------
     34 // ctors, dtors, factory methods
     35 
     36 SMILAnimationController::SMILAnimationController(Document* aDoc)
     37    : mDocument(aDoc) {
     38  MOZ_ASSERT(aDoc, "need a non-null document");
     39 
     40  if (nsRefreshDriver* refreshDriver = GetRefreshDriver()) {
     41    mStartTime = refreshDriver->MostRecentRefresh();
     42  } else {
     43    mStartTime = mozilla::TimeStamp::Now();
     44  }
     45  mCurrentSampleTime = mStartTime;
     46 
     47  Begin();
     48 }
     49 
     50 SMILAnimationController::~SMILAnimationController() {
     51  NS_ASSERTION(mAnimationElementTable.IsEmpty(),
     52               "Animation controller shouldn't be tracking any animation"
     53               " elements when it dies");
     54 }
     55 
     56 void SMILAnimationController::Disconnect() {
     57  MOZ_ASSERT(mDocument, "disconnecting when we weren't connected...?");
     58  MOZ_ASSERT(mRefCnt.get() == 1,
     59             "Expecting to disconnect when doc is sole remaining owner");
     60  NS_ASSERTION(mPauseState & SMILTimeContainer::PAUSE_PAGEHIDE,
     61               "Expecting to be paused for pagehide before disconnect");
     62  mDocument = nullptr;  // (raw pointer)
     63 }
     64 
     65 //----------------------------------------------------------------------
     66 // SMILTimeContainer methods:
     67 
     68 void SMILAnimationController::Pause(uint32_t aType) {
     69  SMILTimeContainer::Pause(aType);
     70  UpdateSampling();
     71 }
     72 
     73 void SMILAnimationController::Resume(uint32_t aType) {
     74  bool wasPaused = !!mPauseState;
     75  // Update mCurrentSampleTime so that calls to GetParentTime--used for
     76  // calculating parent offsets--are accurate
     77  mCurrentSampleTime = mozilla::TimeStamp::Now();
     78 
     79  SMILTimeContainer::Resume(aType);
     80 
     81  if (wasPaused && !mPauseState) {
     82    UpdateSampling();
     83  }
     84 }
     85 
     86 SMILTime SMILAnimationController::GetParentTime() const {
     87  return (SMILTime)(mCurrentSampleTime - mStartTime).ToMilliseconds();
     88 }
     89 
     90 // nsRefreshDriver Callback function
     91 void SMILAnimationController::WillRefresh(mozilla::TimeStamp aTime) {
     92  if (!mIsSampling) {
     93    return;
     94  }
     95  // Although we never expect aTime to go backwards, when we initialise the
     96  // animation controller, if we can't get hold of a refresh driver we
     97  // initialise mCurrentSampleTime to Now(). It may be possible that after
     98  // doing so we get sampled by a refresh driver whose most recent refresh time
     99  // predates when we were initialised, so to be safe we make sure to take the
    100  // most recent time here.
    101  aTime = std::max(mCurrentSampleTime, aTime);
    102 
    103  // Sleep detection: If the time between samples is a whole lot greater than we
    104  // were expecting then we assume the computer went to sleep or someone's
    105  // messing with the clock. In that case, fiddle our parent offset and use our
    106  // average time between samples to calculate the new sample time. This
    107  // prevents us from hanging while trying to catch up on all the missed time.
    108 
    109  // Smoothing of coefficient for the average function. 0.2 should let us track
    110  // the sample rate reasonably tightly without being overly affected by
    111  // occasional delays.
    112  static const double SAMPLE_DUR_WEIGHTING = 0.2;
    113  // If the elapsed time exceeds our expectation by this number of times we'll
    114  // initiate special behaviour to basically ignore the intervening time.
    115  static const double SAMPLE_DEV_THRESHOLD = 200.0;
    116 
    117  SMILTime elapsedTime =
    118      (SMILTime)(aTime - mCurrentSampleTime).ToMilliseconds();
    119  if (mAvgTimeBetweenSamples == 0) {
    120    // First sample.
    121    mAvgTimeBetweenSamples = elapsedTime;
    122  } else {
    123    if (elapsedTime > SAMPLE_DEV_THRESHOLD * mAvgTimeBetweenSamples) {
    124      // Unexpectedly long delay between samples.
    125      NS_WARNING(
    126          "Detected really long delay between samples, continuing from "
    127          "previous sample");
    128      mParentOffset += elapsedTime - mAvgTimeBetweenSamples;
    129    }
    130    // Update the moving average. Due to truncation here the average will
    131    // normally be a little less than it should be but that's probably ok.
    132    mAvgTimeBetweenSamples =
    133        (SMILTime)(elapsedTime * SAMPLE_DUR_WEIGHTING +
    134                   mAvgTimeBetweenSamples * (1.0 - SAMPLE_DUR_WEIGHTING));
    135  }
    136  mCurrentSampleTime = aTime;
    137 
    138  Sample();
    139  UpdateSampling();
    140 }
    141 
    142 //----------------------------------------------------------------------
    143 // Animation element registration methods:
    144 
    145 void SMILAnimationController::RegisterAnimationElement(
    146    SVGAnimationElement* aAnimationElement) {
    147  const bool wasEmpty = mAnimationElementTable.IsEmpty();
    148  mAnimationElementTable.PutEntry(aAnimationElement);
    149  if (wasEmpty) {
    150    UpdateSampling();
    151  }
    152 }
    153 
    154 void SMILAnimationController::UnregisterAnimationElement(
    155    SVGAnimationElement* aAnimationElement) {
    156  mAnimationElementTable.RemoveEntry(aAnimationElement);
    157  if (mAnimationElementTable.IsEmpty()) {
    158    UpdateSampling();
    159  }
    160 }
    161 
    162 //----------------------------------------------------------------------
    163 // Page show/hide
    164 
    165 void SMILAnimationController::OnPageShow() {
    166  Resume(SMILTimeContainer::PAUSE_PAGEHIDE);
    167 }
    168 
    169 void SMILAnimationController::OnPageHide() {
    170  Pause(SMILTimeContainer::PAUSE_PAGEHIDE);
    171 }
    172 
    173 //----------------------------------------------------------------------
    174 // Cycle-collection support
    175 
    176 void SMILAnimationController::Traverse(
    177    nsCycleCollectionTraversalCallback* aCallback) {
    178  // Traverse last compositor table
    179  if (mLastCompositorTable) {
    180    for (SMILCompositor& compositor : *mLastCompositorTable) {
    181      compositor.Traverse(aCallback);
    182    }
    183  }
    184 }
    185 
    186 void SMILAnimationController::Unlink() { mLastCompositorTable = nullptr; }
    187 
    188 //----------------------------------------------------------------------
    189 // Timer-related implementation helpers
    190 
    191 bool SMILAnimationController::ShouldSample() const {
    192  return !mPauseState && !mAnimationElementTable.IsEmpty() &&
    193         !mChildContainerTable.IsEmpty();
    194 }
    195 
    196 void SMILAnimationController::UpdateSampling() {
    197  const bool shouldSample = ShouldSample();
    198  if (!shouldSample) {
    199    mIsSampling = false;
    200    return;
    201  }
    202  mDocument->MaybeScheduleRenderingPhases(
    203      {RenderingPhase::UpdateAnimationsAndSendEvents});
    204  if (!mIsSampling) {
    205    mIsSampling = true;
    206    // We're effectively resuming from a pause so update our current sample time
    207    // or else it will confuse our "average time between samples" calculations.
    208    mCurrentSampleTime = mozilla::TimeStamp::Now();
    209    // TODO(emilio): Not doing the sync first sample breaks
    210    // test_smilDynamicDelayedBeginElement.xhtml which tests for this
    211    // explicitly. Maybe we can avoid this?
    212    Sample();  // Run the first sample manually.
    213  }
    214 }
    215 
    216 //----------------------------------------------------------------------
    217 // Sample-related methods and callbacks
    218 
    219 void SMILAnimationController::DoSample() {
    220  DoSample(true);  // Skip unchanged time containers
    221 }
    222 
    223 void SMILAnimationController::DoSample(bool aSkipUnchangedContainers) {
    224  if (!mDocument) {
    225    NS_ERROR("Shouldn't be sampling after document has disconnected");
    226    return;
    227  }
    228  if (mRunningSample) {
    229    NS_ERROR("Shouldn't be recursively sampling");
    230    return;
    231  }
    232 
    233  mResampleNeeded = false;
    234 
    235  // Set running sample flag -- do this before flushing styles so that when we
    236  // flush styles we don't end up requesting extra samples
    237  AutoRestore<bool> autoRestoreRunningSample(mRunningSample);
    238  mRunningSample = true;
    239 
    240  // STEP 1: Bring model up to date
    241  // (i)  Rewind elements where necessary
    242  // (ii) Run milestone samples
    243  RewindElements();
    244  DoMilestoneSamples();
    245 
    246  // STEP 2: Sample the child time containers
    247  //
    248  // When we sample the child time containers they will simply record the sample
    249  // time in document time.
    250  TimeContainerHashtable activeContainers(mChildContainerTable.Count());
    251  for (SMILTimeContainer* container : mChildContainerTable.Keys()) {
    252    if (!container) {
    253      continue;
    254    }
    255 
    256    if (!container->IsPausedByType(SMILTimeContainer::PAUSE_BEGIN) &&
    257        (container->NeedsSample() || !aSkipUnchangedContainers)) {
    258      container->ClearMilestones();
    259      container->Sample();
    260      container->MarkSeekFinished();
    261      activeContainers.PutEntry(container);
    262    }
    263  }
    264 
    265  // STEP 3: (i)  Sample the timed elements AND
    266  //         (ii) Create a table of compositors
    267  //
    268  // (i) Here we sample the timed elements (fetched from the
    269  // SVGAnimationElements) which determine from the active time if the
    270  // element is active and what its simple time etc. is. This information is
    271  // then passed to its time client (SMILAnimationFunction).
    272  //
    273  // (ii) During the same loop we also build up a table that contains one
    274  // compositor for each animated attribute and which maps animated elements to
    275  // the corresponding compositor for their target attribute.
    276  //
    277  // Note that this compositor table needs to be allocated on the heap so we can
    278  // store it until the next sample. This lets us find out which elements were
    279  // animated in sample 'n-1' but not in sample 'n' (and hence need to have
    280  // their animation effects removed in sample 'n').
    281  //
    282  // Parts (i) and (ii) are not functionally related but we combine them here to
    283  // save iterating over the animation elements twice.
    284 
    285  // Create the compositor table
    286  UniquePtr<SMILCompositorTable> currentCompositorTable(
    287      new SMILCompositorTable(0));
    288  nsTArray<RefPtr<SVGAnimationElement>> animElems(
    289      mAnimationElementTable.Count());
    290 
    291  for (SVGAnimationElement* animElem : mAnimationElementTable.Keys()) {
    292    SampleTimedElement(animElem, &activeContainers);
    293    AddAnimationToCompositorTable(animElem, currentCompositorTable.get());
    294    animElems.AppendElement(animElem);
    295  }
    296  activeContainers.Clear();
    297 
    298  // STEP 4: Compare previous sample's compositors against this sample's.
    299  // (Transfer cached base values across, & remove animation effects from
    300  // no-longer-animated targets.)
    301  if (mLastCompositorTable) {
    302    // * Transfer over cached base values, from last sample's compositors
    303    for (SMILCompositor& compositor : *currentCompositorTable) {
    304      SMILCompositor* lastCompositor =
    305          mLastCompositorTable->GetEntry(compositor.GetKey());
    306 
    307      if (lastCompositor) {
    308        compositor.StealCachedBaseValue(lastCompositor);
    309        if (!lastCompositor->HasSameNumberOfAnimationFunctionsAs(compositor)) {
    310          // If we have multiple animations on the same element, they share a
    311          // compositor. If an active animation ends, it will no longer be in
    312          // the compositor table. We need to force compositing to ensure we
    313          // render the element with any remaining frozen animations even though
    314          // they would not normally trigger compositing.
    315          compositor.ToggleForceCompositing();
    316        }
    317      }
    318    }
    319 
    320    // * For each compositor in current sample's hash table, remove entry from
    321    // prev sample's hash table -- we don't need to clear animation
    322    // effects of those compositors, since they're still being animated.
    323    for (const auto& key : currentCompositorTable->Keys()) {
    324      mLastCompositorTable->RemoveEntry(key);
    325    }
    326 
    327    // * For each entry that remains in prev sample's hash table (i.e. for
    328    // every target that's no longer animated), clear animation effects.
    329    for (SMILCompositor& compositor : *mLastCompositorTable) {
    330      compositor.ClearAnimationEffects();
    331    }
    332  }
    333 
    334  // return early if there are no active animations to avoid a style flush
    335  if (currentCompositorTable->IsEmpty()) {
    336    mLastCompositorTable = nullptr;
    337    return;
    338  }
    339 
    340  // STEP 5: Compose currently-animated attributes.
    341  // XXXdholbert: This step traverses our animation targets in an effectively
    342  // random order. For animation from/to 'inherit' values to work correctly
    343  // when the inherited value is *also* being animated, we really should be
    344  // traversing our animated nodes in an ancestors-first order (bug 501183)
    345  bool mightHavePendingStyleUpdates = false;
    346  for (auto& compositor : *currentCompositorTable) {
    347    compositor.ComposeAttribute(mightHavePendingStyleUpdates);
    348  }
    349 
    350  // Update last compositor table
    351  mLastCompositorTable = std::move(currentCompositorTable);
    352  mMightHavePendingStyleUpdates = mightHavePendingStyleUpdates;
    353 
    354  NS_ASSERTION(!mResampleNeeded, "Resample dirty flag set during sample!");
    355 }
    356 
    357 void SMILAnimationController::RewindElements() {
    358  const bool rewindNeeded = std::any_of(
    359      mChildContainerTable.Keys().cbegin(), mChildContainerTable.Keys().cend(),
    360      [](SMILTimeContainer* container) { return container->NeedsRewind(); });
    361 
    362  if (!rewindNeeded) return;
    363 
    364  for (SVGAnimationElement* animElem : mAnimationElementTable.Keys()) {
    365    SMILTimeContainer* timeContainer = animElem->GetTimeContainer();
    366    if (timeContainer && timeContainer->NeedsRewind()) {
    367      animElem->TimedElement().Rewind();
    368    }
    369  }
    370 
    371  for (SMILTimeContainer* container : mChildContainerTable.Keys()) {
    372    container->ClearNeedsRewind();
    373  }
    374 }
    375 
    376 void SMILAnimationController::DoMilestoneSamples() {
    377  // We need to sample the timing model but because SMIL operates independently
    378  // of the frame-rate, we can get one sample at t=0s and the next at t=10min.
    379  //
    380  // In between those two sample times a whole string of significant events
    381  // might be expected to take place: events firing, new interdependencies
    382  // between animations resolved and dissolved, etc.
    383  //
    384  // Furthermore, at any given time, we want to sample all the intervals that
    385  // end at that time BEFORE any that begin. This behaviour is implied by SMIL's
    386  // endpoint-exclusive timing model.
    387  //
    388  // So we have the animations (specifically the timed elements) register the
    389  // next significant moment (called a milestone) in their lifetime and then we
    390  // step through the model at each of these moments and sample those animations
    391  // registered for those times. This way events can fire in the correct order,
    392  // dependencies can be resolved etc.
    393 
    394  SMILTime sampleTime = std::numeric_limits<SMILTime>::min();
    395 
    396  while (true) {
    397    // We want to find any milestones AT OR BEFORE the current sample time so we
    398    // initialise the next milestone to the moment after (1ms after, to be
    399    // precise) the current sample time and see if there are any milestones
    400    // before that. Any other milestones will be dealt with in a subsequent
    401    // sample.
    402    SMILMilestone nextMilestone(GetCurrentTimeAsSMILTime() + 1, true);
    403    for (SMILTimeContainer* container : mChildContainerTable.Keys()) {
    404      if (container->IsPausedByType(SMILTimeContainer::PAUSE_BEGIN)) {
    405        continue;
    406      }
    407      SMILMilestone thisMilestone;
    408      bool didGetMilestone =
    409          container->GetNextMilestoneInParentTime(thisMilestone);
    410      if (didGetMilestone && thisMilestone < nextMilestone) {
    411        nextMilestone = thisMilestone;
    412      }
    413    }
    414 
    415    if (nextMilestone.mTime > GetCurrentTimeAsSMILTime()) {
    416      break;
    417    }
    418 
    419    nsTArray<RefPtr<dom::SVGAnimationElement>> elements;
    420    for (SMILTimeContainer* container : mChildContainerTable.Keys()) {
    421      if (container->IsPausedByType(SMILTimeContainer::PAUSE_BEGIN)) {
    422        continue;
    423      }
    424      container->PopMilestoneElementsAtMilestone(nextMilestone, elements);
    425    }
    426 
    427    // During the course of a sampling we don't want to actually go backwards.
    428    // Due to negative offsets, early ends and the like, a timed element might
    429    // register a milestone that is actually in the past. That's fine, but it's
    430    // still only going to get *sampled* with whatever time we're up to and no
    431    // earlier.
    432    //
    433    // Because we're only performing this clamping at the last moment, the
    434    // animations will still all get sampled in the correct order and
    435    // dependencies will be appropriately resolved.
    436    sampleTime = std::max(nextMilestone.mTime, sampleTime);
    437 
    438    for (RefPtr<dom::SVGAnimationElement>& elem : elements) {
    439      MOZ_ASSERT(elem, "nullptr animation element in list");
    440      SMILTimeContainer* container = elem->GetTimeContainer();
    441      if (!container)
    442        // The container may be nullptr if the element has been detached from
    443        // its parent since registering a milestone.
    444        continue;
    445 
    446      SMILTimeValue containerTimeValue =
    447          container->ParentToContainerTime(sampleTime);
    448      if (!containerTimeValue.IsDefinite()) continue;
    449 
    450      // Clamp the converted container time to non-negative values.
    451      SMILTime containerTime =
    452          std::max<SMILTime>(0, containerTimeValue.GetMillis());
    453 
    454      if (nextMilestone.mIsEnd) {
    455        elem->TimedElement().SampleEndAt(containerTime);
    456      } else {
    457        elem->TimedElement().SampleAt(containerTime);
    458      }
    459    }
    460  }
    461 }
    462 
    463 /*static*/
    464 void SMILAnimationController::SampleTimedElement(
    465    SVGAnimationElement* aElement, TimeContainerHashtable* aActiveContainers) {
    466  SMILTimeContainer* timeContainer = aElement->GetTimeContainer();
    467  if (!timeContainer) return;
    468 
    469  // We'd like to call timeContainer->NeedsSample() here and skip all timed
    470  // elements that belong to paused time containers that don't need a sample,
    471  // but that doesn't work because we've already called Sample() on all the time
    472  // containers so the paused ones don't need a sample any more and they'll
    473  // return false.
    474  //
    475  // Instead we build up a hashmap of active time containers during the previous
    476  // step (SampleTimeContainer) and then test here if the container for this
    477  // timed element is in the list.
    478  if (!aActiveContainers->GetEntry(timeContainer)) return;
    479 
    480  SMILTime containerTime = timeContainer->GetCurrentTimeAsSMILTime();
    481 
    482  MOZ_ASSERT(!timeContainer->IsSeeking(),
    483             "Doing a regular sample but the time container is still seeking");
    484  aElement->TimedElement().SampleAt(containerTime);
    485 }
    486 
    487 /*static*/
    488 void SMILAnimationController::AddAnimationToCompositorTable(
    489    SVGAnimationElement* aElement, SMILCompositorTable* aCompositorTable) {
    490  // Add a compositor to the hash table if there's not already one there
    491  SMILTargetIdentifier key;
    492  if (!GetTargetIdentifierForAnimation(aElement, key))
    493    // Something's wrong/missing about animation's target; skip this animation
    494    return;
    495 
    496  SMILAnimationFunction& func = aElement->AnimationFunction();
    497 
    498  // Only add active animation functions. If there are no active animations
    499  // targeting an attribute, no compositor will be created and any previously
    500  // applied animations will be cleared.
    501  if (func.IsActiveOrFrozen()) {
    502    // Look up the compositor for our target, & add our animation function
    503    // to its list of animation functions.
    504    SMILCompositor* result = aCompositorTable->PutEntry(key);
    505    result->AddAnimationFunction(&func);
    506 
    507  } else if (func.HasChanged()) {
    508    // Look up the compositor for our target, and force it to skip the
    509    // "nothing's changed so don't bother compositing" optimization for this
    510    // sample. |func| is inactive, but it's probably *newly* inactive (since
    511    // it's got HasChanged() == true), so we need to make sure to recompose
    512    // its target.
    513    SMILCompositor* result = aCompositorTable->PutEntry(key);
    514    result->ToggleForceCompositing();
    515 
    516    // We've now made sure that |func|'s inactivity will be reflected as of
    517    // this sample. We need to clear its HasChanged() flag so that it won't
    518    // trigger this same clause in future samples (until it changes again).
    519    func.ClearHasChanged();
    520  }
    521 }
    522 
    523 static inline bool IsTransformAttribute(const Element* aElement,
    524                                        int32_t aNamespaceID,
    525                                        const nsAtom* aAttributeName) {
    526  if (aNamespaceID != kNameSpaceID_None) {
    527    return false;
    528  }
    529  if (auto* svgElement = SVGElement::FromNode(aElement)) {
    530    return svgElement->GetTransformListAttrName() == aAttributeName;
    531  }
    532  return false;
    533 }
    534 
    535 // Helper function that, given a SVGAnimationElement, looks up its target
    536 // element & target attribute and populates a SMILTargetIdentifier
    537 // for this target.
    538 /*static*/
    539 bool SMILAnimationController::GetTargetIdentifierForAnimation(
    540    SVGAnimationElement* aAnimElem, SMILTargetIdentifier& aResult) {
    541  // Look up target (animated) element
    542  Element* targetElem = aAnimElem->GetTargetElementContent();
    543  if (!targetElem)
    544    // Animation has no target elem -- skip it.
    545    return false;
    546 
    547  // Look up target (animated) attribute
    548  // SMILANIM section 3.1, attributeName may
    549  // have an XMLNS prefix to indicate the XML namespace.
    550  RefPtr<nsAtom> attributeName;
    551  int32_t attributeNamespaceID;
    552  if (!aAnimElem->GetTargetAttributeName(&attributeNamespaceID,
    553                                         getter_AddRefs(attributeName)))
    554    // Animation has no target attr -- skip it.
    555    return false;
    556 
    557  // animateTransform can only animate transforms, conversely transforms
    558  // can only be animated by animateTransform
    559  if (IsTransformAttribute(targetElem, attributeNamespaceID, attributeName) !=
    560      aAnimElem->IsSVGElement(nsGkAtoms::animateTransform))
    561    return false;
    562 
    563  // Construct the key
    564  aResult.mElement = targetElem;
    565  aResult.mAttributeName = attributeName;
    566  aResult.mAttributeNamespaceID = attributeNamespaceID;
    567 
    568  return true;
    569 }
    570 
    571 void SMILAnimationController::PreTraverse() { PreTraverseInSubtree(nullptr); }
    572 
    573 void SMILAnimationController::PreTraverseInSubtree(Element* aRoot) {
    574  MOZ_ASSERT(NS_IsMainThread());
    575 
    576  if (!mMightHavePendingStyleUpdates) {
    577    return;
    578  }
    579 
    580  nsPresContext* context = mDocument->GetPresContext();
    581  if (!context) {
    582    return;
    583  }
    584 
    585  for (SVGAnimationElement* animElement : mAnimationElementTable.Keys()) {
    586    SMILTargetIdentifier key;
    587    if (!GetTargetIdentifierForAnimation(animElement, key)) {
    588      // Something's wrong/missing about animation's target; skip this animation
    589      continue;
    590    }
    591 
    592    // Ignore restyles that aren't in the flattened tree subtree rooted at
    593    // aRoot.
    594    if (aRoot && !nsContentUtils::ContentIsFlattenedTreeDescendantOf(
    595                     key.mElement, aRoot)) {
    596      continue;
    597    }
    598 
    599    context->RestyleManager()->PostRestyleEventForAnimations(
    600        key.mElement, PseudoStyleRequest::NotPseudo(),
    601        RestyleHint::RESTYLE_SMIL);
    602  }
    603 
    604  // Only clear the mMightHavePendingStyleUpdates flag if we definitely posted
    605  // all restyles.
    606  if (!aRoot) {
    607    mMightHavePendingStyleUpdates = false;
    608  }
    609 }
    610 
    611 //----------------------------------------------------------------------
    612 // Add/remove child time containers
    613 
    614 nsresult SMILAnimationController::AddChild(SMILTimeContainer& aChild) {
    615  const bool wasEmpty = mChildContainerTable.IsEmpty();
    616  TimeContainerPtrKey* key = mChildContainerTable.PutEntry(&aChild);
    617  NS_ENSURE_TRUE(key, NS_ERROR_OUT_OF_MEMORY);
    618  if (wasEmpty) {
    619    UpdateSampling();
    620  }
    621  return NS_OK;
    622 }
    623 
    624 void SMILAnimationController::RemoveChild(SMILTimeContainer& aChild) {
    625  mChildContainerTable.RemoveEntry(&aChild);
    626  if (mChildContainerTable.IsEmpty()) {
    627    UpdateSampling();
    628  }
    629 }
    630 
    631 // Helper method
    632 nsRefreshDriver* SMILAnimationController::GetRefreshDriver() {
    633  if (!mDocument) {
    634    NS_ERROR("Requesting refresh driver after document has disconnected!");
    635    return nullptr;
    636  }
    637 
    638  nsPresContext* context = mDocument->GetPresContext();
    639  return context ? context->RefreshDriver() : nullptr;
    640 }
    641 
    642 void SMILAnimationController::FlagDocumentNeedsFlush() {
    643  if (PresShell* presShell = mDocument->GetPresShell()) {
    644    presShell->SetNeedStyleFlush();
    645  }
    646 }
    647 
    648 }  // namespace mozilla