Bug 505115 - Part 9 - Implement the perspective() transform function and the perspective CSS property. r=dbaron
authorMatt Woodrow <mwoodrow@mozilla.com>
Wed, 03 Aug 2011 15:04:22 +1200
changeset 73706 96941ed69aeb5d33faddea7d13d338f9ac87c49a
parent 73705 08b756f93436c48e5c32a01a24dd3633ee2b0593
child 73707 85cc7836c552230773782842fe5be67cc70a6298
push id933
push usermwoodrow@mozilla.com
push dateWed, 03 Aug 2011 03:05:32 +0000
treeherdermozilla-inbound@166f4f247772 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdbaron
bugs505115
milestone8.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 9 - Implement the perspective() transform function and the perspective CSS property. r=dbaron
dom/interfaces/css/nsIDOMCSS2Properties.idl
layout/base/nsDisplayList.cpp
layout/style/nsCSSKeywordList.h
layout/style/nsCSSParser.cpp
layout/style/nsCSSPropList.h
layout/style/nsComputedDOMStyle.cpp
layout/style/nsComputedDOMStyle.h
layout/style/nsRuleNode.cpp
layout/style/nsStyleStruct.cpp
layout/style/nsStyleStruct.h
layout/style/nsStyleTransformMatrix.cpp
layout/style/nsStyleTransformMatrix.h
layout/style/test/property_database.js
layout/style/test/test_transitions_per_property.html
--- a/dom/interfaces/css/nsIDOMCSS2Properties.idl
+++ b/dom/interfaces/css/nsIDOMCSS2Properties.idl
@@ -46,17 +46,17 @@
  * The nsIDOMCSS2Properties interface is a datatype for additional
  * reflection of data already provided in nsIDOMCSSStyleDeclaration in
  * the Document Object Model.
  *
  * For more information on this interface please see
  * http://www.w3.org/TR/DOM-Level-2-Style
  */
 
-[scriptable, uuid(7cf11a5f-4be5-4e31-b427-58d82746b5f5)]
+[scriptable, uuid(1ca298f0-4eaf-4483-8aa2-587f392f84bb)]
 interface nsIDOMCSS2Properties : nsISupports
 {
            attribute DOMString        background;
                                         // raises(DOMException) on setting
 
            attribute DOMString        backgroundAttachment;
                                         // raises(DOMException) on setting
 
@@ -676,16 +676,19 @@ interface nsIDOMCSS2Properties : nsISupp
                                         // raises(DOMException) on setting
 
            attribute DOMString        MozTransform;
                                         // raises(DOMException) on setting
 
            attribute DOMString        MozTransformOrigin;
                                         // raises(DOMException) on setting 
 
+           attribute DOMString        MozPerspective;
+                                        // raises(DOMException) on setting
+
            attribute DOMString        MozWindowShadow;
                                         // raises(DOMException) on setting
 
            attribute DOMString        backgroundSize;
                                         // raises(DOMException) on setting
 
            attribute DOMString        MozTextBlink;
                                         // raises(DOMException) on setting
--- a/layout/base/nsDisplayList.cpp
+++ b/layout/base/nsDisplayList.cpp
@@ -2371,34 +2371,49 @@ nsDisplayTransform::GetResultingTransfor
    * bounds of the frame.
    */
   const nsStyleDisplay* disp = aFrame->GetStyleDisplay();
   nsRect bounds = (aBoundsOverride ? *aBoundsOverride :
                    nsDisplayTransform::GetFrameBoundsForTransform(aFrame));
 
   /* Get the matrix, then change its basis to factor in the origin. */
   PRBool dummy;
+  gfx3DMatrix result =
+    nsStyleTransformMatrix::ReadTransforms(disp->mSpecifiedTransform,
+                                           aFrame->GetStyleContext(),
+                                           aFrame->PresContext(),
+                                           dummy, bounds, aFactor);
+
+  const nsStyleDisplay* parentDisp = nsnull;
+  if (aFrame->GetParent()) {
+    parentDisp = aFrame->GetParent()->GetStyleDisplay();
+  }
+  if (nsLayoutUtils::Are3DTransformsEnabled() &&
+      parentDisp && parentDisp->mChildPerspective.GetUnit() == eStyleUnit_Coord &&
+      parentDisp->mChildPerspective.GetCoordValue() > 0.0) {
+    gfx3DMatrix perspective;
+    perspective._34 =
+      -1.0 / NSAppUnitsToFloatPixels(parentDisp->mChildPerspective.GetCoordValue(),
+                                     aFactor);
+    result = result * perspective;
+  }
   return nsLayoutUtils::ChangeMatrixBasis
-    (newOrigin + toMozOrigin, 
-     nsStyleTransformMatrix::ReadTransforms(disp->mSpecifiedTransform,
-                                            aFrame->GetStyleContext(),
-                                            aFrame->PresContext(),
-                                            dummy, bounds, aFactor));
-}
-
-const gfx3DMatrix&
-nsDisplayTransform::GetTransform(float aFactor)
-{
-  if (mTransform.IsIdentity() || mCachedFactor != aFactor) {
-    mTransform =
-      GetResultingTransformMatrix(mFrame, ToReferenceFrame(),
-                                  aFactor,
-                                  nsnull);
-    mCachedFactor = aFactor;
-  }
+    (newOrigin + toMozOrigin, result);
+}
+
+const gfx3DMatrix&
+nsDisplayTransform::GetTransform(float aFactor)
+{
+  if (mTransform.IsIdentity() || mCachedFactor != aFactor) {
+    mTransform =
+      GetResultingTransformMatrix(mFrame, ToReferenceFrame(),
+                                  aFactor,
+                                  nsnull);
+    mCachedFactor = aFactor;
+  }
   return mTransform;
 }
 
 already_AddRefed<Layer> nsDisplayTransform::BuildLayer(nsDisplayListBuilder *aBuilder,
                                                        LayerManager *aManager,
                                                        const ContainerParameters& aContainerParameters)
 {
   const gfx3DMatrix& newTransformMatrix = 
--- a/layout/style/nsCSSKeywordList.h
+++ b/layout/style/nsCSSKeywordList.h
@@ -362,16 +362,17 @@ CSS_KEY(oblique, oblique)
 CSS_KEY(open-quote, open_quote)
 CSS_KEY(outset, outset)
 CSS_KEY(outside, outside)
 CSS_KEY(overline, overline)
 CSS_KEY(padding-box, padding_box)
 CSS_KEY(painted, painted)
 CSS_KEY(paused, paused)
 CSS_KEY(pc, pc)
+CSS_KEY(perspective, perspective)
 CSS_KEY(physical, physical)
 CSS_KEY(pointer, pointer)
 CSS_KEY(portrait, portrait)
 CSS_KEY(pre, pre)
 CSS_KEY(pre-wrap, pre_wrap)
 CSS_KEY(pre-line, pre_line)
 CSS_KEY(progress, progress)
 CSS_KEY(progressive, progressive)
--- a/layout/style/nsCSSParser.cpp
+++ b/layout/style/nsCSSParser.cpp
@@ -116,16 +116,17 @@ namespace css = mozilla::css;
 #define VARIANT_GRADIENT        0x200000  // eCSSUnit_Gradient
 #define VARIANT_TIMING_FUNCTION 0x400000  // cubic-bezier() and steps()
 #define VARIANT_ALL             0x800000  //
 #define VARIANT_IMAGE_RECT    0x01000000  // eCSSUnit_Function
 // This is an extra bit that says that a VARIANT_ANGLE allows unitless zero:
 #define VARIANT_ZERO_ANGLE    0x02000000  // unitless zero for angles
 #define VARIANT_CALC          0x04000000  // eCSSUnit_Calc
 #define VARIANT_ELEMENT       0x08000000  // eCSSUnit_Element
+#define VARIANT_POSITIVE_LENGTH 0x10000000 // Only lengths greater than 0.0
 
 // Common combinations of variants
 #define VARIANT_AL   (VARIANT_AUTO | VARIANT_LENGTH)
 #define VARIANT_LP   (VARIANT_LENGTH | VARIANT_PERCENT)
 #define VARIANT_AH   (VARIANT_AUTO | VARIANT_INHERIT)
 #define VARIANT_AHLP (VARIANT_AH | VARIANT_LP)
 #define VARIANT_AHI  (VARIANT_AH | VARIANT_INTEGER)
 #define VARIANT_AHK  (VARIANT_AH | VARIANT_KEYWORD)
@@ -4540,16 +4541,22 @@ CSSParserImpl::ParseVariant(nsCSSValue& 
     return PR_TRUE;
   }
   if (((aVariantMask & (VARIANT_LENGTH | VARIANT_ANGLE |
                         VARIANT_FREQUENCY | VARIANT_TIME)) != 0 &&
        eCSSToken_Dimension == tk->mType) ||
       ((aVariantMask & (VARIANT_LENGTH | VARIANT_ZERO_ANGLE)) != 0 &&
        eCSSToken_Number == tk->mType &&
        tk->mNumber == 0.0f)) {
+    if ((aVariantMask & VARIANT_POSITIVE_LENGTH) != 0 && 
+        eCSSToken_Number == tk->mType &&
+        tk->mNumber <= 0.0) {
+        UngetToken();
+        return PR_FALSE;
+    }
     if (TranslateDimension(aValue, aVariantMask, tk->mNumber, tk->mIdent)) {
       return PR_TRUE;
     }
     // Put the token back; we didn't parse it, so we shouldn't consume it
     UngetToken();
     return PR_FALSE;
   }
   if (((aVariantMask & VARIANT_PERCENT) != 0) &&
@@ -7273,44 +7280,46 @@ static PRBool GetFunctionParseInformatio
    */
   enum { eLengthPercentCalc,
          eLengthCalc,
          eTwoLengthPercentCalcs,
          eTwoLengthPercentCalcsOneLengthCalc,
          eAngle,
          eTwoAngles,
          eNumber,
+         ePositiveLength,
          eTwoNumbers,
          eThreeNumbers,
          eThreeNumbersOneAngle,
          eMatrix,
          eMatrix3d,
          eNumVariantMasks };
   static const PRInt32 kMaxElemsPerFunction = 16;
   static const PRInt32 kVariantMasks[eNumVariantMasks][kMaxElemsPerFunction] = {
     {VARIANT_TRANSFORM_LPCALC},
     {VARIANT_LENGTH|VARIANT_CALC},
     {VARIANT_TRANSFORM_LPCALC, VARIANT_TRANSFORM_LPCALC},
     {VARIANT_TRANSFORM_LPCALC, VARIANT_TRANSFORM_LPCALC, VARIANT_LENGTH|VARIANT_CALC},
     {VARIANT_ANGLE_OR_ZERO},
     {VARIANT_ANGLE_OR_ZERO, VARIANT_ANGLE_OR_ZERO},
     {VARIANT_NUMBER},
+    {VARIANT_LENGTH|VARIANT_POSITIVE_LENGTH},
     {VARIANT_NUMBER, VARIANT_NUMBER},
     {VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER},
     {VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_ANGLE_OR_ZERO},
     {VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER,
      VARIANT_TRANSFORM_LPCALC, VARIANT_TRANSFORM_LPCALC},
     {VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER,
      VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER,
      VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER,
      VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER}};
 
 #ifdef DEBUG
   static const PRUint8 kVariantMaskLengths[eNumVariantMasks] =
-    {1, 1, 2, 3, 1, 2, 1, 2, 3, 4, 6, 16};
+    {1, 1, 2, 3, 1, 2, 1, 1, 2, 3, 4, 6, 16};
 #endif
 
   PRInt32 variantIndex = eNumVariantMasks;
 
   aIs3D = PR_FALSE;
 
   switch (aToken) {
   case eCSSKeyword_translatex:
@@ -7404,16 +7413,23 @@ static PRBool GetFunctionParseInformatio
     break;
   case eCSSKeyword_matrix3d:
     /* 16 matrix values, all numbers */
     variantIndex = eMatrix3d;
     aMinElems = 16U;
     aMaxElems = 16U;
     aIs3D = PR_TRUE;
     break;
+  case eCSSKeyword_perspective:
+    /* Exactly one scale number. */
+    variantIndex = ePositiveLength;
+    aMinElems = 1U;
+    aMaxElems = 1U;
+    aIs3D = PR_TRUE;
+    break;
   default:
     /* Oh dear, we didn't match.  Report an error. */
     return PR_FALSE;
   }
 
   NS_ASSERTION(aMinElems > 0, "Didn't update minimum elements!");
   NS_ASSERTION(aMaxElems > 0, "Didn't update maximum elements!");
   NS_ASSERTION(aMinElems <= aMaxElems, "aMinElems > aMaxElems!");
--- a/layout/style/nsCSSPropList.h
+++ b/layout/style/nsCSSPropList.h
@@ -2253,16 +2253,25 @@ CSS_PROP_DISPLAY(
     _moz_transform_origin,
     CSS_PROP_DOMPROP_PREFIXED(TransformOrigin),
     CSS_PROPERTY_PARSE_FUNCTION |
         CSS_PROPERTY_STORES_CALC,
     0,
     kBackgroundPositionKTable,
     CSS_PROP_NO_OFFSET,
     eStyleAnimType_Custom)
+CSS_PROP_DISPLAY(
+    -moz-perspective,
+    perspective,
+    CSS_PROP_DOMPROP_PREFIXED(Perspective),
+    CSS_PROPERTY_PARSE_VALUE,
+    VARIANT_NONE | VARIANT_INHERIT | VARIANT_LENGTH,
+    nsnull,
+    offsetof(nsStyleDisplay, mChildPerspective),
+    eStyleAnimType_Coord)
 CSS_PROP_POSITION(
     top,
     top,
     Top,
     CSS_PROPERTY_PARSE_VALUE |
         CSS_PROPERTY_STORES_CALC,
     VARIANT_AHLP | VARIANT_CALC,
     nsnull,
--- a/layout/style/nsComputedDOMStyle.cpp
+++ b/layout/style/nsComputedDOMStyle.cpp
@@ -936,16 +936,29 @@ nsComputedDOMStyle::DoGetMozTransformOri
   nsROCSSPrimitiveValue* height = GetROCSSPrimitiveValue();
   SetValueToCoord(height, display->mTransformOrigin[1], PR_FALSE,
                   &nsComputedDOMStyle::GetFrameBoundsHeightForTransform);
   valueList->AppendCSSValue(height);
 
   return valueList;
 }
 
+nsIDOMCSSValue*
+nsComputedDOMStyle::DoGetMozPerspective()
+{
+    nsROCSSPrimitiveValue* val = GetROCSSPrimitiveValue();
+    if (GetStyleDisplay()->mChildPerspective.GetUnit() == eStyleUnit_Coord &&
+        GetStyleDisplay()->mChildPerspective.GetCoordValue() == 0.0) {
+        val->SetIdent(eCSSKeyword_none);
+    } else {
+        SetValueToCoord(val, GetStyleDisplay()->mChildPerspective, PR_FALSE);
+    }
+    return val;
+}
+
 /* If the property is "none", hand back "none" wrapped in a value.
  * Otherwise, compute the aggregate transform matrix and hands it back in a
  * "matrix" wrapper.
  */
 nsIDOMCSSValue*
 nsComputedDOMStyle::DoGetMozTransform()
 {
   /* First, get the display data.  We'll need it. */
@@ -4392,16 +4405,17 @@ nsComputedDOMStyle::GetQueryableProperty
     COMPUTED_STYLE_MAP_ENTRY(force_broken_image_icon,       ForceBrokenImageIcon),
     COMPUTED_STYLE_MAP_ENTRY(hyphens,                       Hyphens),
     COMPUTED_STYLE_MAP_ENTRY(image_region,                  ImageRegion),
     COMPUTED_STYLE_MAP_ENTRY(orient,                        Orient),
     COMPUTED_STYLE_MAP_ENTRY_LAYOUT(_moz_outline_radius_bottomLeft, OutlineRadiusBottomLeft),
     COMPUTED_STYLE_MAP_ENTRY_LAYOUT(_moz_outline_radius_bottomRight,OutlineRadiusBottomRight),
     COMPUTED_STYLE_MAP_ENTRY_LAYOUT(_moz_outline_radius_topLeft,    OutlineRadiusTopLeft),
     COMPUTED_STYLE_MAP_ENTRY_LAYOUT(_moz_outline_radius_topRight,   OutlineRadiusTopRight),
+    COMPUTED_STYLE_MAP_ENTRY(perspective,                   MozPerspective),
     COMPUTED_STYLE_MAP_ENTRY(stack_sizing,                  StackSizing),
     COMPUTED_STYLE_MAP_ENTRY(_moz_tab_size,                 MozTabSize),
     COMPUTED_STYLE_MAP_ENTRY(text_blink,                    MozTextBlink),
     COMPUTED_STYLE_MAP_ENTRY(text_decoration_color,         MozTextDecorationColor),
     COMPUTED_STYLE_MAP_ENTRY(text_decoration_line,          MozTextDecorationLine),
     COMPUTED_STYLE_MAP_ENTRY(text_decoration_style,         MozTextDecorationStyle),
     COMPUTED_STYLE_MAP_ENTRY_LAYOUT(_moz_transform,         MozTransform),
     COMPUTED_STYLE_MAP_ENTRY_LAYOUT(_moz_transform_origin,  MozTransformOrigin),
--- a/layout/style/nsComputedDOMStyle.h
+++ b/layout/style/nsComputedDOMStyle.h
@@ -342,16 +342,17 @@ private:
   nsIDOMCSSValue* DoGetOverflow();
   nsIDOMCSSValue* DoGetOverflowX();
   nsIDOMCSSValue* DoGetOverflowY();
   nsIDOMCSSValue* DoGetResize();
   nsIDOMCSSValue* DoGetPageBreakAfter();
   nsIDOMCSSValue* DoGetPageBreakBefore();
   nsIDOMCSSValue* DoGetMozTransform();
   nsIDOMCSSValue* DoGetMozTransformOrigin();
+  nsIDOMCSSValue* DoGetMozPerspective();
   nsIDOMCSSValue* DoGetOrient();
 
   /* User interface properties */
   nsIDOMCSSValue* DoGetCursor();
   nsIDOMCSSValue* DoGetForceBrokenImageIcon();
   nsIDOMCSSValue* DoGetIMEMode();
   nsIDOMCSSValue* DoGetUserFocus();
   nsIDOMCSSValue* DoGetUserInput();
--- a/layout/style/nsRuleNode.cpp
+++ b/layout/style/nsRuleNode.cpp
@@ -4479,16 +4479,21 @@ nsRuleNode::ComputeDisplayData(void* aSt
                     parentDisplay->mTransformOrigin[0],
                     parentDisplay->mTransformOrigin[1],
                     SETCOORD_LPH | SETCOORD_INITIAL_HALF |
                     SETCOORD_BOX_POSITION | SETCOORD_STORE_CALC,
                     aContext, mPresContext, canStoreInRuleTree);
     NS_ASSERTION(result, "Malformed -moz-transform-origin parse!");
   }
 
+  SetCoord(*aRuleData->ValueForPerspective(), 
+           display->mChildPerspective, parentDisplay->mChildPerspective,
+           SETCOORD_LAH | SETCOORD_INITIAL_ZERO | SETCOORD_NONE,
+           aContext, mPresContext, canStoreInRuleTree);
+
   // orient: enum, inherit, initial
   SetDiscrete(*aRuleData->ValueForOrient(),
               display->mOrient, canStoreInRuleTree,
               SETDSC_ENUMERATED, parentDisplay->mOrient,
               NS_STYLE_ORIENT_HORIZONTAL, 0, 0, 0, 0);
 
   COMPUTE_END_RESET(Display, display)
 }
--- a/layout/style/nsStyleStruct.cpp
+++ b/layout/style/nsStyleStruct.cpp
@@ -2028,16 +2028,17 @@ nsStyleDisplay::nsStyleDisplay()
   mOverflowY = NS_STYLE_OVERFLOW_VISIBLE;
   mResize = NS_STYLE_RESIZE_NONE;
   mClipFlags = NS_STYLE_CLIP_AUTO;
   mClip.SetRect(0,0,0,0);
   mOpacity = 1.0f;
   mSpecifiedTransform = nsnull;
   mTransformOrigin[0].SetPercentValue(0.5f); // Transform is centered on origin
   mTransformOrigin[1].SetPercentValue(0.5f); 
+  mChildPerspective.SetCoordValue(0);
   mOrient = NS_STYLE_ORIENT_HORIZONTAL;
 
   mTransitions.AppendElement();
   NS_ABORT_IF_FALSE(mTransitions.Length() == 1,
                     "appending within auto buffer should never fail");
   mTransitions[0].SetInitialValues();
   mTransitionTimingFunctionCount = 1;
   mTransitionDurationCount = 1;
@@ -2093,16 +2094,17 @@ nsStyleDisplay::nsStyleDisplay(const nsS
   mOrient = aSource.mOrient;
 
   /* Copy over the transformation information. */
   mSpecifiedTransform = aSource.mSpecifiedTransform;
   
   /* Copy over transform origin. */
   mTransformOrigin[0] = aSource.mTransformOrigin[0];
   mTransformOrigin[1] = aSource.mTransformOrigin[1];
+  mChildPerspective = aSource.mChildPerspective;
 }
 
 nsChangeHint nsStyleDisplay::CalcDifference(const nsStyleDisplay& aOther) const
 {
   nsChangeHint hint = nsChangeHint(0);
 
   if (!EqualURIs(mBinding, aOther.mBinding)
       || mPosition != aOther.mPosition
@@ -2154,16 +2156,20 @@ nsChangeHint nsStyleDisplay::CalcDiffere
                                          nsChangeHint_UpdateTransformLayer));
     
     for (PRUint8 index = 0; index < 2; ++index)
       if (mTransformOrigin[index] != aOther.mTransformOrigin[index]) {
         NS_UpdateHint(hint, NS_CombineHint(nsChangeHint_ReflowFrame,
                                            nsChangeHint_RepaintFrame));
         break;
       }
+    
+    if (mChildPerspective != aOther.mChildPerspective)
+      NS_UpdateHint(hint, NS_CombineHint(nsChangeHint_ReflowFrame,
+                                         nsChangeHint_RepaintFrame));
   }
 
   // Note:  Our current behavior for handling changes to the
   // transition-duration, transition-delay, and transition-timing-function
   // properties is to do nothing.  In other words, the transition
   // property that matters is what it is when the transition begins, and
   // we don't stop a transition later because the transition property
   // changed.
--- a/layout/style/nsStyleStruct.h
+++ b/layout/style/nsStyleStruct.h
@@ -1510,16 +1510,17 @@ struct nsStyleDisplay {
   PRUint8 mOrient;              // [reset] see nsStyleConsts.h
 
   // mSpecifiedTransform is the list of transform functions as
   // specified, or null to indicate there is no transform.  (inherit or
   // initial are replaced by an actual list of transform functions, or
   // null, as appropriate.) (owned by the style rule)
   const nsCSSValueList *mSpecifiedTransform; // [reset]
   nsStyleCoord mTransformOrigin[2]; // [reset] percent, coord, calc
+  nsStyleCoord mChildPerspective; // [reset] coord
 
   nsAutoTArray<nsTransition, 1> mTransitions; // [reset]
   // The number of elements in mTransitions that are not from repeating
   // a list due to another property being longer.
   PRUint32 mTransitionTimingFunctionCount,
            mTransitionDurationCount,
            mTransitionDelayCount,
            mTransitionPropertyCount;
--- a/layout/style/nsStyleTransformMatrix.cpp
+++ b/layout/style/nsStyleTransformMatrix.cpp
@@ -602,16 +602,44 @@ nsStyleTransformMatrix::ProcessRotate3D(
   temp._34 = 0.0f;
   temp._41 = 0.0f;
   temp._42 = 0.0f;
   temp._43 = 0.0f;
   temp._44 = 1.0f;
   return temp;
 }
 
+/* static */ gfx3DMatrix
+nsStyleTransformMatrix::ProcessPerspective(const nsCSSValue::Array* aData,
+                                           nsStyleContext *aContext,
+                                           nsPresContext *aPresContext,
+                                           PRBool &aCanStoreInRuleTree,
+                                           float aAppUnitsPerMatrixUnit)
+{
+  NS_PRECONDITION(aData->Count() == 2, "Invalid array!");
+
+  /* We want our matrix to look like this:
+   * | 1 0 0        0 |
+   * | 0 1 0        0 |
+   * | 0 0 1 -1/depth |
+   * | 0 0 0        1 |
+   */
+
+  gfx3DMatrix temp;
+
+  float depth;
+  ProcessTranslatePart(depth, aData->Item(1), aContext,
+                       aPresContext, aCanStoreInRuleTree,
+                       0, aAppUnitsPerMatrixUnit);
+  NS_ASSERTION(depth > 0.0, "Perspective must be positive!");
+  temp._34 = -1.0/depth;
+
+  return temp;
+}
+
 /**
  * Return the transform function, as an nsCSSKeyword, for the given
  * nsCSSValue::Array from a transform list.
  */
 /* static */ nsCSSKeyword
 nsStyleTransformMatrix::TransformFunctionOf(const nsCSSValue::Array* aData)
 {
   nsAutoString keyword;
@@ -682,16 +710,19 @@ nsStyleTransformMatrix::MatrixForTransfo
   case eCSSKeyword_matrix:
     return ProcessMatrix(aData, aContext, aPresContext,
                          aCanStoreInRuleTree, aBounds, aAppUnitsPerMatrixUnit);
   case eCSSKeyword_matrix3d:
     return ProcessMatrix3D(aData);
   case eCSSKeyword_interpolatematrix:
     return ProcessInterpolateMatrix(aData, aContext, aPresContext,
                                     aCanStoreInRuleTree, aBounds, aAppUnitsPerMatrixUnit);
+  case eCSSKeyword_perspective:
+    return ProcessPerspective(aData, aContext, aPresContext, 
+                              aCanStoreInRuleTree, aAppUnitsPerMatrixUnit);
   default:
     NS_NOTREACHED("Unknown transform function!");
   }
   return gfx3DMatrix();
 }
 
 /* static */ gfx3DMatrix
 nsStyleTransformMatrix::ReadTransforms(const nsCSSValueList* aList,
--- a/layout/style/nsStyleTransformMatrix.h
+++ b/layout/style/nsStyleTransformMatrix.h
@@ -145,11 +145,16 @@ class nsStyleTransformMatrix
   static gfx3DMatrix ProcessSkewHelper(double aXAngle, double aYAngle);
   static gfx3DMatrix ProcessSkewX(const nsCSSValue::Array *aData);
   static gfx3DMatrix ProcessSkewY(const nsCSSValue::Array *aData);
   static gfx3DMatrix ProcessSkew(const nsCSSValue::Array *aData);
   static gfx3DMatrix ProcessRotateX(const nsCSSValue::Array *aData);
   static gfx3DMatrix ProcessRotateY(const nsCSSValue::Array *aData);
   static gfx3DMatrix ProcessRotateZ(const nsCSSValue::Array *aData);
   static gfx3DMatrix ProcessRotate3D(const nsCSSValue::Array *aData);
+  static gfx3DMatrix ProcessPerspective(const nsCSSValue::Array *aData,
+                                        nsStyleContext *aContext,
+                                        nsPresContext *aPresContext,
+                                        PRBool &aCanStoreInRuleTree,
+                                        float aAppUnitsPerMatrixUnit);
 };
 
 #endif
--- a/layout/style/test/property_database.js
+++ b/layout/style/test/property_database.js
@@ -962,16 +962,24 @@ var gCSSProperties = {
 			"-moz-calc(20px + 50%) -moz-calc(50% - 10px)",
 			"-moz-calc(-20px) -moz-calc(-50%)",
 			"-moz-calc(-20%) -moz-calc(-50%)"
 		],
 		invalid_values: ["red", "auto", "none", "0.5 0.5", "40px #0000ff",
 						 "border", "center red", "right diagonal",
 						 "#00ffff bottom"]
 	},
+	"-moz-perspective": {
+		domProp: "MozPerspective",
+		inherited: false,
+		type: CSS_TYPE_LONGHAND,
+		initial_values: [ "none", "0" ],
+		other_values: [ "1000px", "500.2px", "-100px", "-27.2em" ],
+		invalid_values: [ "pants", "200" ]
+	},
 	"-moz-user-focus": {
 		domProp: "MozUserFocus",
 		inherited: true,
 		type: CSS_TYPE_LONGHAND,
 		initial_values: [ "none" ],
 		other_values: [ "normal", "ignore", "select-all", "select-before", "select-after", "select-same", "select-menu" ],
 		invalid_values: []
 	},
--- a/layout/style/test/test_transitions_per_property.html
+++ b/layout/style/test/test_transitions_per_property.html
@@ -186,16 +186,17 @@ var supported_properties = {
                       test_length_percent_calc_transition,
                       test_length_clamped, test_percent_clamped ],
     "padding-right": [ test_length_transition, test_percent_transition,
                        test_length_percent_calc_transition,
                        test_length_clamped, test_percent_clamped ],
     "padding-top": [ test_length_transition, test_percent_transition,
                      test_length_percent_calc_transition,
                      test_length_clamped, test_percent_clamped ],
+    "-moz-perspective": [ test_length_transition ],
     "right": [ test_length_transition, test_percent_transition,
                test_length_percent_calc_transition,
                test_length_unclamped, test_percent_unclamped ],
     "stop-color": [ test_color_transition ],
     "stop-opacity" : [ test_float_zeroToOne_transition,
                        // opacity is clamped in computed style
                        // (not parsing/interpolation)
                        test_float_zeroToOne_clamped ],