Bug 1437272 - Don't throttle finite transform animations in out-of-view element. r?birtles draft
authorHiroyuki Ikezoe <hikezoe@mozilla.com>
Thu, 08 Mar 2018 18:22:45 +0900
changeset 764794 be24082f7c61459feafb768b6e5571c9f1131997
parent 764793 d67534099f1ce6f028191ca7417f8c45b2f8d024
push id101847
push userhikezoe@mozilla.com
push dateThu, 08 Mar 2018 09:24:42 +0000
reviewersbirtles
bugs1437272
milestone60.0a1
Bug 1437272 - Don't throttle finite transform animations in out-of-view element. r?birtles Transform animation in out-of-view element might move in visible area so if we throttle it the transform animation might come in view suddenly. So we don't throttle transform animations in ouf-of-view element anymore if it's not infinite. Finite animations don't last so that they won't consume CPU so much time. MozReview-Commit-ID: HaMjmxqXPIK
dom/animation/AnimationEffectReadOnly.h
dom/animation/KeyframeEffectReadOnly.cpp
dom/animation/test/mozilla/file_restyles.html
--- a/dom/animation/AnimationEffectReadOnly.h
+++ b/dom/animation/AnimationEffectReadOnly.h
@@ -46,16 +46,20 @@ public:
   {
     return nullptr;
   }
 
   nsISupports* GetParentObject() const { return mDocument; }
 
   bool IsCurrent() const;
   bool IsInEffect() const;
+  bool HasFiniteActiveDuration() const
+  {
+    return SpecifiedTiming().ActiveDuration() != TimeDuration::Forever();
+  }
 
   already_AddRefed<AnimationEffectTimingReadOnly> Timing();
   const TimingParams& SpecifiedTiming() const
   {
     return mTiming->AsTimingParams();
   }
   void SetSpecifiedTiming(const TimingParams& aTiming);
 
--- a/dom/animation/KeyframeEffectReadOnly.cpp
+++ b/dom/animation/KeyframeEffectReadOnly.cpp
@@ -1468,16 +1468,23 @@ KeyframeEffectReadOnly::CanThrottle() co
 
     const bool isVisibilityHidden =
       !frame->IsVisibleOrMayHaveVisibleDescendants();
     if (isVisibilityHidden ||
         frame->IsScrolledOutOfView()) {
       // If there are transform change hints, unthrottle the animation
       // periodically since it might affect the overflow region.
       if (HasTransformThatMightAffectOverflow()) {
+        // Don't throttle finite transform animations since the animation might
+        // suddenly come into view and if it was throttled it will be
+        // out-of-sync.
+        if (HasFiniteActiveDuration()) {
+          return false;
+        }
+
         return isVisibilityHidden
           ? CanThrottleTransformChangesInScrollable(*frame)
           : CanThrottleTransformChanges(*frame);
       }
       return true;
     }
   }
 
--- a/dom/animation/test/mozilla/file_restyles.html
+++ b/dom/animation/test/mozilla/file_restyles.html
@@ -399,17 +399,17 @@ waitForAllPaints(() => {
         return;
       }
 
       await SpecialPowers.pushPrefEnv({ set: [["ui.showHideScrollbars", 1]] });
 
       var parentElement = addDiv(null,
         { style: 'overflow-y: scroll; height: 20px;' });
       var div = addDiv(null,
-        { style: 'animation: rotate 100s; position: relative; top: 100px;' });
+        { style: 'animation: rotate 100s infinite; position: relative; top: 100px;' });
       parentElement.appendChild(div);
       var animation = div.getAnimations()[0];
       var timeAtStart = document.timeline.currentTime;
 
       ok(!animation.isRunningOnCompositor,
          'The transform animation is not running on the compositor');
 
       var markers;
@@ -445,17 +445,17 @@ waitForAllPaints(() => {
       await SpecialPowers.pushPrefEnv({ set: [["ui.showHideScrollbars", 1]] });
 
       // Make sure we start from the state right after requestAnimationFrame.
       await waitForFrame();
 
       var parentElement = addDiv(null,
         { style: 'overflow-y: scroll; height: 20px;' });
       var div = addDiv(null,
-        { style: 'animation: rotate 100s; position: relative; top: 100px;' });
+        { style: 'animation: rotate 100s infinite; position: relative; top: 100px;' });
       parentElement.appendChild(div);
       var animation = div.getAnimations()[0];
       var timeAtStart = document.timeline.currentTime;
 
       ok(!animation.isRunningOnCompositor,
          'The transform animation is not running on the compositor');
 
       var markers;
@@ -498,17 +498,17 @@ waitForAllPaints(() => {
       // conformant Promise micro task.
       if (isAndroid) {
         return;
       }
 
       var parentElement = addDiv(null,
         { style: 'overflow: hidden;' });
       var div = addDiv(null,
-        { style: 'animation: move-in 100s;' });
+        { style: 'animation: move-in 100s infinite;' });
       parentElement.appendChild(div);
       var animation = div.getAnimations()[0];
       var timeAtStart = document.timeline.currentTime;
 
       ok(!animation.isRunningOnCompositor,
          'The transform animation on out of view element ' +
          'is not running on the compositor');
 
@@ -543,17 +543,17 @@ waitForAllPaints(() => {
       }
 
       // Make sure we start from the state right after requestAnimationFrame.
       await waitForFrame();
 
       var parentElement = addDiv(null,
         { style: 'overflow: hidden;' });
       var div = addDiv(null,
-        { style: 'animation: move-in 100s;' });
+        { style: 'animation: move-in 100s infinite;' });
       parentElement.appendChild(div);
       var animation = div.getAnimations()[0];
       var timeAtStart = document.timeline.currentTime;
 
       ok(!animation.isRunningOnCompositor,
          'The transform animation on out of view element ' +
          'is not running on the compositor');
 
@@ -580,16 +580,37 @@ waitForAllPaints(() => {
          'Transform animation running on out of view element ' +
          'should be unthrottled after around 200ms have elapsed. now: ' +
          now + ' start time: ' + timeAtStart);
 
       await ensureElementRemoval(parentElement);
     }
   );
 
+  add_task(async function finite_transform_animations_in_out_of_view_element() {
+    var parentElement = addDiv(null, { style: 'overflow: hidden;' });
+    var div = addDiv(null);
+    var animation =
+      div.animate({ transform: [ 'translateX(120%)', 'translateX(100%)' ] },
+                                // This animation will move a bit but
+                                // will remain out-of-view.
+                  100 * MS_PER_SEC);
+    parentElement.appendChild(div);
+
+    await animation.ready;
+    ok(!SpecialPowers.wrap(animation).isRunningOnCompositor);
+
+    var markers = await observeStyling(20);
+    is(markers.length, 20,
+       'Finite transform animation in out-of-view element should never be ' +
+       'throttled');
+
+    await ensureElementRemoval(parentElement);
+  });
+
   add_task(async function restyling_main_thread_animations_in_scrolled_out_element() {
     var parentElement = addDiv(null,
       { style: 'overflow-y: scroll; height: 20px;' });
     var div = addDiv(null,
       { style: 'animation: background-color 100s; position: relative; top: 20px;' });
     parentElement.appendChild(div);
     var animation = div.getAnimations()[0];