Bug 769193 - Interpolate between transform functions that share common primitives rather than forcing them to fall back to matrix decomposition. r=dbaron
authorDavid Zbarsky <dzbarsky@gmail.com>
Fri, 20 Jul 2012 14:08:37 -0400
changeset 99946 19b5733f954d1958c41860a8c4ac50a290afffdd
parent 99945 166cae54928db71a0d263c2cee07a6f329a9d80c
child 99947 1cbf95c4c4f163e88bcb35820d20c6771cf51c8b
push id23157
push userryanvm@gmail.com
push dateSat, 21 Jul 2012 03:56:05 +0000
treeherdermozilla-central@045c11dd41a6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdbaron
bugs769193
milestone17.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 769193 - Interpolate between transform functions that share common primitives rather than forcing them to fall back to matrix decomposition. r=dbaron
layout/style/nsStyleAnimation.cpp
layout/style/test/test_animations.html
--- a/layout/style/nsStyleAnimation.cpp
+++ b/layout/style/nsStyleAnimation.cpp
@@ -81,16 +81,170 @@ GetCommonUnit(nsCSSProperty aProperty,
       // We can use calc() as the common unit.
       return eCSSUnit_Calc;
     }
     return eCSSUnit_Null;
   }
   return aFirstUnit;
 }
 
+static nsCSSKeyword
+ToPrimitive(nsCSSKeyword aKeyword)
+{
+  switch (aKeyword) {
+    case eCSSKeyword_translatex:
+    case eCSSKeyword_translatey:
+    case eCSSKeyword_translatez:
+    case eCSSKeyword_translate:
+      return eCSSKeyword_translate3d;
+    case eCSSKeyword_scalex:
+    case eCSSKeyword_scaley:
+    case eCSSKeyword_scalez:
+    case eCSSKeyword_scale:
+      return eCSSKeyword_scale3d;
+    default:
+      return aKeyword;
+  }
+}
+
+static already_AddRefed<nsCSSValue::Array>
+AppendFunction(nsCSSKeyword aTransformFunction)
+{
+  PRUint32 nargs;
+  switch (aTransformFunction) {
+    case eCSSKeyword_matrix3d:
+      nargs = 16;
+      break;
+    case eCSSKeyword_matrix:
+      nargs = 6;
+      break;
+    case eCSSKeyword_rotate3d:
+      nargs = 4;
+      break;
+    case eCSSKeyword_interpolatematrix:
+    case eCSSKeyword_translate3d:
+    case eCSSKeyword_scale3d:
+      nargs = 3;
+      break;
+    case eCSSKeyword_translate:
+    case eCSSKeyword_scale:
+      nargs = 2;
+      break;
+    default:
+      NS_ERROR("must be a transform function");
+    case eCSSKeyword_translatex:
+    case eCSSKeyword_translatey:
+    case eCSSKeyword_translatez:
+    case eCSSKeyword_scalex:
+    case eCSSKeyword_scaley:
+    case eCSSKeyword_scalez:
+    case eCSSKeyword_skewx:
+    case eCSSKeyword_skewy:
+    case eCSSKeyword_rotate:
+    case eCSSKeyword_rotatex:
+    case eCSSKeyword_rotatey:
+    case eCSSKeyword_rotatez:
+    case eCSSKeyword_perspective:
+      nargs = 1;
+      break;
+  }
+
+  nsRefPtr<nsCSSValue::Array> arr = nsCSSValue::Array::Create(nargs + 1);
+  arr->Item(0).SetStringValue(
+    NS_ConvertUTF8toUTF16(nsCSSKeywords::GetStringValue(aTransformFunction)),
+    eCSSUnit_Ident);
+
+  return arr.forget();
+}
+
+static already_AddRefed<nsCSSValue::Array>
+ToPrimitive(nsCSSValue::Array* aArray)
+{
+  nsCSSKeyword tfunc = nsStyleTransformMatrix::TransformFunctionOf(aArray);
+  nsCSSKeyword primitive = ToPrimitive(tfunc);
+  nsRefPtr<nsCSSValue::Array> arr = AppendFunction(primitive);
+
+  // FIXME: This would produce fewer calc() expressions if the
+  // zero were of compatible type (length vs. percent) when
+  // needed.
+
+  nsCSSValue zero(0.0f, eCSSUnit_Pixel);
+  nsCSSValue one(1.0f, eCSSUnit_Number);
+  switch(tfunc) {
+    case eCSSKeyword_translate:
+    {
+      NS_ABORT_IF_FALSE(aArray->Count() == 2 || aArray->Count() == 3,
+                        "unexpected count");
+      arr->Item(1) = aArray->Item(1);
+      arr->Item(2) = aArray->Count() == 3 ? aArray->Item(2) : zero;
+      arr->Item(3) = zero;
+      break;
+    }
+    case eCSSKeyword_translatex:
+    {
+      NS_ABORT_IF_FALSE(aArray->Count() == 2, "unexpected count");
+      arr->Item(1) = aArray->Item(1);
+      arr->Item(2) = zero;
+      arr->Item(3) = zero;
+      break;
+    }
+    case eCSSKeyword_translatey:
+    {
+      NS_ABORT_IF_FALSE(aArray->Count() == 2, "unexpected count");
+      arr->Item(1) = zero;
+      arr->Item(2) = aArray->Item(1);
+      arr->Item(3) = zero;
+      break;
+    }
+    case eCSSKeyword_translatez:
+    {
+      NS_ABORT_IF_FALSE(aArray->Count() == 2, "unexpected count");
+      arr->Item(1) = zero;
+      arr->Item(2) = zero;
+      arr->Item(3) = aArray->Item(1);
+      break;
+    }
+    case eCSSKeyword_scale:
+    {
+      NS_ABORT_IF_FALSE(aArray->Count() == 2 || aArray->Count() == 3,
+                        "unexpected count");
+      arr->Item(1) = aArray->Item(1);
+      arr->Item(2) = aArray->Count() == 3 ? aArray->Item(2) : aArray->Item(1);
+      arr->Item(3) = one;
+      break;
+    }
+    case eCSSKeyword_scalex:
+    {
+      NS_ABORT_IF_FALSE(aArray->Count() == 2, "unexpected count");
+      arr->Item(1) = aArray->Item(1);
+      arr->Item(2) = one;
+      arr->Item(3) = one;
+      break;
+    }
+    case eCSSKeyword_scaley:
+    {
+      NS_ABORT_IF_FALSE(aArray->Count() == 2, "unexpected count");
+      arr->Item(1) = one;
+      arr->Item(2) = aArray->Item(1);
+      arr->Item(3) = one;
+      break;
+    }
+    case eCSSKeyword_scalez:
+    {
+      NS_ABORT_IF_FALSE(aArray->Count() == 2, "unexpected count");
+      arr->Item(1) = one;
+      arr->Item(2) = one;
+      arr->Item(3) = aArray->Item(1);
+      break;
+    }
+    default:
+      arr = aArray;
+  }
+  return arr.forget();
+}
 
 // Greatest Common Divisor
 static PRUint32
 gcd(PRUint32 a, PRUint32 b)
 {
   // Euclid's algorithm; O(N) in the worst case.  (There are better
   // ways, but we don't need them for stroke-dasharray animation.)
   NS_ABORT_IF_FALSE(a > 0 && b > 0, "positive numbers expected");
@@ -967,60 +1121,17 @@ AddTransformScale(const nsCSSValue &aVal
   float result = v1 * aCoeff1 + v2 * aCoeff2;
   aResult.SetFloatValue(result + 1.0f, eCSSUnit_Number);
 }
 
 static already_AddRefed<nsCSSValue::Array>
 AppendTransformFunction(nsCSSKeyword aTransformFunction,
                         nsCSSValueList**& aListTail)
 {
-  PRUint32 nargs;
-  switch (aTransformFunction) {
-    case eCSSKeyword_matrix3d:
-      nargs = 16;
-      break;
-    case eCSSKeyword_matrix:
-      nargs = 6;
-      break;
-    case eCSSKeyword_rotate3d:
-      nargs = 4;
-      break;
-    case eCSSKeyword_interpolatematrix:
-    case eCSSKeyword_translate3d:
-    case eCSSKeyword_scale3d:
-      nargs = 3;
-      break;
-    case eCSSKeyword_translate:
-    case eCSSKeyword_scale:
-      nargs = 2;
-      break;
-    default:
-      NS_ERROR("must be a transform function");
-    case eCSSKeyword_translatex:
-    case eCSSKeyword_translatey:
-    case eCSSKeyword_translatez:
-    case eCSSKeyword_scalex:
-    case eCSSKeyword_scaley:
-    case eCSSKeyword_scalez:
-    case eCSSKeyword_skewx:
-    case eCSSKeyword_skewy:
-    case eCSSKeyword_rotate:
-    case eCSSKeyword_rotatex:
-    case eCSSKeyword_rotatey:
-    case eCSSKeyword_rotatez:
-    case eCSSKeyword_perspective:
-      nargs = 1;
-      break;
-  }
-
-  nsRefPtr<nsCSSValue::Array> arr = nsCSSValue::Array::Create(nargs + 1);
-  arr->Item(0).SetStringValue(
-    NS_ConvertUTF8toUTF16(nsCSSKeywords::GetStringValue(aTransformFunction)),
-    eCSSUnit_Ident);
-
+  nsRefPtr<nsCSSValue::Array> arr = AppendFunction(aTransformFunction);
   nsCSSValueList *item = new nsCSSValueList;
   item->mValue.SetArrayValue(arr, eCSSUnit_Function);
 
   *aListTail = item;
   aListTail = &item->mNext;
 
   return arr.forget();
 }
@@ -1361,37 +1472,29 @@ AddDifferentTransformLists(const nsCSSVa
   arr->Item(3).SetPercentValue(aCoeff2);
 
   return result.forget();
 }
 
 static bool
 TransformFunctionsMatch(nsCSSKeyword func1, nsCSSKeyword func2)
 {
-  if (func1 == func2) {
-    return true;
-  }
-
-  if ((func1 == eCSSKeyword_rotatez && func2 == eCSSKeyword_rotate) ||
-      (func1 == eCSSKeyword_rotate && func2 == eCSSKeyword_rotatez)) {
-    return true;
-  }
-  return false;
+  return ToPrimitive(func1) == ToPrimitive(func2);
 }
 
 static nsCSSValueList*
 AddTransformLists(const nsCSSValueList* aList1, double aCoeff1,
                   const nsCSSValueList* aList2, double aCoeff2)
 {
   nsAutoPtr<nsCSSValueList> result;
   nsCSSValueList **resultTail = getter_Transfers(result);
 
   do {
-    const nsCSSValue::Array *a1 = aList1->mValue.GetArrayValue(),
-                            *a2 = aList2->mValue.GetArrayValue();
+    nsRefPtr<nsCSSValue::Array> a1 = ToPrimitive(aList1->mValue.GetArrayValue()),
+                                a2 = ToPrimitive(aList2->mValue.GetArrayValue());
     NS_ABORT_IF_FALSE(TransformFunctionsMatch(nsStyleTransformMatrix::TransformFunctionOf(a1),
                                               nsStyleTransformMatrix::TransformFunctionOf(a2)),
                       "transform function mismatch");
     NS_ABORT_IF_FALSE(!*resultTail,
                       "resultTail isn't pointing to the tail (may leak)");
 
     nsCSSKeyword tfunc = nsStyleTransformMatrix::TransformFunctionOf(a1);
     nsRefPtr<nsCSSValue::Array> arr;
@@ -1399,92 +1502,27 @@ AddTransformLists(const nsCSSValueList* 
         tfunc != eCSSKeyword_matrix3d &&
         tfunc != eCSSKeyword_interpolatematrix &&
         tfunc != eCSSKeyword_rotate3d &&
         tfunc != eCSSKeyword_perspective) {
       arr = AppendTransformFunction(tfunc, resultTail);
     }
 
     switch (tfunc) {
-      case eCSSKeyword_translate: {
-        NS_ABORT_IF_FALSE(a1->Count() == 2 || a1->Count() == 3,
-                          "unexpected count");
-        NS_ABORT_IF_FALSE(a2->Count() == 2 || a2->Count() == 3,
-                          "unexpected count");
-
-        // FIXME: This would produce fewer calc() expressions if the
-        // zero were of compatible type (length vs. percent) when
-        // needed.
-        nsCSSValue zero(0.0f, eCSSUnit_Pixel);
-        // Add Y component of translation.
-        AddTransformTranslate(a1->Count() == 3 ? a1->Item(2) : zero,
-                              aCoeff1,
-                              a2->Count() == 3 ? a2->Item(2) : zero,
-                              aCoeff2,
-                              arr->Item(2));
-
-        // Add X component of translation (which can be merged with case
-        // below in non-DEBUG).
-        AddTransformTranslate(a1->Item(1), aCoeff1, a2->Item(1), aCoeff2,
-                              arr->Item(1));
-        break;
-      }
-      case eCSSKeyword_translatex:
-      case eCSSKeyword_translatey:
-      case eCSSKeyword_translatez: {
-        NS_ABORT_IF_FALSE(a1->Count() == 2, "unexpected count");
-        NS_ABORT_IF_FALSE(a2->Count() == 2, "unexpected count");
-        AddTransformTranslate(a1->Item(1), aCoeff1, a2->Item(1), aCoeff2,
-                              arr->Item(1));
-        break;
-      }
       case eCSSKeyword_translate3d: {
           NS_ABORT_IF_FALSE(a1->Count() == 4, "unexpected count");
           NS_ABORT_IF_FALSE(a2->Count() == 4, "unexpected count");
           AddTransformTranslate(a1->Item(1), aCoeff1, a2->Item(1), aCoeff2,
                                 arr->Item(1));
           AddTransformTranslate(a1->Item(2), aCoeff1, a2->Item(2), aCoeff2,
                                 arr->Item(2));
           AddTransformTranslate(a1->Item(3), aCoeff1, a2->Item(3), aCoeff2,
                                 arr->Item(3));
           break;
       }
-      case eCSSKeyword_scale: {
-        NS_ABORT_IF_FALSE(a1->Count() == 2 || a1->Count() == 3,
-                          "unexpected count");
-        NS_ABORT_IF_FALSE(a2->Count() == 2 || a2->Count() == 3,
-                          "unexpected count");
-
-        // This is different from translate(), since an omitted second
-        // parameter repeats the first rather than being zero.
-        // Add Y component of scale.
-        AddTransformScale(a1->Count() == 3 ? a1->Item(2) : a1->Item(1),
-                          aCoeff1,
-                          a2->Count() == 3 ? a2->Item(2) : a2->Item(1),
-                          aCoeff2,
-                          arr->Item(2));
-
-        // Add X component of scale (which can be merged with case below
-        // in non-DEBUG).
-        AddTransformScale(a1->Item(1), aCoeff1, a2->Item(1), aCoeff2,
-                          arr->Item(1));
-
-        break;
-      }
-      case eCSSKeyword_scalex:
-      case eCSSKeyword_scaley: 
-      case eCSSKeyword_scalez: {
-        NS_ABORT_IF_FALSE(a1->Count() == 2, "unexpected count");
-        NS_ABORT_IF_FALSE(a2->Count() == 2, "unexpected count");
-
-        AddTransformScale(a1->Item(1), aCoeff1, a2->Item(1), aCoeff2,
-                          arr->Item(1));
-
-        break;
-      }
       case eCSSKeyword_scale3d: {
           NS_ABORT_IF_FALSE(a1->Count() == 4, "unexpected count");
           NS_ABORT_IF_FALSE(a2->Count() == 4, "unexpected count");
 
           AddTransformScale(a1->Item(1), aCoeff1, a2->Item(1), aCoeff2,
                             arr->Item(1));
           AddTransformScale(a1->Item(2), aCoeff1, a2->Item(2), aCoeff2,
                             arr->Item(2));
--- a/layout/style/test/test_animations.html
+++ b/layout/style/test/test_animations.html
@@ -96,16 +96,23 @@ https://bugzilla.mozilla.org/show_bug.cg
   }
   @keyframes cascade2 {
     0% { text-indent: 0 }
     25% { text-indent: 30px; animation-timing-function: ease-in } /* beaten by rule below */
     50% { text-indent: 0 }
     25% { text-indent: 50px }
     100% { text-indent: 100px }
   }
+
+  @keyframes primitives1 {
+    from { -moz-transform: rotate(0deg) translateX(0px) scaleX(1)
+      translate(0px) scale3d(1, 1, 1); }
+    to { -moz-transform: rotate(270deg) translate3d(0px, 0px, 0px) scale(1)
+      translateY(0px) scaleY(1); }
+  }
   </style>
 </head>
 <body>
 <a target="_blank" href="https://bugzilla.mozilla.org/show_bug.cgi?id=435442">Mozilla Bug 435442</a>
 <div id="display"></div>
 <pre id="test">
 <script type="application/javascript">
 "use strict";
@@ -1385,14 +1392,26 @@ is(cs.textIndent, "25px", "cascade2 test
 advance_clock(1000);
 is(cs.textIndent, "0px", "cascade2 test at 4s");
 advance_clock(3000);
 is(cs.textIndent, "75px", "cascade2 test at 7s");
 advance_clock(1000);
 is(cs.textIndent, "100px", "cascade2 test at 8s");
 done_div();
 
+new_div("-moz-animation: primitives1 2s linear forwards");
+is(cs.getPropertyValue("-moz-transform"), "matrix(1, 0, 0, 1, 0, 0)",
+    "primitives1 at 0s");
+advance_clock(1000);
+is(cs.getPropertyValue("-moz-transform"),
+   "matrix(-0.707107, 0.707107, -0.707107, -0.707107, 0, 0)",
+   "primitives1 at 1s");
+advance_clock(1000);
+is(cs.getPropertyValue("-moz-transform"), "matrix(0, -1, 1, 0, 0, 0)",
+    "primitives1 at 0s");
+done_div();
+
 SpecialPowers.DOMWindowUtils.restoreNormalRefresh();
 
 </script>
 </pre>
 </body>
 </html>