Bug 1425837 - Part 8: Test that individual transforms run on the compositor thread. r=hiro,birtles
authorBoris Chiou <boris.chiou@gmail.com>
Mon, 18 Mar 2019 18:05:04 +0000
changeset 464941 9fe743aa6c94f49346902934f40f8f55663bc54d
parent 464940 af39dc228d19a34892e32faab8fb105352708099
child 464942 08126c1f53fe82ab685bd3e75e97ce7bf516a3d9
push id80792
push userbchiou@mozilla.com
push dateTue, 19 Mar 2019 08:04:11 +0000
treeherderautoland@08126c1f53fe [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewershiro, birtles
bugs1425837
milestone68.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1425837 - Part 8: Test that individual transforms run on the compositor thread. r=hiro,birtles This also adds the missing preference in test_transition_per_property.html. Depends on D22567 Differential Revision: https://phabricator.services.mozilla.com/D22568
dom/animation/test/chrome.ini
dom/animation/test/chrome/test_running_on_compositor.html
dom/base/nsDOMWindowUtils.cpp
layout/style/test/animation_utils.js
layout/style/test/mochitest.ini
layout/style/test/test_animations_omta.html
layout/style/test/test_transitions_per_property.html
--- a/dom/animation/test/chrome.ini
+++ b/dom/animation/test/chrome.ini
@@ -1,12 +1,13 @@
 [DEFAULT]
 prefs =
   dom.animations-api.compositing.enabled=true
   gfx.omta.background-color=true
+  layout.css.individual-transform.enabled=true
 support-files =
   testcommon.js
   ../../imptests/testharness.js
   ../../imptests/testharnessreport.js
   !/dom/animation/test/chrome/file_animate_xrays.html
 
 [chrome/test_animate_xrays.html]
 # file_animate_xrays.html needs to go in mochitest.ini since it is served
--- a/dom/animation/test/chrome/test_running_on_compositor.html
+++ b/dom/animation/test/chrome/test_running_on_compositor.html
@@ -1052,10 +1052,95 @@ promise_test(async t => {
   await waitForPaints();
 
   assert_animation_is_not_running_on_compositor(animation,
     'background-color animation should NOT be running on the compositor ' +
     'if the pref is disabled');
 }, 'background-color animation does not run on the compositor if the pref ' +
    'is disabled');
 
+promise_test(async t => {
+  const div = addDiv(t);
+  const animation = div.animate({ translate: ['0px', '100px'] },
+                                100 * MS_PER_SEC);
+
+  await waitForAnimationReadyToRestyle(animation);
+  await waitForPaints();
+
+  assert_animation_is_running_on_compositor(animation,
+    'translate animation should be running on the compositor');
+}, 'translate animation runs on the compositor');
+
+promise_test(async t => {
+  const div = addDiv(t);
+  const animation = div.animate({ rotate: ['0deg', '45deg'] },
+                                100 * MS_PER_SEC);
+
+  await waitForAnimationReadyToRestyle(animation);
+  await waitForPaints();
+
+  assert_animation_is_running_on_compositor(animation,
+    'rotate animation should be running on the compositor');
+}, 'rotate animation runs on the compositor');
+
+promise_test(async t => {
+  const div = addDiv(t);
+  const animation = div.animate({ scale: ['1 1', '2 2'] },
+                                100 * MS_PER_SEC);
+
+  await waitForAnimationReadyToRestyle(animation);
+  await waitForPaints();
+
+  assert_animation_is_running_on_compositor(animation,
+    'scale animation should be running on the compositor');
+}, 'scale animation runs on the compositor');
+
+promise_test(async t => {
+  const div = addDiv(t);
+  const animation = div.animate({ translate: ['0px', '100px'],
+                                  rotate: ['0deg', '45deg'],
+                                  transform: ['translate(20px)',
+                                              'translate(30px)'] },
+                                100 * MS_PER_SEC);
+
+  await waitForAnimationReadyToRestyle(animation);
+  await waitForPaints();
+
+  assert_animation_is_running_on_compositor(animation,
+    'multiple transform-like properties animation should be running on the ' +
+    'compositor');
+
+  const properties = animation.effect.getProperties();
+  properties.forEach(property => {
+    assert_true(property.runningOnCompositor,
+                property.property + ' is running on the compositor');
+  });
+}, 'Multiple transform-like properties animation runs on the compositor');
+
+promise_test(async t => {
+  const div = addDiv(t);
+  const animation = div.animate({ translate: ['0px', '100px'],
+                                  rotate: ['0deg', '45deg'],
+                                  transform: ['translate(20px)',
+                                              'translate(30px)'] },
+                                100 * MS_PER_SEC);
+
+  div.style.setProperty('translate', '50px', 'important');
+  getComputedStyle(div).translate;
+
+  await waitForAnimationReadyToRestyle(animation);
+  await waitForPaints();
+
+  assert_animation_is_not_running_on_compositor(animation,
+     'Animation overridden by an !important rule reports that it is ' +
+     'NOT running on the compositor');
+
+  const properties = animation.effect.getProperties();
+  properties.forEach(property => {
+    assert_true(!property.runningOnCompositor,
+                property.property + ' is not running on the compositor');
+  });
+}, 'Multiple transform-like properties animation does not runs on the ' +
+   'compositor because one of the transform-like property is overridden ' +
+   'by an !important rule');
+
 </script>
 </body>
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -3407,17 +3407,20 @@ nsDOMWindowUtils::GetOMTAStyle(Element* 
 
     if (aProperty.EqualsLiteral("opacity")) {
       OMTAValue value = GetOMTAValue(frame, DisplayItemType::TYPE_OPACITY,
                                      GetWebRenderBridge());
       if (value.type() == OMTAValue::Tfloat) {
         cssValue = new nsROCSSPrimitiveValue;
         cssValue->SetNumber(value.get_float());
       }
-    } else if (aProperty.EqualsLiteral("transform")) {
+    } else if (aProperty.EqualsLiteral("transform") ||
+               aProperty.EqualsLiteral("translate") ||
+               aProperty.EqualsLiteral("rotate") ||
+               aProperty.EqualsLiteral("scale")) {
       OMTAValue value = GetOMTAValue(frame, DisplayItemType::TYPE_TRANSFORM,
                                      GetWebRenderBridge());
       if (value.type() == OMTAValue::TMatrix4x4) {
         cssValue = nsComputedDOMStyle::MatrixToCSSValue(value.get_Matrix4x4());
       }
     } else if (aProperty.EqualsLiteral("background-color")) {
       OMTAValue value = GetOMTAValue(
           frame, DisplayItemType::TYPE_BACKGROUND_COLOR, GetWebRenderBridge());
--- a/layout/style/test/animation_utils.js
+++ b/layout/style/test/animation_utils.js
@@ -418,40 +418,52 @@ const ExpectComparisonTo = {
 
   // Many callers of this method will pass 'undefined' for
   // expectedComparisonResult.
   window.omta_is_approx = function(elem, property, expected, tolerance,
                                    runningOn, desc, expectedComparisonResult,
                                    pseudo) {
     // Check input
     // FIXME: Auto generate this array.
-    const omtaProperties = [ "transform", "opacity", "background-color" ];
+    const omtaProperties = [ "transform", "translate", "rotate", "scale",
+                             "opacity", "background-color" ];
     if (!omtaProperties.includes(property)) {
       ok(false, property + " is not an OMTA property");
       return;
     }
     var normalize;
     var compare;
     var normalizedToString = JSON.stringify;
     switch (property) {
       case "transform":
+      case "translate":
+      case "rotate":
+      case "scale":
         normalize = convertTo3dMatrix;
         compare = matricesRoughlyEqual;
         normalizedToString = convert3dMatrixToString;
         break;
       case "opacity":
         normalize = parseFloat;
         compare = function(a, b, error) { return Math.abs(a - b) <= error; };
         break;
       default:
-        normalize = function(value) { return value; };
+        normalize = value => value;
         compare = function(a, b, error) { return a == b; };
         break;
     }
 
+    if (!!expected.compositorValue) {
+      const originalNormalize = normalize;
+      normalize = value =>
+        !!value.compositorValue
+          ? originalNormalize(value.compositorValue)
+          : originalNormalize(value);
+    }
+
     // Get actual values
     var compositorStr =
       SpecialPowers.DOMWindowUtils.getOMTAStyle(elem, property, pseudo);
     var computedStr = window.getComputedStyle(elem, pseudo)[property];
 
     // Prepare expected value
     var expectedValue = normalize(expected);
     if (expectedValue === null) {
@@ -502,19 +514,50 @@ const ExpectComparisonTo = {
     if (actualValue === null) {
       ok(false, desc + ": should return a valid result - got " + actualStr);
       return;
     }
     okOrTodo(compare(expectedValue, actualValue, tolerance),
              desc + " - got " + actualStr + ", expected " +
              normalizedToString(expectedValue));
 
-    // For compositor animations do an additional check that they match
-    // the value calculated on the main thread
-    if (actualStr === compositorStr) {
+    // For transform-like properties, if we have multiple transform-like
+    // properties, the OMTA value and getComputedStyle() must be different,
+    // so use this flag to skip the following tests.
+    // FIXME: Putting this property on the expected value is a little bit odd.
+    // It's not really a product of the expected value, but rather the kind of
+    // test we're running. That said, the omta_is, omta_todo_is etc. methods are
+    // already pretty complex and adding another parameter would probably
+    // complicate things too much so this is fine for now. If we extend these
+    // functions any more, though, we should probably reconsider this API.
+    if (expected.usesMultipleProperties) {
+      return;
+    }
+
+    if (typeof expected.computed !== 'undefined') {
+      // For some tests we specify a separate computed value for comparing
+      // with getComputedStyle.
+      //
+      // In particular, we do this for the individual transform functions since
+      // the form returned from getComputedStyle() reflects the individual
+      // properties (e.g. 'translate: 100px') while the form we read back from
+      // the compositor represents the combined result of all the transform
+      // properties as a single transform matrix (e.g. [0, 0, 0, 0, 100, 0]).
+      //
+      // Despite the fact that we can't directly compare the OMTA value against
+      // the getComputedStyle value in this case, it is still worth checking the
+      // result of getComputedStyle since it will help to alert us if some
+      // discrepancy arises between the way we calculate values on the main
+      // thread and compositor.
+      okOrTodo(computedStr == expected.computed,
+               desc + ": Computed style should be equal to " +
+               expected.computed);
+    } else if (actualStr === compositorStr) {
+      // For compositor animations do an additional check that they match
+      // the value calculated on the main thread
       var computedValue = normalize(computedStr);
       if (computedValue === null) {
         ok(false, desc + ": test framework should parse computed style" +
                   " - got " + computedStr);
         return;
       }
       okOrTodo(compare(computedValue, actualValue, 0.0),
                desc + ": OMTA style and computed style should be equal" +
--- a/layout/style/test/mochitest.ini
+++ b/layout/style/test/mochitest.ini
@@ -1,16 +1,17 @@
 [DEFAULT]
 prefs =
   dom.animations-api.compositing.enabled=true
   dom.animations-api.core.enabled=true
   dom.animations-api.getAnimations.enabled=true
   dom.animations-api.implicit-keyframes.enabled=true
   dom.animations-api.timelines.enabled=true
   gfx.omta.background-color=true
+  layout.css.individual-transform.enabled=true
   layout.css.step-position-jump.enabled=true
 support-files =
   animation_utils.js
   ccd-quirks.html
   ccd.sjs
   ccd-standards.html
   chrome/bug418986-2.js
   chrome/match.png
--- a/layout/style/test/test_animations_omta.html
+++ b/layout/style/test/test_animations_omta.html
@@ -2512,10 +2512,103 @@ if (SpecialPowers.DOMWindowUtils.layerMa
        "background-color on <a> element after the link is visited");
 
     extraStyle.remove();
     done_element();
     gDiv = null;
   });
 }
 
+// Normal translate animation.
+addAsyncAnimTest(async function() {
+  new_div("translate: 100px; " +
+          "transition: translate 10s linear");
+  await waitForPaintsFlushed();
+
+  gDiv.style.translate = "200px";
+  await waitForPaintsFlushed();
+
+  omta_is("translate", { compositorValue: { tx: 100 }, computed: "100px" },
+          RunningOn.Compositor,
+          "translate transition runs on compositor thread");
+
+  advance_clock(5000);
+  omta_is("translate", { compositorValue: { tx: 150 }, computed: "150px" },
+          RunningOn.Compositor, "translate on compositor at 5s");
+
+  done_div();
+});
+
+// Normal rotate animation.
+addAsyncAnimTest(async function() {
+  new_div("rotate: 0deg; " +
+          "transition: rotate 10s linear");
+  await waitForPaintsFlushed();
+
+  gDiv.style.rotate = "90deg";
+  await waitForPaintsFlushed();
+
+  omta_is("rotate", { compositorValue: [ 1, 0, 0, 1, 0, 0 ], computed: "0deg"},
+          RunningOn.Compositor, "rotate transition runs on compositor thread");
+
+  advance_clock(5000);
+  omta_is("rotate",
+          { compositorValue: [ Math.cos(Math.PI / 4), Math.sin(Math.PI / 4),
+                              -Math.sin(Math.PI / 4), Math.cos(Math.PI / 4),
+                               0, 0 ],
+            computed: "45deg" }, RunningOn.Compositor,
+          "rotate on compositor at 5s");
+
+  done_div();
+});
+
+// Normal scale animation.
+addAsyncAnimTest(async function() {
+  new_div("scale: 1 1; " +
+          "transition: scale 10s linear");
+  await waitForPaintsFlushed();
+
+  gDiv.style.scale = "2 2";
+  await waitForPaintsFlushed();
+
+  omta_is("scale", { compositorValue: [ 1, 0, 0, 1, 0, 0 ], computed: "1" },
+          RunningOn.Compositor, "scale transition runs on compositor thread");
+
+  advance_clock(5000);
+  omta_is("scale",
+          { compositorValue: [ 1.5, 0, 0, 1.5, 0, 0 ], computed: "1.5" },
+          RunningOn.Compositor, "scale on compositor at 5s");
+
+  done_div();
+});
+
+// Normal multiple transform-like properties animation.
+addAsyncAnimTest(async function() {
+  new_div("translate: 100px; " +
+          "scale: 1 1; " +
+          "transform: translate(200px); " +
+          "transition: all 10s linear");
+  await waitForPaintsFlushed();
+
+  gDiv.style.translate = "200px";
+  gDiv.style.scale = "2 2";
+  gDiv.style.transform = "translate(300px)";
+  await waitForPaintsFlushed();
+
+  omta_is("transform", { compositorValue: { tx: 300 },
+                         usesMultipleProperties: true },
+          RunningOn.Compositor,
+          "transform-like properties transition runs on compositor thread");
+
+  advance_clock(5000);
+  omta_is("transform",
+          // The order is: translate, scale, transform.
+          // So the translate() in transform should be multiplied by 1.5.
+          { compositorValue: [ 1.5, 0, 0, 1.5, (150 + 250*1.5), 0 ],
+            usesMultipleProperties: true },
+          RunningOn.Compositor,
+          "transform-like properties on compositor at 5s");
+
+  done_div();
+});
+
 </script>
 </html>
--- a/layout/style/test/test_transitions_per_property.html
+++ b/layout/style/test/test_transitions_per_property.html
@@ -2836,21 +2836,21 @@ function test_rotate_transition(prop) {
   //
   // We don't test for interpolation of the numbers here since it's quite
   // complicated and this is tested by the web-platform tests for this property.
   // Now that we have web-platform tests for animation properties the main
   // purpose of the tests in this file is to check that transitions run on the
   // properties we expect them to.
   div.style.transitionProperty = 'none';
   div.style[prop] = '0 1 0 45deg';
-  is(cs[prop], '0 1 0 45deg',
+  is(cs[prop], 'y 45deg',
      `rotate property ${prop}: computed value before transition`);
   div.style.transitionProperty = prop;
   div.style[prop] = '0 1 0 145deg';
-  is(cs[prop], '0 1 0 70deg',
+  is(cs[prop], 'y 70deg',
      `rotate property ${prop}: interpolation of angles`);
   check_distance(prop, '0 1 0 45deg', '0 1 0 70deg', '0 1 0 145deg');
 }
 
 function test_scale_transition(prop) {
   // One value: <number>
   test_number_transition(prop);
 
@@ -2878,31 +2878,53 @@ function test_scale_transition(prop) {
 function test_translate_transition(prop) {
   // One value: <length-percentage>
   test_length_transition(prop);
   test_length_unclamped(prop);
   test_percent_transition(prop);
   test_percent_unclamped(prop);
 
   // Two values: <length-percentage> <length-percentage>
+  // Note: Cannot use test_length_percent_pair_transition(prop) and
+  // test_length_percent_calc_transition(prop) because we don't resolve the
+  // percentage.
   test_length_pair_transition(prop);
-  test_length_percent_pair_transition(prop);
-  test_length_percent_calc_transition(prop);
+
+  div.style.setProperty("transition-property", "none", "");
+  div.style.setProperty(prop, "4px 50%", "");
+  is(cs.getPropertyValue(prop), "4px 50%",
+    `length-valued property ${prop}: computed value before transition`);
+  div.style.setProperty("transition-property", prop, "");
+  div.style.setProperty(prop, "12px 70%", "");
+  is(cs.getPropertyValue(prop), "6px 55%",
+    `length-valued property ${prop}: interpolation of lengths`);
+  check_distance(prop, "4px 50%", "6px 55%", "12px 70%");
+
+  div.style.setProperty("transition-property", "none", "");
+  div.style.setProperty(prop, "4px 50%", "");
+  is(cs.getPropertyValue(prop), "4px 50%",
+    `length-valued property ${prop}: computed value before transition`);
+  div.style.setProperty("transition-property", prop, "");
+  div.style.setProperty(prop, "20% 20px", "");
+  is(cs.getPropertyValue(prop), "calc(5% + 3px) calc(37.5% + 5px)",
+    `length-valued property ${prop}: interpolation of lengths`);
+  check_distance(prop, "4px 50%", "calc(5% + 3px) calc(37.5% + 5px)",
+                 "20% 20px");
   // We can't use test_length_percent_pair_unclamped here since
   // it assumes that "0px 0px" is serialized as "0px 0px" but
   // translate should serialize it as "0px".
 
   // Three values: <length-percentage> <length-percentage> <length>
   div.style.transitionProperty = 'none';
   div.style[prop] = '10px 200% 30px';
-  is(cs[prop], '10px 20px 30px',
+  is(cs[prop], '10px 200% 30px',
      `translate property ${prop}: computed value before transition`);
   div.style.transitionProperty = prop;
   div.style[prop] = '50px 600% 70px';
-  is(cs[prop], '20px 30px 40px',
+  is(cs[prop], '20px 300% 40px',
      `translate property ${prop}: interpolation of three values`);
   check_distance(prop, '10px 20px 30px', '20px 30px 40px', '50px 60px 70px');
 }
 
 function test_font_variations_transition(prop) {
   is(prop, "font-variation-settings", "only designed for one property");
 
   div.style.setProperty("transition-property", "none", "");