Bug 848293 - Update AnimationEvent to be compatible with the spec, r=dbaron
authorOlli Pettay <Olli.Pettay@helsinki.fi>
Sun, 05 May 2013 16:22:29 +0300
changeset 130891 cdc859533b8d767d143f9f7671b96ba06bd4b1a0
parent 130890 39c44b2b1ef52335e714dec37ed4fed734aa4337
child 130892 3370e2c73ceaa575ba4afe19a70ea09e8ad75002
push id24637
push userphilringnalda@gmail.com
push dateMon, 06 May 2013 00:15:12 +0000
treeherdermozilla-central@b109e2dbf03b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdbaron
bugs848293
milestone23.0a1
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 848293 - Update AnimationEvent to be compatible with the spec, r=dbaron
content/events/src/nsDOMAnimationEvent.cpp
content/events/src/nsDOMAnimationEvent.h
content/events/src/nsDOMEvent.cpp
dom/interfaces/events/nsIDOMAnimationEvent.idl
dom/webidl/AnimationEvent.webidl
layout/style/nsAnimationManager.cpp
layout/style/nsAnimationManager.h
layout/style/test/test_animations.html
widget/nsGUIEvent.h
--- a/content/events/src/nsDOMAnimationEvent.cpp
+++ b/content/events/src/nsDOMAnimationEvent.cpp
@@ -10,17 +10,18 @@
 #include "nsIXPCScriptable.h"
 
 nsDOMAnimationEvent::nsDOMAnimationEvent(mozilla::dom::EventTarget* aOwner,
                                          nsPresContext *aPresContext,
                                          nsAnimationEvent *aEvent)
   : nsDOMEvent(aOwner, aPresContext,
                aEvent ? aEvent : new nsAnimationEvent(false, 0,
                                                       EmptyString(),
-                                                      0.0))
+                                                      0.0,
+                                                      EmptyString()))
 {
   if (aEvent) {
     mEventIsInternal = false;
   }
   else {
     mEventIsInternal = true;
     mEvent->time = PR_Now();
   }
@@ -40,43 +41,68 @@ DOMCI_DATA(AnimationEvent, nsDOMAnimatio
 NS_INTERFACE_MAP_BEGIN(nsDOMAnimationEvent)
   NS_INTERFACE_MAP_ENTRY(nsIDOMAnimationEvent)
   NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(AnimationEvent)
 NS_INTERFACE_MAP_END_INHERITING(nsDOMEvent)
 
 NS_IMPL_ADDREF_INHERITED(nsDOMAnimationEvent, nsDOMEvent)
 NS_IMPL_RELEASE_INHERITED(nsDOMAnimationEvent, nsDOMEvent)
 
+//static
+already_AddRefed<nsDOMAnimationEvent>
+nsDOMAnimationEvent::Constructor(const mozilla::dom::GlobalObject& aGlobal,
+                                 const nsAString& aType,
+                                 const mozilla::dom::AnimationEventInit& aParam,
+                                 mozilla::ErrorResult& aRv)
+{
+  nsCOMPtr<mozilla::dom::EventTarget> t = do_QueryInterface(aGlobal.Get());
+  nsRefPtr<nsDOMAnimationEvent> e = new nsDOMAnimationEvent(t, nullptr, nullptr);
+  bool trusted = e->Init(t);
+  aRv = e->InitAnimationEvent(aType, aParam.mBubbles, aParam.mCancelable,
+                              aParam.mAnimationName, aParam.mElapsedTime,
+                              aParam.mPseudoElement);
+  e->SetTrusted(trusted);
+  return e.forget();
+}
+
 NS_IMETHODIMP
 nsDOMAnimationEvent::GetAnimationName(nsAString & aAnimationName)
 {
   aAnimationName = AnimationEvent()->animationName;
   return NS_OK;
 }
 
 NS_IMETHODIMP
 nsDOMAnimationEvent::GetElapsedTime(float *aElapsedTime)
 {
   *aElapsedTime = ElapsedTime();
   return NS_OK;
 }
 
 NS_IMETHODIMP
+nsDOMAnimationEvent::GetPseudoElement(nsAString& aPseudoElement)
+{
+  aPseudoElement = AnimationEvent()->pseudoElement;
+  return NS_OK;
+}
+
+NS_IMETHODIMP
 nsDOMAnimationEvent::InitAnimationEvent(const nsAString & typeArg,
                                         bool canBubbleArg,
                                         bool cancelableArg,
                                         const nsAString & animationNameArg,
-                                        float elapsedTimeArg)
+                                        float elapsedTimeArg,
+                                        const nsAString & pseudoElementArg)
 {
   nsresult rv = nsDOMEvent::InitEvent(typeArg, canBubbleArg, cancelableArg);
   NS_ENSURE_SUCCESS(rv, rv);
 
   AnimationEvent()->animationName = animationNameArg;
   AnimationEvent()->elapsedTime = elapsedTimeArg;
-
+  AnimationEvent()->pseudoElement = pseudoElementArg;
   return NS_OK;
 }
 
 nsresult
 NS_NewDOMAnimationEvent(nsIDOMEvent **aInstancePtrResult,
                         mozilla::dom::EventTarget* aOwner,
                         nsPresContext *aPresContext,
                         nsAnimationEvent *aEvent)
--- a/content/events/src/nsDOMAnimationEvent.h
+++ b/content/events/src/nsDOMAnimationEvent.h
@@ -20,39 +20,48 @@ public:
                       nsPresContext *aPresContext,
                       nsAnimationEvent *aEvent);
   ~nsDOMAnimationEvent();
 
   NS_DECL_ISUPPORTS_INHERITED
   NS_FORWARD_TO_NSDOMEVENT
   NS_DECL_NSIDOMANIMATIONEVENT
 
+  static already_AddRefed<nsDOMAnimationEvent>
+  Constructor(const mozilla::dom::GlobalObject& aGlobal,
+              const nsAString& aType,
+              const mozilla::dom::AnimationEventInit& aParam,
+              mozilla::ErrorResult& aRv);
+
   virtual JSObject* WrapObject(JSContext* aCx,
-			       JS::Handle<JSObject*> aScope) MOZ_OVERRIDE
+                               JS::Handle<JSObject*> aScope) MOZ_OVERRIDE
   {
     return mozilla::dom::AnimationEventBinding::Wrap(aCx, aScope, this);
   }
 
   // xpidl implementation
   // GetAnimationName(nsAString& aAnimationName);
+  // GetPseudoElement(nsAString& aPseudoElement);
 
   float ElapsedTime()
   {
     return AnimationEvent()->elapsedTime;
   }
 
   void InitAnimationEvent(const nsAString& aType,
                           bool aCanBubble,
                           bool aCancelable,
                           const nsAString& aAnimationName,
                           float aElapsedTime,
+                          const mozilla::dom::Optional<nsAString>& aPseudoElement,
                           mozilla::ErrorResult& aRv)
   {
     aRv = InitAnimationEvent(aType, aCanBubble, aCancelable, aAnimationName,
-                             aElapsedTime);
+                             aElapsedTime, aPseudoElement.WasPassed() ?
+                               aPseudoElement.Value() : EmptyString());
   }
 private:
   nsAnimationEvent* AnimationEvent() {
     NS_ABORT_IF_FALSE(mEvent->eventStructType == NS_ANIMATION_EVENT,
                       "unexpected struct type");
     return static_cast<nsAnimationEvent*>(mEvent);
   }
 };
--- a/content/events/src/nsDOMEvent.cpp
+++ b/content/events/src/nsDOMEvent.cpp
@@ -835,17 +835,18 @@ nsDOMEvent::DuplicatePrivateData()
       break;
     }
     case NS_ANIMATION_EVENT:
     {
       nsAnimationEvent* oldAnimationEvent =
         static_cast<nsAnimationEvent*>(mEvent);
       newEvent = new nsAnimationEvent(false, msg,
                                       oldAnimationEvent->animationName,
-                                      oldAnimationEvent->elapsedTime);
+                                      oldAnimationEvent->elapsedTime,
+                                      oldAnimationEvent->pseudoElement);
       NS_ENSURE_TRUE(newEvent, NS_ERROR_OUT_OF_MEMORY);
       break;
     }
     case NS_TOUCH_EVENT:
     {
       nsTouchEvent *oldTouchEvent = static_cast<nsTouchEvent*>(mEvent);
       newEvent = new nsTouchEvent(false, oldTouchEvent);
       NS_ENSURE_TRUE(newEvent, NS_ERROR_OUT_OF_MEMORY);
--- a/dom/interfaces/events/nsIDOMAnimationEvent.idl
+++ b/dom/interfaces/events/nsIDOMAnimationEvent.idl
@@ -6,18 +6,20 @@
 #include "nsIDOMEvent.idl"
 
 /**
  * Animation events are defined in:
  * http://www.w3.org/TR/css3-animations/#animation-events-
  * http://dev.w3.org/csswg/css3-animations/#animation-events-
  */
 
-[scriptable, builtinclass, uuid(778a701a-e8f2-4cfd-b431-79d6fb3889d8)]
+[scriptable, builtinclass, uuid(12626092-ee93-44d1-8ecd-160298f98b0f)]
 interface nsIDOMAnimationEvent : nsIDOMEvent {
   readonly attribute DOMString          animationName;
   readonly attribute float              elapsedTime;
+  readonly attribute DOMString          pseudoElement;
   void               initAnimationEvent(in DOMString typeArg,
                                         in boolean canBubbleArg,
                                         in boolean cancelableArg,
                                         in DOMString propertyNameArg,
-                                        in float elapsedTimeArg);
+                                        in float elapsedTimeArg,
+                                        [optional] in DOMString pseudoElementArg);
 };
--- a/dom/webidl/AnimationEvent.webidl
+++ b/dom/webidl/AnimationEvent.webidl
@@ -6,19 +6,32 @@
  * The origin of this IDL file is
  * http://www.w3.org/TR/css3-animations/#animation-events-
  * http://dev.w3.org/csswg/css3-animations/#animation-events-
  *
  * Copyright © 2012 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C
  * liability, trademark and document use rules apply.
  */
 
+[Constructor(DOMString type, optional AnimationEventInit eventInitDict)]
 interface AnimationEvent : Event {
   readonly attribute DOMString animationName;
   readonly attribute float     elapsedTime;
+  readonly attribute DOMString pseudoElement;
+};
 
+dictionary AnimationEventInit : EventInit {
+  DOMString animationName = "";
+  float elapsedTime = 0;
+  DOMString pseudoElement = "";
+};
+
+// initAnimationEvent is a legacy method, and removed from the latest version
+// of the specification.
+partial interface AnimationEvent {
   [Throws]
   void initAnimationEvent(DOMString type,
                           boolean canBubble,
                           boolean cancelable,
                           DOMString animationName,
-                          float elapsedTime);
+                          float elapsedTime,
+                          optional DOMString pseudoElement);
 };
--- a/layout/style/nsAnimationManager.cpp
+++ b/layout/style/nsAnimationManager.cpp
@@ -38,34 +38,35 @@ ElementAnimationsPropertyDtor(void      
 #endif
   delete ea;
 }
 
 double
 ElementAnimations::GetPositionInIteration(TimeDuration aElapsedDuration,
                                           TimeDuration aIterationDuration,
                                           double aIterationCount,
-                                          uint32_t aDirection, bool aIsForElement,
+                                          uint32_t aDirection,
                                           ElementAnimation* aAnimation,
                                           ElementAnimations* aEa,
                                           EventArray* aEventsToDispatch)
 {
+  MOZ_ASSERT(!aAnimation == !aEa && !aAnimation == !aEventsToDispatch);
+
   // Set |currentIterationCount| to the (fractional) number of
   // iterations we've completed up to the current position.
   double currentIterationCount = aElapsedDuration / aIterationDuration;
   bool dispatchStartOrIteration = false;
   if (currentIterationCount >= aIterationCount) {
     if (aAnimation) {
       // Dispatch 'animationend' when needed.
-      if (aIsForElement &&
-          aAnimation->mLastNotification !=
+      if (aAnimation->mLastNotification !=
             ElementAnimation::LAST_NOTIFICATION_END) {
         aAnimation->mLastNotification = ElementAnimation::LAST_NOTIFICATION_END;
         AnimationEventInfo ei(aEa->mElement, aAnimation->mName, NS_ANIMATION_END,
-                              aElapsedDuration);
+                              aElapsedDuration, aEa->PseudoElement());
         aEventsToDispatch->AppendElement(ei);
       }
 
       if (!aAnimation->FillsForwards()) {
         // No animation data.
         return -1;
       }
     } else {
@@ -124,31 +125,31 @@ ElementAnimations::GetPositionInIteratio
       thisIterationReverse = (uint64_t(whichIteration) & 1) == 0;
       break;
   }
   if (thisIterationReverse) {
     positionInIteration = 1.0 - positionInIteration;
   }
 
   // Dispatch 'animationstart' or 'animationiteration' when needed.
-  if (aAnimation && aIsForElement && dispatchStartOrIteration &&
+  if (aAnimation && dispatchStartOrIteration &&
       whichIteration != aAnimation->mLastNotification) {
     // Notify 'animationstart' even if a negative delay puts us
     // past the first iteration.
     // Note that when somebody changes the animation-duration
     // dynamically, this will fire an extra iteration event
     // immediately in many cases.  It's not clear to me if that's the
     // right thing to do.
     uint32_t message =
       aAnimation->mLastNotification == ElementAnimation::LAST_NOTIFICATION_NONE
         ? NS_ANIMATION_START : NS_ANIMATION_ITERATION;
 
     aAnimation->mLastNotification = whichIteration;
     AnimationEventInfo ei(aEa->mElement, aAnimation->mName, message,
-                          aElapsedDuration);
+                          aElapsedDuration, aEa->PseudoElement());
     aEventsToDispatch->AppendElement(ei);
   }
 
   return positionInIteration;
 }
 
 void
 ElementAnimations::EnsureStyleRuleFor(TimeStamp aRefreshTime,
@@ -178,18 +179,17 @@ ElementAnimations::EnsureStyleRuleFor(Ti
       uint32_t oldLastNotification = anim.mLastNotification;
 
       // We need to call GetPositionInIteration here to populate
       // aEventsToDispatch.
       // The ElapsedDurationAt() call here handles pausing.  But:
       // FIXME: avoid recalculating every time when paused.
       GetPositionInIteration(anim.ElapsedDurationAt(aRefreshTime),
                              anim.mIterationDuration, anim.mIterationCount,
-                             anim.mDirection, IsForElement(),
-                             &anim, this, &aEventsToDispatch);
+                             anim.mDirection, &anim, this, &aEventsToDispatch);
 
       // GetPositionInIteration just adjusted mLastNotification; check
       // its new value against the value before we called
       // GetPositionInIteration.
       // XXX We shouldn't really be using mLastNotification as a general
       // indicator that the animation has finished, it should be reserved for
       // events. If we use it differently in the future this use might need
       // changing.
@@ -228,18 +228,18 @@ ElementAnimations::EnsureStyleRuleFor(Ti
         continue;
       }
 
       // The ElapsedDurationAt() call here handles pausing.  But:
       // FIXME: avoid recalculating every time when paused.
       double positionInIteration =
         GetPositionInIteration(anim.ElapsedDurationAt(aRefreshTime),
                                anim.mIterationDuration, anim.mIterationCount,
-                               anim.mDirection, IsForElement(),
-                               &anim, this, &aEventsToDispatch);
+                               anim.mDirection, &anim, this,
+                               &aEventsToDispatch);
 
       // The position is -1 when we don't have fill data for the current time,
       // so we shouldn't animate.
       if (positionInIteration == -1)
         continue;
 
       NS_ABORT_IF_FALSE(0.0 <= positionInIteration &&
                           positionInIteration <= 1.0,
--- a/layout/style/nsAnimationManager.h
+++ b/layout/style/nsAnimationManager.h
@@ -24,28 +24,31 @@ class Declaration;
 }
 
 struct AnimationEventInfo {
   nsRefPtr<mozilla::dom::Element> mElement;
   nsAnimationEvent mEvent;
 
   AnimationEventInfo(mozilla::dom::Element *aElement,
                      const nsString& aAnimationName,
-                     uint32_t aMessage, mozilla::TimeDuration aElapsedTime)
+                     uint32_t aMessage, mozilla::TimeDuration aElapsedTime,
+                     const nsAString& aPseudoElement)
     : mElement(aElement),
-      mEvent(true, aMessage, aAnimationName, aElapsedTime.ToSeconds())
+      mEvent(true, aMessage, aAnimationName, aElapsedTime.ToSeconds(),
+             aPseudoElement)
   {
   }
 
   // nsAnimationEvent doesn't support copy-construction, so we need
   // to ourselves in order to work with nsTArray
   AnimationEventInfo(const AnimationEventInfo &aOther)
     : mElement(aOther.mElement),
       mEvent(true, aOther.mEvent.message,
-             aOther.mEvent.animationName, aOther.mEvent.elapsedTime)
+             aOther.mEvent.animationName, aOther.mEvent.elapsedTime,
+             aOther.mEvent.pseudoElement)
   {
   }
 };
 
 typedef InfallibleTArray<AnimationEventInfo> EventArray;
 
 struct AnimationPropertySegment
 {
@@ -140,29 +143,37 @@ struct ElementAnimations MOZ_FINAL
   // This function returns -1 for the position if the animation should not be
   // run (because it is not currently active and has no fill behavior), but
   // only does so if aAnimation is non-null; with a null aAnimation it is an
   // error to give aCurrentTime < aStartTime, and fill-forwards is assumed.
   static double GetPositionInIteration(TimeDuration aElapsedDuration,
                                        TimeDuration aIterationDuration,
                                        double aIterationCount,
                                        uint32_t aDirection,
-                                       bool aIsForElement = true,
                                        ElementAnimation* aAnimation = nullptr,
                                        ElementAnimations* aEa = nullptr,
                                        EventArray* aEventsToDispatch = nullptr);
 
   void EnsureStyleRuleFor(TimeStamp aRefreshTime,
                           EventArray &aEventsToDispatch,
                           bool aIsThrottled);
 
   bool IsForElement() const { // rather than for a pseudo-element
     return mElementProperty == nsGkAtoms::animationsProperty;
   }
 
+  nsString PseudoElement()
+  {
+    return mElementProperty == nsGkAtoms::animationsProperty ?
+             EmptyString() :
+             mElementProperty == nsGkAtoms::animationsOfBeforeProperty ?
+               NS_LITERAL_STRING("::before") :
+               NS_LITERAL_STRING("::after");
+  }
+
   void PostRestyleForAnimation(nsPresContext *aPresContext) {
     nsRestyleHint styleHint = IsForElement() ? eRestyle_Self : eRestyle_Subtree;
     aPresContext->PresShell()->RestyleForAnimation(mElement, styleHint);
   }
 
   // True if this animation can be performed on the compositor thread.
   virtual bool CanPerformOnCompositorThread(CanAnimateFlags aFlags) const MOZ_OVERRIDE;
   virtual bool HasAnimationOfProperty(nsCSSProperty aProperty) const MOZ_OVERRIDE;
--- a/layout/style/test/test_animations.html
+++ b/layout/style/test/test_animations.html
@@ -61,17 +61,17 @@ https://bugzilla.mozilla.org/show_bug.cg
   }
 
   @keyframes always_fifty {
     from, to { margin-left: 50px }
   }
 
   #withbefore::before, #withafter::after {
     content: "";
-    animation: anim2 1s linear alternate infinite;
+    animation: anim2 1s linear alternate 3;
   }
 
   @keyframes multiprop {
     0% {
       padding-top: 10px; padding-left: 30px;
       animation-timing-function: ease;
     }
     25% {
@@ -131,16 +131,31 @@ https://bugzilla.mozilla.org/show_bug.cg
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=435442">Mozilla Bug 435442</a>
 <div id="display"></div>
 <pre id="test">
 <script type="application/javascript">
 "use strict";
 
 /** Test for css3-animations (Bug 435442) **/
 
+var e = new AnimationEvent("foo",
+                            {
+                              bubbles: true,
+                              cancelable: true,
+                              animationName: "name",
+                              elapsedTime: 0.5,
+                              pseudoElement: "pseudo"
+                            });
+is(e.bubbles, true);
+is(e.cancelable, true);
+is(e.animationName, "name");
+is(e.elapsedTime, 0.5);
+is(e.pseudoElement, "pseudo");
+is(e.isTrusted, false)
+
 function advance_clock(milliseconds) {
   SpecialPowers.DOMWindowUtils.advanceTimeAndRefresh(milliseconds);
 }
 
 var display = document.getElementById("display");
 var div = null;
 var cs = null;
 var events_received = [];
@@ -236,17 +251,18 @@ function test_fill_mode(fill_mode, fills
   if (fills_backwards)
     is(cs.marginLeft, "0px", desc + "does affect value during delay (2s)");
   else
     is(cs.marginLeft, "30px", desc + "doesn't affect value during delay (2s)");
   check_events([], "before start in test_fill_mode");
   advance_clock(1000);
   check_events([{ type: 'animationstart', target: div,
                   bubbles: true, cancelable: true,
-                  animationName: 'anim1', elapsedTime: 0.0 }],
+                  animationName: 'anim1', elapsedTime: 0.0,
+                  pseudoElement: "" }],
                "right after start in test_fill_mode");
   if (fills_backwards)
     is(cs.marginLeft, "0px", desc + "affects value at start of animation");
   advance_clock(125);
   is(cs.marginLeft, "2px", desc + "affects value during animation");
   advance_clock(2375);
   is(cs.marginLeft, "40px", desc + "affects value during animation");
   advance_clock(2500);
@@ -254,17 +270,18 @@ function test_fill_mode(fill_mode, fills
   advance_clock(2500);
   is(cs.marginLeft, "90px", desc + "affects value during animation");
   advance_clock(2375);
   is(cs.marginLeft, "99.5px", desc + "affects value during animation");
   check_events([], "before end in test_fill_mode");
   advance_clock(125);
   check_events([{ type: 'animationend', target: div,
                   bubbles: true, cancelable: true,
-                  animationName: 'anim1', elapsedTime: 10.0 }],
+                  animationName: 'anim1', elapsedTime: 10.0,
+                  pseudoElement: "" }],
                "right after end in test_fill_mode");
   if (fills_forwards)
     is(cs.marginLeft, "100px", desc + "affects value at end of animation");
   advance_clock(10);
   if (fills_forwards)
     is(cs.marginLeft, "100px", desc + "does affect value after animation");
   else
     is(cs.marginLeft, "30px", desc + "does not affect value after animation");
@@ -1178,35 +1195,38 @@ done_div();
 
 // test large negative delay that causes the animation to start
 // in the fourth iteration
 new_div("animation: anim2 1s -3.6s ease-in 5 alternate forwards");
 listen(); // rely on no flush having happened yet
 is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.4), 0.01,
           "large negative delay test at 0ms");
 check_events([{ type: 'animationstart', target: div,
-                animationName: 'anim2', elapsedTime: 3.6 }],
+                animationName: 'anim2', elapsedTime: 3.6,
+                pseudoElement: "" }],
              "right after start in large negative delay test");
 advance_clock(380);
 is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.02), 0.01,
           "large negative delay test at 380ms");
 check_events([]);
 advance_clock(20);
 is(cs.marginRight, "0px", "large negative delay test at 400ms");
 check_events([{ type: 'animationiteration', target: div,
-                animationName: 'anim2', elapsedTime: 4.0 }],
+                animationName: 'anim2', elapsedTime: 4.0,
+                pseudoElement: "" }],
              "right after start in large negative delay test");
 advance_clock(800);
 is_approx(px_to_num(cs.marginRight), 100 * gTF.ease_in(0.8), 0.01,
           "large negative delay test at 1200ms");
 check_events([]);
 advance_clock(200);
 is(cs.marginRight, "100px", "large negative delay test at 1400ms");
 check_events([{ type: 'animationend', target: div,
-                animationName: 'anim2', elapsedTime: 5.0 }],
+                animationName: 'anim2', elapsedTime: 5.0,
+                pseudoElement: "" }],
              "right after start in large negative delay test");
 done_div();
 
 /*
  * css3-animations:  3.9. The 'animation-fill-mode' Property
  * http://dev.w3.org/csswg/css3-animations/#the-animation-fill-mode-property-
  */
 
@@ -1232,34 +1252,42 @@ advance_clock(400);
 is(cs_before.marginRight, "40px", ":before test at 400ms");
 advance_clock(800);
 is(cs_before.marginRight, "80px", ":before test at 1200ms");
 is(cs.marginRight, "0px", ":before animation should not affect element");
 advance_clock(800);
 is(cs_before.marginRight, "0px", ":before test at 2000ms");
 advance_clock(300);
 is(cs_before.marginRight, "30px", ":before test at 2300ms");
-check_events([], "no events should be fired for animations on :before");
+advance_clock(700);
+check_events([ { type: "animationstart", animationName: "anim2", elapsedTime: 0, pseudoElement: "::before" },
+               { type: "animationiteration", animationName: "anim2", elapsedTime: 1.2, pseudoElement: "::before" },
+               { type: "animationiteration", animationName: "anim2", elapsedTime: 2, pseudoElement: "::before" },
+               { type: "animationend", animationName: "anim2", elapsedTime: 3, pseudoElement: "::before" }]);
 done_div();
 
 new_div("");
 listen();
 div.id = "withafter";
 var cs_after = getComputedStyle(div, ":after");
 is(cs_after.marginRight, "0px", ":after test at 0ms");
 advance_clock(400);
 is(cs_after.marginRight, "40px", ":after test at 400ms");
 advance_clock(800);
 is(cs_after.marginRight, "80px", ":after test at 1200ms");
 is(cs.marginRight, "0px", ":after animation should not affect element");
 advance_clock(800);
 is(cs_after.marginRight, "0px", ":after test at 2000ms");
 advance_clock(300);
 is(cs_after.marginRight, "30px", ":after test at 2300ms");
-check_events([], "no events should be fired for animations on :after");
+advance_clock(700);
+check_events([ { type: "animationstart", animationName: "anim2", elapsedTime: 0, pseudoElement: "::after" },
+               { type: "animationiteration", animationName: "anim2", elapsedTime: 1.2, pseudoElement: "::after" },
+               { type: "animationiteration", animationName: "anim2", elapsedTime: 2, pseudoElement: "::after" },
+               { type: "animationend", animationName: "anim2", elapsedTime: 3, pseudoElement: "::after" }]);
 done_div();
 
 /**
  * Test handling of properties that are present in only some of the
  * keyframes.
  */
 new_div("animation: multiprop 1s ease-in-out alternate infinite");
 is(cs.paddingTop, "10px", "multiprop top at 0ms");
--- a/widget/nsGUIEvent.h
+++ b/widget/nsGUIEvent.h
@@ -1770,24 +1770,27 @@ public:
   float elapsedTime;
   nsString pseudoElement;
 };
 
 class nsAnimationEvent : public nsEvent
 {
 public:
   nsAnimationEvent(bool isTrusted, uint32_t msg,
-                   const nsString &animationNameArg, float elapsedTimeArg)
+                   const nsAString &animationNameArg, float elapsedTimeArg,
+                   const nsAString &pseudoElementArg)
     : nsEvent(isTrusted, msg, NS_ANIMATION_EVENT),
-      animationName(animationNameArg), elapsedTime(elapsedTimeArg)
+      animationName(animationNameArg), elapsedTime(elapsedTimeArg),
+      pseudoElement(pseudoElementArg)
   {
   }
 
   nsString animationName;
   float elapsedTime;
+  nsString pseudoElement;
 };
 
 /**
  * Native event pluginEvent for plugins.
  */
 
 class nsPluginEvent : public nsGUIEvent
 {