Bug 1287983 part 2 - Add transitionstart/transitionrun event handling. r?birtles draft
authorMantaroh Yoshinaga <mantaroh@gmail.com>
Wed, 19 Oct 2016 15:16:51 +0900
changeset 427849 8c215345073ca09fb86d7741476ab5b8010a0991
parent 427848 796fca96d7c159e0aa12e609fbd2741dd0980a7a
child 427850 d6ab373f010658772045642cae3eb5f21a4b66d4
push id33142
push usermantaroh@gmail.com
push dateFri, 21 Oct 2016 00:27:27 +0000
reviewersbirtles
bugs1287983
milestone52.0a1
Bug 1287983 part 2 - Add transitionstart/transitionrun event handling. r?birtles The specification of CSS-Transition event is as follow: https://drafts.csswg.org/css-transitions-2/#transition-events MozReview-Commit-ID: CWgsRRLhnXp
layout/style/nsTransitionManager.cpp
layout/style/nsTransitionManager.h
--- a/layout/style/nsTransitionManager.cpp
+++ b/layout/style/nsTransitionManager.cpp
@@ -41,16 +41,26 @@ using mozilla::TimeDuration;
 using mozilla::dom::Animation;
 using mozilla::dom::AnimationPlayState;
 using mozilla::dom::CSSTransition;
 using mozilla::dom::KeyframeEffectReadOnly;
 
 using namespace mozilla;
 using namespace mozilla::css;
 
+typedef mozilla::ComputedTiming::AnimationPhase AnimationPhase;
+
+namespace {
+struct TransitionEventParams {
+  EventMessage mMessage;
+  StickyTimeDuration mElapsedTime;
+  TimeStamp mTimeStamp;
+};
+} // anonymous namespace
+
 double
 ElementPropertyTransition::CurrentValuePortion() const
 {
   MOZ_ASSERT(!GetLocalTime().IsNull(),
              "Getting the value portion of an animation that's not being "
              "sampled");
 
   // Transitions use a fill mode of 'backwards' so GetComputedTiming will
@@ -167,42 +177,149 @@ CSSTransition::UpdateTiming(SeekFlag aSe
   }
 
   Animation::UpdateTiming(aSeekFlag, aSyncNotifyFlag);
 }
 
 void
 CSSTransition::QueueEvents()
 {
-  AnimationPlayState playState = PlayState();
-  bool newlyFinished = !mWasFinishedOnLastTick &&
-                       playState == AnimationPlayState::Finished;
-  mWasFinishedOnLastTick = playState == AnimationPlayState::Finished;
-
-  if (!newlyFinished || !mEffect || !mOwningElement.IsSet()) {
+  if (!mEffect ||
+      !mOwningElement.IsSet()) {
     return;
   }
 
   dom::Element* owningElement;
   CSSPseudoElementType owningPseudoType;
   mOwningElement.GetElement(owningElement, owningPseudoType);
   MOZ_ASSERT(owningElement, "Owning element should be set");
 
   nsPresContext* presContext = mOwningElement.GetRenderedPresContext();
   if (!presContext) {
     return;
   }
 
+  ComputedTiming computedTiming = mEffect->GetComputedTiming();
+  const StickyTimeDuration zeroDuration;
+  StickyTimeDuration intervalStartTime =
+    std::max(std::min(StickyTimeDuration(-mEffect->SpecifiedTiming().mDelay),
+                      computedTiming.mActiveDuration), zeroDuration);
+  StickyTimeDuration intervalEndTime =
+    std::max(std::min((EffectEnd() - mEffect->SpecifiedTiming().mDelay),
+                      computedTiming.mActiveDuration), zeroDuration);
+
+  // TimeStamps to use for ordering the events when they are dispatched. We
+  // use a TimeStamp so we can compare events produced by different elements,
+  // perhaps even with different timelines.
+  // The zero timestamp is for transitionrun events where we ignore the delay
+  // for the purpose of ordering events.
+  TimeStamp zeroTimeStamp  = AnimationTimeToTimeStamp(zeroDuration);
+  TimeStamp startTimeStamp = ElapsedTimeToTimeStamp(intervalStartTime);
+  TimeStamp endTimeStamp   = ElapsedTimeToTimeStamp(intervalEndTime);
+
+  TransitionPhase currentPhase;
+  if (mPendingState != PendingState::NotPending &&
+      (mPreviousTransitionPhase == TransitionPhase::Idle ||
+       mPreviousTransitionPhase == TransitionPhase::Pending))
+  {
+    currentPhase = TransitionPhase::Pending;
+  } else {
+    currentPhase = static_cast<TransitionPhase>(computedTiming.mPhase);
+  }
+
+  AutoTArray<TransitionEventParams, 3> events;
+  switch (mPreviousTransitionPhase) {
+    case TransitionPhase::Idle:
+      if (currentPhase == TransitionPhase::Pending ||
+          currentPhase == TransitionPhase::Before) {
+        events.AppendElement(TransitionEventParams{ eTransitionRun,
+                                                   intervalStartTime,
+                                                   zeroTimeStamp });
+      } else if (currentPhase == TransitionPhase::Active) {
+        events.AppendElement(TransitionEventParams{ eTransitionRun,
+                                                   intervalStartTime,
+                                                   zeroTimeStamp });
+        events.AppendElement(TransitionEventParams{ eTransitionStart,
+                                                   intervalStartTime,
+                                                   startTimeStamp });
+      } else if (currentPhase == TransitionPhase::After) {
+        events.AppendElement(TransitionEventParams{ eTransitionRun,
+                                                   intervalStartTime,
+                                                   zeroTimeStamp });
+        events.AppendElement(TransitionEventParams{ eTransitionStart,
+                                                   intervalStartTime,
+                                                   startTimeStamp });
+        events.AppendElement(TransitionEventParams{ eTransitionEnd,
+                                                   intervalEndTime,
+                                                   endTimeStamp });
+      }
+      break;
+
+    case TransitionPhase::Pending:
+    case TransitionPhase::Before:
+      if (currentPhase == TransitionPhase::Active) {
+        events.AppendElement(TransitionEventParams{ eTransitionStart,
+                                                   intervalStartTime,
+                                                   startTimeStamp });
+      } else if (currentPhase == TransitionPhase::After) {
+        events.AppendElement(TransitionEventParams{ eTransitionStart,
+                                                   intervalStartTime,
+                                                   startTimeStamp });
+        events.AppendElement(TransitionEventParams{ eTransitionEnd,
+                                                   intervalEndTime,
+                                                   endTimeStamp });
+      }
+      break;
+
+    case TransitionPhase::Active:
+      if (currentPhase == TransitionPhase::After) {
+        events.AppendElement(TransitionEventParams{ eTransitionEnd,
+                                                   intervalEndTime,
+                                                   endTimeStamp });
+      } else if (currentPhase == TransitionPhase::Before) {
+        events.AppendElement(TransitionEventParams{ eTransitionEnd,
+                                                   intervalStartTime,
+                                                   startTimeStamp });
+      }
+      break;
+
+    case TransitionPhase::After:
+      if (currentPhase == TransitionPhase::Active) {
+        events.AppendElement(TransitionEventParams{ eTransitionStart,
+                                                   intervalEndTime,
+                                                   startTimeStamp });
+      } else if (currentPhase == TransitionPhase::Before) {
+        events.AppendElement(TransitionEventParams{ eTransitionStart,
+                                                   intervalEndTime,
+                                                   startTimeStamp });
+        events.AppendElement(TransitionEventParams{ eTransitionEnd,
+                                                   intervalStartTime,
+                                                   endTimeStamp });
+      }
+      break;
+  }
+  mPreviousTransitionPhase = currentPhase;
+
   nsTransitionManager* manager = presContext->TransitionManager();
-  manager->QueueEvent(TransitionEventInfo(owningElement, owningPseudoType,
-                                          TransitionProperty(),
-                                          mEffect->GetComputedTiming()
-                                            .mDuration,
-                                          AnimationTimeToTimeStamp(EffectEnd()),
-                                          this));
+  for (const TransitionEventParams& evt : events) {
+    manager->QueueEvent(TransitionEventInfo(owningElement, owningPseudoType,
+                                            evt.mMessage,
+                                            TransitionProperty(),
+                                            evt.mElapsedTime,
+                                            evt.mTimeStamp,
+                                            this));
+  }
+}
+
+TimeStamp
+CSSTransition::ElapsedTimeToTimeStamp(
+  const StickyTimeDuration& aElapsedTime) const
+{
+  return AnimationTimeToTimeStamp(aElapsedTime +
+                                  mEffect->SpecifiedTiming().mDelay);
 }
 
 void
 CSSTransition::Tick()
 {
   Animation::Tick();
   QueueEvents();
 }
--- a/layout/style/nsTransitionManager.h
+++ b/layout/style/nsTransitionManager.h
@@ -3,16 +3,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* Code to start and animate CSS transitions. */
 
 #ifndef nsTransitionManager_h_
 #define nsTransitionManager_h_
 
+#include "mozilla/ComputedTiming.h"
 #include "mozilla/ContentEvents.h"
 #include "mozilla/EffectCompositor.h" // For EffectCompositor::CascadeLevel
 #include "mozilla/MemoryReporting.h"
 #include "mozilla/dom/Animation.h"
 #include "mozilla/dom/KeyframeEffectReadOnly.h"
 #include "AnimationCommon.h"
 #include "nsCSSProps.h"
 
@@ -114,17 +115,17 @@ struct ElementPropertyTransition : publi
 
 namespace dom {
 
 class CSSTransition final : public Animation
 {
 public:
  explicit CSSTransition(nsIGlobalObject* aGlobal)
     : dom::Animation(aGlobal)
-    , mWasFinishedOnLastTick(false)
+    , mPreviousTransitionPhase(TransitionPhase::Idle)
     , mNeedsNewAnimationIndexWhenRun(false)
     , mTransitionProperty(eCSSProperty_UNKNOWN)
   {
   }
 
   JSObject* WrapObject(JSContext* aCx,
                        JS::Handle<JSObject*> aGivenProto) override;
 
@@ -221,16 +222,20 @@ protected:
   }
 
   // Animation overrides
   void UpdateTiming(SeekFlag aSeekFlag,
                     SyncNotifyFlag aSyncNotifyFlag) override;
 
   void QueueEvents();
 
+  // Converts an TransitionEvent's elapsedTime value to an equivalent TimeStamp
+  // that can be used to sort events by when they occurred.
+  TimeStamp ElapsedTimeToTimeStamp(const StickyTimeDuration& aElapsedTime) const;
+
   // The (pseudo-)element whose computed transition-property refers to this
   // transition (if any).
   //
   // This is used for determining the relative composite order of transitions
   // generated from CSS markup.
   //
   // Typically this will be the same as the target element of the keyframe
   // effect associated with this transition. However, it can differ in the
@@ -240,17 +245,27 @@ protected:
   // b) If this transition is cancelled (e.g. by updating the
   //    transition-property or removing the owning element from the document),
   // c) If this object is generated from script using the CSSTransition
   //    constructor.
   //
   // For (b) and (c) the owning element will return !IsSet().
   OwningElementRef mOwningElement;
 
-  bool mWasFinishedOnLastTick;
+  // The 'transition phase' used to determine which transition events need
+  // to be queued on this tick.
+  // See: https://drafts.csswg.org/css-transitions-2/#transition-phase
+  enum class TransitionPhase {
+    Idle   = static_cast<int>(ComputedTiming::AnimationPhase::Null),
+    Before = static_cast<int>(ComputedTiming::AnimationPhase::Before),
+    Active = static_cast<int>(ComputedTiming::AnimationPhase::Active),
+    After  = static_cast<int>(ComputedTiming::AnimationPhase::After),
+    Pending
+  };
+  TransitionPhase mPreviousTransitionPhase;
 
   // When true, indicates that when this transition next leaves the idle state,
   // its animation index should be updated.
   bool mNeedsNewAnimationIndexWhenRun;
 
   // Store the transition property and to-value here since we need that
   // information in order to determine if there is an existing transition
   // for a given style change. We can't store that information on the
@@ -282,39 +297,40 @@ struct AnimationTypeTraits<dom::CSSTrans
 struct TransitionEventInfo {
   RefPtr<dom::Element> mElement;
   RefPtr<dom::Animation> mAnimation;
   InternalTransitionEvent mEvent;
   TimeStamp mTimeStamp;
 
   TransitionEventInfo(dom::Element* aElement,
                       CSSPseudoElementType aPseudoType,
+                      EventMessage aMessage,
                       nsCSSPropertyID aProperty,
                       StickyTimeDuration aDuration,
                       const TimeStamp& aTimeStamp,
                       dom::Animation* aAnimation)
     : mElement(aElement)
     , mAnimation(aAnimation)
-    , mEvent(true, eTransitionEnd)
+    , mEvent(true, aMessage)
     , mTimeStamp(aTimeStamp)
   {
     // XXX Looks like nobody initialize WidgetEvent::time
     mEvent.mPropertyName =
       NS_ConvertUTF8toUTF16(nsCSSProps::GetStringValue(aProperty));
     mEvent.mElapsedTime = aDuration.ToSeconds();
     mEvent.mPseudoElement =
       AnimationCollection<dom::CSSTransition>::PseudoTypeAsString(aPseudoType);
   }
 
   // InternalTransitionEvent doesn't support copy-construction, so we need
   // to ourselves in order to work with nsTArray
   TransitionEventInfo(const TransitionEventInfo& aOther)
     : mElement(aOther.mElement)
     , mAnimation(aOther.mAnimation)
-    , mEvent(true, eTransitionEnd)
+    , mEvent(aOther.mEvent)
     , mTimeStamp(aOther.mTimeStamp)
   {
     mEvent.AssignTransitionEventData(aOther.mEvent, false);
   }
 };
 
 } // namespace mozilla