LCOV - code coverage report
Current view: top level - dom/smil - nsSMILAnimationController.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 109 331 32.9 %
Date: 2017-07-14 16:53:18 Functions: 17 36 47.2 %
Legend: Lines: hit not hit

          Line data    Source code
       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 "nsSMILAnimationController.h"
       8             : 
       9             : #include <algorithm>
      10             : 
      11             : #include "mozilla/AutoRestore.h"
      12             : #include "mozilla/dom/Element.h"
      13             : #include "mozilla/dom/SVGAnimationElement.h"
      14             : #include "mozilla/RestyleManagerInlines.h"
      15             : #include "nsContentUtils.h"
      16             : #include "nsCSSProps.h"
      17             : #include "nsIDocument.h"
      18             : #include "nsIPresShell.h"
      19             : #include "nsIPresShellInlines.h"
      20             : #include "nsITimer.h"
      21             : #include "nsSMILCompositor.h"
      22             : #include "nsSMILCSSProperty.h"
      23             : #include "nsSMILTimedElement.h"
      24             : #include "RestyleTracker.h"
      25             : 
      26             : using namespace mozilla;
      27             : using namespace mozilla::dom;
      28             : 
      29             : //----------------------------------------------------------------------
      30             : // nsSMILAnimationController implementation
      31             : 
      32             : //----------------------------------------------------------------------
      33             : // ctors, dtors, factory methods
      34             : 
      35          22 : nsSMILAnimationController::nsSMILAnimationController(nsIDocument* aDoc)
      36             :   : mAvgTimeBetweenSamples(0),
      37             :     mResampleNeeded(false),
      38             :     mDeferredStartSampling(false),
      39             :     mRunningSample(false),
      40             :     mRegisteredWithRefreshDriver(false),
      41             :     mMightHavePendingStyleUpdates(false),
      42          22 :     mDocument(aDoc)
      43             : {
      44          22 :   MOZ_ASSERT(aDoc, "need a non-null document");
      45             : 
      46          22 :   nsRefreshDriver* refreshDriver = GetRefreshDriver();
      47          22 :   if (refreshDriver) {
      48           1 :     mStartTime = refreshDriver->MostRecentRefresh();
      49             :   } else {
      50          21 :     mStartTime = mozilla::TimeStamp::Now();
      51             :   }
      52          22 :   mCurrentSampleTime = mStartTime;
      53             : 
      54          22 :   Begin();
      55          22 : }
      56             : 
      57           0 : nsSMILAnimationController::~nsSMILAnimationController()
      58             : {
      59           0 :   NS_ASSERTION(mAnimationElementTable.Count() == 0,
      60             :                "Animation controller shouldn't be tracking any animation"
      61             :                " elements when it dies");
      62           0 :   NS_ASSERTION(!mRegisteredWithRefreshDriver,
      63             :                "Leaving stale entry in refresh driver's observer list");
      64           0 : }
      65             : 
      66             : void
      67           0 : nsSMILAnimationController::Disconnect()
      68             : {
      69           0 :   MOZ_ASSERT(mDocument, "disconnecting when we weren't connected...?");
      70           0 :   MOZ_ASSERT(mRefCnt.get() == 1,
      71             :              "Expecting to disconnect when doc is sole remaining owner");
      72           0 :   NS_ASSERTION(mPauseState & nsSMILTimeContainer::PAUSE_PAGEHIDE,
      73             :                "Expecting to be paused for pagehide before disconnect");
      74             : 
      75           0 :   StopSampling(GetRefreshDriver());
      76             : 
      77           0 :   mDocument = nullptr; // (raw pointer)
      78           0 : }
      79             : 
      80             : //----------------------------------------------------------------------
      81             : // nsSMILTimeContainer methods:
      82             : 
      83             : void
      84          22 : nsSMILAnimationController::Pause(uint32_t aType)
      85             : {
      86          22 :   nsSMILTimeContainer::Pause(aType);
      87             : 
      88          22 :   if (mPauseState) {
      89          22 :     mDeferredStartSampling = false;
      90          22 :     StopSampling(GetRefreshDriver());
      91             :   }
      92          22 : }
      93             : 
      94             : void
      95          23 : nsSMILAnimationController::Resume(uint32_t aType)
      96             : {
      97          23 :   bool wasPaused = (mPauseState != 0);
      98             :   // Update mCurrentSampleTime so that calls to GetParentTime--used for
      99             :   // calculating parent offsets--are accurate
     100          23 :   mCurrentSampleTime = mozilla::TimeStamp::Now();
     101             : 
     102          23 :   nsSMILTimeContainer::Resume(aType);
     103             : 
     104          23 :   if (wasPaused && !mPauseState && mChildContainerTable.Count()) {
     105           1 :     MaybeStartSampling(GetRefreshDriver());
     106           1 :     Sample(); // Run the first sample manually
     107             :   }
     108          23 : }
     109             : 
     110             : nsSMILTime
     111          68 : nsSMILAnimationController::GetParentTime() const
     112             : {
     113          68 :   return (nsSMILTime)(mCurrentSampleTime - mStartTime).ToMilliseconds();
     114             : }
     115             : 
     116             : //----------------------------------------------------------------------
     117             : // nsARefreshObserver methods:
     118          22 : NS_IMPL_ADDREF(nsSMILAnimationController)
     119           0 : NS_IMPL_RELEASE(nsSMILAnimationController)
     120             : 
     121             : // nsRefreshDriver Callback function
     122             : void
     123           0 : nsSMILAnimationController::WillRefresh(mozilla::TimeStamp aTime)
     124             : {
     125             :   // Although we never expect aTime to go backwards, when we initialise the
     126             :   // animation controller, if we can't get hold of a refresh driver we
     127             :   // initialise mCurrentSampleTime to Now(). It may be possible that after
     128             :   // doing so we get sampled by a refresh driver whose most recent refresh time
     129             :   // predates when we were initialised, so to be safe we make sure to take the
     130             :   // most recent time here.
     131           0 :   aTime = std::max(mCurrentSampleTime, aTime);
     132             : 
     133             :   // Sleep detection: If the time between samples is a whole lot greater than we
     134             :   // were expecting then we assume the computer went to sleep or someone's
     135             :   // messing with the clock. In that case, fiddle our parent offset and use our
     136             :   // average time between samples to calculate the new sample time. This
     137             :   // prevents us from hanging while trying to catch up on all the missed time.
     138             : 
     139             :   // Smoothing of coefficient for the average function. 0.2 should let us track
     140             :   // the sample rate reasonably tightly without being overly affected by
     141             :   // occasional delays.
     142             :   static const double SAMPLE_DUR_WEIGHTING = 0.2;
     143             :   // If the elapsed time exceeds our expectation by this number of times we'll
     144             :   // initiate special behaviour to basically ignore the intervening time.
     145             :   static const double SAMPLE_DEV_THRESHOLD = 200.0;
     146             : 
     147             :   nsSMILTime elapsedTime =
     148           0 :     (nsSMILTime)(aTime - mCurrentSampleTime).ToMilliseconds();
     149           0 :   if (mAvgTimeBetweenSamples == 0) {
     150             :     // First sample.
     151           0 :     mAvgTimeBetweenSamples = elapsedTime;
     152             :   } else {
     153           0 :     if (elapsedTime > SAMPLE_DEV_THRESHOLD * mAvgTimeBetweenSamples) {
     154             :       // Unexpectedly long delay between samples.
     155             :       NS_WARNING("Detected really long delay between samples, continuing from "
     156           0 :                  "previous sample");
     157           0 :       mParentOffset += elapsedTime - mAvgTimeBetweenSamples;
     158             :     }
     159             :     // Update the moving average. Due to truncation here the average will
     160             :     // normally be a little less than it should be but that's probably ok.
     161           0 :     mAvgTimeBetweenSamples =
     162           0 :       (nsSMILTime)(elapsedTime * SAMPLE_DUR_WEIGHTING +
     163           0 :       mAvgTimeBetweenSamples * (1.0 - SAMPLE_DUR_WEIGHTING));
     164             :   }
     165           0 :   mCurrentSampleTime = aTime;
     166             : 
     167           0 :   Sample();
     168           0 : }
     169             : 
     170             : //----------------------------------------------------------------------
     171             : // Animation element registration methods:
     172             : 
     173             : void
     174           0 : nsSMILAnimationController::RegisterAnimationElement(
     175             :                                   SVGAnimationElement* aAnimationElement)
     176             : {
     177           0 :   mAnimationElementTable.PutEntry(aAnimationElement);
     178           0 :   if (mDeferredStartSampling) {
     179           0 :     mDeferredStartSampling = false;
     180           0 :     if (mChildContainerTable.Count()) {
     181             :       // mAnimationElementTable was empty, but now we've added its 1st element
     182           0 :       MOZ_ASSERT(mAnimationElementTable.Count() == 1,
     183             :                  "we shouldn't have deferred sampling if we already had "
     184             :                  "animations registered");
     185           0 :       StartSampling(GetRefreshDriver());
     186           0 :       Sample(); // Run the first sample manually
     187             :     } // else, don't sample until a time container is registered (via AddChild)
     188             :   }
     189           0 : }
     190             : 
     191             : void
     192           0 : nsSMILAnimationController::UnregisterAnimationElement(
     193             :                                   SVGAnimationElement* aAnimationElement)
     194             : {
     195           0 :   mAnimationElementTable.RemoveEntry(aAnimationElement);
     196           0 : }
     197             : 
     198             : //----------------------------------------------------------------------
     199             : // Page show/hide
     200             : 
     201             : void
     202           1 : nsSMILAnimationController::OnPageShow()
     203             : {
     204           1 :   Resume(nsSMILTimeContainer::PAUSE_PAGEHIDE);
     205           1 : }
     206             : 
     207             : void
     208           1 : nsSMILAnimationController::OnPageHide()
     209             : {
     210           1 :   Pause(nsSMILTimeContainer::PAUSE_PAGEHIDE);
     211           1 : }
     212             : 
     213             : //----------------------------------------------------------------------
     214             : // Cycle-collection support
     215             : 
     216             : void
     217           0 : nsSMILAnimationController::Traverse(
     218             :     nsCycleCollectionTraversalCallback* aCallback)
     219             : {
     220             :   // Traverse last compositor table
     221           0 :   if (mLastCompositorTable) {
     222           0 :     for (auto iter = mLastCompositorTable->Iter(); !iter.Done(); iter.Next()) {
     223           0 :       nsSMILCompositor* compositor = iter.Get();
     224           0 :       compositor->Traverse(aCallback);
     225             :     }
     226             :   }
     227           0 : }
     228             : 
     229             : void
     230           0 : nsSMILAnimationController::Unlink()
     231             : {
     232           0 :   mLastCompositorTable = nullptr;
     233           0 : }
     234             : 
     235             : //----------------------------------------------------------------------
     236             : // Refresh driver lifecycle related methods
     237             : 
     238             : void
     239          21 : nsSMILAnimationController::NotifyRefreshDriverCreated(
     240             :     nsRefreshDriver* aRefreshDriver)
     241             : {
     242          21 :   if (!mPauseState) {
     243           0 :     MaybeStartSampling(aRefreshDriver);
     244             :   }
     245          21 : }
     246             : 
     247             : void
     248           0 : nsSMILAnimationController::NotifyRefreshDriverDestroying(
     249             :     nsRefreshDriver* aRefreshDriver)
     250             : {
     251           0 :   if (!mPauseState && !mDeferredStartSampling) {
     252           0 :     StopSampling(aRefreshDriver);
     253             :   }
     254           0 : }
     255             : 
     256             : //----------------------------------------------------------------------
     257             : // Timer-related implementation helpers
     258             : 
     259             : void
     260           0 : nsSMILAnimationController::StartSampling(nsRefreshDriver* aRefreshDriver)
     261             : {
     262           0 :   NS_ASSERTION(mPauseState == 0, "Starting sampling but controller is paused");
     263           0 :   NS_ASSERTION(!mDeferredStartSampling,
     264             :                "Started sampling but the deferred start flag is still set");
     265           0 :   if (aRefreshDriver) {
     266           0 :     MOZ_ASSERT(!mRegisteredWithRefreshDriver,
     267             :                "Redundantly registering with refresh driver");
     268           0 :     MOZ_ASSERT(!GetRefreshDriver() || aRefreshDriver == GetRefreshDriver(),
     269             :                "Starting sampling with wrong refresh driver");
     270             :     // We're effectively resuming from a pause so update our current sample time
     271             :     // or else it will confuse our "average time between samples" calculations.
     272           0 :     mCurrentSampleTime = mozilla::TimeStamp::Now();
     273           0 :     aRefreshDriver->AddRefreshObserver(this, FlushType::Style);
     274           0 :     mRegisteredWithRefreshDriver = true;
     275             :   }
     276           0 : }
     277             : 
     278             : void
     279          22 : nsSMILAnimationController::StopSampling(nsRefreshDriver* aRefreshDriver)
     280             : {
     281          22 :   if (aRefreshDriver && mRegisteredWithRefreshDriver) {
     282             :     // NOTE: The document might already have been detached from its PresContext
     283             :     // (and RefreshDriver), which would make GetRefreshDriver() return null.
     284           0 :     MOZ_ASSERT(!GetRefreshDriver() || aRefreshDriver == GetRefreshDriver(),
     285             :                "Stopping sampling with wrong refresh driver");
     286           0 :     aRefreshDriver->RemoveRefreshObserver(this, FlushType::Style);
     287           0 :     mRegisteredWithRefreshDriver = false;
     288             :   }
     289          22 : }
     290             : 
     291             : void
     292           1 : nsSMILAnimationController::MaybeStartSampling(nsRefreshDriver* aRefreshDriver)
     293             : {
     294           1 :   if (mDeferredStartSampling) {
     295             :     // We've received earlier 'MaybeStartSampling' calls, and we're
     296             :     // deferring until we get a registered animation.
     297           0 :     return;
     298             :   }
     299             : 
     300           1 :   if (mAnimationElementTable.Count()) {
     301           0 :     StartSampling(aRefreshDriver);
     302             :   } else {
     303           1 :     mDeferredStartSampling = true;
     304             :   }
     305             : }
     306             : 
     307             : //----------------------------------------------------------------------
     308             : // Sample-related methods and callbacks
     309             : 
     310             : void
     311           1 : nsSMILAnimationController::DoSample()
     312             : {
     313           1 :   DoSample(true); // Skip unchanged time containers
     314           1 : }
     315             : 
     316             : void
     317          58 : nsSMILAnimationController::DoSample(bool aSkipUnchangedContainers)
     318             : {
     319          58 :   if (!mDocument) {
     320           0 :     NS_ERROR("Shouldn't be sampling after document has disconnected");
     321           0 :     return;
     322             :   }
     323          58 :   if (mRunningSample) {
     324           0 :     NS_ERROR("Shouldn't be recursively sampling");
     325           0 :     return;
     326             :   }
     327             : 
     328          58 :   bool isStyleFlushNeeded = mResampleNeeded;
     329          58 :   mResampleNeeded = false;
     330             : 
     331          58 :   nsCOMPtr<nsIDocument> document(mDocument);  // keeps 'this' alive too
     332             : 
     333             :   // Set running sample flag -- do this before flushing styles so that when we
     334             :   // flush styles we don't end up requesting extra samples
     335          58 :   AutoRestore<bool> autoRestoreRunningSample(mRunningSample);
     336          58 :   mRunningSample = true;
     337             : 
     338             :   // STEP 1: Bring model up to date
     339             :   // (i)  Rewind elements where necessary
     340             :   // (ii) Run milestone samples
     341          58 :   RewindElements();
     342          58 :   DoMilestoneSamples();
     343             : 
     344             :   // STEP 2: Sample the child time containers
     345             :   //
     346             :   // When we sample the child time containers they will simply record the sample
     347             :   // time in document time.
     348          58 :   TimeContainerHashtable activeContainers(mChildContainerTable.Count());
     349         116 :   for (auto iter = mChildContainerTable.Iter(); !iter.Done(); iter.Next()) {
     350          58 :     nsSMILTimeContainer* container = iter.Get()->GetKey();
     351          58 :     if (!container) {
     352           0 :       continue;
     353             :     }
     354             : 
     355         174 :     if (!container->IsPausedByType(nsSMILTimeContainer::PAUSE_BEGIN) &&
     356          58 :         (container->NeedsSample() || !aSkipUnchangedContainers)) {
     357          58 :       container->ClearMilestones();
     358          58 :       container->Sample();
     359          58 :       container->MarkSeekFinished();
     360          58 :       activeContainers.PutEntry(container);
     361             :     }
     362             :   }
     363             : 
     364             :   // STEP 3: (i)  Sample the timed elements AND
     365             :   //         (ii) Create a table of compositors
     366             :   //
     367             :   // (i) Here we sample the timed elements (fetched from the
     368             :   // SVGAnimationElements) which determine from the active time if the
     369             :   // element is active and what its simple time etc. is. This information is
     370             :   // then passed to its time client (nsSMILAnimationFunction).
     371             :   //
     372             :   // (ii) During the same loop we also build up a table that contains one
     373             :   // compositor for each animated attribute and which maps animated elements to
     374             :   // the corresponding compositor for their target attribute.
     375             :   //
     376             :   // Note that this compositor table needs to be allocated on the heap so we can
     377             :   // store it until the next sample. This lets us find out which elements were
     378             :   // animated in sample 'n-1' but not in sample 'n' (and hence need to have
     379             :   // their animation effects removed in sample 'n').
     380             :   //
     381             :   // Parts (i) and (ii) are not functionally related but we combine them here to
     382             :   // save iterating over the animation elements twice.
     383             : 
     384             :   // Create the compositor table
     385             :   nsAutoPtr<nsSMILCompositorTable>
     386          58 :     currentCompositorTable(new nsSMILCompositorTable(0));
     387             :   nsTArray<RefPtr<SVGAnimationElement>>
     388          58 :     animElems(mAnimationElementTable.Count());
     389             : 
     390          58 :   for (auto iter = mAnimationElementTable.Iter(); !iter.Done(); iter.Next()) {
     391           0 :     SVGAnimationElement* animElem = iter.Get()->GetKey();
     392           0 :     SampleTimedElement(animElem, &activeContainers);
     393           0 :     AddAnimationToCompositorTable(animElem,
     394             :                                   currentCompositorTable,
     395           0 :                                   isStyleFlushNeeded);
     396           0 :     animElems.AppendElement(animElem);
     397             :   }
     398          58 :   activeContainers.Clear();
     399             : 
     400             :   // STEP 4: Compare previous sample's compositors against this sample's.
     401             :   // (Transfer cached base values across, & remove animation effects from
     402             :   // no-longer-animated targets.)
     403          58 :   if (mLastCompositorTable) {
     404             :     // * Transfer over cached base values, from last sample's compositors
     405           0 :     for (auto iter = currentCompositorTable->Iter();
     406           0 :          !iter.Done();
     407           0 :          iter.Next()) {
     408           0 :       nsSMILCompositor* compositor = iter.Get();
     409             :       nsSMILCompositor* lastCompositor =
     410           0 :         mLastCompositorTable->GetEntry(compositor->GetKey());
     411             : 
     412           0 :       if (lastCompositor) {
     413           0 :         compositor->StealCachedBaseValue(lastCompositor);
     414             :       }
     415             :     }
     416             : 
     417             :     // * For each compositor in current sample's hash table, remove entry from
     418             :     // prev sample's hash table -- we don't need to clear animation
     419             :     // effects of those compositors, since they're still being animated.
     420           0 :     for (auto iter = currentCompositorTable->Iter();
     421           0 :          !iter.Done();
     422           0 :          iter.Next()) {
     423           0 :       mLastCompositorTable->RemoveEntry(iter.Get()->GetKey());
     424             :     }
     425             : 
     426             :     // * For each entry that remains in prev sample's hash table (i.e. for
     427             :     // every target that's no longer animated), clear animation effects.
     428           0 :     for (auto iter = mLastCompositorTable->Iter(); !iter.Done(); iter.Next()) {
     429           0 :       iter.Get()->ClearAnimationEffects();
     430             :     }
     431             :   }
     432             : 
     433             :   // return early if there are no active animations to avoid a style flush
     434          58 :   if (currentCompositorTable->Count() == 0) {
     435          58 :     mLastCompositorTable = nullptr;
     436          58 :     return;
     437             :   }
     438             : 
     439           0 :   if (isStyleFlushNeeded) {
     440           0 :     document->FlushPendingNotifications(FlushType::Style);
     441             :   }
     442             : 
     443             :   // WARNING:
     444             :   // WARNING: the above flush may have destroyed the pres shell and/or
     445             :   // WARNING: frames and other layout related objects.
     446             :   // WARNING:
     447             : 
     448             :   // STEP 5: Compose currently-animated attributes.
     449             :   // XXXdholbert: This step traverses our animation targets in an effectively
     450             :   // random order. For animation from/to 'inherit' values to work correctly
     451             :   // when the inherited value is *also* being animated, we really should be
     452             :   // traversing our animated nodes in an ancestors-first order (bug 501183)
     453           0 :   bool mightHavePendingStyleUpdates = false;
     454           0 :   for (auto iter = currentCompositorTable->Iter(); !iter.Done(); iter.Next()) {
     455           0 :     iter.Get()->ComposeAttribute(mightHavePendingStyleUpdates);
     456             :   }
     457             : 
     458             :   // Update last compositor table
     459           0 :   mLastCompositorTable = currentCompositorTable.forget();
     460           0 :   mMightHavePendingStyleUpdates = mightHavePendingStyleUpdates;
     461             : 
     462           0 :   NS_ASSERTION(!mResampleNeeded, "Resample dirty flag set during sample!");
     463             : }
     464             : 
     465             : void
     466          58 : nsSMILAnimationController::RewindElements()
     467             : {
     468          58 :   bool rewindNeeded = false;
     469         116 :   for (auto iter = mChildContainerTable.Iter(); !iter.Done(); iter.Next()) {
     470          58 :     nsSMILTimeContainer* container = iter.Get()->GetKey();
     471          58 :     if (container->NeedsRewind()) {
     472           0 :       rewindNeeded = true;
     473           0 :       break;
     474             :     }
     475             :   }
     476             : 
     477          58 :   if (!rewindNeeded)
     478          58 :     return;
     479             : 
     480           0 :   for (auto iter = mAnimationElementTable.Iter(); !iter.Done(); iter.Next()) {
     481           0 :     SVGAnimationElement* animElem = iter.Get()->GetKey();
     482           0 :     nsSMILTimeContainer* timeContainer = animElem->GetTimeContainer();
     483           0 :     if (timeContainer && timeContainer->NeedsRewind()) {
     484           0 :       animElem->TimedElement().Rewind();
     485             :     }
     486             :   }
     487             : 
     488           0 :   for (auto iter = mChildContainerTable.Iter(); !iter.Done(); iter.Next()) {
     489           0 :     iter.Get()->GetKey()->ClearNeedsRewind();
     490             :   }
     491             : }
     492             : 
     493             : void
     494          58 : nsSMILAnimationController::DoMilestoneSamples()
     495             : {
     496             :   // We need to sample the timing model but because SMIL operates independently
     497             :   // of the frame-rate, we can get one sample at t=0s and the next at t=10min.
     498             :   //
     499             :   // In between those two sample times a whole string of significant events
     500             :   // might be expected to take place: events firing, new interdependencies
     501             :   // between animations resolved and dissolved, etc.
     502             :   //
     503             :   // Furthermore, at any given time, we want to sample all the intervals that
     504             :   // end at that time BEFORE any that begin. This behaviour is implied by SMIL's
     505             :   // endpoint-exclusive timing model.
     506             :   //
     507             :   // So we have the animations (specifically the timed elements) register the
     508             :   // next significant moment (called a milestone) in their lifetime and then we
     509             :   // step through the model at each of these moments and sample those animations
     510             :   // registered for those times. This way events can fire in the correct order,
     511             :   // dependencies can be resolved etc.
     512             : 
     513          58 :   nsSMILTime sampleTime = INT64_MIN;
     514             : 
     515             :   while (true) {
     516             :     // We want to find any milestones AT OR BEFORE the current sample time so we
     517             :     // initialise the next milestone to the moment after (1ms after, to be
     518             :     // precise) the current sample time and see if there are any milestones
     519             :     // before that. Any other milestones will be dealt with in a subsequent
     520             :     // sample.
     521          58 :     nsSMILMilestone nextMilestone(GetCurrentTime() + 1, true);
     522         116 :     for (auto iter = mChildContainerTable.Iter(); !iter.Done(); iter.Next()) {
     523          58 :       nsSMILTimeContainer* container = iter.Get()->GetKey();
     524          58 :       if (container->IsPausedByType(nsSMILTimeContainer::PAUSE_BEGIN)) {
     525           0 :         continue;
     526             :       }
     527          58 :       nsSMILMilestone thisMilestone;
     528             :       bool didGetMilestone =
     529          58 :         container->GetNextMilestoneInParentTime(thisMilestone);
     530          58 :       if (didGetMilestone && thisMilestone < nextMilestone) {
     531           0 :         nextMilestone = thisMilestone;
     532             :       }
     533             :     }
     534             : 
     535          58 :     if (nextMilestone.mTime > GetCurrentTime()) {
     536          58 :       break;
     537             :     }
     538             : 
     539           0 :     nsTArray<RefPtr<mozilla::dom::SVGAnimationElement>> elements;
     540           0 :     for (auto iter = mChildContainerTable.Iter(); !iter.Done(); iter.Next()) {
     541           0 :       nsSMILTimeContainer* container = iter.Get()->GetKey();
     542           0 :       if (container->IsPausedByType(nsSMILTimeContainer::PAUSE_BEGIN)) {
     543           0 :         continue;
     544             :       }
     545           0 :       container->PopMilestoneElementsAtMilestone(nextMilestone, elements);
     546             :     }
     547             : 
     548           0 :     uint32_t length = elements.Length();
     549             : 
     550             :     // During the course of a sampling we don't want to actually go backwards.
     551             :     // Due to negative offsets, early ends and the like, a timed element might
     552             :     // register a milestone that is actually in the past. That's fine, but it's
     553             :     // still only going to get *sampled* with whatever time we're up to and no
     554             :     // earlier.
     555             :     //
     556             :     // Because we're only performing this clamping at the last moment, the
     557             :     // animations will still all get sampled in the correct order and
     558             :     // dependencies will be appropriately resolved.
     559           0 :     sampleTime = std::max(nextMilestone.mTime, sampleTime);
     560             : 
     561           0 :     for (uint32_t i = 0; i < length; ++i) {
     562           0 :       SVGAnimationElement* elem = elements[i].get();
     563           0 :       MOZ_ASSERT(elem, "nullptr animation element in list");
     564           0 :       nsSMILTimeContainer* container = elem->GetTimeContainer();
     565           0 :       if (!container)
     566             :         // The container may be nullptr if the element has been detached from its
     567             :         // parent since registering a milestone.
     568           0 :         continue;
     569             : 
     570             :       nsSMILTimeValue containerTimeValue =
     571           0 :         container->ParentToContainerTime(sampleTime);
     572           0 :       if (!containerTimeValue.IsDefinite())
     573           0 :         continue;
     574             : 
     575             :       // Clamp the converted container time to non-negative values.
     576           0 :       nsSMILTime containerTime = std::max<nsSMILTime>(0, containerTimeValue.GetMillis());
     577             : 
     578           0 :       if (nextMilestone.mIsEnd) {
     579           0 :         elem->TimedElement().SampleEndAt(containerTime);
     580             :       } else {
     581           0 :         elem->TimedElement().SampleAt(containerTime);
     582             :       }
     583             :     }
     584           0 :   }
     585          58 : }
     586             : 
     587             : /*static*/ void
     588           0 : nsSMILAnimationController::SampleTimedElement(
     589             :   SVGAnimationElement* aElement, TimeContainerHashtable* aActiveContainers)
     590             : {
     591           0 :   nsSMILTimeContainer* timeContainer = aElement->GetTimeContainer();
     592           0 :   if (!timeContainer)
     593           0 :     return;
     594             : 
     595             :   // We'd like to call timeContainer->NeedsSample() here and skip all timed
     596             :   // elements that belong to paused time containers that don't need a sample,
     597             :   // but that doesn't work because we've already called Sample() on all the time
     598             :   // containers so the paused ones don't need a sample any more and they'll
     599             :   // return false.
     600             :   //
     601             :   // Instead we build up a hashmap of active time containers during the previous
     602             :   // step (SampleTimeContainer) and then test here if the container for this
     603             :   // timed element is in the list.
     604           0 :   if (!aActiveContainers->GetEntry(timeContainer))
     605           0 :     return;
     606             : 
     607           0 :   nsSMILTime containerTime = timeContainer->GetCurrentTime();
     608             : 
     609           0 :   MOZ_ASSERT(!timeContainer->IsSeeking(),
     610             :              "Doing a regular sample but the time container is still seeking");
     611           0 :   aElement->TimedElement().SampleAt(containerTime);
     612             : }
     613             : 
     614             : /*static*/ void
     615           0 : nsSMILAnimationController::AddAnimationToCompositorTable(
     616             :   SVGAnimationElement* aElement,
     617             :   nsSMILCompositorTable* aCompositorTable,
     618             :   bool& aStyleFlushNeeded)
     619             : {
     620             :   // Add a compositor to the hash table if there's not already one there
     621           0 :   nsSMILTargetIdentifier key;
     622           0 :   if (!GetTargetIdentifierForAnimation(aElement, key))
     623             :     // Something's wrong/missing about animation's target; skip this animation
     624           0 :     return;
     625             : 
     626           0 :   nsSMILAnimationFunction& func = aElement->AnimationFunction();
     627             : 
     628             :   // Only add active animation functions. If there are no active animations
     629             :   // targeting an attribute, no compositor will be created and any previously
     630             :   // applied animations will be cleared.
     631           0 :   if (func.IsActiveOrFrozen()) {
     632             :     // Look up the compositor for our target, & add our animation function
     633             :     // to its list of animation functions.
     634           0 :     nsSMILCompositor* result = aCompositorTable->PutEntry(key);
     635           0 :     result->AddAnimationFunction(&func);
     636             : 
     637           0 :   } else if (func.HasChanged()) {
     638             :     // Look up the compositor for our target, and force it to skip the
     639             :     // "nothing's changed so don't bother compositing" optimization for this
     640             :     // sample. |func| is inactive, but it's probably *newly* inactive (since
     641             :     // it's got HasChanged() == true), so we need to make sure to recompose
     642             :     // its target.
     643           0 :     nsSMILCompositor* result = aCompositorTable->PutEntry(key);
     644           0 :     result->ToggleForceCompositing();
     645             : 
     646             :     // We've now made sure that |func|'s inactivity will be reflected as of
     647             :     // this sample. We need to clear its HasChanged() flag so that it won't
     648             :     // trigger this same clause in future samples (until it changes again).
     649           0 :     func.ClearHasChanged();
     650             :   }
     651           0 :   aStyleFlushNeeded |= func.ValueNeedsReparsingEverySample();
     652             : }
     653             : 
     654             : static inline bool
     655           0 : IsTransformAttribute(int32_t aNamespaceID, nsIAtom *aAttributeName)
     656             : {
     657           0 :   return aNamespaceID == kNameSpaceID_None &&
     658           0 :          (aAttributeName == nsGkAtoms::transform ||
     659           0 :           aAttributeName == nsGkAtoms::patternTransform ||
     660           0 :           aAttributeName == nsGkAtoms::gradientTransform);
     661             : }
     662             : 
     663             : // Helper function that, given a SVGAnimationElement, looks up its target
     664             : // element & target attribute and populates a nsSMILTargetIdentifier
     665             : // for this target.
     666             : /*static*/ bool
     667           0 : nsSMILAnimationController::GetTargetIdentifierForAnimation(
     668             :     SVGAnimationElement* aAnimElem, nsSMILTargetIdentifier& aResult)
     669             : {
     670             :   // Look up target (animated) element
     671           0 :   Element* targetElem = aAnimElem->GetTargetElementContent();
     672           0 :   if (!targetElem)
     673             :     // Animation has no target elem -- skip it.
     674           0 :     return false;
     675             : 
     676             :   // Look up target (animated) attribute
     677             :   // SMILANIM section 3.1, attributeName may
     678             :   // have an XMLNS prefix to indicate the XML namespace.
     679           0 :   nsCOMPtr<nsIAtom> attributeName;
     680             :   int32_t attributeNamespaceID;
     681           0 :   if (!aAnimElem->GetTargetAttributeName(&attributeNamespaceID,
     682           0 :                                          getter_AddRefs(attributeName)))
     683             :     // Animation has no target attr -- skip it.
     684           0 :     return false;
     685             : 
     686             :   // animateTransform can only animate transforms, conversely transforms
     687             :   // can only be animated by animateTransform
     688           0 :   if (IsTransformAttribute(attributeNamespaceID, attributeName) !=
     689           0 :       (aAnimElem->IsSVGElement(nsGkAtoms::animateTransform)))
     690           0 :     return false;
     691             : 
     692             :   // Construct the key
     693           0 :   aResult.mElement              = targetElem;
     694           0 :   aResult.mAttributeName        = attributeName;
     695           0 :   aResult.mAttributeNamespaceID = attributeNamespaceID;
     696             : 
     697           0 :   return true;
     698             : }
     699             : 
     700             : void
     701           0 : nsSMILAnimationController::AddStyleUpdatesTo(RestyleTracker& aTracker)
     702             : {
     703           0 :   MOZ_ASSERT(mMightHavePendingStyleUpdates,
     704             :              "Should only add style updates when we think we might have some");
     705             : 
     706           0 :   for (auto iter = mAnimationElementTable.Iter(); !iter.Done(); iter.Next()) {
     707           0 :     SVGAnimationElement* animElement = iter.Get()->GetKey();
     708             : 
     709           0 :     nsSMILTargetIdentifier key;
     710           0 :     if (!GetTargetIdentifierForAnimation(animElement, key)) {
     711             :       // Something's wrong/missing about animation's target; skip this animation
     712           0 :       continue;
     713             :     }
     714             : 
     715           0 :     aTracker.AddPendingRestyle(key.mElement,
     716             :                                eRestyle_StyleAttribute_Animations,
     717           0 :                                nsChangeHint(0));
     718             :   }
     719             : 
     720           0 :   mMightHavePendingStyleUpdates = false;
     721           0 : }
     722             : 
     723             : bool
     724           0 : nsSMILAnimationController::PreTraverse()
     725             : {
     726           0 :   return PreTraverseInSubtree(nullptr);
     727             : }
     728             : 
     729             : bool
     730           0 : nsSMILAnimationController::PreTraverseInSubtree(Element* aRoot)
     731             : {
     732           0 :   MOZ_ASSERT(NS_IsMainThread());
     733             : 
     734           0 :   if (!mMightHavePendingStyleUpdates) {
     735           0 :     return false;
     736             :   }
     737             : 
     738           0 :   nsIPresShell* shell = mDocument->GetShell();
     739           0 :   if (!shell) {
     740           0 :     return false;
     741             :   }
     742             : 
     743           0 :   nsPresContext* context = shell->GetPresContext();
     744           0 :   if (!context) {
     745           0 :     return false;
     746             :   }
     747           0 :   MOZ_ASSERT(context->RestyleManager()->IsServo(),
     748             :              "PreTraverse should only be called for the servo style system");
     749             : 
     750           0 :   bool foundElementsNeedingRestyle = false;
     751           0 :   for (auto iter = mAnimationElementTable.Iter(); !iter.Done(); iter.Next()) {
     752           0 :     SVGAnimationElement* animElement = iter.Get()->GetKey();
     753             : 
     754           0 :     nsSMILTargetIdentifier key;
     755           0 :     if (!GetTargetIdentifierForAnimation(animElement, key)) {
     756             :       // Something's wrong/missing about animation's target; skip this animation
     757           0 :       continue;
     758             :     }
     759             : 
     760             :     // Ignore restyles that aren't in the flattened tree subtree rooted at
     761             :     // aRoot.
     762           0 :     if (aRoot &&
     763           0 :         !nsContentUtils::ContentIsFlattenedTreeDescendantOf(key.mElement,
     764           0 :                                                             aRoot)) {
     765           0 :       continue;
     766             :     }
     767             : 
     768           0 :     context->RestyleManager()->AsServo()->
     769             :       PostRestyleEventForAnimations(key.mElement,
     770             :                                     CSSPseudoElementType::NotPseudo,
     771           0 :                                     eRestyle_StyleAttribute_Animations);
     772             : 
     773           0 :     foundElementsNeedingRestyle = true;
     774             :   }
     775             : 
     776             :   // Only clear the mMightHavePendingStyleUpdates flag if we definitely posted
     777             :   // all restyles.
     778           0 :   if (!aRoot) {
     779           0 :     mMightHavePendingStyleUpdates = false;
     780             :   }
     781             : 
     782           0 :   return foundElementsNeedingRestyle;
     783             : }
     784             : 
     785             : //----------------------------------------------------------------------
     786             : // Add/remove child time containers
     787             : 
     788             : nsresult
     789          22 : nsSMILAnimationController::AddChild(nsSMILTimeContainer& aChild)
     790             : {
     791          22 :   TimeContainerPtrKey* key = mChildContainerTable.PutEntry(&aChild);
     792          22 :   NS_ENSURE_TRUE(key, NS_ERROR_OUT_OF_MEMORY);
     793             : 
     794          22 :   if (!mPauseState && mChildContainerTable.Count() == 1) {
     795           0 :     MaybeStartSampling(GetRefreshDriver());
     796           0 :     Sample(); // Run the first sample manually
     797             :   }
     798             : 
     799          22 :   return NS_OK;
     800             : }
     801             : 
     802             : void
     803           0 : nsSMILAnimationController::RemoveChild(nsSMILTimeContainer& aChild)
     804             : {
     805           0 :   mChildContainerTable.RemoveEntry(&aChild);
     806             : 
     807           0 :   if (!mPauseState && mChildContainerTable.Count() == 0) {
     808           0 :     StopSampling(GetRefreshDriver());
     809             :   }
     810           0 : }
     811             : 
     812             : // Helper method
     813             : nsRefreshDriver*
     814          45 : nsSMILAnimationController::GetRefreshDriver()
     815             : {
     816          45 :   if (!mDocument) {
     817           0 :     NS_ERROR("Requesting refresh driver after document has disconnected!");
     818           0 :     return nullptr;
     819             :   }
     820             : 
     821          45 :   nsIPresShell* shell = mDocument->GetShell();
     822          45 :   if (!shell) {
     823          42 :     return nullptr;
     824             :   }
     825             : 
     826           3 :   nsPresContext* context = shell->GetPresContext();
     827           3 :   return context ? context->RefreshDriver() : nullptr;
     828             : }
     829             : 
     830             : void
     831          57 : nsSMILAnimationController::FlagDocumentNeedsFlush()
     832             : {
     833          57 :   if (nsIPresShell* shell = mDocument->GetShell()) {
     834          57 :     shell->SetNeedStyleFlush();
     835             :   }
     836          57 : }

Generated by: LCOV version 1.13