dom/smil/nsSMILTimedElement.cpp

Tue, 06 Jan 2015 21:39:09 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Tue, 06 Jan 2015 21:39:09 +0100
branch
TOR_BUG_9701
changeset 8
97036ab72558
permissions
-rw-r--r--

Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.

     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 "mozilla/DebugOnly.h"
     8 #include "mozilla/BasicEvents.h"
     9 #include "mozilla/EventDispatcher.h"
    10 #include "mozilla/dom/SVGAnimationElement.h"
    11 #include "nsSMILTimedElement.h"
    12 #include "nsAttrValueInlines.h"
    13 #include "nsSMILAnimationFunction.h"
    14 #include "nsSMILTimeValue.h"
    15 #include "nsSMILTimeValueSpec.h"
    16 #include "nsSMILInstanceTime.h"
    17 #include "nsSMILParserUtils.h"
    18 #include "nsSMILTimeContainer.h"
    19 #include "nsGkAtoms.h"
    20 #include "nsReadableUtils.h"
    21 #include "nsMathUtils.h"
    22 #include "nsThreadUtils.h"
    23 #include "nsIPresShell.h"
    24 #include "prdtoa.h"
    25 #include "plstr.h"
    26 #include "prtime.h"
    27 #include "nsString.h"
    28 #include "mozilla/AutoRestore.h"
    29 #include "nsCharSeparatedTokenizer.h"
    30 #include <algorithm>
    32 using namespace mozilla;
    33 using namespace mozilla::dom;
    35 //----------------------------------------------------------------------
    36 // Helper class: InstanceTimeComparator
    38 // Upon inserting an instance time into one of our instance time lists we assign
    39 // it a serial number. This allows us to sort the instance times in such a way
    40 // that where we have several equal instance times, the ones added later will
    41 // sort later. This means that when we call UpdateCurrentInterval during the
    42 // waiting state we won't unnecessarily change the begin instance.
    43 //
    44 // The serial number also means that every instance time has an unambiguous
    45 // position in the array so we can use RemoveElementSorted and the like.
    46 bool
    47 nsSMILTimedElement::InstanceTimeComparator::Equals(
    48     const nsSMILInstanceTime* aElem1,
    49     const nsSMILInstanceTime* aElem2) const
    50 {
    51   NS_ABORT_IF_FALSE(aElem1 && aElem2,
    52       "Trying to compare null instance time pointers");
    53   NS_ABORT_IF_FALSE(aElem1->Serial() && aElem2->Serial(),
    54       "Instance times have not been assigned serial numbers");
    55   NS_ABORT_IF_FALSE(aElem1 == aElem2 || aElem1->Serial() != aElem2->Serial(),
    56       "Serial numbers are not unique");
    58   return aElem1->Serial() == aElem2->Serial();
    59 }
    61 bool
    62 nsSMILTimedElement::InstanceTimeComparator::LessThan(
    63     const nsSMILInstanceTime* aElem1,
    64     const nsSMILInstanceTime* aElem2) const
    65 {
    66   NS_ABORT_IF_FALSE(aElem1 && aElem2,
    67       "Trying to compare null instance time pointers");
    68   NS_ABORT_IF_FALSE(aElem1->Serial() && aElem2->Serial(),
    69       "Instance times have not been assigned serial numbers");
    71   int8_t cmp = aElem1->Time().CompareTo(aElem2->Time());
    72   return cmp == 0 ? aElem1->Serial() < aElem2->Serial() : cmp < 0;
    73 }
    75 //----------------------------------------------------------------------
    76 // Helper class: AsyncTimeEventRunner
    78 namespace
    79 {
    80   class AsyncTimeEventRunner : public nsRunnable
    81   {
    82   protected:
    83     nsRefPtr<nsIContent> mTarget;
    84     uint32_t             mMsg;
    85     int32_t              mDetail;
    87   public:
    88     AsyncTimeEventRunner(nsIContent* aTarget, uint32_t aMsg, int32_t aDetail)
    89       : mTarget(aTarget), mMsg(aMsg), mDetail(aDetail)
    90     {
    91     }
    93     NS_IMETHOD Run()
    94     {
    95       InternalUIEvent event(true, mMsg);
    96       event.eventStructType = NS_SMIL_TIME_EVENT;
    97       event.detail = mDetail;
    99       nsPresContext* context = nullptr;
   100       nsIDocument* doc = mTarget->GetCurrentDoc();
   101       if (doc) {
   102         nsCOMPtr<nsIPresShell> shell = doc->GetShell();
   103         if (shell) {
   104           context = shell->GetPresContext();
   105         }
   106       }
   108       return EventDispatcher::Dispatch(mTarget, context, &event);
   109     }
   110   };
   111 }
   113 //----------------------------------------------------------------------
   114 // Helper class: AutoIntervalUpdateBatcher
   116 // Stack-based helper class to set the mDeferIntervalUpdates flag on an
   117 // nsSMILTimedElement and perform the UpdateCurrentInterval when the object is
   118 // destroyed.
   119 //
   120 // If several of these objects are allocated on the stack, the update will not
   121 // be performed until the last object for a given nsSMILTimedElement is
   122 // destroyed.
   123 class MOZ_STACK_CLASS nsSMILTimedElement::AutoIntervalUpdateBatcher
   124 {
   125 public:
   126   AutoIntervalUpdateBatcher(nsSMILTimedElement& aTimedElement)
   127     : mTimedElement(aTimedElement),
   128       mDidSetFlag(!aTimedElement.mDeferIntervalUpdates)
   129   {
   130     mTimedElement.mDeferIntervalUpdates = true;
   131   }
   133   ~AutoIntervalUpdateBatcher()
   134   {
   135     if (!mDidSetFlag)
   136       return;
   138     mTimedElement.mDeferIntervalUpdates = false;
   140     if (mTimedElement.mDoDeferredUpdate) {
   141       mTimedElement.mDoDeferredUpdate = false;
   142       mTimedElement.UpdateCurrentInterval();
   143     }
   144   }
   146 private:
   147   nsSMILTimedElement& mTimedElement;
   148   bool mDidSetFlag;
   149 };
   151 //----------------------------------------------------------------------
   152 // Helper class: AutoIntervalUpdater
   154 // Stack-based helper class to call UpdateCurrentInterval when it is destroyed
   155 // which helps avoid bugs where we forget to call UpdateCurrentInterval in the
   156 // case of early returns (e.g. due to parse errors).
   157 //
   158 // This can be safely used in conjunction with AutoIntervalUpdateBatcher; any
   159 // calls to UpdateCurrentInterval made by this class will simply be deferred if
   160 // there is an AutoIntervalUpdateBatcher on the stack.
   161 class MOZ_STACK_CLASS nsSMILTimedElement::AutoIntervalUpdater
   162 {
   163 public:
   164   AutoIntervalUpdater(nsSMILTimedElement& aTimedElement)
   165     : mTimedElement(aTimedElement) { }
   167   ~AutoIntervalUpdater()
   168   {
   169     mTimedElement.UpdateCurrentInterval();
   170   }
   172 private:
   173   nsSMILTimedElement& mTimedElement;
   174 };
   176 //----------------------------------------------------------------------
   177 // Templated helper functions
   179 // Selectively remove elements from an array of type
   180 // nsTArray<nsRefPtr<nsSMILInstanceTime> > with O(n) performance.
   181 template <class TestFunctor>
   182 void
   183 nsSMILTimedElement::RemoveInstanceTimes(InstanceTimeList& aArray,
   184                                         TestFunctor& aTest)
   185 {
   186   InstanceTimeList newArray;
   187   for (uint32_t i = 0; i < aArray.Length(); ++i) {
   188     nsSMILInstanceTime* item = aArray[i].get();
   189     if (aTest(item, i)) {
   190       // As per bugs 665334 and 669225 we should be careful not to remove the
   191       // instance time that corresponds to the previous interval's end time.
   192       //
   193       // Most functors supplied here fulfil this condition by checking if the
   194       // instance time is marked as "ShouldPreserve" and if so, not deleting it.
   195       //
   196       // However, when filtering instance times, we sometimes need to drop even
   197       // instance times marked as "ShouldPreserve". In that case we take special
   198       // care not to delete the end instance time of the previous interval.
   199       NS_ABORT_IF_FALSE(!GetPreviousInterval() ||
   200         item != GetPreviousInterval()->End(),
   201         "Removing end instance time of previous interval");
   202       item->Unlink();
   203     } else {
   204       newArray.AppendElement(item);
   205     }
   206   }
   207   aArray.Clear();
   208   aArray.SwapElements(newArray);
   209 }
   211 //----------------------------------------------------------------------
   212 // Static members
   214 nsAttrValue::EnumTable nsSMILTimedElement::sFillModeTable[] = {
   215       {"remove", FILL_REMOVE},
   216       {"freeze", FILL_FREEZE},
   217       {nullptr, 0}
   218 };
   220 nsAttrValue::EnumTable nsSMILTimedElement::sRestartModeTable[] = {
   221       {"always", RESTART_ALWAYS},
   222       {"whenNotActive", RESTART_WHENNOTACTIVE},
   223       {"never", RESTART_NEVER},
   224       {nullptr, 0}
   225 };
   227 const nsSMILMilestone nsSMILTimedElement::sMaxMilestone(INT64_MAX, false);
   229 // The thresholds at which point we start filtering intervals and instance times
   230 // indiscriminately.
   231 // See FilterIntervals and FilterInstanceTimes.
   232 const uint8_t nsSMILTimedElement::sMaxNumIntervals = 20;
   233 const uint8_t nsSMILTimedElement::sMaxNumInstanceTimes = 100;
   235 // Detect if we arrive in some sort of undetected recursive syncbase dependency
   236 // relationship
   237 const uint8_t nsSMILTimedElement::sMaxUpdateIntervalRecursionDepth = 20;
   239 //----------------------------------------------------------------------
   240 // Ctor, dtor
   242 nsSMILTimedElement::nsSMILTimedElement()
   243 :
   244   mAnimationElement(nullptr),
   245   mFillMode(FILL_REMOVE),
   246   mRestartMode(RESTART_ALWAYS),
   247   mInstanceSerialIndex(0),
   248   mClient(nullptr),
   249   mCurrentInterval(nullptr),
   250   mCurrentRepeatIteration(0),
   251   mPrevRegisteredMilestone(sMaxMilestone),
   252   mElementState(STATE_STARTUP),
   253   mSeekState(SEEK_NOT_SEEKING),
   254   mDeferIntervalUpdates(false),
   255   mDoDeferredUpdate(false),
   256   mDeleteCount(0),
   257   mUpdateIntervalRecursionDepth(0)
   258 {
   259   mSimpleDur.SetIndefinite();
   260   mMin.SetMillis(0L);
   261   mMax.SetIndefinite();
   262 }
   264 nsSMILTimedElement::~nsSMILTimedElement()
   265 {
   266   // Unlink all instance times from dependent intervals
   267   for (uint32_t i = 0; i < mBeginInstances.Length(); ++i) {
   268     mBeginInstances[i]->Unlink();
   269   }
   270   mBeginInstances.Clear();
   271   for (uint32_t i = 0; i < mEndInstances.Length(); ++i) {
   272     mEndInstances[i]->Unlink();
   273   }
   274   mEndInstances.Clear();
   276   // Notify anyone listening to our intervals that they're gone
   277   // (We shouldn't get any callbacks from this because all our instance times
   278   // are now disassociated with any intervals)
   279   ClearIntervals();
   281   // The following assertions are important in their own right (for checking
   282   // correct behavior) but also because AutoIntervalUpdateBatcher holds pointers
   283   // to class so if they fail there's the possibility we might have dangling
   284   // pointers.
   285   NS_ABORT_IF_FALSE(!mDeferIntervalUpdates,
   286       "Interval updates should no longer be blocked when an nsSMILTimedElement "
   287       "disappears");
   288   NS_ABORT_IF_FALSE(!mDoDeferredUpdate,
   289       "There should no longer be any pending updates when an "
   290       "nsSMILTimedElement disappears");
   291 }
   293 void
   294 nsSMILTimedElement::SetAnimationElement(SVGAnimationElement* aElement)
   295 {
   296   NS_ABORT_IF_FALSE(aElement, "NULL owner element");
   297   NS_ABORT_IF_FALSE(!mAnimationElement, "Re-setting owner");
   298   mAnimationElement = aElement;
   299 }
   301 nsSMILTimeContainer*
   302 nsSMILTimedElement::GetTimeContainer()
   303 {
   304   return mAnimationElement ? mAnimationElement->GetTimeContainer() : nullptr;
   305 }
   307 dom::Element*
   308 nsSMILTimedElement::GetTargetElement()
   309 {
   310   return mAnimationElement ?
   311       mAnimationElement->GetTargetElementContent() :
   312       nullptr;
   313 }
   315 //----------------------------------------------------------------------
   316 // nsIDOMElementTimeControl methods
   317 //
   318 // The definition of the ElementTimeControl interface differs between SMIL
   319 // Animation and SVG 1.1. In SMIL Animation all methods have a void return
   320 // type and the new instance time is simply added to the list and restart
   321 // semantics are applied as with any other instance time. In the SVG definition
   322 // the methods return a bool depending on the restart mode.
   323 //
   324 // This inconsistency has now been addressed by an erratum in SVG 1.1:
   325 //
   326 // http://www.w3.org/2003/01/REC-SVG11-20030114-errata#elementtimecontrol-interface
   327 //
   328 // which favours the definition in SMIL, i.e. instance times are just added
   329 // without first checking the restart mode.
   331 nsresult
   332 nsSMILTimedElement::BeginElementAt(double aOffsetSeconds)
   333 {
   334   nsSMILTimeContainer* container = GetTimeContainer();
   335   if (!container)
   336     return NS_ERROR_FAILURE;
   338   nsSMILTime currentTime = container->GetCurrentTime();
   339   return AddInstanceTimeFromCurrentTime(currentTime, aOffsetSeconds, true);
   340 }
   342 nsresult
   343 nsSMILTimedElement::EndElementAt(double aOffsetSeconds)
   344 {
   345   nsSMILTimeContainer* container = GetTimeContainer();
   346   if (!container)
   347     return NS_ERROR_FAILURE;
   349   nsSMILTime currentTime = container->GetCurrentTime();
   350   return AddInstanceTimeFromCurrentTime(currentTime, aOffsetSeconds, false);
   351 }
   353 //----------------------------------------------------------------------
   354 // nsSVGAnimationElement methods
   356 nsSMILTimeValue
   357 nsSMILTimedElement::GetStartTime() const
   358 {
   359   return mElementState == STATE_WAITING || mElementState == STATE_ACTIVE
   360          ? mCurrentInterval->Begin()->Time()
   361          : nsSMILTimeValue();
   362 }
   364 //----------------------------------------------------------------------
   365 // Hyperlinking support
   367 nsSMILTimeValue
   368 nsSMILTimedElement::GetHyperlinkTime() const
   369 {
   370   nsSMILTimeValue hyperlinkTime; // Default ctor creates unresolved time
   372   if (mElementState == STATE_ACTIVE) {
   373     hyperlinkTime = mCurrentInterval->Begin()->Time();
   374   } else if (!mBeginInstances.IsEmpty()) {
   375     hyperlinkTime = mBeginInstances[0]->Time();
   376   }
   378   return hyperlinkTime;
   379 }
   381 //----------------------------------------------------------------------
   382 // nsSMILTimedElement
   384 void
   385 nsSMILTimedElement::AddInstanceTime(nsSMILInstanceTime* aInstanceTime,
   386                                     bool aIsBegin)
   387 {
   388   NS_ABORT_IF_FALSE(aInstanceTime, "Attempting to add null instance time");
   390   // Event-sensitivity: If an element is not active (but the parent time
   391   // container is), then events are only handled for begin specifications.
   392   if (mElementState != STATE_ACTIVE && !aIsBegin &&
   393       aInstanceTime->IsDynamic())
   394   {
   395     // No need to call Unlink here--dynamic instance times shouldn't be linked
   396     // to anything that's going to miss them
   397     NS_ABORT_IF_FALSE(!aInstanceTime->GetBaseInterval(),
   398         "Dynamic instance time has a base interval--we probably need to unlink"
   399         " it if we're not going to use it");
   400     return;
   401   }
   403   aInstanceTime->SetSerial(++mInstanceSerialIndex);
   404   InstanceTimeList& instanceList = aIsBegin ? mBeginInstances : mEndInstances;
   405   nsRefPtr<nsSMILInstanceTime>* inserted =
   406     instanceList.InsertElementSorted(aInstanceTime, InstanceTimeComparator());
   407   if (!inserted) {
   408     NS_WARNING("Insufficient memory to insert instance time");
   409     return;
   410   }
   412   UpdateCurrentInterval();
   413 }
   415 void
   416 nsSMILTimedElement::UpdateInstanceTime(nsSMILInstanceTime* aInstanceTime,
   417                                        nsSMILTimeValue& aUpdatedTime,
   418                                        bool aIsBegin)
   419 {
   420   NS_ABORT_IF_FALSE(aInstanceTime, "Attempting to update null instance time");
   422   // The reason we update the time here and not in the nsSMILTimeValueSpec is
   423   // that it means we *could* re-sort more efficiently by doing a sorted remove
   424   // and insert but currently this doesn't seem to be necessary given how
   425   // infrequently we get these change notices.
   426   aInstanceTime->DependentUpdate(aUpdatedTime);
   427   InstanceTimeList& instanceList = aIsBegin ? mBeginInstances : mEndInstances;
   428   instanceList.Sort(InstanceTimeComparator());
   430   // Generally speaking, UpdateCurrentInterval makes changes to the current
   431   // interval and sends changes notices itself. However, in this case because
   432   // instance times are shared between the instance time list and the intervals
   433   // we are effectively changing the current interval outside
   434   // UpdateCurrentInterval so we need to explicitly signal that we've made
   435   // a change.
   436   //
   437   // This wouldn't be necessary if we cloned instance times on adding them to
   438   // the current interval but this introduces other complications (particularly
   439   // detecting which instance time is being used to define the begin of the
   440   // current interval when doing a Reset).
   441   bool changedCurrentInterval = mCurrentInterval &&
   442     (mCurrentInterval->Begin() == aInstanceTime ||
   443      mCurrentInterval->End() == aInstanceTime);
   445   UpdateCurrentInterval(changedCurrentInterval);
   446 }
   448 void
   449 nsSMILTimedElement::RemoveInstanceTime(nsSMILInstanceTime* aInstanceTime,
   450                                        bool aIsBegin)
   451 {
   452   NS_ABORT_IF_FALSE(aInstanceTime, "Attempting to remove null instance time");
   454   // If the instance time should be kept (because it is or was the fixed end
   455   // point of an interval) then just disassociate it from the creator.
   456   if (aInstanceTime->ShouldPreserve()) {
   457     aInstanceTime->Unlink();
   458     return;
   459   }
   461   InstanceTimeList& instanceList = aIsBegin ? mBeginInstances : mEndInstances;
   462   mozilla::DebugOnly<bool> found =
   463     instanceList.RemoveElementSorted(aInstanceTime, InstanceTimeComparator());
   464   NS_ABORT_IF_FALSE(found, "Couldn't find instance time to delete");
   466   UpdateCurrentInterval();
   467 }
   469 namespace
   470 {
   471   class MOZ_STACK_CLASS RemoveByCreator
   472   {
   473   public:
   474     RemoveByCreator(const nsSMILTimeValueSpec* aCreator) : mCreator(aCreator)
   475     { }
   477     bool operator()(nsSMILInstanceTime* aInstanceTime, uint32_t /*aIndex*/)
   478     {
   479       if (aInstanceTime->GetCreator() != mCreator)
   480         return false;
   482       // If the instance time should be kept (because it is or was the fixed end
   483       // point of an interval) then just disassociate it from the creator.
   484       if (aInstanceTime->ShouldPreserve()) {
   485         aInstanceTime->Unlink();
   486         return false;
   487       }
   489       return true;
   490     }
   492   private:
   493     const nsSMILTimeValueSpec* mCreator;
   494   };
   495 }
   497 void
   498 nsSMILTimedElement::RemoveInstanceTimesForCreator(
   499     const nsSMILTimeValueSpec* aCreator, bool aIsBegin)
   500 {
   501   NS_ABORT_IF_FALSE(aCreator, "Creator not set");
   503   InstanceTimeList& instances = aIsBegin ? mBeginInstances : mEndInstances;
   504   RemoveByCreator removeByCreator(aCreator);
   505   RemoveInstanceTimes(instances, removeByCreator);
   507   UpdateCurrentInterval();
   508 }
   510 void
   511 nsSMILTimedElement::SetTimeClient(nsSMILAnimationFunction* aClient)
   512 {
   513   //
   514   // No need to check for nullptr. A nullptr parameter simply means to remove the
   515   // previous client which we do by setting to nullptr anyway.
   516   //
   518   mClient = aClient;
   519 }
   521 void
   522 nsSMILTimedElement::SampleAt(nsSMILTime aContainerTime)
   523 {
   524   // Milestones are cleared before a sample
   525   mPrevRegisteredMilestone = sMaxMilestone;
   527   DoSampleAt(aContainerTime, false);
   528 }
   530 void
   531 nsSMILTimedElement::SampleEndAt(nsSMILTime aContainerTime)
   532 {
   533   // Milestones are cleared before a sample
   534   mPrevRegisteredMilestone = sMaxMilestone;
   536   // If the current interval changes, we don't bother trying to remove any old
   537   // milestones we'd registered. So it's possible to get a call here to end an
   538   // interval at a time that no longer reflects the end of the current interval.
   539   //
   540   // For now we just check that we're actually in an interval but note that the
   541   // initial sample we use to initialise the model is an end sample. This is
   542   // because we want to resolve all the instance times before committing to an
   543   // initial interval. Therefore an end sample from the startup state is also
   544   // acceptable.
   545   if (mElementState == STATE_ACTIVE || mElementState == STATE_STARTUP) {
   546     DoSampleAt(aContainerTime, true); // End sample
   547   } else {
   548     // Even if this was an unnecessary milestone sample we want to be sure that
   549     // our next real milestone is registered.
   550     RegisterMilestone();
   551   }
   552 }
   554 void
   555 nsSMILTimedElement::DoSampleAt(nsSMILTime aContainerTime, bool aEndOnly)
   556 {
   557   NS_ABORT_IF_FALSE(mAnimationElement,
   558       "Got sample before being registered with an animation element");
   559   NS_ABORT_IF_FALSE(GetTimeContainer(),
   560       "Got sample without being registered with a time container");
   562   // This could probably happen if we later implement externalResourcesRequired
   563   // (bug 277955) and whilst waiting for those resources (and the animation to
   564   // start) we transfer a node from another document fragment that has already
   565   // started. In such a case we might receive milestone samples registered with
   566   // the already active container.
   567   if (GetTimeContainer()->IsPausedByType(nsSMILTimeContainer::PAUSE_BEGIN))
   568     return;
   570   // We use an end-sample to start animation since an end-sample lets us
   571   // tentatively create an interval without committing to it (by transitioning
   572   // to the ACTIVE state) and this is necessary because we might have
   573   // dependencies on other animations that are yet to start. After these
   574   // other animations start, it may be necessary to revise our initial interval.
   575   //
   576   // However, sometimes instead of an end-sample we can get a regular sample
   577   // during STARTUP state. This can happen, for example, if we register
   578   // a milestone before time t=0 and are then re-bound to the tree (which sends
   579   // us back to the STARTUP state). In such a case we should just ignore the
   580   // sample and wait for our real initial sample which will be an end-sample.
   581   if (mElementState == STATE_STARTUP && !aEndOnly)
   582     return;
   584   bool finishedSeek = false;
   585   if (GetTimeContainer()->IsSeeking() && mSeekState == SEEK_NOT_SEEKING) {
   586     mSeekState = mElementState == STATE_ACTIVE ?
   587                  SEEK_FORWARD_FROM_ACTIVE :
   588                  SEEK_FORWARD_FROM_INACTIVE;
   589   } else if (mSeekState != SEEK_NOT_SEEKING &&
   590              !GetTimeContainer()->IsSeeking()) {
   591     finishedSeek = true;
   592   }
   594   bool            stateChanged;
   595   nsSMILTimeValue sampleTime(aContainerTime);
   597   do {
   598 #ifdef DEBUG
   599     // Check invariant
   600     if (mElementState == STATE_STARTUP || mElementState == STATE_POSTACTIVE) {
   601       NS_ABORT_IF_FALSE(!mCurrentInterval,
   602           "Shouldn't have current interval in startup or postactive states");
   603     } else {
   604       NS_ABORT_IF_FALSE(mCurrentInterval,
   605           "Should have current interval in waiting and active states");
   606     }
   607 #endif
   609     stateChanged = false;
   611     switch (mElementState)
   612     {
   613     case STATE_STARTUP:
   614       {
   615         nsSMILInterval firstInterval;
   616         mElementState = GetNextInterval(nullptr, nullptr, nullptr, firstInterval)
   617          ? STATE_WAITING
   618          : STATE_POSTACTIVE;
   619         stateChanged = true;
   620         if (mElementState == STATE_WAITING) {
   621           mCurrentInterval = new nsSMILInterval(firstInterval);
   622           NotifyNewInterval();
   623         }
   624       }
   625       break;
   627     case STATE_WAITING:
   628       {
   629         if (mCurrentInterval->Begin()->Time() <= sampleTime) {
   630           mElementState = STATE_ACTIVE;
   631           mCurrentInterval->FixBegin();
   632           if (mClient) {
   633             mClient->Activate(mCurrentInterval->Begin()->Time().GetMillis());
   634           }
   635           if (mSeekState == SEEK_NOT_SEEKING) {
   636             FireTimeEventAsync(NS_SMIL_BEGIN, 0);
   637           }
   638           if (HasPlayed()) {
   639             Reset(); // Apply restart behaviour
   640             // The call to Reset() may mean that the end point of our current
   641             // interval should be changed and so we should update the interval
   642             // now. However, calling UpdateCurrentInterval could result in the
   643             // interval getting deleted (perhaps through some web of syncbase
   644             // dependencies) therefore we make updating the interval the last
   645             // thing we do. There is no guarantee that mCurrentInterval is set
   646             // after this.
   647             UpdateCurrentInterval();
   648           }
   649           stateChanged = true;
   650         }
   651       }
   652       break;
   654     case STATE_ACTIVE:
   655       {
   656         // Ending early will change the interval but we don't notify dependents
   657         // of the change until we have closed off the current interval (since we
   658         // don't want dependencies to un-end our early end).
   659         bool didApplyEarlyEnd = ApplyEarlyEnd(sampleTime);
   661         if (mCurrentInterval->End()->Time() <= sampleTime) {
   662           nsSMILInterval newInterval;
   663           mElementState =
   664             GetNextInterval(mCurrentInterval, nullptr, nullptr, newInterval)
   665             ? STATE_WAITING
   666             : STATE_POSTACTIVE;
   667           if (mClient) {
   668             mClient->Inactivate(mFillMode == FILL_FREEZE);
   669           }
   670           mCurrentInterval->FixEnd();
   671           if (mSeekState == SEEK_NOT_SEEKING) {
   672             FireTimeEventAsync(NS_SMIL_END, 0);
   673           }
   674           mCurrentRepeatIteration = 0;
   675           mOldIntervals.AppendElement(mCurrentInterval.forget());
   676           SampleFillValue();
   677           if (mElementState == STATE_WAITING) {
   678             mCurrentInterval = new nsSMILInterval(newInterval);
   679           }
   680           // We are now in a consistent state to dispatch notifications
   681           if (didApplyEarlyEnd) {
   682             NotifyChangedInterval(
   683                 mOldIntervals[mOldIntervals.Length() - 1], false, true);
   684           }
   685           if (mElementState == STATE_WAITING) {
   686             NotifyNewInterval();
   687           }
   688           FilterHistory();
   689           stateChanged = true;
   690         } else {
   691           NS_ABORT_IF_FALSE(!didApplyEarlyEnd,
   692               "We got an early end, but didn't end");
   693           nsSMILTime beginTime = mCurrentInterval->Begin()->Time().GetMillis();
   694           NS_ASSERTION(aContainerTime >= beginTime,
   695                        "Sample time should not precede current interval");
   696           nsSMILTime activeTime = aContainerTime - beginTime;
   698           // The 'min' attribute can cause the active interval to be longer than
   699           // the 'repeating interval'.
   700           // In that extended period we apply the fill mode.
   701           if (GetRepeatDuration() <= nsSMILTimeValue(activeTime)) {
   702             if (mClient && mClient->IsActive()) {
   703               mClient->Inactivate(mFillMode == FILL_FREEZE);
   704             }
   705             SampleFillValue();
   706           } else {
   707             SampleSimpleTime(activeTime);
   709             // We register our repeat times as milestones (except when we're
   710             // seeking) so we should get a sample at exactly the time we repeat.
   711             // (And even when we are seeking we want to update
   712             // mCurrentRepeatIteration so we do that first before testing the
   713             // seek state.)
   714             uint32_t prevRepeatIteration = mCurrentRepeatIteration;
   715             if (
   716               ActiveTimeToSimpleTime(activeTime, mCurrentRepeatIteration)==0 &&
   717               mCurrentRepeatIteration != prevRepeatIteration &&
   718               mCurrentRepeatIteration &&
   719               mSeekState == SEEK_NOT_SEEKING) {
   720               FireTimeEventAsync(NS_SMIL_REPEAT,
   721                             static_cast<int32_t>(mCurrentRepeatIteration));
   722             }
   723           }
   724         }
   725       }
   726       break;
   728     case STATE_POSTACTIVE:
   729       break;
   730     }
   732   // Generally we continue driving the state machine so long as we have changed
   733   // state. However, for end samples we only drive the state machine as far as
   734   // the waiting or postactive state because we don't want to commit to any new
   735   // interval (by transitioning to the active state) until all the end samples
   736   // have finished and we then have complete information about the available
   737   // instance times upon which to base our next interval.
   738   } while (stateChanged && (!aEndOnly || (mElementState != STATE_WAITING &&
   739                                           mElementState != STATE_POSTACTIVE)));
   741   if (finishedSeek) {
   742     DoPostSeek();
   743   }
   744   RegisterMilestone();
   745 }
   747 void
   748 nsSMILTimedElement::HandleContainerTimeChange()
   749 {
   750   // In future we could possibly introduce a separate change notice for time
   751   // container changes and only notify those dependents who live in other time
   752   // containers. For now we don't bother because when we re-resolve the time in
   753   // the nsSMILTimeValueSpec we'll check if anything has changed and if not, we
   754   // won't go any further.
   755   if (mElementState == STATE_WAITING || mElementState == STATE_ACTIVE) {
   756     NotifyChangedInterval(mCurrentInterval, false, false);
   757   }
   758 }
   760 namespace
   761 {
   762   bool
   763   RemoveNonDynamic(nsSMILInstanceTime* aInstanceTime)
   764   {
   765     // Generally dynamically-generated instance times (DOM calls, event-based
   766     // times) are not associated with their creator nsSMILTimeValueSpec since
   767     // they may outlive them.
   768     NS_ABORT_IF_FALSE(!aInstanceTime->IsDynamic() ||
   769          !aInstanceTime->GetCreator(),
   770         "Dynamic instance time should be unlinked from its creator");
   771     return !aInstanceTime->IsDynamic() && !aInstanceTime->ShouldPreserve();
   772   }
   773 }
   775 void
   776 nsSMILTimedElement::Rewind()
   777 {
   778   NS_ABORT_IF_FALSE(mAnimationElement,
   779       "Got rewind request before being attached to an animation element");
   781   // It's possible to get a rewind request whilst we're already in the middle of
   782   // a backwards seek. This can happen when we're performing tree surgery and
   783   // seeking containers at the same time because we can end up requesting
   784   // a local rewind on an element after binding it to a new container and then
   785   // performing a rewind on that container as a whole without sampling in
   786   // between.
   787   //
   788   // However, it should currently be impossible to get a rewind in the middle of
   789   // a forwards seek since forwards seeks are detected and processed within the
   790   // same (re)sample.
   791   if (mSeekState == SEEK_NOT_SEEKING) {
   792     mSeekState = mElementState == STATE_ACTIVE ?
   793                  SEEK_BACKWARD_FROM_ACTIVE :
   794                  SEEK_BACKWARD_FROM_INACTIVE;
   795   }
   796   NS_ABORT_IF_FALSE(mSeekState == SEEK_BACKWARD_FROM_INACTIVE ||
   797                     mSeekState == SEEK_BACKWARD_FROM_ACTIVE,
   798                     "Rewind in the middle of a forwards seek?");
   800   // Putting us in the startup state will ensure we skip doing any interval
   801   // updates
   802   mElementState = STATE_STARTUP;
   803   ClearIntervals();
   805   UnsetBeginSpec(RemoveNonDynamic);
   806   UnsetEndSpec(RemoveNonDynamic);
   808   if (mClient) {
   809     mClient->Inactivate(false);
   810   }
   812   if (mAnimationElement->HasAnimAttr(nsGkAtoms::begin)) {
   813     nsAutoString attValue;
   814     mAnimationElement->GetAnimAttr(nsGkAtoms::begin, attValue);
   815     SetBeginSpec(attValue, mAnimationElement, RemoveNonDynamic);
   816   }
   818   if (mAnimationElement->HasAnimAttr(nsGkAtoms::end)) {
   819     nsAutoString attValue;
   820     mAnimationElement->GetAnimAttr(nsGkAtoms::end, attValue);
   821     SetEndSpec(attValue, mAnimationElement, RemoveNonDynamic);
   822   }
   824   mPrevRegisteredMilestone = sMaxMilestone;
   825   RegisterMilestone();
   826   NS_ABORT_IF_FALSE(!mCurrentInterval,
   827                     "Current interval is set at end of rewind");
   828 }
   830 namespace
   831 {
   832   bool
   833   RemoveNonDOM(nsSMILInstanceTime* aInstanceTime)
   834   {
   835     return !aInstanceTime->FromDOM() && !aInstanceTime->ShouldPreserve();
   836   }
   837 }
   839 bool
   840 nsSMILTimedElement::SetAttr(nsIAtom* aAttribute, const nsAString& aValue,
   841                             nsAttrValue& aResult,
   842                             Element* aContextNode,
   843                             nsresult* aParseResult)
   844 {
   845   bool foundMatch = true;
   846   nsresult parseResult = NS_OK;
   848   if (aAttribute == nsGkAtoms::begin) {
   849     parseResult = SetBeginSpec(aValue, aContextNode, RemoveNonDOM);
   850   } else if (aAttribute == nsGkAtoms::dur) {
   851     parseResult = SetSimpleDuration(aValue);
   852   } else if (aAttribute == nsGkAtoms::end) {
   853     parseResult = SetEndSpec(aValue, aContextNode, RemoveNonDOM);
   854   } else if (aAttribute == nsGkAtoms::fill) {
   855     parseResult = SetFillMode(aValue);
   856   } else if (aAttribute == nsGkAtoms::max) {
   857     parseResult = SetMax(aValue);
   858   } else if (aAttribute == nsGkAtoms::min) {
   859     parseResult = SetMin(aValue);
   860   } else if (aAttribute == nsGkAtoms::repeatCount) {
   861     parseResult = SetRepeatCount(aValue);
   862   } else if (aAttribute == nsGkAtoms::repeatDur) {
   863     parseResult = SetRepeatDur(aValue);
   864   } else if (aAttribute == nsGkAtoms::restart) {
   865     parseResult = SetRestart(aValue);
   866   } else {
   867     foundMatch = false;
   868   }
   870   if (foundMatch) {
   871     aResult.SetTo(aValue);
   872     if (aParseResult) {
   873       *aParseResult = parseResult;
   874     }
   875   }
   877   return foundMatch;
   878 }
   880 bool
   881 nsSMILTimedElement::UnsetAttr(nsIAtom* aAttribute)
   882 {
   883   bool foundMatch = true;
   885   if (aAttribute == nsGkAtoms::begin) {
   886     UnsetBeginSpec(RemoveNonDOM);
   887   } else if (aAttribute == nsGkAtoms::dur) {
   888     UnsetSimpleDuration();
   889   } else if (aAttribute == nsGkAtoms::end) {
   890     UnsetEndSpec(RemoveNonDOM);
   891   } else if (aAttribute == nsGkAtoms::fill) {
   892     UnsetFillMode();
   893   } else if (aAttribute == nsGkAtoms::max) {
   894     UnsetMax();
   895   } else if (aAttribute == nsGkAtoms::min) {
   896     UnsetMin();
   897   } else if (aAttribute == nsGkAtoms::repeatCount) {
   898     UnsetRepeatCount();
   899   } else if (aAttribute == nsGkAtoms::repeatDur) {
   900     UnsetRepeatDur();
   901   } else if (aAttribute == nsGkAtoms::restart) {
   902     UnsetRestart();
   903   } else {
   904     foundMatch = false;
   905   }
   907   return foundMatch;
   908 }
   910 //----------------------------------------------------------------------
   911 // Setters and unsetters
   913 nsresult
   914 nsSMILTimedElement::SetBeginSpec(const nsAString& aBeginSpec,
   915                                  Element* aContextNode,
   916                                  RemovalTestFunction aRemove)
   917 {
   918   return SetBeginOrEndSpec(aBeginSpec, aContextNode, true /*isBegin*/,
   919                            aRemove);
   920 }
   922 void
   923 nsSMILTimedElement::UnsetBeginSpec(RemovalTestFunction aRemove)
   924 {
   925   ClearSpecs(mBeginSpecs, mBeginInstances, aRemove);
   926   UpdateCurrentInterval();
   927 }
   929 nsresult
   930 nsSMILTimedElement::SetEndSpec(const nsAString& aEndSpec,
   931                                Element* aContextNode,
   932                                RemovalTestFunction aRemove)
   933 {
   934   return SetBeginOrEndSpec(aEndSpec, aContextNode, false /*!isBegin*/,
   935                            aRemove);
   936 }
   938 void
   939 nsSMILTimedElement::UnsetEndSpec(RemovalTestFunction aRemove)
   940 {
   941   ClearSpecs(mEndSpecs, mEndInstances, aRemove);
   942   UpdateCurrentInterval();
   943 }
   945 nsresult
   946 nsSMILTimedElement::SetSimpleDuration(const nsAString& aDurSpec)
   947 {
   948   // Update the current interval before returning
   949   AutoIntervalUpdater updater(*this);
   951   nsSMILTimeValue duration;
   952   const nsAString& dur = nsSMILParserUtils::TrimWhitespace(aDurSpec);
   954   // SVG-specific: "For SVG's animation elements, if "media" is specified, the
   955   // attribute will be ignored." (SVG 1.1, section 19.2.6)
   956   if (dur.EqualsLiteral("media") || dur.EqualsLiteral("indefinite")) {
   957     duration.SetIndefinite();
   958   } else {
   959     if (!nsSMILParserUtils::ParseClockValue(dur, &duration) ||
   960         duration.GetMillis() == 0L) {
   961       mSimpleDur.SetIndefinite();
   962       return NS_ERROR_FAILURE;
   963     }
   964   }
   965   // mSimpleDur should never be unresolved. ParseClockValue will either set
   966   // duration to resolved or will return false.
   967   NS_ABORT_IF_FALSE(duration.IsResolved(),
   968     "Setting unresolved simple duration");
   970   mSimpleDur = duration;
   972   return NS_OK;
   973 }
   975 void
   976 nsSMILTimedElement::UnsetSimpleDuration()
   977 {
   978   mSimpleDur.SetIndefinite();
   979   UpdateCurrentInterval();
   980 }
   982 nsresult
   983 nsSMILTimedElement::SetMin(const nsAString& aMinSpec)
   984 {
   985   // Update the current interval before returning
   986   AutoIntervalUpdater updater(*this);
   988   nsSMILTimeValue duration;
   989   const nsAString& min = nsSMILParserUtils::TrimWhitespace(aMinSpec);
   991   if (min.EqualsLiteral("media")) {
   992     duration.SetMillis(0L);
   993   } else {
   994     if (!nsSMILParserUtils::ParseClockValue(min, &duration)) {
   995       mMin.SetMillis(0L);
   996       return NS_ERROR_FAILURE;
   997     }
   998   }
  1000   NS_ABORT_IF_FALSE(duration.GetMillis() >= 0L, "Invalid duration");
  1002   mMin = duration;
  1004   return NS_OK;
  1007 void
  1008 nsSMILTimedElement::UnsetMin()
  1010   mMin.SetMillis(0L);
  1011   UpdateCurrentInterval();
  1014 nsresult
  1015 nsSMILTimedElement::SetMax(const nsAString& aMaxSpec)
  1017   // Update the current interval before returning
  1018   AutoIntervalUpdater updater(*this);
  1020   nsSMILTimeValue duration;
  1021   const nsAString& max = nsSMILParserUtils::TrimWhitespace(aMaxSpec);
  1023   if (max.EqualsLiteral("media") || max.EqualsLiteral("indefinite")) {
  1024     duration.SetIndefinite();
  1025   } else {
  1026     if (!nsSMILParserUtils::ParseClockValue(max, &duration) ||
  1027         duration.GetMillis() == 0L) {
  1028       mMax.SetIndefinite();
  1029       return NS_ERROR_FAILURE;
  1031     NS_ABORT_IF_FALSE(duration.GetMillis() > 0L, "Invalid duration");
  1034   mMax = duration;
  1036   return NS_OK;
  1039 void
  1040 nsSMILTimedElement::UnsetMax()
  1042   mMax.SetIndefinite();
  1043   UpdateCurrentInterval();
  1046 nsresult
  1047 nsSMILTimedElement::SetRestart(const nsAString& aRestartSpec)
  1049   nsAttrValue temp;
  1050   bool parseResult
  1051     = temp.ParseEnumValue(aRestartSpec, sRestartModeTable, true);
  1052   mRestartMode = parseResult
  1053                ? nsSMILRestartMode(temp.GetEnumValue())
  1054                : RESTART_ALWAYS;
  1055   UpdateCurrentInterval();
  1056   return parseResult ? NS_OK : NS_ERROR_FAILURE;
  1059 void
  1060 nsSMILTimedElement::UnsetRestart()
  1062   mRestartMode = RESTART_ALWAYS;
  1063   UpdateCurrentInterval();
  1066 nsresult
  1067 nsSMILTimedElement::SetRepeatCount(const nsAString& aRepeatCountSpec)
  1069   // Update the current interval before returning
  1070   AutoIntervalUpdater updater(*this);
  1072   nsSMILRepeatCount newRepeatCount;
  1074   if (nsSMILParserUtils::ParseRepeatCount(aRepeatCountSpec, newRepeatCount)) {
  1075     mRepeatCount = newRepeatCount;
  1076     return NS_OK;
  1078   mRepeatCount.Unset();
  1079   return NS_ERROR_FAILURE;
  1082 void
  1083 nsSMILTimedElement::UnsetRepeatCount()
  1085   mRepeatCount.Unset();
  1086   UpdateCurrentInterval();
  1089 nsresult
  1090 nsSMILTimedElement::SetRepeatDur(const nsAString& aRepeatDurSpec)
  1092   // Update the current interval before returning
  1093   AutoIntervalUpdater updater(*this);
  1095   nsSMILTimeValue duration;
  1097   const nsAString& repeatDur =
  1098     nsSMILParserUtils::TrimWhitespace(aRepeatDurSpec);
  1100   if (repeatDur.EqualsLiteral("indefinite")) {
  1101     duration.SetIndefinite();
  1102   } else {
  1103     if (!nsSMILParserUtils::ParseClockValue(repeatDur, &duration)) {
  1104       mRepeatDur.SetUnresolved();
  1105       return NS_ERROR_FAILURE;
  1109   mRepeatDur = duration;
  1111   return NS_OK;
  1114 void
  1115 nsSMILTimedElement::UnsetRepeatDur()
  1117   mRepeatDur.SetUnresolved();
  1118   UpdateCurrentInterval();
  1121 nsresult
  1122 nsSMILTimedElement::SetFillMode(const nsAString& aFillModeSpec)
  1124   uint16_t previousFillMode = mFillMode;
  1126   nsAttrValue temp;
  1127   bool parseResult =
  1128     temp.ParseEnumValue(aFillModeSpec, sFillModeTable, true);
  1129   mFillMode = parseResult
  1130             ? nsSMILFillMode(temp.GetEnumValue())
  1131             : FILL_REMOVE;
  1133   // Update fill mode of client
  1134   if (mFillMode != previousFillMode && HasClientInFillRange()) {
  1135     mClient->Inactivate(mFillMode == FILL_FREEZE);
  1136     SampleFillValue();
  1139   return parseResult ? NS_OK : NS_ERROR_FAILURE;
  1142 void
  1143 nsSMILTimedElement::UnsetFillMode()
  1145   uint16_t previousFillMode = mFillMode;
  1146   mFillMode = FILL_REMOVE;
  1147   if (previousFillMode == FILL_FREEZE && HasClientInFillRange()) {
  1148     mClient->Inactivate(false);
  1152 void
  1153 nsSMILTimedElement::AddDependent(nsSMILTimeValueSpec& aDependent)
  1155   // There's probably no harm in attempting to register a dependent
  1156   // nsSMILTimeValueSpec twice, but we're not expecting it to happen.
  1157   NS_ABORT_IF_FALSE(!mTimeDependents.GetEntry(&aDependent),
  1158       "nsSMILTimeValueSpec is already registered as a dependency");
  1159   mTimeDependents.PutEntry(&aDependent);
  1161   // Add current interval. We could add historical intervals too but that would
  1162   // cause unpredictable results since some intervals may have been filtered.
  1163   // SMIL doesn't say what to do here so for simplicity and consistency we
  1164   // simply add the current interval if there is one.
  1165   //
  1166   // It's not necessary to call SyncPauseTime since we're dealing with
  1167   // historical instance times not newly added ones.
  1168   if (mCurrentInterval) {
  1169     aDependent.HandleNewInterval(*mCurrentInterval, GetTimeContainer());
  1173 void
  1174 nsSMILTimedElement::RemoveDependent(nsSMILTimeValueSpec& aDependent)
  1176   mTimeDependents.RemoveEntry(&aDependent);
  1179 bool
  1180 nsSMILTimedElement::IsTimeDependent(const nsSMILTimedElement& aOther) const
  1182   const nsSMILInstanceTime* thisBegin = GetEffectiveBeginInstance();
  1183   const nsSMILInstanceTime* otherBegin = aOther.GetEffectiveBeginInstance();
  1185   if (!thisBegin || !otherBegin)
  1186     return false;
  1188   return thisBegin->IsDependentOn(*otherBegin);
  1191 void
  1192 nsSMILTimedElement::BindToTree(nsIContent* aContextNode)
  1194   // Reset previously registered milestone since we may be registering with
  1195   // a different time container now.
  1196   mPrevRegisteredMilestone = sMaxMilestone;
  1198   // If we were already active then clear all our timing information and start
  1199   // afresh
  1200   if (mElementState != STATE_STARTUP) {
  1201     mSeekState = SEEK_NOT_SEEKING;
  1202     Rewind();
  1205   // Scope updateBatcher to last only for the ResolveReferences calls:
  1207     AutoIntervalUpdateBatcher updateBatcher(*this);
  1209     // Resolve references to other parts of the tree
  1210     uint32_t count = mBeginSpecs.Length();
  1211     for (uint32_t i = 0; i < count; ++i) {
  1212       mBeginSpecs[i]->ResolveReferences(aContextNode);
  1215     count = mEndSpecs.Length();
  1216     for (uint32_t j = 0; j < count; ++j) {
  1217       mEndSpecs[j]->ResolveReferences(aContextNode);
  1221   RegisterMilestone();
  1224 void
  1225 nsSMILTimedElement::HandleTargetElementChange(Element* aNewTarget)
  1227   AutoIntervalUpdateBatcher updateBatcher(*this);
  1229   uint32_t count = mBeginSpecs.Length();
  1230   for (uint32_t i = 0; i < count; ++i) {
  1231     mBeginSpecs[i]->HandleTargetElementChange(aNewTarget);
  1234   count = mEndSpecs.Length();
  1235   for (uint32_t j = 0; j < count; ++j) {
  1236     mEndSpecs[j]->HandleTargetElementChange(aNewTarget);
  1240 void
  1241 nsSMILTimedElement::Traverse(nsCycleCollectionTraversalCallback* aCallback)
  1243   uint32_t count = mBeginSpecs.Length();
  1244   for (uint32_t i = 0; i < count; ++i) {
  1245     nsSMILTimeValueSpec* beginSpec = mBeginSpecs[i];
  1246     NS_ABORT_IF_FALSE(beginSpec,
  1247         "null nsSMILTimeValueSpec in list of begin specs");
  1248     beginSpec->Traverse(aCallback);
  1251   count = mEndSpecs.Length();
  1252   for (uint32_t j = 0; j < count; ++j) {
  1253     nsSMILTimeValueSpec* endSpec = mEndSpecs[j];
  1254     NS_ABORT_IF_FALSE(endSpec, "null nsSMILTimeValueSpec in list of end specs");
  1255     endSpec->Traverse(aCallback);
  1259 void
  1260 nsSMILTimedElement::Unlink()
  1262   AutoIntervalUpdateBatcher updateBatcher(*this);
  1264   // Remove dependencies on other elements
  1265   uint32_t count = mBeginSpecs.Length();
  1266   for (uint32_t i = 0; i < count; ++i) {
  1267     nsSMILTimeValueSpec* beginSpec = mBeginSpecs[i];
  1268     NS_ABORT_IF_FALSE(beginSpec,
  1269         "null nsSMILTimeValueSpec in list of begin specs");
  1270     beginSpec->Unlink();
  1273   count = mEndSpecs.Length();
  1274   for (uint32_t j = 0; j < count; ++j) {
  1275     nsSMILTimeValueSpec* endSpec = mEndSpecs[j];
  1276     NS_ABORT_IF_FALSE(endSpec, "null nsSMILTimeValueSpec in list of end specs");
  1277     endSpec->Unlink();
  1280   ClearIntervals();
  1282   // Make sure we don't notify other elements of new intervals
  1283   mTimeDependents.Clear();
  1286 //----------------------------------------------------------------------
  1287 // Implementation helpers
  1289 nsresult
  1290 nsSMILTimedElement::SetBeginOrEndSpec(const nsAString& aSpec,
  1291                                       Element* aContextNode,
  1292                                       bool aIsBegin,
  1293                                       RemovalTestFunction aRemove)
  1295   TimeValueSpecList& timeSpecsList = aIsBegin ? mBeginSpecs : mEndSpecs;
  1296   InstanceTimeList& instances = aIsBegin ? mBeginInstances : mEndInstances;
  1298   ClearSpecs(timeSpecsList, instances, aRemove);
  1300   AutoIntervalUpdateBatcher updateBatcher(*this);
  1302   nsCharSeparatedTokenizer tokenizer(aSpec, ';');
  1303   if (!tokenizer.hasMoreTokens()) { // Empty list
  1304     return NS_ERROR_FAILURE;
  1307   nsresult rv = NS_OK;
  1308   while (tokenizer.hasMoreTokens() && NS_SUCCEEDED(rv)) {
  1309     nsAutoPtr<nsSMILTimeValueSpec>
  1310       spec(new nsSMILTimeValueSpec(*this, aIsBegin));
  1311     rv = spec->SetSpec(tokenizer.nextToken(), aContextNode);
  1312     if (NS_SUCCEEDED(rv)) {
  1313       timeSpecsList.AppendElement(spec.forget());
  1317   if (NS_FAILED(rv)) {
  1318     ClearSpecs(timeSpecsList, instances, aRemove);
  1321   return rv;
  1324 namespace
  1326   // Adaptor functor for RemoveInstanceTimes that allows us to use function
  1327   // pointers instead.
  1328   // Without this we'd have to either templatize ClearSpecs and all its callers
  1329   // or pass bool flags around to specify which removal function to use here.
  1330   class MOZ_STACK_CLASS RemoveByFunction
  1332   public:
  1333     RemoveByFunction(nsSMILTimedElement::RemovalTestFunction aFunction)
  1334       : mFunction(aFunction) { }
  1335     bool operator()(nsSMILInstanceTime* aInstanceTime, uint32_t /*aIndex*/)
  1337       return mFunction(aInstanceTime);
  1340   private:
  1341     nsSMILTimedElement::RemovalTestFunction mFunction;
  1342   };
  1345 void
  1346 nsSMILTimedElement::ClearSpecs(TimeValueSpecList& aSpecs,
  1347                                InstanceTimeList& aInstances,
  1348                                RemovalTestFunction aRemove)
  1350   AutoIntervalUpdateBatcher updateBatcher(*this);
  1352   for (uint32_t i = 0; i < aSpecs.Length(); ++i) {
  1353     aSpecs[i]->Unlink();
  1355   aSpecs.Clear();
  1357   RemoveByFunction removeByFunction(aRemove);
  1358   RemoveInstanceTimes(aInstances, removeByFunction);
  1361 void
  1362 nsSMILTimedElement::ClearIntervals()
  1364   if (mElementState != STATE_STARTUP) {
  1365     mElementState = STATE_POSTACTIVE;
  1367   mCurrentRepeatIteration = 0;
  1368   ResetCurrentInterval();
  1370   // Remove old intervals
  1371   for (int32_t i = mOldIntervals.Length() - 1; i >= 0; --i) {
  1372     mOldIntervals[i]->Unlink();
  1374   mOldIntervals.Clear();
  1377 bool
  1378 nsSMILTimedElement::ApplyEarlyEnd(const nsSMILTimeValue& aSampleTime)
  1380   // This should only be called within DoSampleAt as a helper function
  1381   NS_ABORT_IF_FALSE(mElementState == STATE_ACTIVE,
  1382       "Unexpected state to try to apply an early end");
  1384   bool updated = false;
  1386   // Only apply an early end if we're not already ending.
  1387   if (mCurrentInterval->End()->Time() > aSampleTime) {
  1388     nsSMILInstanceTime* earlyEnd = CheckForEarlyEnd(aSampleTime);
  1389     if (earlyEnd) {
  1390       if (earlyEnd->IsDependent()) {
  1391         // Generate a new instance time for the early end since the
  1392         // existing instance time is part of some dependency chain that we
  1393         // don't want to participate in.
  1394         nsRefPtr<nsSMILInstanceTime> newEarlyEnd =
  1395           new nsSMILInstanceTime(earlyEnd->Time());
  1396         mCurrentInterval->SetEnd(*newEarlyEnd);
  1397       } else {
  1398         mCurrentInterval->SetEnd(*earlyEnd);
  1400       updated = true;
  1403   return updated;
  1406 namespace
  1408   class MOZ_STACK_CLASS RemoveReset
  1410   public:
  1411     RemoveReset(const nsSMILInstanceTime* aCurrentIntervalBegin)
  1412       : mCurrentIntervalBegin(aCurrentIntervalBegin) { }
  1413     bool operator()(nsSMILInstanceTime* aInstanceTime, uint32_t /*aIndex*/)
  1415       // SMIL 3.0 section 5.4.3, 'Resetting element state':
  1416       //   Any instance times associated with past Event-values, Repeat-values,
  1417       //   Accesskey-values or added via DOM method calls are removed from the
  1418       //   dependent begin and end instance times lists. In effect, all events
  1419       //   and DOM methods calls in the past are cleared. This does not apply to
  1420       //   an instance time that defines the begin of the current interval.
  1421       return aInstanceTime->IsDynamic() &&
  1422              !aInstanceTime->ShouldPreserve() &&
  1423              (!mCurrentIntervalBegin || aInstanceTime != mCurrentIntervalBegin);
  1426   private:
  1427     const nsSMILInstanceTime* mCurrentIntervalBegin;
  1428   };
  1431 void
  1432 nsSMILTimedElement::Reset()
  1434   RemoveReset resetBegin(mCurrentInterval ? mCurrentInterval->Begin() : nullptr);
  1435   RemoveInstanceTimes(mBeginInstances, resetBegin);
  1437   RemoveReset resetEnd(nullptr);
  1438   RemoveInstanceTimes(mEndInstances, resetEnd);
  1441 void
  1442 nsSMILTimedElement::DoPostSeek()
  1444   // Finish backwards seek
  1445   if (mSeekState == SEEK_BACKWARD_FROM_INACTIVE ||
  1446       mSeekState == SEEK_BACKWARD_FROM_ACTIVE) {
  1447     // Previously some dynamic instance times may have been marked to be
  1448     // preserved because they were endpoints of an historic interval (which may
  1449     // or may not have been filtered). Now that we've finished a seek we should
  1450     // clear that flag for those instance times whose intervals are no longer
  1451     // historic.
  1452     UnpreserveInstanceTimes(mBeginInstances);
  1453     UnpreserveInstanceTimes(mEndInstances);
  1455     // Now that the times have been unmarked perform a reset. This might seem
  1456     // counter-intuitive when we're only doing a seek within an interval but
  1457     // SMIL seems to require this. SMIL 3.0, 'Hyperlinks and timing':
  1458     //   Resolved end times associated with events, Repeat-values,
  1459     //   Accesskey-values or added via DOM method calls are cleared when seeking
  1460     //   to time earlier than the resolved end time.
  1461     Reset();
  1462     UpdateCurrentInterval();
  1465   switch (mSeekState)
  1467   case SEEK_FORWARD_FROM_ACTIVE:
  1468   case SEEK_BACKWARD_FROM_ACTIVE:
  1469     if (mElementState != STATE_ACTIVE) {
  1470       FireTimeEventAsync(NS_SMIL_END, 0);
  1472     break;
  1474   case SEEK_FORWARD_FROM_INACTIVE:
  1475   case SEEK_BACKWARD_FROM_INACTIVE:
  1476     if (mElementState == STATE_ACTIVE) {
  1477       FireTimeEventAsync(NS_SMIL_BEGIN, 0);
  1479     break;
  1481   case SEEK_NOT_SEEKING:
  1482     /* Do nothing */
  1483     break;
  1486   mSeekState = SEEK_NOT_SEEKING;
  1489 void
  1490 nsSMILTimedElement::UnpreserveInstanceTimes(InstanceTimeList& aList)
  1492   const nsSMILInterval* prevInterval = GetPreviousInterval();
  1493   const nsSMILInstanceTime* cutoff = mCurrentInterval ?
  1494       mCurrentInterval->Begin() :
  1495       prevInterval ? prevInterval->Begin() : nullptr;
  1496   uint32_t count = aList.Length();
  1497   for (uint32_t i = 0; i < count; ++i) {
  1498     nsSMILInstanceTime* instance = aList[i].get();
  1499     if (!cutoff || cutoff->Time().CompareTo(instance->Time()) < 0) {
  1500       instance->UnmarkShouldPreserve();
  1505 void
  1506 nsSMILTimedElement::FilterHistory()
  1508   // We should filter the intervals first, since instance times still used in an
  1509   // interval won't be filtered.
  1510   FilterIntervals();
  1511   FilterInstanceTimes(mBeginInstances);
  1512   FilterInstanceTimes(mEndInstances);
  1515 void
  1516 nsSMILTimedElement::FilterIntervals()
  1518   // We can filter old intervals that:
  1519   //
  1520   // a) are not the previous interval; AND
  1521   // b) are not in the middle of a dependency chain; AND
  1522   // c) are not the first interval
  1523   //
  1524   // Condition (a) is necessary since the previous interval is used for applying
  1525   // fill effects and updating the current interval.
  1526   //
  1527   // Condition (b) is necessary since even if this interval itself is not
  1528   // active, it may be part of a dependency chain that includes active
  1529   // intervals. Such chains are used to establish priorities within the
  1530   // animation sandwich.
  1531   //
  1532   // Condition (c) is necessary to support hyperlinks that target animations
  1533   // since in some cases the defined behavior is to seek the document back to
  1534   // the first resolved begin time. Presumably the intention here is not
  1535   // actually to use the first resolved begin time, the
  1536   // _the_first_resolved_begin_time_that_produced_an_interval. That is,
  1537   // if we have begin="-5s; -3s; 1s; 3s" with a duration on 1s, we should seek
  1538   // to 1s. The spec doesn't say this but I'm pretty sure that is the intention.
  1539   // It seems negative times were simply not considered.
  1540   //
  1541   // Although the above conditions allow us to safely filter intervals for most
  1542   // scenarios they do not cover all cases and there will still be scenarios
  1543   // that generate intervals indefinitely. In such a case we simply set
  1544   // a maximum number of intervals and drop any intervals beyond that threshold.
  1546   uint32_t threshold = mOldIntervals.Length() > sMaxNumIntervals ?
  1547                        mOldIntervals.Length() - sMaxNumIntervals :
  1548                        0;
  1549   IntervalList filteredList;
  1550   for (uint32_t i = 0; i < mOldIntervals.Length(); ++i)
  1552     nsSMILInterval* interval = mOldIntervals[i].get();
  1553     if (i != 0 && /*skip first interval*/
  1554         i + 1 < mOldIntervals.Length() && /*skip previous interval*/
  1555         (i < threshold || !interval->IsDependencyChainLink())) {
  1556       interval->Unlink(true /*filtered, not deleted*/);
  1557     } else {
  1558       filteredList.AppendElement(mOldIntervals[i].forget());
  1561   mOldIntervals.Clear();
  1562   mOldIntervals.SwapElements(filteredList);
  1565 namespace
  1567   class MOZ_STACK_CLASS RemoveFiltered
  1569   public:
  1570     RemoveFiltered(nsSMILTimeValue aCutoff) : mCutoff(aCutoff) { }
  1571     bool operator()(nsSMILInstanceTime* aInstanceTime, uint32_t /*aIndex*/)
  1573       // We can filter instance times that:
  1574       // a) Precede the end point of the previous interval; AND
  1575       // b) Are NOT syncbase times that might be updated to a time after the end
  1576       //    point of the previous interval; AND
  1577       // c) Are NOT fixed end points in any remaining interval.
  1578       return aInstanceTime->Time() < mCutoff &&
  1579              aInstanceTime->IsFixedTime() &&
  1580              !aInstanceTime->ShouldPreserve();
  1583   private:
  1584     nsSMILTimeValue mCutoff;
  1585   };
  1587   class MOZ_STACK_CLASS RemoveBelowThreshold
  1589   public:
  1590     RemoveBelowThreshold(uint32_t aThreshold,
  1591                          nsTArray<const nsSMILInstanceTime *>& aTimesToKeep)
  1592       : mThreshold(aThreshold),
  1593         mTimesToKeep(aTimesToKeep) { }
  1594     bool operator()(nsSMILInstanceTime* aInstanceTime, uint32_t aIndex)
  1596       return aIndex < mThreshold && !mTimesToKeep.Contains(aInstanceTime);
  1599   private:
  1600     uint32_t mThreshold;
  1601     nsTArray<const nsSMILInstanceTime *>& mTimesToKeep;
  1602   };
  1605 void
  1606 nsSMILTimedElement::FilterInstanceTimes(InstanceTimeList& aList)
  1608   if (GetPreviousInterval()) {
  1609     RemoveFiltered removeFiltered(GetPreviousInterval()->End()->Time());
  1610     RemoveInstanceTimes(aList, removeFiltered);
  1613   // As with intervals it is possible to create a document that, even despite
  1614   // our most aggressive filtering, will generate instance times indefinitely
  1615   // (e.g. cyclic dependencies with TimeEvents---we can't filter such times as
  1616   // they're unpredictable due to the possibility of seeking the document which
  1617   // may prevent some events from being generated). Therefore we introduce
  1618   // a hard cutoff at which point we just drop the oldest instance times.
  1619   if (aList.Length() > sMaxNumInstanceTimes) {
  1620     uint32_t threshold = aList.Length() - sMaxNumInstanceTimes;
  1621     // There are a few instance times we should keep though, notably:
  1622     // - the current interval begin time,
  1623     // - the previous interval end time (see note in RemoveInstanceTimes)
  1624     // - the first interval begin time (see note in FilterIntervals)
  1625     nsTArray<const nsSMILInstanceTime *> timesToKeep;
  1626     if (mCurrentInterval) {
  1627       timesToKeep.AppendElement(mCurrentInterval->Begin());
  1629     const nsSMILInterval* prevInterval = GetPreviousInterval();
  1630     if (prevInterval) {
  1631       timesToKeep.AppendElement(prevInterval->End());
  1633     if (!mOldIntervals.IsEmpty()) {
  1634       timesToKeep.AppendElement(mOldIntervals[0]->Begin());
  1636     RemoveBelowThreshold removeBelowThreshold(threshold, timesToKeep);
  1637     RemoveInstanceTimes(aList, removeBelowThreshold);
  1641 //
  1642 // This method is based on the pseudocode given in the SMILANIM spec.
  1643 //
  1644 // See:
  1645 // http://www.w3.org/TR/2001/REC-smil-animation-20010904/#Timing-BeginEnd-LC-Start
  1646 //
  1647 bool
  1648 nsSMILTimedElement::GetNextInterval(const nsSMILInterval* aPrevInterval,
  1649                                     const nsSMILInterval* aReplacedInterval,
  1650                                     const nsSMILInstanceTime* aFixedBeginTime,
  1651                                     nsSMILInterval& aResult) const
  1653   NS_ABORT_IF_FALSE(!aFixedBeginTime || aFixedBeginTime->Time().IsDefinite(),
  1654       "Unresolved or indefinite begin time specified for interval start");
  1655   static const nsSMILTimeValue zeroTime(0L);
  1657   if (mRestartMode == RESTART_NEVER && aPrevInterval)
  1658     return false;
  1660   // Calc starting point
  1661   nsSMILTimeValue beginAfter;
  1662   bool prevIntervalWasZeroDur = false;
  1663   if (aPrevInterval) {
  1664     beginAfter = aPrevInterval->End()->Time();
  1665     prevIntervalWasZeroDur
  1666       = aPrevInterval->End()->Time() == aPrevInterval->Begin()->Time();
  1667   } else {
  1668     beginAfter.SetMillis(INT64_MIN);
  1671   nsRefPtr<nsSMILInstanceTime> tempBegin;
  1672   nsRefPtr<nsSMILInstanceTime> tempEnd;
  1674   while (true) {
  1675     // Calculate begin time
  1676     if (aFixedBeginTime) {
  1677       if (aFixedBeginTime->Time() < beginAfter) {
  1678         return false;
  1680       // our ref-counting is not const-correct
  1681       tempBegin = const_cast<nsSMILInstanceTime*>(aFixedBeginTime);
  1682     } else if ((!mAnimationElement ||
  1683                 !mAnimationElement->HasAnimAttr(nsGkAtoms::begin)) &&
  1684                beginAfter <= zeroTime) {
  1685       tempBegin = new nsSMILInstanceTime(nsSMILTimeValue(0));
  1686     } else {
  1687       int32_t beginPos = 0;
  1688       do {
  1689         tempBegin =
  1690           GetNextGreaterOrEqual(mBeginInstances, beginAfter, beginPos);
  1691         if (!tempBegin || !tempBegin->Time().IsDefinite()) {
  1692           return false;
  1694       // If we're updating the current interval then skip any begin time that is
  1695       // dependent on the current interval's begin time. e.g.
  1696       //   <animate id="a" begin="b.begin; a.begin+2s"...
  1697       // If b's interval disappears whilst 'a' is in the waiting state the begin
  1698       // time at "a.begin+2s" should be skipped since 'a' never begun.
  1699       } while (aReplacedInterval &&
  1700                tempBegin->GetBaseTime() == aReplacedInterval->Begin());
  1702     NS_ABORT_IF_FALSE(tempBegin && tempBegin->Time().IsDefinite() &&
  1703         tempBegin->Time() >= beginAfter,
  1704         "Got a bad begin time while fetching next interval");
  1706     // Calculate end time
  1708       int32_t endPos = 0;
  1709       do {
  1710         tempEnd =
  1711           GetNextGreaterOrEqual(mEndInstances, tempBegin->Time(), endPos);
  1713         // SMIL doesn't allow for coincident zero-duration intervals, so if the
  1714         // previous interval was zero-duration, and tempEnd is going to give us
  1715         // another zero duration interval, then look for another end to use
  1716         // instead.
  1717         if (tempEnd && prevIntervalWasZeroDur &&
  1718             tempEnd->Time() == beginAfter) {
  1719           tempEnd = GetNextGreater(mEndInstances, tempBegin->Time(), endPos);
  1721       // As above with begin times, avoid creating self-referential loops
  1722       // between instance times by checking that the newly found end instance
  1723       // time is not already dependent on the end of the current interval.
  1724       } while (tempEnd && aReplacedInterval &&
  1725                tempEnd->GetBaseTime() == aReplacedInterval->End());
  1727       if (!tempEnd) {
  1728         // If all the ends are before the beginning we have a bad interval
  1729         // UNLESS:
  1730         // a) We never had any end attribute to begin with (the SMIL pseudocode
  1731         //    places this condition earlier in the flow but that fails to allow
  1732         //    for DOM calls when no "indefinite" condition is given), OR
  1733         // b) We never had any end instance times to begin with, OR
  1734         // c) We have end events which leave the interval open-ended.
  1735         bool openEndedIntervalOk = mEndSpecs.IsEmpty() ||
  1736                                    mEndInstances.IsEmpty() ||
  1737                                    EndHasEventConditions();
  1739         // The above conditions correspond with the SMIL pseudocode but SMIL
  1740         // doesn't address self-dependent instance times which we choose to
  1741         // ignore.
  1742         //
  1743         // Therefore we add a qualification of (b) above that even if
  1744         // there are end instance times but they all depend on the end of the
  1745         // current interval we should act as if they didn't exist and allow the
  1746         // open-ended interval.
  1747         //
  1748         // In the following condition we don't use |= because it doesn't provide
  1749         // short-circuit behavior.
  1750         openEndedIntervalOk = openEndedIntervalOk ||
  1751                              (aReplacedInterval &&
  1752                               AreEndTimesDependentOn(aReplacedInterval->End()));
  1754         if (!openEndedIntervalOk) {
  1755           return false; // Bad interval
  1759       nsSMILTimeValue intervalEnd = tempEnd
  1760                                   ? tempEnd->Time() : nsSMILTimeValue();
  1761       nsSMILTimeValue activeEnd = CalcActiveEnd(tempBegin->Time(), intervalEnd);
  1763       if (!tempEnd || intervalEnd != activeEnd) {
  1764         tempEnd = new nsSMILInstanceTime(activeEnd);
  1767     NS_ABORT_IF_FALSE(tempEnd, "Failed to get end point for next interval");
  1769     // When we choose the interval endpoints, we don't allow coincident
  1770     // zero-duration intervals, so if we arrive here and we have a zero-duration
  1771     // interval starting at the same point as a previous zero-duration interval,
  1772     // then it must be because we've applied constraints to the active duration.
  1773     // In that case, we will potentially run into an infinite loop, so we break
  1774     // it by searching for the next interval that starts AFTER our current
  1775     // zero-duration interval.
  1776     if (prevIntervalWasZeroDur && tempEnd->Time() == beginAfter) {
  1777       if (prevIntervalWasZeroDur) {
  1778         beginAfter.SetMillis(tempBegin->Time().GetMillis() + 1);
  1779         prevIntervalWasZeroDur = false;
  1780         continue;
  1783     prevIntervalWasZeroDur = tempBegin->Time() == tempEnd->Time();
  1785     // Check for valid interval
  1786     if (tempEnd->Time() > zeroTime ||
  1787        (tempBegin->Time() == zeroTime && tempEnd->Time() == zeroTime)) {
  1788       aResult.Set(*tempBegin, *tempEnd);
  1789       return true;
  1792     if (mRestartMode == RESTART_NEVER) {
  1793       // tempEnd <= 0 so we're going to loop which effectively means restarting
  1794       return false;
  1797     beginAfter = tempEnd->Time();
  1799   NS_NOTREACHED("Hmm... we really shouldn't be here");
  1801   return false;
  1804 nsSMILInstanceTime*
  1805 nsSMILTimedElement::GetNextGreater(const InstanceTimeList& aList,
  1806                                    const nsSMILTimeValue& aBase,
  1807                                    int32_t& aPosition) const
  1809   nsSMILInstanceTime* result = nullptr;
  1810   while ((result = GetNextGreaterOrEqual(aList, aBase, aPosition)) &&
  1811          result->Time() == aBase) { }
  1812   return result;
  1815 nsSMILInstanceTime*
  1816 nsSMILTimedElement::GetNextGreaterOrEqual(const InstanceTimeList& aList,
  1817                                           const nsSMILTimeValue& aBase,
  1818                                           int32_t& aPosition) const
  1820   nsSMILInstanceTime* result = nullptr;
  1821   int32_t count = aList.Length();
  1823   for (; aPosition < count && !result; ++aPosition) {
  1824     nsSMILInstanceTime* val = aList[aPosition].get();
  1825     NS_ABORT_IF_FALSE(val, "NULL instance time in list");
  1826     if (val->Time() >= aBase) {
  1827       result = val;
  1831   return result;
  1834 /**
  1835  * @see SMILANIM 3.3.4
  1836  */
  1837 nsSMILTimeValue
  1838 nsSMILTimedElement::CalcActiveEnd(const nsSMILTimeValue& aBegin,
  1839                                   const nsSMILTimeValue& aEnd) const
  1841   nsSMILTimeValue result;
  1843   NS_ABORT_IF_FALSE(mSimpleDur.IsResolved(),
  1844     "Unresolved simple duration in CalcActiveEnd");
  1845   NS_ABORT_IF_FALSE(aBegin.IsDefinite(),
  1846     "Indefinite or unresolved begin time in CalcActiveEnd");
  1848   result = GetRepeatDuration();
  1850   if (aEnd.IsDefinite()) {
  1851     nsSMILTime activeDur = aEnd.GetMillis() - aBegin.GetMillis();
  1853     if (result.IsDefinite()) {
  1854       result.SetMillis(std::min(result.GetMillis(), activeDur));
  1855     } else {
  1856       result.SetMillis(activeDur);
  1860   result = ApplyMinAndMax(result);
  1862   if (result.IsDefinite()) {
  1863     nsSMILTime activeEnd = result.GetMillis() + aBegin.GetMillis();
  1864     result.SetMillis(activeEnd);
  1867   return result;
  1870 nsSMILTimeValue
  1871 nsSMILTimedElement::GetRepeatDuration() const
  1873   nsSMILTimeValue multipliedDuration;
  1874   if (mRepeatCount.IsDefinite() && mSimpleDur.IsDefinite()) {
  1875     multipliedDuration.SetMillis(
  1876       nsSMILTime(mRepeatCount * double(mSimpleDur.GetMillis())));
  1877   } else {
  1878     multipliedDuration.SetIndefinite();
  1881   nsSMILTimeValue repeatDuration;
  1883   if (mRepeatDur.IsResolved()) {
  1884     repeatDuration = std::min(multipliedDuration, mRepeatDur);
  1885   } else if (mRepeatCount.IsSet()) {
  1886     repeatDuration = multipliedDuration;
  1887   } else {
  1888     repeatDuration = mSimpleDur;
  1891   return repeatDuration;
  1894 nsSMILTimeValue
  1895 nsSMILTimedElement::ApplyMinAndMax(const nsSMILTimeValue& aDuration) const
  1897   if (!aDuration.IsResolved()) {
  1898     return aDuration;
  1901   if (mMax < mMin) {
  1902     return aDuration;
  1905   nsSMILTimeValue result;
  1907   if (aDuration > mMax) {
  1908     result = mMax;
  1909   } else if (aDuration < mMin) {
  1910     result = mMin;
  1911   } else {
  1912     result = aDuration;
  1915   return result;
  1918 nsSMILTime
  1919 nsSMILTimedElement::ActiveTimeToSimpleTime(nsSMILTime aActiveTime,
  1920                                            uint32_t& aRepeatIteration)
  1922   nsSMILTime result;
  1924   NS_ABORT_IF_FALSE(mSimpleDur.IsResolved(),
  1925       "Unresolved simple duration in ActiveTimeToSimpleTime");
  1926   NS_ABORT_IF_FALSE(aActiveTime >= 0, "Expecting non-negative active time");
  1927   // Note that a negative aActiveTime will give us a negative value for
  1928   // aRepeatIteration, which is bad because aRepeatIteration is unsigned
  1930   if (mSimpleDur.IsIndefinite() || mSimpleDur.GetMillis() == 0L) {
  1931     aRepeatIteration = 0;
  1932     result = aActiveTime;
  1933   } else {
  1934     result = aActiveTime % mSimpleDur.GetMillis();
  1935     aRepeatIteration = (uint32_t)(aActiveTime / mSimpleDur.GetMillis());
  1938   return result;
  1941 //
  1942 // Although in many cases it would be possible to check for an early end and
  1943 // adjust the current interval well in advance the SMIL Animation spec seems to
  1944 // indicate that we should only apply an early end at the latest possible
  1945 // moment. In particular, this paragraph from section 3.6.8:
  1946 //
  1947 // 'If restart  is set to "always", then the current interval will end early if
  1948 // there is an instance time in the begin list that is before (i.e. earlier
  1949 // than) the defined end for the current interval. Ending in this manner will
  1950 // also send a changed time notice to all time dependents for the current
  1951 // interval end.'
  1952 //
  1953 nsSMILInstanceTime*
  1954 nsSMILTimedElement::CheckForEarlyEnd(
  1955     const nsSMILTimeValue& aContainerTime) const
  1957   NS_ABORT_IF_FALSE(mCurrentInterval,
  1958       "Checking for an early end but the current interval is not set");
  1959   if (mRestartMode != RESTART_ALWAYS)
  1960     return nullptr;
  1962   int32_t position = 0;
  1963   nsSMILInstanceTime* nextBegin =
  1964     GetNextGreater(mBeginInstances, mCurrentInterval->Begin()->Time(),
  1965                    position);
  1967   if (nextBegin &&
  1968       nextBegin->Time() > mCurrentInterval->Begin()->Time() &&
  1969       nextBegin->Time() < mCurrentInterval->End()->Time() &&
  1970       nextBegin->Time() <= aContainerTime) {
  1971     return nextBegin;
  1974   return nullptr;
  1977 void
  1978 nsSMILTimedElement::UpdateCurrentInterval(bool aForceChangeNotice)
  1980   // Check if updates are currently blocked (batched)
  1981   if (mDeferIntervalUpdates) {
  1982     mDoDeferredUpdate = true;
  1983     return;
  1986   // We adopt the convention of not resolving intervals until the first
  1987   // sample. Otherwise, every time each attribute is set we'll re-resolve the
  1988   // current interval and notify all our time dependents of the change.
  1989   //
  1990   // The disadvantage of deferring resolving the interval is that DOM calls to
  1991   // to getStartTime will throw an INVALID_STATE_ERR exception until the
  1992   // document timeline begins since the start time has not yet been resolved.
  1993   if (mElementState == STATE_STARTUP)
  1994     return;
  1996   // Although SMIL gives rules for detecting cycles in change notifications,
  1997   // some configurations can lead to create-delete-create-delete-etc. cycles
  1998   // which SMIL does not consider.
  1999   //
  2000   // In order to provide consistent behavior in such cases, we detect two
  2001   // deletes in a row and then refuse to create any further intervals. That is,
  2002   // we say the configuration is invalid.
  2003   if (mDeleteCount > 1) {
  2004     // When we update the delete count we also set the state to post active, so
  2005     // if we're not post active here then something other than
  2006     // UpdateCurrentInterval has updated the element state in between and all
  2007     // bets are off.
  2008     NS_ABORT_IF_FALSE(mElementState == STATE_POSTACTIVE,
  2009       "Expected to be in post-active state after performing double delete");
  2010     return;
  2013   // Check that we aren't stuck in infinite recursion updating some syncbase
  2014   // dependencies. Generally such situations should be detected in advance and
  2015   // the chain broken in a sensible and predictable manner, so if we're hitting
  2016   // this assertion we need to work out how to detect the case that's causing
  2017   // it. In release builds, just bail out before we overflow the stack.
  2018   AutoRestore<uint8_t> depthRestorer(mUpdateIntervalRecursionDepth);
  2019   if (++mUpdateIntervalRecursionDepth > sMaxUpdateIntervalRecursionDepth) {
  2020     NS_ABORT_IF_FALSE(false,
  2021         "Update current interval recursion depth exceeded threshold");
  2022     return;
  2025   // If the interval is active the begin time is fixed.
  2026   const nsSMILInstanceTime* beginTime = mElementState == STATE_ACTIVE
  2027                                       ? mCurrentInterval->Begin()
  2028                                       : nullptr;
  2029   nsSMILInterval updatedInterval;
  2030   if (GetNextInterval(GetPreviousInterval(), mCurrentInterval,
  2031                       beginTime, updatedInterval)) {
  2033     if (mElementState == STATE_POSTACTIVE) {
  2035       NS_ABORT_IF_FALSE(!mCurrentInterval,
  2036           "In postactive state but the interval has been set");
  2037       mCurrentInterval = new nsSMILInterval(updatedInterval);
  2038       mElementState = STATE_WAITING;
  2039       NotifyNewInterval();
  2041     } else {
  2043       bool beginChanged = false;
  2044       bool endChanged   = false;
  2046       if (mElementState != STATE_ACTIVE &&
  2047           !updatedInterval.Begin()->SameTimeAndBase(
  2048             *mCurrentInterval->Begin())) {
  2049         mCurrentInterval->SetBegin(*updatedInterval.Begin());
  2050         beginChanged = true;
  2053       if (!updatedInterval.End()->SameTimeAndBase(*mCurrentInterval->End())) {
  2054         mCurrentInterval->SetEnd(*updatedInterval.End());
  2055         endChanged = true;
  2058       if (beginChanged || endChanged || aForceChangeNotice) {
  2059         NotifyChangedInterval(mCurrentInterval, beginChanged, endChanged);
  2063     // There's a chance our next milestone has now changed, so update the time
  2064     // container
  2065     RegisterMilestone();
  2066   } else { // GetNextInterval failed: Current interval is no longer valid
  2067     if (mElementState == STATE_ACTIVE) {
  2068       // The interval is active so we can't just delete it, instead trim it so
  2069       // that begin==end.
  2070       if (!mCurrentInterval->End()->SameTimeAndBase(*mCurrentInterval->Begin()))
  2072         mCurrentInterval->SetEnd(*mCurrentInterval->Begin());
  2073         NotifyChangedInterval(mCurrentInterval, false, true);
  2075       // The transition to the postactive state will take place on the next
  2076       // sample (along with firing end events, clearing intervals etc.)
  2077       RegisterMilestone();
  2078     } else if (mElementState == STATE_WAITING) {
  2079       AutoRestore<uint8_t> deleteCountRestorer(mDeleteCount);
  2080       ++mDeleteCount;
  2081       mElementState = STATE_POSTACTIVE;
  2082       ResetCurrentInterval();
  2087 void
  2088 nsSMILTimedElement::SampleSimpleTime(nsSMILTime aActiveTime)
  2090   if (mClient) {
  2091     uint32_t repeatIteration;
  2092     nsSMILTime simpleTime =
  2093       ActiveTimeToSimpleTime(aActiveTime, repeatIteration);
  2094     mClient->SampleAt(simpleTime, mSimpleDur, repeatIteration);
  2098 void
  2099 nsSMILTimedElement::SampleFillValue()
  2101   if (mFillMode != FILL_FREEZE || !mClient)
  2102     return;
  2104   nsSMILTime activeTime;
  2106   if (mElementState == STATE_WAITING || mElementState == STATE_POSTACTIVE) {
  2107     const nsSMILInterval* prevInterval = GetPreviousInterval();
  2108     NS_ABORT_IF_FALSE(prevInterval,
  2109         "Attempting to sample fill value but there is no previous interval");
  2110     NS_ABORT_IF_FALSE(prevInterval->End()->Time().IsDefinite() &&
  2111         prevInterval->End()->IsFixedTime(),
  2112         "Attempting to sample fill value but the endpoint of the previous "
  2113         "interval is not resolved and fixed");
  2115     activeTime = prevInterval->End()->Time().GetMillis() -
  2116                  prevInterval->Begin()->Time().GetMillis();
  2118     // If the interval's repeat duration was shorter than its active duration,
  2119     // use the end of the repeat duration to determine the frozen animation's
  2120     // state.
  2121     nsSMILTimeValue repeatDuration = GetRepeatDuration();
  2122     if (repeatDuration.IsDefinite()) {
  2123       activeTime = std::min(repeatDuration.GetMillis(), activeTime);
  2125   } else {
  2126     MOZ_ASSERT(mElementState == STATE_ACTIVE,
  2127         "Attempting to sample fill value when we're in an unexpected state "
  2128         "(probably STATE_STARTUP)");
  2130     // If we are being asked to sample the fill value while active we *must*
  2131     // have a repeat duration shorter than the active duration so use that.
  2132     MOZ_ASSERT(GetRepeatDuration().IsDefinite(),
  2133         "Attempting to sample fill value of an active animation with "
  2134         "an indefinite repeat duration");
  2135     activeTime = GetRepeatDuration().GetMillis();
  2138   uint32_t repeatIteration;
  2139   nsSMILTime simpleTime =
  2140     ActiveTimeToSimpleTime(activeTime, repeatIteration);
  2142   if (simpleTime == 0L && repeatIteration) {
  2143     mClient->SampleLastValue(--repeatIteration);
  2144   } else {
  2145     mClient->SampleAt(simpleTime, mSimpleDur, repeatIteration);
  2149 nsresult
  2150 nsSMILTimedElement::AddInstanceTimeFromCurrentTime(nsSMILTime aCurrentTime,
  2151     double aOffsetSeconds, bool aIsBegin)
  2153   double offset = aOffsetSeconds * PR_MSEC_PER_SEC;
  2155   // Check we won't overflow the range of nsSMILTime
  2156   if (aCurrentTime + NS_round(offset) > INT64_MAX)
  2157     return NS_ERROR_ILLEGAL_VALUE;
  2159   nsSMILTimeValue timeVal(aCurrentTime + int64_t(NS_round(offset)));
  2161   nsRefPtr<nsSMILInstanceTime> instanceTime =
  2162     new nsSMILInstanceTime(timeVal, nsSMILInstanceTime::SOURCE_DOM);
  2164   AddInstanceTime(instanceTime, aIsBegin);
  2166   return NS_OK;
  2169 void
  2170 nsSMILTimedElement::RegisterMilestone()
  2172   nsSMILTimeContainer* container = GetTimeContainer();
  2173   if (!container)
  2174     return;
  2175   NS_ABORT_IF_FALSE(mAnimationElement,
  2176       "Got a time container without an owning animation element");
  2178   nsSMILMilestone nextMilestone;
  2179   if (!GetNextMilestone(nextMilestone))
  2180     return;
  2182   // This method is called every time we might possibly have updated our
  2183   // current interval, but since nsSMILTimeContainer makes no attempt to filter
  2184   // out redundant milestones we do some rudimentary filtering here. It's not
  2185   // perfect, but unnecessary samples are fairly cheap.
  2186   if (nextMilestone >= mPrevRegisteredMilestone)
  2187     return;
  2189   container->AddMilestone(nextMilestone, *mAnimationElement);
  2190   mPrevRegisteredMilestone = nextMilestone;
  2193 bool
  2194 nsSMILTimedElement::GetNextMilestone(nsSMILMilestone& aNextMilestone) const
  2196   // Return the next key moment in our lifetime.
  2197   //
  2198   // XXX It may be possible in future to optimise this so that we only register
  2199   // for milestones if:
  2200   // a) We have time dependents, or
  2201   // b) We are dependent on events or syncbase relationships, or
  2202   // c) There are registered listeners for our events
  2203   //
  2204   // Then for the simple case where everything uses offset values we could
  2205   // ignore milestones altogether.
  2206   //
  2207   // We'd need to be careful, however, that if one of those conditions became
  2208   // true in between samples that we registered our next milestone at that
  2209   // point.
  2211   switch (mElementState)
  2213   case STATE_STARTUP:
  2214     // All elements register for an initial end sample at t=0 where we resolve
  2215     // our initial interval.
  2216     aNextMilestone.mIsEnd = true; // Initial sample should be an end sample
  2217     aNextMilestone.mTime = 0;
  2218     return true;
  2220   case STATE_WAITING:
  2221     NS_ABORT_IF_FALSE(mCurrentInterval,
  2222         "In waiting state but the current interval has not been set");
  2223     aNextMilestone.mIsEnd = false;
  2224     aNextMilestone.mTime = mCurrentInterval->Begin()->Time().GetMillis();
  2225     return true;
  2227   case STATE_ACTIVE:
  2229       // Work out what comes next: the interval end or the next repeat iteration
  2230       nsSMILTimeValue nextRepeat;
  2231       if (mSeekState == SEEK_NOT_SEEKING && mSimpleDur.IsDefinite()) {
  2232         nsSMILTime nextRepeatActiveTime =
  2233           (mCurrentRepeatIteration + 1) * mSimpleDur.GetMillis();
  2234         // Check that the repeat fits within the repeat duration
  2235         if (nsSMILTimeValue(nextRepeatActiveTime) < GetRepeatDuration()) {
  2236           nextRepeat.SetMillis(mCurrentInterval->Begin()->Time().GetMillis() +
  2237                                nextRepeatActiveTime);
  2240       nsSMILTimeValue nextMilestone =
  2241         std::min(mCurrentInterval->End()->Time(), nextRepeat);
  2243       // Check for an early end before that time
  2244       nsSMILInstanceTime* earlyEnd = CheckForEarlyEnd(nextMilestone);
  2245       if (earlyEnd) {
  2246         aNextMilestone.mIsEnd = true;
  2247         aNextMilestone.mTime = earlyEnd->Time().GetMillis();
  2248         return true;
  2251       // Apply the previously calculated milestone
  2252       if (nextMilestone.IsDefinite()) {
  2253         aNextMilestone.mIsEnd = nextMilestone != nextRepeat;
  2254         aNextMilestone.mTime = nextMilestone.GetMillis();
  2255         return true;
  2258       return false;
  2261   case STATE_POSTACTIVE:
  2262     return false;
  2264   MOZ_CRASH("Invalid element state");
  2267 void
  2268 nsSMILTimedElement::NotifyNewInterval()
  2270   NS_ABORT_IF_FALSE(mCurrentInterval,
  2271       "Attempting to notify dependents of a new interval but the interval "
  2272       "is not set");
  2274   nsSMILTimeContainer* container = GetTimeContainer();
  2275   if (container) {
  2276     container->SyncPauseTime();
  2279   NotifyTimeDependentsParams params = { this, container };
  2280   mTimeDependents.EnumerateEntries(NotifyNewIntervalCallback, &params);
  2283 void
  2284 nsSMILTimedElement::NotifyChangedInterval(nsSMILInterval* aInterval,
  2285                                           bool aBeginObjectChanged,
  2286                                           bool aEndObjectChanged)
  2288   NS_ABORT_IF_FALSE(aInterval, "Null interval for change notification");
  2290   nsSMILTimeContainer* container = GetTimeContainer();
  2291   if (container) {
  2292     container->SyncPauseTime();
  2295   // Copy the instance times list since notifying the instance times can result
  2296   // in a chain reaction whereby our own interval gets deleted along with its
  2297   // instance times.
  2298   InstanceTimeList times;
  2299   aInterval->GetDependentTimes(times);
  2301   for (uint32_t i = 0; i < times.Length(); ++i) {
  2302     times[i]->HandleChangedInterval(container, aBeginObjectChanged,
  2303                                     aEndObjectChanged);
  2307 void
  2308 nsSMILTimedElement::FireTimeEventAsync(uint32_t aMsg, int32_t aDetail)
  2310   if (!mAnimationElement)
  2311     return;
  2313   nsCOMPtr<nsIRunnable> event =
  2314     new AsyncTimeEventRunner(mAnimationElement, aMsg, aDetail);
  2315   NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
  2318 const nsSMILInstanceTime*
  2319 nsSMILTimedElement::GetEffectiveBeginInstance() const
  2321   switch (mElementState)
  2323   case STATE_STARTUP:
  2324     return nullptr;
  2326   case STATE_ACTIVE:
  2327     return mCurrentInterval->Begin();
  2329   case STATE_WAITING:
  2330   case STATE_POSTACTIVE:
  2332       const nsSMILInterval* prevInterval = GetPreviousInterval();
  2333       return prevInterval ? prevInterval->Begin() : nullptr;
  2336   MOZ_CRASH("Invalid element state");
  2339 const nsSMILInterval*
  2340 nsSMILTimedElement::GetPreviousInterval() const
  2342   return mOldIntervals.IsEmpty()
  2343     ? nullptr
  2344     : mOldIntervals[mOldIntervals.Length()-1].get();
  2347 bool
  2348 nsSMILTimedElement::HasClientInFillRange() const
  2350   // Returns true if we have a client that is in the range where it will fill
  2351   return mClient &&
  2352          ((mElementState != STATE_ACTIVE && HasPlayed()) ||
  2353           (mElementState == STATE_ACTIVE && !mClient->IsActive()));
  2356 bool
  2357 nsSMILTimedElement::EndHasEventConditions() const
  2359   for (uint32_t i = 0; i < mEndSpecs.Length(); ++i) {
  2360     if (mEndSpecs[i]->IsEventBased())
  2361       return true;
  2363   return false;
  2366 bool
  2367 nsSMILTimedElement::AreEndTimesDependentOn(
  2368   const nsSMILInstanceTime* aBase) const
  2370   if (mEndInstances.IsEmpty())
  2371     return false;
  2373   for (uint32_t i = 0; i < mEndInstances.Length(); ++i) {
  2374     if (mEndInstances[i]->GetBaseTime() != aBase) {
  2375       return false;
  2378   return true;
  2381 //----------------------------------------------------------------------
  2382 // Hashtable callback functions
  2384 /* static */ PLDHashOperator
  2385 nsSMILTimedElement::NotifyNewIntervalCallback(TimeValueSpecPtrKey* aKey,
  2386                                               void* aData)
  2388   NS_ABORT_IF_FALSE(aKey, "Null hash key for time container hash table");
  2389   NS_ABORT_IF_FALSE(aKey->GetKey(),
  2390                     "null nsSMILTimeValueSpec in set of time dependents");
  2392   NotifyTimeDependentsParams* params =
  2393     static_cast<NotifyTimeDependentsParams*>(aData);
  2394   NS_ABORT_IF_FALSE(params, "null data ptr while enumerating hashtable");
  2395   nsSMILInterval* interval = params->mTimedElement->mCurrentInterval;
  2396   // It's possible that in notifying one new time dependent of a new interval
  2397   // that a chain reaction is triggered which results in the original interval
  2398   // disappearing. If that's the case we can skip sending further notifications.
  2399   if (!interval)
  2400     return PL_DHASH_STOP;
  2402   nsSMILTimeValueSpec* spec = aKey->GetKey();
  2403   spec->HandleNewInterval(*interval, params->mTimeContainer);
  2404   return PL_DHASH_NEXT;

mercurial