Bug 485157: SMIL event timing, part 3 event registration and timing, r=smaug, dholbert; sr=roc, a=roc
authorBrian Birtles <birtles@gmail.com>
Wed, 18 Aug 2010 19:20:24 +0900
changeset 50804 26e34fda3938
parent 50803 f23bab9fa177
child 50805 31a2747a096b
push id15162
push userbbirtles@mozilla.com
push date2010-08-18 10:24 +0000
treeherdermozilla-central@ca457b5758e0 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerssmaug, dholbert, roc, roc
bugs485157
milestone2.0b5pre
Bug 485157: SMIL event timing, part 3 event registration and timing, r=smaug, dholbert; sr=roc, a=roc
content/smil/nsSMILTimeValueSpec.cpp
content/smil/nsSMILTimeValueSpec.h
content/smil/nsSMILTimedElement.cpp
content/smil/nsSMILTimedElement.h
layout/reftests/svg/smil/event/event-begin-1.svg
layout/reftests/svg/smil/event/event-begin-load-1.svg
layout/reftests/svg/smil/event/event-begin-offset-1.svg
layout/reftests/svg/smil/event/event-begin-offset-2.svg
layout/reftests/svg/smil/event/event-begin-timeevent-1.svg
layout/reftests/svg/smil/event/event-begin-timeevent-2.svg
layout/reftests/svg/smil/event/event-begin-timeevent-3.svg
layout/reftests/svg/smil/event/event-bubble-1.svg
layout/reftests/svg/smil/event/event-custom-1.svg
layout/reftests/svg/smil/event/event-end-1.svg
layout/reftests/svg/smil/event/event-end-2.svg
layout/reftests/svg/smil/event/event-end-open-1.svg
layout/reftests/svg/smil/event/event-preventDefault-1.svg
layout/reftests/svg/smil/event/event-seek-1.svg
layout/reftests/svg/smil/event/event-util.js
layout/reftests/svg/smil/event/green-box-ref.svg
layout/reftests/svg/smil/event/reftest.list
layout/reftests/svg/smil/reftest.list
--- a/content/smil/nsSMILTimeValueSpec.cpp
+++ b/content/smil/nsSMILTimeValueSpec.cpp
@@ -39,42 +39,63 @@
 #include "nsSMILInterval.h"
 #include "nsSMILTimeContainer.h"
 #include "nsSMILTimeValue.h"
 #include "nsSMILTimedElement.h"
 #include "nsSMILInstanceTime.h"
 #include "nsSMILParserUtils.h"
 #include "nsISMILAnimationElement.h"
 #include "nsContentUtils.h"
+#include "nsIEventListenerManager.h"
+#include "nsIDOMEventGroup.h"
+#include "nsGUIEvent.h"
 #include "nsString.h"
 
 //----------------------------------------------------------------------
+// Nested class: EventListener
+
+NS_IMPL_ISUPPORTS1(nsSMILTimeValueSpec::EventListener, nsIDOMEventListener)
+
+NS_IMETHODIMP
+nsSMILTimeValueSpec::EventListener::HandleEvent(nsIDOMEvent* aEvent)
+{
+  if (mSpec) {
+    mSpec->HandleEvent(aEvent);
+  }
+  return NS_OK;
+}
+
+//----------------------------------------------------------------------
 // Implementation
 
 #ifdef _MSC_VER
 // Disable "warning C4355: 'this' : used in base member initializer list".
-// We can ignore that warning because we know that mTimebase's constructor
-// doesn't dereference the pointer passed to it.
+// We can ignore that warning because we know that mReferencedElement's
+// constructor doesn't dereference the pointer passed to it.
 #pragma warning(push)
 #pragma warning(disable:4355)
 #endif
 nsSMILTimeValueSpec::nsSMILTimeValueSpec(nsSMILTimedElement& aOwner,
                                          PRBool aIsBegin)
   : mOwner(&aOwner),
     mIsBegin(aIsBegin),
-    mTimebase(this)
+    mReferencedElement(this)
 #ifdef _MSC_VER
 #pragma warning(pop)
 #endif
 {
 }
 
 nsSMILTimeValueSpec::~nsSMILTimeValueSpec()
 {
-  UnregisterFromTimebase(GetTimebaseElement());
+  UnregisterFromReferencedElement(mReferencedElement.get());
+  if (mEventListener) {
+    mEventListener->Disconnect();
+    mEventListener = nsnull;
+  }
 }
 
 nsresult
 nsSMILTimeValueSpec::SetSpec(const nsAString& aStringSpec,
                              nsIContent* aContextNode)
 {
   nsSMILTimeValueSpecParams params;
   nsresult rv =
@@ -86,51 +107,58 @@ nsSMILTimeValueSpec::SetSpec(const nsASt
   mParams = params;
 
   // According to SMIL 3.0:
   //   The special value "indefinite" does not yield an instance time in the
   //   begin list. It will, however yield a single instance with the value
   //   "indefinite" in an end list. This value is not removed by a reset.
   if (mParams.mType == nsSMILTimeValueSpecParams::OFFSET ||
       (!mIsBegin && mParams.mType == nsSMILTimeValueSpecParams::INDEFINITE)) {
-    nsRefPtr<nsSMILInstanceTime> instance =
-      new nsSMILInstanceTime(mParams.mOffset);
-    if (!instance)
-      return NS_ERROR_OUT_OF_MEMORY;
-    mOwner->AddInstanceTime(instance, mIsBegin);
+    mOwner->AddInstanceTime(new nsSMILInstanceTime(mParams.mOffset), mIsBegin);
   }
 
   ResolveReferences(aContextNode);
 
   return rv;
 }
 
 void
 nsSMILTimeValueSpec::ResolveReferences(nsIContent* aContextNode)
 {
-  if (mParams.mType != nsSMILTimeValueSpecParams::SYNCBASE)
+  if (mParams.mType != nsSMILTimeValueSpecParams::SYNCBASE &&
+      mParams.mType != nsSMILTimeValueSpecParams::EVENT)
     return;
 
   NS_ABORT_IF_FALSE(aContextNode,
       "null context node for resolving timing references against");
 
   // If we're not bound to the document yet, don't worry, we'll get called again
   // when that happens
   if (!aContextNode->IsInDoc())
     return;
 
   // Hold ref to the old content so that it isn't destroyed in between resetting
-  // the timebase and using the pointer to update the timebase.
-  nsRefPtr<nsIContent> oldTimebaseContent = mTimebase.get();
+  // the referenced element and using the pointer to update the referenced
+  // element.
+  nsRefPtr<nsIContent> oldReferencedContent = mReferencedElement.get();
 
-  NS_ABORT_IF_FALSE(mParams.mDependentElemID, "NULL syncbase element id");
+  // XXX Support default event targets
+  NS_ABORT_IF_FALSE(mParams.mDependentElemID, "NULL dependent element id");
   nsString idStr;
   mParams.mDependentElemID->ToString(idStr);
-  mTimebase.ResetWithID(aContextNode, idStr);
-  UpdateTimebase(oldTimebaseContent, mTimebase.get());
+  mReferencedElement.ResetWithID(aContextNode, idStr);
+  UpdateReferencedElement(oldReferencedContent, mReferencedElement.get());
+}
+
+PRBool
+nsSMILTimeValueSpec::IsEventBased() const
+{
+  return mParams.mType == nsSMILTimeValueSpecParams::EVENT ||
+         mParams.mType == nsSMILTimeValueSpecParams::REPEAT ||
+         mParams.mType == nsSMILTimeValueSpecParams::ACCESSKEY;
 }
 
 void
 nsSMILTimeValueSpec::HandleNewInterval(nsSMILInterval& aInterval,
                                        const nsSMILTimeContainer* aSrcContainer)
 {
   const nsSMILInstanceTime& baseInstance = mParams.mSyncBegin
     ? *aInterval.Begin() : *aInterval.End();
@@ -141,19 +169,16 @@ nsSMILTimeValueSpec::HandleNewInterval(n
   if (newTime.IsResolved()) {
     newTime.SetMillis(newTime.GetMillis() + mParams.mOffset.GetMillis());
   }
 
   // Create the instance time and register it with the interval
   nsRefPtr<nsSMILInstanceTime> newInstance =
     new nsSMILInstanceTime(newTime, nsSMILInstanceTime::SOURCE_SYNCBASE, this,
                            &aInterval);
-  if (!newInstance)
-    return;
-
   mOwner->AddInstanceTime(newInstance, mIsBegin);
 }
 
 void
 nsSMILTimeValueSpec::HandleChangedInstanceTime(
     const nsSMILInstanceTime& aBaseTime,
     const nsSMILTimeContainer* aSrcContainer,
     nsSMILInstanceTime& aInstanceTimeToUpdate,
@@ -191,70 +216,164 @@ PRBool
 nsSMILTimeValueSpec::DependsOnBegin() const
 {
   return mParams.mSyncBegin;
 }
 
 void
 nsSMILTimeValueSpec::Traverse(nsCycleCollectionTraversalCallback* aCallback)
 {
-  mTimebase.Traverse(aCallback);
+  mReferencedElement.Traverse(aCallback);
 }
 
 void
 nsSMILTimeValueSpec::Unlink()
 {
-  UnregisterFromTimebase(GetTimebaseElement());
-  mTimebase.Unlink();
+  UnregisterFromReferencedElement(mReferencedElement.get());
+  mReferencedElement.Unlink();
 }
 
 //----------------------------------------------------------------------
 // Implementation helpers
 
 void
-nsSMILTimeValueSpec::UpdateTimebase(nsIContent* aFrom, nsIContent* aTo)
+nsSMILTimeValueSpec::UpdateReferencedElement(nsIContent* aFrom, nsIContent* aTo)
 {
   if (aFrom == aTo)
     return;
 
-  UnregisterFromTimebase(GetTimedElementFromContent(aFrom));
+  UnregisterFromReferencedElement(aFrom);
 
-  nsSMILTimedElement* to = GetTimedElementFromContent(aTo);
-  if (to) {
-    to->AddDependent(*this);
+  if (mParams.mType == nsSMILTimeValueSpecParams::SYNCBASE) {
+    nsSMILTimedElement* to = GetTimedElementFromContent(aTo);
+    if (to) {
+      to->AddDependent(*this);
+    }
+  } else if (mParams.mType == nsSMILTimeValueSpecParams::EVENT) {
+    RegisterEventListener(aTo);
   }
 }
 
 void
-nsSMILTimeValueSpec::UnregisterFromTimebase(nsSMILTimedElement* aTimedElement)
+nsSMILTimeValueSpec::UnregisterFromReferencedElement(nsIContent* aContent)
 {
-  if (!aTimedElement)
+  if (!aContent)
     return;
 
-  aTimedElement->RemoveDependent(*this);
-  mOwner->RemoveInstanceTimesForCreator(this, mIsBegin);
+  if (mParams.mType == nsSMILTimeValueSpecParams::SYNCBASE) {
+    nsSMILTimedElement* timedElement = GetTimedElementFromContent(aContent);
+    if (timedElement) {
+      timedElement->RemoveDependent(*this);
+    }
+    mOwner->RemoveInstanceTimesForCreator(this, mIsBegin);
+  } else if (mParams.mType == nsSMILTimeValueSpecParams::EVENT) {
+    UnregisterEventListener(aContent);
+  }
 }
 
 nsSMILTimedElement*
 nsSMILTimeValueSpec::GetTimedElementFromContent(nsIContent* aContent)
 {
   if (!aContent)
     return nsnull;
 
   nsCOMPtr<nsISMILAnimationElement> animElement = do_QueryInterface(aContent);
   if (!animElement)
     return nsnull;
 
   return &animElement->TimedElement();
 }
 
-nsSMILTimedElement*
-nsSMILTimeValueSpec::GetTimebaseElement()
+void
+nsSMILTimeValueSpec::RegisterEventListener(nsIContent* aTarget)
 {
-  return GetTimedElementFromContent(mTimebase.get());
+  NS_ABORT_IF_FALSE(mParams.mType == nsSMILTimeValueSpecParams::EVENT,
+    "Attempting to register event-listener for non-event nsSMILTimeValueSpec");
+  NS_ABORT_IF_FALSE(mParams.mEventSymbol,
+    "Attempting to register event-listener but there is no event name");
+
+  // XXX Support default event targets
+  if (!aTarget)
+    return;
+
+  if (!mEventListener) {
+    mEventListener = new EventListener(this);
+  }
+
+  nsCOMPtr<nsIDOMEventGroup> sysGroup;
+  nsIEventListenerManager* elm =
+    GetEventListenerManager(aTarget, getter_AddRefs(sysGroup));
+  if (!elm)
+    return;
+  
+  elm->AddEventListenerByType(mEventListener,
+                              nsDependentAtomString(mParams.mEventSymbol),
+                              NS_EVENT_FLAG_BUBBLE |
+                              NS_PRIV_EVENT_UNTRUSTED_PERMITTED,
+                              sysGroup);
+}
+
+void
+nsSMILTimeValueSpec::UnregisterEventListener(nsIContent* aTarget)
+{
+  if (!aTarget || !mEventListener)
+    return;
+
+  nsCOMPtr<nsIDOMEventGroup> sysGroup;
+  nsIEventListenerManager* elm =
+    GetEventListenerManager(aTarget, getter_AddRefs(sysGroup));
+  if (!elm)
+    return;
+
+  elm->RemoveEventListenerByType(mEventListener,
+                                 nsDependentAtomString(mParams.mEventSymbol),
+                                 NS_EVENT_FLAG_BUBBLE |
+                                 NS_PRIV_EVENT_UNTRUSTED_PERMITTED,
+                                 sysGroup);
+}
+
+nsIEventListenerManager*
+nsSMILTimeValueSpec::GetEventListenerManager(nsIContent* aTarget,
+                                             nsIDOMEventGroup** aSystemGroup)
+{
+  NS_ABORT_IF_FALSE(aTarget, "null target; can't get EventListenerManager");
+  NS_ABORT_IF_FALSE(aSystemGroup && !*aSystemGroup,
+      "Bad out param for system group");
+
+  nsIEventListenerManager* elm = aTarget->GetListenerManager(PR_TRUE);
+  if (!elm)
+    return nsnull;
+
+  aTarget->GetSystemEventGroup(aSystemGroup);
+  if (!*aSystemGroup)
+    return nsnull;
+
+  return elm;
+}
+
+void
+nsSMILTimeValueSpec::HandleEvent(nsIDOMEvent* aEvent)
+{
+  NS_ABORT_IF_FALSE(mEventListener, "Got event without an event listener");
+  NS_ABORT_IF_FALSE(mParams.mType == nsSMILTimeValueSpecParams::EVENT,
+    "Got event for non-event nsSMILTimeValueSpec");
+
+  // XXX In the long run we should get the time from the event itself which will
+  // store the time in global document time which we'll need to convert to our
+  // time container
+  nsSMILTimeContainer* container = mOwner->GetTimeContainer();
+  if (!container)
+    return;
+
+  nsSMILTime currentTime = container->GetCurrentTime();
+  nsSMILTimeValue newTime(currentTime + mParams.mOffset.GetMillis());
+
+  nsRefPtr<nsSMILInstanceTime> newInstance =
+    new nsSMILInstanceTime(newTime, nsSMILInstanceTime::SOURCE_EVENT);
+  mOwner->AddInstanceTime(newInstance, mIsBegin);
 }
 
 nsSMILTimeValue
 nsSMILTimeValueSpec::ConvertBetweenTimeContainers(
     const nsSMILTimeValue& aSrcTime,
     const nsSMILTimeContainer* aSrcContainer)
 {
   // If the source time is either indefinite or unresolved the result is going
--- a/content/smil/nsSMILTimeValueSpec.h
+++ b/content/smil/nsSMILTimeValueSpec.h
@@ -36,16 +36,17 @@
  * ***** END LICENSE BLOCK ***** */
 
 #ifndef NS_SMILTIMEVALUESPEC_H_
 #define NS_SMILTIMEVALUESPEC_H_
 
 #include "nsSMILTimeValueSpecParams.h"
 #include "nsReferencedElement.h"
 #include "nsAutoPtr.h"
+#include "nsIDOMEventListener.h"
 
 class nsAString;
 class nsSMILTimeValue;
 class nsSMILTimedElement;
 class nsSMILTimeContainer;
 class nsSMILInstanceTime;
 class nsSMILInterval;
 
@@ -63,16 +64,17 @@ class nsSMILInterval;
 class nsSMILTimeValueSpec
 {
 public:
   nsSMILTimeValueSpec(nsSMILTimedElement& aOwner, PRBool aIsBegin);
   ~nsSMILTimeValueSpec();
 
   nsresult SetSpec(const nsAString& aStringSpec, nsIContent* aContextNode);
   void     ResolveReferences(nsIContent* aContextNode);
+  PRBool   IsEventBased() const;
 
   void     HandleNewInterval(nsSMILInterval& aInterval,
                              const nsSMILTimeContainer* aSrcContainer);
 
   // For created nsSMILInstanceTime objects
   PRBool   DependsOnBegin() const;
   void     HandleChangedInstanceTime(const nsSMILInstanceTime& aBaseTime,
                                      const nsSMILTimeContainer* aSrcContainer,
@@ -80,41 +82,64 @@ public:
                                      PRBool aObjectChanged);
   void     HandleDeletedInstanceTime(nsSMILInstanceTime& aInstanceTime);
 
   // Cycle-collection support
   void Traverse(nsCycleCollectionTraversalCallback* aCallback);
   void Unlink();
 
 protected:
-  void UpdateTimebase(nsIContent* aFrom, nsIContent* aTo);
-  void UnregisterFromTimebase(nsSMILTimedElement* aTimedElement);
+  void UpdateReferencedElement(nsIContent* aFrom, nsIContent* aTo);
+  void UnregisterFromReferencedElement(nsIContent* aContent);
   nsSMILTimedElement* GetTimedElementFromContent(nsIContent* aContent);
-  nsSMILTimedElement* GetTimebaseElement();
+  void RegisterEventListener(nsIContent* aTarget);
+  void UnregisterEventListener(nsIContent* aTarget);
+  nsIEventListenerManager* GetEventListenerManager(nsIContent* aTarget,
+      nsIDOMEventGroup** aSystemGroup);
+  void HandleEvent(nsIDOMEvent* aEvent);
   nsSMILTimeValue ConvertBetweenTimeContainers(const nsSMILTimeValue& aSrcTime,
                                       const nsSMILTimeContainer* aSrcContainer);
 
   nsSMILTimedElement*           mOwner;
   PRPackedBool                  mIsBegin; // Indicates if *we* are a begin spec,
                                           // not to be confused with
                                           // mParams.mSyncBegin which indicates
                                           // if we're synced with the begin of
                                           // the target.
   nsSMILTimeValueSpecParams     mParams;
 
-  class TimebaseElement : public nsReferencedElement {
+  class TimeReferenceElement : public nsReferencedElement
+  {
   public:
-    TimebaseElement(nsSMILTimeValueSpec* aOwner) : mSpec(aOwner) { }
+    TimeReferenceElement(nsSMILTimeValueSpec* aOwner) : mSpec(aOwner) { }
 
   protected:
-    virtual void ElementChanged(Element* aFrom, Element* aTo) {
+    virtual void ElementChanged(Element* aFrom, Element* aTo)
+    {
       nsReferencedElement::ElementChanged(aFrom, aTo);
-      mSpec->UpdateTimebase(aFrom, aTo);
+      mSpec->UpdateReferencedElement(aFrom, aTo);
     }
     virtual PRBool IsPersistent() { return PR_TRUE; }
   private:
     nsSMILTimeValueSpec* mSpec;
   };
 
-  TimebaseElement mTimebase;
+  TimeReferenceElement mReferencedElement;
+
+  class EventListener : public nsIDOMEventListener
+  {
+  public:
+    EventListener(nsSMILTimeValueSpec* aOwner) : mSpec(aOwner) { }
+    void Disconnect()
+    {
+      mSpec = nsnull;
+    }
+
+    NS_DECL_ISUPPORTS
+    NS_DECL_NSIDOMEVENTLISTENER
+
+  private:
+    nsSMILTimeValueSpec* mSpec;
+  };
+  nsCOMPtr<EventListener> mEventListener;
 };
 
 #endif // NS_SMILTIMEVALUESPEC_H_
--- a/content/smil/nsSMILTimedElement.cpp
+++ b/content/smil/nsSMILTimedElement.cpp
@@ -181,17 +181,16 @@ const PRUint8 nsSMILTimedElement::sMaxNu
 //----------------------------------------------------------------------
 // Ctor, dtor
 
 nsSMILTimedElement::nsSMILTimedElement()
 :
   mAnimationElement(nsnull),
   mFillMode(FILL_REMOVE),
   mRestartMode(RESTART_ALWAYS),
-  mEndHasEventConditions(PR_FALSE),
   mInstanceSerialIndex(0),
   mClient(nsnull),
   mCurrentInterval(nsnull),
   mCurrentRepeatIteration(0),
   mPrevRegisteredMilestone(sMaxMilestone),
   mElementState(STATE_STARTUP),
   mSeekState(SEEK_NOT_SEEKING)
 {
@@ -821,19 +820,16 @@ nsSMILTimedElement::UnsetBeginSpec(Remov
   UpdateCurrentInterval();
 }
 
 nsresult
 nsSMILTimedElement::SetEndSpec(const nsAString& aEndSpec,
                                nsIContent* aContextNode,
                                RemovalTestFunction aRemove)
 {
-  // XXX When implementing events etc., don't forget to ensure
-  // mEndHasEventConditions is set if the specification contains conditions that
-  // describe event-values, repeat-values or accessKey-values.
   return SetBeginOrEndSpec(aEndSpec, aContextNode, PR_FALSE /*!isBegin*/,
                            aRemove);
 }
 
 void
 nsSMILTimedElement::UnsetEndSpec(RemovalTestFunction aRemove)
 {
   ClearSpecs(mEndSpecs, mEndInstances, aRemove);
@@ -1331,27 +1327,16 @@ nsSMILTimedElement::DoPostSeek()
     // 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();
   }
 
-  // XXX
-  // Note that SMIL gives the very cryptic description:
-  //   The associated time for the event is the document time before the seek.
-  //   This action does not resolve any times in the instance times list for end
-  //   times.
-  //
-  // The second sentence was added as a clarification in a SMIL 2.0 erratum.
-  // Presumably the intention is that we fire the event as implemented below but
-  // don't act on it. This makes sense at least for dependencies within the same
-  // time container. So we'll probably need to set a flag here to ensure we
-  // don't actually act on it when we implement event-based timing.
   switch (mSeekState)
   {
   case SEEK_FORWARD_FROM_ACTIVE:
   case SEEK_BACKWARD_FROM_ACTIVE:
     if (mElementState != STATE_ACTIVE) {
       FireTimeEventAsync(NS_SMIL_END, 0);
     }
     break;
@@ -1566,28 +1551,28 @@ nsSMILTimedElement::GetNextInterval(cons
       // If the last interval ended at the same point and was zero-duration and
       // this one is too, look for another end to use instead
       if (tempEnd && tempEnd->Time() == tempBegin->Time() &&
           prevIntervalWasZeroDur) {
         tempEnd = GetNextGreater(mEndInstances, tempBegin->Time(), endPos);
       }
 
       // If all the ends are before the beginning we have a bad interval UNLESS:
-      // a) We have end events which leave the interval open-ended, OR
-      // b) We never had any end attribute to begin with (and hence we should
+      // a) We never had any end attribute to begin with (and hence we should
       //    just use the active duration after allowing for the possibility of
-      //    an end instance provided by a DOM call)
-      // c) We have an end attribute but no end instances--this is a special
+      //    an end instance provided by a DOM call), OR
+      // b) We have an end attribute but no end instances--this is a special
       //    case that is needed for syncbase timing so that animations of the
       //    following sort: <animate id="a" end="a.begin+1s" ... /> can be
       //    resolved (see SVGT 1.2 Test Suite animate-elem-221-t.svg) by first
-      //    establishing an interval of unresolved duration.
-      PRBool openEndedIntervalOk = mEndHasEventConditions ||
-          mEndSpecs.IsEmpty() ||
-          mEndInstances.IsEmpty();
+      //    establishing an interval of unresolved duration, OR
+      // c) We have end events which leave the interval open-ended.
+      PRBool openEndedIntervalOk = mEndSpecs.IsEmpty() ||
+                                   mEndInstances.IsEmpty() ||
+                                   EndHasEventConditions();
       if (!tempEnd && !openEndedIntervalOk)
         return NS_ERROR_FAILURE; // Bad interval
 
       nsSMILTimeValue intervalEnd = tempEnd
                                   ? tempEnd->Time() : nsSMILTimeValue();
       nsSMILTimeValue activeEnd = CalcActiveEnd(tempBegin->Time(), intervalEnd);
 
       if (!tempEnd || intervalEnd != activeEnd) {
@@ -1923,18 +1908,16 @@ void
 nsSMILTimedElement::AddInstanceTimeFromCurrentTime(nsSMILTime aCurrentTime,
     double aOffsetSeconds, PRBool aIsBegin)
 {
   double offset = aOffsetSeconds * PR_MSEC_PER_SEC;
   nsSMILTime timeWithOffset = aCurrentTime + PRInt64(NS_round(offset));
 
   nsSMILTimeValue timeVal(timeWithOffset);
 
-  // XXX If we re-use this method for event-based timing we'll need to change it
-  // so we don't end up setting SOURCE_DOM for event-based times.
   nsRefPtr<nsSMILInstanceTime> instanceTime =
     new nsSMILInstanceTime(timeVal, nsSMILInstanceTime::SOURCE_DOM);
 
   AddInstanceTime(instanceTime, aIsBegin);
 }
 
 void
 nsSMILTimedElement::RegisterMilestone()
@@ -2101,16 +2084,26 @@ nsSMILTimedElement::GetEffectiveBeginIns
 const nsSMILInterval*
 nsSMILTimedElement::GetPreviousInterval() const
 {
   return mOldIntervals.IsEmpty()
     ? nsnull
     : mOldIntervals[mOldIntervals.Length()-1].get();
 }
 
+PRBool
+nsSMILTimedElement::EndHasEventConditions() const
+{
+  for (PRUint32 i = 0; i < mEndSpecs.Length(); ++i) {
+    if (mEndSpecs[i]->IsEventBased())
+      return PR_TRUE;
+  }
+  return PR_FALSE;
+}
+
 //----------------------------------------------------------------------
 // Hashtable callback functions
 
 /* static */ PR_CALLBACK PLDHashOperator
 nsSMILTimedElement::NotifyNewIntervalCallback(TimeValueSpecPtrKey* aKey,
                                               void* aData)
 {
   NS_ABORT_IF_FALSE(aKey, "Null hash key for time container hash table");
--- a/content/smil/nsSMILTimedElement.h
+++ b/content/smil/nsSMILTimedElement.h
@@ -487,16 +487,17 @@ protected:
   PRBool            GetNextMilestone(nsSMILMilestone& aNextMilestone) const;
 
   void              NotifyNewInterval();
   void              NotifyChangedInterval();
   void              FireTimeEventAsync(PRUint32 aMsg, PRInt32 aDetail);
   const nsSMILInstanceTime* GetEffectiveBeginInstance() const;
   const nsSMILInterval* GetPreviousInterval() const;
   PRBool            HasPlayed() const { return !mOldIntervals.IsEmpty(); }
+  PRBool            EndHasEventConditions() const;
 
   // Hashtable callback methods
   PR_STATIC_CALLBACK(PLDHashOperator) NotifyNewIntervalCallback(
       TimeValueSpecPtrKey* aKey, void* aData);
 
   //
   // Members
   //
@@ -525,18 +526,16 @@ protected:
   {
     RESTART_ALWAYS,
     RESTART_WHENNOTACTIVE,
     RESTART_NEVER
   };
   nsSMILRestartMode               mRestartMode;
   static nsAttrValue::EnumTable   sRestartModeTable[];
 
-  PRPackedBool                    mEndHasEventConditions;
-
   InstanceTimeList                mBeginInstances;
   InstanceTimeList                mEndInstances;
   PRUint32                        mInstanceSerialIndex;
 
   nsSMILAnimationFunction*        mClient;
   nsAutoPtr<nsSMILInterval>       mCurrentInterval;
   IntervalList                    mOldIntervals;
   PRUint32                        mCurrentRepeatIteration;
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/smil/event/event-begin-1.svg
@@ -0,0 +1,16 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:xlink="http://www.w3.org/1999/xlink"
+     class="reftest-wait"
+     onload="
+        document.documentElement.pauseAnimations();
+        document.documentElement.setCurrentTime(0);
+        click('circle');
+        delayedSnapshot(2)">
+  <script xlink:href="event-util.js" type="text/javascript"/>
+  <circle id="circle" r="10"/>
+  <rect width="100" height="100" fill="red">
+    <set attributeName="fill" attributeType="CSS"
+      to="green"
+      begin="circle.click" dur="4s"/>
+  </rect>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/smil/event/event-begin-load-1.svg
@@ -0,0 +1,14 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:xlink="http://www.w3.org/1999/xlink"
+     class="reftest-wait" id="svg"
+     onload="
+        document.documentElement.pauseAnimations();
+        document.documentElement.setCurrentTime(0);
+        delayedSnapshot(2)">
+  <script xlink:href="event-util.js" type="text/javascript"/>
+  <rect width="100" height="100" fill="red">
+    <set attributeName="fill" attributeType="CSS"
+      to="green"
+      begin="svg.SVGLoad" dur="4s"/>
+  </rect>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/smil/event/event-begin-offset-1.svg
@@ -0,0 +1,16 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:xlink="http://www.w3.org/1999/xlink"
+     class="reftest-wait"
+     onload="
+        document.documentElement.pauseAnimations();
+        document.documentElement.setCurrentTime(0);
+        click('circle');
+        delayedSnapshot(6)">
+  <script xlink:href="event-util.js" type="text/javascript"/>
+  <circle id="circle" r="10"/>
+  <rect width="100" height="100" fill="red">
+    <set attributeName="fill" attributeType="CSS"
+      to="green"
+      begin="circle.click+4s" dur="4s"/>
+  </rect>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/smil/event/event-begin-offset-2.svg
@@ -0,0 +1,16 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:xlink="http://www.w3.org/1999/xlink"
+     class="reftest-wait"
+     onload="
+        document.documentElement.pauseAnimations();
+        document.documentElement.setCurrentTime(4);
+        click('circle');
+        delayedSnapshot(4)">
+  <script xlink:href="event-util.js" type="text/javascript"/>
+  <circle id="circle" r="10"/>
+  <rect width="100" height="100" fill="red">
+    <animate attributeName="fill" attributeType="CSS"
+      values="orange; green" calcMode="discrete"
+      begin="circle.click-4s" dur="8s"/>
+  </rect>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/smil/event/event-begin-timeevent-1.svg
@@ -0,0 +1,17 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:xlink="http://www.w3.org/1999/xlink"
+     class="reftest-wait"
+     onload="
+        document.documentElement.pauseAnimations();
+        document.documentElement.setCurrentTime(0);
+        /* Make sure the event gets fired */
+        document.documentElement.setCurrentTime(0.1);
+        delayedSnapshot(2)">
+  <script xlink:href="event-util.js" type="text/javascript"/>
+  <rect width="100" height="100" fill="red">
+    <set attributeName="x" to="0" begin="0s" id="a"/>
+    <set attributeName="fill" attributeType="CSS"
+      to="green"
+      begin="a.beginEvent" dur="4s"/>
+  </rect>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/smil/event/event-begin-timeevent-2.svg
@@ -0,0 +1,18 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:xlink="http://www.w3.org/1999/xlink"
+     class="reftest-wait"
+     onload="
+        document.documentElement.pauseAnimations();
+        document.documentElement.setCurrentTime(0);
+        /* Make sure the event gets fired */
+        document.documentElement.setCurrentTime(0.1);
+        document.documentElement.setCurrentTime(0.6);
+        delayedSnapshot(2.5)">
+  <script xlink:href="event-util.js" type="text/javascript"/>
+  <rect width="100" height="100" fill="red">
+    <set attributeName="x" to="0" begin="0s" dur="0.5s" id="a"/>
+    <set attributeName="fill" attributeType="CSS"
+      to="green"
+      begin="a.endEvent" dur="4s"/>
+  </rect>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/smil/event/event-begin-timeevent-3.svg
@@ -0,0 +1,16 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:xlink="http://www.w3.org/1999/xlink"
+     class="reftest-wait"
+     onload="
+        document.documentElement.pauseAnimations();
+        document.documentElement.setCurrentTime(0.499);
+        document.documentElement.unpauseAnimations();
+        window.setTimeout(finish, 100, 2);">
+  <script xlink:href="event-util.js" type="text/javascript"/>
+  <rect width="100" height="100" fill="red">
+    <set attributeName="x" to="0" begin="0s" dur="0.5s" repeatCount="2" id="a"/>
+    <set attributeName="fill" attributeType="CSS"
+      to="green"
+      begin="a.repeatEvent" dur="4s"/>
+  </rect>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/smil/event/event-bubble-1.svg
@@ -0,0 +1,18 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:xlink="http://www.w3.org/1999/xlink"
+     class="reftest-wait"
+     onload="
+        document.documentElement.pauseAnimations();
+        document.documentElement.setCurrentTime(0);
+        click('circle');
+        delayedSnapshot(2)">
+  <script xlink:href="event-util.js" type="text/javascript"/>
+  <g id="g">
+    <circle id="circle" r="10"/>
+  </g>
+  <rect width="100" height="100" fill="red">
+    <set attributeName="fill" attributeType="CSS"
+      to="green"
+      begin="g.click" dur="4s"/>
+  </rect>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/smil/event/event-custom-1.svg
@@ -0,0 +1,26 @@
+<!-- Tests support for custom events -->
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:xlink="http://www.w3.org/1999/xlink"
+     class="reftest-wait"
+     onload="sendEvent()">
+  <script xlink:href="event-util.js" type="text/javascript"/>
+  <script type="text/javascript">
+    function sendEvent()
+    {
+      document.documentElement.pauseAnimations();
+      document.documentElement.setCurrentTime(0);
+      var evt = document.createEvent("SVGEvents");
+      evt.initEvent("user.defined", false, false);
+      var target = document.getElementById('rect');
+      target.dispatchEvent(evt);
+      delayedSnapshot(2);
+    }
+  </script>
+  <rect width="100" height="100" fill="red" id="rect">
+    <!-- SMIL allows periods to be embedded in the event name by escaping them
+         with a backslash. (Otherwise the part before the period would be
+         treated as an ID reference.) Test that we support that. -->
+    <set attributeName="fill" attributeType="CSS" to="green"
+      begin="rect.user\.defined" dur="4s"/>
+  </rect>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/smil/event/event-end-1.svg
@@ -0,0 +1,16 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:xlink="http://www.w3.org/1999/xlink"
+     class="reftest-wait"
+     onload="
+        document.documentElement.pauseAnimations();
+        document.documentElement.setCurrentTime(2);
+        click('circle');
+        delayedSnapshot(3)">
+  <script xlink:href="event-util.js" type="text/javascript"/>
+  <circle id="circle" r="10"/>
+  <rect width="100" height="100" fill="green">
+    <set attributeName="fill" attributeType="CSS"
+      to="red"
+      begin="1s" end="circle.click" dur="4s"/>
+  </rect>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/smil/event/event-end-2.svg
@@ -0,0 +1,18 @@
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:xlink="http://www.w3.org/1999/xlink"
+     class="reftest-wait"
+     onload="
+        document.documentElement.pauseAnimations();
+        document.documentElement.setCurrentTime(0);
+        /* Click event should be ignored since the element is inactive as per
+         * SMIL's event sensitivity rules */
+        click('circle');
+        delayedSnapshot(3)">
+  <script xlink:href="event-util.js" type="text/javascript"/>
+  <circle id="circle" r="10"/>
+  <rect width="100" height="100" fill="red">
+    <set attributeName="fill" attributeType="CSS"
+      to="green"
+      begin="1s" end="circle.click+2s" dur="4s"/>
+  </rect>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/smil/event/event-end-open-1.svg
@@ -0,0 +1,20 @@
+<!--
+  Generally speaking, when constructing intervals if all the end instance times
+  are before the next begin time there's no valid interval.
+
+  However, SMIL specifically makes an exception when the end attribute has event
+  conditions in which case an unresolved end is used.
+ -->
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:xlink="http://www.w3.org/1999/xlink"
+     class="reftest-wait"
+     onload="
+        document.documentElement.pauseAnimations();
+        document.documentElement.setCurrentTime(2);
+        document.documentElement.removeAttribute('class')">
+  <circle id="circle" r="10"/>
+  <rect width="100" height="100" fill="red">
+    <set attributeName="fill" attributeType="CSS" to="green"
+      begin="0s; 2s" end="1s; circle.click"/>
+  </rect>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/smil/event/event-preventDefault-1.svg
@@ -0,0 +1,16 @@
+<!-- Calling preventDefault on the event should have no effect -->
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:xlink="http://www.w3.org/1999/xlink"
+     class="reftest-wait"
+     onload="
+        document.documentElement.pauseAnimations();
+        document.documentElement.setCurrentTime(0);
+        click('circle');
+        delayedSnapshot(2)">
+  <script xlink:href="event-util.js" type="text/javascript"/>
+  <circle id="circle" r="10" onclick="evt.preventDefault()"/>
+  <rect width="100" height="100" fill="red">
+    <set attributeName="fill" attributeType="CSS"
+      to="green" begin="circle.click" dur="4s"/>
+  </rect>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/smil/event/event-seek-1.svg
@@ -0,0 +1,19 @@
+<!-- Test a backwards seek with an event-generated time -->
+<svg xmlns="http://www.w3.org/2000/svg"
+     xmlns:xlink="http://www.w3.org/1999/xlink"
+     id="svg"
+     class="reftest-wait"
+     onload="window.setTimeout(seek, 10)">
+  <script xlink:href="event-util.js" type="text/javascript"/>
+  <script type="text/javascript">
+    function seek()
+    {
+      document.documentElement.setCurrentTime(40);
+      finish(30);
+    }
+  </script>
+  <rect width="100" height="100" fill="red">
+    <set attributeName="fill" attributeType="CSS" to="green"
+      begin="svg.SVGLoad+20s"/>
+  </rect>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/smil/event/event-util.js
@@ -0,0 +1,21 @@
+// Allows a moment for events to be processed then performs a seek and runs
+// a snapshot.
+function delayedSnapshot(seekTimeInSeconds) {
+  // Allow time for events to be processed
+  window.setTimeout(finish, 10, seekTimeInSeconds);
+}
+
+function finish(seekTimeInSeconds) {
+  document.documentElement.pauseAnimations();
+  if (seekTimeInSeconds)
+    document.documentElement.setCurrentTime(seekTimeInSeconds);
+  document.documentElement.removeAttribute("class");
+}
+
+function click(targetId) {
+  var evt = document.createEvent("MouseEvents");
+  evt.initMouseEvent("click", true, true, window,
+    0, 0, 0, 0, 0, false, false, false, false, 0, null);
+  var target = document.getElementById(targetId);
+  target.dispatchEvent(evt);
+}
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/smil/event/green-box-ref.svg
@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg">
+  <rect width="100" height="100" fill="green"/>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/smil/event/reftest.list
@@ -0,0 +1,16 @@
+# Tests related to SVG Animation (using SMIL) that use event timing.
+
+== event-begin-1.svg green-box-ref.svg
+== event-begin-offset-1.svg green-box-ref.svg
+== event-begin-offset-2.svg green-box-ref.svg
+== event-begin-timeevent-1.svg green-box-ref.svg
+== event-begin-timeevent-2.svg green-box-ref.svg
+== event-begin-timeevent-3.svg green-box-ref.svg
+== event-begin-load-1.svg green-box-ref.svg
+== event-bubble-1.svg green-box-ref.svg
+== event-custom-1.svg green-box-ref.svg
+== event-end-1.svg green-box-ref.svg
+== event-end-2.svg green-box-ref.svg
+== event-end-open-1.svg green-box-ref.svg
+== event-preventDefault-1.svg green-box-ref.svg
+== event-seek-1.svg green-box-ref.svg
--- a/layout/reftests/svg/smil/reftest.list
+++ b/layout/reftests/svg/smil/reftest.list
@@ -45,16 +45,19 @@ include restart/reftest.list
 include pause/reftest.list
 
 # syncbase tests
 include syncbase/reftest.list
 
 # seek tests
 include seek/reftest.list
 
+# event tests
+include event/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