LCOV - app.info - content/smil/nsSMILTimedElement.cpp

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-07-07 | 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