Bug 1264125 part 5 - Call the queueing events when canceling transition via Style or Script. r?birtles draft
authorMantaroh Yoshinaga <mantaroh@gmail.com>
Tue, 20 Dec 2016 16:03:29 +0900
changeset 451399 c12e5b747c495205aa6a94940c7614244c3e4c86
parent 451398 89357c8a82bc81518ce688cd4b8ccb6b6371b8f8
child 451400 681aa145dd88653ba3781cfd9ad68e3021724c96
push id39153
push usermantaroh@gmail.com
push dateTue, 20 Dec 2016 07:36:10 +0000
reviewersbirtles
bugs1264125
milestone53.0a1
Bug 1264125 part 5 - Call the queueing events when canceling transition via Style or Script. r?birtles MozReview-Commit-ID: 5qWef4xCZXX
dom/animation/Animation.cpp
dom/animation/Animation.h
layout/style/nsTransitionManager.cpp
layout/style/nsTransitionManager.h
--- a/dom/animation/Animation.cpp
+++ b/dom/animation/Animation.cpp
@@ -225,26 +225,33 @@ Animation::SetTimeline(AnimationTimeline
 // https://w3c.github.io/web-animations/#setting-the-timeline
 void
 Animation::SetTimelineNoUpdate(AnimationTimeline* aTimeline)
 {
   if (mTimeline == aTimeline) {
     return;
   }
 
+  StickyTimeDuration activeTime = mEffect
+                                  ? mEffect->GetComputedTiming().mActiveTime
+                                  : StickyTimeDuration();
+
   RefPtr<AnimationTimeline> oldTimeline = mTimeline;
   if (oldTimeline) {
     oldTimeline->RemoveAnimation(this);
   }
 
   mTimeline = aTimeline;
   if (!mStartTime.IsNull()) {
     mHoldTime.SetNull();
   }
 
+  if (!aTimeline) {
+    MaybeQueueCancelEvent(activeTime);
+  }
   UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async);
 }
 
 // https://w3c.github.io/web-animations/#set-the-animation-start-time
 void
 Animation::SetStartTime(const Nullable<TimeDuration>& aNewStartTime)
 {
   if (aNewStartTime == mStartTime) {
@@ -765,24 +772,29 @@ Animation::CancelNoUpdate()
 
   if (mFinished) {
     mFinished->MaybeReject(NS_ERROR_DOM_ABORT_ERR);
   }
   ResetFinishedPromise();
 
   DispatchPlaybackEvent(NS_LITERAL_STRING("cancel"));
 
+  StickyTimeDuration activeTime = mEffect
+                                  ? mEffect->GetComputedTiming().mActiveTime
+                                  : StickyTimeDuration();
+
   mHoldTime.SetNull();
   mStartTime.SetNull();
 
   UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async);
 
   if (mTimeline) {
     mTimeline->RemoveAnimation(this);
   }
+  MaybeQueueCancelEvent(activeTime);
 }
 
 bool
 Animation::ShouldBeSynchronizedWithMainThread(
   nsCSSPropertyID aProperty,
   const nsIFrame* aFrame,
   AnimationPerformanceWarning::Type& aPerformanceWarning) const
 {
@@ -851,16 +863,27 @@ Animation::HasLowerCompositeOrderThan(co
                nullptr;
       };
     auto thisTransition  = asCSSTransitionForSorting(*this);
     auto otherTransition = asCSSTransitionForSorting(aOther);
     if (thisTransition && otherTransition) {
       return thisTransition->HasLowerCompositeOrderThan(*otherTransition);
     }
     if (thisTransition || otherTransition) {
+      // Cancelled transitions no longer have an owning element. To be strictly
+      // correct we should store a strong reference to the owning element
+      // so that if we arrive here while sorting cancel events, we can sort
+      // them in the correct order.
+      //
+      // However, given that cancel events are almost always queued
+      // synchronously in some deterministic manner, we can be fairly sure
+      // that cancel events will be dispatched in a deterministic order
+      // (which is our only hard requirement until specs say otherwise).
+      // Furthermore, we only reach here when we have events with equal
+      // timestamps so this is an edge case we can probably ignore for now.
       return thisTransition;
     }
   }
 
   // 2. CSS Animations sort next
   {
     auto asCSSAnimationForSorting =
       [] (const Animation& anim) -> const CSSAnimation*
--- a/dom/animation/Animation.h
+++ b/dom/animation/Animation.h
@@ -318,16 +318,26 @@ public:
    * updated in |aStyleRule|.
    */
   void ComposeStyle(RefPtr<AnimValuesStyleRule>& aStyleRule,
                     const nsCSSPropertyIDSet& aPropertiesToSkip);
 
   void NotifyEffectTimingUpdated();
   void NotifyGeometricAnimationsStartingThisFrame();
 
+  /**
+   * Used by subclasses to synchronously queue a cancel event in situations
+   * where the Animation may have been cancelled.
+   *
+   * We need to do this synchronously because after a CSS animation/transition
+   * is canceled, it will be released by its owning element and may not still
+   * exist when we would normally go to queue events on the next tick.
+   */
+  virtual void MaybeQueueCancelEvent(StickyTimeDuration aActiveTime) {};
+
 protected:
   void SilentlySetCurrentTime(const TimeDuration& aNewCurrentTime);
   void SilentlySetPlaybackRate(double aPlaybackRate);
   void CancelNoUpdate();
   void PlayNoUpdate(ErrorResult& aRv, LimitBehavior aLimitBehavior);
   void PauseNoUpdate(ErrorResult& aRv);
   void ResumeAt(const TimeDuration& aReadyTime);
   void PauseAt(const TimeDuration& aReadyTime);
--- a/layout/style/nsTransitionManager.cpp
+++ b/layout/style/nsTransitionManager.cpp
@@ -175,17 +175,17 @@ CSSTransition::UpdateTiming(SeekFlag aSe
     mAnimationIndex = sNextAnimationIndex++;
     mNeedsNewAnimationIndexWhenRun = false;
   }
 
   Animation::UpdateTiming(aSeekFlag, aSyncNotifyFlag);
 }
 
 void
-CSSTransition::QueueEvents()
+CSSTransition::QueueEvents(StickyTimeDuration aActiveTime)
 {
   if (!mEffect ||
       !mOwningElement.IsSet()) {
     return;
   }
 
   dom::Element* owningElement;
   CSSPseudoElementType owningPseudoType;
@@ -225,22 +225,19 @@ CSSTransition::QueueEvents()
     currentPhase = static_cast<TransitionPhase>(computedTiming.mPhase);
   }
 
   AutoTArray<TransitionEventParams, 3> events;
 
   // Handle cancel events firts
   if (mPreviousTransitionPhase != TransitionPhase::Idle &&
       currentPhase == TransitionPhase::Idle) {
-    // FIXME: bug 1264125: We will need to get active time when cancelling
-    //                     the transition.
-    StickyTimeDuration activeTime(0);
-    TimeStamp activeTimeStamp = ElapsedTimeToTimeStamp(activeTime);
+    TimeStamp activeTimeStamp = ElapsedTimeToTimeStamp(aActiveTime);
     events.AppendElement(TransitionEventParams{ eTransitionCancel,
-                                                activeTime,
+                                                aActiveTime,
                                                 activeTimeStamp });
   }
 
   // All other events
   switch (mPreviousTransitionPhase) {
     case TransitionPhase::Idle:
       if (currentPhase == TransitionPhase::Pending ||
           currentPhase == TransitionPhase::Before) {
--- a/layout/style/nsTransitionManager.h
+++ b/layout/style/nsTransitionManager.h
@@ -157,16 +157,28 @@ public:
     // defined until that moment.
     //
     // See longer explanation in CSSAnimation::CancelFromStyle.
     mAnimationIndex = sNextAnimationIndex++;
     mNeedsNewAnimationIndexWhenRun = true;
 
     Animation::CancelFromStyle();
 
+    // The above call to Animation::CancelFromStyle may cause a transitioncancel
+    // event to be queued. However, it will also remove the transition from its
+    // timeline. If this transition was the last animation attached to
+    // the timeline, the timeline will stop observing the refresh driver and
+    // there may be no subsequent tick fro dispatching animation events.
+    //
+    // To ensure the cancel event is dispatched we tell the timeline it needs to
+    // observe the refresh driver for at least one more tick.
+    if (mTimeline) {
+      mTimeline->NotifyAnimationUpdated(*this);
+    }
+
     // It is important we do this *after* calling CancelFromStyle().
     // This is because CancelFromStyle() will end up posting a restyle and
     // that restyle should target the *transitions* level of the cascade.
     // However, once we clear the owning element, CascadeLevel() will begin
     // returning CascadeLevel::Animations.
     mOwningElement = OwningElementRef();
   }
 
@@ -209,28 +221,32 @@ public:
   // because the animation on the compositor may be running ahead while
   // main-thread is busy.
   static Nullable<TimeDuration> GetCurrentTimeAt(
       const DocumentTimeline& aTimeline,
       const TimeStamp& aBaseTime,
       const TimeDuration& aStartTime,
       double aPlaybackRate);
 
+  void MaybeQueueCancelEvent(StickyTimeDuration aActiveTime) override {
+    QueueEvents(aActiveTime);
+  }
+
 protected:
   virtual ~CSSTransition()
   {
     MOZ_ASSERT(!mOwningElement.IsSet(), "Owning element should be cleared "
                                         "before a CSS transition is destroyed");
   }
 
   // Animation overrides
   void UpdateTiming(SeekFlag aSeekFlag,
                     SyncNotifyFlag aSyncNotifyFlag) override;
 
-  void QueueEvents();
+  void QueueEvents(StickyTimeDuration activeTime = StickyTimeDuration());
 
   // 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