Bug 1305325 - Part 9: Send animations even if it's paused, finished or zero playback rate. r?birtles. draft
authorHiroyuki Ikezoe <hiikezoe@mozilla-japan.org>
Fri, 02 Dec 2016 15:34:13 +0900
changeset 447344 19c7b6f5bdbd546a20bc348fb0c0bc068b8fc38f
parent 447343 6da935acf4cdb8e80d2e355a69e57417c6d683da
child 447345 170300ec1bb5df532747226fc4bcf0ded53d0185
push id38039
push userhiikezoe@mozilla-japan.org
push dateSat, 03 Dec 2016 23:26:35 +0000
reviewersbirtles
bugs1305325
milestone53.0a1
Bug 1305325 - Part 9: Send animations even if it's paused, finished or zero playback rate. r?birtles. If all of animations on an element are paused, finished or zero playback rate, we don't send those animations to the compositor. Also in this change, we send zero active duration animations to the compositor in the same way as normail animations. MozReview-Commit-ID: CHjv6Buy5fa
dom/animation/Animation.h
dom/animation/EffectCompositor.cpp
dom/animation/KeyframeEffectReadOnly.cpp
gfx/layers/Layers.cpp
gfx/layers/composite/AsyncCompositionManager.cpp
gfx/layers/ipc/LayersMessages.ipdlh
layout/painting/nsDisplayList.cpp
layout/style/test/file_animations_effect_timing_enddelay.html
layout/style/test/test_animations_omta.html
--- a/dom/animation/Animation.h
+++ b/dom/animation/Animation.h
@@ -269,34 +269,23 @@ public:
   {
     return GetEffect() && GetEffect()->IsCurrent();
   }
   bool IsInEffect() const
   {
     return GetEffect() && GetEffect()->IsInEffect();
   }
 
-  /**
-   * Returns true if this animation's playback state makes it a candidate for
-   * running on the compositor.
-   * We send animations to the compositor when their target effect is 'current'
-   * (a definition that is roughly equivalent to when they are in their before
-   * or active phase). However, we don't send animations to the compositor when
-   * they are paused/pausing (including being effectively paused due to
-   * having a zero playback rate), have a zero-duration active interval, or have
-   * no target effect at all.
-   */
-  bool IsPlayableOnCompositor() const
+  bool IsPlaying() const
   {
-    return HasCurrentEffect() &&
-           mPlaybackRate != 0.0 &&
+    return mPlaybackRate != 0.0 &&
            (PlayState() == AnimationPlayState::Running ||
-            mPendingState == PendingState::PlayPending) &&
-           !GetEffect()->IsActiveDurationZero();
+            mPendingState == PendingState::PlayPending);
   }
+
   bool IsRelevant() const { return mIsRelevant; }
   void UpdateRelevance();
 
   /**
    * Returns true if this Animation has a lower composite order than aOther.
    */
   bool HasLowerCompositeOrderThan(const Animation& aOther) const;
 
--- a/dom/animation/EffectCompositor.cpp
+++ b/dom/animation/EffectCompositor.cpp
@@ -51,16 +51,68 @@ NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(
                                cb.Flags());
     }
   }
 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
 
 NS_IMPL_CYCLE_COLLECTION_ROOT_NATIVE(EffectCompositor, AddRef)
 NS_IMPL_CYCLE_COLLECTION_UNROOT_NATIVE(EffectCompositor, Release)
 
+namespace {
+enum class MatchForCompositor {
+  // This animation matches and should run on the compositor if possible.
+  Yes,
+  // This (not currently playing) animation matches and can be run on the
+  // compositor if there are other animations for this property that return
+  // 'Yes'.
+  IfNeeded,
+  // This animation does not match or can't be run on the compositor.
+  No,
+  // This animation does not match or can't be run on the compositor and,
+  // furthermore, its presence means we should not run any animations for this
+  // property on the compositor.
+  NoAndBlockThisProperty
+};
+}
+
+static MatchForCompositor
+IsMatchForCompositor(const KeyframeEffectReadOnly& aEffect,
+                     nsCSSPropertyID aProperty,
+                     const nsIFrame* aFrame)
+{
+  const Animation* animation = aEffect.GetAnimation();
+  MOZ_ASSERT(animation);
+
+  if (!animation->IsRelevant()) {
+    return MatchForCompositor::No;
+  }
+
+  bool isPlaying = animation->IsPlaying();
+
+  // If we are finding animations for transform, check if there are other
+  // animations that should block the transform animation. e.g. geometric
+  // properties' animation. This check should be done regardless of whether
+  // the effect has the target property |aProperty| or not.
+  AnimationPerformanceWarning::Type warningType;
+  if (aProperty == eCSSProperty_transform &&
+      isPlaying &&
+      aEffect.ShouldBlockAsyncTransformAnimations(aFrame, warningType)) {
+    EffectCompositor::SetPerformanceWarning(
+      aFrame, aProperty,
+      AnimationPerformanceWarning(warningType));
+    return MatchForCompositor::NoAndBlockThisProperty;
+  }
+
+  if (!aEffect.HasEffectiveAnimationOfProperty(aProperty)) {
+    return MatchForCompositor::No;
+  }
+
+  return isPlaying ? MatchForCompositor::Yes : MatchForCompositor::IfNeeded;
+}
+
 // Helper function to factor out the common logic from
 // GetAnimationsForCompositor and HasAnimationsForCompositor.
 //
 // Takes an optional array to fill with eligible animations.
 //
 // Returns true if there are eligible animations, false otherwise.
 bool
 FindAnimationsForCompositor(const nsIFrame* aFrame,
@@ -127,55 +179,55 @@ FindAnimationsForCompositor(const nsIFra
         aFrame, aProperty,
         AnimationPerformanceWarning(
           AnimationPerformanceWarning::Type::HasRenderingObserver));
       return false;
     }
     content = content->GetParent();
   }
 
-  bool foundSome = false;
+  bool foundRunningAnimations = false;
   for (KeyframeEffectReadOnly* effect : *effects) {
-    MOZ_ASSERT(effect && effect->GetAnimation());
-    Animation* animation = effect->GetAnimation();
+    MatchForCompositor matchResult =
+      IsMatchForCompositor(*effect, aProperty, aFrame);
 
-    if (!animation->IsPlayableOnCompositor()) {
-      continue;
-    }
-
-    AnimationPerformanceWarning::Type warningType;
-    if (aProperty == eCSSProperty_transform &&
-        effect->ShouldBlockAsyncTransformAnimations(aFrame,
-                                                    warningType)) {
+    if (matchResult == MatchForCompositor::NoAndBlockThisProperty) {
       if (aMatches) {
         aMatches->Clear();
       }
-      EffectCompositor::SetPerformanceWarning(
-        aFrame, aProperty,
-        AnimationPerformanceWarning(warningType));
       return false;
     }
 
-    if (!effect->HasEffectiveAnimationOfProperty(aProperty)) {
+    if (matchResult == MatchForCompositor::No) {
       continue;
     }
 
     if (aMatches) {
-      aMatches->AppendElement(animation);
+      aMatches->AppendElement(effect->GetAnimation());
     }
-    foundSome = true;
+
+    if (matchResult == MatchForCompositor::Yes) {
+      foundRunningAnimations = true;
+    }
   }
 
-  MOZ_ASSERT(!foundSome || !aMatches || !aMatches->IsEmpty(),
+  // If all animations we added were not currently playing animations, don't
+  // send them to the compositor.
+  if (aMatches && !foundRunningAnimations) {
+    aMatches->Clear();
+  }
+
+  MOZ_ASSERT(!foundRunningAnimations || !aMatches || !aMatches->IsEmpty(),
              "If return value is true, matches array should be non-empty");
 
-  if (aMatches && foundSome) {
+  if (aMatches && foundRunningAnimations) {
     aMatches->Sort(AnimationPtrComparator<RefPtr<dom::Animation>>());
   }
-  return foundSome;
+
+  return foundRunningAnimations;
 }
 
 void
 EffectCompositor::RequestRestyle(dom::Element* aElement,
                                  CSSPseudoElementType aPseudoType,
                                  RestyleType aRestyleType,
                                  CascadeLevel aCascadeLevel)
 {
--- a/dom/animation/KeyframeEffectReadOnly.cpp
+++ b/dom/animation/KeyframeEffectReadOnly.cpp
@@ -1277,17 +1277,17 @@ bool
 KeyframeEffectReadOnly::ShouldBlockAsyncTransformAnimations(
   const nsIFrame* aFrame,
   AnimationPerformanceWarning::Type& aPerformanceWarning) const
 {
   // We currently only expect this method to be called for effects whose
   // animations are eligible for the compositor since, Animations that are
   // paused, zero-duration, finished etc. should not block other animations from
   // running on the compositor.
-  MOZ_ASSERT(mAnimation && mAnimation->IsPlayableOnCompositor());
+  MOZ_ASSERT(mAnimation && mAnimation->IsPlaying());
 
   EffectSet* effectSet =
     EffectSet::GetEffectSet(mTarget->mElement, mTarget->mPseudoType);
   for (const AnimationProperty& property : mProperties) {
     // If there is a property for animations level that is overridden by
     // !important rules, it should not block other animations from running
     // on the compositor.
     // NOTE: We don't currently check for !important rules for properties that
--- a/gfx/layers/Layers.cpp
+++ b/gfx/layers/Layers.cpp
@@ -492,18 +492,20 @@ Layer::StartPendingAnimations(const Time
   ForEachNode<ForwardIterator>(
       this,
       [&aReadyTime](Layer *layer)
       {
         bool updated = false;
         for (size_t animIdx = 0, animEnd = layer->mAnimations.Length();
              animIdx < animEnd; animIdx++) {
           Animation& anim = layer->mAnimations[animIdx];
-          if (anim.startTime().IsNull()) {
-            anim.startTime() = aReadyTime - anim.initialCurrentTime();
+
+          // If the animation is play-pending, resolve the start time.
+          if (anim.startTime().IsNull() && !anim.isNotPlaying()) {
+            anim.startTime() = aReadyTime - anim.holdTime() + anim.delay();
             updated = true;
           }
         }
         if (updated) {
           layer->Mutated();
         }
       });
 }
--- a/gfx/layers/composite/AsyncCompositionManager.cpp
+++ b/gfx/layers/composite/AsyncCompositionManager.cpp
@@ -664,23 +664,29 @@ SampleAnimations(Layer* aLayer, TimeStam
 
         // Process in order, since later animations override earlier ones.
         for (size_t i = 0, iEnd = animations.Length(); i < iEnd; ++i) {
           Animation& animation = animations[i];
           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());
+          MOZ_ASSERT(!animation.startTime().IsNull() ||
+                     animation.isNotPlaying(),
+                     "Failed to resolve start time of play-pending animations");
+          // If the animation is not currently playing , e.g. paused or
+          // finished, then use the hold time to stay at the same position.
+          TimeDuration elapsedDuration = animation.isNotPlaying()
+            ? animation.holdTime()
+            : (aPoint - animation.startTime())
+                .MultDouble(animation.playbackRate());
           TimingParams timing;
           timing.mDuration.emplace(animation.duration());
           timing.mDelay = animation.delay();
+          timing.mEndDelay = animation.endDelay();
           timing.mIterations = animation.iterations();
           timing.mIterationStart = animation.iterationStart();
           timing.mDirection =
             static_cast<dom::PlaybackDirection>(animation.direction());
           timing.mFill = static_cast<dom::FillMode>(animation.fillMode());
           timing.mFunction =
             AnimationUtils::TimingFunctionToComputedTimingFunction(
               animation.easingFunction());
--- a/gfx/layers/ipc/LayersMessages.ipdlh
+++ b/gfx/layers/ipc/LayersMessages.ipdlh
@@ -182,21 +182,27 @@ struct TransformData {
 union AnimationData {
   null_t;
   TransformData;
 };
 
 struct Animation {
   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 endDelay;
+  // The value of the animation's current time at the moment it was sent to the
+  // compositor.  This value will be used for below cases:
+  // 1) Animations that are play-pending. Initially these animations will have a
+  //    null |startTime|. Once the animation is read to start (i.e. painting has
+  //    finished), we calculate an appropriate value of |startTime| such that
+  //    playback begins from |holdTime|.
+  // 2) Not playing animations (e.g. paused and finished animations). In this
+  //   case the |holdTime| represents the current time the animation will
+  //   maintain.
+  TimeDuration holdTime;
   TimeDuration duration;
   // For each frame, the interpolation point is computed based on the
   // startTime, the direction, the duration, and the current time.
   // The segments must uniquely cover the portion from 0.0 to 1.0
   AnimationSegment[] segments;
   // Number of times to repeat the animation, including positive infinity.
   // Values <= 0 mean the animation will not play (although events are still
   // dispatched on the main thread).
@@ -207,16 +213,19 @@ struct Animation {
   // This uses dom::FillMode.
   uint8_t fillMode;
   nsCSSPropertyID property;
   AnimationData data;
   float playbackRate;
   // This is used in the transformed progress calculation.
   TimingFunction easingFunction;
   uint8_t iterationComposite;
+  // True if the animation has a fixed current time (e.g. paused and
+  // forward-filling animations).
+  bool isNotPlaying;
 };
 
 // Change a layer's attributes
 struct CommonLayerAttributes {
   IntRect layerBounds;
   LayerIntRegion visibleRegion;
   EventRegions eventRegions;
   TransformMatrix transform;
--- a/layout/painting/nsDisplayList.cpp
+++ b/layout/painting/nsDisplayList.cpp
@@ -389,20 +389,23 @@ static void
 AddAnimationForProperty(nsIFrame* aFrame, const AnimationProperty& aProperty,
                         dom::Animation* aAnimation, Layer* aLayer,
                         AnimationData& aData, bool aPending)
 {
   MOZ_ASSERT(aLayer->AsContainerLayer(), "Should only animate ContainerLayer");
   MOZ_ASSERT(aAnimation->GetEffect(),
              "Should not be adding an animation without an effect");
   MOZ_ASSERT(!aAnimation->GetCurrentOrPendingStartTime().IsNull() ||
+             !aAnimation->IsPlaying() ||
              (aAnimation->GetTimeline() &&
               aAnimation->GetTimeline()->TracksWallclockTime()),
-             "Animation should either have a resolved start time or "
-             "a timeline that tracks wallclock time");
+             "If the animation has an unresolved start time it should either"
+             " be static (so we don't need a start time) or else have a"
+             " timeline capable of converting TimeStamps (so we can calculate"
+             " one later");
   nsStyleContext* styleContext = aFrame->StyleContext();
   nsPresContext* presContext = aFrame->PresContext();
   TransformReferenceBox refBox(aFrame);
 
   layers::Animation* animation =
     aPending ?
     aLayer->AddAnimationForNextTransaction() :
     aLayer->AddAnimation();
@@ -433,31 +436,33 @@ AddAnimationForProperty(nsIFrame* aFrame
 
   const ComputedTiming computedTiming =
     aAnimation->GetEffect()->GetComputedTiming();
   Nullable<TimeDuration> startTime = aAnimation->GetCurrentOrPendingStartTime();
   animation->startTime() = startTime.IsNull()
                            ? TimeStamp()
                            : aAnimation->GetTimeline()->
                               ToTimeStamp(startTime.Value());
-  animation->initialCurrentTime() = aAnimation->GetCurrentTime().Value()
-                                    - timing.mDelay;
+  animation->holdTime() = aAnimation->GetCurrentTime().Value();
+
   animation->delay() = timing.mDelay;
+  animation->endDelay() = timing.mEndDelay;
   animation->duration() = computedTiming.mDuration;
   animation->iterations() = computedTiming.mIterations;
   animation->iterationStart() = computedTiming.mIterationStart;
   animation->direction() = static_cast<uint8_t>(timing.mDirection);
   animation->fillMode() = static_cast<uint8_t>(computedTiming.mFill);
   animation->property() = aProperty.mProperty;
   animation->playbackRate() = aAnimation->PlaybackRate();
   animation->data() = aData;
   animation->easingFunction() = ToTimingFunction(timing.mFunction);
   animation->iterationComposite() =
     static_cast<uint8_t>(aAnimation->GetEffect()->
                          AsKeyframeEffect()->IterationComposite());
+  animation->isNotPlaying() = !aAnimation->IsPlaying();
 
   for (uint32_t segIdx = 0; segIdx < aProperty.mSegments.Length(); segIdx++) {
     const AnimationPropertySegment& segment = aProperty.mSegments[segIdx];
 
     AnimationSegment* animSegment = animation->segments().AppendElement();
     if (aProperty.mProperty == eCSSProperty_transform) {
       animSegment->startState() = InfallibleTArray<TransformFunction>();
       animSegment->endState() = InfallibleTArray<TransformFunction>();
@@ -492,17 +497,17 @@ AddAnimationsForProperty(nsIFrame* aFram
              "inconsistent property flags");
 
   DebugOnly<EffectSet*> effects = EffectSet::GetEffectSet(aFrame);
   MOZ_ASSERT(effects);
 
   // Add from first to last (since last overrides)
   for (size_t animIdx = 0; animIdx < aAnimations.Length(); animIdx++) {
     dom::Animation* anim = aAnimations[animIdx];
-    if (!anim->IsPlayableOnCompositor()) {
+    if (!anim->IsRelevant()) {
       continue;
     }
 
     dom::KeyframeEffectReadOnly* keyframeEffect =
       anim->GetEffect() ? anim->GetEffect()->AsKeyframeEffect() : nullptr;
     MOZ_ASSERT(keyframeEffect,
                "A playing animation should have a keyframe effect");
     const AnimationProperty* property =
--- a/layout/style/test/file_animations_effect_timing_enddelay.html
+++ b/layout/style/test/file_animations_effect_timing_enddelay.html
@@ -96,18 +96,18 @@ addAsyncAnimTest(function *() {
   var [ div ] = new_div("");
   var animation = div.animate(
     [ { transform: 'translate(0px)' }, { transform: 'translate(100px)' } ],
     { duration: 1000, endDelay: 1000, fill: 'forwards' });
   yield waitForPaints();
 
   advance_clock(1500);
   yield waitForPaints();
-  omta_is(div, "transform", { tx: 100 }, RunningOn.MainThread,
-          "The end delay is performed on the main thread");
+  omta_is(div, "transform", { tx: 100 }, RunningOn.Compositor,
+          "The end delay is performed on the compositor thread");
 
   done_div();
 });
 
 addAsyncAnimTest(function *() {
   var [ div ] = new_div("");
   var animation = div.animate(
     [ { transform: 'translate(0px)' }, { transform: 'translate(100px)' } ],
--- a/layout/style/test/test_animations_omta.html
+++ b/layout/style/test/test_animations_omta.html
@@ -1894,17 +1894,17 @@ addAsyncAnimTest(function *() {
  * Bug 1004365 - zero-duration animations
  */
 
 addAsyncAnimTest(function *() {
   new_div("transform: translate(0, 200px); animation: anim4 0s 1s both");
   listen();
   yield waitForPaintsFlushed();
   advance_clock(0);
-  omta_is("transform", { ty: 0 }, RunningOn.MainThread,
+  omta_is("transform", { ty: 0 }, RunningOn.Compositor,
           "transform during backwards fill of zero-duration animation");
   advance_clock(2000); // Skip over animation
   yield waitForPaints();
   omta_is("transform", { ty: 100 }, RunningOn.MainThread,
           "transform during backwards fill of zero-duration animation");
   check_events([{ type: 'animationstart', target: gDiv,
                   animationName: 'anim4', elapsedTime: 0,
                   pseudoElement: "" },
@@ -1949,17 +1949,17 @@ addAsyncAnimTest(function *() {
 // We do however still want to test with an infinite repeat count and zero
 // duration to ensure this does not confuse the screening of OMTA animations.
 addAsyncAnimTest(function *() {
   new_div("transform: translate(0, 200px); " +
           "animation: anim4 0s 1s both infinite");
   listen();
   yield waitForPaintsFlushed();
   advance_clock(0);
-  omta_is("transform", { ty: 0 }, RunningOn.MainThread,
+  omta_is("transform", { ty: 0 }, RunningOn.Compositor,
           "transform during backwards fill of infinitely repeating " +
           "zero-duration animation");
   advance_clock(2000);
   yield waitForPaints();
   omta_is("transform", { ty: 100 }, RunningOn.MainThread,
           "transform during forwards fill of infinitely repeating " +
           "zero-duration animation");
   check_events([{ type: 'animationstart', target: gDiv,