LCOV - code coverage report
Current view: directory - content/smil - nsSMILTimedElement.cpp (source / functions) Found Hit Coverage
Test: app.info Lines: 924 2 0.2 %
Date: 2012-06-02 Functions: 104 2 1.9 %

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

Generated by: LCOV version 1.7