Bug 1223658 - Part 2: Pass delay property to compositor. r?birtles draft
authorHiroyuki Ikezoe <hiikezoe@mozilla-japan.org>
Fri, 14 Oct 2016 19:14:01 +0900
changeset 425231 98241d7d281235a9baa0c35e43fbdf1d5b45e6c4
parent 425227 1391a2889aeb2bdd61ad6ef838e65826e35aabc2
child 425232 b6aac79fcfcd2f50d121b3324bbc585e6fd7705c
push id32372
push userbmo:hiikezoe@mozilla-japan.org
push dateFri, 14 Oct 2016 10:51:45 +0000
reviewersbirtles
bugs1223658
milestone52.0a1
Bug 1223658 - Part 2: Pass delay property to compositor. r?birtles The check of negative elapsedDuration is basically no longer valid since animation delay is not factored into start time any more. But still we have somtimes met negative elapsedDuration sice we use a previous vsync time stamp for async animations to make the animations more sync. This is not a problem in most cases but makes two reftests intermitent failure because both of them used steps(1, start), the steps(1, start) composed different results in the before phase and in the active phase. To avoid this difference this patch replace the steps(1, start) with steps(1, end). Once we incorpolate playbackRate into GetCurrentOrPendingStartTime, we don't need to call AnimationTimeToTimeStamp for deviding delay by playbackRate since the time passed to AnimationTimeToTimeStamp does not contain delay any more. MozReview-Commit-ID: IVE2IFfNgm0
dom/animation/Animation.cpp
dom/animation/Animation.h
gfx/layers/composite/AsyncCompositionManager.cpp
gfx/layers/ipc/LayersMessages.ipdlh
layout/base/nsDisplayList.cpp
layout/reftests/css-transitions/stacking-context-opacity-wins-over-important-style.html
layout/reftests/css-transitions/stacking-context-transform-wins-over-important-style.html
--- a/dom/animation/Animation.cpp
+++ b/dom/animation/Animation.cpp
@@ -659,22 +659,32 @@ Animation::GetCurrentOrPendingStartTime(
     return result;
   }
 
   if (mPendingReadyTime.IsNull() || mHoldTime.IsNull()) {
     return result;
   }
 
   // Calculate the equivalent start time from the pending ready time.
-  // This is the same as the calculation performed in ResumeAt and will
-  // need to incorporate the playbackRate when implemented (bug 1127380).
-  result.SetValue(mPendingReadyTime.Value() - mHoldTime.Value());
+  result = StartTimeFromReadyTime(mPendingReadyTime.Value());
+
   return result;
 }
 
+TimeDuration
+Animation::StartTimeFromReadyTime(const TimeDuration& aReadyTime) const
+{
+  MOZ_ASSERT(!mHoldTime.IsNull(), "Hold time should be set in order to"
+                                  " convert a ready time to a start time");
+  if (mPlaybackRate == 0) {
+    return aReadyTime;
+  }
+  return aReadyTime - mHoldTime.Value().MultDouble(1 / mPlaybackRate);
+}
+
 TimeStamp
 Animation::AnimationTimeToTimeStamp(const StickyTimeDuration& aTime) const
 {
   // Initializes to null. Return the same object every time to benefit from
   // return-value-optimization.
   TimeStamp result;
 
   // We *don't* check for mTimeline->TracksWallclockTime() here because that
@@ -1061,22 +1071,19 @@ Animation::ResumeAt(const TimeDuration& 
              "Expected to resume a play-pending animation");
   MOZ_ASSERT(mHoldTime.IsNull() != mStartTime.IsNull(),
              "An animation in the play-pending state should have either a"
              " resolved hold time or resolved start time (but not both)");
 
   // If we aborted a pending pause operation we will already have a start time
   // we should use. In all other cases, we resolve it from the ready time.
   if (mStartTime.IsNull()) {
+    mStartTime = StartTimeFromReadyTime(aReadyTime);
     if (mPlaybackRate != 0) {
-      mStartTime.SetValue(aReadyTime -
-                          (mHoldTime.Value().MultDouble(1 / mPlaybackRate)));
       mHoldTime.SetNull();
-    } else {
-      mStartTime.SetValue(aReadyTime);
     }
   }
   mPendingState = PendingState::NotPending;
 
   UpdateTiming(SeekFlag::NoSeek, SyncNotifyFlag::Async);
 
   if (mReady) {
     mReady->MaybeResolve(this);
--- a/dom/animation/Animation.h
+++ b/dom/animation/Animation.h
@@ -235,16 +235,23 @@ public:
    *
    * This method returns the start time, if resolved. Otherwise, if we have
    * a pending ready time, it returns the corresponding start time. If neither
    * of those are available, it returns null.
    */
   Nullable<TimeDuration> GetCurrentOrPendingStartTime() const;
 
   /**
+   * Calculates the corresponding start time to use for an animation that is
+   * currently pending with current time |mHoldTime| but should behave
+   * as if it began or resumed playback at timeline time |aReadyTime|.
+   */
+  TimeDuration StartTimeFromReadyTime(const TimeDuration& aReadyTime) const;
+
+  /**
    * Converts a time in the timescale of this Animation's currentTime, to a
    * TimeStamp. Returns a null TimeStamp if the conversion cannot be performed
    * because of the current state of this Animation (e.g. it has no timeline, a
    * zero playbackRate, an unresolved start time etc.) or the value of the time
    * passed-in (e.g. an infinite time).
    */
   TimeStamp AnimationTimeToTimeStamp(const StickyTimeDuration& aTime) const;
 
--- a/gfx/layers/composite/AsyncCompositionManager.cpp
+++ b/gfx/layers/composite/AsyncCompositionManager.cpp
@@ -668,34 +668,19 @@ SampleAnimations(Layer* aLayer, TimeStam
           AnimData& animData = animationData[i];
 
           activeAnimations = true;
 
           MOZ_ASSERT(!animation.startTime().IsNull(),
                      "Failed to resolve start time of pending animations");
           TimeDuration elapsedDuration =
             (aPoint - animation.startTime()).MultDouble(animation.playbackRate());
-          // Skip animations that are yet to start.
-          //
-          // Currently, this should only happen when the refresh driver is under test
-          // control and is made to produce a time in the past or is restored from
-          // test control causing it to jump backwards in time.
-          //
-          // Since activeAnimations is true, this could mean we keep compositing
-          // unnecessarily during the delay, but so long as this only happens while
-          // the refresh driver is under test control that should be ok.
-          if (elapsedDuration.ToSeconds() < 0) {
-            continue;
-          }
-
           TimingParams timing;
           timing.mDuration.emplace(animation.duration());
-          // Currently animations run on the compositor have their delay factored
-          // into their start time, hence the delay is effectively zero.
-          timing.mDelay = TimeDuration(0);
+          timing.mDelay = animation.delay();
           timing.mIterations = animation.iterations();
           timing.mIterationStart = animation.iterationStart();
           timing.mDirection =
             static_cast<dom::PlaybackDirection>(animation.direction());
           // Animations typically only run on the compositor during their active
           // interval but if we end up sampling them outside that range (for
           // example, while they are waiting to be removed) we currently just
           // assume that we should fill.
@@ -704,18 +689,19 @@ SampleAnimations(Layer* aLayer, TimeStam
             AnimationUtils::TimingFunctionToComputedTimingFunction(
               animation.easingFunction());
 
           ComputedTiming computedTiming =
             dom::AnimationEffectReadOnly::GetComputedTimingAt(
               Nullable<TimeDuration>(elapsedDuration), timing,
               animation.playbackRate());
 
-          MOZ_ASSERT(!computedTiming.mProgress.IsNull(),
-                     "iteration progress should not be null");
+          if (computedTiming.mProgress.IsNull()) {
+            continue;
+          }
 
           uint32_t segmentIndex = 0;
           size_t segmentSize = animation.segments().Length();
           AnimationSegment* segment = animation.segments().Elements();
           while (segment->endPortion() < computedTiming.mProgress.Value() &&
                  segmentIndex < segmentSize - 1) {
             ++segment;
             ++segmentIndex;
--- a/gfx/layers/ipc/LayersMessages.ipdlh
+++ b/gfx/layers/ipc/LayersMessages.ipdlh
@@ -173,20 +173,18 @@ struct TransformData {
 };
 
 union AnimationData {
   null_t;
   TransformData;
 };
 
 struct Animation {
-  // Unlike in nsAnimationManager, this start time is at the end of the
-  // delay.  If the delay is changed dynamically, the layer's data will
-  // be updated.
   TimeStamp startTime;
+  TimeDuration delay;
   // The value of the animation's current time at the moment it was created.
   // For animations that are waiting to start, their startTime will be null.
   // Once the animation is ready to start, we calculate an appropriate value
   // of startTime such that we begin playback from initialCurrentTime.
   TimeDuration initialCurrentTime;
   TimeDuration duration;
   // For each frame, the interpolation point is computed based on the
   // startTime, the direction, the duration, and the current time.
--- a/layout/base/nsDisplayList.cpp
+++ b/layout/base/nsDisplayList.cpp
@@ -417,20 +417,21 @@ AddAnimationForProperty(nsIFrame* aFrame
       UpdateStartValueFromReplacedTransition();
   }
 
   const ComputedTiming computedTiming =
     aAnimation->GetEffect()->GetComputedTiming();
   Nullable<TimeDuration> startTime = aAnimation->GetCurrentOrPendingStartTime();
   animation->startTime() = startTime.IsNull()
                            ? TimeStamp()
-                           : aAnimation->AnimationTimeToTimeStamp(
-                              StickyTimeDuration(timing.mDelay));
+                           : aAnimation->GetTimeline()->
+                              ToTimeStamp(startTime.Value());
   animation->initialCurrentTime() = aAnimation->GetCurrentTime().Value()
                                     - timing.mDelay;
+  animation->delay() = timing.mDelay;
   animation->duration() = computedTiming.mDuration;
   animation->iterations() = computedTiming.mIterations;
   animation->iterationStart() = computedTiming.mIterationStart;
   animation->direction() = static_cast<uint8_t>(timing.mDirection);
   animation->property() = aProperty.mProperty;
   animation->playbackRate() = aAnimation->PlaybackRate();
   animation->data() = aData;
   animation->easingFunction() = ToTimingFunction(timing.mFunction);
--- a/layout/reftests/css-transitions/stacking-context-opacity-wins-over-important-style.html
+++ b/layout/reftests/css-transitions/stacking-context-opacity-wins-over-important-style.html
@@ -9,27 +9,34 @@ span {
   width: 100px;
   position: fixed;
   background: green;
   top: 50px;
 }
 #test {
   width: 100px; height: 100px;
   background: blue;
-  transition: opacity 100s steps(1, start);
-  opacity: 0 ! important;
+  /*
+   * On the compositor we use a previous vsync time stamp rather than the
+   * current time stamp, as a result sometimes transition may be still in
+   * before phase after waiting a frame. To compose the same opacity value
+   * regardless of whether the transition is in before or active phase, we use
+   * steps(1, end) here.
+   */
+  transition: opacity 100s steps(1, end);
+  opacity: 1 ! important;
 }
 </style>
 <span></span>
 <div id="test"></div>
 <script>
 window.addEventListener("load", () => {
   var target = document.getElementById("test");
   getComputedStyle(target).opacity;
 
-  target.style.setProperty("opacity", "1", "important");
+  target.style.setProperty("opacity", "0", "important");
   getComputedStyle(target).opacity;
 
   requestAnimationFrame(() => {
     document.documentElement.classList.remove("reftest-wait");
   });
 });
 </script>
--- a/layout/reftests/css-transitions/stacking-context-transform-wins-over-important-style.html
+++ b/layout/reftests/css-transitions/stacking-context-transform-wins-over-important-style.html
@@ -9,27 +9,34 @@ span {
   width: 100px;
   position: fixed;
   background: green;
   top: 50px;
 }
 #test {
   width: 100px; height: 100px;
   background: blue;
-  transition: transform 100s steps(1, start);
-  transform: translateX(200px) ! important;
+  /*
+   * On the compositor we use a previous vsync time stamp rather than the
+   * current time stamp, as a result sometimes transition may be still in
+   * before phase after waiting a frame. To compose the same transform value
+   * regardless of whether the transition is in before or active phase, we use
+   * steps(1, end) here.
+   */
+  transition: transform 100s steps(1, end);
+  transform: none ! important;
 }
 </style>
 <span></span>
 <div id="test"></div>
 <script>
 window.addEventListener("load", () => {
   var target = document.getElementById("test");
   getComputedStyle(target).transform;
 
-  target.style.setProperty("transform", "none", "important");
+  target.style.setProperty("transform", "translateX(200px)", "important");
   getComputedStyle(target).transform;
 
   requestAnimationFrame(() => {
     document.documentElement.classList.remove("reftest-wait");
   });
 });
 </script>