Bug 505115 - Part 16 - Implement transitions/animations for 3d transforms. r=dbaron, derf
authorMatt Woodrow <mwoodrow@mozilla.com>
Tue, 27 Sep 2011 10:53:33 +1300
changeset 77618 97983a84f0c3b972986e7d7c992f02b804f9dae6
parent 77617 c8e237c03adb5feb6a144c8f3bb55b13f773401e
child 77619 29b915205135330ae63bdd200f87eec9182fce42
push id21221
push userbzbarsky@mozilla.com
push dateTue, 27 Sep 2011 07:08:27 +0000
treeherdermozilla-central@d305835a6726 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdbaron, derf
bugs505115
milestone9.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 505115 - Part 16 - Implement transitions/animations for 3d transforms. r=dbaron, derf
layout/style/nsComputedDOMStyle.cpp
layout/style/nsStyleAnimation.cpp
layout/style/nsStyleAnimation.h
layout/style/nsStyleTransformMatrix.cpp
layout/style/test/test_transitions_per_property.html
--- a/layout/style/nsComputedDOMStyle.cpp
+++ b/layout/style/nsComputedDOMStyle.cpp
@@ -1053,37 +1053,63 @@ nsComputedDOMStyle::DoGetMozTransform()
    gfx3DMatrix matrix =
      nsStyleTransformMatrix::ReadTransforms(display->mSpecifiedTransform,
                                             mStyleContextHolder,
                                             mStyleContextHolder->PresContext(),
                                             dummy,
                                             bounds,
                                             float(nsDeviceContext::AppUnitsPerCSSPixel()));
 
-  if (!matrix.Is2D()) {
-    nsROCSSPrimitiveValue* val = GetROCSSPrimitiveValue();
-
-    /* Set it to "none." */
-    val->SetIdent(eCSSKeyword_none);
-    return val;
+  PRBool is3D = !matrix.Is2D();
+
+  nsAutoString resultString(NS_LITERAL_STRING("matrix"));
+  if (is3D) {
+    resultString.Append(NS_LITERAL_STRING("3d"));
   }
 
-  nsAutoString resultString(NS_LITERAL_STRING("matrix("));
+  resultString.Append(NS_LITERAL_STRING("("));
   resultString.AppendFloat(matrix._11);
   resultString.Append(NS_LITERAL_STRING(", "));
   resultString.AppendFloat(matrix._12);
   resultString.Append(NS_LITERAL_STRING(", "));
+  if (is3D) {
+    resultString.AppendFloat(matrix._13);
+    resultString.Append(NS_LITERAL_STRING(", "));
+    resultString.AppendFloat(matrix._14);
+    resultString.Append(NS_LITERAL_STRING(", "));
+  }
   resultString.AppendFloat(matrix._21);
   resultString.Append(NS_LITERAL_STRING(", "));
   resultString.AppendFloat(matrix._22);
   resultString.Append(NS_LITERAL_STRING(", "));
+  if (is3D) {
+    resultString.AppendFloat(matrix._23);
+    resultString.Append(NS_LITERAL_STRING(", "));
+    resultString.AppendFloat(matrix._24);
+    resultString.Append(NS_LITERAL_STRING(", "));
+    resultString.AppendFloat(matrix._31);
+    resultString.Append(NS_LITERAL_STRING(", "));
+    resultString.AppendFloat(matrix._32);
+    resultString.Append(NS_LITERAL_STRING(", "));
+    resultString.AppendFloat(matrix._33);
+    resultString.Append(NS_LITERAL_STRING(", "));
+    resultString.AppendFloat(matrix._34);
+    resultString.Append(NS_LITERAL_STRING(", "));
+  }
   resultString.AppendFloat(matrix._41);
   resultString.Append(NS_LITERAL_STRING("px, "));
   resultString.AppendFloat(matrix._42);
-  resultString.Append(NS_LITERAL_STRING("px)"));
+  resultString.Append(NS_LITERAL_STRING("px"));
+  if (is3D) {
+    resultString.Append(NS_LITERAL_STRING(", "));
+    resultString.AppendFloat(matrix._43);
+    resultString.Append(NS_LITERAL_STRING("px, "));
+    resultString.AppendFloat(matrix._44);
+  }
+  resultString.Append(NS_LITERAL_STRING(")"));
 
   /* Create a value to hold our result. */
   nsROCSSPrimitiveValue* val = GetROCSSPrimitiveValue();
 
   val->SetString(resultString);
   return val;
 }
 
--- a/layout/style/nsStyleAnimation.cpp
+++ b/layout/style/nsStyleAnimation.cpp
@@ -48,16 +48,17 @@
 #include "nsStyleSet.h"
 #include "nsComputedDOMStyle.h"
 #include "nsCSSParser.h"
 #include "mozilla/css/Declaration.h"
 #include "mozilla/dom/Element.h"
 #include "prlog.h"
 #include <math.h>
 #include "gfxMatrix.h"
+#include "gfxQuaternion.h"
 
 namespace css = mozilla::css;
 namespace dom = mozilla::dom;
 
 // HELPER METHODS
 // --------------
 /*
  * Given two units, this method returns a common unit that they can both be
@@ -903,34 +904,53 @@ AddTransformScale(const nsCSSValue &aVal
   aResult.SetFloatValue(result + 1.0f, eCSSUnit_Number);
 }
 
 static already_AddRefed<nsCSSValue::Array>
 AppendTransformFunction(nsCSSKeyword aTransformFunction,
                         nsCSSValueList**& aListTail)
 {
   PRUint32 nargs;
-  if (aTransformFunction == eCSSKeyword_matrix) {
-    nargs = 6;
-  } else if (aTransformFunction == eCSSKeyword_translate ||
-             aTransformFunction == eCSSKeyword_skew ||
-             aTransformFunction == eCSSKeyword_scale) {
-    nargs = 2;
-  } else if (aTransformFunction == eCSSKeyword_interpolatematrix) {
-    nargs = 4;
-  } else {
-    NS_ABORT_IF_FALSE(aTransformFunction == eCSSKeyword_translatex ||
-                      aTransformFunction == eCSSKeyword_translatey ||
-                      aTransformFunction == eCSSKeyword_scalex ||
-                      aTransformFunction == eCSSKeyword_scaley ||
-                      aTransformFunction == eCSSKeyword_skewx ||
-                      aTransformFunction == eCSSKeyword_skewy ||
-                      aTransformFunction == eCSSKeyword_rotate,
-                      "must be a transform function");
-    nargs = 1;
+  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_skew:
+    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);
 
   nsCSSValueList *item = new nsCSSValueList;
@@ -1059,22 +1079,27 @@ AppendTransformFunction(nsCSSKeyword aTr
  *     [ cos(φ)  tan(θ) sec(φ) ] [ sec(φ)    0   ]
  *     [ sin(φ)     sec(φ)     ] [   0    cos(φ) ]
  *
  *     [    1   tan(θ) ]
  *     [ tan(φ)    1   ]
  */
 
 /*
- * DecomposeMatrix implements the non-translation parts of the above
- * decomposition algorithm.
+ * Decompose2DMatrix implements the above decomposition algorithm.
  */
+
+#define XYSHEAR 0
+#define XZSHEAR 1
+#define YZSHEAR 2
+
 static PRBool
-DecomposeMatrix(const gfxMatrix &aMatrix,
-                float &aRotate, float &aXYShear, float &aScaleX, float &aScaleY)
+Decompose2DMatrix(const gfxMatrix &aMatrix, gfxPoint3D &aScale,
+                  float aShear[3], gfxQuaternion &aRotate,
+                  gfxPoint3D &aTranslate)
 {
   float A = aMatrix.xx,
         B = aMatrix.yx,
         C = aMatrix.xy,
         D = aMatrix.yy;
   if (A * D == B * C) {
     // singular matrix
     return PR_FALSE;
@@ -1088,145 +1113,286 @@ DecomposeMatrix(const gfxMatrix &aMatrix
   C -= A * XYshear;
   D -= B * XYshear;
 
   float scaleY = sqrt(C * C + D * D);
   C /= scaleY;
   D /= scaleY;
   XYshear /= scaleY;
 
- // A*D - B*C should now be 1 or -1
+  // A*D - B*C should now be 1 or -1
   NS_ASSERTION(0.99 < NS_ABS(A*D - B*C) && NS_ABS(A*D - B*C) < 1.01,
                "determinant should now be 1 or -1");
   if (A * D < B * C) {
     A = -A;
     B = -B;
     C = -C;
     D = -D;
     XYshear = -XYshear;
     scaleX = -scaleX;
   }
 
-  float rotation = atan2f(B, A);
+  float rotate = atan2f(B, A);
+  aRotate = gfxQuaternion(0, 0, sin(rotate/2), cos(rotate/2));
+  aShear[XYSHEAR] = XYshear;
+  aScale.x = scaleX;
+  aScale.y = scaleY;
+  aTranslate.x = aMatrix.x0;
+  aTranslate.y = aMatrix.y0;
+  return PR_TRUE;
+}
+
+/**
+ * Implementation of the unmatrix algorithm, specified by:
+ *
+ * http://dev.w3.org/csswg/css3-2d-transforms/#unmatrix
+ *
+ * This, in turn, refers to the unmatrix program in Graphics Gems,
+ * available from http://tog.acm.org/resources/GraphicsGems/ , and in
+ * particular as the file GraphicsGems/gemsii/unmatrix.c
+ * in http://tog.acm.org/resources/GraphicsGems/AllGems.tar.gz
+ */
+static PRBool
+Decompose3DMatrix(const gfx3DMatrix &aMatrix, gfxPoint3D &aScale,
+                  float aShear[3], gfxQuaternion &aRotate,
+                  gfxPoint3D &aTranslate, gfxPointH3D &aPerspective)
+{
+  gfx3DMatrix local = aMatrix;
+
+  if (local[3][3] == 0) {
+    return PR_FALSE;
+  }
+  /* Normalize the matrix */
+  local.Normalize();
+
+  /** 
+   * perspective is used to solve for perspective, but it also provides
+   * an easy way to test for singularity of the upper 3x3 component.
+   */
+  gfx3DMatrix perspective = local;
+  gfxPointH3D empty(0, 0, 0, 1);
+  perspective.SetTransposedVector(3, empty);
+
+  if (perspective.Determinant() == 0.0) {
+    return PR_FALSE;
+  }
+
+  /* First, isolate perspective. */
+  if (local[0][3] != 0 || local[1][3] != 0 ||
+      local[2][3] != 0) {
+    /* aPerspective is the right hand side of the equation. */
+    aPerspective = local.TransposedVector(3);
 
-  aRotate = rotation;
-  aXYShear = XYshear;
-  aScaleX = scaleX;
-  aScaleY = scaleY;
+    /** 
+     * Solve the equation by inverting perspective and multiplying
+     * aPerspective by the inverse.
+     */
+    perspective.Invert();
+    aPerspective = perspective.TransposeTransform4D(aPerspective);
+    
+    /* Clear the perspective partition */
+    local.SetTransposedVector(3, empty);
+  } else {
+    aPerspective = gfxPointH3D(0, 0, 0, 1);
+  }
+
+  /* Next take care of translation */
+  for (int i = 0; i < 3; i++) {
+    aTranslate[i] = local[3][i];
+    local[3][i] = 0;
+  }
+
+  /* Now get scale and shear. */
+
+  /* Compute X scale factor and normalize first row. */
+  aScale.x = local[0].Length();
+  local[0] /= aScale.x;
+    
+  /* Compute XY shear factor and make 2nd local orthogonal to 1st. */
+  aShear[XYSHEAR] = local[0].DotProduct(local[1]);
+  local[1] -= local[0] * aShear[XYSHEAR];
+  
+  /* Now, compute Y scale and normalize 2nd local. */
+  aScale.y = local[1].Length();
+  local[1] /= aScale.y;
+  aShear[XYSHEAR] /= aScale.y;
+
+  /* Compute XZ and YZ shears, make 3rd local orthogonal */
+  aShear[XZSHEAR] = local[0].DotProduct(local[2]);
+  local[2] -= local[0] * aShear[XZSHEAR];
+  aShear[YZSHEAR] = local[1].DotProduct(local[2]);
+  local[2] -= local[1] * aShear[YZSHEAR];
+
+  /* Next, get Z scale and normalize 3rd local. */
+  aScale.z = local[2].Length();
+  local[2] /= aScale.z;
+
+  aShear[XZSHEAR] /= aScale.z;
+  aShear[YZSHEAR] /= aScale.z;
+
+  /**
+   * At this point, the matrix (in locals) is orthonormal.
+   * Check for a coordinate system flip.  If the determinant
+   * is -1, then negate the matrix and the scaling factors.
+   */
+  if (local[0].DotProduct(local[1].CrossProduct(local[2])) < 0) {
+    aScale *= -1;
+    for (int i = 0; i < 3; i++) {
+      local[i] *= -1;
+    }
+  }
+
+  /* Now, get the rotations out */
+  aRotate = gfxQuaternion(local);
 
   return PR_TRUE;
 }
 
-/* Force small values to zero.  We do this to avoid having sin(360deg)
- * evaluate to a tiny but nonzero value.
- */
-static double FlushToZero(double aVal)
+template<typename T>
+T InterpolateNumerically(const T& aOne, const T& aTwo, double aCoeff)
 {
-  if (-FLT_EPSILON < aVal && aVal < FLT_EPSILON)
-    return 0.0f;
-  else
-    return aVal;
-}
-
-/* Computes tan(aTheta).  For values of aTheta such that tan(aTheta) is
- * undefined or very large, SafeTangent returns a manageably large value
- * of the correct sign.
- */
-static double SafeTangent(double aTheta)
-{
-  const double kEpsilon = 0.0001;
-
-  /* tan(theta) = sin(theta)/cos(theta); problems arise when
-   * cos(theta) is too close to zero.  Limit cos(theta) to the
-   * range [-1, -epsilon] U [epsilon, 1].
-   */
-  double sinTheta = sin(aTheta);
-  double cosTheta = cos(aTheta);
-
-  if (cosTheta >= 0 && cosTheta < kEpsilon)
-    cosTheta = kEpsilon;
-  else if (cosTheta < 0 && cosTheta >= -kEpsilon)
-    cosTheta = -kEpsilon;
-
-  return FlushToZero(sinTheta / cosTheta);
+  return aOne + (aTwo - aOne) * aCoeff;
 }
 
 
-/* static */ gfxMatrix
-nsStyleAnimation::InterpolateTransformMatrix(const gfxMatrix &aMatrix1,
-                                             double aCoeff1,
-                                             const gfxMatrix &aMatrix2,
-                                             double aCoeff2)
+/* static */ gfx3DMatrix
+nsStyleAnimation::InterpolateTransformMatrix(const gfx3DMatrix &aMatrix1,
+                                             const gfx3DMatrix &aMatrix2,
+                                             double aProgress)
 {
-  float rotate1, XYshear1, scaleX1, scaleY1;
-  DecomposeMatrix(aMatrix1, rotate1, XYshear1, scaleX1, scaleY1);
-  float rotate2, XYshear2, scaleX2, scaleY2;
-  DecomposeMatrix(aMatrix2, rotate2, XYshear2, scaleX2, scaleY2);
+  // Decompose both matrices
+
+  // TODO: What do we do if one of these returns PR_FALSE (singular matrix)
+
+  gfxPoint3D scale1(1, 1, 1), translate1;
+  gfxPointH3D perspective1(0, 0, 0, 1);
+  gfxQuaternion rotate1;
+  float shear1[3] = { 0.0f, 0.0f, 0.0f};
+
+  gfxPoint3D scale2(1, 1, 1), translate2;
+  gfxPointH3D perspective2(0, 0, 0, 1);
+  gfxQuaternion rotate2;
+  float shear2[3] = { 0.0f, 0.0f, 0.0f};
 
-  float rotate = rotate1 * aCoeff1 + rotate2 * aCoeff2;
+  gfxMatrix matrix2d1, matrix2d2;
+  if (aMatrix1.Is2D(&matrix2d1) && aMatrix2.Is2D(&matrix2d2)) {
+    Decompose2DMatrix(matrix2d1, scale1, shear1, rotate1, translate1);
+    Decompose2DMatrix(matrix2d2, scale2, shear2, rotate2, translate2);
+  } else {
+    Decompose3DMatrix(aMatrix1, scale1, shear1,
+                      rotate1, translate1, perspective1);
+    Decompose3DMatrix(aMatrix2, scale2, shear2,
+                      rotate2, translate2, perspective2);
+  }
 
-  float skewX = atanf(XYshear1) * aCoeff1 + atanf(XYshear2) * aCoeff2;
+  // Interpolate each of the pieces
+  gfx3DMatrix result;
 
-  // Handle scale, and the two matrix components where identity is 1, by
-  // subtracting 1, multiplying by the coefficients, and then adding 1
-  // back.  This gets the right AddWeighted behavior and gets us the
-  // interpolation-against-identity behavior for free.
-  float scaleX =
-    ((scaleX1 - 1.0f) * aCoeff1 + (scaleX2 - 1.0f) * aCoeff2) + 1.0f;
-  float scaleY =
-    ((scaleY1 - 1.0f) * aCoeff1 + (scaleY2 - 1.0f) * aCoeff2) + 1.0f;
+  gfxPointH3D perspective = 
+    InterpolateNumerically(perspective1, perspective2, aProgress);
+  result.SetTransposedVector(3, perspective);
+ 
+  gfxPoint3D translate = 
+    InterpolateNumerically(translate1, translate2, aProgress);
+  result.Translate(translate);
+
+  gfxQuaternion q3 = rotate1.Slerp(rotate2, aProgress);
+  gfx3DMatrix rotate = q3.ToMatrix();
+  if (!rotate.IsIdentity()) {
+      result = rotate * result;
+  }
 
-  gfxMatrix result;
+  // TODO: Would it be better to interpolate these as angles? How do we convert back to angles?
+  float yzshear =
+    InterpolateNumerically(shear1[YZSHEAR], shear2[YZSHEAR], aProgress);
+  if (yzshear != 0.0) {
+    result.SkewYZ(yzshear);
+  }
 
-  gfxMatrix skew;
-  skew.xy = SafeTangent(skewX);
-  result.Translate(gfxPoint(aMatrix1.x0 * aCoeff1 + aMatrix2.x0 * aCoeff2,
-                   aMatrix1.y0 * aCoeff1 + aMatrix2.y0 * aCoeff2));
-  result.Rotate(rotate);
-  result.PreMultiply(skew);
-  result.Scale(scaleX, scaleY);
+  float xzshear =
+    InterpolateNumerically(shear1[XZSHEAR], shear2[XZSHEAR], aProgress);
+  if (xzshear != 0.0) {
+    result.SkewXZ(xzshear);
+  }
+
+  float xyshear =
+    InterpolateNumerically(shear1[XYSHEAR], shear2[XYSHEAR], aProgress);
+  if (xyshear != 0.0) {
+    result.SkewXY(xyshear);
+  }
+
+  gfxPoint3D scale = 
+    InterpolateNumerically(scale1, scale2, aProgress);
+  if (scale != gfxPoint3D(1.0, 1.0, 1.0)) {
+    result.Scale(scale.x, scale.y, scale.z);
+  }
 
   return result;
 }
 
 static nsCSSValueList*
 AddDifferentTransformLists(const nsCSSValueList* aList1, double aCoeff1,
                            const nsCSSValueList* aList2, double aCoeff2)
 {
   nsAutoPtr<nsCSSValueList> result;
   nsCSSValueList **resultTail = getter_Transfers(result);
 
   nsRefPtr<nsCSSValue::Array> arr;
   arr = AppendTransformFunction(eCSSKeyword_interpolatematrix, resultTail);
+  
+  // FIXME: We should change the other transform code to also only
+  // take a single progress value, as having values that don't
+  // sum to 1 doesn't make sense for these.
+  if (aList1 == aList2) {
+    arr->Item(1).Reset();
+  } else {
+    aList1->CloneInto(arr->Item(1).SetListValue());
+  }
 
-  arr->Item(1).SetPercentValue(aCoeff1);
-  aList1->CloneInto(arr->Item(2).SetListValue());
+  aList2->CloneInto(arr->Item(2).SetListValue());
   arr->Item(3).SetPercentValue(aCoeff2);
-  aList2->CloneInto(arr->Item(4).SetListValue());
 
   return result.forget();
 }
 
+static PRBool
+TransformFunctionsMatch(nsCSSKeyword func1, nsCSSKeyword func2)
+{
+  if (func1 == func2) {
+    return PR_TRUE;
+  }
+
+  if (func1 == eCSSKeyword_rotatez && func2 == eCSSKeyword_rotate ||
+      func1 == eCSSKeyword_rotate && func2 == eCSSKeyword_rotatez) {
+    return PR_TRUE;
+  }
+  return PR_FALSE;
+}
+
 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();
-    NS_ABORT_IF_FALSE(nsStyleTransformMatrix::TransformFunctionOf(a1) ==
-                      nsStyleTransformMatrix::TransformFunctionOf(a2),
+    NS_ABORT_IF_FALSE(TransformFunctionsMatch(nsStyleTransformMatrix::TransformFunctionOf(a1),
+                                              nsStyleTransformMatrix::TransformFunctionOf(a2)),
                       "transform function mismatch");
 
     nsCSSKeyword tfunc = nsStyleTransformMatrix::TransformFunctionOf(a1);
     nsRefPtr<nsCSSValue::Array> arr;
-    if (tfunc != eCSSKeyword_matrix && tfunc != eCSSKeyword_interpolatematrix) {
+    if (tfunc != eCSSKeyword_matrix &&
+        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,
@@ -1245,23 +1411,35 @@ AddTransformLists(const nsCSSValueList* 
 
         // 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_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 skew() and translate(), since an
         // omitted second parameter repeats the first rather than being
@@ -1276,25 +1454,39 @@ AddTransformLists(const nsCSSValueList* 
         // 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_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));
+          AddTransformScale(a1->Item(3), aCoeff1, a2->Item(3), aCoeff2,
+                            arr->Item(3));
+
+          break;
+      }
       // It would probably be nicer to animate skew in tangent space
       // rather than angle space.  However, it's easy to specify
       // skews with infinite tangents, and behavior changes pretty
       // drastically when crossing such skews (since the direction of
       // animation flips), so interop is probably more important here.
       case eCSSKeyword_skew: {
         NS_ABORT_IF_FALSE(a1->Count() == 2 || a1->Count() == 3,
                           "unexpected count");
@@ -1313,38 +1505,48 @@ AddTransformLists(const nsCSSValueList* 
         // in non-DEBUG).
         AddCSSValueAngle(a1->Item(1), aCoeff1, a2->Item(1), aCoeff2,
                          arr->Item(1));
 
         break;
       }
       case eCSSKeyword_skewx:
       case eCSSKeyword_skewy:
-      case eCSSKeyword_rotate: {
+      case eCSSKeyword_rotate:
+      case eCSSKeyword_rotatex:
+      case eCSSKeyword_rotatey:
+      case eCSSKeyword_rotatez: {
         NS_ABORT_IF_FALSE(a1->Count() == 2, "unexpected count");
         NS_ABORT_IF_FALSE(a2->Count() == 2, "unexpected count");
 
         AddCSSValueAngle(a1->Item(1), aCoeff1, a2->Item(1), aCoeff2,
                          arr->Item(1));
 
         break;
       }
       case eCSSKeyword_matrix:
-      case eCSSKeyword_interpolatematrix: {
+      case eCSSKeyword_matrix3d:
+      case eCSSKeyword_interpolatematrix:
+      case eCSSKeyword_rotate3d:
+      case eCSSKeyword_perspective: {
         // FIXME: If the matrix contains only numbers then we could decompose
-        // here. We can't do this for matrix3d though, so it's probably
-        // best to stay consistent.
+        // here.
 
         // Construct temporary lists with only this item in them.
         nsCSSValueList tempList1, tempList2;
         tempList1.mValue = aList1->mValue;
         tempList2.mValue = aList2->mValue;
 
-        *resultTail =
-          AddDifferentTransformLists(&tempList1, aCoeff1, &tempList2, aCoeff2);
+        if (aList1 == aList2) {
+          *resultTail =
+            AddDifferentTransformLists(&tempList1, aCoeff1, &tempList1, aCoeff2);
+        } else {
+          *resultTail =
+            AddDifferentTransformLists(&tempList1, aCoeff1, &tempList2, aCoeff2);
+        }
 
         while ((*resultTail)->mNext) {
           resultTail = &(*resultTail)->mNext;
         }
 
         break;
       }
       default:
@@ -1769,32 +1971,33 @@ nsStyleAnimation::AddWeighted(nsCSSPrope
       nsAutoPtr<nsCSSValueList> result;
       if (list1->mValue.GetUnit() == eCSSUnit_None) {
         if (list2->mValue.GetUnit() == eCSSUnit_None) {
           result = new nsCSSValueList;
           if (result) {
             result->mValue.SetNoneValue();
           }
         } else {
-          result = AddTransformLists(list2, aCoeff2, list2, 0);
+          result = AddTransformLists(list2, 0, list2, aCoeff2);
         }
       } else {
         if (list2->mValue.GetUnit() == eCSSUnit_None) {
-          result = AddTransformLists(list1, aCoeff1, list1, 0);
+          result = AddTransformLists(list1, 0, list1, aCoeff1);
         } else {
           PRBool match = PR_TRUE;
 
           {
             const nsCSSValueList *item1 = list1, *item2 = list2;
             do {
               nsCSSKeyword func1 = nsStyleTransformMatrix::TransformFunctionOf(
                                      item1->mValue.GetArrayValue());
               nsCSSKeyword func2 = nsStyleTransformMatrix::TransformFunctionOf(
                                      item2->mValue.GetArrayValue());
-              if (func1 != func2) {
+
+              if (!TransformFunctionsMatch(func1, func2)) {
                 break;
               }
 
               item1 = item1->mNext;
               item2 = item2->mNext;
             } while (item1 && item2);
             if (item1 || item2) {
               // Either |break| above or length mismatch.
--- a/layout/style/nsStyleAnimation.h
+++ b/layout/style/nsStyleAnimation.h
@@ -53,17 +53,17 @@
 class nsPresContext;
 class nsStyleContext;
 class nsCSSValue;
 struct nsCSSValueList;
 struct nsCSSValuePair;
 struct nsCSSValueTriplet;
 struct nsCSSValuePairList;
 struct nsCSSRect;
-struct gfxMatrix;
+class gfx3DMatrix;
 
 namespace mozilla {
 namespace dom {
 class Element;
 } // namespace dom
 } // namespace mozilla
 
 /**
@@ -228,22 +228,22 @@ public:
   static PRBool ExtractComputedValue(nsCSSProperty aProperty,
                                      nsStyleContext* aStyleContext,
                                      Value& aComputedValue);
 
    /**
     * Interpolates between 2 matrices by decomposing them.
     *
     * @param aMatrix1   First matrix, using CSS pixel units.
-    * @param aCoeff1    Interpolation value in the range [0.0, 1.0]
     * @param aMatrix2   Second matrix, using CSS pixel units.
-    * @param aCoeff2    Interpolation value in the range [0.0, 1.0]
+    * @param aProgress  Interpolation value in the range [0.0, 1.0]
     */
-   static gfxMatrix InterpolateTransformMatrix(const gfxMatrix &aMatrix1, double aCoeff1,
-                                               const gfxMatrix &aMatrix2, double aCoeff2);
+   static gfx3DMatrix InterpolateTransformMatrix(const gfx3DMatrix &aMatrix1,
+                                                 const gfx3DMatrix &aMatrix2, 
+                                                 double aProgress);
 
   /**
    * The types and values for the values that we extract and animate.
    */
   enum Unit {
     eUnit_Null, // not initialized
     eUnit_Normal,
     eUnit_Auto,
--- a/layout/style/nsStyleTransformMatrix.cpp
+++ b/layout/style/nsStyleTransformMatrix.cpp
@@ -201,44 +201,34 @@ nsStyleTransformMatrix::ProcessMatrix3D(
 /* Helper function to process two matrices that we need to interpolate between */
 /* static */ gfx3DMatrix
 nsStyleTransformMatrix::ProcessInterpolateMatrix(const nsCSSValue::Array* aData,
                                                  nsStyleContext* aContext,
                                                  nsPresContext* aPresContext,
                                                  PRBool& aCanStoreInRuleTree,
                                                  nsRect& aBounds, float aAppUnitsPerMatrixUnit)
 {
-  NS_PRECONDITION(aData->Count() == 5, "Invalid array!");
-
-  double coeff1 = aData->Item(1).GetPercentValue();
-  gfx3DMatrix matrix1 = ReadTransforms(aData->Item(2).GetListValue(),
-                                       aContext, aPresContext,
-                                       aCanStoreInRuleTree,
-                                       aBounds, aAppUnitsPerMatrixUnit);
-  double coeff2 = aData->Item(3).GetPercentValue();
-  gfx3DMatrix matrix2 = ReadTransforms(aData->Item(4).GetListValue(),
-                                       aContext, aPresContext,
-                                       aCanStoreInRuleTree,
-                                       aBounds, aAppUnitsPerMatrixUnit);
+  NS_PRECONDITION(aData->Count() == 4, "Invalid array!");
 
-  gfxMatrix matrix2d1, matrix2d2;
-#ifdef DEBUG
-  PRBool is2d =
-#endif
-  matrix1.Is2D(&matrix2d1);
-  NS_ABORT_IF_FALSE(is2d, "Can't do animations with 3d transforms!");
-#ifdef DEBUG
-  is2d =
-#endif
-  matrix2.Is2D(&matrix2d2);
-  NS_ABORT_IF_FALSE(is2d, "Can't do animations with 3d transforms!");
+  gfx3DMatrix matrix1, matrix2;
+  if (aData->Item(1).GetUnit() == eCSSUnit_List) {
+    matrix1 = ReadTransforms(aData->Item(1).GetListValue(),
+                             aContext, aPresContext,
+                             aCanStoreInRuleTree,
+                             aBounds, aAppUnitsPerMatrixUnit);
+  }
+  if (aData->Item(2).GetUnit() == eCSSUnit_List) {
+    matrix2 = ReadTransforms(aData->Item(2).GetListValue(),
+                             aContext, aPresContext,
+                             aCanStoreInRuleTree,
+                             aBounds, aAppUnitsPerMatrixUnit);
+  }
+  double progress = aData->Item(3).GetPercentValue();
 
-  return gfx3DMatrix::From2D(
-    nsStyleAnimation::InterpolateTransformMatrix(matrix2d1, coeff1, 
-                                                 matrix2d2, coeff2));
+  return nsStyleAnimation::InterpolateTransformMatrix(matrix1, matrix2, progress);
 }
 
 /* Helper function to process a translatex function. */
 /* static */ gfx3DMatrix
 nsStyleTransformMatrix::ProcessTranslateX(const nsCSSValue::Array* aData,
                                           nsStyleContext* aContext,
                                           nsPresContext* aPresContext,
                                           PRBool& aCanStoreInRuleTree,
--- a/layout/style/test/test_transitions_per_property.html
+++ b/layout/style/test/test_transitions_per_property.html
@@ -1420,30 +1420,33 @@ function test_transform_transition(prop)
       expected_uncomputed: 'skewX(22.5deg)' },
     { start: 'skewX(0)', end: 'skewX(-45deg)',
       expected_uncomputed: 'skewX(-11.25deg)' },
     { start: 'skewY(45deg)', end: 'skewY(-45deg)',
       expected_uncomputed: 'skewY(22.5deg)' },
 
     // matrix : skewX
     { start: 'matrix(1, 0, 3, 1, 0px, 0px)', end: 'none',
-      expected: 'matrix(1, 0, ' + Math.tan(Math.atan(3) * 0.75) + ', 1, 0px, 0px)',
+      expected: 'matrix(1, 0, ' + 3 * 0.75 + ', 1, 0px, 0px)',
       round_error_ok: true },
     { start: 'skewX(0)', end: 'skewX(-45deg) translate(0)',
-      expected: 'matrix(1, 0, -0.198912367, 1, 0px, 0px)',
+      expected: 'matrix(1, 0, -0.25, 1, 0px, 0px)',
       round_error_ok: true },
     // matrix : rotate
     { start: 'rotate(-30deg)', end: 'matrix(0, 1, -1, 0, 0px, 0px)',
-      expected: 'matrix(1, 0, 0, 1, 0px, 0px)' },
+      expected: 'matrix(1, 0, 0, 1, 0px, 0px)',
+      round_error_ok: true },
     { start: 'rotate(-30deg) translateX(0)',
       end: 'translateX(0) rotate(-90deg)',
-      expected: c('rotate(-45deg)') },
+      expected: c('rotate(-45deg)'),
+      round_error_ok: true },
     // matrix decomposition of skewY
     { start: 'skewY(60deg)', end: 'skewY(-60deg) translateX(0)',
-      expected: c('rotate(30deg) skewX(30deg) scale(2, 0.5)'),
+      /* rotate(30deg) skewX(60deg)/2 scale(2, 0.5) */
+      expected: c('rotate(30deg) skewX(' + Math.atan(Math.tan(Math.PI * 60/180) / 2) + 'rad) scale(2, 0.5)'),
       round_error_ok: true },
 
     // matrix decomposition
 
     // Four pairs of the same matrix expressed different ways.
     { start: 'matrix(-1, 0, 0, -1, 0pt, 0pt)', /* rotate(180deg) */
       end: 'matrix(1, 0, 0, 1, 0, 0)',
       expected: c('rotate(135deg)') },
@@ -1488,71 +1491,71 @@ function test_transform_transition(prop)
     // the animations in
     // http://dbaron.org/css/test/2010/transition-negative-determinant
     // don't flip when they finish, and then wrote tests corresponding
     // to the current code's behavior.
     // ... start with four with positive determinants
     { start: 'none',
       end: 'matrix(1, 0, 1.5, 1, 0pt, 0pt)',
       /* skewX(atan(1.5)) */
-      expected: 'matrix(1, 0, ' + Math.tan(Math.atan(1.5) * 0.25).toFixed(6) + ', 1, 0px, 0px)',
+      expected: 'matrix(1, 0, ' + 1.5 * 0.25 + ', 1, 0px, 0px)',
       round_error_ok: true },       
     { start: 'none',
       end: 'matrix(-1, 0, 2, -1, 0pt, 0pt)',
               /* rotate(180deg) skewX(atan(-2)) */
-      expected: c('rotate(45deg) matrix(1, 0, ' + Math.tan(Math.atan(-2) * 0.25) + ', 1, 0px, 0px)'),
+      expected: c('rotate(45deg) matrix(1, 0, ' + -2 * 0.25 + ', 1, 0px, 0px)'),
       round_error_ok: true },
     { start: 'none',
       end: 'matrix(0, -1, 1, -3, 0pt, 0pt)',
               /* rotate(-90deg) skewX(atan(3)) */
-      expected: c('rotate(-22.5deg) matrix(1, 0, ' + Math.tan(Math.atan(3) * 0.25) + ', 1, 0px, 0px)'),
+      expected: c('rotate(-22.5deg) matrix(1, 0, ' + 3 * 0.25 + ', 1, 0px, 0px)'),
       round_error_ok: true },
     { start: 'none',
       end: 'matrix(0, 1, -1, 4, 0pt, 0pt)',
               /* rotate(90deg) skewX(atan(4)) */
-      expected: c('rotate(22.5deg) matrix(1, 0, ' + Math.tan(Math.atan(4) * 0.25) + ', 1, 0px, 0px)'),
+      expected: c('rotate(22.5deg) matrix(1, 0, ' + 4 * 0.25 + ', 1, 0px, 0px)'),
       round_error_ok: true },
     // and then four with negative determinants
     { start: 'none',
       end: 'matrix(1, 0, 1, -1, 0pt, 0pt)',
               /* rotate(-180deg) skewX(atan(-1)) scaleX(-1) */
-      expected: c('rotate(-45deg) matrix(1, 0, ' + Math.tan(Math.atan(-1) * 0.25) + ', 1, 0px, 0px) scaleX(0.5)'),
+      expected: c('rotate(-45deg) matrix(1, 0, ' + -1 * 0.25 + ', 1, 0px, 0px) scaleX(0.5)'),
       round_error_ok: true },
     { start: 'none',
       end: 'matrix(-1, 0, -1, 1, 0pt, 0pt)',
               /* skewX(atan(-1)) scaleX(-1) */
-      expected: c('matrix(1, 0, ' + Math.tan(Math.atan(-1) * 0.25) + ', 1, 0px, 0px) scaleX(0.5)') },
+      expected: c('matrix(1, 0, ' + -1 * 0.25 + ', 1, 0px, 0px) scaleX(0.5)') },
     { start: 'none',
       end: 'matrix(0, 1, 1, -2, 0pt, 0pt)',
               /* rotate(-90deg) skewX(atan(2)) scaleX(-1) */
-      expected: c('rotate(-22.5deg) matrix(1, 0, ' + Math.tan(Math.atan(2) * 0.25) + ', 1, 0px, 0px) scaleX(0.5)'),
+      expected: c('rotate(-22.5deg) matrix(1, 0, ' + 2 * 0.25 + ', 1, 0px, 0px) scaleX(0.5)'),
       round_error_ok: true },
     { start: 'none',
       end: 'matrix(0, -1, -1, 0.5, 0pt, 0pt)',
               /* rotate(90deg) skewX(atan(0.5)) scaleX(-1) */
-      expected: c('rotate(22.5deg) matrix(1, 0, ' + Math.tan(Math.atan(0.5) * 0.25) + ', 1, 0px, 0px) scaleX(0.5)'),
+      expected: c('rotate(22.5deg) matrix(1, 0, ' + 0.5 * 0.25 + ', 1, 0px, 0px) scaleX(0.5)'),
       round_error_ok: true },
 
     // lists vs. matrix decomposition
     { start: 'translate(10px) skewY(45deg)',
       end: 'translate(30px) skewY(-45deg)',
       expected_uncomputed: 'translate(15px) skewY(22.5deg)' },
     { start: 'skewY(45deg) rotate(90deg)',
       end: 'skewY(-45deg) rotate(90deg)',
       expected_uncomputed: 'skewY(22.5deg) rotate(90deg)' },
     { start: 'skewY(45deg) rotate(90deg) translate(0)',
       end: 'skewY(-45deg) rotate(90deg)',
-      expected: c('rotate(90deg) skewX(-22.5deg)'),
+      expected: 'matrix(0, 1, -1, -0.5, 0px, 0px)',
       round_error_ok: true },
     { start: 'skewX(45deg) rotate(90deg)',
       end: 'skewX(-45deg) rotate(90deg)',
       expected_uncomputed: 'skewX(22.5deg) rotate(90deg)' },
     { start: 'skewX(-60deg) rotate(90deg) translate(0)',
       end: 'skewX(60deg) rotate(90deg)',
-      expected: c('rotate(120deg) skewX(30deg) scale(2, 0.5)'),
+      expected: c('rotate(120deg) skewX(' + Math.atan(Math.tan(Math.PI * 60/180) / 2) + 'rad) scale(2, 0.5)'),
       round_error_ok: true },
   ];
 
   var matrix_re = /^matrix\(([^,]*), ([^,]*), ([^,]*), ([^,]*), ([^,]*)px, ([^,]*)px\)$/;
   for (var i in tests) {
     var test = tests[i];
     if (!("expected" in test)) {
       var v = test.expected_uncomputed;