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
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 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)
+{
+  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)
 {
-  return GetTimedElementFromContent(mTimebase.get());
+  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