Bug 1279403 - Part 1: Force to apply corresponding change hint if there is no corresponding layer to generate display item even if animation's segment is transform:none or 100% opacity. r? draft
authorHiroyuki Ikezoe <hiikezoe@mozilla-japan.org>
Mon, 20 Jun 2016 15:32:51 +0900
changeset 379964 9787bc23019dc7c540704065acddfdea3baa0d52
parent 379963 310d8c17b3667cd123419b9a3f2f48e5cfbcae46
child 379965 b897aecd13489aaee78d1c966f3b2c07c22ef33b
push id21101
push userhiikezoe@mozilla-japan.org
push dateMon, 20 Jun 2016 06:33:26 +0000
bugs1279403
milestone50.0a1
Bug 1279403 - Part 1: Force to apply corresponding change hint if there is no corresponding layer to generate display item even if animation's segment is transform:none or 100% opacity. r? To create a stacking context for animations on transform:none segment, we need to set NS_FRAME_MAY_BE_TRANSFORMED. The fix is comming in part 2. Note that in case of animations which has properties preventing running on the compositor, e.g., width or height, corresponding layer is not created at all, but even in such cases, we normally set valid change hint for such animations in each tick, i.e. restyles in each tick. For example: div.animate([{ opacity: 1, width: '100px' }, { opacity: 0, width: '200px' }], 1000); This animation causes restyles in every ticks without this patch, this patch does not affect such animations at all. The only animations which will be affected by this patch is that animations which has opacity/transform but did not have those properies. e.g, setting transform by setKeyframes or changing target element from other target which prevents running on the compositor, etc. MozReview-Commit-ID: 78fYqyX8uDX
dom/animation/test/chrome/test_running_on_compositor.html
layout/base/RestyleManager.cpp
layout/reftests/web-animations/reftest.list
layout/reftests/web-animations/stacking-context-animation-changing-target-ref.html
layout/reftests/web-animations/stacking-context-opacity-changing-keyframe.html
layout/reftests/web-animations/stacking-context-opacity-changing-target.html
--- a/dom/animation/test/chrome/test_running_on_compositor.html
+++ b/dom/animation/test/chrome/test_running_on_compositor.html
@@ -442,11 +442,55 @@ promise_test(function(t) {
 
     animation.effect.target = null;
     assert_equals(animation.isRunningOnCompositor, false,
                   'Animation reports that it is NOT running on the ' +
                   'compositor after setting null target');
   });
 }, 'animation is removed from the compositor when setting null target');
 
+promise_test(function(t) {
+  var div = addDiv(t);
+  var animation = div.animate([{ opacity: 1, offset: 0 },
+                               { opacity: 1, offset: 0.99 },
+                               { opacity: 0, offset: 1 }], 100 * MS_PER_SEC);
+
+  var another = addDiv(t);
+
+  return animation.ready.then(function() {
+    assert_equals(animation.isRunningOnCompositor, omtaEnabled,
+                  'Opacity animation on a 100% opacity keyframe reports ' +
+                  'that it is running on the compositor from the begining');
+
+    animation.effect.target = another;
+    return waitForFrame();
+  }).then(function() {
+    assert_equals(animation.isRunningOnCompositor, omtaEnabled,
+                  'Opacity animation on a 100% opacity keyframe keeps ' +
+                  'running on the compositor after changing the target ' +
+                  'element');
+  });
+}, 'opacity animation on a 100% opacity keyframe keeps running on the ' +
+   'compositor after changing the target element');
+
+promise_test(function(t) {
+  var div = addDiv(t);
+  var animation = div.animate({ color: ['red', 'black'] }, 100 * MS_PER_SEC);
+
+  return animation.ready.then(function() {
+    assert_equals(animation.isRunningOnCompositor, false,
+                  'Color animation reports that it is not running on the ' +
+                  'compositor');
+
+    animation.effect.setKeyframes([{ opacity: 1, offset: 0 },
+                                   { opacity: 1, offset: 0.99 },
+                                   { opacity: 0, offset: 1 }]);
+    return waitForFrame();
+  }).then(function() {
+    assert_equals(animation.isRunningOnCompositor, omtaEnabled,
+                  '100% opacity animation switched from property which is ' +
+                  'not running on the compositor is running on the compositor');
+  });
+}, '100% opacity animation switched from property which can not be running on ' +
+   'the compositor is running on the compositor');
 
 </script>
 </body>
--- a/layout/base/RestyleManager.cpp
+++ b/layout/base/RestyleManager.cpp
@@ -2749,16 +2749,24 @@ ElementRestyler::AddLayerChangesForAnima
       // nsChangeHint_UpdateTransformLayer, ApplyRenderingChangeToTree would
       // complain that we're updating a transform layer without a transform).
       if (layerInfo.mLayerType == nsDisplayItem::TYPE_TRANSFORM &&
           !mFrame->StyleDisplay()->HasTransformStyle()) {
         continue;
       }
       hint |= layerInfo.mChangeHint;
     }
+
+    // We consider it's the first paint for the frame if we have an animation
+    // for the property but have no layer.
+    if (!layer &&
+        nsLayoutUtils::HasInEffectAnimationOfProperty(mFrame,
+                                                      layerInfo.mProperty)) {
+      hint |= layerInfo.mChangeHint;
+    }
   }
   if (hint) {
     mChangeList->AppendChange(mFrame, mContent, hint);
   }
 }
 
 void
 ElementRestyler::CaptureChange(nsStyleContext* aOldContext,
--- a/layout/reftests/web-animations/reftest.list
+++ b/layout/reftests/web-animations/reftest.list
@@ -1,3 +1,7 @@
 test-pref(dom.animations-api.core.enabled,true) == 1246046-1.html green-box.html
 test-pref(dom.animations-api.core.enabled,true) == 1267937-1.html 1267937-ref.html
 test-pref(dom.animations-api.core.enabled,true) == stacking-context-transform-none-animation-before-appending-element.html stacking-context-animation-ref.html
+test-pref(dom.animations-api.core.enabled,true) == stacking-context-opacity-changing-keyframe.html stacking-context-animation-ref.html
+test-pref(dom.animations-api.core.enabled,true) == stacking-context-opacity-changing-target.html stacking-context-animation-changing-target-ref.html
+test-pref(layers.offmainthreadcomposition.async-animations,false) test-pref(dom.animations-api.core.enabled,true) == stacking-context-opacity-changing-keyframe.html stacking-context-animation-ref.html
+test-pref(layers.offmainthreadcomposition.async-animations,false) test-pref(dom.animations-api.core.enabled,true) == stacking-context-opacity-changing-target.html stacking-context-animation-changing-target-ref.html
new file mode 100644
--- /dev/null
+++ b/layout/reftests/web-animations/stacking-context-animation-changing-target-ref.html
@@ -0,0 +1,24 @@
+<!DOCTYPE html>
+<title>Reference of testcases for bug 1223658</title>
+<style>
+span {
+  height: 100px;
+  width: 100px;
+  background: green;
+  position: fixed;
+  top: 50px;
+}
+div {
+  height: 100px;
+  width: 100px;
+  background: blue;
+}
+#first {
+}
+#second {
+  position: fixed;
+}
+</style>
+<span></span>
+<div id="first"></div>
+<div id="second"></div>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/web-animations/stacking-context-opacity-changing-keyframe.html
@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<title>Changing keyframes to opacity frames creates a stacking context</title>
+<style>
+span {
+  height: 100px;
+  width: 100px;
+  position: fixed;
+  background: green;
+  top: 50px;
+}
+#test {
+  width: 100px; height: 100px;
+  background: blue;
+}
+</style>
+<span></span>
+<div id="test"></div>
+<script>
+  var anim = document.getElementById("test")
+    .animate({ }, { duration: 100000 });
+  anim.ready.then(function() {
+    anim.effect.setKeyframes({ opacity: [1, 1] });
+    requestAnimationFrame(function() {
+      document.documentElement.classList.remove("reftest-wait");
+    });
+  });
+</script>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/web-animations/stacking-context-opacity-changing-target.html
@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html class="reftest-wait">
+<title>
+Opacity animation creates a stacking context when changing its target
+</title>
+<style>
+span {
+  height: 100px;
+  width: 100px;
+  position: fixed;
+  background: green;
+  top: 50px;
+}
+div {
+  width: 100px; height: 100px;
+  background: blue;
+}
+</style>
+<span></span>
+<div id="test"></div>
+<div id="another"></div>
+<script>
+  var anim = document.getElementById("test")
+    .animate({ opacity: [1, 1] }, { duration: 100000 });
+  anim.ready.then(function() {
+    anim.effect.target = document.getElementById("another");
+    requestAnimationFrame(function() {
+      document.documentElement.classList.remove("reftest-wait");
+    });
+  });
+</script>