Bug 1504065 - Support background-color animations on the compositor for nsIDOMWindowUtils::GetOMTAValue. r=birtles
authorHiroyuki Ikezoe <hikezoe@mozilla.com>
Wed, 28 Nov 2018 00:59:15 +0000
changeset 448461 a30f77050399cefc43236e7c552760ce6a68275a
parent 448460 d99d8f275d8bfa954671ea2663bbe37a561d7583
child 448462 94ecc40729d0302ee3953dbab0d280d4a6b174c4
push id35114
push usernbeleuzu@mozilla.com
push dateWed, 28 Nov 2018 09:51:41 +0000
treeherdermozilla-central@9234dc84cd93 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbirtles
bugs1504065
milestone65.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 1504065 - Support background-color animations on the compositor for nsIDOMWindowUtils::GetOMTAValue. r=birtles Depends on D13001 Differential Revision: https://phabricator.services.mozilla.com/D13002
dom/base/nsDOMWindowUtils.cpp
gfx/layers/AnimationHelper.cpp
gfx/layers/AnimationHelper.h
gfx/layers/ipc/LayersMessages.ipdlh
layout/style/nsComputedDOMStyle.cpp
layout/style/nsComputedDOMStyle.h
layout/style/test/animation_utils.js
layout/style/test/mochitest.ini
layout/style/test/test_animations_omta.html
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -3728,16 +3728,24 @@ nsDOMWindowUtils::GetOMTAStyle(Element* 
       }
     } else if (aProperty.EqualsLiteral("transform")) {
       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());
+      if (value.type() == OMTAValue::Tnscolor) {
+        cssValue = new nsROCSSPrimitiveValue;
+        nsComputedDOMStyle::SetToRGBAColor(cssValue, value.get_nscolor());
+      }
     }
   }
 
   if (cssValue) {
     nsString text;
     ErrorResult rv;
     cssValue->GetCssText(text, rv);
     aResult.Assign(text);
--- a/gfx/layers/AnimationHelper.cpp
+++ b/gfx/layers/AnimationHelper.cpp
@@ -50,16 +50,19 @@ CompositorAnimationStorage::GetOMTAValue
 {
   OMTAValue omtaValue = mozilla::null_t();
   auto animatedValue = GetAnimatedValue(aId);
   if (!animatedValue) {
     return omtaValue;
   }
 
   switch (animatedValue->mType) {
+    case AnimatedValue::COLOR:
+      omtaValue = animatedValue->mColor;
+      break;
     case AnimatedValue::OPACITY:
       omtaValue = animatedValue->mOpacity;
       break;
     case AnimatedValue::TRANSFORM: {
       gfx::Matrix4x4 transform = animatedValue->mTransform.mFrameTransform;
       const TransformData& data = animatedValue->mTransform.mData;
       float scale = data.appUnitsPerDevPixel();
       gfx::Point3D transformOrigin = data.transformOrigin();
@@ -114,16 +117,28 @@ CompositorAnimationStorage::SetAnimatedV
   const TransformData dontCare = {};
   SetAnimatedValue(aId,
                    std::move(aTransformInDevSpace),
                    gfx::Matrix4x4(),
                    dontCare);
 }
 
 void
+CompositorAnimationStorage::SetAnimatedValue(uint64_t aId, nscolor aColor)
+{
+  MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
+  auto count = mAnimatedValues.Count();
+  AnimatedValue* value = mAnimatedValues.LookupOrAdd(aId, aColor);
+  if (count == mAnimatedValues.Count()) {
+    MOZ_ASSERT(value->mType == AnimatedValue::COLOR);
+    value->mColor = aColor;
+  }
+}
+
+void
 CompositorAnimationStorage::SetAnimatedValue(uint64_t aId,
                                              const float& aOpacity)
 {
   MOZ_ASSERT(CompositorThreadHolder::IsInCompositorThread());
   auto count = mAnimatedValues.Count();
   AnimatedValue* value = mAnimatedValues.LookupOrAdd(aId, aOpacity);
   if (count == mAnimatedValues.Count()) {
     MOZ_ASSERT(value->mType == AnimatedValue::OPACITY);
--- a/gfx/layers/AnimationHelper.h
+++ b/gfx/layers/AnimationHelper.h
@@ -50,22 +50,24 @@ struct AnimationTransform {
   gfx::Matrix4x4 mFrameTransform;
   TransformData mData;
 };
 
 struct AnimatedValue {
   enum {
     TRANSFORM,
     OPACITY,
+    COLOR,
     NONE
   } mType {NONE};
 
   union {
     AnimationTransform mTransform;
     float mOpacity;
+    nscolor mColor;
   };
 
   AnimatedValue(gfx::Matrix4x4&& aTransformInDevSpace,
                 gfx::Matrix4x4&& aFrameTransform,
                 const TransformData& aData)
     : mType(AnimatedValue::TRANSFORM)
     , mOpacity(0.0)
   {
@@ -75,16 +77,22 @@ struct AnimatedValue {
   }
 
   explicit AnimatedValue(const float& aValue)
     : mType(AnimatedValue::OPACITY)
     , mOpacity(aValue)
   {
   }
 
+  explicit AnimatedValue(nscolor aValue)
+    : mType(AnimatedValue::COLOR)
+    , mColor(aValue)
+  {
+  }
+
   ~AnimatedValue() {}
 
 private:
   AnimatedValue() = delete;
 };
 
 // CompositorAnimationStorage stores the animations and animated values
 // keyed by a CompositorAnimationsId. The "animations" are a representation of
@@ -124,16 +132,21 @@ public:
                         gfx::Matrix4x4&& aTransformInDevSpace);
 
   /**
    * Set the animation opacity based on the unique id
    */
   void SetAnimatedValue(uint64_t aId, const float& aOpacity);
 
   /**
+   * Set the animation color based on the unique id
+   */
+  void SetAnimatedValue(uint64_t aId, nscolor aColor);
+
+  /**
    * Return the animated value if a given id can map to its animated value
    */
   AnimatedValue* GetAnimatedValue(const uint64_t& aId) const;
 
   OMTAValue GetOMTAValue(const uint64_t& aId) const;
 
   /**
    * Return the iterator of animated value table
--- a/gfx/layers/ipc/LayersMessages.ipdlh
+++ b/gfx/layers/ipc/LayersMessages.ipdlh
@@ -562,14 +562,15 @@ struct TransactionInfo
 
 union MaybeTransform {
   Matrix4x4;
   void_t;
 };
 
 union OMTAValue {
   null_t;
+  nscolor;
   float;
   Matrix4x4;
 };
 
 } // namespace
 } // namespace
--- a/layout/style/nsComputedDOMStyle.cpp
+++ b/layout/style/nsComputedDOMStyle.cpp
@@ -1112,17 +1112,17 @@ nsComputedDOMStyle::DoGetBinding()
 }
 
 already_AddRefed<CSSValue>
 nsComputedDOMStyle::DoGetBottom()
 {
   return GetOffsetWidthFor(eSideBottom);
 }
 
-void
+/* static */ void
 nsComputedDOMStyle::SetToRGBAColor(nsROCSSPrimitiveValue* aValue,
                                    nscolor aColor)
 {
   nsAutoString string;
   const bool hasAlpha = NS_GET_A(aColor) != 255;
   if (hasAlpha) {
     string.AppendLiteral("rgba(");
   } else {
--- a/layout/style/nsComputedDOMStyle.h
+++ b/layout/style/nsComputedDOMStyle.h
@@ -126,16 +126,17 @@ public:
                                      mozilla::MutationClosureData*) override;
   virtual nsIDocument* DocToUpdate() override;
 
   nsDOMCSSDeclaration::ParsingEnvironment
   GetParsingEnvironment(nsIPrincipal* aSubjectPrincipal) const final;
 
   static already_AddRefed<nsROCSSPrimitiveValue>
     MatrixToCSSValue(const mozilla::gfx::Matrix4x4& aMatrix);
+  static void SetToRGBAColor(nsROCSSPrimitiveValue* aValue, nscolor aColor);
 
   static void RegisterPrefChangeCallbacks();
   static void UnregisterPrefChangeCallbacks();
 
   // nsIMutationObserver
   NS_DECL_NSIMUTATIONOBSERVER_PARENTCHAINCHANGED
 
 private:
@@ -423,17 +424,16 @@ private:
   already_AddRefed<CSSValue> DoGetPaintOrder();
 
 
   // For working around a MSVC bug. See related comment in
   // GenerateComputedDOMStyleGenerated.py.
   already_AddRefed<CSSValue> DummyGetter();
 
   /* Helper functions */
-  void SetToRGBAColor(nsROCSSPrimitiveValue* aValue, nscolor aColor);
   void SetValueFromComplexColor(nsROCSSPrimitiveValue* aValue,
                                 const mozilla::StyleComplexColor& aColor);
   void SetValueToPositionCoord(const mozilla::Position::Coord& aCoord,
                                nsROCSSPrimitiveValue* aValue);
   void SetValueToPosition(const mozilla::Position& aPosition,
                           nsDOMCSSValueList* aValueList);
   void SetValueToURLValue(const mozilla::css::URLValue* aURL,
                           nsROCSSPrimitiveValue* aValue);
--- a/layout/style/test/animation_utils.js
+++ b/layout/style/test/animation_utils.js
@@ -417,29 +417,40 @@ 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
-    const omtaProperties = [ "transform", "opacity" ];
+    // FIXME: Auto generate this array.
+    const omtaProperties = [ "transform", "opacity", "background-color" ];
     if (!omtaProperties.includes(property)) {
       ok(false, property + " is not an OMTA property");
       return;
     }
-    var isTransform = property == "transform";
-    var normalize = isTransform ? convertTo3dMatrix : parseFloat;
-    var compare = isTransform ?
-                  matricesRoughlyEqual :
-                  function(a, b, error) { return Math.abs(a - b) <= error; };
-    var normalizedToString = isTransform ?
-                             convert3dMatrixToString :
-                             JSON.stringify;
+    var normalize;
+    var compare;
+    var normalizedToString = JSON.stringify;
+    switch (property) {
+      case "transform":
+        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; };
+        compare = function(a, b, error) { return a == b; };
+        break;
+    }
 
     // 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);
--- a/layout/style/test/mochitest.ini
+++ b/layout/style/test/mochitest.ini
@@ -1,15 +1,16 @@
 [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.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
@@ -2386,10 +2386,127 @@ addAsyncAnimTest(async function() {
   // compositor thread.
   await waitForPaints();
   omta_is("transform", { tx: 200 }, RunningOn.MainThread,
           "transition runs on main thread at end of active interval");
 
   done_div();
 });
 
+if (SpecialPowers.DOMWindowUtils.layerManagerType != 'WebRender') {
+  // Normal background-color animation.
+  addAsyncAnimTest(async function() {
+    new_div("background-color: rgb(255, 0, 0); " +
+            "transition: background-color 10s linear");
+    await waitForPaintsFlushed();
+
+    gDiv.style.backgroundColor = "rgb(0, 255, 0)";
+    await waitForPaintsFlushed();
+
+    omta_is("background-color", "rgb(255, 0, 0)", RunningOn.Compositor,
+            "background-color transition runs on compositor thread");
+
+    advance_clock(5000);
+    omta_is("background-color", "rgb(128, 128, 0)", RunningOn.Compositor,
+            "background-color on compositor at 5s");
+
+    done_div();
+  });
+
+  // background-color animation with currentColor.
+  addAsyncAnimTest(async function() {
+    new_div("color: rgb(255, 0, 0); " +
+            "background-color: currentColor; " +
+            "transition: background-color 10s linear");
+    await waitForPaintsFlushed();
+
+    gDiv.style.backgroundColor = "rgb(0, 255, 0)";
+    await waitForPaintsFlushed();
+
+    omta_is("background-color", "rgb(255, 0, 0)", RunningOn.Compositor,
+            "background-color transition starting with current-color runs on " +
+            "compositor thread");
+
+    advance_clock(5000);
+    omta_is("background-color", "rgb(128, 128, 0)", RunningOn.Compositor,
+            "background-color on compositor at 5s");
+
+    done_div();
+  });
+
+  // Tests that a background-color animation from inherited currentColor to
+  // a normal color on the compositor is updated when the parent color is
+  // changed.
+  addAsyncAnimTest(async function() {
+    new_div("");
+    const parent = document.createElement("div");
+    gDiv.parentNode.insertBefore(parent, gDiv);
+    parent.style.color = "rgb(255, 0, 0)";
+    parent.appendChild(gDiv);
+
+    gDiv.animate({ backgroundColor: [ "currentColor", "rgb(0, 255, 0)" ] }, 1000);
+
+    await waitForPaintsFlushed();
+
+    omta_is("background-color", "rgb(255, 0, 0)", RunningOn.Compositor,
+            "background-color animation starting with current-color runs on " +
+            "compositor thread");
+
+    advance_clock(500);
+
+    omta_is("background-color", "rgb(128, 128, 0)", RunningOn.Compositor,
+            "background-color on compositor at 5s");
+
+    // Change the parent's color in the middle of the animation.
+    parent.style.color = "rgb(0, 0, 255)";
+    await waitForPaintsFlushed();
+
+    omta_is("background-color", "rgb(0, 128, 128)", RunningOn.Compositor,
+            "background-color on compositor is reflected by the parent's " +
+            "color change");
+
+    done_div();
+    parent.remove();
+  });
+
+  // Tests that a background-color animation from currentColor to a normal color
+  // on <a> element is updated when the link is visited.
+  addAsyncAnimTest(async function() {
+    [ gDiv ] = new_element("a", "display: block");
+    gDiv.setAttribute("href", "not-exist.html");
+    gDiv.classList.add("visited");
+
+    const extraStyle = document.createElement('style');
+    document.head.appendChild(extraStyle);
+    extraStyle.sheet.insertRule(".visited:visited { color: rgb(0, 0, 255); }", 0);
+    extraStyle.sheet.insertRule(".visited:link { color: rgb(255, 0, 0); }", 1);
+
+    gDiv.animate({ backgroundColor: [ "currentColor", "rgb(0, 255, 0)" ] }, 1000);
+    await waitForPaintsFlushed();
+
+    omta_is("background-color", "rgb(255, 0, 0)", RunningOn.Compositor,
+            "background-color animation starting with current-color runs on " +
+            "compositor thread");
+
+    advance_clock(500);
+
+    omta_is("background-color", "rgb(128, 128, 0)", RunningOn.Compositor,
+            "background-color on compositor at 5s");
+
+    gDiv.setAttribute("href", window.top.location.href);
+    await waitForVisitedLinkColoring(gDiv, "color", "rgb(0, 0, 255)");
+    await waitForPaintsFlushed();
+
+    // `omta_is` checks that the result on the compositor equals to the value by
+    // getComputedValue() but getComputedValue lies for visited link values so
+    // we use getOMTAStyle directly instead.
+    is(SpecialPowers.DOMWindowUtils.getOMTAStyle(gDiv, "background-color"),
+       "rgb(0, 128, 128)",
+       "background-color on <a> element after the link is visited");
+
+    extraStyle.remove();
+    done_element();
+    gDiv = null;
+  });
+}
+
 </script>
 </html>