Bug 492458 - SVG SMIL: Implement backwards seeking - Part 2 - backwards seeking. r=dholbert, sr=roc
authorBrian Birtles <birtles@gmail.com>
Sat, 03 Jul 2010 14:52:51 +0900
changeset 47197 2e0a75bcb159bc6b381b064248a3c7e522eb1bb8
parent 47196 5dbb57834cac5a4e83b10981412252aa63a28270
child 47198 443b653e6b2e5b417e160ad57956149e20d32564
push idunknown
push userunknown
push dateunknown
reviewersdholbert, roc
bugs492458
milestone2.0b2pre
Bug 492458 - SVG SMIL: Implement backwards seeking - Part 2 - backwards seeking. r=dholbert, sr=roc
content/smil/nsSMILAnimationController.cpp
content/smil/nsSMILAnimationController.h
content/smil/nsSMILInstanceTime.cpp
content/smil/nsSMILInstanceTime.h
content/smil/nsSMILTimeContainer.cpp
content/smil/nsSMILTimeContainer.h
content/smil/nsSMILTimedElement.cpp
content/smil/nsSMILTimedElement.h
content/smil/test/Makefile.in
content/smil/test/test_smilBackwardsSeeking.xhtml
content/smil/test/test_smilSetCurrentTime.xhtml
layout/reftests/svg/smil/reftest.list
layout/reftests/svg/smil/seek/anim-standard-ref.svg
layout/reftests/svg/smil/seek/anim-standard-ref.xhtml
layout/reftests/svg/smil/seek/anim-x-seek-1a.svg
layout/reftests/svg/smil/seek/anim-x-seek-1b.svg
layout/reftests/svg/smil/seek/anim-x-seek-1c.svg
layout/reftests/svg/smil/seek/anim-x-seek-1d.svg
layout/reftests/svg/smil/seek/anim-x-seek-1e.svg
layout/reftests/svg/smil/seek/anim-x-seek-cross-container-1a.xhtml
layout/reftests/svg/smil/seek/anim-x-seek-dynamic-1a.svg
layout/reftests/svg/smil/seek/anim-x-seek-dynamic-1b.svg
layout/reftests/svg/smil/seek/anim-x-seek-dynamic-1c.svg
layout/reftests/svg/smil/seek/anim-x-seek-dynamic-1d.svg
layout/reftests/svg/smil/seek/anim-x-seek-dynamic-1e.svg
layout/reftests/svg/smil/seek/anim-x-seek-dynamic-1f.svg
layout/reftests/svg/smil/seek/anim-x-seek-dynamic-1g.svg
layout/reftests/svg/smil/seek/anim-x-seek-dynamic-1h.svg
layout/reftests/svg/smil/seek/anim-x-seek-negative-1a.svg
layout/reftests/svg/smil/seek/reftest.list
--- a/content/smil/nsSMILAnimationController.cpp
+++ b/content/smil/nsSMILAnimationController.cpp
@@ -320,16 +320,19 @@ nsSMILAnimationController::DoSample()
 
 void
 nsSMILAnimationController::DoSample(PRBool aSkipUnchangedContainers)
 {
   // Reset resample flag
   mResampleNeeded = PR_FALSE;
 
   // STEP 1: Bring model up to date
+  // (i)  Rewind elements where necessary
+  // (ii) Run milestone samples
+  RewindElements();
   DoMilestoneSamples();
 
   // STEP 2: Sample the child time containers
   //
   // When we sample the child time containers they will simply record the sample
   // time in document time.
   TimeContainerHashtable activeContainers;
   activeContainers.Init(mChildContainerTable.Count());
@@ -398,16 +401,66 @@ nsSMILAnimationController::DoSample(PRBo
 
   // Update last compositor table
   mLastCompositorTable = currentCompositorTable.forget();
 
   NS_ASSERTION(!mResampleNeeded, "Resample dirty flag set during sample!");
 }
 
 void
+nsSMILAnimationController::RewindElements()
+{
+  PRBool rewindNeeded = PR_FALSE;
+  mChildContainerTable.EnumerateEntries(RewindNeeded, &rewindNeeded);
+  if (!rewindNeeded)
+    return;
+
+  mAnimationElementTable.EnumerateEntries(RewindAnimation, nsnull);
+  mChildContainerTable.EnumerateEntries(ClearRewindNeeded, nsnull);
+}
+
+/*static*/ PR_CALLBACK PLDHashOperator
+nsSMILAnimationController::RewindNeeded(TimeContainerPtrKey* aKey,
+                                        void* aData)
+{
+  NS_ABORT_IF_FALSE(aData,
+      "Null data pointer during time container enumeration");
+  PRBool* rewindNeeded = static_cast<PRBool*>(aData);
+
+  nsSMILTimeContainer* container = aKey->GetKey();
+  if (container->NeedsRewind()) {
+    *rewindNeeded = PR_TRUE;
+    return PL_DHASH_STOP;
+  }
+
+  return PL_DHASH_NEXT;
+}
+
+/*static*/ PR_CALLBACK PLDHashOperator
+nsSMILAnimationController::RewindAnimation(AnimationElementPtrKey* aKey,
+                                           void* aData)
+{
+  nsISMILAnimationElement* animElem = aKey->GetKey();
+  nsSMILTimeContainer* timeContainer = animElem->GetTimeContainer();
+  if (timeContainer && timeContainer->NeedsRewind()) {
+    animElem->TimedElement().Rewind();
+  }
+
+  return PL_DHASH_NEXT;
+}
+
+/*static*/ PR_CALLBACK PLDHashOperator
+nsSMILAnimationController::ClearRewindNeeded(TimeContainerPtrKey* aKey,
+                                             void* aData)
+{
+  aKey->GetKey()->ClearNeedsRewind();
+  return PL_DHASH_NEXT;
+}
+
+void
 nsSMILAnimationController::DoMilestoneSamples()
 {
   // We need to sample the timing model but because SMIL operates independently
   // of the frame-rate, we can get one sample at t=0s and the next at t=10min.
   //
   // In between those two sample times a whole string of significant events
   // might be expected to take place: events firing, new interdependencies
   // between animations resolved and dissolved, etc.
@@ -537,16 +590,17 @@ nsSMILAnimationController::SampleTimeCon
   SampleTimeContainerParams* params =
     static_cast<SampleTimeContainerParams*>(aData);
 
   nsSMILTimeContainer* container = aKey->GetKey();
   if (!container->IsPausedByType(nsSMILTimeContainer::PAUSE_BEGIN) &&
       (container->NeedsSample() || !params->mSkipUnchangedContainers)) {
     container->ClearMilestones();
     container->Sample();
+    container->MarkSeekFinished();
     params->mActiveContainers->PutEntry(container);
   }
 
   return PL_DHASH_NEXT;
 }
 
 /*static*/ PR_CALLBACK PLDHashOperator
 nsSMILAnimationController::SampleAnimation(AnimationElementPtrKey* aKey,
@@ -582,16 +636,18 @@ nsSMILAnimationController::SampleTimedEl
   // Instead we build up a hashmap of active time containers during the previous
   // step (SampleTimeContainer) and then test here if the container for this
   // timed element is in the list.
   if (!aActiveContainers->GetEntry(timeContainer))
     return;
 
   nsSMILTime containerTime = timeContainer->GetCurrentTime();
 
+  NS_ABORT_IF_FALSE(!timeContainer->IsSeeking(),
+      "Doing a regular sample but the time container is still seeking");
   aElement->TimedElement().SampleAt(containerTime);
 }
 
 /*static*/ void
 nsSMILAnimationController::AddAnimationToCompositorTable(
   nsISMILAnimationElement* aElement, nsSMILCompositorTable* aCompositorTable)
 {
   // Add a compositor to the hash table if there's not already one there
--- a/content/smil/nsSMILAnimationController.h
+++ b/content/smil/nsSMILAnimationController.h
@@ -147,21 +147,31 @@ protected:
 
   // Cycle-collection implementation helpers
   PR_STATIC_CALLBACK(PLDHashOperator) CompositorTableEntryTraverse(
       nsSMILCompositor* aCompositor, void* aArg);
 
   // Sample-related callbacks and implementation helpers
   virtual void DoSample();
   void DoSample(PRBool aSkipUnchangedContainers);
+
+  void RewindElements();
+  PR_STATIC_CALLBACK(PLDHashOperator) RewindNeeded(
+      TimeContainerPtrKey* aKey, void* aData);
+  PR_STATIC_CALLBACK(PLDHashOperator) RewindAnimation(
+      AnimationElementPtrKey* aKey, void* aData);
+  PR_STATIC_CALLBACK(PLDHashOperator) ClearRewindNeeded(
+      TimeContainerPtrKey* aKey, void* aData);
+
   void DoMilestoneSamples();
   PR_STATIC_CALLBACK(PLDHashOperator) GetNextMilestone(
       TimeContainerPtrKey* aKey, void* aData);
   PR_STATIC_CALLBACK(PLDHashOperator) GetMilestoneElements(
       TimeContainerPtrKey* aKey, void* aData);
+
   PR_STATIC_CALLBACK(PLDHashOperator) SampleTimeContainer(
       TimeContainerPtrKey* aKey, void* aData);
   PR_STATIC_CALLBACK(PLDHashOperator) SampleAnimation(
       AnimationElementPtrKey* aKey, void* aData);
   static void SampleTimedElement(nsISMILAnimationElement* aElement,
                                  TimeContainerHashtable* aActiveContainers);
   static void AddAnimationToCompositorTable(
     nsISMILAnimationElement* aElement, nsSMILCompositorTable* aCompositorTable);
--- a/content/smil/nsSMILInstanceTime.cpp
+++ b/content/smil/nsSMILInstanceTime.cpp
@@ -169,30 +169,45 @@ nsSMILInstanceTime::HandleFilteredInterv
   NS_ABORT_IF_FALSE(mBaseInterval,
       "Got call to HandleFilteredInterval on an independent instance time");
 
   mBaseInterval = nsnull;
   mFlags &= ~kMayUpdate; // Can't update without a base interval
   mCreator = nsnull;
 }
 
+PRBool
+nsSMILInstanceTime::ShouldPreserve() const
+{
+  return mFixedEndpointRefCnt > 0 || (mFlags & kWasDynamicEndpoint);
+}
+
+void
+nsSMILInstanceTime::UnmarkShouldPreserve()
+{
+  mFlags &= ~kWasDynamicEndpoint;
+}
+
 void
 nsSMILInstanceTime::AddRefFixedEndpoint()
 {
   NS_ABORT_IF_FALSE(mFixedEndpointRefCnt < PR_UINT16_MAX,
       "Fixed endpoint reference count upper limit reached");
   ++mFixedEndpointRefCnt;
   mFlags &= ~kMayUpdate; // Once fixed, always fixed
 }
 
 void
 nsSMILInstanceTime::ReleaseFixedEndpoint()
 {
   NS_ABORT_IF_FALSE(mFixedEndpointRefCnt > 0, "Duplicate release");
   --mFixedEndpointRefCnt;
+  if (mFixedEndpointRefCnt == 0 && IsDynamic()) {
+    mFlags |= kWasDynamicEndpoint;
+  }
 }
 
 PRBool
 nsSMILInstanceTime::IsDependentOn(const nsSMILInstanceTime& aOther) const
 {
   if (mVisited)
     return PR_FALSE;
 
--- a/content/smil/nsSMILInstanceTime.h
+++ b/content/smil/nsSMILInstanceTime.h
@@ -95,17 +95,19 @@ public:
   void HandleFilteredInterval();
 
   const nsSMILTimeValue& Time() const { return mTime; }
   const nsSMILTimeValueSpec* GetCreator() const { return mCreator; }
 
   PRBool IsDynamic() const { return !!(mFlags & kDynamic); }
   PRBool IsFixedTime() const { return !(mFlags & kMayUpdate); }
   PRBool FromDOM() const { return !!(mFlags & kFromDOM); }
-  PRBool IsUsedAsFixedEndpoint() const { return mFixedEndpointRefCnt > 0; }
+
+  PRBool ShouldPreserve() const;
+  void   UnmarkShouldPreserve();
 
   void AddRefFixedEndpoint();
   void ReleaseFixedEndpoint();
 
   void DependentUpdate(const nsSMILTimeValue& aNewTime)
   {
     NS_ABORT_IF_FALSE(!IsFixedTime(),
         "Updating an instance time that is not expected to be updated");
@@ -148,17 +150,22 @@ protected:
     // in the past as they may be updated to a future time.
     kMayUpdate = 2,
 
     // Indicates that this instance time was generated from the DOM as opposed
     // to an nsSMILTimeValueSpec. When a 'begin' or 'end' attribute is set or
     // reset we should clear all the instance times that have been generated by
     // that attribute (and hence an nsSMILTimeValueSpec), but not those from the
     // DOM.
-    kFromDOM = 4
+    kFromDOM = 4,
+
+    // Indicates that this instance time was used as the endpoint of an interval
+    // that has been filtered or removed. However, since it is a dynamic time it
+    // should be preserved and not filtered.
+    kWasDynamicEndpoint = 8
   };
   PRUint8       mFlags;   // Combination of kDynamic, kMayUpdate, etc.
   PRPackedBool  mVisited; // (mutable) Cycle tracking
 
   // Additional reference count to determine if this instance time is currently
   // used as a fixed endpoint in any intervals. Instance times that are used in
   // this way should not be removed when the owning nsSMILTimedElement removes
   // instance times in response to a restart or in an attempt to free up memory
--- a/content/smil/nsSMILTimeContainer.cpp
+++ b/content/smil/nsSMILTimeContainer.cpp
@@ -41,16 +41,18 @@
 
 nsSMILTimeContainer::nsSMILTimeContainer()
 :
   mParent(nsnull),
   mCurrentTime(0L),
   mParentOffset(0L),
   mPauseStart(0L),
   mNeedsPauseSample(PR_FALSE),
+  mNeedsRewind(PR_FALSE),
+  mIsSeeking(PR_FALSE),
   mPauseState(PAUSE_BEGIN)
 {
 }
 
 nsSMILTimeContainer::~nsSMILTimeContainer()
 {
   if (mParent) {
     mParent->RemoveChild(*this);
@@ -140,29 +142,40 @@ nsSMILTimeContainer::GetCurrentTime() co
     return 0L;
 
   return mCurrentTime;
 }
 
 void
 nsSMILTimeContainer::SetCurrentTime(nsSMILTime aSeekTo)
 {
+  // SVG 1.1 doesn't specify what to do for negative times so we adopt SVGT1.2's
+  // behaviour of clamping negative times to 0.
+  aSeekTo = PR_MAX(0, aSeekTo);
+
   // The following behaviour is consistent with:
   // http://www.w3.org/2003/01/REC-SVG11-20030114-errata
   //  #getCurrentTime_setCurrentTime_undefined_before_document_timeline_begin
   // which says that if SetCurrentTime is called before the document timeline
   // has begun we should still adjust the offset.
   nsSMILTime parentTime = GetParentTime();
   mParentOffset = parentTime - aSeekTo;
+  mIsSeeking = PR_TRUE;
 
   if (IsPaused()) {
     mNeedsPauseSample = PR_TRUE;
     mPauseStart = parentTime;
   }
 
+  if (aSeekTo < mCurrentTime) {
+    // Backwards seek
+    mNeedsRewind = PR_TRUE;
+    ClearMilestones();
+  }
+
   // Force an update to the current time in case we get a call to GetCurrentTime
   // before another call to Sample().
   UpdateCurrentTime();
 
   NotifyTimeChange();
 }
 
 nsSMILTime
--- a/content/smil/nsSMILTimeContainer.h
+++ b/content/smil/nsSMILTimeContainer.h
@@ -166,16 +166,31 @@ public:
    * Return if this time container should be sampled or can be skipped.
    *
    * This is most useful as an optimisation for skipping time containers that
    * don't require a sample.
    */
   PRBool NeedsSample() const { return !mPauseState || mNeedsPauseSample; }
 
   /*
+   * Indicates if the elements of this time container need to be rewound.
+   * This occurs during a backwards seek.
+   */
+  PRBool NeedsRewind() const { return mNeedsRewind; }
+  void ClearNeedsRewind() { mNeedsRewind = PR_FALSE; }
+
+  /*
+   * Indicates the time container is currently processing a SetCurrentTime
+   * request and appropriate seek behaviour should be applied by child elements
+   * (e.g. not firing time events).
+   */
+  PRBool IsSeeking() const { return mIsSeeking; }
+  void MarkSeekFinished() { mIsSeeking = PR_FALSE; }
+
+  /*
    * Sets the parent time container.
    *
    * The callee still retains ownership of the time container.
    */
   nsresult SetParent(nsSMILTimeContainer* aParent);
 
   /*
    * Registers an element for a sample at the given time.
@@ -274,16 +289,19 @@ protected:
   nsSMILTime mParentOffset;
 
   // The timestamp in parent time when the container was paused
   nsSMILTime mPauseStart;
 
   // Whether or not a pause sample is required
   PRPackedBool mNeedsPauseSample;
 
+  PRPackedBool mNeedsRewind; // Backwards seek performed
+  PRPackedBool mIsSeeking; // Currently in the middle of a seek operation
+
   // A bitfield of the pause state for all pause requests
   PRUint32 mPauseState;
 
   struct MilestoneEntry
   {
     MilestoneEntry(nsSMILMilestone aMilestone,
                    nsISMILAnimationElement& aElement)
       : mMilestone(aMilestone), mTimebase(&aElement)
--- a/content/smil/nsSMILTimedElement.cpp
+++ b/content/smil/nsSMILTimedElement.cpp
@@ -146,17 +146,18 @@ nsSMILTimedElement::nsSMILTimedElement()
   mFillMode(FILL_REMOVE),
   mRestartMode(RESTART_ALWAYS),
   mBeginSpecSet(PR_FALSE),
   mEndHasEventConditions(PR_FALSE),
   mInstanceSerialIndex(0),
   mClient(nsnull),
   mCurrentInterval(nsnull),
   mPrevRegisteredMilestone(sMaxMilestone),
-  mElementState(STATE_STARTUP)
+  mElementState(STATE_STARTUP),
+  mSeekState(SEEK_NOT_SEEKING)
 {
   mSimpleDur.SetIndefinite();
   mMin.SetMillis(0L);
   mMax.SetIndefinite();
   mTimeDependents.Init();
 }
 
 nsSMILTimedElement::~nsSMILTimedElement()
@@ -451,16 +452,26 @@ nsSMILTimedElement::DoSampleAt(nsSMILTim
   // the already active container.
   if (GetTimeContainer()->IsPausedByType(nsSMILTimeContainer::PAUSE_BEGIN))
     return;
 
   NS_ABORT_IF_FALSE(mElementState != STATE_STARTUP || aEndOnly,
       "Got a regular sample during startup state, expected an end sample"
       " instead");
 
+  PRBool finishedSeek = PR_FALSE;
+  if (GetTimeContainer()->IsSeeking() && mSeekState == SEEK_NOT_SEEKING) {
+    mSeekState = mElementState == STATE_ACTIVE ?
+                 SEEK_FORWARD_FROM_ACTIVE :
+                 SEEK_FORWARD_FROM_INACTIVE;
+  } else if (mSeekState != SEEK_NOT_SEEKING &&
+             !GetTimeContainer()->IsSeeking()) {
+    finishedSeek = PR_TRUE;
+  }
+
   PRBool          stateChanged;
   nsSMILTimeValue sampleTime(aContainerTime);
 
   do {
 #ifdef DEBUG
     // Check invariant
     if (mElementState == STATE_STARTUP || mElementState == STATE_POSTACTIVE) {
       NS_ABORT_IF_FALSE(!mCurrentInterval,
@@ -536,16 +547,18 @@ nsSMILTimedElement::DoSampleAt(nsSMILTim
           if (mElementState == STATE_WAITING) {
             mCurrentInterval = new nsSMILInterval(newInterval);
             NotifyNewInterval();
           }
           FilterHistory();
           stateChanged = PR_TRUE;
         } else {
           nsSMILTime beginTime = mCurrentInterval->Begin()->Time().GetMillis();
+          NS_ASSERTION(aContainerTime >= beginTime,
+                       "Sample time should not precede current interval");
           nsSMILTime activeTime = aContainerTime - beginTime;
           SampleSimpleTime(activeTime);
         }
       }
       break;
 
     case STATE_POSTACTIVE:
       break;
@@ -555,32 +568,78 @@ nsSMILTimedElement::DoSampleAt(nsSMILTim
   // state. However, for end samples we only drive the state machine as far as
   // the waiting or postactive state because we don't want to commit to any new
   // interval (by transitioning to the active state) until all the end samples
   // have finished and we then have complete information about the available
   // instance times upon which to base our next interval.
   } while (stateChanged && (!aEndOnly || (mElementState != STATE_WAITING &&
                                           mElementState != STATE_POSTACTIVE)));
 
+  if (finishedSeek) {
+    DoPostSeek();
+  }
   RegisterMilestone();
 }
 
 void
 nsSMILTimedElement::HandleContainerTimeChange()
 {
   // In future we could possibly introduce a separate change notice for time
   // container changes and only notify those dependents who live in other time
   // containers. For now we don't bother because when we re-resolve the time in
   // the nsSMILTimeValueSpec we'll check if anything has changed and if not, we
   // won't go any further.
   if (mElementState == STATE_WAITING || mElementState == STATE_ACTIVE) {
     NotifyChangedInterval();
   }
 }
 
+void
+nsSMILTimedElement::Rewind()
+{
+  NS_ABORT_IF_FALSE(mAnimationElement,
+      "Got rewind request before being attached to an animation element");
+  NS_ABORT_IF_FALSE(mSeekState == SEEK_NOT_SEEKING,
+      "Got rewind request whilst already seeking");
+
+  mSeekState = mElementState == STATE_ACTIVE ?
+               SEEK_BACKWARD_FROM_ACTIVE :
+               SEEK_BACKWARD_FROM_INACTIVE;
+
+  // Set the STARTUP state first so that if we get any callbacks we won't waste
+  // time recalculating the current interval
+  mElementState = STATE_STARTUP;
+
+  // Clear the intervals and instance times except those instance times we can't
+  // regenerate (DOM calls etc.)
+  RewindTiming();
+
+  UnsetBeginSpec();
+  UnsetEndSpec();
+
+  if (mClient) {
+    mClient->Inactivate(PR_FALSE);
+  }
+
+  if (mAnimationElement->HasAnimAttr(nsGkAtoms::begin)) {
+    nsAutoString attValue;
+    mAnimationElement->GetAnimAttr(nsGkAtoms::begin, attValue);
+    SetBeginSpec(attValue, &mAnimationElement->Content());
+  }
+
+  if (mAnimationElement->HasAnimAttr(nsGkAtoms::end)) {
+    nsAutoString attValue;
+    mAnimationElement->GetAnimAttr(nsGkAtoms::end, attValue);
+    SetEndSpec(attValue, &mAnimationElement->Content());
+  }
+
+  mPrevRegisteredMilestone = sMaxMilestone;
+  RegisterMilestone();
+}
+
 PRBool
 nsSMILTimedElement::SetAttr(nsIAtom* aAttribute, const nsAString& aValue,
                             nsAttrValue& aResult, nsIContent* aContextNode,
                             nsresult* aParseResult)
 {
   PRBool foundMatch = PR_TRUE;
   nsresult parseResult = NS_OK;
 
@@ -1067,16 +1126,60 @@ nsSMILTimedElement::ClearBeginOrEndSpecs
   // Remove only those instance times generated by the attribute, not those from
   // DOM calls.
   InstanceTimeList& instances = aIsBegin ? mBeginInstances : mEndInstances;
   RemoveNonDOM removeNonDOM;
   RemoveInstanceTimes(instances, removeNonDOM);
 }
 
 void
+nsSMILTimedElement::RewindTiming()
+{
+  RewindInstanceTimes(mBeginInstances);
+  RewindInstanceTimes(mEndInstances);
+
+  if (mCurrentInterval) {
+    mCurrentInterval->Unlink();
+    mCurrentInterval = nsnull;
+  }
+
+  for (PRInt32 i = mOldIntervals.Length() - 1; i >= 0; --i) {
+    mOldIntervals[i]->Unlink();
+  }
+  mOldIntervals.Clear();
+}
+
+namespace
+{
+  class RemoveNonDynamic
+  {
+  public:
+    PRBool operator()(nsSMILInstanceTime* aInstanceTime, PRUint32 /*aIndex*/)
+    {
+      // Generally dynamically-generated instance times (DOM calls, event-based
+      // times) are not associated with their creator nsSMILTimeValueSpec.
+      // If that ever changes though we'll need to make sure to disassociate
+      // them here otherwise they'll get removed when we clear the set of
+      // nsSMILTimeValueSpecs later on.
+      NS_ABORT_IF_FALSE(!aInstanceTime->IsDynamic() ||
+           !aInstanceTime->GetCreator(),
+          "Instance time retained during rewind needs to be unlinked");
+      return !aInstanceTime->IsDynamic();
+    }
+  };
+}
+
+void
+nsSMILTimedElement::RewindInstanceTimes(InstanceTimeList& aList)
+{
+  RemoveNonDynamic removeNonDynamic;
+  RemoveInstanceTimes(aList, removeNonDynamic);
+}
+
+void
 nsSMILTimedElement::ApplyEarlyEnd(const nsSMILTimeValue& aSampleTime)
 {
   // This should only be called within DoSampleAt as a helper function
   NS_ABORT_IF_FALSE(mElementState == STATE_ACTIVE,
       "Unexpected state to try to apply an early end");
 
   // Only apply an early end if we're not already ending.
   if (mCurrentInterval->End()->Time() > aSampleTime) {
@@ -1108,17 +1211,17 @@ namespace
     {
       // SMIL 3.0 section 5.4.3, 'Resetting element state':
       //   Any instance times associated with past Event-values, Repeat-values,
       //   Accesskey-values or added via DOM method calls are removed from the
       //   dependent begin and end instance times lists. In effect, all events
       //   and DOM methods calls in the past are cleared. This does not apply to
       //   an instance time that defines the begin of the current interval.
       return aInstanceTime->IsDynamic() &&
-             !aInstanceTime->IsUsedAsFixedEndpoint() &&
+             !aInstanceTime->ShouldPreserve() &&
              (!mCurrentIntervalBegin || aInstanceTime != mCurrentIntervalBegin);
     }
 
   private:
     const nsSMILInstanceTime* mCurrentIntervalBegin;
   };
 }
 
@@ -1128,16 +1231,67 @@ nsSMILTimedElement::Reset()
   RemoveReset resetBegin(mCurrentInterval ? mCurrentInterval->Begin() : nsnull);
   RemoveInstanceTimes(mBeginInstances, resetBegin);
 
   RemoveReset resetEnd(nsnull);
   RemoveInstanceTimes(mEndInstances, resetEnd);
 }
 
 void
+nsSMILTimedElement::DoPostSeek()
+{
+  // XXX When implementing TimeEvents we'll need to compare mElementState with
+  // mSeekState and dispatch events as follows:
+  //     ACTIVE->INACTIVE: End event
+  //     INACTIVE->ACTIVE: Begin event
+  //     ACTIVE->ACTIVE: Nothing (even if they're different intervals)
+  //     INACTIVE->INACTIVE: Nothing (even if we've skipped intervals)
+
+  // Finish backwards seek
+  if (mSeekState == SEEK_BACKWARD_FROM_INACTIVE ||
+      mSeekState == SEEK_BACKWARD_FROM_ACTIVE) {
+    // Previously some dynamic instance times may have been marked to be
+    // preserved because they were endpoints of an historic interval (which may
+    // or may not have been filtered). Now that we've finished a seek we should
+    // clear that flag for those instance times whose intervals are no longer
+    // historic.
+    UnpreserveInstanceTimes(mBeginInstances);
+    UnpreserveInstanceTimes(mEndInstances);
+
+    // Now that the times have been unmarked perform a reset. This might seem
+    // counter-intuitive when we're only doing a seek within an interval but
+    // SMIL seems to require this. SMIL 3.0, 'Hyperlinks and timing':
+    //   Resolved end times associated with events, Repeat-values,
+    //   Accesskey-values or added via DOM method calls are cleared when seeking
+    //   to time earlier than the resolved end time.
+    Reset();
+    UpdateCurrentInterval();
+  }
+
+  mSeekState = SEEK_NOT_SEEKING;
+}
+
+void
+nsSMILTimedElement::UnpreserveInstanceTimes(InstanceTimeList& aList)
+{
+  const nsSMILInterval* prevInterval = GetPreviousInterval();
+  const nsSMILInstanceTime* cutoff = mCurrentInterval ?
+      mCurrentInterval->Begin() :
+      prevInterval ? prevInterval->Begin() : nsnull;
+  InstanceTimeComparator cmp;
+  PRUint32 count = aList.Length();
+  for (PRUint32 i = 0; i < count; ++i) {
+    nsSMILInstanceTime* instance = aList[i].get();
+    if (!cutoff || cmp.LessThan(cutoff, instance)) {
+      instance->UnmarkShouldPreserve();
+    }
+  }
+}
+
+void
 nsSMILTimedElement::FilterHistory()
 {
   // We should filter the intervals first, since instance times still used in an
   // interval won't be filtered.
   FilterIntervals();
   FilterInstanceTimes(mBeginInstances);
   FilterInstanceTimes(mEndInstances);
 }
@@ -1191,17 +1345,17 @@ namespace
     {
       // We can filter instance times that:
       // a) Precede the end point of the previous interval; AND
       // b) Are NOT syncbase times that might be updated to a time after the end
       //    point of the previous interval; AND
       // c) Are NOT fixed end points in any remaining interval.
       return aInstanceTime->Time() < mCutoff &&
              aInstanceTime->IsFixedTime() &&
-             !aInstanceTime->IsUsedAsFixedEndpoint();
+             !aInstanceTime->ShouldPreserve();
     }
 
   private:
     nsSMILTimeValue mCutoff;
   };
 
   class RemoveBelowThreshold
   {
@@ -1495,16 +1649,19 @@ nsSMILTimedElement::ApplyMinAndMax(const
 nsSMILTime
 nsSMILTimedElement::ActiveTimeToSimpleTime(nsSMILTime aActiveTime,
                                            PRUint32& aRepeatIteration)
 {
   nsSMILTime result;
 
   NS_ASSERTION(mSimpleDur.IsResolved() || mSimpleDur.IsIndefinite(),
       "Unresolved simple duration in ActiveTimeToSimpleTime");
+  NS_ASSERTION(aActiveTime >= 0, "Expecting non-negative active time");
+  // Note that a negative aActiveTime will give us a negative value for
+  // aRepeatIteration, which is bad because aRepeatIteration is unsigned
 
   if (mSimpleDur.IsIndefinite() || mSimpleDur.GetMillis() == 0L) {
     aRepeatIteration = 0;
     result = aActiveTime;
   } else {
     result = aActiveTime % mSimpleDur.GetMillis();
     aRepeatIteration = (PRUint32)(aActiveTime / mSimpleDur.GetMillis());
   }
--- a/content/smil/nsSMILTimedElement.h
+++ b/content/smil/nsSMILTimedElement.h
@@ -224,16 +224,26 @@ public:
    * Informs the timed element that its time container has changed time
    * relative to document time. The timed element therefore needs to update its
    * dependent elements (which may belong to a different time container) so they
    * can re-resolve their times.
    */
   void HandleContainerTimeChange();
 
   /**
+   * Resets this timed element's accumulated times and intervals back to start
+   * up state.
+   *
+   * This is used for backwards seeking where rather than accumulating
+   * historical timing state and winding it back, we reset the element and seek
+   * forwards.
+   */
+  void Rewind();
+
+  /**
    * Attempts to set an attribute on this timed element.
    *
    * @param aAttribute  The name of the attribute to set. The namespace of this
    *                    attribute is not specified as it is checked by the host
    *                    element. Only attributes in the namespace defined for
    *                    SMIL attributes in the host language are passed to the
    *                    timed element.
    * @param aValue      The attribute value.
@@ -368,16 +378,18 @@ protected:
   void              UnsetRepeatCount();
   void              UnsetRepeatDur();
   void              UnsetFillMode();
 
   nsresult          SetBeginOrEndSpec(const nsAString& aSpec,
                                       nsIContent* aContextNode,
                                       PRBool aIsBegin);
   void              ClearBeginOrEndSpecs(PRBool aIsBegin);
+  void              RewindTiming();
+  void              RewindInstanceTimes(InstanceTimeList& aList);
   void              DoSampleAt(nsSMILTime aContainerTime, PRBool aEndOnly);
 
   /**
    * Helper function to check for an early end and, if necessary, update the
    * current interval accordingly.
    *
    * See SMIL 3.0, section 5.4.5, Element life cycle, "Active Time - Playing an
    * interval" for a description of ending early.
@@ -392,16 +404,30 @@ protected:
   /**
    * Clears certain state in response to the element restarting.
    *
    * This state is described in SMIL 3.0, section 5.4.3, Resetting element state
    */
   void Reset();
 
   /**
+   * Completes a seek operation by sending appropriate events and, in the case
+   * of a backwards seek, updating the state of timing information that was
+   * previously considered historical.
+   */
+  void DoPostSeek();
+
+  /**
+   * Unmarks instance times that were previously preserved because they were
+   * considered important historical milestones but are no longer such because
+   * a backwards seek has been performed.
+   */
+  void UnpreserveInstanceTimes(InstanceTimeList& aList);
+
+  /**
    * Helper function to iterate through this element's accumulated timing
    * information (specifically old nsSMILIntervals and nsSMILTimeInstanceTimes)
    * and discard items that are no longer needed or exceed some threshold of
    * accumulated state.
    */
   void FilterHistory();
 
   // Helper functions for FilterHistory to clear old nsSMILIntervals and
@@ -533,11 +559,21 @@ protected:
   enum nsSMILElementState
   {
     STATE_STARTUP,
     STATE_WAITING,
     STATE_ACTIVE,
     STATE_POSTACTIVE
   };
   nsSMILElementState              mElementState;
+
+  enum nsSMILSeekState
+  {
+    SEEK_NOT_SEEKING,
+    SEEK_FORWARD_FROM_ACTIVE,
+    SEEK_FORWARD_FROM_INACTIVE,
+    SEEK_BACKWARD_FROM_ACTIVE,
+    SEEK_BACKWARD_FROM_INACTIVE
+  };
+  nsSMILSeekState                 mSeekState;
 };
 
 #endif // NS_SMILTIMEDELEMENT_H_
--- a/content/smil/test/Makefile.in
+++ b/content/smil/test/Makefile.in
@@ -52,16 +52,17 @@ include $(topsrcdir)/config/rules.mk
 	  db_smilCSSPropertyList.js \
 	  db_smilMappedAttrList.js \
 	  smilAnimateMotionValueLists.js \
 	  smilTestUtils.js \
 	  smilXHR_helper.svg \
 	  test_smilAnimateMotion.xhtml \
 	  test_smilAnimateMotionInvalidValues.xhtml \
 	  test_smilAnimateMotionOverrideRules.xhtml \
+	  test_smilBackwardsSeeking.xhtml \
 	  test_smilChangeAfterFrozen.xhtml \
 	  test_smilContainerBinding.xhtml \
 	  test_smilCrossContainer.xhtml \
 	  test_smilCSSFontStretchRelative.xhtml \
 	  test_smilCSSFromBy.xhtml \
 	  test_smilCSSFromTo.xhtml \
 	  test_smilCSSInherit.xhtml \
 	  test_smilCSSInvalidValues.xhtml \
new file mode 100644
--- /dev/null
+++ b/content/smil/test/test_smilBackwardsSeeking.xhtml
@@ -0,0 +1,191 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<head>
+  <title>Test for backwards seeking behavior </title>
+  <script type="text/javascript" src="/MochiKit/packed.js"></script>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+</head>
+<body>
+<p id="display"></p>
+<div id="content" style="display: none">
+<svg id="svg" xmlns="http://www.w3.org/2000/svg" />
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+<![CDATA[
+/** Test for backwards seeking behavior  **/
+
+var gSvg = document.getElementById("svg");
+
+SimpleTest.waitForExplicitFinish();
+
+function main()
+{
+  // Pause our document, so that the setCurrentTime calls are the only
+  // thing affecting document time
+  gSvg.pauseAnimations();
+
+  // We define a series of scenarios, sample times, and expected return values
+  // from getStartTime.
+  //
+  // Each scenario is basically a variation on the following arrangement:
+  //
+  // <svg>
+  //   <set ... dur="1s" begin="<A-BEGIN>"/>
+  //   <set ... dur="1s" begin="<B-BEGIN>"/>
+  // </svg>
+  //
+  // Each test then consists of the following:
+  //    animA: attributes to be applied to a
+  //    animB: attributes to be applied to b
+  //    times: a series of triples which consist of:
+  //             <sample time, a's expected start time, b's expected start time>
+  //           * The sample time is the time passed to setCurrentTime and so is
+  //             in seconds.
+  //           * The expected start times are compared with the return value of
+  //             getStartTime. To check for an unresolved start time where
+  //             getStartTime would normally throw an exception use
+  //             'unresolved'.
+  //           * We also allow the special notation to indicate a call to
+  //             beginElement
+  //             <'beginElementAt', id of animation element, offset>
+  //
+  // In the diagrams below '^' means the time before the seek and '*' is the
+  // seek time.
+  var testCases = Array();
+
+  // 0: Simple case
+  //
+  //   A:     +-------
+  //   B:     +-------       begin: a.begin
+  //        *            ^
+  testCases[0] = {
+    'animA': {'begin':'1s', 'id':'a'},
+    'animB': {'begin':'a.begin'},
+    'times': [ [0, 1, 1],
+               [1, 1, 1],
+               [2, 'unresolved', 'unresolved'],
+               [0, 1, 1],
+               [1.5, 1, 1],
+               [1, 1, 1],
+               [2, 'unresolved', 'unresolved'] ]
+  };
+
+  // 1: Restored times should be live
+  //
+  // When we restore times they should be live. So we have the following
+  // scenario.
+  //
+  //   A:     +-------
+  //   B:     +-------       begin: a.begin
+  //       *            ^
+  //
+  // Then we call beginElement at an earlier time which should give us the
+  // following.
+  //
+  //   A:   +-------
+  //   B:   +-------
+  //       *            ^
+  //
+  //  If the times are not live however we'll end up with this
+  //
+  //   A:   +-------
+  //   B:   +-+-------
+  //       *            ^
+  testCases[1] = {
+    'animA': {'begin':'1s', 'id':'a', 'restart':'whenNotActive'},
+    'animB': {'begin':'a.begin', 'restart':'always'},
+    'times': [ [0, 1, 1],
+               [2, 'unresolved', 'unresolved'],
+               [0.25, 1, 1],
+               ['beginElementAt', 'a', 0.25], // = start time of 0.5
+               [0.25, 0.5, 0.5],
+               [1, 0.5, 0.5],
+               [1.5, 'unresolved', 'unresolved'] ]
+  };
+
+  // 2: Multiple intervals A
+  //
+  //   A:  +-  +-
+  //   B:          +-  +-   begin: a.begin+4s
+  //             *    ^
+  testCases[2] = {
+    'animA': {'begin':'1s; 3s', 'id':'a'},
+    'animB': {'begin':'a.begin+4s'},
+    'times': [ [0, 1, 5],
+               [3, 3, 5],
+               [6.5, 'unresolved', 7],
+               [4, 'unresolved', 5],
+               [6, 'unresolved', 7],
+               [2, 3, 5],
+               ['beginElementAt', 'a', 0],
+               [2, 2, 5],
+               [5, 'unresolved', 5],
+               [6, 'unresolved', 6],
+               [7, 'unresolved', 7],
+               [8, 'unresolved', 'unresolved'] ]
+  };
+
+  for (var i = 0; i < testCases.length; i++) {
+    gSvg.setCurrentTime(0);
+    var test = testCases[i];
+
+    // Create animation elements
+    var animA = createAnim(test.animA);
+    var animB = createAnim(test.animB);
+
+    // Run samples
+    for (var j = 0; j < test.times.length; j++) {
+      var times = test.times[j];
+      if (times[0] == 'beginElementAt') {
+        var anim = getElement(times[1]);
+        anim.beginElementAt(times[2]);
+      } else {
+        gSvg.setCurrentTime(times[0]);
+        checkStartTime(animA, times[1], times[0], i, 'a');
+        checkStartTime(animB, times[2], times[0], i, 'b');
+      }
+    }
+
+    // Tidy up
+    removeElement(animA);
+    removeElement(animB);
+  }
+
+  SimpleTest.finish();
+}
+
+function createAnim(attr)
+{
+  const svgns = "http://www.w3.org/2000/svg";
+  var anim = document.createElementNS(svgns, 'set');
+  anim.setAttribute('attributeName','x');
+  anim.setAttribute('to','10');
+  anim.setAttribute('dur','1s');
+  for (name in attr) {
+    anim.setAttribute(name, attr[name]);
+  }
+  return gSvg.appendChild(anim);
+}
+
+function checkStartTime(anim, expectedStartTime, sampleTime, caseNum, id)
+{
+  var startTime = 'unresolved';
+  try {
+    startTime = anim.getStartTime();
+  } catch (e) {
+    if (e.code != DOMException.INVALID_STATE_ERR)
+      throw e;
+  }
+
+  var msg = "Test case " + caseNum + ", t=" + sampleTime + " animation '" +
+    id + "': Unexpected getStartTime:";
+  is(startTime, expectedStartTime, msg);
+}
+
+window.addEventListener("load", main, false);
+]]>
+</script>
+</pre>
+</body>
+</html>
--- a/content/smil/test/test_smilSetCurrentTime.xhtml
+++ b/content/smil/test/test_smilSetCurrentTime.xhtml
@@ -26,17 +26,18 @@ SimpleTest.waitForExplicitFinish();
 
 function main() {
   ok(gSvg.animationsPaused(), "should be paused by <svg> load handler");
   is(gSvg.getCurrentTime(), 0, "should be paused at 0 in <svg> load handler");
 
   // Test that seeking takes effect immediately
   for (var i = 0; i < gTimes.length; i++) {
     gSvg.setCurrentTime(gTimes[i]);
-    assertFloatsEqual(gSvg.getCurrentTime(), gTimes[i]);
+    // We adopt the SVGT1.2 behavior of clamping negative times to 0
+    assertFloatsEqual(gSvg.getCurrentTime(), Math.max(gTimes[i], 0.0));
   }
 
   // Test that seeking isn't messed up by timeouts
   // (using tail recursion to set up the chain of timeout function calls)
   var func = function() {
     checkTimesAfterIndex(0);
   }
   setTimeout(func, gWaitTime);
@@ -51,17 +52,17 @@ function checkTimesAfterIndex(index) {
   if (index == gTimes.length) {
     // base case -- we're done!
     SimpleTest.finish();
     return;
   }
 
   gSvg.setCurrentTime(gTimes[index]);
   var func = function() {
-    assertFloatsEqual(gSvg.getCurrentTime(), gTimes[index]);
+    assertFloatsEqual(gSvg.getCurrentTime(), Math.max(gTimes[index], 0.0));
     checkTimesAfterIndex(index + 1);
   }
   setTimeout(func, gWaitTime);
 }
 
 function assertFloatsEqual(aVal, aExpected) {
   ok(Math.abs(aVal - aExpected) <= PRECISION_LEVEL,
      "getCurrentTime returned " + aVal + " after seeking to " + aExpected)
--- a/layout/reftests/svg/smil/reftest.list
+++ b/layout/reftests/svg/smil/reftest.list
@@ -42,16 +42,19 @@ include repeat/reftest.list
 include restart/reftest.list
 
 # pause tests
 include pause/reftest.list
 
 # syncbase tests
 include syncbase/reftest.list
 
+# seek tests
+include seek/reftest.list
+
 # General tests
 == anim-discrete-values-1.svg      anim-standard-ref.svg
 == anim-discrete-replace-sum-1.svg anim-standard-ref.svg
 == anim-discrete-sum-none-1.svg    anim-standard-ref.svg
 == anim-discrete-sum-sum-1.svg     anim-standard-ref.svg
 
 == anim-discrete-to-1.svg          anim-standard-ref.svg
 == anim-discrete-to-2.svg          anim-standard-ref.svg
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/smil/seek/anim-standard-ref.svg
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+  <rect x="15" y="15" width="200" height="200" fill="blue"/>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/smil/seek/anim-standard-ref.xhtml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<html xmlns="http://www.w3.org/1999/xhtml">
+<body>
+<svg xmlns="http://www.w3.org/2000/svg" width="230px" height="230px">
+  <rect x="15" y="15" width="200" height="200" fill="blue"/>
+</svg>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/smil/seek/anim-x-seek-1a.svg
@@ -0,0 +1,18 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+     class="reftest-wait"
+     onload="go()">
+  <script type="text/javascript">
+  function go() {
+    // Pause, seek to middle of animation, and then seek to exactly 1 duration
+    // before the animation begins (to make sure animation effects are cleared)
+    var svg = document.documentElement;
+    svg.pauseAnimations();
+    svg.setCurrentTime(3);
+    svg.setCurrentTime(0);
+    svg.removeAttribute("class");
+  }
+  </script>
+  <rect x="15" y="15" width="200" height="200" fill="blue">
+    <set attributeName="x" to="100" begin="2s" dur="2s"/>
+  </rect>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/smil/seek/anim-x-seek-1b.svg
@@ -0,0 +1,18 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+     class="reftest-wait"
+     onload="go()">
+  <script type="text/javascript">
+  function go() {
+    // Pause, seek to middle of animation, and then seek to exactly 1 duration
+    // before the animation begins (to make sure animation effects are cleared)
+    var svg = document.documentElement;
+    svg.pauseAnimations();
+    svg.setCurrentTime(3);
+    svg.setCurrentTime(0);
+    svg.removeAttribute("class");
+  }
+  </script>
+  <rect x="15" y="15" width="200" height="200" fill="blue">
+    <set attributeName="x" to="100" begin="2s" dur="2s"/>
+  </rect>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/smil/seek/anim-x-seek-1c.svg
@@ -0,0 +1,18 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+     class="reftest-wait"
+     onload="go()">
+  <script type="text/javascript">
+  function go() {
+    // Pause, seek to middle of animation, and then seek to the exact ending
+    // of the animation (to make sure animation effects are cleared)
+    var svg = document.documentElement;
+    svg.pauseAnimations();
+    svg.setCurrentTime(3);
+    svg.setCurrentTime(4);
+    svg.removeAttribute("class");
+  }
+  </script>
+  <rect x="15" y="15" width="200" height="200" fill="blue">
+    <set attributeName="x" to="100" begin="2s" dur="2s"/>
+  </rect>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/smil/seek/anim-x-seek-1d.svg
@@ -0,0 +1,18 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+     class="reftest-wait"
+     onload="go()">
+  <script type="text/javascript">
+  function go() {
+    // Pause, seek to middle of animation, and then seek to a time after
+    // the animation is over (to make sure animation effects are cleared)
+    var svg = document.documentElement;
+    svg.pauseAnimations();
+    svg.setCurrentTime(3);
+    svg.setCurrentTime(5);
+    svg.removeAttribute("class");
+  }
+  </script>
+  <rect x="15" y="15" width="200" height="200" fill="blue">
+    <set attributeName="x" to="100" begin="2s" dur="2s"/>
+  </rect>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/smil/seek/anim-x-seek-1e.svg
@@ -0,0 +1,17 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+     class="reftest-wait"
+     onload="go()">
+  <script type="text/javascript">
+  function go() {
+    // Test a backwards seek covering many intervals
+    var svg = document.documentElement;
+    svg.pauseAnimations();
+    svg.setCurrentTime(15);
+    svg.setCurrentTime(3.5);
+    svg.removeAttribute("class");
+  }
+  </script>
+  <rect x="100" y="15" width="200" height="200" fill="blue">
+    <set attributeName="x" to="15" begin="1s; 3s; 5s; 7s; 9s; 11s" dur="1s"/>
+  </rect>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/smil/seek/anim-x-seek-cross-container-1a.xhtml
@@ -0,0 +1,35 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+<html xmlns="http://www.w3.org/1999/xhtml" class="reftest-wait">
+<!--
+  Test backwards seeking with cross-time container dependencies.
+ -->
+<head>
+<script>
+function snapshot()
+{
+  var a = document.getElementById("svg-a");
+  var b = document.getElementById("svg-b");
+  a.pauseAnimations();
+  b.pauseAnimations();
+  a.setCurrentTime(0);
+  b.setCurrentTime(2); // 'b' has now begun and 'a' has a begin time of '-1s' in
+                       // a's container time
+  b.setCurrentTime(1); // Perform a backwards seek--'a' should now have a begin
+                       // time of '0s' in container time
+  a.setCurrentTime(1); // So seeking forward 1s should get us to the middle of
+                       // the interval
+  document.documentElement.removeAttribute("class");
+}
+</script>
+</head>
+<body onload="snapshot()">
+<svg xmlns="http://www.w3.org/2000/svg" width="230px" height="230px" id="svg-a">
+  <rect x="100" y="15" width="200" height="200" fill="blue">
+    <animate attributeName="x" from="0" to="30" begin="b.begin" dur="2s"/>
+  </rect>
+</svg>
+<svg xmlns="http://www.w3.org/2000/svg" width="230px" height="230px" id="svg-b">
+  <set attributeName="y" to="0" begin="1s" id="b"/>
+</svg>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/smil/seek/anim-x-seek-dynamic-1a.svg
@@ -0,0 +1,20 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+     class="reftest-wait"
+     onload="go()">
+  <script type="text/javascript">
+  function go() {
+    // Perform a backwards seek within an interval whose begin point is
+    // generated by a dynamic event (to make sure such times are preserved when
+    // we rewind).
+    var svg = document.documentElement;
+    svg.pauseAnimations();
+    document.getElementById('a').beginElementAt(2);
+    svg.setCurrentTime(3);
+    svg.setCurrentTime(2.5);
+    svg.removeAttribute("class");
+  }
+  </script>
+  <rect x="100" y="15" width="200" height="200" fill="blue">
+    <set attributeName="x" to="15" begin="indefinite" dur="2s" id="a"/>
+  </rect>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/smil/seek/anim-x-seek-dynamic-1b.svg
@@ -0,0 +1,19 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+     class="reftest-wait"
+     onload="go()">
+  <script type="text/javascript">
+  function go() {
+    // Check that dynamic end times are preserved during a backwards seek
+    var svg = document.documentElement;
+    svg.pauseAnimations();
+    svg.setCurrentTime(3);
+    document.getElementById('a').endElement();
+    svg.setCurrentTime(4);
+    svg.setCurrentTime(3);
+    svg.removeAttribute("class");
+  }
+  </script>
+  <rect x="15" y="15" width="200" height="200" fill="blue">
+    <set attributeName="x" to="100" begin="2s" dur="2s" id="a"/>
+  </rect>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/smil/seek/anim-x-seek-dynamic-1c.svg
@@ -0,0 +1,31 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+     class="reftest-wait"
+     onload="go()">
+  <script type="text/javascript">
+  function go() {
+    // Check that dynamic times are preserved even from filtering
+    var svg = document.documentElement;
+    var a = document.getElementById('a');
+    svg.pauseAnimations();
+    // Generate a series of intervals with dynamic begin points
+    svg.setCurrentTime(1);
+    a.beginElement();
+    svg.setCurrentTime(3);
+    a.beginElement();
+    svg.setCurrentTime(5);
+    a.beginElement();
+    svg.setCurrentTime(7);
+    a.beginElement();
+    svg.setCurrentTime(9);
+    a.beginElement();
+    svg.setCurrentTime(11);
+    // By now, the first interval will have been filtered but test that we have
+    // preserved the instance time in order to reconstruct it
+    svg.setCurrentTime(1.5);
+    svg.removeAttribute("class");
+  }
+  </script>
+  <rect x="100" y="15" width="200" height="200" fill="blue">
+    <set attributeName="x" to="15" begin="indefinite" dur="1s" id="a"/>
+  </rect>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/smil/seek/anim-x-seek-dynamic-1d.svg
@@ -0,0 +1,30 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+     class="reftest-wait"
+     onload="go()">
+  <script type="text/javascript">
+  function go() {
+    // Check that whilst processing past intervals reset does not clear dynamic
+    // instance times of future intervals
+    var svg = document.documentElement;
+    var a = document.getElementById('a');
+    svg.pauseAnimations();
+    // Generate a series of intervals with dynamic begin points
+    svg.setCurrentTime(1);
+    a.beginElement();
+    svg.setCurrentTime(3);
+    a.beginElement();
+    svg.setCurrentTime(5);
+    a.beginElement();
+    svg.setCurrentTime(7);
+    a.beginElement();
+    svg.setCurrentTime(5.5); // Backwards seek will cause us to rebuild the
+                             // world but in the process we should not clear the
+                             // dynamic instance times for future intervals on
+                             // each restart
+    svg.removeAttribute("class");
+  }
+  </script>
+  <rect x="100" y="15" width="200" height="200" fill="blue">
+    <set attributeName="x" to="15" begin="indefinite" dur="1s" id="a"/>
+  </rect>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/smil/seek/anim-x-seek-dynamic-1e.svg
@@ -0,0 +1,23 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+     class="reftest-wait"
+     onload="go()">
+  <script type="text/javascript">
+  function go() {
+    // Check that dynamic times are reset during a forwards seek
+    var svg = document.documentElement;
+    var a = document.getElementById('a');
+    svg.pauseAnimations();
+    svg.setCurrentTime(1); // First interval 1s-2s
+    a.beginElement();
+    svg.setCurrentTime(2);
+    a.beginElementAt(1); // Add instance time t=3s
+    a.beginElementAt(3); // Add instance time t=5s -- should be cleared during
+                         // the seek when we process the restart at t=3s
+    svg.setCurrentTime(5); // Hence at t=5s we should be inactive
+    svg.removeAttribute("class");
+  }
+  </script>
+  <rect x="15" y="15" width="200" height="200" fill="blue">
+    <set attributeName="x" to="100" begin="indefinite" dur="1s" id="a"/>
+  </rect>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/smil/seek/anim-x-seek-dynamic-1f.svg
@@ -0,0 +1,30 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+     class="reftest-wait"
+     onload="go()">
+  <script type="text/javascript">
+  function go() {
+    // Check that dynamic times are reset during a backwards seek
+    var svg = document.documentElement;
+    var a = document.getElementById('a');
+    svg.pauseAnimations();
+    svg.setCurrentTime(1);
+    a.beginElement();    // First interval: 1s-2s
+    a.endElementAt(1);
+    a.beginElementAt(2); // Second interval: 3s-indef
+    svg.setCurrentTime(5);
+    a.endElementAt(1);   // Add end time at 6s
+    svg.setCurrentTime(4); // Perform backwards seek from 5s->4s
+    svg.setCurrentTime(6); // End time at 6s should have been cleared
+                           // and we should still be active
+                           // (See SMIL 3.0, 'Hyperlinks and timing' which has:
+                           //   Resolved end times associated with events,
+                           //   Repeat-values, Accesskey-values or added via DOM
+                           //   method calls are cleared when seeking to time
+                           //   earlier than the resolved end time.
+    svg.removeAttribute("class");
+  }
+  </script>
+  <rect x="100" y="15" width="200" height="200" fill="blue">
+    <set attributeName="x" to="15" begin="indefinite" dur="indefinite" id="a"/>
+  </rect>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/smil/seek/anim-x-seek-dynamic-1g.svg
@@ -0,0 +1,23 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+     class="reftest-wait"
+     onload="go()">
+  <script type="text/javascript">
+  function go() {
+    // Check that dynamic interval end times are reset during a backwards seek
+    var svg = document.documentElement;
+    var a = document.getElementById('a');
+    svg.pauseAnimations();
+    svg.setCurrentTime(1);
+    a.beginElement(); // First interval 1s-2s
+    a.endElementAt(1);
+    svg.setCurrentTime(3);
+    svg.setCurrentTime(1.5); // Backwards seek to 1.5s -- instance time at t=2s
+                             // should be reset
+    svg.setCurrentTime(3);   // Should still be active
+    svg.removeAttribute("class");
+  }
+  </script>
+  <rect x="100" y="15" width="200" height="200" fill="blue">
+    <set attributeName="x" to="15" begin="indefinite" dur="indefinite" id="a"/>
+  </rect>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/smil/seek/anim-x-seek-dynamic-1h.svg
@@ -0,0 +1,21 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+     class="reftest-wait"
+     onload="go()">
+  <script type="text/javascript">
+  function go() {
+    // Check that dynamic times are not prematurely reset during a forwards seek
+    var svg = document.documentElement;
+    var a = document.getElementById('a');
+    svg.pauseAnimations();
+    svg.setCurrentTime(1);
+    a.beginElement();      // Interval: 1s-3s
+    a.endElementAt(2);
+    svg.setCurrentTime(2); // Should not cause end time at t=3s to be reset
+    svg.setCurrentTime(3); // Should no longer be active
+    svg.removeAttribute("class");
+  }
+  </script>
+  <rect x="15" y="15" width="200" height="200" fill="blue">
+    <set attributeName="x" to="100" begin="indefinite" dur="indefinite" id="a"/>
+  </rect>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/smil/seek/anim-x-seek-negative-1a.svg
@@ -0,0 +1,16 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+     class="reftest-wait"
+     onload="go()">
+  <script type="text/javascript">
+  function go() {
+    // A negative seek should be clamped to 0
+    var svg = document.documentElement;
+    svg.pauseAnimations();
+    svg.setCurrentTime(-1);
+    svg.removeAttribute("class");
+  }
+  </script>
+  <rect x="100" y="15" width="200" height="200" fill="blue">
+    <animate attributeName="x" from="0" to="30" begin="-1s" dur="2s"/>
+  </rect>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/smil/seek/reftest.list
@@ -0,0 +1,16 @@
+# Tests for seeking behaviour
+== anim-x-seek-1a.svg anim-standard-ref.svg
+== anim-x-seek-1b.svg anim-standard-ref.svg
+== anim-x-seek-1c.svg anim-standard-ref.svg
+== anim-x-seek-1d.svg anim-standard-ref.svg
+== anim-x-seek-1e.svg anim-standard-ref.svg
+== anim-x-seek-dynamic-1a.svg anim-standard-ref.svg
+== anim-x-seek-dynamic-1b.svg anim-standard-ref.svg
+== anim-x-seek-dynamic-1c.svg anim-standard-ref.svg
+== anim-x-seek-dynamic-1d.svg anim-standard-ref.svg
+== anim-x-seek-dynamic-1e.svg anim-standard-ref.svg
+== anim-x-seek-dynamic-1f.svg anim-standard-ref.svg
+== anim-x-seek-dynamic-1g.svg anim-standard-ref.svg
+== anim-x-seek-dynamic-1h.svg anim-standard-ref.svg
+== anim-x-seek-negative-1a.svg anim-standard-ref.svg
+== anim-x-seek-cross-container-1a.xhtml anim-standard-ref.xhtml