Bug 527270: Implement SMIL TimeEvents. r=dholbert,smaug; sr=roc; a=blocking-betaN
authorBrian Birtles <birtles@gmail.com>
Sat, 31 Jul 2010 16:02:52 +0900
changeset 48450 f73e5032cfadb8b8d575b8d1d40814700e038530
parent 48449 99146dea1c85e0c73094025d5fbb3659fe9c3879
child 48451 ee695e20ee8b4908f21dcf705904f9afa7df8116
push id14740
push userbbirtles@mozilla.com
push dateSat, 31 Jul 2010 07:22:55 +0000
treeherderautoland@f73e5032cfad [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdholbert, smaug, roc, blocking-betaN
bugs527270
milestone2.0b3pre
first release with
nightly linux32
f73e5032cfad / 4.0b3pre / 20100731030927 / files
nightly linux64
f73e5032cfad / 4.0b3pre / 20100731030739 / files
nightly mac
f73e5032cfad / 4.0b3pre / 20100731030843 / files
nightly win32
f73e5032cfad / 4.0b3pre / 20100731040142 / files
nightly win64
f73e5032cfad / 4.0b3pre / 20100731040539 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 527270: Implement SMIL TimeEvents. r=dholbert,smaug; sr=roc; a=blocking-betaN
content/base/public/nsContentUtils.h
content/base/src/nsContentUtils.cpp
content/base/src/nsGkAtomList.h
content/events/public/nsIPrivateDOMEvent.h
content/events/src/nsDOMEvent.cpp
content/events/src/nsDOMEvent.h
content/events/src/nsEventDispatcher.cpp
content/events/src/nsEventListenerManager.cpp
content/smil/Makefile.in
content/smil/nsDOMTimeEvent.cpp
content/smil/nsDOMTimeEvent.h
content/smil/nsSMILTimedElement.cpp
content/smil/nsSMILTimedElement.h
content/smil/test/Makefile.in
content/smil/test/test_smilTimeEvents.xhtml
content/svg/content/src/nsSVGAnimationElement.cpp
content/svg/content/src/nsSVGAnimationElement.h
content/svg/content/src/nsSVGElement.cpp
dom/base/nsDOMClassInfo.cpp
dom/base/nsDOMClassInfoClasses.h
dom/interfaces/smil/Makefile.in
dom/interfaces/smil/nsIDOMTimeEvent.idl
widget/public/nsGUIEvent.h
--- a/content/base/public/nsContentUtils.h
+++ b/content/base/public/nsContentUtils.h
@@ -152,16 +152,17 @@ class Element;
 extern const char kLoadAsData[];
 
 enum EventNameType {
   EventNameType_None = 0x0000,
   EventNameType_HTML = 0x0001,
   EventNameType_XUL = 0x0002,
   EventNameType_SVGGraphic = 0x0004, // svg graphic elements
   EventNameType_SVGSVG = 0x0008, // the svg element
+  EventNameType_SMIL = 0x0016, // smil elements
 
   EventNameType_HTMLXUL = 0x0003,
   EventNameType_All = 0xFFFF
 };
 
 struct EventNameMapping
 {
   nsIAtom* mAtom;
--- a/content/base/src/nsContentUtils.cpp
+++ b/content/base/src/nsContentUtils.cpp
@@ -570,16 +570,24 @@ nsContentUtils::InitializeEventTable() {
     { nsGkAtoms::onSVGResize,                   NS_SVG_RESIZE, EventNameType_None, NS_SVG_EVENT },
     { nsGkAtoms::onSVGScroll,                   NS_SVG_SCROLL, EventNameType_None, NS_SVG_EVENT },
 
     { nsGkAtoms::onSVGZoom,                     NS_SVG_ZOOM, EventNameType_None, NS_SVGZOOM_EVENT },
 
     // This is a bit hackish, but SVG's event names are weird.
     { nsGkAtoms::onzoom,                        NS_SVG_ZOOM, EventNameType_SVGSVG, NS_EVENT_NULL },
 #endif // MOZ_SVG
+#ifdef MOZ_SMIL
+    { nsGkAtoms::onbegin,                       NS_SMIL_BEGIN, EventNameType_SMIL, NS_EVENT_NULL },
+    { nsGkAtoms::onbeginEvent,                  NS_SMIL_BEGIN, EventNameType_None, NS_SMIL_TIME_EVENT },
+    { nsGkAtoms::onend,                         NS_SMIL_END, EventNameType_SMIL, NS_EVENT_NULL },
+    { nsGkAtoms::onendEvent,                    NS_SMIL_END, EventNameType_None, NS_SMIL_TIME_EVENT },
+    { nsGkAtoms::onrepeat,                      NS_SMIL_REPEAT, EventNameType_SMIL, NS_EVENT_NULL },
+    { nsGkAtoms::onrepeatEvent,                 NS_SMIL_REPEAT, EventNameType_None, NS_SMIL_TIME_EVENT },
+#endif // MOZ_SMIL
 #ifdef MOZ_MEDIA
     { nsGkAtoms::onloadstart,                   NS_LOADSTART, EventNameType_HTML, NS_EVENT_NULL },
     { nsGkAtoms::onprogress,                    NS_PROGRESS, EventNameType_HTML, NS_EVENT_NULL },
     { nsGkAtoms::onsuspend,                     NS_SUSPEND, EventNameType_HTML, NS_EVENT_NULL },
     { nsGkAtoms::onemptied,                     NS_EMPTIED, EventNameType_HTML, NS_EVENT_NULL },
     { nsGkAtoms::onstalled,                     NS_STALLED, EventNameType_HTML, NS_EVENT_NULL },
     { nsGkAtoms::onplay,                        NS_PLAY, EventNameType_HTML, NS_EVENT_NULL },
     { nsGkAtoms::onpause,                       NS_PAUSE, EventNameType_HTML, NS_EVENT_NULL },
--- a/content/base/src/nsGkAtomList.h
+++ b/content/base/src/nsGkAtomList.h
@@ -1304,16 +1304,22 @@ GK_ATOM(begin, "begin")
 GK_ATOM(by, "by")
 GK_ATOM(calcMode, "calcMode")
 GK_ATOM(css, "CSS")
 GK_ATOM(dur, "dur")
 GK_ATOM(keyPoints, "keyPoints")
 GK_ATOM(keySplines, "keySplines")
 GK_ATOM(keyTimes, "keyTimes")
 GK_ATOM(mozAnimateMotionDummyAttr, "_mozAnimateMotionDummyAttr")
+GK_ATOM(onbegin, "onbegin")
+GK_ATOM(onbeginEvent, "onbeginEvent")
+GK_ATOM(onend, "onend")
+GK_ATOM(onendEvent, "onendEvent")
+GK_ATOM(onrepeat, "onrepeat")
+GK_ATOM(onrepeatEvent, "onrepeatEvent")
 GK_ATOM(repeatCount, "repeatCount")
 GK_ATOM(repeatDur, "repeatDur")
 GK_ATOM(restart, "restart")
 GK_ATOM(to, "to")
 GK_ATOM(XML, "XML")
 #endif
 
 #ifdef MOZ_MATHML
--- a/content/events/public/nsIPrivateDOMEvent.h
+++ b/content/events/public/nsIPrivateDOMEvent.h
@@ -98,16 +98,20 @@ NS_NewDOMBeforeUnloadEvent(nsIDOMEvent**
 nsresult
 NS_NewDOMPageTransitionEvent(nsIDOMEvent** aResult, nsPresContext* aPresContext, nsEvent* aEvent);
 #ifdef MOZ_SVG
 nsresult
 NS_NewDOMSVGEvent(nsIDOMEvent** aResult, nsPresContext* aPresContext, class nsEvent* aEvent);
 nsresult
 NS_NewDOMSVGZoomEvent(nsIDOMEvent** aResult, nsPresContext* aPresContext, class nsGUIEvent* aEvent);
 #endif // MOZ_SVG
+#ifdef MOZ_SMIL
+nsresult
+NS_NewDOMTimeEvent(nsIDOMEvent** aResult, nsPresContext* aPresContext, class nsEvent* aEvent);
+#endif // MOZ_SMIL
 nsresult
 NS_NewDOMXULCommandEvent(nsIDOMEvent** aResult, nsPresContext* aPresContext, class nsInputEvent* aEvent);
 nsresult
 NS_NewDOMCommandEvent(nsIDOMEvent** aInstancePtrResult, nsPresContext* aPresContext, nsCommandEvent* aEvent);
 nsresult
 NS_NewDOMMessageEvent(nsIDOMEvent** aInstancePtrResult, nsPresContext* aPresContext, class nsEvent* aEvent);
 nsresult
 NS_NewDOMProgressEvent(nsIDOMEvent** aInstancePtrResult, nsPresContext* aPresContext, class nsEvent* aEvent);
--- a/content/events/src/nsDOMEvent.cpp
+++ b/content/events/src/nsDOMEvent.cpp
@@ -76,16 +76,19 @@ static const char* const sEventNames[] =
   "DOMAttrModified", "DOMCharacterDataModified",
   "DOMActivate", "DOMFocusIn", "DOMFocusOut",
   "pageshow", "pagehide", "DOMMouseScroll", "MozMousePixelScroll",
   "offline", "online", "copy", "cut", "paste",
 #ifdef MOZ_SVG
   "SVGLoad", "SVGUnload", "SVGAbort", "SVGError", "SVGResize", "SVGScroll",
   "SVGZoom",
 #endif // MOZ_SVG
+#ifdef MOZ_SMIL
+  "beginEvent", "endEvent", "repeatEvent",
+#endif // MOZ_SMIL
 #ifdef MOZ_MEDIA
   "loadstart", "progress", "suspend", "emptied", "stalled", "play", "pause",
   "loadedmetadata", "loadeddata", "waiting", "playing", "canplay",
   "canplaythrough", "seeking", "seeked", "timeupdate", "ended", "ratechange",
   "durationchange", "volumechange",
 #endif // MOZ_MEDIA
   "MozAfterPaint",
   "MozSwipeGesture",
@@ -768,16 +771,25 @@ NS_METHOD nsDOMEvent::DuplicatePrivateDa
     case NS_SVGZOOM_EVENT:
     {
       newEvent = new nsGUIEvent(PR_FALSE, msg, nsnull);
       NS_ENSURE_TRUE(newEvent, NS_ERROR_OUT_OF_MEMORY);
       newEvent->eventStructType = NS_SVGZOOM_EVENT;
       break;
     }
 #endif // MOZ_SVG
+#ifdef MOZ_SMIL
+    case NS_SMIL_TIME_EVENT:
+    {
+      newEvent = new nsUIEvent(PR_FALSE, msg, 0);
+      NS_ENSURE_TRUE(newEvent, NS_ERROR_OUT_OF_MEMORY);
+      newEvent->eventStructType = NS_SMIL_TIME_EVENT;
+      break;
+    }
+#endif // MOZ_SMIL
     case NS_SIMPLE_GESTURE_EVENT:
     {
       nsSimpleGestureEvent* oldSimpleGestureEvent = static_cast<nsSimpleGestureEvent*>(mEvent);
       nsSimpleGestureEvent* simpleGestureEvent = 
         new nsSimpleGestureEvent(PR_FALSE, msg, nsnull, 0, 0.0);
       NS_ENSURE_TRUE(simpleGestureEvent, NS_ERROR_OUT_OF_MEMORY);
       isInputEvent = PR_TRUE;
       simpleGestureEvent->direction = oldSimpleGestureEvent->direction;
@@ -1220,16 +1232,24 @@ const char* nsDOMEvent::GetEventName(PRU
     return sEventNames[eDOMEvents_SVGError];
   case NS_SVG_RESIZE:
     return sEventNames[eDOMEvents_SVGResize];
   case NS_SVG_SCROLL:
     return sEventNames[eDOMEvents_SVGScroll];
   case NS_SVG_ZOOM:
     return sEventNames[eDOMEvents_SVGZoom];
 #endif // MOZ_SVG
+#ifdef MOZ_SMIL
+  case NS_SMIL_BEGIN:
+    return sEventNames[eDOMEvents_beginEvent];
+  case NS_SMIL_END:
+    return sEventNames[eDOMEvents_endEvent];
+  case NS_SMIL_REPEAT:
+    return sEventNames[eDOMEvents_repeatEvent];
+#endif // MOZ_SMIL
 #ifdef MOZ_MEDIA
   case NS_LOADSTART:
     return sEventNames[eDOMEvents_loadstart];
   case NS_PROGRESS:
     return sEventNames[eDOMEvents_progress];
   case NS_SUSPEND:
     return sEventNames[eDOMEvents_suspend];
   case NS_EMPTIED:
--- a/content/events/src/nsDOMEvent.h
+++ b/content/events/src/nsDOMEvent.h
@@ -137,16 +137,21 @@ public:
     eDOMEvents_SVGLoad,
     eDOMEvents_SVGUnload,
     eDOMEvents_SVGAbort,
     eDOMEvents_SVGError,
     eDOMEvents_SVGResize,
     eDOMEvents_SVGScroll,
     eDOMEvents_SVGZoom,
 #endif // MOZ_SVG
+#ifdef MOZ_SMIL
+    eDOMEvents_beginEvent,
+    eDOMEvents_endEvent,
+    eDOMEvents_repeatEvent,
+#endif // MOZ_SMIL
 #ifdef MOZ_MEDIA
     eDOMEvents_loadstart,
     eDOMEvents_progress,
     eDOMEvents_suspend,
     eDOMEvents_emptied,
     eDOMEvents_stalled,
     eDOMEvents_play,
     eDOMEvents_pause,
--- a/content/events/src/nsEventDispatcher.cpp
+++ b/content/events/src/nsEventDispatcher.cpp
@@ -737,16 +737,20 @@ nsEventDispatcher::CreateEvent(nsPresCon
 #ifdef MOZ_SVG
     case NS_SVG_EVENT:
       return NS_NewDOMSVGEvent(aDOMEvent, aPresContext,
                                aEvent);
     case NS_SVGZOOM_EVENT:
       return NS_NewDOMSVGZoomEvent(aDOMEvent, aPresContext,
                                    static_cast<nsGUIEvent*>(aEvent));
 #endif // MOZ_SVG
+#ifdef MOZ_SMIL
+    case NS_SMIL_TIME_EVENT:
+      return NS_NewDOMTimeEvent(aDOMEvent, aPresContext, aEvent);
+#endif // MOZ_SMIL
 
     case NS_COMMAND_EVENT:
       return NS_NewDOMCommandEvent(aDOMEvent, aPresContext,
                                    static_cast<nsCommandEvent*>(aEvent));
     case NS_SIMPLE_GESTURE_EVENT:
       return NS_NewDOMSimpleGestureEvent(aDOMEvent, aPresContext,
                                          static_cast<nsSimpleGestureEvent*>(aEvent));
     case NS_TRANSITION_EVENT:
@@ -792,16 +796,21 @@ nsEventDispatcher::CreateEvent(nsPresCon
 #ifdef MOZ_SVG
   if (aEventType.LowerCaseEqualsLiteral("svgevent") ||
       aEventType.LowerCaseEqualsLiteral("svgevents"))
     return NS_NewDOMSVGEvent(aDOMEvent, aPresContext, nsnull);
   if (aEventType.LowerCaseEqualsLiteral("svgzoomevent") ||
       aEventType.LowerCaseEqualsLiteral("svgzoomevents"))
     return NS_NewDOMSVGZoomEvent(aDOMEvent, aPresContext, nsnull);
 #endif // MOZ_SVG
+#ifdef MOZ_SMIL
+  if (aEventType.LowerCaseEqualsLiteral("timeevent") ||
+      aEventType.LowerCaseEqualsLiteral("timeevents"))
+    return NS_NewDOMTimeEvent(aDOMEvent, aPresContext, nsnull);
+#endif // MOZ_SMIL
   if (aEventType.LowerCaseEqualsLiteral("xulcommandevent") ||
       aEventType.LowerCaseEqualsLiteral("xulcommandevents"))
     return NS_NewDOMXULCommandEvent(aDOMEvent, aPresContext, nsnull);
   if (aEventType.LowerCaseEqualsLiteral("commandevent") ||
       aEventType.LowerCaseEqualsLiteral("commandevents"))
     return NS_NewDOMCommandEvent(aDOMEvent, aPresContext, nsnull);
   if (aEventType.LowerCaseEqualsLiteral("datacontainerevent") ||
       aEventType.LowerCaseEqualsLiteral("datacontainerevents"))
--- a/content/events/src/nsEventListenerManager.cpp
+++ b/content/events/src/nsEventListenerManager.cpp
@@ -978,16 +978,24 @@ nsEventListenerManager::CompileEventHand
         attrName = nsGkAtoms::onerror;
       else if (aName == nsGkAtoms::onSVGResize)
         attrName = nsGkAtoms::onresize;
       else if (aName == nsGkAtoms::onSVGScroll)
         attrName = nsGkAtoms::onscroll;
       else if (aName == nsGkAtoms::onSVGZoom)
         attrName = nsGkAtoms::onzoom;
 #endif // MOZ_SVG
+#ifdef MOZ_SMIL
+      else if (aName == nsGkAtoms::onbeginEvent)
+        attrName = nsGkAtoms::onbegin;
+      else if (aName == nsGkAtoms::onrepeatEvent)
+        attrName = nsGkAtoms::onrepeat;
+      else if (aName == nsGkAtoms::onendEvent)
+        attrName = nsGkAtoms::onend;
+#endif // MOZ_SMIL
 
       content->GetAttr(kNameSpaceID_None, attrName, handlerBody);
 
       PRUint32 lineNo = 0;
       nsCAutoString url (NS_LITERAL_CSTRING("javascript:alert('TODO: FIXME')"));
       nsIDocument* doc = nsnull;
       nsCOMPtr<nsINode> node = do_QueryInterface(aCurrentTarget);
       if (node) {
--- a/content/smil/Makefile.in
+++ b/content/smil/Makefile.in
@@ -49,16 +49,17 @@ LIBXUL_LIBRARY	= 1
 
 # nsSMILKeySpline is used by CSS transitions -- need to build it regardless
 # of whether SMIL is enabled.
 CPPSRCS		= nsSMILKeySpline.cpp
 EXPORTS		= nsSMILKeySpline.h
 
 ifdef MOZ_SMIL
 CPPSRCS		+= \
+	nsDOMTimeEvent.cpp \
 	nsSMILAnimationController.cpp \
 	nsSMILAnimationFunction.cpp \
 	nsSMILCompositor.cpp \
 	nsSMILCSSProperty.cpp \
 	nsSMILCSSValueType.cpp \
 	nsSMILFloatType.cpp \
 	nsSMILInstanceTime.cpp \
 	nsSMILInterval.cpp \
@@ -100,14 +101,15 @@ EXPORTS		+= \
 	  nsSMILMilestone.h \
 	  nsSMILTimeContainer.h \
 	  nsSMILTypes.h \
 	  $(NULL)
 
 INCLUDES += 	\
 		-I$(srcdir)/../base/src \
 		-I$(srcdir)/../../layout/style \
+		-I$(srcdir)/../events/src \
 		$(NULL)
 endif # MOZ_SMIL
 
 include $(topsrcdir)/config/rules.mk
 
 DEFINES += -D_IMPL_NS_LAYOUT
new file mode 100644
--- /dev/null
+++ b/content/smil/nsDOMTimeEvent.cpp
@@ -0,0 +1,130 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Mozilla SMIL Module.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Brian Birtles <birtles@gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "nsDOMTimeEvent.h"
+#include "nsGUIEvent.h"
+#include "nsPresContext.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsIDOMAbstractView.h"
+
+nsDOMTimeEvent::nsDOMTimeEvent(nsPresContext* aPresContext, nsEvent* aEvent)
+  : nsDOMEvent(aPresContext, aEvent ? aEvent : new nsUIEvent(PR_FALSE, 0, 0)),
+    mDetail(0)
+{
+  if (aEvent) {
+    mEventIsInternal = PR_FALSE;
+  } else {
+    mEventIsInternal = PR_TRUE;
+    mEvent->eventStructType = NS_SMIL_TIME_EVENT;
+  }
+
+  if (mEvent->eventStructType == NS_SMIL_TIME_EVENT) {
+    nsUIEvent* event = static_cast<nsUIEvent*>(mEvent);
+    mDetail = event->detail;
+  }
+
+  mEvent->flags |= NS_EVENT_FLAG_CANT_BUBBLE |
+                   NS_EVENT_FLAG_CANT_CANCEL;
+
+  if (mPresContext) {
+    nsCOMPtr<nsISupports> container = mPresContext->GetContainer();
+    if (container) {
+      nsCOMPtr<nsIDOMWindowInternal> window = do_GetInterface(container);
+      if (window) {
+        mView = do_QueryInterface(window);
+      }
+    }
+  }
+}
+
+NS_IMPL_CYCLE_COLLECTION_CLASS(nsDOMTimeEvent)
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(nsDOMTimeEvent, nsDOMEvent)
+  NS_IMPL_CYCLE_COLLECTION_UNLINK_NSCOMPTR(mView)
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(nsDOMTimeEvent, nsDOMEvent)
+  NS_IMPL_CYCLE_COLLECTION_TRAVERSE_NSCOMPTR(mView)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_ADDREF_INHERITED(nsDOMTimeEvent, nsDOMEvent)
+NS_IMPL_RELEASE_INHERITED(nsDOMTimeEvent, nsDOMEvent)
+
+DOMCI_DATA(TimeEvent, nsDOMTimeEvent)
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(nsDOMTimeEvent)
+  NS_INTERFACE_MAP_ENTRY(nsIDOMTimeEvent)
+  NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(TimeEvent)
+NS_INTERFACE_MAP_END_INHERITING(nsDOMEvent)
+
+NS_IMETHODIMP
+nsDOMTimeEvent::GetView(nsIDOMAbstractView** aView)
+{
+  *aView = mView;
+  NS_IF_ADDREF(*aView);
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMTimeEvent::GetDetail(PRInt32* aDetail)
+{
+  *aDetail = mDetail;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDOMTimeEvent::InitTimeEvent(const nsAString& aTypeArg,
+                              nsIDOMAbstractView* aViewArg,
+                              PRInt32 aDetailArg)
+{
+  nsresult rv = nsDOMEvent::InitEvent(aTypeArg, PR_FALSE /*doesn't bubble*/,
+                                                PR_FALSE /*can't cancel*/);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  mDetail = aDetailArg;
+  mView = aViewArg;
+
+  return NS_OK;
+}
+
+nsresult NS_NewDOMTimeEvent(nsIDOMEvent** aInstancePtrResult,
+                            nsPresContext* aPresContext,
+                            nsEvent* aEvent)
+{
+  nsDOMTimeEvent* it = new nsDOMTimeEvent(aPresContext, aEvent);
+  return CallQueryInterface(it, aInstancePtrResult);
+}
new file mode 100644
--- /dev/null
+++ b/content/smil/nsDOMTimeEvent.h
@@ -0,0 +1,65 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Mozilla SMIL Module.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Brian Birtles <birtles@gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#ifndef NS_DOMTIMEEVENT_H_
+#define NS_DOMTIMEEVENT_H_
+
+#include "nsIDOMTimeEvent.h"
+#include "nsDOMEvent.h"
+
+class nsDOMTimeEvent : public nsDOMEvent,
+                       public nsIDOMTimeEvent
+{
+public:
+  nsDOMTimeEvent(nsPresContext* aPresContext, nsEvent* aEvent);
+                     
+  // nsISupports interface:
+  NS_DECL_ISUPPORTS_INHERITED
+  NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED(nsDOMTimeEvent, nsDOMEvent)
+
+  // nsIDOMTimeEvent interface:
+  NS_DECL_NSIDOMTIMEEVENT
+
+  // Forward to base class
+  NS_FORWARD_TO_NSDOMEVENT
+
+private:
+  nsCOMPtr<nsIDOMAbstractView> mView;
+  PRInt32                      mDetail;
+};
+
+#endif // NS_DOMTIMEEVENT_H_
--- a/content/smil/nsSMILTimedElement.cpp
+++ b/content/smil/nsSMILTimedElement.cpp
@@ -38,25 +38,29 @@
 #include "nsSMILTimedElement.h"
 #include "nsSMILAnimationFunction.h"
 #include "nsSMILTimeValue.h"
 #include "nsSMILTimeValueSpec.h"
 #include "nsSMILInstanceTime.h"
 #include "nsSMILParserUtils.h"
 #include "nsSMILTimeContainer.h"
 #include "nsGkAtoms.h"
+#include "nsGUIEvent.h"
+#include "nsEventDispatcher.h"
 #include "nsReadableUtils.h"
 #include "nsMathUtils.h"
+#include "nsThreadUtils.h"
+#include "nsIPresShell.h"
 #include "prdtoa.h"
 #include "plstr.h"
 #include "prtime.h"
 #include "nsString.h"
 
 //----------------------------------------------------------------------
-// Helper classes -- InstanceTimeComparator
+// Helper class: InstanceTimeComparator
 
 // Upon inserting an instance time into one of our instance time lists we assign
 // it a serial number. This allows us to sort the instance times in such a way
 // that where we have several equal instance times, the ones added later will
 // sort later. This means that when we call UpdateCurrentInterval during the
 // waiting state we won't unnecessarily change the begin instance.
 //
 // The serial number also means that every instance time has an unambiguous
@@ -86,16 +90,53 @@ nsSMILTimedElement::InstanceTimeComparat
   NS_ABORT_IF_FALSE(aElem1->Serial() && aElem2->Serial(),
       "Instance times have not been assigned serial numbers");
 
   PRInt8 cmp = aElem1->Time().CompareTo(aElem2->Time());
   return cmp == 0 ? aElem1->Serial() < aElem2->Serial() : cmp < 0;
 }
 
 //----------------------------------------------------------------------
+// Helper class: AsyncTimeEventRunner
+
+namespace
+{
+  class AsyncTimeEventRunner : public nsRunnable
+  {
+  protected:
+    nsRefPtr<nsIContent> mTarget;
+    PRUint32             mMsg;
+    PRInt32              mDetail;
+
+  public:
+    AsyncTimeEventRunner(nsIContent* aTarget, PRUint32 aMsg, PRInt32 aDetail)
+      : mTarget(aTarget), mMsg(aMsg), mDetail(aDetail)
+    {
+    }
+
+    NS_IMETHOD Run()
+    {
+      nsUIEvent event(PR_TRUE, mMsg, mDetail);
+      event.eventStructType = NS_SMIL_TIME_EVENT;
+
+      nsPresContext* context = nsnull;
+      nsIDocument* doc = mTarget->GetCurrentDoc();
+      if (doc) {
+        nsCOMPtr<nsIPresShell> shell = doc->GetShell();
+        if (shell) {
+          context = shell->GetPresContext();
+        }
+      }
+
+      return nsEventDispatcher::Dispatch(mTarget, context, &event);
+    }
+  };
+}
+
+//----------------------------------------------------------------------
 // Templated helper functions
 
 // Selectively remove elements from an array of type
 // nsTArray<nsRefPtr<nsSMILInstanceTime> > with O(n) performance.
 template <class TestFunctor>
 void
 nsSMILTimedElement::RemoveInstanceTimes(InstanceTimeList& aArray,
                                         TestFunctor& aTest)
@@ -145,16 +186,17 @@ nsSMILTimedElement::nsSMILTimedElement()
   mAnimationElement(nsnull),
   mFillMode(FILL_REMOVE),
   mRestartMode(RESTART_ALWAYS),
   mBeginSpecSet(PR_FALSE),
   mEndHasEventConditions(PR_FALSE),
   mInstanceSerialIndex(0),
   mClient(nsnull),
   mCurrentInterval(nsnull),
+  mCurrentRepeatIteration(0),
   mPrevRegisteredMilestone(sMaxMilestone),
   mElementState(STATE_STARTUP),
   mSeekState(SEEK_NOT_SEEKING)
 {
   mSimpleDur.SetIndefinite();
   mMin.SetMillis(0L);
   mMax.SetIndefinite();
   mTimeDependents.Init();
@@ -501,30 +543,31 @@ nsSMILTimedElement::DoSampleAt(nsSMILTim
       }
       break;
 
     case STATE_WAITING:
       {
         if (mCurrentInterval->Begin()->Time() <= sampleTime) {
           mElementState = STATE_ACTIVE;
           mCurrentInterval->FixBegin();
-          if (HasPlayed()) {
-            Reset(); // Apply restart behaviour
-          }
           if (mClient) {
             mClient->Activate(mCurrentInterval->Begin()->Time().GetMillis());
           }
+          if (mSeekState == SEEK_NOT_SEEKING) {
+            FireTimeEventAsync(NS_SMIL_BEGIN, 0);
+          }
           if (HasPlayed()) {
+            Reset(); // Apply restart behaviour
             // The call to Reset() may mean that the end point of our current
             // interval should be changed and so we should update the interval
             // now. However, calling UpdateCurrentInterval could result in the
             // interval getting deleted (perhaps through some web of syncbase
             // dependencies) therefore we make updating the interval the last
-            // thing we do. There is no guarantee that mCurrentInterval.IsSet()
-            // is true after this.
+            // thing we do. There is no guarantee that mCurrentInterval is set
+            // after this.
             UpdateCurrentInterval();
           }
           stateChanged = PR_TRUE;
         }
       }
       break;
 
     case STATE_ACTIVE:
@@ -536,31 +579,48 @@ nsSMILTimedElement::DoSampleAt(nsSMILTim
           mElementState =
             NS_SUCCEEDED(GetNextInterval(mCurrentInterval, nsnull, newInterval))
             ? STATE_WAITING
             : STATE_POSTACTIVE;
           if (mClient) {
             mClient->Inactivate(mFillMode == FILL_FREEZE);
           }
           mCurrentInterval->FixEnd();
+          if (mSeekState == SEEK_NOT_SEEKING) {
+            FireTimeEventAsync(NS_SMIL_END, 0);
+          }
+          mCurrentRepeatIteration = 0;
           mOldIntervals.AppendElement(mCurrentInterval.forget());
           // We must update mOldIntervals before calling SampleFillValue
           SampleFillValue();
           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);
+          // We register our repeat times as milestones (except when we're
+          // seeking) so we should get a sample at exactly the time we repeat.
+          // (And even when we are seeking we want to update
+          // mCurrentRepeatIteration so we do that first before testing the seek
+          // state.)
+          PRUint32 prevRepeatIteration = mCurrentRepeatIteration;
+          if (ActiveTimeToSimpleTime(activeTime, mCurrentRepeatIteration)==0 &&
+              mCurrentRepeatIteration != prevRepeatIteration &&
+              mCurrentRepeatIteration &&
+              mSeekState == SEEK_NOT_SEEKING) {
+            FireTimeEventAsync(NS_SMIL_REPEAT,
+                          static_cast<PRInt32>(mCurrentRepeatIteration));
+          }
         }
       }
       break;
 
     case STATE_POSTACTIVE:
       break;
     }
 
@@ -602,16 +662,17 @@ nsSMILTimedElement::Rewind()
 
   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;
+  mCurrentRepeatIteration = 0;
 
   // Clear the intervals and instance times except those instance times we can't
   // regenerate (DOM calls etc.)
   RewindTiming();
 
   UnsetBeginSpec();
   UnsetEndSpec();
 
@@ -1233,23 +1294,16 @@ nsSMILTimedElement::Reset()
 
   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.
@@ -1261,16 +1315,44 @@ 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;
+
+  case SEEK_FORWARD_FROM_INACTIVE:
+  case SEEK_BACKWARD_FROM_INACTIVE:
+    if (mElementState == STATE_ACTIVE) {
+      FireTimeEventAsync(NS_SMIL_BEGIN, 0);
+    }
+    break;
+  }
+
   mSeekState = SEEK_NOT_SEEKING;
 }
 
 void
 nsSMILTimedElement::UnpreserveInstanceTimes(InstanceTimeList& aList)
 {
   const nsSMILInterval* prevInterval = GetPreviousInterval();
   const nsSMILInstanceTime* cutoff = mCurrentInterval ?
@@ -1856,20 +1938,16 @@ nsSMILTimedElement::RegisterMilestone()
   mPrevRegisteredMilestone = nextMilestone;
 }
 
 PRBool
 nsSMILTimedElement::GetNextMilestone(nsSMILMilestone& aNextMilestone) const
 {
   // Return the next key moment in our lifetime.
   //
-  // XXX Once we implement TimeEvents and event based timing we might need to
-  // include repeat times too, particularly if it's important to get them in
-  // order.
-  //
   // XXX It may be possible in future to optimise this so that we only register
   // for milestones if:
   // a) We have time dependents, or
   // b) We are dependent on events or syncbase relationships, or
   // c) There are registered listeners for our events
   //
   // Then for the simple case where everything uses offset values we could
   // ignore milestones altogether.
@@ -1891,32 +1969,37 @@ nsSMILTimedElement::GetNextMilestone(nsS
     NS_ABORT_IF_FALSE(mCurrentInterval,
         "In waiting state but the current interval has not been set");
     aNextMilestone.mIsEnd = PR_FALSE;
     aNextMilestone.mTime = mCurrentInterval->Begin()->Time().GetMillis();
     return PR_TRUE;
 
   case STATE_ACTIVE:
     {
-      // XXX When we implement TimeEvents, we may need to consider what comes
-      // next: the interval end or an interval repeat.
+      // Work out what comes next: the interval end or the next repeat iteration
+      nsSMILTimeValue nextRepeat;
+      if (mSeekState == SEEK_NOT_SEEKING && mSimpleDur.IsResolved()) {
+        nextRepeat.SetMillis(mCurrentInterval->Begin()->Time().GetMillis() +
+            (mCurrentRepeatIteration + 1) * mSimpleDur.GetMillis());
+      }
+      nsSMILTimeValue nextMilestone =
+        NS_MIN(mCurrentInterval->End()->Time(), nextRepeat);
 
-      // Check for an early end
-      nsSMILInstanceTime* earlyEnd =
-        CheckForEarlyEnd(mCurrentInterval->End()->Time());
+      // Check for an early end before that time
+      nsSMILInstanceTime* earlyEnd = CheckForEarlyEnd(nextMilestone);
       if (earlyEnd) {
         aNextMilestone.mIsEnd = PR_TRUE;
         aNextMilestone.mTime = earlyEnd->Time().GetMillis();
         return PR_TRUE;
       }
 
-      // Otherwise it's just the next interval end
-      if (mCurrentInterval->End()->Time().IsResolved()) {
-        aNextMilestone.mIsEnd = PR_TRUE;
-        aNextMilestone.mTime = mCurrentInterval->End()->Time().GetMillis();
+      // Apply the previously calculated milestone
+      if (nextMilestone.IsResolved()) {
+        aNextMilestone.mIsEnd = nextMilestone != nextRepeat;
+        aNextMilestone.mTime = nextMilestone.GetMillis();
         return PR_TRUE;
       }
 
       return PR_FALSE;
     }
 
   case STATE_POSTACTIVE:
     return PR_FALSE;
@@ -1953,16 +2036,27 @@ nsSMILTimedElement::NotifyChangedInterva
   nsSMILTimeContainer* container = GetTimeContainer();
   if (container) {
     container->SyncPauseTime();
   }
 
   mCurrentInterval->NotifyChanged(container);
 }
 
+void
+nsSMILTimedElement::FireTimeEventAsync(PRUint32 aMsg, PRInt32 aDetail)
+{
+  if (!mAnimationElement)
+    return;
+
+  nsCOMPtr<nsIRunnable> event =
+    new AsyncTimeEventRunner(&mAnimationElement->Content(), aMsg, aDetail);
+  NS_DispatchToMainThread(event, NS_DISPATCH_NORMAL);
+}
+
 const nsSMILInstanceTime*
 nsSMILTimedElement::GetEffectiveBeginInstance() const
 {
   switch (mElementState)
   {
   case STATE_STARTUP:
     return nsnull;
 
--- a/content/smil/nsSMILTimedElement.h
+++ b/content/smil/nsSMILTimedElement.h
@@ -477,16 +477,17 @@ protected:
   void              SampleFillValue();
   void              AddInstanceTimeFromCurrentTime(nsSMILTime aCurrentTime,
                         double aOffsetSeconds, PRBool aIsBegin);
   void              RegisterMilestone();
   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(); }
 
   // Hashtable callback methods
   PR_STATIC_CALLBACK(PLDHashOperator) NotifyNewIntervalCallback(
       TimeValueSpecPtrKey* aKey, void* aData);
 
@@ -534,16 +535,17 @@ protected:
 
   InstanceTimeList                mBeginInstances;
   InstanceTimeList                mEndInstances;
   PRUint32                        mInstanceSerialIndex;
 
   nsSMILAnimationFunction*        mClient;
   nsAutoPtr<nsSMILInterval>       mCurrentInterval;
   IntervalList                    mOldIntervals;
+  PRUint32                        mCurrentRepeatIteration;
   nsSMILMilestone                 mPrevRegisteredMilestone;
   static const nsSMILMilestone    sMaxMilestone;
   static const PRUint8            sMaxNumIntervals;
   static const PRUint8            sMaxNumInstanceTimes;
 
   // Set of dependent time value specs to be notified when establishing a new
   // current interval. Change notifications and delete notifications are handled
   // by the interval.
--- a/content/smil/test/Makefile.in
+++ b/content/smil/test/Makefile.in
@@ -77,16 +77,17 @@ include $(topsrcdir)/config/rules.mk
 	  test_smilGetSimpleDuration.xhtml \
 	  test_smilKeySplines.xhtml \
 	  test_smilKeyTimesPacedMode.xhtml \
 	  test_smilSetCurrentTime.xhtml \
 	  test_smilSync.xhtml \
 	  test_smilSyncbaseTarget.xhtml \
 	  test_smilSyncTransform.xhtml \
 	  test_smilTextZoom.xhtml \
+	  test_smilTimeEvents.xhtml \
 	  test_smilTiming.xhtml \
 	  test_smilTimingZeroIntervals.xhtml \
 	  test_smilUpdatedInterval.xhtml \
 	  test_smilXHR.xhtml \
 		$(NULL)
 
 libs:: $(_TEST_FILES)
 	$(INSTALL) $^ $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)
new file mode 100644
--- /dev/null
+++ b/content/smil/test/test_smilTimeEvents.xhtml
@@ -0,0 +1,292 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+<!--
+https://bugzilla.mozilla.org/show_bug.cgi?id=572270
+-->
+<head>
+  <title>Test TimeEvents dispatching</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>
+<a target="_blank"
+  href="https://bugzilla.mozilla.org/show_bug.cgi?id=572270">Mozilla Bug
+  572270</a>
+<p id="display"></p>
+<div id="content" style="display: none">
+<svg id="svg" xmlns="http://www.w3.org/2000/svg" width="100px" height="100px">
+  <g font-size="10px">
+    <circle cx="0" cy="0" r="15" fill="blue" id="circle"
+      onbegin="parentHandler(evt)" onrepeat="parentHandler(evt)"
+      onend="parentHandler(evt)">
+      <animate attributeName="cy" from="0" to="100" dur="60s" begin="2s"
+        id="anim" repeatCount="2"
+        onbegin="handleOnBegin(evt)" onrepeat="handleOnRepeat(evt)"
+        onend="handleOnEnd(evt)"/>
+    </circle>
+  </g>
+</svg>
+</div>
+<pre id="test">
+<script class="testbody" type="text/javascript">
+<![CDATA[
+/** Test SMIL TimeEvents dispatching **/
+
+/* Global Variables */
+const gTimeoutDur = 5000; // Time until we give up waiting for events in ms
+var gSvg    = document.getElementById("svg");
+var gAnim   = document.getElementById('anim');
+var gCircle = document.getElementById('circle');
+var gExpectedEvents = new Array();
+var gTimeoutID;
+var gTestStages =
+  [ testPlaybackBegin,
+    testPlaybackRepeat,
+    testPlaybackEnd,
+    testForwardsSeekToMid,
+    testForwardsSeekToNextInterval,
+    testForwardsSeekPastEnd,
+    testBackwardsSeekToMid,
+    testBackwardsSeekToStart,
+    testCreateEvent,
+    testRegistration
+    ];
+
+SimpleTest.waitForExplicitFinish();
+
+function continueTest()
+{
+  if (gTestStages.length == 0) {
+    SimpleTest.finish();
+    return;
+  }
+  gTestStages.shift()();
+}
+
+function testPlaybackBegin()
+{
+  // Test events are dispatched through normal playback
+  gSvg.pauseAnimations();
+  gSvg.setCurrentTime(1.99);
+  gExpectedEvents.push("beginEvent", "beginEvent"); // Two registered handlers
+  gTimeoutID = setTimeout(timeoutFail, gTimeoutDur);
+  gSvg.unpauseAnimations();
+}
+
+function testPlaybackRepeat()
+{
+  gSvg.pauseAnimations();
+  gSvg.setCurrentTime(61.99);
+  gExpectedEvents.push(["repeatEvent", 1], ["repeatEvent", 1]);
+  gTimeoutID = setTimeout(timeoutFail, gTimeoutDur);
+  gSvg.unpauseAnimations();
+}
+
+function testPlaybackEnd()
+{
+  gSvg.pauseAnimations();
+  gSvg.setCurrentTime(121.99);
+  gExpectedEvents.push("endEvent", "endEvent");
+  gTimeoutID = setTimeout(timeoutFail, gTimeoutDur);
+  gSvg.unpauseAnimations();
+}
+
+function testForwardsSeekToMid()
+{
+  gSvg.pauseAnimations();
+  // Set animation parameters to something that repeats a lot
+  gSvg.setCurrentTime(0);
+  gAnim.setAttribute('begin', '2s; 102s');
+  gAnim.setAttribute('dur', '15s');
+  gAnim.setAttribute('repeatCount', '6');
+  gSvg.setCurrentTime(46.99);
+  gExpectedEvents.push("beginEvent", "beginEvent",
+                       ["repeatEvent", 3], ["repeatEvent", 3]);
+  gTimeoutID = setTimeout(timeoutFail, gTimeoutDur);
+  gSvg.unpauseAnimations();
+}
+
+function testForwardsSeekToNextInterval()
+{
+  // Skip to next interval -- we shouldn't get any additional begin or end
+  // events in between
+  gSvg.pauseAnimations();
+  gSvg.setCurrentTime(131.99);
+  gExpectedEvents.push(["repeatEvent", 2], ["repeatEvent", 2]);
+  gTimeoutID = setTimeout(timeoutFail, gTimeoutDur);
+  gSvg.unpauseAnimations();
+}
+
+function testForwardsSeekPastEnd()
+{
+  gSvg.pauseAnimations();
+  gSvg.setCurrentTime(200);
+  gExpectedEvents.push("endEvent", "endEvent");
+  gTimeoutID = setTimeout(timeoutFail, gTimeoutDur);
+  gSvg.unpauseAnimations();
+}
+
+function testBackwardsSeekToMid()
+{
+  gSvg.pauseAnimations();
+  gSvg.setCurrentTime(31.99);
+  gExpectedEvents.push("beginEvent", "beginEvent",
+                       ["repeatEvent", 2], ["repeatEvent", 2]);
+  gTimeoutID = setTimeout(timeoutFail, gTimeoutDur);
+  gSvg.unpauseAnimations();
+}
+
+function testBackwardsSeekToStart()
+{
+  gSvg.pauseAnimations();
+  gExpectedEvents.push("endEvent", "endEvent");
+  gTimeoutID = setTimeout(timeoutFail, gTimeoutDur);
+  gSvg.setCurrentTime(0);
+}
+
+function testCreateEvent()
+{
+  var evt;
+  try {
+    evt = document.createEvent("TimeEvents");
+  } catch (e) {
+    ok(false, "Failed to create TimeEvent via script: " + e);
+    return;
+  }
+  evt.initTimeEvent("repeatEvent", null, 3);
+  is(evt.type, "repeatEvent", "Unexpected type for user-generated event");
+  is(evt.detail, 3, "Unexpected detail for user-generated event");
+  is(evt.target, null, "Unexpected event target");
+  is(evt.currentTarget, null, "Unexpected event current target");
+  is(evt.eventPhase, evt.AT_TARGET);
+  is(evt.bubbles, false, "Event should not bubble");
+  is(evt.cancelable, false, "Event should not be cancelable");
+  is(evt.view, null, "Event view should be null");
+
+  // Prior to dispatch we should be able to change the event type
+  evt.initTimeEvent("beginEvent", document.defaultView, 0);
+  is(evt.type, "beginEvent", "Failed to update event type before dispatch");
+  is(evt.detail, 0, "Failed to update event detail before dispatch");
+  is(evt.view, document.defaultView, "Event view should be set");
+
+  // But not directly as it's readonly
+  try {
+    evt.type = "endEvent";
+  } catch(e) { }
+  is(evt.type, "beginEvent", "Event type should be readonly");
+
+  // Likewise the detail field should be readonly
+  try {
+    evt.detail = "8";
+  } catch(e) { }
+  is(evt.detail, 0, "Event detail should be readonly");
+
+  // Dispatch
+  gExpectedEvents.push("beginEvent", "beginEvent");
+  gTimeoutID = setTimeout(timeoutFail, gTimeoutDur);
+  gAnim.dispatchEvent(evt);
+}
+
+function testRegistration()
+{
+  gSvg.pauseAnimations();
+  // Reset animation to something simple
+  gSvg.setCurrentTime(0);
+  gAnim.setAttribute('begin', '2s');
+  gAnim.setAttribute('dur', '50s');
+
+  // Remove attribute handler
+  gAnim.removeAttribute('onbegin');
+
+  // Add bogus handlers
+  gAnim.setAttribute('onbeginElement', 'handleOnBegin(evt)');
+  gAnim.addEventListener("begin", handleOnBegin, false);
+  gAnim.addEventListener("onbegin", handleOnBegin, false);
+
+  // We should now have just one legitimate listener: the one registered to
+  // handle 'beginElement'
+  gSvg.setCurrentTime(1.99);
+  gExpectedEvents.push("beginEvent");
+  gTimeoutID = setTimeout(timeoutFail, gTimeoutDur);
+  gSvg.unpauseAnimations();
+}
+
+function handleOnBegin(evt)
+{
+  is(evt.type, "beginEvent", "Expected begin event but got " + evt.type);
+  checkExpectedEvent(evt);
+}
+
+function handleOnRepeat(evt)
+{
+  is(evt.type, "repeatEvent", "Expected repeat event but got " + evt.type);
+  checkExpectedEvent(evt);
+}
+
+function handleOnEnd(evt)
+{
+  is(evt.type, "endEvent", "Expected end event but got " + evt.type);
+  checkExpectedEvent(evt);
+}
+
+function sanityCheckEvent(evt)
+{
+  is(evt.target, gAnim, "Unexpected event target");
+  is(evt.currentTarget, gAnim, "Unexpected event current target");
+  is(evt.eventPhase, evt.AT_TARGET);
+  is(evt.bubbles, false, "Event should not bubble");
+  is(evt.cancelable, false, "Event should not be cancelable");
+  // Currently we set event timestamps to 0 which DOM 2 allows. This isn't
+  // correct since SMIL uses this field to avoid synchronisation slew but first
+  // we need to fix bug 323039 and bug 77992 which involve storing timestamps as
+  // 64-bit integers and deciding whether those timestamps should be related to
+  // the epoch or system start.
+  is(evt.timeStamp, 0, "Event timeStamp should be 0");
+  ok(evt.view !== null, "Event view not set");
+}
+
+function checkExpectedEvent(evt)
+{
+  sanityCheckEvent(evt);
+  ok(gExpectedEvents.length > 0, "Unexpected event: " + evt.type);
+  if (gExpectedEvents.length == 0) return;
+
+  var expected = gExpectedEvents.shift();
+  if (typeof expected == 'string') {
+    is(evt.type, expected, "Unexpected event type");
+    is(evt.detail, 0, "Unexpected event detail (repeat iteration)");
+  } else {
+    is(evt.type, expected[0], "Unexpected event type");
+    is(evt.detail, expected[1], "Unexpected event detail (repeat iteration)");
+  }
+  if (gExpectedEvents.length == 0) {
+    clearTimeout(gTimeoutID);
+    continueTest();
+  }
+}
+
+function timeoutFail()
+{
+  ok(false, "Timed out waiting for events: " + gExpectedEvents.join(', '));
+  SimpleTest.finish(); // No point continuing
+}
+
+function parentHandler(evt)
+{
+  ok(false, "Handler on parent got called but event shouldn't bubble.");
+}
+
+window.addEventListener("load", continueTest, false);
+
+// Register event handlers *in addition* to the handlers already added via the
+// "onbegin", "onend", "onrepeat" attributes on the <animate> and <circle>
+// elements. This is to test that both types of registration work.
+gAnim.addEventListener("beginEvent", handleOnBegin, false);
+gAnim.addEventListener("repeatEvent", handleOnRepeat, false);
+gAnim.addEventListener("endEvent", handleOnEnd, false);
+gCircle.addEventListener("beginEvent", parentHandler, false);
+]]>
+</script>
+</pre>
+</body>
+</html>
--- a/content/svg/content/src/nsSVGAnimationElement.cpp
+++ b/content/svg/content/src/nsSVGAnimationElement.cpp
@@ -471,16 +471,22 @@ nsSVGAnimationElement::EndElementAt(floa
   if (NS_FAILED(rv))
     return rv;
 
   AnimationNeedsResample();
  
   return NS_OK;
 }
 
+PRBool
+nsSVGAnimationElement::IsEventName(nsIAtom* aName)
+{
+  return nsContentUtils::IsEventAttributeName(aName, EventNameType_SMIL);
+}
+
 void
 nsSVGAnimationElement::UpdateHrefTarget(nsIContent* aNodeForContext,
                                         const nsAString& aHrefStr)
 {
   nsCOMPtr<nsIURI> targetURI;
   nsCOMPtr<nsIURI> baseURI = GetBaseURI();
   nsContentUtils::NewURIWithDocumentCharset(getter_AddRefs(targetURI),
                                             aHrefStr, GetOwnerDoc(), baseURI);
--- a/content/svg/content/src/nsSVGAnimationElement.h
+++ b/content/svg/content/src/nsSVGAnimationElement.h
@@ -93,16 +93,19 @@ public:
   virtual PRBool HasAnimAttr(nsIAtom* aAttName) const;
   virtual mozilla::dom::Element* GetTargetElementContent();
   virtual nsIAtom* GetTargetAttributeName() const;
   virtual nsSMILTargetAttrType GetTargetAttributeType() const;
   virtual nsSMILTimedElement& TimedElement();
   virtual nsSMILTimeContainer* GetTimeContainer();
 
 protected:
+  // nsSVGElement overrides
+  PRBool IsEventName(nsIAtom* aName);
+
   void UpdateHrefTarget(nsIContent* aNodeForContext,
                         const nsAString& aHrefStr);
 
   class TargetReference : public nsReferencedElement {
   public:
     TargetReference(nsSVGAnimationElement* aAnimationElement) :
       mAnimationElement(aAnimationElement) {}
   protected:
--- a/content/svg/content/src/nsSVGElement.cpp
+++ b/content/svg/content/src/nsSVGElement.cpp
@@ -1359,16 +1359,24 @@ nsIAtom* nsSVGElement::GetEventNameForAt
   if (aAttr == nsGkAtoms::onerror)
     return nsGkAtoms::onSVGError;
   if (aAttr == nsGkAtoms::onresize)
     return nsGkAtoms::onSVGResize;
   if (aAttr == nsGkAtoms::onscroll)
     return nsGkAtoms::onSVGScroll;
   if (aAttr == nsGkAtoms::onzoom)
     return nsGkAtoms::onSVGZoom;
+#ifdef MOZ_SMIL
+  if (aAttr == nsGkAtoms::onbegin)
+    return nsGkAtoms::onbeginEvent;
+  if (aAttr == nsGkAtoms::onrepeat)
+    return nsGkAtoms::onrepeatEvent;
+  if (aAttr == nsGkAtoms::onend)
+    return nsGkAtoms::onendEvent;
+#endif // MOZ_SMIL
 
   return aAttr;
 }
 
 nsSVGSVGElement *
 nsSVGElement::GetCtx()
 {
   nsCOMPtr<nsIDOMSVGSVGElement> svg;
--- a/dom/base/nsDOMClassInfo.cpp
+++ b/dom/base/nsDOMClassInfo.cpp
@@ -372,16 +372,17 @@
 #ifdef MOZ_SMIL
 #include "nsIDOMSVGAnimateElement.h"
 #include "nsIDOMSVGAnimateTransformElement.h"
 #include "nsIDOMSVGAnimateMotionElement.h"
 #include "nsIDOMSVGMpathElement.h"
 #include "nsIDOMSVGSetElement.h"
 #include "nsIDOMSVGAnimationElement.h"
 #include "nsIDOMElementTimeControl.h"
+#include "nsIDOMTimeEvent.h"
 #endif // MOZ_SMIL
 #include "nsIDOMSVGAnimTransformList.h"
 #include "nsIDOMSVGCircleElement.h"
 #include "nsIDOMSVGClipPathElement.h"
 #include "nsIDOMSVGDefsElement.h"
 #include "nsIDOMSVGDescElement.h"
 #include "nsIDOMSVGDocument.h"
 #include "nsIDOMSVGElement.h"
@@ -998,16 +999,18 @@ static nsDOMClassInfoData sClassInfoData
   NS_DEFINE_CLASSINFO_DATA(SVGAnimateTransformElement, nsElementSH,
                            ELEMENT_SCRIPTABLE_FLAGS)
   NS_DEFINE_CLASSINFO_DATA(SVGAnimateMotionElement, nsElementSH,
                            ELEMENT_SCRIPTABLE_FLAGS)
   NS_DEFINE_CLASSINFO_DATA(SVGMpathElement, nsElementSH,
                            ELEMENT_SCRIPTABLE_FLAGS)
   NS_DEFINE_CLASSINFO_DATA(SVGSetElement, nsElementSH,
                            ELEMENT_SCRIPTABLE_FLAGS)
+  NS_DEFINE_CLASSINFO_DATA(TimeEvent, nsDOMGenericSH,
+                           DOM_DEFAULT_SCRIPTABLE_FLAGS)
 #endif // MOZ_SMIL
   NS_DEFINE_CLASSINFO_DATA(SVGCircleElement, nsElementSH,
                            ELEMENT_SCRIPTABLE_FLAGS)
   NS_DEFINE_CLASSINFO_DATA(SVGClipPathElement, nsElementSH,
                            ELEMENT_SCRIPTABLE_FLAGS)
   NS_DEFINE_CLASSINFO_DATA(SVGDefsElement, nsElementSH,
                            ELEMENT_SCRIPTABLE_FLAGS)
   NS_DEFINE_CLASSINFO_DATA(SVGDescElement, nsElementSH,
@@ -3036,16 +3039,20 @@ nsDOMClassInfo::Init()
     DOM_CLASSINFO_SVG_ELEMENT_MAP_ENTRIES
   DOM_CLASSINFO_MAP_END
 
   DOM_CLASSINFO_MAP_BEGIN(SVGMpathElement, nsIDOMSVGMpathElement)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMSVGURIReference)
     DOM_CLASSINFO_SVG_ELEMENT_MAP_ENTRIES
   DOM_CLASSINFO_MAP_END
 
+  DOM_CLASSINFO_MAP_BEGIN(TimeEvent, nsIDOMTimeEvent)
+    DOM_CLASSINFO_MAP_ENTRY(nsIDOMTimeEvent)
+    DOM_CLASSINFO_EVENT_MAP_ENTRIES
+  DOM_CLASSINFO_MAP_END
 #endif // MOZ_SMIL
 
   DOM_CLASSINFO_MAP_BEGIN(SVGCircleElement, nsIDOMSVGCircleElement)
     DOM_CLASSINFO_MAP_ENTRY(nsIDOMSVGCircleElement)
     DOM_CLASSINFO_SVG_GRAPHIC_ELEMENT_MAP_ENTRIES
   DOM_CLASSINFO_MAP_END
 
   DOM_CLASSINFO_MAP_BEGIN(SVGClipPathElement, nsIDOMSVGClipPathElement)
--- a/dom/base/nsDOMClassInfoClasses.h
+++ b/dom/base/nsDOMClassInfoClasses.h
@@ -235,16 +235,17 @@ DOMCI_CLASS(SVGDocument)
 DOMCI_CLASS(SVGAElement)
 DOMCI_CLASS(SVGAltGlyphElement)
 #ifdef MOZ_SMIL
 DOMCI_CLASS(SVGAnimateElement)
 DOMCI_CLASS(SVGAnimateTransformElement)
 DOMCI_CLASS(SVGAnimateMotionElement)
 DOMCI_CLASS(SVGMpathElement)
 DOMCI_CLASS(SVGSetElement)
+DOMCI_CLASS(TimeEvent)
 #endif // MOZ_SMIL
 DOMCI_CLASS(SVGCircleElement)
 DOMCI_CLASS(SVGClipPathElement)
 DOMCI_CLASS(SVGDefsElement)
 DOMCI_CLASS(SVGDescElement)
 DOMCI_CLASS(SVGEllipseElement)
 DOMCI_CLASS(SVGFEBlendElement)
 DOMCI_CLASS(SVGFEColorMatrixElement)
--- a/dom/interfaces/smil/Makefile.in
+++ b/dom/interfaces/smil/Makefile.in
@@ -43,11 +43,12 @@ VPATH		= @srcdir@
 
 include $(DEPTH)/config/autoconf.mk
 
 MODULE		= dom
 XPIDL_MODULE	= dom_smil
 
 XPIDLSRCS	= \
 		nsIDOMElementTimeControl.idl \
+		nsIDOMTimeEvent.idl \
 		$(NULL)
 
 include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/dom/interfaces/smil/nsIDOMTimeEvent.idl
@@ -0,0 +1,57 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is the Mozilla SMIL Module.
+ *
+ * The Initial Developer of the Original Code is Mozilla Foundation.
+ * Portions created by the Initial Developer are Copyright (C) 2010
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Brian Birtles <birtles@gmail.com>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+#include "nsIDOMEvent.idl"
+
+/**
+ * The SMIL TimeEvent interface.
+ *
+ * For more information please refer to:
+ * http://www.w3.org/TR/SMIL/smil-timing.html#Events-TimeEvent
+ * http://www.w3.org/TR/SVG/animate.html#InterfaceTimeEvent
+ */
+
+[scriptable, uuid(0d309c26-ddbb-44cb-9af1-3008972349e3)]
+interface nsIDOMTimeEvent : nsIDOMEvent
+{
+  readonly attribute long detail;
+  readonly attribute nsIDOMAbstractView view;
+  
+  void initTimeEvent(in DOMString typeArg,
+                     in nsIDOMAbstractView viewArg,
+                     in long detailArg);
+};
--- a/widget/public/nsGUIEvent.h
+++ b/widget/public/nsGUIEvent.h
@@ -96,16 +96,19 @@ class nsHashKey;
 #define NS_SCROLLAREA_EVENT               25
 #define NS_TRANSITION_EVENT               26
 
 #define NS_UI_EVENT                       27
 #ifdef MOZ_SVG
 #define NS_SVG_EVENT                      30
 #define NS_SVGZOOM_EVENT                  31
 #endif // MOZ_SVG
+#ifdef MOZ_SMIL
+#define NS_SMIL_TIME_EVENT                32
+#endif // MOZ_SMIL
 
 #define NS_QUERY_CONTENT_EVENT            33
 
 #define NS_DRAG_EVENT                     35
 #define NS_NOTIFYPAINT_EVENT              36
 #define NS_SIMPLE_GESTURE_EVENT           37
 #define NS_SELECTION_EVENT                38
 #define NS_CONTENT_COMMAND_EVENT          39
@@ -455,16 +458,23 @@ class nsHashKey;
 #define NS_ORIENTATION_EVENT         4000
 
 #define NS_SCROLLAREA_EVENT_START    4100
 #define NS_SCROLLEDAREACHANGED       (NS_SCROLLAREA_EVENT_START)
 
 #define NS_TRANSITION_EVENT_START    4200
 #define NS_TRANSITION_END            (NS_TRANSITION_EVENT_START)
 
+#ifdef MOZ_SMIL
+#define NS_SMIL_TIME_EVENT_START     4300
+#define NS_SMIL_BEGIN                (NS_SMIL_TIME_EVENT_START)
+#define NS_SMIL_END                  (NS_SMIL_TIME_EVENT_START + 1)
+#define NS_SMIL_REPEAT               (NS_SMIL_TIME_EVENT_START + 2)
+#endif // MOZ_SMIL
+
 /**
  * Return status for event processors, nsEventStatus, is defined in
  * nsEvent.h.
  */
 
 /**
  * different types of (top-level) window z-level positioning
  */