LCOV - code coverage report
Current view: top level - dom/smil - nsSMILTimedElement.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 1 992 0.1 %
Date: 2017-07-14 16:53:18 Functions: 0 109 0.0 %
Legend: Lines: hit not hit

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

Generated by: LCOV version 1.13