dom/smil/nsSMILAnimationController.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

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

mercurial