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 id14266
push userbbirtles@mozilla.com
push dateSat, 03 Jul 2010 05:53:29 +0000
treeherdermozilla-central@2e0a75bcb159 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdholbert, roc
bugs492458
milestone2.0b2pre
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
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