Bug 435293. Implement CSS transforms. r=dbaron,r+sr=roc
authorKeith Schwarz <keith@keithschwarz.com>
Sat, 13 Sep 2008 21:42:11 +1200
changeset 19214 b827e694565dc90907b641cd9f4e4f7179bee895
parent 19213 d07d215cf20fefe2ea70d6711394dfa65da3cce8
child 19215 db31a92daecba10e547e6b5762434d92735eecab
push id2024
push userrocallahan@mozilla.com
push dateSat, 13 Sep 2008 09:42:43 +0000
treeherdermozilla-central@b827e694565d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersdbaron, r
bugs435293
milestone1.9.1b1pre
Bug 435293. Implement CSS transforms. r=dbaron,r+sr=roc
dom/public/idl/css/nsIDOMCSS2Properties.idl
gfx/public/nsCoord.h
layout/base/nsCSSFrameConstructor.cpp
layout/base/nsDisplayList.cpp
layout/base/nsDisplayList.h
layout/base/nsLayoutUtils.cpp
layout/base/nsLayoutUtils.h
layout/base/nsPresShell.cpp
layout/generic/nsContainerFrame.cpp
layout/generic/nsFrame.cpp
layout/generic/nsGfxScrollFrame.cpp
layout/generic/nsIFrame.h
layout/reftests/reftest.list
layout/reftests/transform/abspos-1-ref.html
layout/reftests/transform/abspos-1a.html
layout/reftests/transform/abspos-1b.html
layout/reftests/transform/abspos-1c.html
layout/reftests/transform/abspos-1d.html
layout/reftests/transform/abspos-1e.html
layout/reftests/transform/origin-1-ref.html
layout/reftests/transform/origin-1a.html
layout/reftests/transform/origin-1b.html
layout/reftests/transform/origin-2-ref.html
layout/reftests/transform/origin-2a.html
layout/reftests/transform/origin-2b.html
layout/reftests/transform/origin-2c.html
layout/reftests/transform/origin-name-1-ref.html
layout/reftests/transform/origin-name-1a.html
layout/reftests/transform/origin-name-1b.html
layout/reftests/transform/origin-name-2-ref.html
layout/reftests/transform/origin-name-2a.html
layout/reftests/transform/origin-name-2b.html
layout/reftests/transform/origin-name-2c.html
layout/reftests/transform/origin-name-3-ref.html
layout/reftests/transform/origin-name-3a.html
layout/reftests/transform/origin-name-3b.html
layout/reftests/transform/percent-1-ref.html
layout/reftests/transform/percent-1a.html
layout/reftests/transform/percent-1b.html
layout/reftests/transform/percent-1c.html
layout/reftests/transform/percent-1d.html
layout/reftests/transform/percent-1e.html
layout/reftests/transform/percent-1f.html
layout/reftests/transform/percent-1g.html
layout/reftests/transform/reftest.list
layout/reftests/transform/rotate-1-ref.html
layout/reftests/transform/rotate-1a.html
layout/reftests/transform/rotate-1b.html
layout/reftests/transform/rotate-1c.html
layout/reftests/transform/rotate-1d.html
layout/reftests/transform/rotate-1e.html
layout/reftests/transform/rotate-2-ref.html
layout/reftests/transform/rotate-2a.html
layout/reftests/transform/transform-svg-1-ref.xhtml
layout/reftests/transform/transform-svg-1a.xhtml
layout/reftests/transform/transform-svg-1b.xhtml
layout/reftests/transform/transform-svg-2-fail.xhtml
layout/reftests/transform/transform-svg-2-ref.xhtml
layout/reftests/transform/transform-svg-2a.xhtml
layout/reftests/transform/translate-1-ref.html
layout/reftests/transform/translate-1a.html
layout/reftests/transform/translate-1b.html
layout/reftests/transform/translate-1c.html
layout/reftests/transform/translate-1d.html
layout/reftests/transform/translate-1e.html
layout/reftests/transform/translatex-1-ref-2.html
layout/reftests/transform/translatex-1-ref.html
layout/reftests/transform/translatex-1a.html
layout/reftests/transform/translatex-1b.html
layout/reftests/transform/translatex-1c.html
layout/reftests/transform/translatex-1d.html
layout/reftests/transform/translatex-1e.html
layout/reftests/transform/translatex-2.html
layout/reftests/transform/translatey-1-ref-2.html
layout/reftests/transform/translatey-1-ref.html
layout/reftests/transform/translatey-1a.html
layout/reftests/transform/translatey-1b.html
layout/reftests/transform/translatey-1c.html
layout/reftests/transform/translatey-1d.html
layout/reftests/transform/translatey-1e.html
layout/reftests/transform/translatey-2.html
layout/style/Makefile.in
layout/style/nsCSSDeclaration.cpp
layout/style/nsCSSKeywordList.h
layout/style/nsCSSParser.cpp
layout/style/nsCSSPropList.h
layout/style/nsCSSStruct.cpp
layout/style/nsCSSStruct.h
layout/style/nsCSSValue.cpp
layout/style/nsCSSValue.h
layout/style/nsComputedDOMStyle.cpp
layout/style/nsComputedDOMStyle.h
layout/style/nsRuleNode.cpp
layout/style/nsRuleNode.h
layout/style/nsStyleStruct.cpp
layout/style/nsStyleStruct.h
layout/style/nsStyleTransformMatrix.cpp
layout/style/nsStyleTransformMatrix.h
layout/style/test/property_database.js
layout/svg/base/src/nsSVGForeignObjectFrame.cpp
layout/svg/base/src/nsSVGForeignObjectFrame.h
view/public/nsIView.h
view/public/nsIViewObserver.h
view/src/nsScrollPortView.cpp
view/src/nsView.cpp
view/src/nsViewManager.cpp
--- a/dom/public/idl/css/nsIDOMCSS2Properties.idl
+++ b/dom/public/idl/css/nsIDOMCSS2Properties.idl
@@ -401,17 +401,17 @@ interface nsIDOMCSS2Properties : nsISupp
 
            attribute DOMString        wordSpacing;
                                         // raises(DOMException) on setting
 
            attribute DOMString        zIndex;
                                         // raises(DOMException) on setting
 };
 
-[scriptable, uuid(216343fe-4e61-11dd-9843-001485f1fdbb)]
+[scriptable, uuid(06b9eb9a-a845-4cb7-a941-fa87305ded4b)]
 interface nsIDOMNSCSS2Properties : nsIDOMCSS2Properties
 {
            /* Non-DOM 2 extensions */
 
            /* Mozilla extension CSS properties */
            attribute DOMString        MozAppearance;
                                         // raises(DOMException) on setting
 
@@ -609,9 +609,16 @@ interface nsIDOMNSCSS2Properties : nsIDO
            attribute DOMString        MozColumnRuleStyle;
                                         // raises(DOMException) on setting
 
            attribute DOMString        MozColumnRuleColor;
                                         // raises(DOMException) on setting
 
            attribute DOMString        wordWrap;
                                         // raises(DOMException) on setting
+
+           attribute DOMString        MozTransform;
+                                        // raises(DOMException) on setting
+
+           attribute DOMString        MozTransformOrigin;
+                                        // raises(DOMException) on setting 
+	
 };
--- a/gfx/public/nsCoord.h
+++ b/gfx/public/nsCoord.h
@@ -315,17 +315,17 @@ inline PRInt32 NSToIntCeil(float aValue)
 inline PRInt32 NSToIntRound(float aValue)
 {
   return NS_lroundf(aValue);
 }
 
 /* 
  * App Unit/Pixel conversions
  */
-inline nscoord NSFloatPixelsToAppUnits(float aPixels, PRInt32 aAppUnitsPerPixel)
+inline nscoord NSFloatPixelsToAppUnits(float aPixels, float aAppUnitsPerPixel)
 {
   float product = aPixels * aAppUnitsPerPixel;
   nscoord result;
 
 #ifdef NS_COORD_IS_FLOAT
   // No need to bounds-check if converting float to float
   result = NSToCoordRound(product);
 #else
@@ -349,22 +349,22 @@ inline nscoord NSIntPixelsToAppUnits(PRI
 {
   // The cast to nscoord makes sure we don't overflow if we ever change
   // nscoord to float
   nscoord r = aPixels * (nscoord)aAppUnitsPerPixel;
   VERIFY_COORD(r);
   return r;
 }
 
-inline float NSAppUnitsToFloatPixels(nscoord aAppUnits, PRInt32 aAppUnitsPerPixel)
+inline float NSAppUnitsToFloatPixels(nscoord aAppUnits, float aAppUnitsPerPixel)
 {
   return (float(aAppUnits) / aAppUnitsPerPixel);
 }
 
-inline PRInt32 NSAppUnitsToIntPixels(nscoord aAppUnits, PRInt32 aAppUnitsPerPixel)
+inline PRInt32 NSAppUnitsToIntPixels(nscoord aAppUnits, float aAppUnitsPerPixel)
 {
   return NSToIntRound(float(aAppUnits) / aAppUnitsPerPixel);
 }
 
 /// handy constants
 #define TWIPS_PER_POINT_INT           20
 #define TWIPS_PER_POINT_FLOAT         20.0f
 #define POINTS_PER_INCH_INT           72
--- a/layout/base/nsCSSFrameConstructor.cpp
+++ b/layout/base/nsCSSFrameConstructor.cpp
@@ -1071,20 +1071,22 @@ class nsFrameConstructorSaveState {
 public:
   nsFrameConstructorSaveState();
   ~nsFrameConstructorSaveState();
 
 private:
   nsAbsoluteItems* mItems;                // pointer to struct whose data we save/restore
   PRBool*          mFirstLetterStyle;
   PRBool*          mFirstLineStyle;
+  PRBool*          mFixedPosIsAbsPos;
 
   nsAbsoluteItems  mSavedItems;           // copy of original data
   PRBool           mSavedFirstLetterStyle;
   PRBool           mSavedFirstLineStyle;
+  PRBool           mSavedFixedPosIsAbsPos;
 
   // The name of the child list in which our frames would belong
   nsIAtom* mChildListName;
   nsFrameConstructorState* mState;
 
   friend class nsFrameConstructorState;
 };
 
@@ -1104,16 +1106,24 @@ public:
 #endif
 
   // Containing block information for out-of-flow frames.
   nsAbsoluteItems           mFixedItems;
   nsAbsoluteItems           mAbsoluteItems;
   nsAbsoluteItems           mFloatedItems;
   PRBool                    mFirstLetterStyle;
   PRBool                    mFirstLineStyle;
+
+  // When working with the -moz-transform property, we want to hook
+  // the abs-pos and fixed-pos lists together, since transformed
+  // elements are fixed-pos containing blocks.  This flag determines
+  // whether or not we want to wire the fixed-pos and abs-pos lists
+  // together.
+  PRBool                    mFixedPosIsAbsPos;
+
   nsCOMPtr<nsILayoutHistoryState> mFrameState;
   nsPseudoFrames            mPseudoFrames;
   // These bits will be added to the state bits of any frame we construct
   // using this state.
   nsFrameState              mAdditionalStateBits; 
 
   // Constructor
   // Use the passed-in history state.
@@ -1184,16 +1194,31 @@ public:
                     nsStyleContext* aStyleContext,
                     nsIFrame* aParentFrame,
                     PRBool aCanBePositioned = PR_TRUE,
                     PRBool aCanBeFloated = PR_TRUE,
                     PRBool aIsOutOfFlowPopup = PR_FALSE,
                     PRBool aInsertAfter = PR_FALSE,
                     nsIFrame* aInsertAfterFrame = nsnull);
 
+  /**
+   * Function to return the fixed-pos element list.  Normally this will just hand back the
+   * fixed-pos element list, but in case we're dealing with a transformed element that's
+   * acting as an abs-pos and fixed-pos container, we'll hand back the abs-pos list.  Callers should
+   * use this function if they want to get the list acting as the fixed-pos item parent.
+   */
+  nsAbsoluteItems& GetFixedItems()
+  {
+    return mFixedPosIsAbsPos ? mAbsoluteItems : mFixedItems;
+  }
+  const nsAbsoluteItems& GetFixedItems() const
+  {
+    return mFixedPosIsAbsPos ? mAbsoluteItems : mFixedItems;
+  }
+
 protected:
   friend class nsFrameConstructorSaveState;
 
   /**
    * ProcessFrameInsertions takes the frames in aFrameItems and adds them as
    * kids to the aChildListName child list of |aFrameItems.containingBlock|.
    */
   void ProcessFrameInsertions(nsAbsoluteItems& aFrameItems,
@@ -1212,16 +1237,17 @@ nsFrameConstructorState::nsFrameConstruc
     mRootBox(nsIRootBox::GetRootBox(aPresShell)),
     mPopupItems(mRootBox ? mRootBox->GetPopupSetFrame() : nsnull),
 #endif
     mFixedItems(aFixedContainingBlock),
     mAbsoluteItems(aAbsoluteContainingBlock),
     mFloatedItems(aFloatContainingBlock),
     mFirstLetterStyle(PR_FALSE),
     mFirstLineStyle(PR_FALSE),
+    mFixedPosIsAbsPos(PR_FALSE),
     mFrameState(aHistoryState),
     mPseudoFrames(),
     mAdditionalStateBits(0)
 {
   MOZ_COUNT_CTOR(nsFrameConstructorState);
 }
 
 nsFrameConstructorState::nsFrameConstructorState(nsIPresShell* aPresShell,
@@ -1235,16 +1261,17 @@ nsFrameConstructorState::nsFrameConstruc
     mRootBox(nsIRootBox::GetRootBox(aPresShell)),
     mPopupItems(mRootBox ? mRootBox->GetPopupSetFrame() : nsnull),
 #endif
     mFixedItems(aFixedContainingBlock),
     mAbsoluteItems(aAbsoluteContainingBlock),
     mFloatedItems(aFloatContainingBlock),
     mFirstLetterStyle(PR_FALSE),
     mFirstLineStyle(PR_FALSE),
+    mFixedPosIsAbsPos(PR_FALSE),
     mPseudoFrames(),
     mAdditionalStateBits(0)
 {
   MOZ_COUNT_CTOR(nsFrameConstructorState);
   mFrameState = aPresShell->GetDocument()->GetLayoutHistoryState();
 }
 
 nsFrameConstructorState::~nsFrameConstructorState()
@@ -1281,18 +1308,29 @@ AdjustAbsoluteContainingBlock(nsIFrame* 
 void
 nsFrameConstructorState::PushAbsoluteContainingBlock(nsIFrame* aNewAbsoluteContainingBlock,
                                                      nsFrameConstructorSaveState& aSaveState)
 {
   aSaveState.mItems = &mAbsoluteItems;
   aSaveState.mSavedItems = mAbsoluteItems;
   aSaveState.mChildListName = nsGkAtoms::absoluteList;
   aSaveState.mState = this;
+
+  /* Store whether we're wiring the abs-pos and fixed-pos lists together. */
+  aSaveState.mFixedPosIsAbsPos = &mFixedPosIsAbsPos;
+  aSaveState.mSavedFixedPosIsAbsPos = mFixedPosIsAbsPos;
+
   mAbsoluteItems = 
     nsAbsoluteItems(AdjustAbsoluteContainingBlock(aNewAbsoluteContainingBlock));
+
+  /* See if we're wiring the fixed-pos and abs-pos lists together.  This happens iff
+   * we're a transformed element.
+   */
+  mFixedPosIsAbsPos = (aNewAbsoluteContainingBlock &&
+                       aNewAbsoluteContainingBlock->GetStyleDisplay()->HasTransform());
 }
 
 void
 nsFrameConstructorState::PushFloatContainingBlock(nsIFrame* aNewFloatContainingBlock,
                                                   nsFrameConstructorSaveState& aSaveState,
                                                   PRBool aFirstLetterStyle,
                                                   PRBool aFirstLineStyle)
 {
@@ -1345,18 +1383,18 @@ nsFrameConstructorState::GetGeometricPar
   }
 
   if (aStyleDisplay->mPosition == NS_STYLE_POSITION_ABSOLUTE &&
       mAbsoluteItems.containingBlock) {
     return mAbsoluteItems.containingBlock;
   }
 
   if (aStyleDisplay->mPosition == NS_STYLE_POSITION_FIXED &&
-      mFixedItems.containingBlock) {
-    return mFixedItems.containingBlock;
+      GetFixedItems().containingBlock) {
+    return GetFixedItems().containingBlock;
   }
 
   return aContentParentFrame;
 }
 
 nsresult
 nsFrameConstructorState::AddChild(nsIFrame* aNewFrame,
                                   nsFrameItems& aFrameItems,
@@ -1397,21 +1435,21 @@ nsFrameConstructorState::AddChild(nsIFra
     if (disp->mPosition == NS_STYLE_POSITION_ABSOLUTE &&
         mAbsoluteItems.containingBlock) {
       NS_ASSERTION(aNewFrame->GetParent() == mAbsoluteItems.containingBlock,
                    "Abs pos whose parent is not the abs pos containing block?");
       needPlaceholder = PR_TRUE;
       frameItems = &mAbsoluteItems;
     }
     if (disp->mPosition == NS_STYLE_POSITION_FIXED &&
-        mFixedItems.containingBlock) {
-      NS_ASSERTION(aNewFrame->GetParent() == mFixedItems.containingBlock,
+        GetFixedItems().containingBlock) {
+      NS_ASSERTION(aNewFrame->GetParent() == GetFixedItems().containingBlock,
                    "Fixed pos whose parent is not the fixed pos containing block?");
       needPlaceholder = PR_TRUE;
-      frameItems = &mFixedItems;
+      frameItems = &GetFixedItems();
     }
   }
 
   if (needPlaceholder) {
     NS_ASSERTION(frameItems != &aFrameItems,
                  "Putting frame in-flow _and_ want a placeholder?");
     nsIFrame* placeholderFrame;
     nsresult rv =
@@ -1543,19 +1581,21 @@ nsFrameConstructorState::ProcessFrameIns
   NS_ASSERTION(NS_SUCCEEDED(rv), "Frames getting lost!");
 }
 
 
 nsFrameConstructorSaveState::nsFrameConstructorSaveState()
   : mItems(nsnull),
     mFirstLetterStyle(nsnull),
     mFirstLineStyle(nsnull),
+    mFixedPosIsAbsPos(nsnull),
     mSavedItems(nsnull),
     mSavedFirstLetterStyle(PR_FALSE),
     mSavedFirstLineStyle(PR_FALSE),
+    mSavedFixedPosIsAbsPos(PR_FALSE),
     mChildListName(nsnull),
     mState(nsnull)
 {
 }
 
 nsFrameConstructorSaveState::~nsFrameConstructorSaveState()
 {
   // Restore the state
@@ -1570,16 +1610,19 @@ nsFrameConstructorSaveState::~nsFrameCon
 #endif
   }
   if (mFirstLetterStyle) {
     *mFirstLetterStyle = mSavedFirstLetterStyle;
   }
   if (mFirstLineStyle) {
     *mFirstLineStyle = mSavedFirstLineStyle;
   }
+  if (mFixedPosIsAbsPos) {
+    *mFixedPosIsAbsPos = mSavedFixedPosIsAbsPos;
+  }
 }
 
 static 
 PRBool IsBorderCollapse(nsIFrame* aFrame)
 {
   for (nsIFrame* frame = aFrame; frame; frame = frame->GetParent()) {
     if (nsGkAtoms::tableFrame == frame->GetType()) {
       return ((nsTableFrame*)frame)->IsBorderCollapse();
@@ -6465,25 +6508,27 @@ nsCSSFrameConstructor::ConstructFrameByD
     }
     // Create an area frame
     // pass a temporary stylecontext, the correct one will be set later
     newFrame = NS_NewFloatingItemWrapperFrame(mPresShell, aStyleContext);
 
     rv = ConstructBlock(aState, aDisplay, aContent, 
                         aState.GetGeometricParent(aDisplay, aParentFrame),
                         aParentFrame, aStyleContext, &newFrame, aFrameItems,
-                        aDisplay->mPosition == NS_STYLE_POSITION_RELATIVE);
+                        aDisplay->mPosition == NS_STYLE_POSITION_RELATIVE ||
+                        aDisplay->HasTransform());
     if (NS_FAILED(rv)) {
       return rv;
     }
 
     addedToFrameList = PR_TRUE;
   }
-  // See if it's relatively positioned
-  else if ((NS_STYLE_POSITION_RELATIVE == aDisplay->mPosition) &&
+  // See if it's relatively positioned or transformed
+  else if ((NS_STYLE_POSITION_RELATIVE == aDisplay->mPosition ||
+            aDisplay->HasTransform()) &&
            (aDisplay->IsBlockInside() ||
             (NS_STYLE_DISPLAY_INLINE == aDisplay->mDisplay))) {
     if (!aHasPseudoParent && !aState.mPseudoFrames.IsEmpty()) {
       ProcessPseudoFrames(aState, aFrameItems); 
     }
     // Is it block-level or inline-level?
     if (aDisplay->IsBlockInside()) {
       // Create a wrapper frame. Only need space manager if it's inline-block
--- a/layout/base/nsDisplayList.cpp
+++ b/layout/base/nsDisplayList.cpp
@@ -46,16 +46,18 @@
 
 #include "nsCSSRendering.h"
 #include "nsISelectionController.h"
 #include "nsIPresShell.h"
 #include "nsRegion.h"
 #include "nsFrameManager.h"
 #include "gfxContext.h"
 #include "nsStyleStructInlines.h"
+#include "nsStyleTransformMatrix.h"
+#include "gfxMatrix.h"
 #ifdef MOZ_SVG
 #include "nsSVGIntegrationUtils.h"
 #endif
 
 nsDisplayListBuilder::nsDisplayListBuilder(nsIFrame* aReferenceFrame,
     PRBool aIsForEvents, PRBool aBuildCaret)
     : mReferenceFrame(aReferenceFrame),
       mMovingFrame(nsnull),
@@ -365,17 +367,17 @@ nsIFrame* nsDisplayList::HitTest(nsDispl
       if (f) {
         if (!f->GetMouseThrough()) {
           aState->mItemBuffer.SetLength(itemBufferStart);
           return f;
         }
       }
     }
   }
-  NS_ASSERTION(aState->mItemBuffer.Length() == itemBufferStart,
+  NS_ASSERTION(aState->mItemBuffer.Length() == PRUint32(itemBufferStart),
                "How did we forget to pop some elements?");
   return nsnull;
 }
 
 static void Sort(nsDisplayList* aList, PRInt32 aCount, nsDisplayList::SortLEQ aCmp,
                  void* aClosure) {
   if (aCount < 2)
     return;
@@ -930,16 +932,405 @@ PRBool nsDisplayClip::TryMerge(nsDisplay
 }
 
 nsDisplayWrapList* nsDisplayClip::WrapWithClone(nsDisplayListBuilder* aBuilder,
                                                 nsDisplayItem* aItem) {
   return new (aBuilder)
     nsDisplayClip(aItem->GetUnderlyingFrame(), mClippingFrame, aItem, mClip);
 }
 
+
+
+///////////////////////////////////////////////////
+// nsDisplayTransform Implementation
+//
+
+// Write #define UNIFIED_CONTINUATIONS here to have the transform property try
+// to transform content with continuations as one unified block instead of
+// several smaller ones.  This is currently disabled because it doesn't work
+// correctly, since when the frames are initially being reflown, their
+// continuations all compute their bounding rects independently of each other
+// and consequently get the wrong value.  Write #define DEBUG_HIT here to have
+// the nsDisplayTransform class dump out a bunch of information about hit
+// detection.
+#undef  UNIFIED_CONTINUATIONS
+#undef  DEBUG_HIT
+
+/* Returns the bounds of a frame as defined for transforms.  If
+ * UNIFIED_CONTINUATIONS is not defined, this is simply the frame's bounding
+ * rectangle, translated to the origin. Otherwise, returns the smallest
+ * rectangle containing a frame and all of its continuations.  For example, if
+ * there is a <span> element with several continuations split over several
+ * lines, this function will return the rectangle containing all of those
+ * continuations.  This rectangle is relative to the origin of the frame's local
+ * coordinate space.
+ */
+#ifndef UNIFIED_CONTINUATIONS
+
+nsRect
+nsDisplayTransform::GetFrameBoundsForTransform(const nsIFrame* aFrame)
+{
+  NS_PRECONDITION(aFrame, "Can't get the bounds of a nonexistent frame!");
+  return nsRect(nsPoint(0, 0), aFrame->GetSize());
+}
+
+#else
+
+nsRect
+nsDisplayTransform::GetFrameBoundsForTransform(const nsIFrame* aFrame)
+{
+  NS_PRECONDITION(aFrame, "Can't get the bounds of a nonexistent frame!");
+
+  nsRect result;
+  
+  /* Iterate through the continuation list, unioning together all the
+   * bounding rects.
+   */
+  for (const nsIFrame *currFrame = aFrame->GetFirstContinuation();
+       currFrame != nsnull;
+       currFrame = currFrame->GetNextContinuation())
+    {
+      /* Get the frame rect in local coordinates, then translate back to the
+       * original coordinates.
+       */
+      result.UnionRect(result, nsRect(currFrame->GetOffsetTo(aFrame),
+                                      currFrame->GetSize()));
+    }
+
+  return result;
+}
+
+#endif
+
+/* Returns the delta specified by the -moz-tranform-origin property.
+ * This is a positive delta, meaning that it indicates the direction to move
+ * to get from (0, 0) of the frame to the transform origin.
+ */
+static
+gfxPoint GetDeltaToMozTransformOrigin(const nsIFrame* aFrame,
+                                      float aFactor,
+                                      const nsRect* aBoundsOverride)
+{
+  NS_PRECONDITION(aFrame, "Can't get delta for a null frame!");
+  NS_PRECONDITION(aFrame->GetStyleDisplay()->HasTransform(),
+                  "Can't get a delta for an untransformed frame!");
+
+  /* For both of the coordinates, if the value of -moz-transform is a
+   * percentage, it's relative to the size of the frame.  Otherwise, if it's
+   * a distance, it's already computed for us!
+   */
+  const nsStyleDisplay* display = aFrame->GetStyleDisplay();
+  nsRect boundingRect = (aBoundsOverride ? *aBoundsOverride :
+                         nsDisplayTransform::GetFrameBoundsForTransform(aFrame));
+
+  /* Allows us to access named variables by index. */
+  gfxPoint result;
+  gfxFloat* coords[2] = {&result.x, &result.y};
+  const nscoord* dimensions[2] =
+    {&boundingRect.width, &boundingRect.height};
+
+  for (PRUint8 index = 0; index < 2; ++index) {
+    /* If the -moz-transform-origin specifies a percentage, take the percentage
+     * of the size of the box.
+     */
+    if (display->mTransformOrigin[index].GetUnit() == eStyleUnit_Percent)
+      *coords[index] = NSAppUnitsToFloatPixels(*dimensions[index], aFactor) *
+        display->mTransformOrigin[index].GetPercentValue();
+    
+    /* Otherwise, it's a length. */
+    else
+      *coords[index] =
+        NSAppUnitsToFloatPixels(display->
+                                mTransformOrigin[index].GetCoordValue(),
+                                aFactor);
+  }
+  
+  /* Adjust based on the origin of the rectangle. */
+  result.x += NSAppUnitsToFloatPixels(boundingRect.x, aFactor);
+  result.y += NSAppUnitsToFloatPixels(boundingRect.y, aFactor);
+
+  return result;
+}
+
+/* Wraps up the -moz-transform matrix in a change-of-basis matrix pair that
+ * translates from local coordinate space to transform coordinate space, then
+ * hands it back.
+ */
+gfxMatrix
+nsDisplayTransform::GetResultingTransformMatrix(const nsIFrame* aFrame,
+                                                const nsPoint &aOrigin,
+                                                float aFactor,
+                                                const nsRect* aBoundsOverride)
+{
+  NS_PRECONDITION(aFrame, "Cannot get transform matrix for a null frame!");
+  NS_PRECONDITION(aFrame->GetStyleDisplay()->HasTransform(),
+                  "Cannot get transform matrix if frame isn't transformed!");
+
+  /* Account for the -moz-transform-origin property by translating the
+   * coordinate space to the new origin.
+   */
+  gfxPoint toMozOrigin = GetDeltaToMozTransformOrigin(aFrame, aFactor, aBoundsOverride);
+  gfxPoint newOrigin = gfxPoint(NSAppUnitsToFloatPixels(aOrigin.x, aFactor),
+                                NSAppUnitsToFloatPixels(aOrigin.y, aFactor));
+
+  /* Get the underlying transform matrix.  This requires us to get the
+   * 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. */
+  return nsLayoutUtils::ChangeMatrixBasis
+    (newOrigin + toMozOrigin, disp->mTransform.GetThebesMatrix(bounds, aFactor));
+}
+
+/* Painting applies the transform, paints the sublist, then unapplies
+ * the transform.
+ */
+void nsDisplayTransform::Paint(nsDisplayListBuilder *aBuilder,
+                               nsIRenderingContext *aCtx,
+                               const nsRect &aDirtyRect)
+{
+  /* Here's how this is going to work:
+   * 1. Convert the stored transform matrix into a gfxMatrix
+   * 2. Read out the old graphics matrix.
+   * 3. Compute the net graphics matrix at this point.
+   * 4. Set that as the active matrix.
+   * 5. Apply the inverse transform to the dirty rect so that children think
+   *    they're drawing in local space.
+   * 6. Render everything.
+   * 7. Reset the matrix.
+   */
+  /* Get the context and automatically save and restore it. */
+  gfxContext* gfx = aCtx->ThebesContext();
+  gfxContextAutoSaveRestore autoRestorer(gfx);
+
+  /* Unit conversion is based on the local presentation context. */
+  float factor = mFrame->PresContext()->AppUnitsPerDevPixel();
+
+  /* Compute the new matrix by taking the old matrix and multiplying the
+   * transform matrix of this frame only.  The new transform is prepended to
+   * the old transform, since that way, if we have several stacked transforms,
+   * the innermost transform is applied first.
+   */
+  gfxMatrix newTransformMatrix =
+    GetResultingTransformMatrix(mFrame, aBuilder->ToReferenceFrame(mFrame),
+                                factor, nsnull);
+
+  newTransformMatrix.Multiply(gfx->CurrentMatrix());
+
+  /* Set the matrix for the transform based on the old matrix and the new
+   * transform data.
+   */
+  gfx->SetMatrix(newTransformMatrix);
+
+  /* Now, send the paint call down.  As we do this, we need to be sure to
+   * untransform the dirty rect, since we want everything that's painting to
+   * think that it's painting in its original rectangular coordinate space.
+   */    
+  mStoredList.Paint(aBuilder, aCtx,
+                    UntransformRect(aDirtyRect, mFrame,
+                                    aBuilder->ToReferenceFrame(mFrame)));
+
+  /* The AutoSaveRestore object will clean things up. */
+}
+
+/* We don't need to do anything here. */
+PRBool nsDisplayTransform::OptimizeVisibility(nsDisplayListBuilder *aBuilder,
+                                              nsRegion *aVisibleRegion)
+{
+  return PR_TRUE;
+}
+
+#ifdef DEBUG_HIT
+#include <time.h>
+#endif
+
+/* HitTest does some fun stuff with matrix transforms to obtain the answer. */
+nsIFrame *nsDisplayTransform::HitTest(nsDisplayListBuilder *aBuilder,
+                                      nsPoint aPt,
+                                      HitTestState *aState)
+{
+  /* Here's how this works:
+   * 1. Get the matrix.  If it's singular, abort (clearly we didn't hit
+   *    anything).
+   * 2. Invert the matrix.
+   * 3. Use it to transform the point into the correct space.
+   * 4. Pass that point down through to the list's version of HitTest.
+   */
+  float factor = nsPresContext::AppUnitsPerCSSPixel();
+  gfxMatrix matrix =
+    GetResultingTransformMatrix(mFrame, aBuilder->ToReferenceFrame(mFrame),
+                                factor, nsnull);
+  if (matrix.IsSingular())
+    return nsnull;
+
+  /* We want to go from transformed-space to regular space.
+   * Thus we have to invert the matrix, which normally does
+   * the reverse operation (e.g. regular->transformed)
+   */
+  matrix.Invert();
+
+  /* Now, apply the transform and pass it down the channel. */
+  gfxPoint result = matrix.Transform(gfxPoint(NSAppUnitsToFloatPixels(aPt.x, factor),
+                                              NSAppUnitsToFloatPixels(aPt.y, factor)));
+
+#ifdef DEBUG_HIT
+  printf("Frame: %p\n", dynamic_cast<void *>(mFrame));
+  printf("  Untransformed point: (%f, %f)\n", result.x, result.y);
+#endif
+
+  nsIFrame* resultFrame =
+    mStoredList.HitTest(aBuilder,
+                        nsPoint(NSFloatPixelsToAppUnits(float(result.x), factor),
+                                NSFloatPixelsToAppUnits(float(result.y), factor)), aState);
+  
+#ifdef DEBUG_HIT
+  if (resultFrame)
+    printf("  Hit!  Time: %f, frame: %p\n", static_cast<double>(clock()),
+           dynamic_cast<void *>(resultFrame));
+  printf("=== end of hit test ===\n");
+#endif
+
+  return resultFrame;
+}
+
+/* The bounding rectangle for the object is the overflow rectangle translated
+ * by the reference point.
+ */
+nsRect nsDisplayTransform::GetBounds(nsDisplayListBuilder *aBuilder)
+{
+  return mFrame->GetOverflowRect() + aBuilder->ToReferenceFrame(mFrame);
+}
+
+/* The transform is opaque iff the transform consists solely of scales and
+ * transforms and if the underlying content is opaque.  Thus if the transform
+ * is of the form
+ *
+ * |a c e|
+ * |b d f|
+ * |0 0 1|
+ *
+ * We need b and c to be zero.
+ */
+PRBool nsDisplayTransform::IsOpaque(nsDisplayListBuilder *aBuilder)
+{
+  const nsStyleDisplay* disp = mFrame->GetStyleDisplay();
+  return disp->mTransform.GetMainMatrixEntry(1) == 0.0f &&
+    disp->mTransform.GetMainMatrixEntry(2) == 0.0f &&
+    mStoredList.IsOpaque(aBuilder);
+}
+
+/* The transform is uniform if it fills the entire bounding rect and the
+ * wrapped list is uniform.  See IsOpaque for discussion of why this
+ * works.
+ */
+PRBool nsDisplayTransform::IsUniform(nsDisplayListBuilder *aBuilder)
+{
+  const nsStyleDisplay* disp = mFrame->GetStyleDisplay();
+  return disp->mTransform.GetMainMatrixEntry(1) == 0.0f &&
+    disp->mTransform.GetMainMatrixEntry(2) == 0.0f &&
+    mStoredList.IsUniform(aBuilder);
+}
+
+/* If UNIFIED_CONTINUATIONS is defined, we can merge two display lists that
+ * share the same underlying content.  Otherwise, doing so results in graphical
+ * glitches.
+ */
+#ifndef UNIFIED_CONTINUATIONS
+
+PRBool
+nsDisplayTransform::TryMerge(nsDisplayListBuilder *aBuilder,
+                             nsDisplayItem *aItem)
+{
+  return PR_FALSE;
+}
+
+#else
+
+PRBool
+nsDisplayTransform::TryMerge(nsDisplayListBuilder *aBuilder,
+                             nsDisplayItem *aItem)
+{
+  NS_PRECONDITION(aItem, "Why did you try merging with a null item?");
+  NS_PRECONDITION(aBuilder, "Why did you try merging with a null builder?");
+
+  /* Make sure that we're dealing with two transforms. */
+  if (aItem->GetType() != TYPE_TRANSFORM)
+    return PR_FALSE;
+
+  /* Check to see that both frames are part of the same content. */
+  if (aItem->GetUnderlyingFrame()->GetContent() != mFrame->GetContent())
+    return PR_FALSE;
+
+  /* Now, move everything over to this frame and signal that
+   * we merged things!
+   */
+  mStoredList.GetList()->
+    AppendToBottom(&static_cast<nsDisplayTransform *>(aItem)->mStoredList);
+  return PR_TRUE;
+}
+
+#endif
+
+/* TransformRect takes in as parameters a rectangle (in app space) and returns
+ * the smallest rectangle (in app space) containing the transformed image of
+ * that rectangle.  That is, it takes the four corners of the rectangle,
+ * transforms them according to the matrix associated with the specified frame,
+ * then returns the smallest rectangle containing the four transformed points.
+ *
+ * @param aUntransformedBounds The rectangle (in app units) to transform.
+ * @param aFrame The frame whose transformation should be applied.
+ * @param aOrigin The delta from the frame origin to the coordinate space origin
+ * @param aBoundsOverride (optional) Force the frame bounds to be the
+ *        specified bounds.
+ * @return The smallest rectangle containing the image of the transformed
+ *         rectangle.
+ */
+nsRect nsDisplayTransform::TransformRect(const nsRect &aUntransformedBounds,
+                                         const nsIFrame* aFrame,
+                                         const nsPoint &aOrigin,
+                                         const nsRect* aBoundsOverride)
+{
+  NS_PRECONDITION(aFrame, "Can't take the transform based on a null frame!");
+  NS_PRECONDITION(aFrame->GetStyleDisplay()->HasTransform(),
+                  "Cannot transform a rectangle if there's no transformation!");
+
+  float factor = nsPresContext::AppUnitsPerCSSPixel();
+  return nsLayoutUtils::MatrixTransformRect
+    (aUntransformedBounds,
+     GetResultingTransformMatrix(aFrame, aOrigin, factor, aBoundsOverride),
+     factor);
+}
+
+nsRect nsDisplayTransform::UntransformRect(const nsRect &aUntransformedBounds,
+                                           const nsIFrame* aFrame,
+                                           const nsPoint &aOrigin)
+{
+  NS_PRECONDITION(aFrame, "Can't take the transform based on a null frame!");
+  NS_PRECONDITION(aFrame->GetStyleDisplay()->HasTransform(),
+                  "Cannot transform a rectangle if there's no transformation!");
+
+
+  /* Grab the matrix.  If the transform is degenerate, just hand back the
+   * empty rect.
+   */
+  float factor = nsPresContext::AppUnitsPerCSSPixel();
+  gfxMatrix matrix = GetResultingTransformMatrix(aFrame, aOrigin, factor, nsnull);
+  if (matrix.IsSingular())
+    return nsRect();
+
+  /* We want to untransform the matrix, so invert the transformation first! */
+  matrix.Invert();
+
+  return nsLayoutUtils::MatrixTransformRect(aUntransformedBounds, matrix,
+                                            factor);
+}
+
 #ifdef MOZ_SVG
 nsDisplaySVGEffects::nsDisplaySVGEffects(nsIFrame* aFrame, nsDisplayList* aList)
     : nsDisplayWrapList(aFrame, aList), mEffectsFrame(aFrame),
       mBounds(aFrame->GetOverflowRect())
 {
   MOZ_COUNT_CTOR(nsDisplaySVGEffects);
 }
 
--- a/layout/base/nsDisplayList.h
+++ b/layout/base/nsDisplayList.h
@@ -48,17 +48,16 @@
 #include "nsCOMPtr.h"
 #include "nsIFrame.h"
 #include "nsPoint.h"
 #include "nsRect.h"
 #include "nsISelection.h"
 #include "nsCaret.h"
 #include "plarena.h"
 #include "nsLayoutUtils.h"
-#include "nsTArray.h"
 
 #include <stdlib.h>
 
 class nsIPresShell;
 class nsIContent;
 class nsRegion;
 class nsIRenderingContext;
 class nsIDeviceContext;
@@ -202,17 +201,17 @@ public:
    */
   nsIFrame* ReferenceFrame() { return mReferenceFrame; }
   /**
    * @return a point pt such that adding pt to a coordinate relative to aFrame
    * makes it relative to ReferenceFrame(), i.e., returns 
    * aFrame->GetOffsetTo(ReferenceFrame()). It may be optimized to be faster
    * than aFrame->GetOffsetTo(ReferenceFrame()) (but currently isn't).
    */
-  nsPoint ToReferenceFrame(nsIFrame* aFrame) {
+  nsPoint ToReferenceFrame(const nsIFrame* aFrame) {
     return aFrame->GetOffsetTo(ReferenceFrame());
   }
   /**
    * When building the display list, the scrollframe aFrame will be "ignored"
    * for the purposes of clipping, and its scrollbars will be hidden. We use
    * this to allow RenderOffscreen to render a whole document without beign
    * clipped by the viewport or drawing the viewport scrollbars.
    */
@@ -390,17 +389,18 @@ public:
   enum Type {
     TYPE_GENERIC,
     TYPE_OUTLINE,
     TYPE_CLIP,
     TYPE_OPACITY,
 #ifdef MOZ_SVG
     TYPE_SVG_EFFECTS,
 #endif
-    TYPE_WRAPLIST
+    TYPE_WRAPLIST,
+    TYPE_TRANSFORM
   };
 
   struct HitTestState {
     ~HitTestState() {
       NS_ASSERTION(mItemBuffer.Length() == 0,
                    "mItemBuffer should have been cleared");
     }
     nsAutoTArray<nsDisplayItem*, 100> mItemBuffer;
@@ -1289,9 +1289,120 @@ public:
 
 private:
   nsIFrame* mEffectsFrame;
   // relative to mEffectsFrame
   nsRect    mBounds;
 };
 #endif
 
+/* A display item that applies a transformation to all of its descendent
+ * elements.  This wrapper should only be used if there is a transform applied
+ * to the root element.
+ * INVARIANT: The wrapped frame is transformed.
+ * INVARIANT: The wrapped frame is non-null.
+ */ 
+class nsDisplayTransform: public nsDisplayItem
+{
+public:
+  /* Constructor accepts a display list, empties it, and wraps it up.  It also
+   * ferries the underlying frame to the nsDisplayItem constructor.
+   */
+  nsDisplayTransform(nsIFrame *aFrame, nsDisplayList *aList) :
+    nsDisplayItem(aFrame), mStoredList(aFrame, aList)
+  {
+    MOZ_COUNT_CTOR(nsDisplayTransform);
+  }
+
+#ifdef NS_BUILD_REFCNT_LOGGING
+  virtual ~nsDisplayTransform()
+  {
+    MOZ_COUNT_DTOR(nsDisplayTransform);
+  }
+#endif
+
+  NS_DISPLAY_DECL_NAME("nsDisplayTransform");
+
+  virtual Type GetType() 
+  {
+    return TYPE_TRANSFORM;
+  }
+
+  virtual nsIFrame* HitTest(nsDisplayListBuilder *aBuilder, nsPoint aPt,
+                            HitTestState *aState);
+  virtual nsRect GetBounds(nsDisplayListBuilder *aBuilder);
+  virtual PRBool IsOpaque(nsDisplayListBuilder *aBuilder);
+  virtual PRBool IsUniform(nsDisplayListBuilder *aBuilder);
+  virtual void   Paint(nsDisplayListBuilder *aBuilder,
+                       nsIRenderingContext *aCtx,
+                       const nsRect &aDirtyRect);
+  virtual PRBool OptimizeVisibility(nsDisplayListBuilder *aBuilder,
+                                    nsRegion *aVisibleRegion);
+  virtual PRBool TryMerge(nsDisplayListBuilder *aBuilder, nsDisplayItem *aItem);
+
+  /**
+   * TransformRect takes in as parameters a rectangle (in aFrame's coordinate
+   * space) and returns the smallest rectangle (in aFrame's coordinate space)
+   * containing the transformed image of that rectangle.  That is, it takes
+   * the four corners of the rectangle, transforms them according to the
+   * matrix associated with the specified frame, then returns the smallest
+   * rectangle containing the four transformed points.
+   *
+   * @param untransformedBounds The rectangle (in app units) to transform.
+   * @param aFrame The frame whose transformation should be applied.  This
+   *        function raises an assertion if aFrame is null or doesn't have a
+   *        transform applied to it.
+   * @param aOrigin The origin of the transform relative to aFrame's local
+   *        coordinate space.
+   * @param aBoundsOverride (optional) Rather than using the frame's computed
+   *        bounding rect as frame bounds, use this rectangle instead.  Pass
+   *        nsnull (or nothing at all) to use the default.
+   */
+  static nsRect TransformRect(const nsRect &aUntransformedBounds, 
+                              const nsIFrame* aFrame,
+                              const nsPoint &aOrigin,
+                              const nsRect* aBoundsOverride = nsnull);
+
+  /* UntransformRect is like TransformRect, except that it inverts the
+   * transform.
+   */
+  static nsRect UntransformRect(const nsRect &aUntransformedBounds, 
+                                const nsIFrame* aFrame,
+                                const nsPoint &aOrigin);
+
+  /**
+   * Returns the bounds of a frame as defined for transforms.  If
+   * UNIFIED_CONTINUATIONS is not defined, this is simply the frame's bounding
+   * rectangle, translated to the origin.  Otherwise, returns the smallest
+   * rectangle containing a frame and all of its continuations.  For example,
+   * if there is a <span> element with several continuations split over
+   * several lines, this function will return the rectangle containing all of
+   * those continuations.  This rectangle is relative to the origin of the
+   * frame's local coordinate space.
+   *
+   * @param aFrame The frame to get the bounding rect for.
+   * @return The frame's bounding rect, as described above.
+   */
+  static nsRect GetFrameBoundsForTransform(const nsIFrame* aFrame);
+
+  /**
+   * Given a frame with the -moz-transform property, returns the
+   * transformation matrix for that frame.
+   *
+   * @param aFrame The frame to get the matrix from.
+   * @param aOrigin Relative to which point this transform should be applied.
+   * @param aScaleFactor The number of app units per graphics unit.
+   * @param aBoundsOverride [optional] If this is nsnull (the default), the
+   *        computation will use the value of GetFrameBoundsForTransform(aFrame)
+   *        for the frame's bounding rectangle. Otherwise, it will use the
+   *        value of aBoundsOverride.  This is mostly for internal use and in
+   *        most cases you will not need to specify a value.
+   */
+  static gfxMatrix GetResultingTransformMatrix(const nsIFrame* aFrame,
+                                               const nsPoint& aOrigin,
+                                               float aFactor,
+                                               const nsRect* aBoundsOverride = nsnull);
+
+private:
+  nsDisplayWrapList mStoredList;
+};
+
 #endif /*NSDISPLAYLIST_H_*/
--- a/layout/base/nsLayoutUtils.cpp
+++ b/layout/base/nsLayoutUtils.cpp
@@ -71,16 +71,18 @@
 #include "nsCSSRendering.h"
 #include "nsContentUtils.h"
 #include "nsThemeConstants.h"
 #include "nsPIDOMWindow.h"
 #include "nsIBaseWindow.h"
 #include "nsIDocShell.h"
 #include "nsIDocShellTreeItem.h"
 #include "nsIWidget.h"
+#include "gfxMatrix.h"
+#include "gfxTypes.h"
 
 #ifdef MOZ_SVG
 #include "nsSVGUtils.h"
 #include "nsSVGIntegrationUtils.h"
 #include "nsSVGForeignObjectFrame.h"
 #include "nsSVGOuterSVGFrame.h"
 #endif
 
@@ -634,46 +636,169 @@ nsLayoutUtils::GetEventCoordinatesRelati
                   aEvent->eventStructType != NS_MOUSE_SCROLL_EVENT &&
                   aEvent->eventStructType != NS_DRAG_EVENT))
     return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
 
   const nsGUIEvent* GUIEvent = static_cast<const nsGUIEvent*>(aEvent);
   if (!GUIEvent->widget)
     return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
 
-  // If it is, or is a descendant of, an SVG foreignobject frame,
-  // then we need to do extra work
+  /* If we walk up the frame tree and discover that any of the frames are
+   * transformed, we need to do extra work to convert from the global
+   * space to the local space.
+   */
   nsIFrame* rootFrame = aFrame;
+  PRBool transformFound = PR_FALSE;
+
   for (nsIFrame* f = aFrame; f; f = GetCrossDocParentFrame(f)) {
-#ifdef MOZ_SVG
-    if (f->IsFrameOfType(nsIFrame::eSVGForeignObject) && f->GetFirstChild(nsnull)) {
-      nsSVGForeignObjectFrame* fo = static_cast<nsSVGForeignObjectFrame*>(f);
-      nsIFrame* outer = nsSVGUtils::GetOuterSVGFrame(fo);
-      return fo->TransformPointFromOuter(
-          GetEventCoordinatesRelativeTo(aEvent, outer)) -
-        aFrame->GetOffsetTo(fo->GetFirstChild(nsnull));
-    }
-#endif
+    if (f->IsTransformed())
+      transformFound = PR_TRUE;
+
     rootFrame = f;
   }
 
   nsIView* rootView = rootFrame->GetView();
   if (!rootView)
     return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
 
   nsPoint widgetToView = TranslateWidgetToView(rootFrame->PresContext(),
                                GUIEvent->widget, GUIEvent->refPoint,
                                rootView);
 
   if (widgetToView == nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE))
     return nsPoint(NS_UNCONSTRAINEDSIZE, NS_UNCONSTRAINEDSIZE);
 
+  /* If we encountered a transform, we can't do simple arithmetic to figure
+   * out how to convert back to aFrame's coordinates and must use the CTM.
+   */
+  if (transformFound)
+    return InvertTransformsToRoot(aFrame, widgetToView);
+  
+  /* Otherwise, all coordinate systems are translations of one another,
+   * so we can just subtract out the different.
+   */
   return widgetToView - aFrame->GetOffsetTo(rootFrame);
 }
 
+gfxMatrix
+nsLayoutUtils::ChangeMatrixBasis(const gfxPoint &aOrigin,
+                                 const gfxMatrix &aMatrix)
+{
+  /* These are translation matrices from world-to-origin of relative frame and
+   * vice-versa.  Although I could use the gfxMatrix::Translate function to
+   * accomplish this, I'm hoping to reduce the overall number of matrix
+   * operations by hardcoding as many of the matrices as possible.
+   */
+  gfxMatrix worldToOrigin(1.0, 0.0, 0.0, 1.0, -aOrigin.x, -aOrigin.y);
+  gfxMatrix originToWorld(1.0, 0.0, 0.0, 1.0,  aOrigin.x,  aOrigin.y);
+
+  /* Multiply all three to get the transform! */
+  gfxMatrix result(worldToOrigin);
+  result.Multiply(aMatrix);
+  result.Multiply(originToWorld);
+  return result;
+}
+
+/**
+ * Given a gfxFloat, constrains its value to be between nscoord_MIN and nscoord_MAX.
+ *
+ * @param aVal The value to constrain (in/out)
+ */
+static void ConstrainToCoordValues(gfxFloat &aVal)
+{
+  if (aVal <= nscoord_MIN)
+    aVal = nscoord_MIN;
+  else if (aVal >= nscoord_MAX)
+    aVal = nscoord_MAX;
+}
+
+nsRect
+nsLayoutUtils::RoundGfxRectToAppRect(const gfxRect &aRect, float aFactor)
+{ 
+  /* Get a new gfxRect whose units are app units by scaling by the specified factor. */ 
+  gfxRect scaledRect(aRect.pos.x * aFactor, aRect.pos.y * aFactor,
+                     aRect.size.width * aFactor,
+                     aRect.size.height * aFactor);
+  
+  /* Round outward. */
+  scaledRect.RoundOut();
+
+  /* We now need to constrain our results to the max and min values for coords. */
+  ConstrainToCoordValues(scaledRect.pos.x);
+  ConstrainToCoordValues(scaledRect.pos.y);
+  ConstrainToCoordValues(scaledRect.size.width);
+  ConstrainToCoordValues(scaledRect.size.height);
+  
+  /* Now typecast everything back.  This is guaranteed to be safe. */
+  return nsRect(nscoord(scaledRect.pos.x), nscoord(scaledRect.pos.y),
+                nscoord(scaledRect.size.width), nscoord(scaledRect.size.height));
+}
+
+nsRect
+nsLayoutUtils::MatrixTransformRect(const nsRect &aBounds,
+                                   const gfxMatrix &aMatrix, float aFactor)
+{
+  gfxRect image = aMatrix.TransformBounds(gfxRect(NSAppUnitsToFloatPixels(aBounds.x, aFactor),
+                                                  NSAppUnitsToFloatPixels(aBounds.y, aFactor),
+                                                  NSAppUnitsToFloatPixels(aBounds.width, aFactor),
+                                                  NSAppUnitsToFloatPixels(aBounds.height, aFactor)));
+  
+  return RoundGfxRectToAppRect(image, aFactor);
+}
+
+nsPoint
+nsLayoutUtils::MatrixTransformPoint(const nsPoint &aPoint,
+                                    const gfxMatrix &aMatrix, float aFactor)
+{
+  gfxPoint image = aMatrix.Transform(gfxPoint(NSAppUnitsToFloatPixels(aPoint.x, aFactor),
+                                              NSAppUnitsToFloatPixels(aPoint.y, aFactor)));
+  return nsPoint(NSFloatPixelsToAppUnits(float(image.x), aFactor),
+                 NSFloatPixelsToAppUnits(float(image.y), aFactor));
+}
+
+/**
+ * Returns the CTM at the specified frame.
+ *
+ * @param aFrame The frame at which we should calculate the CTM.
+ * @return The CTM at the specified frame.
+ */
+static gfxMatrix GetCTMAt(nsIFrame *aFrame)
+{
+  gfxMatrix ctm;
+
+  /* Starting at the specified frame, we'll use the GetTransformMatrix
+   * function of the frame, which gives us a matrix from this frame up
+   * to some other ancestor frame.  Once this function returns null,
+   * we've hit the top of the frame tree and can stop.  We get the CTM
+   * by simply accumulating all of these matrices together.
+   */
+  while (aFrame)
+    ctm *= aFrame->GetTransformMatrix(&aFrame);
+  return ctm;
+}
+
+nsPoint
+nsLayoutUtils::InvertTransformsToRoot(nsIFrame *aFrame,
+                                      const nsPoint &aPoint)
+{
+  NS_PRECONDITION(aFrame, "Why are you inverting transforms when there is no frame?");
+
+  /* To invert everything to the root, we'll get the CTM, invert it, and use it to transform
+   * the point.
+   */
+  gfxMatrix ctm = GetCTMAt(aFrame);
+
+  /* If the ctm is singular, hand back (0, 0) as a sentinel. */
+  if (ctm.IsSingular())
+    return nsPoint(0, 0);
+
+  /* Otherwise, invert the CTM and use it to transform the point. */
+  return MatrixTransformPoint(aPoint, ctm.Invert(), aFrame->PresContext()->AppUnitsPerDevPixel());
+}
+
 nsPoint
 nsLayoutUtils::GetEventCoordinatesForNearestView(nsEvent* aEvent,
                                                  nsIFrame* aFrame,
                                                  nsIView** aView)
 {
   if (!aEvent || (aEvent->eventStructType != NS_MOUSE_EVENT && 
                   aEvent->eventStructType != NS_MOUSE_SCROLL_EVENT &&
                   aEvent->eventStructType != NS_DRAG_EVENT))
--- a/layout/base/nsLayoutUtils.h
+++ b/layout/base/nsLayoutUtils.h
@@ -382,27 +382,89 @@ public:
    * @param aView  view to which returned coordinates are relative
    * @return the point in the view's coordinates
    */
   static nsPoint TranslateWidgetToView(nsPresContext* aPresContext, 
                                        nsIWidget* aWidget, nsIntPoint aPt,
                                        nsIView* aView);
 
   /**
+   * Given a matrix and a point, let T be the transformation matrix translating points
+   * in the coordinate space with origin aOrigin to the coordinate space used by the
+   * matrix.  If M is the stored matrix, this function returns (T-1)MT, the matrix
+   * that's equivalent to aMatrix but in the coordinate space that treats aOrigin
+   * as the origin.
+   *
+   * @param aOrigin The origin to translate to.
+   * @param aMatrix The matrix to change the basis of.
+   * @return A matrix equivalent to aMatrix, but operating in the coordinate system with
+   *         origin aOrigin.
+   */
+  static gfxMatrix ChangeMatrixBasis(const gfxPoint &aOrigin, const gfxMatrix &aMatrix);
+
+  /**
    * Given aFrame, the root frame of a stacking context, find its descendant
    * frame under the point aPt that receives a mouse event at that location,
    * or nsnull if there is no such frame.
    * @param aPt the point, relative to the frame origin
    * @param aShouldIgnoreSuppression a boolean to control if the display
    * list builder should ignore paint suppression or not
    */
   static nsIFrame* GetFrameForPoint(nsIFrame* aFrame, nsPoint aPt,
                                     PRBool aShouldIgnoreSuppression = PR_FALSE);
 
   /**
+   * Given a point in the global coordinate space, returns that point expressed
+   * in the coordinate system of aFrame.  This effectively inverts all transforms
+   * between this point and the root frame.
+   *
+   * @param aFrame The frame that acts as the coordinate space container.
+   * @param aPoint The point, in the global space, to get in the frame-local space.
+   * @return aPoint, expressed in aFrame's canonical coordinate space.
+   */
+  static nsPoint InvertTransformsToRoot(nsIFrame* aFrame,
+                                        const nsPoint &aPt);
+
+
+  /**
+   * Helper function that, given a rectangle and a matrix, returns the smallest
+   * rectangle containing the image of the source rectangle.
+   *
+   * @param aBounds The rectangle to transform.
+   * @param aMatrix The matrix to transform it with.
+   * @param aFactor The number of app units per graphics unit.
+   * @return The smallest rect that contains the image of aBounds.
+   */
+  static nsRect MatrixTransformRect(const nsRect &aBounds,
+                                    const gfxMatrix &aMatrix, float aFactor);
+
+  /**
+   * Helper function that, given a point and a matrix, returns the image
+   * of that point under the matrix transform.
+   *
+   * @param aPoint The point to transform.
+   * @param aMatrix The matrix to transform it with.
+   * @param aFactor The number of app units per graphics unit.
+   * @return The image of the point under the transform.
+   */
+  static nsPoint MatrixTransformPoint(const nsPoint &aPoint,
+                                      const gfxMatrix &aMatrix, float aFactor);
+
+  /**
+   * Given a graphics rectangle in graphics space, return a rectangle in
+   * app space that contains the graphics rectangle, rounding out as necessary.
+   *
+   * @param aRect The graphics rect to round outward.
+   * @param aFactor The number of app units per graphics unit.
+   * @return The smallest rectangle in app space that contains aRect.
+   */
+  static nsRect RoundGfxRectToAppRect(const gfxRect &aRect, float aFactor);
+
+
+  /**
    * Given aFrame, the root frame of a stacking context, paint it and its
    * descendants to aRenderingContext. 
    * @param aRenderingContext a rendering context translated so that (0,0)
    * is the origin of aFrame; for best results, (0,0) should transform
    * to pixel-aligned coordinates
    * @param aDirtyRegion the region that must be painted, in the coordinates
    * of aFrame
    * @param aBackground paint the dirty area with this color before drawing
--- a/layout/base/nsPresShell.cpp
+++ b/layout/base/nsPresShell.cpp
@@ -908,16 +908,17 @@ public:
                          nsGUIEvent*     aEvent,
                          nsEventStatus*  aEventStatus);
   NS_IMETHOD HandleDOMEventWithTarget(nsIContent* aTargetContent,
                                       nsEvent* aEvent,
                                       nsEventStatus* aStatus);
   NS_IMETHOD ResizeReflow(nsIView *aView, nscoord aWidth, nscoord aHeight);
   NS_IMETHOD_(PRBool) IsVisible();
   NS_IMETHOD_(void) WillPaint();
+  NS_IMETHOD_(void) InvalidateFrameForView(nsIView *view);
 
   // caret handling
   NS_IMETHOD GetCaret(nsCaret **aOutCaret);
   NS_IMETHOD_(void) MaybeInvalidateCaretPosition();
   NS_IMETHOD SetCaretEnabled(PRBool aInEnable);
   NS_IMETHOD SetCaretReadOnly(PRBool aReadOnly);
   NS_IMETHOD GetCaretEnabled(PRBool *aOutEnabled);
   NS_IMETHOD SetCaretVisibilityDuringSelection(PRBool aVisibility);
@@ -1006,17 +1007,16 @@ public:
 protected:
   virtual ~PresShell();
 
   void HandlePostedReflowCallbacks();
   void CancelPostedReflowCallbacks();
 
   void UnsuppressAndInvalidate();
 
-
   void WillCauseReflow() {
     nsContentUtils::AddScriptBlocker();
     ++mChangeNestCount;
   }
   nsresult DidCauseReflow();
   friend class nsAutoCauseReflowNotifier;
 
   void     WillDoReflow();
@@ -2605,17 +2605,17 @@ PresShell::CreateResizeEventTimer ()
     mResizeEventTimer->InitWithFuncCallback(sResizeEventCallback, this, RESIZE_EVENT_DELAY, 
                                             nsITimer::TYPE_ONE_SHOT);
   }
 }
 
 void
 PresShell::KillResizeEventTimer()
 {
-  if(mResizeEventTimer) {
+  if (mResizeEventTimer) {
     mResizeEventTimer->Cancel();
     mResizeEventTimer = nsnull;
   }
 }
 
 void
 PresShell::sResizeEventCallback(nsITimer *aTimer, void* aPresShell)
 {
@@ -4008,22 +4008,22 @@ PresShell::ScrollContentIntoView(nsICont
   // if we're resetting focus because a window just got an activate
   // event. If we are, we do not want to scroll the frame into view.
   // Example: The user clicks on an anchor, and then deactivates the 
   // window. When they reactivate the window, the expected behavior
   // is not for the anchor link to scroll back into view. That is what
   // this check is preventing.
   // XXX: The dependency on the command dispatcher needs to be fixed.
   nsPIDOMWindow* ourWindow = currentDoc->GetWindow();
-  if(ourWindow) {
+  if (ourWindow) {
     nsIFocusController *focusController = ourWindow->GetRootFocusController();
     if (focusController) {
       PRBool dontScroll = PR_FALSE;
       focusController->GetSuppressFocusScroll(&dontScroll);
-      if(dontScroll) {
+      if (dontScroll) {
         return NS_OK;
       }
     }
   }
 
   // This is a two-step process.
   // Step 1: Find the bounds of the rect we want to scroll into view.  For
   //         example, for an inline frame we may want to scroll in the whole
@@ -4186,16 +4186,24 @@ PresShell::GetSelectionForCopy(nsISelect
     rv = NS_OK;
   }
 
   *outSelection = sel;
   NS_IF_ADDREF(*outSelection);
   return rv;
 }
 
+/* Just hook this call into InvalidateOverflowRect */
+void
+PresShell::InvalidateFrameForView(nsIView *aView)
+{
+  nsIFrame* frame = nsLayoutUtils::GetFrameFor(aView);
+  if (frame)
+    frame->InvalidateOverflowRect();
+}
 
 NS_IMETHODIMP
 PresShell::DoGetContents(const nsACString& aMimeType, PRUint32 aFlags, PRBool aSelectionOnly, nsAString& aOutValue)
 {
   aOutValue.Truncate();
   
   if (!mDocument) return NS_ERROR_FAILURE;
 
--- a/layout/generic/nsContainerFrame.cpp
+++ b/layout/generic/nsContainerFrame.cpp
@@ -555,16 +555,22 @@ nsContainerFrame::SyncFrameViewPropertie
   NS_ASSERTION(!aStyleContext || aFrame->GetStyleContext() == aStyleContext,
                "Wrong style context for frame?");
 
   if (!aView) {
     return;
   }
 
   nsIViewManager* vm = aView->GetViewManager();
+ 
+  /* If this frame has a -moz-transform property, tell it to invalidate on a scroll
+   * rather than doing a BitBlt.
+   */
+  if (aFrame->GetStyleDisplay()->HasTransform())
+    aView->SetInvalidateFrameOnScroll();
 
   if (nsnull == aStyleContext) {
     aStyleContext = aFrame->GetStyleContext();
   }
 
   // Make sure visibility is correct
   if (0 == (aFlags & NS_FRAME_NO_VISIBILITY)) {
     // See if the view should be hidden or visible
@@ -628,17 +634,17 @@ PRBool
 nsContainerFrame::FrameNeedsView(nsIFrame* aFrame)
 {
   // XXX Check needed because frame construction can't properly figure out when
   // a frame is the child of a scrollframe
   if (aFrame->GetStyleContext()->GetPseudoType() ==
       nsCSSAnonBoxes::scrolledContent) {
     return PR_TRUE;
   }
-  return aFrame->NeedsView();
+  return aFrame->NeedsView() || aFrame->GetStyleDisplay()->HasTransform();
 }
 
 static nscoord GetCoord(const nsStyleCoord& aCoord, nscoord aIfNotCoord)
 {
   return aCoord.GetUnit() == eStyleUnit_Coord
            ? aCoord.GetCoordValue()
            : aIfNotCoord;
 }
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -434,16 +434,22 @@ nsFrame::Init(nsIContent*      aContent,
   }
   if (mParent) {
     nsFrameState state = mParent->GetStateBits();
 
     // Make bits that are currently off (see constructor) the same:
     mState |= state & (NS_FRAME_INDEPENDENT_SELECTION |
                        NS_FRAME_GENERATED_CONTENT);
   }
+  if (GetStyleDisplay()->HasTransform()) {
+    // The frame gets reconstructed if we toggle the -moz-transform
+    // property, so we can set this bit here and then ignore it.
+    mState |= NS_FRAME_MAY_BE_TRANSFORMED;
+  }
+  
   DidSetStyleContext();
 
   if (IsBoxWrapped())
     InitBoxMetrics(PR_FALSE);
 
   return NS_OK;
 }
 
@@ -661,16 +667,23 @@ nsIFrame::GetPaddingRect() const
 {
   nsMargin b(GetUsedBorder());
   ApplySkipSides(b);
   nsRect r(mRect);
   r.Deflate(b);
   return r;
 }
 
+PRBool
+nsIFrame::IsTransformed() const
+{
+  return (mState & NS_FRAME_MAY_BE_TRANSFORMED) &&
+    GetStyleDisplay()->HasTransform();
+}
+
 nsRect
 nsIFrame::GetContentRect() const
 {
   nsMargin bp(GetUsedBorderAndPadding());
   ApplySkipSides(bp);
   nsRect r(mRect);
   r.Deflate(bp);
   return r;
@@ -1176,16 +1189,23 @@ nsIFrame::BuildDisplayListForStackingCon
   if (IsFrameOfType(eReplaced) && !IsVisibleForPainting(aBuilder))
     return NS_OK;
 
   nsRect absPosClip;
   const nsStyleDisplay* disp = GetStyleDisplay();
   PRBool applyAbsPosClipping =
       ApplyAbsPosClipping(aBuilder, disp, this, &absPosClip);
   nsRect dirtyRect = aDirtyRect;
+
+  /* If we're being transformed, we need to invert the matrix transform so that we don't 
+   * grab points in the wrong coordinate system!
+   */
+  if ((mState & NS_FRAME_MAY_BE_TRANSFORMED) && disp->HasTransform())
+    dirtyRect = nsDisplayTransform::UntransformRect(dirtyRect, this, nsPoint(0, 0));
+  
   if (applyAbsPosClipping) {
     dirtyRect.IntersectRect(dirtyRect,
                             absPosClip - aBuilder->ToReferenceFrame(this));
   }
 
 #ifdef MOZ_SVG
   PRBool usingSVGEffects = nsSVGIntegrationUtils::UsingEffectsForFrame(this);
   if (usingSVGEffects) {
@@ -1270,26 +1290,48 @@ nsIFrame::BuildDisplayListForStackingCon
     nsDisplayItem* item = wrapper.WrapList(aBuilder, this, &resultList);
     if (!item)
       return NS_ERROR_OUT_OF_MEMORY;
     // resultList was emptied
     resultList.AppendToTop(item);
   }
  
 #ifdef MOZ_SVG
+  /* If there are any SVG effects, wrap up the list in an effects list. */
   if (usingSVGEffects) {
-    rv = aList->AppendNewToTop(new (aBuilder) nsDisplaySVGEffects(this, &resultList));
+    nsDisplaySVGEffects* svgList = new (aBuilder) nsDisplaySVGEffects(this, &resultList);
+    if (!svgList)
+      return NS_ERROR_OUT_OF_MEMORY;
+
+    /* List now emptied, so add the new list to the top. */
+    resultList.AppendToTop(svgList);
   } else
 #endif
+
+  /* If there is any opacity, wrap it up in an opacity list. */
   if (disp->mOpacity < 1.0f) {
-    rv = aList->AppendNewToTop(new (aBuilder) nsDisplayOpacity(this, &resultList));
-  } else {
-    aList->AppendToTop(&resultList);
-  }
-
+    nsDisplayOpacity* opacityList = new (aBuilder) nsDisplayOpacity(this, &resultList);
+    if (!opacityList)
+      return NS_ERROR_OUT_OF_MEMORY;
+
+    resultList.AppendToTop(opacityList);
+  }
+
+  /* If we're going to apply a transformation, wrap everything in an
+   * nsDisplayTransform.
+   */
+  if ((mState & NS_FRAME_MAY_BE_TRANSFORMED) && disp->HasTransform()) {
+    nsDisplayTransform* transform = new (aBuilder) nsDisplayTransform(this, &resultList);
+    if (!transform)  
+      return NS_ERROR_OUT_OF_MEMORY;
+
+    resultList.AppendToTop(transform);
+  }
+
+  aList->AppendToTop(&resultList);
   return rv;
 }
 
 class nsDisplaySummary : public nsDisplayItem
 {
 public:
   nsDisplaySummary(nsIFrame* aFrame) : nsDisplayItem(aFrame) {
     MOZ_COUNT_CTOR(nsDisplaySummary);
@@ -1418,17 +1460,21 @@ nsIFrame::BuildDisplayListForChild(nsDis
   
   const nsStyleDisplay* ourDisp = GetStyleDisplay();
   // REVIEW: Taken from nsBoxFrame::Paint
   // Don't paint our children if the theme object is a leaf.
   if (IsThemed(ourDisp) &&
       !PresContext()->GetTheme()->WidgetIsContainer(ourDisp->mAppearance))
     return NS_OK;
 
-  PRBool isComposited = disp->mOpacity != 1.0f
+  // Child is composited if it's transformed, partially transparent, or has
+  // SVG effects.
+  PRBool isComposited = disp->mOpacity != 1.0f ||
+    ((aChild->mState & NS_FRAME_MAY_BE_TRANSFORMED) && 
+     aChild->GetStyleDisplay()->HasTransform())
 #ifdef MOZ_SVG
     || nsSVGIntegrationUtils::UsingEffectsForFrame(aChild)
 #endif
     ;
   PRBool isPositioned = disp->IsPositioned();
   if (isComposited || isPositioned || (aFlags & DISPLAY_CHILD_FORCE_STACKING_CONTEXT)) {
     // If you change this, also change IsPseudoStackingContextFromStyle()
     pseudoStackingContext = PR_TRUE;
@@ -3603,31 +3649,144 @@ nsIFrame::Invalidate(const nsRect& aDama
     shell->IsPaintingSuppressed(&suppressed);
     if (suppressed)
       return;
   }
   
   InvalidateInternal(aDamageRect, 0, 0, nsnull, aImmediate);
 }
 
+/**
+ * Helper function that funnels an InvalidateInternal request up to the
+ * parent.  This function is used so that if MOZ_SVG is not defined, we still
+ * have unified control paths in the InvalidateInternal chain.
+ *
+ * @param aDamageRect The rect to invalidate.
+ * @param aX The x offset from the origin of this frame to the rectangle.
+ * @param aY The y offset from the origin of this frame to the rectangle.
+ * @param aImmediate Whether to redraw immediately.
+ * @return None, though this funnels the request up to the parent frame.
+ */
+void
+nsIFrame::InvalidateInternalAfterResize(const nsRect& aDamageRect, nscoord aX,
+                                        nscoord aY, PRBool aImmediate)
+{
+  /* If we're a transformed frame, then we need to apply our transform to the
+   * damage rectangle so that the redraw correctly redraws the transformed
+   * region.  We're moved over aX and aY from our origin, but since this aX
+   * and aY is contained within our border, we need to scoot back by -aX and
+   * -aY to get back to the origin of the transform.
+   *
+   * There's one more problem, though, and that's that we don't know what
+   * coordinate space this rectangle is in.  Sometimes it's in the local
+   * coordinate space for the frame, and sometimes its in the transformed
+   * coordinate space.  If we get it wrong, we'll display incorrectly.  Until I
+   * find a better fix for this problem, we'll invalidate the union of the two
+   * rectangles (original rectangle and transformed rectangle).  At least one of
+   * these will be correct.
+   *
+   * See bug #452496 for more details.
+   */
+  if ((mState & NS_FRAME_MAY_BE_TRANSFORMED) &&
+      GetStyleDisplay()->HasTransform()) {
+    nsRect newDamageRect;
+    newDamageRect.UnionRect(nsDisplayTransform::TransformRect
+                            (aDamageRect, this, nsPoint(-aX, -aY)), aDamageRect);
+    GetParent()->
+      InvalidateInternal(newDamageRect, aX + mRect.x, aY + mRect.y, this,
+                         aImmediate);
+  }
+  else 
+    GetParent()->
+      InvalidateInternal(aDamageRect, aX + mRect.x, aY + mRect.y, this, aImmediate);
+}
+
 void
 nsIFrame::InvalidateInternal(const nsRect& aDamageRect, nscoord aX, nscoord aY,
                              nsIFrame* aForChild, PRBool aImmediate)
 {
 #ifdef MOZ_SVG
   if (nsSVGIntegrationUtils::UsingEffectsForFrame(this)) {
     nsRect r = nsSVGIntegrationUtils::GetInvalidAreaForChangedSource(this,
             aDamageRect + nsPoint(aX, aY));
-    GetParent()->InvalidateInternal(r, mRect.x, mRect.y, this, aImmediate);
+    /* Rectangle is now in our own local space, so aX and aY are effectively
+     * zero.  Thus we'll pretend that the entire time this was in our own
+     * local coordinate space and do any remaining processing.
+     */
+    InvalidateInternalAfterResize(r, 0, 0, aImmediate);
     return;
   }
 #endif
-
-  GetParent()->
-    InvalidateInternal(aDamageRect, aX + mRect.x, aY + mRect.y, this, aImmediate);
+  
+  InvalidateInternalAfterResize(aDamageRect, aX, aY, aImmediate);
+}
+
+gfxMatrix
+nsIFrame::GetTransformMatrix(nsIFrame **aOutAncestor)
+{
+  NS_PRECONDITION(aOutAncestor, "Need a place to put the ancestor!");
+
+  /* Whether or not we're transformed, the matrix will be relative to our
+   * cross-doc parent frame.
+   */
+  *aOutAncestor = nsLayoutUtils::GetCrossDocParentFrame(this);
+
+  /* If we're transformed, we want to hand back the combination
+   * transform/translate matrix that will apply our current transform, then
+   * shift us to our parent.
+   */
+  if (IsTransformed()) {
+    /* Compute the delta to the parent, which we need because we are converting
+     * coordinates to our parent.
+     */
+    NS_ASSERTION(*aOutAncestor, "Cannot transform the viewport frame!");
+    nsPoint delta = GetOffsetTo(*aOutAncestor);
+    PRInt32 scaleFactor = PresContext()->AppUnitsPerDevPixel();
+
+    gfxMatrix result =
+      nsDisplayTransform::GetResultingTransformMatrix(this, nsPoint(0, 0),
+                                                      scaleFactor);
+    /* Combine the raw transform with a translation to our parent. */
+    result *= gfxMatrix().Translate
+      (gfxPoint(NSAppUnitsToFloatPixels(delta.x, scaleFactor),
+                NSAppUnitsToFloatPixels(delta.y, scaleFactor)));
+    return result;
+  }
+  
+  /* Otherwise, we're not transformed.  In that case, we'll walk up the frame
+   * tree until we either hit the root frame or something that may be
+   * transformed.  We'll then change coordinates into that frame, since we're
+   * guaranteed that nothing in-between can be transformed.  First, however,
+   * we have to check to see if we have a parent.  If not, we'll set the
+   * outparam to null (indicating that there's nothing left) and will hand back
+   * the identity matrix.
+   */
+  if (!*aOutAncestor)
+    return gfxMatrix();
+  
+  /* Keep iterating while the frame can't possibly be transformed. */
+  while (!((*aOutAncestor)->mState & NS_FRAME_MAY_BE_TRANSFORMED)) {
+    /* If no parent, stop iterating.  Otherwise, update the ancestor. */
+    nsIFrame* parent = nsLayoutUtils::GetCrossDocParentFrame(*aOutAncestor);
+    if (!parent)
+      break;
+
+    *aOutAncestor = parent;
+  }
+
+  NS_ASSERTION(*aOutAncestor, "Somehow ended up with a null ancestor...?");
+
+  /* Translate from this frame to our ancestor, if it exists.  That's the
+   * entire transform, so we're done.
+   */
+  nsPoint delta = GetOffsetTo(*aOutAncestor);
+  PRInt32 scaleFactor = PresContext()->AppUnitsPerDevPixel();
+  return gfxMatrix().Translate
+    (gfxPoint(NSAppUnitsToFloatPixels(delta.x, scaleFactor),
+              NSAppUnitsToFloatPixels(delta.y, scaleFactor)));
 }
 
 void
 nsIFrame::InvalidateRectDifference(const nsRect& aR1, const nsRect& aR2)
 {
   nsRect sizeHStrip, sizeVStrip;
   nsLayoutUtils::GetRectDifferenceStrips(aR1, aR2, &sizeHStrip, &sizeVStrip);
   Invalidate(sizeVStrip);
@@ -5434,16 +5593,27 @@ nsIFrame::FinishAndStoreOverflow(nsRect*
   // Overflow area must always include the frame's top-left and bottom-right,
   // even if the frame rect is empty.
   // Pending a real fix for bug 426879, don't do this for inline frames
   // with zero width.
   if (aNewSize.width != 0 || !IsInlineFrame(this))
     aOverflowArea->UnionRectIncludeEmpty(*aOverflowArea,
                                          nsRect(nsPoint(0, 0), aNewSize));
 
+  /* If we're transformed, transform the overflow rect by the current transformation. */
+  if ((mState & NS_FRAME_MAY_BE_TRANSFORMED) && 
+      GetStyleDisplay()->HasTransform()) {
+    /* Since our size might not actually have been computed yet, we need to make sure that we use the
+     * correct dimensions by overriding the stored bounding rectangle with the value the caller has
+     * ensured us we'll use.
+     */
+    nsRect newBounds(nsPoint(0, 0), aNewSize);
+    *aOverflowArea = nsDisplayTransform::TransformRect(*aOverflowArea, this, nsPoint(0, 0), &newBounds);
+  }
+  
   PRBool geometricOverflow =
     aOverflowArea->x < 0 || aOverflowArea->y < 0 ||
     aOverflowArea->XMost() > aNewSize.width || aOverflowArea->YMost() > aNewSize.height;
   // Clear geometric overflow area if we clip our children
   NS_ASSERTION((disp->mOverflowY == NS_STYLE_OVERFLOW_CLIP) ==
                (disp->mOverflowX == NS_STYLE_OVERFLOW_CLIP),
                "If one overflow is clip, the other should be too");
   if (geometricOverflow &&
--- a/layout/generic/nsGfxScrollFrame.cpp
+++ b/layout/generic/nsGfxScrollFrame.cpp
@@ -1386,19 +1386,26 @@ nsGfxScrollFrameInner::BuildDisplayList(
   }
   return NS_OK;
 }
 
 PRBool
 nsGfxScrollFrameInner::NeedsClipWidget() const
 {
   // Scrollports contained in form controls (e.g., listboxes) don't get
-  // widgets.
+  // widgets.  Also, transformed elements don't need clip widgets since they
+  // result in graphical glitches.
   for (nsIFrame* parentFrame = mOuter; parentFrame;
-       parentFrame = parentFrame->GetParent()) {
+       parentFrame = nsLayoutUtils::GetCrossDocParentFrame(parentFrame)) {
+    
+    /* See if we have a transform... we should have no widget if that's the case. */
+    if (parentFrame->GetStyleDisplay()->HasTransform())
+      return PR_FALSE;
+
+    /* If we're a form element, we don't need a widget. */
     nsIFormControlFrame* fcFrame;
     if ((NS_SUCCEEDED(parentFrame->QueryInterface(NS_GET_IID(nsIFormControlFrame), (void**)&fcFrame)))) {
       return PR_FALSE;
     }
   }
 
   // Scrollports that don't ever show associated scrollbars don't get
   // widgets, because they will seldom actually be scrolled.
--- a/layout/generic/nsIFrame.h
+++ b/layout/generic/nsIFrame.h
@@ -48,16 +48,17 @@
 
 #include <stdio.h>
 #include "nsISupports.h"
 #include "nsEvent.h"
 #include "nsStyleStruct.h"
 #include "nsStyleContext.h"
 #include "nsIContent.h"
 #include "nsHTMLReflowMetrics.h"
+#include "gfxMatrix.h"
 
 /**
  * New rules of reflow:
  * 1. you get a WillReflow() followed by a Reflow() followed by a DidReflow() in order
  *    (no separate pass over the tree)
  * 2. it's the parent frame's responsibility to size/position the child's view (not
  *    the child frame's responsibility as it is today) during reflow (and before
  *    sending the DidReflow() notification)
@@ -226,17 +227,21 @@ enum {
   // If this bit is set, the frame was created from anonymous content.
   NS_FRAME_INDEPENDENT_SELECTION =              0x00004000,
 
   // If this bit is set, the frame is "special" (lame term, I know),
   // which means that it is part of the mangled frame hierarchy that
   // results when an inline has been split because of a nested block.
   NS_FRAME_IS_SPECIAL =                         0x00008000,
 
-  NS_FRAME_THIS_BIT_BELONGS_TO_ROC_DO_NOT_USE_OR_I_WILL_HUNT_YOU_DOWN = 0x00010000,
+  // If this bit is set, the frame may have a transform that it applies
+  // to its coordinate system (e.g. CSS transform, SVG foreignObject).
+  // This is used primarily in GetTransformMatrix to optimize for the
+  // common case.
+  NS_FRAME_MAY_BE_TRANSFORMED =                 0x00010000,
 
 #ifdef IBMBIDI
   // If this bit is set, the frame itself is a bidi continuation,
   // or is incomplete (its next sibling is a bidi continuation)
   NS_FRAME_IS_BIDI =                            0x00020000,
 #endif
 
   // If this bit is set the frame has descendant with a view
@@ -897,16 +902,22 @@ public:
                                     PRUint32                aFlags = 0);
 
   /**
    * Does this frame type always need a view?
    */
   virtual PRBool NeedsView() { return PR_FALSE; }
 
   /**
+   * Returns whether this frame has a transform matrix applied to it.  This is true
+   * if we have the -moz-transform property or if we're an SVGForeignObjectFrame.
+   */
+  virtual PRBool IsTransformed() const;
+
+  /**
    * This frame needs a view with a widget (e.g. because it's fixed
    * positioned), so we call this to create the widget. If widgets for
    * this frame type need to be of a certain type or require special
    * initialization, that can be done here.
    */
   virtual nsresult CreateWidgetForView(nsIView* aView);
 
   /**
@@ -1545,17 +1556,29 @@ public:
   virtual nsIWidget* GetWindow() const;
 
   /**
    * Get the "type" of the frame. May return a NULL atom pointer
    *
    * @see nsGkAtoms
    */
   virtual nsIAtom* GetType() const = 0;
-  
+
+  /**
+   * Returns a transformation matrix that converts points in this frame's coordinate space
+   * to points in some ancestor frame's coordinate space.  The frame decides which ancestor
+   * it will use as a reference point.  If this frame has no ancestor, aOutAncestor will be
+   * set to null.
+   *
+   * @param aOutAncestor [out] The ancestor frame the frame has chosen.  If this frame has no
+   *        ancestor, aOutAncestor will be nsnull.
+   * @return A gfxMatrix that converts points in this frame's coordinate space into
+   *         points in aOutAncestor's coordinate space.
+   */
+  virtual gfxMatrix GetTransformMatrix(nsIFrame **aOutAncestor);
 
   /**
    * Bit-flags to pass to IsFrameOfType()
    */
   enum {
     eMathML =                           1 << 0,
     eSVG =                              1 << 1,
     eSVGForeignObject =                 1 << 2,
@@ -1662,16 +1685,30 @@ public:
    *   In case it's true, pending notifications will be flushed which
    *   could cause frames to be deleted (including |this|).
    */  
   virtual void InvalidateInternal(const nsRect& aDamageRect,
                                   nscoord aOffsetX, nscoord aOffsetY,
                                   nsIFrame* aForChild, PRBool aImmediate);
 
   /**
+   * Helper function that funnels an InvalidateInternal request up to the
+   * parent.  This function is used so that if MOZ_SVG is not defined, we still
+   * have unified control paths in the InvalidateInternal chain.
+   *
+   * @param aDamageRect The rect to invalidate.
+   * @param aX The x offset from the origin of this frame to the rectangle.
+   * @param aY The y offset from the origin of this frame to the rectangle.
+   * @param aImmediate Whether to redraw immediately.
+   * @return None, though this funnels the request up to the parent frame.
+   */
+  void InvalidateInternalAfterResize(const nsRect& aDamageRect, nscoord aX,
+                                     nscoord aY, PRBool aImmediate);
+
+  /**
    * Take two rectangles in the coordinate system of this frame which
    * have the same origin and invalidate the difference between them.
    * This is a helper method to be used when a frame is being resized.
    *
    * @param aR1 the first rectangle
    * @param aR2 the second rectangle
    */
   void InvalidateRectDifference(const nsRect& aR1, const nsRect& aR2);
--- a/layout/reftests/reftest.list
+++ b/layout/reftests/reftest.list
@@ -106,16 +106,19 @@ include text-decoration/reftest.list
 include text-indent/reftest.list
 
 # text-shadow/
 include text-shadow/reftest.list
 
 # text-transform/
 include text-transform/reftest.list
 
+# -moz-transform/
+include transform/reftest.list
+
 # xul-document-load/
 include xul-document-load/reftest.list
 
 # xul grid
 include ../xul/base/src/grid/reftests/reftest.list
 
 # z-index/
 include z-index/reftest.list
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/abspos-1-ref.html
@@ -0,0 +1,12 @@
+<html>
+<head>
+</head>
+<body>
+  <div style="width: 100px; height: 200px; position: relative; left: 50px; top: 50px; background-color: gold;">
+    A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
+    <div style="background-color: navy; color: gold; width: 200px; height: 100px; position: absolute; left: 50px; top: 100px;">
+      0 1 2 3 4 5 6 7 8 9
+    </div>
+  </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/abspos-1a.html
@@ -0,0 +1,12 @@
+<html>
+<head>
+</head>
+<body>
+  <div style="width: 100px; height: 200px; -moz-transform: translate(50px); background-color: gold;">
+    A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
+    <div style="background-color: navy; color: gold; width: 200px; height: 100px; position: absolute; left: 50px; top: 100px;">
+      0 1 2 3 4 5 6 7 8 9
+    </div>
+  </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/abspos-1b.html
@@ -0,0 +1,12 @@
+<html>
+<head>
+</head>
+<body>
+  <div style="width: 100px; height: 200px; -moz-transform: translate(50px) ;background-color: gold;">
+    A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
+    <div style="background-color: navy; color: gold; width: 200px; height: 100px; position: fixed; left: 50px; top: 100px;">
+      0 1 2 3 4 5 6 7 8 9
+    </div>
+  </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/abspos-1c.html
@@ -0,0 +1,12 @@
+<html>
+<head>
+</head>
+<body>
+  <div style="width: 100px; height: 200px; -moz-transform: translate(50px) ;background-color: gold;">
+    A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
+    <div style="background-color: navy; color: gold; width: 200px; height: 100px; position: fixed; right: -150px; bottom: 0px;">
+      0 1 2 3 4 5 6 7 8 9
+    </div>
+  </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/abspos-1d.html
@@ -0,0 +1,12 @@
+<html>
+<head>
+</head>
+<body>
+  <div style="width: 100px; height: 200px; -moz-transform: translate(50px) ;background-color: gold;">
+    A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
+    <div style="background-color: navy; color: gold; width: 200px; height: 100px; position: absolute; right: -150px; bottom: 0px;">
+      0 1 2 3 4 5 6 7 8 9
+    </div>
+  </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/abspos-1e.html
@@ -0,0 +1,12 @@
+<html>
+<head>
+</head>
+<body>
+  <div style="width: 100px; height: 200px; -moz-transform: translate(50px) ;background-color: gold;">
+    A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
+    <div style="background-color: navy; color: gold; width: 200px; height: 100px; position: absolute; right: -151px; bottom: 0px;">
+      0 1 2 3 4 5 6 7 8 9
+    </div>
+  </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/origin-1-ref.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+</head>
+<body>
+  <div style="width:200px;height:100px;border:1px solid black; -moz-transform: rotate(45deg);">
+	Some text!
+  </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/origin-1a.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+</head>
+<body>
+  <div style="width:200px;height:100px;border:1px solid black; -moz-transform: rotate(45deg); -moz-transform-origin: 0% 0%">
+	Some text!
+  </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/origin-1b.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+</head>
+<body>
+  <div style="width:200px;height:100px;border:1px solid black; -moz-transform: rotate(45deg); -moz-transform-origin:100% 50%">
+	Some text!
+  </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/origin-2-ref.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+</head>
+<body>
+  <div style="width:200px;height:100px;border:1px solid black; -moz-transform: rotate(45deg);">
+	Some text!
+  </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/origin-2a.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+</head>
+<body>
+  <div style="width:200px;height:100px;border:1px solid black; -moz-transform: rotate(45deg); -moz-transform-origin: 101px 51px;">
+	Some text!
+  </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/origin-2b.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+</head>
+<body>
+  <div style="width:200px;height:100px;border:1px solid black; -moz-transform: rotate(45deg); -moz-transform-origin: 101px 50%;">
+	Some text!
+  </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/origin-2c.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+</head>
+<body>
+  <div style="width:200px;height:100px;border:1px solid black; -moz-transform: rotate(45deg); -moz-transform-origin: 50% 51px;">
+	Some text!
+  </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/origin-name-1-ref.html
@@ -0,0 +1,8 @@
+<html>
+<head>
+</head>
+<body>
+  <div style="margin-left: 300px; margin-top:300px; width:100px; height:200px; background-color:#202040; -moz-transform: rotate(45deg); -moz-transform-origin: 0% 0%;">
+  </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/origin-name-1a.html
@@ -0,0 +1,8 @@
+<html>
+<head>
+</head>
+<body>
+  <div style="margin-left: 300px; margin-top:300px; width:100px; height:200px; background-color:#202040; -moz-transform: rotate(45deg); -moz-transform-origin: top left;">
+  </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/origin-name-1b.html
@@ -0,0 +1,8 @@
+<html>
+<head>
+</head>
+<body>
+  <div style="margin-left: 300px; margin-top:300px; width:100px; height:200px; background-color:#202040; -moz-transform: rotate(45deg); -moz-transform-origin: left top;">
+  </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/origin-name-2-ref.html
@@ -0,0 +1,8 @@
+<html>
+<head>
+</head>
+<body>
+  <div style="margin-left: 300px; margin-top:300px; width:100px; height:200px; background-color:#202040; -moz-transform: rotate(45deg); -moz-transform-origin: 50% 0%;">
+  </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/origin-name-2a.html
@@ -0,0 +1,8 @@
+<html>
+<head>
+</head>
+<body>
+  <div style="margin-left: 300px; margin-top:300px; width:100px; height:200px; background-color:#202040; -moz-transform: rotate(45deg); -moz-transform-origin: top;">
+  </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/origin-name-2b.html
@@ -0,0 +1,8 @@
+<html>
+<head>
+</head>
+<body>
+  <div style="margin-left: 300px; margin-top:300px; width:100px; height:200px; background-color:#202040; -moz-transform: rotate(45deg); -moz-transform-origin: top center;">
+  </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/origin-name-2c.html
@@ -0,0 +1,8 @@
+<html>
+<head>
+</head>
+<body>
+  <div style="margin-left: 300px; margin-top:300px; width:100px; height:200px; background-color:#202040; -moz-transform: rotate(45deg); -moz-transform-origin: center top;">
+  </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/origin-name-3-ref.html
@@ -0,0 +1,8 @@
+<html>
+<head>
+</head>
+<body>
+  <div style="margin-left: 300px; margin-top:300px; width:100px; height:200px; background-color:#202040; -moz-transform: rotate(45deg); -moz-transform-origin: 100% 0%;">
+  </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/origin-name-3a.html
@@ -0,0 +1,8 @@
+<html>
+<head>
+</head>
+<body>
+  <div style="margin-left: 300px; margin-top:300px; width:100px; height:200px; background-color:#202040; -moz-transform: rotate(45deg); -moz-transform-origin: top right;">
+  </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/origin-name-3b.html
@@ -0,0 +1,8 @@
+<html>
+<head>
+</head>
+<body>
+  <div style="margin-left: 300px; margin-top:300px; width:100px; height:200px; background-color:#202040; -moz-transform: rotate(45deg); -moz-transform-origin: right top;">
+  </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/percent-1-ref.html
@@ -0,0 +1,14 @@
+<html>
+<head>
+  <style type="text/css">
+    .transformed
+    {
+    -moz-transform: rotate(10deg) translatex(50px) rotate(10deg) translatey(50px) skewx(10deg) translate(25px);
+    }
+  </style>
+</head>
+<body>
+  <div style="width:100px; height:50px; background-color:gold; position: absolute; left:100px; top:100px;" class="transformed">
+  </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/percent-1a.html
@@ -0,0 +1,14 @@
+<html>
+<head>
+  <style type="text/css">
+    .transformed
+    {
+    -moz-transform: rotate(10deg) translatex(50%) rotate(10deg) translatey(50px) skewx(10deg) translate(25px);
+    }
+  </style>
+</head>
+<body>
+  <div style="width:100px; height:50px; background-color:gold; position: absolute; left:100px; top:100px;" class="transformed">
+  </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/percent-1b.html
@@ -0,0 +1,14 @@
+<html>
+<head>
+  <style type="text/css">
+    .transformed
+    {
+    -moz-transform: rotate(10deg) translatex(50px) rotate(10deg) translatey(100%) skewx(10deg) translate(25px);
+    }
+  </style>
+</head>
+<body>
+  <div style="width:100px; height:50px; background-color:gold; position: absolute; left:100px; top:100px;" class="transformed">
+  </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/percent-1c.html
@@ -0,0 +1,14 @@
+<html>
+<head>
+  <style type="text/css">
+    .transformed
+    {
+    -moz-transform: rotate(10deg) translatex(50px) rotate(10deg) translatey(50px) skewx(10deg) translate(25%, 50%);
+    }
+  </style>
+</head>
+<body>
+  <div style="width:100px; height:50px; background-color:gold; position: absolute; left:100px; top:100px;" class="transformed">
+  </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/percent-1d.html
@@ -0,0 +1,14 @@
+<html>
+<head>
+  <style type="text/css">
+    .transformed
+    {
+    -moz-transform: rotate(10deg) translatex(50%) rotate(10deg) translatey(100%) skewx(10deg) translate(25px);
+    }
+  </style>
+</head>
+<body>
+  <div style="width:100px; height:50px; background-color:gold; position: absolute; left:100px; top:100px;" class="transformed">
+  </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/percent-1e.html
@@ -0,0 +1,14 @@
+<html>
+<head>
+  <style type="text/css">
+    .transformed
+    {
+    -moz-transform: rotate(10deg) translatex(50%) rotate(10deg) translatey(100%) skewx(10deg) translate(25%, 50%);
+    }
+  </style>
+</head>
+<body>
+  <div style="width:100px; height:50px; background-color:gold; position: absolute; left:100px; top:100px;" class="transformed">
+  </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/percent-1f.html
@@ -0,0 +1,14 @@
+<html>
+<head>
+  <style type="text/css">
+    .transformed
+    {
+    -moz-transform: rotate(10deg) translatex(50%) rotate(10deg) translatey(50px) skewx(10deg) translate(25%, 50%);
+    }
+  </style>
+</head>
+<body>
+  <div style="width:100px; height:50px; background-color:gold; position: absolute; left:100px; top:100px;" class="transformed">
+  </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/percent-1g.html
@@ -0,0 +1,14 @@
+<html>
+<head>
+  <style type="text/css">
+    .transformed
+    {
+    -moz-transform: rotate(10deg) translatex(50px) rotate(10deg) translatey(100%) skewx(10deg) translate(25%, 50%);
+    }
+  </style>
+</head>
+<body>
+  <div style="width:100px; height:50px; background-color:gold; position: absolute; left:100px; top:100px;" class="transformed">
+  </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/reftest.list
@@ -0,0 +1,63 @@
+# translatex should act like position: relative
+== translatex-1a.html translatex-1-ref.html
+== translatex-1b.html translatex-1-ref.html
+== translatex-1c.html translatex-1-ref.html
+== translatex-1d.html translatex-1-ref.html
+== translatex-1e.html translatex-1-ref.html
+== translatex-1a.html translatex-1-ref-2.html
+# translatey should act like position: relative
+== translatey-1a.html translatey-1-ref.html
+== translatey-1b.html translatey-1-ref.html
+== translatey-1c.html translatey-1-ref.html
+== translatey-1d.html translatey-1-ref.html
+== translatey-1e.html translatey-1-ref.html
+# matrices defined to be translations should act like position: relative
+== translatex-2.html translatex-1-ref.html
+== translatey-2.html translatey-1-ref.html
+# translate should act like position: relative
+== translate-1a.html translate-1-ref.html
+== translate-1b.html translate-1-ref.html
+== translate-1c.html translate-1-ref.html
+== translate-1d.html translate-1-ref.html
+== translate-1e.html translate-1-ref.html
+# rotate: Several rotations of the same object should be idempotent
+== rotate-1a.html rotate-1-ref.html
+== rotate-1b.html rotate-1-ref.html
+== rotate-1c.html rotate-1-ref.html
+== rotate-1d.html rotate-1-ref.html
+== rotate-1e.html rotate-1-ref.html
+# rotate: 90deg rotations should be indistinguishable from objects constructed to look the same.
+== rotate-2a.html rotate-2-ref.html
+# -moz-transform-origin: We should NOT get the same images when using different -moz-transform-origins.
+!= origin-1a.html origin-1-ref.html
+!= origin-1b.html origin-1-ref.html
+# -moz-transform-origin: We should get the same images when using equivalent -moz-transform-origins.
+== origin-2a.html origin-2-ref.html
+== origin-2b.html origin-2-ref.html
+== origin-2c.html origin-2-ref.html
+# translate with percentages should be indistinguishable from translate with equivalent values.
+== percent-1a.html percent-1-ref.html
+== percent-1b.html percent-1-ref.html
+== percent-1c.html percent-1-ref.html
+== percent-1d.html percent-1-ref.html
+== percent-1e.html percent-1-ref.html
+== percent-1f.html percent-1-ref.html
+== percent-1g.html percent-1-ref.html
+# Transformed elements are abs-pos and fixed-pos containing blocks.
+== abspos-1a.html abspos-1-ref.html
+== abspos-1b.html abspos-1-ref.html
+== abspos-1c.html abspos-1-ref.html
+== abspos-1d.html abspos-1-ref.html
+!= abspos-1e.html abspos-1-ref.html
+# Origin can use "top" "right" etc.
+== origin-name-1a.html origin-name-1-ref.html
+== origin-name-1b.html origin-name-1-ref.html
+== origin-name-2a.html origin-name-2-ref.html
+== origin-name-2b.html origin-name-2-ref.html
+== origin-name-2c.html origin-name-2-ref.html
+== origin-name-3a.html origin-name-3-ref.html
+== origin-name-3b.html origin-name-3-ref.html
+# SVG effects should work on transforms.
+== transform-svg-1a.xhtml transform-svg-1-ref.xhtml
+== transform-svg-2a.xhtml transform-svg-2-ref.xhtml
+!= transform-svg-2a.xhtml transform-svg-2-fail.xhtml
\ No newline at end of file
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/rotate-1-ref.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+</head>
+<body>
+  <div style="-moz-transform: rotate(45deg);">
+    Test Text
+  </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/rotate-1a.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+</head>
+<body>
+  <div style="-moz-transform: rotate(45deg) rotate(360deg);">
+    Test Text
+  </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/rotate-1b.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+</head>
+<body>
+  <div style="-moz-transform: rotate(45deg) rotate(400grad);">
+    Test Text
+  </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/rotate-1c.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+</head>
+<body>
+  <div style="-moz-transform: rotate(45deg) rotate(100deg) rotate(80deg) rotate(200grad);">
+    Test Text
+  </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/rotate-1d.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+</head>
+<body>
+  <div style="-moz-transform: rotate(-45deg) rotate(100grad);">
+    Test Text
+  </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/rotate-1e.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+</head>
+<body>
+  <div style="-moz-transform: rotate(-135deg) rotate(3.1415926535897932384626433rad);">
+    Test Text
+  </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/rotate-2-ref.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+</head>
+<body>
+  <div style="width:200px;height:100px;border:1px solid black;">
+
+  </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/rotate-2a.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+</head>
+<body>
+  <div style="width:100px;height:200px;-moz-transform: rotate(-90deg) translate(50px, -50px); border: 1px solid black;">
+
+  </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/transform-svg-1-ref.xhtml
@@ -0,0 +1,12 @@
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/licenses/publicdomain/
+-->
+<html xmlns="http://www.w3.org/1999/xhtml">
+<body style="margin:0">
+  <div style="position: relative; left:100px; top:100px; width:300px; height:300px; background:lime;">
+    <div style="height:200px;"/>
+    <div style="height:100px; background:blue;"/>
+  </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/transform-svg-1a.xhtml
@@ -0,0 +1,20 @@
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/licenses/publicdomain/
+-->
+<html xmlns="http://www.w3.org/1999/xhtml"
+     xmlns:svg="http://www.w3.org/2000/svg"
+     xmlns:xlink="http://www.w3.org/1999/xlink">
+<body style="margin:0">
+  <div style="clip-path: url(#c1); width:400px; height:200px; background:lime; -moz-transform: translate(100px, 100px);">
+    <div style="height:200px;"/>
+    <div style="height:200px; background:blue;"/>
+  </div>
+
+  <svg:svg height="0">
+    <svg:clipPath id="c1" clipPathUnits="userSpaceOnuse">
+      <svg:rect x="0" y="0" width="300" height="300"/>
+    </svg:clipPath>
+  </svg:svg>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/transform-svg-1b.xhtml
@@ -0,0 +1,20 @@
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/licenses/publicdomain/
+-->
+<html xmlns="http://www.w3.org/1999/xhtml"
+     xmlns:svg="http://www.w3.org/2000/svg"
+     xmlns:xlink="http://www.w3.org/1999/xlink">
+<body style="margin:0">
+  <div style="clip-path: url(#c1); width:400px; height:400px; background:blue; -moz-transform: rotate(135deg);">
+    <div style="height:200px;"/>
+    <div style="height:200px; background:lime;"/>
+  </div>
+
+  <svg:svg height="0">
+    <svg:clipPath id="c1" clipPathUnits="userSpaceOnuse">
+      <svg:rect x="0" y="0" width="300" height="300"/>
+    </svg:clipPath>
+  </svg:svg>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/transform-svg-2-fail.xhtml
@@ -0,0 +1,20 @@
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/licenses/publicdomain/
+-->
+<html xmlns="http://www.w3.org/1999/xhtml"
+     xmlns:svg="http://www.w3.org/2000/svg"
+     xmlns:xlink="http://www.w3.org/1999/xlink">
+<body style="margin:0">
+  <div style="clip-path: url(#c1); width:400px; height:400px; background:blue;">
+    <div style="height:200px;"/>
+    <div style="height:200px; background:lime;"/>
+  </div>
+
+  <svg:svg height="0">
+    <svg:clipPath id="c1" clipPathUnits="userSpaceOnuse">
+      <svg:circle cx="100" cy="100" r="200"/>
+    </svg:clipPath>
+  </svg:svg>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/transform-svg-2-ref.xhtml
@@ -0,0 +1,20 @@
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/licenses/publicdomain/
+-->
+<html xmlns="http://www.w3.org/1999/xhtml"
+     xmlns:svg="http://www.w3.org/2000/svg"
+     xmlns:xlink="http://www.w3.org/1999/xlink">
+<body style="margin:0">
+  <div style="clip-path: url(#c1); width:400px; height:400px; background:blue; position: relative; left:100px; top:100px;">
+    <div style="height:200px;"/>
+    <div style="height:200px; background:lime;"/>
+  </div>
+
+  <svg:svg height="0">
+    <svg:clipPath id="c1" clipPathUnits="userSpaceOnuse">
+      <svg:circle cx="200" cy="200" r="200"/>
+    </svg:clipPath>
+  </svg:svg>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/transform-svg-2a.xhtml
@@ -0,0 +1,20 @@
+<!--
+     Any copyright is dedicated to the Public Domain.
+     http://creativecommons.org/licenses/publicdomain/
+-->
+<html xmlns="http://www.w3.org/1999/xhtml"
+     xmlns:svg="http://www.w3.org/2000/svg"
+     xmlns:xlink="http://www.w3.org/1999/xlink">
+<body style="margin:0">
+  <div style="clip-path: url(#c1); width:400px; height:400px; background:blue; -moz-transform: translate(100px);">
+    <div style="height:200px;"/>
+    <div style="height:200px; background:lime;"/>
+  </div>
+
+  <svg:svg height="0">
+    <svg:clipPath id="c1" clipPathUnits="userSpaceOnuse">
+      <svg:circle cx="200" cy="200" r="200"/>
+    </svg:clipPath>
+  </svg:svg>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/translate-1-ref.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+</head>
+<body>
+  <div style="position: relative; left: 50px; top: 50px;">
+    Test Text
+  </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/translate-1a.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+</head>
+<body>
+  <div style="-moz-transform: translate(50px);">
+    Test Text
+  </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/translate-1b.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+</head>
+<body>
+  <div style="-moz-transform: translate(50px) rotate(360deg);">
+    Test Text
+  </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/translate-1c.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+</head>
+<body>
+  <div style="-moz-transform: translate(25px) translate(25px);">
+    Test Text
+  </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/translate-1d.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+</head>
+<body>
+  <div style="-moz-transform: translate(25px); position:relative; top:25px; left:25px;">
+    Test Text
+  </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/translate-1e.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+</head>
+<body>
+  <div style="-moz-transform: translate(50px) translate(-100px) translate(150px) translate(-50px);">
+    Test Text
+  </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/translatex-1-ref-2.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+</head>
+<body>
+  <div style="margin-left: 50px;">
+    Test Text
+  </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/translatex-1-ref.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+</head>
+<body>
+  <div style="position:relative; left:50px;">
+    Test Text
+  </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/translatex-1a.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+</head>
+<body>
+  <div style="-moz-transform: translatex(50px);">
+    Test Text
+  </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/translatex-1b.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+</head>
+<body>
+  <div style="-moz-transform: translatex(50px) rotate(360deg);">
+    Test Text
+  </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/translatex-1c.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+</head>
+<body>
+  <div style="-moz-transform: translatex(25px) translatex(25px);">
+    Test Text
+  </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/translatex-1d.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+</head>
+<body>
+  <div style="-moz-transform: translatex(25px); position:relative; left:25px; top:0px;">
+    Test Text
+  </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/translatex-1e.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+</head>
+<body>
+  <div style="-moz-transform: translatex(50px) translatex(-100px) translatex(150px) translatex(-50px);">
+    Test Text
+  </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/translatex-2.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+</head>
+<body>
+  <div style="-moz-transform: matrix(1, 0, 0, 1, 50px, 0);">
+    Test Text
+  </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/translatey-1-ref-2.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+</head>
+<body>
+  <div style="margin-top: 50px;">
+    Test Text
+  </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/translatey-1-ref.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+</head>
+<body>
+  <div style="position:relative; top:50px;">
+    Test Text
+  </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/translatey-1a.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+</head>
+<body>
+  <div style="-moz-transform: translatey(50px);">
+    Test Text
+  </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/translatey-1b.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+</head>
+<body>
+  <div style="-moz-transform: translatey(50px) rotate(360deg);">
+    Test Text
+  </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/translatey-1c.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+</head>
+<body>
+  <div style="-moz-transform: translatey(25px) translatey(25px);">
+    Test Text
+  </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/translatey-1d.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+</head>
+<body>
+  <div style="-moz-transform: translatey(25px); position:relative; top:25px; left:0px;">
+    Test Text
+  </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/translatey-1e.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+</head>
+<body>
+  <div style="-moz-transform: translatey(50px) translatey(-100px) translatey(150px) translatey(-50px);">
+    Test Text
+  </div>
+</body>
+</html>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/translatey-2.html
@@ -0,0 +1,9 @@
+<html>
+<head>
+</head>
+<body>
+  <div style="-moz-transform: matrix(1, 0, 0, 1, 0, 50px);">
+    Test Text
+  </div>
+</body>
+</html>
--- a/layout/style/Makefile.in
+++ b/layout/style/Makefile.in
@@ -117,16 +117,17 @@ EXPORTS		= \
 		nsRuleWalker.h \
 		nsStyleContext.h \
 		nsStyleCoord.h \
 		nsStyleSet.h \
 		nsStyleStruct.h \
 		nsStyleStructFwd.h \
 		nsStyleStructInlines.h \
 		nsStyleStructList.h \
+		nsStyleTransformMatrix.h \
 		nsStyleUtil.h \
 		$(NULL)
 
 CPPSRCS		= \
 		nsCSSAnonBoxes.cpp \
 		nsCSSDataBlock.cpp \
 		nsCSSDeclaration.cpp \
 		nsCSSKeywords.cpp \
@@ -156,16 +157,17 @@ CPPSRCS		= \
 		nsLayoutStylesheetCache.cpp \
 		nsMediaFeatures.cpp \
 		nsROCSSPrimitiveValue.cpp \
 		nsRuleNode.cpp \
 		nsStyleContext.cpp \
 		nsStyleCoord.cpp \
 		nsStyleSet.cpp \
 		nsStyleStruct.cpp \
+		nsStyleTransformMatrix.cpp \
 		nsStyleUtil.cpp \
 		$(NULL)
 
 FORCE_STATIC_LIB = 1
 
 include $(topsrcdir)/config/rules.mk
 
 LOCAL_INCLUDES	= \
--- a/layout/style/nsCSSDeclaration.cpp
+++ b/layout/style/nsCSSDeclaration.cpp
@@ -221,22 +221,24 @@ PRBool nsCSSDeclaration::AppendValueToSt
           AppendCSSValueToString(aProperty, rect->mLeft, aResult);
           aResult.Append(PRUnichar(')'));
         }
       } break;
       case eCSSType_ValuePair: {
         const nsCSSValuePair *pair = static_cast<const nsCSSValuePair*>(storage);
         AppendCSSValueToString(aProperty, pair->mXValue, aResult);
         if (pair->mYValue != pair->mXValue ||
-            (aProperty == eCSSProperty_background_position &&
+            ((aProperty == eCSSProperty_background_position ||
+              aProperty == eCSSProperty__moz_transform_origin) &&
              pair->mXValue.GetUnit() != eCSSUnit_Inherit &&
              pair->mXValue.GetUnit() != eCSSUnit_Initial)) {
           // Only output a Y value if it's different from the X value
           // or if it's a background-position value other than 'initial'
-          // or 'inherit'.
+          // or 'inherit' or if it's a -moz-transform-origin value other
+          // than 'initial' or 'inherit'.
           aResult.Append(PRUnichar(' '));
           AppendCSSValueToString(aProperty, pair->mYValue, aResult);
         }
       } break;
       case eCSSType_ValueList: {
         const nsCSSValueList* val =
             *static_cast<nsCSSValueList*const*>(storage);
         do {
@@ -324,16 +326,39 @@ nsCSSDeclaration::AppendCSSValueToString
         ((eCSSUnit_Counter <= unit && unit <= eCSSUnit_Counters) &&
          i == array->Count() - 1)
         ? eCSSProperty_list_style_type : aProperty;
       if (AppendCSSValueToString(prop, array->Item(i), aResult)) {
         mark = PR_TRUE;
       }
     }
   }
+  /* Although Function is backed by an Array, we'll handle it separately
+   * because it's a bit quirky.
+   */
+  else if (eCSSUnit_Function == unit) {
+    const nsCSSValue::Array* array = aValue.GetArrayValue();
+    NS_ASSERTION(array->Count() >= 1, "Functions must have at least one element for the name.");
+
+    /* Append the function name. */
+    AppendCSSValueToString(aProperty, array->Item(0), aResult);
+    aResult.AppendLiteral("(");
+
+    /* Now, step through the function contents, writing each of them as we go. */
+    for (PRUint16 index = 1; index < array->Count(); ++index) {
+      AppendCSSValueToString(aProperty, array->Item(index), aResult);
+
+      /* If we're not at the final element, append a comma. */
+      if (index + 1 != array->Count())
+        aResult.AppendLiteral(", ");
+    }
+
+    /* Finally, append the closing parenthesis. */
+    aResult.AppendLiteral(")");
+  }
   else if (eCSSUnit_Integer == unit) {
     nsAutoString tmpStr;
     tmpStr.AppendInt(aValue.GetIntValue(), 10);
     aResult.Append(tmpStr);
   }
   else if (eCSSUnit_Enumerated == unit) {
     if (eCSSProperty_text_decoration == aProperty) {
       PRInt32 intValue = aValue.GetIntValue();
@@ -448,16 +473,17 @@ nsCSSDeclaration::AppendCSSValueToString
 
     case eCSSUnit_String:       break;
     case eCSSUnit_URL:          break;
     case eCSSUnit_Image:        break;
     case eCSSUnit_Array:        break;
     case eCSSUnit_Attr:
     case eCSSUnit_Counter:
     case eCSSUnit_Counters:     aResult.Append(PRUnichar(')'));    break;
+    case eCSSUnit_Function:     break;
     case eCSSUnit_Integer:      break;
     case eCSSUnit_Enumerated:   break;
     case eCSSUnit_EnumColor:    break;
     case eCSSUnit_Color:        break;
     case eCSSUnit_Percent:      aResult.Append(PRUnichar('%'));    break;
     case eCSSUnit_Number:       break;
 
     case eCSSUnit_Inch:         aResult.AppendLiteral("in");   break;
--- a/layout/style/nsCSSKeywordList.h
+++ b/layout/style/nsCSSKeywordList.h
@@ -331,16 +331,17 @@ CSS_KEY(low, low)
 CSS_KEY(lower, lower)
 CSS_KEY(lower-alpha, lower_alpha)
 CSS_KEY(lower-greek, lower_greek)
 CSS_KEY(lower-latin, lower_latin)
 CSS_KEY(lower-roman, lower_roman)
 CSS_KEY(lowercase, lowercase)
 CSS_KEY(ltr, ltr)
 CSS_KEY(margin-box, margin_box)
+CSS_KEY(matrix, matrix)
 CSS_KEY(medium, medium)
 CSS_KEY(menu, menu)
 CSS_KEY(menutext, menutext)
 CSS_KEY(message-box, message_box)
 CSS_KEY(middle, middle)
 CSS_KEY(mix, mix)
 CSS_KEY(mm, mm)
 CSS_KEY(move, move)
@@ -386,35 +387,42 @@ CSS_KEY(relative, relative)
 CSS_KEY(repeat, repeat)
 CSS_KEY(repeat-x, repeat_x)
 CSS_KEY(repeat-y, repeat_y)
 CSS_KEY(reverse, reverse)
 CSS_KEY(ridge, ridge)
 CSS_KEY(right, right)
 CSS_KEY(right-side, right_side)
 CSS_KEY(rightwards, rightwards)
+CSS_KEY(rotate, rotate)
 CSS_KEY(round, round)
 CSS_KEY(row-resize, row_resize)
 CSS_KEY(rtl, rtl)
 CSS_KEY(s, s)
 CSS_KEY(s-resize, s_resize)
+CSS_KEY(scale, scale)
+CSS_KEY(scalex, scalex)
+CSS_KEY(scaley, scaley)
 CSS_KEY(scroll, scroll)
 CSS_KEY(scrollbar, scrollbar)
 CSS_KEY(scrollbar-small, scrollbar_small)
 CSS_KEY(se-resize, se_resize)
 CSS_KEY(select-after, select_after)
 CSS_KEY(select-all, select_all)
 CSS_KEY(select-before, select_before)
 CSS_KEY(select-menu, select_menu)
 CSS_KEY(select-same, select_same)
 CSS_KEY(semi-condensed, semi_condensed)
 CSS_KEY(semi-expanded, semi_expanded)
 CSS_KEY(separate, separate)
 CSS_KEY(show, show)
 CSS_KEY(silent, silent)
+CSS_KEY(skew, skew)
+CSS_KEY(skewx, skewx)
+CSS_KEY(skewy, skewy)
 CSS_KEY(slow, slow)
 CSS_KEY(slower, slower)
 CSS_KEY(small, small)
 CSS_KEY(small-caps, small_caps)
 CSS_KEY(small-caption, small_caption)
 CSS_KEY(smaller, smaller)
 CSS_KEY(soft, soft)
 CSS_KEY(solid, solid)
@@ -445,16 +453,19 @@ CSS_KEY(thin, thin)
 CSS_KEY(threeddarkshadow, threeddarkshadow)
 CSS_KEY(threedface, threedface)
 CSS_KEY(threedhighlight, threedhighlight)
 CSS_KEY(threedlightshadow, threedlightshadow)
 CSS_KEY(threedshadow, threedshadow)
 CSS_KEY(toggle, toggle)
 CSS_KEY(top, top)
 CSS_KEY(top-outside, top_outside)
+CSS_KEY(translate, translate)
+CSS_KEY(translatex, translatex)
+CSS_KEY(translatey, translatey)
 CSS_KEY(tri-state, tri_state)
 CSS_KEY(ultra-condensed, ultra_condensed)
 CSS_KEY(ultra-expanded, ultra_expanded)
 CSS_KEY(underline, underline)
 CSS_KEY(upper-alpha, upper_alpha)
 CSS_KEY(upper-latin, upper_latin)
 CSS_KEY(upper-roman, upper_roman)
 CSS_KEY(uppercase, uppercase)
--- a/layout/style/nsCSSParser.cpp
+++ b/layout/style/nsCSSParser.cpp
@@ -78,16 +78,18 @@
 #include "nsIMediaList.h"
 #include "nsILookAndFeel.h"
 #include "nsStyleUtil.h"
 #include "nsIPrincipal.h"
 #include "prprf.h"
 #include "math.h"
 #include "nsContentUtils.h"
 #include "nsDOMError.h"
+#include "nsAutoPtr.h"
+#include "nsTArray.h"
 
 // Flags for ParseVariant method
 #define VARIANT_KEYWORD         0x000001  // K
 #define VARIANT_LENGTH          0x000002  // L
 #define VARIANT_PERCENT         0x000004  // P
 #define VARIANT_COLOR           0x000008  // C eCSSUnit_Color, eCSSUnit_String (e.g.  "red")
 #define VARIANT_URL             0x000010  // U
 #define VARIANT_NUMBER          0x000020  // N
@@ -263,16 +265,17 @@ protected:
   void AssertInitialState() {
     NS_PRECONDITION(!mHTMLMediaMode, "Bad initial state");
     NS_PRECONDITION(!mUnresolvablePrefixException, "Bad initial state");
     NS_PRECONDITION(!mParsingCompoundProperty, "Bad initial state");
   }
 
   PRBool ExpectSymbol(PRUnichar aSymbol, PRBool aSkipWS);
   PRBool ExpectEndProperty();
+  PRBool CheckEndProperty();
   nsSubstring* NextIdent();
   void SkipUntil(PRUnichar aStopSymbol);
   void SkipRuleSet();
   PRBool SkipAtRule();
   PRBool SkipDeclaration(PRBool aCheckForBraces);
   PRBool GetNonCloseParenToken(PRBool aSkipWS);
 
   PRBool PushGroup(nsICSSGroupRule* aRule);
@@ -383,16 +386,18 @@ protected:
 
   void InitBoxPropsAsPhysical(const nsCSSProperty *aSourceProperties);
 
   // Property specific parsing routines
   PRBool ParseAzimuth(nsCSSValue& aValue);
   PRBool ParseBackground();
   PRBool ParseBackgroundPosition();
   PRBool ParseBackgroundPositionValues();
+  PRBool ParseBoxPosition(nsCSSValuePair& aOut);
+  PRBool ParseBoxPositionValues(nsCSSValuePair& aOut);
   PRBool ParseBorderColor();
   PRBool ParseBorderColors(nsCSSValueList** aResult,
                            nsCSSProperty aProperty);
   PRBool ParseBorderImage();
   PRBool ParseBorderSpacing();
   PRBool ParseBorderSide(const nsCSSProperty aPropIDs[],
                          PRBool aSetAllSides);
   PRBool ParseDirectionalBorderSide(const nsCSSProperty aPropIDs[],
@@ -415,16 +420,17 @@ protected:
   PRBool ParseOneFamily(nsAString& aValue);
   PRBool ParseFamily(nsCSSValue& aValue);
   PRBool ParseFontSrc(nsCSSValue& aValue);
   PRBool ParseFontSrcFormat(nsTArray<nsCSSValue>& values);
   PRBool ParseFontRanges(nsCSSValue& aValue);
   PRBool ParseListStyle();
   PRBool ParseMargin();
   PRBool ParseMarks(nsCSSValue& aValue);
+  PRBool ParseMozTransform();
   PRBool ParseOutline();
   PRBool ParseOverflow();
   PRBool ParsePadding();
   PRBool ParsePause();
   PRBool ParseQuotes();
   PRBool ParseSize();
   PRBool ParseTextDecoration(nsCSSValue& aValue);
 
@@ -472,16 +478,30 @@ protected:
   void SetParsingCompoundProperty(PRBool aBool) {
     NS_ASSERTION(aBool == PR_TRUE || aBool == PR_FALSE, "bad PRBool value");
     mParsingCompoundProperty = aBool;
   }
   PRBool IsParsingCompoundProperty(void) const {
     return mParsingCompoundProperty;
   }
 
+  /* Functions for -moz-transform Parsing */
+  PRBool ReadSingleTransform(nsCSSValueList**& aTail);
+  PRBool ParseFunction(const nsString &aFunction, const PRInt32 aAllowedTypes[],
+                       PRUint16 aMinElems, PRUint16 aMaxElems,
+                       nsCSSValue &aValue);
+  PRBool ParseFunctionInternals(const PRInt32 aVariantMask[],
+                                PRUint16 aMinElems,
+                                PRUint16 aMaxElems,
+                                nsTArray<nsCSSValue>& aOutput);
+
+  /* Functions for -moz-transform-origin Parsing */
+  PRBool ParseMozTransformOrigin();
+
+
   /* Find and return the correct namespace ID for the prefix aPrefix.
      If the prefix cannot be resolved to a namespace, this method will
      return false.  Otherwise it will return true.  When returning
      false, it may set the low-level error code, depending on the
      value of mUnresolvablePrefixException.
 
      This method never returns kNameSpaceID_Unknown or
      kNameSpaceID_None for aNameSpaceID while returning true.
@@ -1251,34 +1271,49 @@ CSSParserImpl::ExpectSymbol(PRUnichar aS
   }
   if (mToken.IsSymbol(aSymbol)) {
     return PR_TRUE;
   }
   UngetToken();
   return PR_FALSE;
 }
 
+// Checks to see if we're at the end of a property.  If an error occurs during
+// the check, does not signal a parse error.
 PRBool
-CSSParserImpl::ExpectEndProperty()
+CSSParserImpl::CheckEndProperty()
 {
   if (!GetToken(PR_TRUE)) {
     return PR_TRUE; // properties may end with eof
   }
   if ((eCSSToken_Symbol == mToken.mType) &&
-      ((';' == mToken.mSymbol) || ('!' == mToken.mSymbol) || ('}' == mToken.mSymbol))) {
+      ((';' == mToken.mSymbol) ||
+       ('!' == mToken.mSymbol) ||
+       ('}' == mToken.mSymbol))) {
     // XXX need to verify that ! is only followed by "important [;|}]
     // XXX this requires a multi-token pushback buffer
     UngetToken();
     return PR_TRUE;
   }
-  REPORT_UNEXPECTED_TOKEN(PEExpectEndValue);
   UngetToken();
   return PR_FALSE;
 }
 
+// Checks if we're at the end of a property, raising an error if we're not.
+PRBool
+CSSParserImpl::ExpectEndProperty()
+{
+  if (CheckEndProperty())
+    return PR_TRUE;
+
+  // If we're here, we read something incorrect, so we should report it.
+  REPORT_UNEXPECTED_TOKEN(PRExpectEndValue);
+  return PR_FALSE;
+}
+
 
 nsSubstring*
 CSSParserImpl::NextIdent()
 {
   // XXX Error reporting?
   if (!GetToken(PR_TRUE)) {
     return nsnull;
   }
@@ -4173,17 +4208,17 @@ CSSParserImpl::ParsePositiveVariant(nsCS
   if (ParseVariant(aValue, aVariantMask, aKeywordTable)) {
     if (eCSSUnit_Number == aValue.GetUnit() ||
         aValue.IsLengthUnit()){
       if (aValue.GetFloatValue() < 0) {
         UngetToken();
         return PR_FALSE;
       }
     }
-    else if(aValue.GetUnit() == eCSSUnit_Percent) {
+    else if (aValue.GetUnit() == eCSSUnit_Percent) {
       if (aValue.GetPercentValue() < 0) {
         UngetToken();
         return PR_FALSE;
       }
     } else if (aValue.GetUnit() == eCSSUnit_Integer) {
       if (aValue.GetIntValue() < 0) {
         UngetToken();
         return PR_FALSE;
@@ -4781,16 +4816,18 @@ CSSParserImpl::ParseProperty(nsCSSProper
                                        NS_BOXPROP_SOURCE_PHYSICAL);
   case eCSSProperty_border_start_style:
     return ParseDirectionalBoxProperty(eCSSProperty_border_start_style,
                                        NS_BOXPROP_SOURCE_LOGICAL);
   case eCSSProperty__moz_border_radius:
     return ParseBorderRadius();
   case eCSSProperty__moz_outline_radius:
     return ParseOutlineRadius();
+  case eCSSProperty_box_shadow:
+    return ParseBoxShadow();
   case eCSSProperty_clip:
     return ParseRect(mTempData.mDisplay.mClip, eCSSProperty_clip);
   case eCSSProperty__moz_column_rule:
     return ParseBorderSide(kColumnRuleIDs, PR_FALSE);
   case eCSSProperty_content:
     return ParseContent();
   case eCSSProperty_counter_increment:
     return ParseCounterData(&mTempData.mContent.mCounterIncrement,
@@ -4844,18 +4881,20 @@ CSSParserImpl::ParseProperty(nsCSSProper
   case eCSSProperty_pause:
     return ParsePause();
   case eCSSProperty_quotes:
     return ParseQuotes();
   case eCSSProperty_size:
     return ParseSize();
   case eCSSProperty_text_shadow:
     return ParseTextShadow();
-  case eCSSProperty_box_shadow:
-    return ParseBoxShadow();
+  case eCSSProperty__moz_transform:
+    return ParseMozTransform();
+  case eCSSProperty__moz_transform_origin:
+    return ParseMozTransformOrigin();
 
 #ifdef MOZ_SVG
   case eCSSProperty_fill:
     return ParsePaint(&mTempData.mSVG.mFill, eCSSProperty_fill);
   case eCSSProperty_stroke:
     return ParsePaint(&mTempData.mSVG.mStroke, eCSSProperty_stroke);
   case eCSSProperty_stroke_dasharray:
     return ParseDasharray();
@@ -4903,17 +4942,16 @@ CSSParserImpl::ParseProperty(nsCSSProper
   case eCSSProperty_border_start_width_value:
   case eCSSProperty_border_left_width_ltr_source:
   case eCSSProperty_border_left_width_rtl_source:
   case eCSSProperty_border_right_width_ltr_source:
   case eCSSProperty_border_right_width_rtl_source:
     // The user can't use these
     REPORT_UNEXPECTED(PEInaccessibleProperty2);
     return PR_FALSE;
-
   default:  // must be single property
     {
       nsCSSValue value;
       if (ParseSingleValueProperty(value, aPropID)) {
         if (ExpectEndProperty()) {
           AppendValue(aPropID, value);
           return PR_TRUE;
         }
@@ -4994,16 +5032,18 @@ CSSParserImpl::ParseSingleValueProperty(
   case eCSSProperty_padding_end:
   case eCSSProperty_padding_left:
   case eCSSProperty_padding_right:
   case eCSSProperty_padding_start:
   case eCSSProperty_pause:
   case eCSSProperty_quotes:
   case eCSSProperty_size:
   case eCSSProperty_text_shadow:
+  case eCSSProperty__moz_transform:
+  case eCSSProperty__moz_transform_origin:
   case eCSSProperty_COUNT:
 #ifdef MOZ_SVG
   case eCSSProperty_fill:
   case eCSSProperty_stroke:
   case eCSSProperty_stroke_dasharray:
   case eCSSProperty_marker:
 #endif
     NS_ERROR("not a single value property");
@@ -5560,17 +5600,17 @@ CSSParserImpl::ParseAzimuth(nsCSSValue& 
       }
     }
     return PR_TRUE;
   }
   return PR_FALSE;
 }
 
 static nsCSSValue
-BackgroundPositionMaskToCSSValue(PRInt32 aMask, PRBool isX)
+BoxPositionMaskToCSSValue(PRInt32 aMask, PRBool isX)
 {
   PRInt32 val = NS_STYLE_BG_POSITION_CENTER;
   if (isX) {
     if (aMask & BG_LEFT) {
       val = NS_STYLE_BG_POSITION_LEFT;
     }
     else if (aMask & BG_RIGHT) {
       val = NS_STYLE_BG_POSITION_RIGHT;
@@ -5739,29 +5779,47 @@ CSSParserImpl::ParseBackground()
 
   return ExpectEndProperty() &&
          (haveColor || haveImage || haveRepeat || haveAttach || havePosition);
 }
 
 PRBool
 CSSParserImpl::ParseBackgroundPosition()
 {
-  if (!ParseBackgroundPositionValues() ||
-      !ExpectEndProperty())
+  if (!ParseBoxPosition(mTempData.mColor.mBackPosition))
     return PR_FALSE;
   mTempData.SetPropertyBit(eCSSProperty_background_position);
   return PR_TRUE;
 }
 
 PRBool
 CSSParserImpl::ParseBackgroundPositionValues()
 {
+  return ParseBoxPositionValues(mTempData.mColor.mBackPosition);
+}
+
+/**
+ * Parses two values that correspond to positions in a box.  These can be
+ * values corresponding to percentages of the box, raw offsets, or keywords
+ * like "top," "left center," etc.
+ *
+ * @param aOut The nsCSSValuePair where to place the result.
+ * @return Whether or not the operation succeeded.
+ */
+PRBool CSSParserImpl::ParseBoxPosition(nsCSSValuePair &aOut)
+{
+  // Need to read the box positions and the end of the property.
+  return ParseBoxPositionValues(aOut) && ExpectEndProperty();
+}
+
+PRBool CSSParserImpl::ParseBoxPositionValues(nsCSSValuePair &aOut)
+{
   // First try a percentage or a length value
-  nsCSSValue &xValue = mTempData.mColor.mBackPosition.mXValue,
-             &yValue = mTempData.mColor.mBackPosition.mYValue;
+  nsCSSValue &xValue = aOut.mXValue,
+             &yValue = aOut.mYValue;
   if (ParseVariant(xValue, VARIANT_HLP, nsnull)) {
     if (eCSSUnit_Inherit == xValue.GetUnit() ||
         eCSSUnit_Initial == xValue.GetUnit()) {  // both are inherited or both are set to initial
       yValue = xValue;
       return PR_TRUE;
     }
     // We have one percentage/length. Get the optional second
     // percentage/length/keyword.
@@ -5771,17 +5829,17 @@ CSSParserImpl::ParseBackgroundPositionVa
     }
 
     if (ParseEnum(yValue, nsCSSProps::kBackgroundPositionKTable)) {
       PRInt32 yVal = yValue.GetIntValue();
       if (!(yVal & BG_CTB)) {
         // The second keyword can only be 'center', 'top', or 'bottom'
         return PR_FALSE;
       }
-      yValue = BackgroundPositionMaskToCSSValue(yVal, PR_FALSE);
+      yValue = BoxPositionMaskToCSSValue(yVal, PR_FALSE);
       return PR_TRUE;
     }
 
     // If only one percentage or length value is given, it sets the
     // horizontal position only, and the vertical position will be 50%.
     yValue.SetPercentValue(0.5f);
     return PR_TRUE;
   }
@@ -5807,32 +5865,32 @@ CSSParserImpl::ParseBackgroundPositionVa
     else {
       // Only one keyword.  See if we have a length or percentage.
       if (ParseVariant(yValue, VARIANT_LP, nsnull)) {
         if (!(mask & BG_CLR)) {
           // The first keyword can only be 'center', 'left', or 'right'
           return PR_FALSE;
         }
 
-        xValue = BackgroundPositionMaskToCSSValue(mask, PR_TRUE);
+        xValue = BoxPositionMaskToCSSValue(mask, PR_TRUE);
         return PR_TRUE;
       }
     }
   }
 
   // Check for bad input. Bad input consists of no matching keywords,
   // or pairs of x keywords or pairs of y keywords.
   if ((mask == 0) || (mask == (BG_TOP | BG_BOTTOM)) ||
       (mask == (BG_LEFT | BG_RIGHT))) {
     return PR_FALSE;
   }
 
   // Create style values
-  xValue = BackgroundPositionMaskToCSSValue(mask, PR_TRUE);
-  yValue = BackgroundPositionMaskToCSSValue(mask, PR_FALSE);
+  xValue = BoxPositionMaskToCSSValue(mask, PR_TRUE);
+  yValue = BoxPositionMaskToCSSValue(mask, PR_FALSE);
   return PR_TRUE;
 }
 
 // These must be in CSS order (top,right,bottom,left) for indexing to work
 static const nsCSSProperty kBorderStyleIDs[] = {
   eCSSProperty_border_top_style,
   eCSSProperty_border_right_style_value,
   eCSSProperty_border_bottom_style,
@@ -6318,17 +6376,17 @@ CSSParserImpl::ParseCounterData(nsCSSVal
     { "none", eCSSUnit_None },
     { "inherit", eCSSUnit_Inherit },
     { "-moz-initial", eCSSUnit_Initial }
   };
   for (const SingleCounterPropValue *sv = singleValues,
            *sv_end = singleValues + NS_ARRAY_LENGTH(singleValues);
        sv != sv_end; ++sv) {
     if (ident->LowerCaseEqualsASCII(sv->str)) {
-      if (ExpectEndProperty()) {
+      if (CheckEndProperty()) {
         nsCSSValuePairList* dataHead = new nsCSSValuePairList();
         if (!dataHead) {
           mScanner.SetLowLevelError(NS_ERROR_OUT_OF_MEMORY);
           return PR_FALSE;
         }
         dataHead->mXValue = nsCSSValue(sv->unit);
         *aResult = dataHead;
         mTempData.SetPropertyBit(aPropID);
@@ -6609,16 +6667,383 @@ CSSParserImpl::ParseOneFamily(nsAString&
     return PR_TRUE;
 
   } else {
     UngetToken();
     return PR_FALSE;
   }
 }
 
+///////////////////////////////////////////////////////
+// -moz-transform Parsing Implementation
+
+/* Reads a function list of arguments.  Do not call this function
+ * directly; it's mean to be caled from ParseFunction.
+ */
+PRBool
+CSSParserImpl::ParseFunctionInternals(const PRInt32 aVariantMask[],
+                                      PRUint16 aMinElems,
+                                      PRUint16 aMaxElems,
+                                      nsTArray<nsCSSValue> &aOutput)
+{
+  for (PRUint16 index = 0; index < aMaxElems; ++index) {
+    nsCSSValue newValue;
+    if (!ParseVariant(newValue, aVariantMask[index], nsnull))
+      return PR_FALSE;
+
+    if (!aOutput.AppendElement(newValue)) {
+      mScanner.SetLowLevelError(NS_ERROR_OUT_OF_MEMORY);
+      return PR_FALSE;
+    }
+    
+    // See whether to continue or whether to look for end of function.
+    if (!ExpectSymbol(',', PR_TRUE)) {
+      // We need to read the closing parenthesis, and also must take care
+      // that we haven't read too few symbols.
+      return ExpectSymbol(')', PR_TRUE) && (index + 1) >= aMinElems;
+    }
+  }
+
+  // If we're here, we finished looping without hitting the end, so we read too
+  // many elements.
+  return PR_FALSE;
+}
+
+/* Parses a function [ input of the form (a [, b]*) ] and stores it
+ * as an nsCSSValue that holds a function of the form
+ * function-name arg1 arg2 ... argN
+ *
+ * On error, the return value is PR_FALSE.
+ *
+ * @param aFunction The name of the function that we're reading.
+ * @param aAllowedTypes An array of values corresponding to the legal
+ *        types for each element in the function.  The zeroth element in the
+ *        array corresponds to the first function parameter, etc.  The length
+ *        of this array _must_ be greater than or equal to aMaxElems or the
+ *        behavior is undefined.
+ * @param aMinElems Minimum number of elements to read.  Reading fewer than
+ *        this many elements will result in the function failing.
+ * @param aMaxElems Maximum number of elements to read.  Reading more than
+ *        this many elements will result in the function failing.
+ * @param aValue (out) The value that was parsed.
+ */
+PRBool
+CSSParserImpl::ParseFunction(const nsString &aFunction,
+                             const PRInt32 aAllowedTypes[],
+                             PRUint16 aMinElems, PRUint16 aMaxElems,
+                             nsCSSValue &aValue)
+{
+  typedef nsTArray<nsCSSValue>::size_type arrlen_t;
+
+  /* 2^16 - 2, so that if we have 2^16 - 2 transforms, we have 2^16 - 1
+   * elements stored in the the nsCSSValue::Array.
+   */
+  static const arrlen_t MAX_ALLOWED_ELEMS = 0xFFFE;
+
+  /* Make a copy of the function name, since the reference is _probably_ to
+   * mToken.mIdent, which is going to get overwritten during the course of this
+   * function.
+   */
+  nsString functionName(aFunction);
+
+  /* First things first... read the parenthesis.  If it fails, abort. */
+  if (!ExpectSymbol('(', PR_TRUE))
+    return PR_FALSE;
+  
+  /* Read in a list of values as an nsTArray, failing if we can't or if
+   * it's out of bounds.
+   */
+  nsTArray<nsCSSValue> foundValues;
+  if (!ParseFunctionInternals(aAllowedTypes, aMinElems, aMaxElems,
+                              foundValues))
+    return PR_FALSE;
+  
+  /* Now, convert this nsTArray into an nsCSSValue::Array object.
+   * We'll need N + 1 spots, one for the function name and the rest for the
+   * arguments.  In case the user has given us more than 2^16 - 2 arguments,
+   * we'll truncate them at 2^16 - 2 arguments.
+   */
+  PRUint16 numElements = (foundValues.Length() <= MAX_ALLOWED_ELEMS ?
+                          foundValues.Length() + 1 : MAX_ALLOWED_ELEMS);
+  nsRefPtr<nsCSSValue::Array> convertedArray =
+	  nsCSSValue::Array::Create(numElements);
+  if (!convertedArray) {
+    mScanner.SetLowLevelError(NS_ERROR_OUT_OF_MEMORY);
+    return PR_FALSE;
+  }
+  
+  /* Copy things over. */
+  convertedArray->Item(0).SetStringValue(functionName, eCSSUnit_String);
+  for (PRUint16 index = 0; index + 1 < numElements; ++index)
+    convertedArray->Item(index + 1) = foundValues[static_cast<arrlen_t>(index)];
+  
+  /* Fill in the outparam value with the array. */
+  aValue.SetArrayValue(convertedArray, eCSSUnit_Function);
+  
+  /* Return it! */
+  return PR_TRUE;
+}
+
+/**
+ * Given a token, determines the minimum and maximum number of function
+ * parameters to read, along with the mask that should be used to read
+ * those function parameters.  If the token isn't a transform function,
+ * returns an error.
+ *
+ * @param aToken The token identifying the function.
+ * @param aMinElems [out] The minimum number of elements to read.
+ * @param aMaxElems [out] The maximum number of elements to read
+ * @param aVariantMask [out] The variant mask to use during parsing
+ * @return Whether the information was loaded successfully.
+ */
+static PRBool GetFunctionParseInformation(nsCSSKeyword aToken,
+                                          PRUint16 &aMinElems,
+                                          PRUint16 &aMaxElems,
+                                          const PRInt32 *& aVariantMask)
+{
+/* These types represent the common variant masks that will be used to
+   * parse out the individual functions.  The order in the enumeration
+   * must match the order in which the masks are declared.
+   */
+  enum { eLengthPercent,
+         eTwoLengthPercents,
+         eAngle,
+         eNumber,
+         eTwoNumbers,
+         eMatrix,
+         eNumVariantMasks };
+  static const PRInt32 kMaxElemsPerFunction = 6;
+  static const PRInt32 kVariantMasks[eNumVariantMasks][kMaxElemsPerFunction] = {
+    {VARIANT_LENGTH | VARIANT_PERCENT},
+    {VARIANT_LENGTH | VARIANT_PERCENT, VARIANT_LENGTH | VARIANT_PERCENT},
+    {VARIANT_ANGLE},
+    {VARIANT_NUMBER},
+    {VARIANT_NUMBER, VARIANT_NUMBER},
+    {VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER, VARIANT_NUMBER,
+     VARIANT_LENGTH | VARIANT_PERCENT, VARIANT_LENGTH | VARIANT_PERCENT}};
+
+#ifdef DEBUG
+  static const PRUint8 kVariantMaskLengths[eNumVariantMasks] =
+    {1, 2, 1, 1, 2, 6};
+#endif
+
+  PRInt32 variantIndex = eNumVariantMasks;
+
+  switch (aToken) {
+  case eCSSKeyword_translatex:
+    /* Exactly one length or percent. */
+    variantIndex = eLengthPercent;
+    aMinElems = 1U;
+    aMaxElems = 1U;
+    break;
+  case eCSSKeyword_translatey:
+    /* Exactly one length or percent. */
+    variantIndex = eLengthPercent;
+    aMinElems = 1U;
+    aMaxElems = 1U;
+    break;
+  case eCSSKeyword_scalex:
+    /* Exactly one scale factor. */
+    variantIndex = eNumber;
+    aMinElems = 1U;
+    aMaxElems = 1U;
+    break;
+  case eCSSKeyword_scaley:
+    /* Exactly one scale factor. */
+    variantIndex = eNumber;
+    aMinElems = 1U;
+    aMaxElems = 1U;
+    break;
+  case eCSSKeyword_rotate:
+    /* Exactly one angle. */
+    variantIndex = eAngle;
+    aMinElems = 1U;
+    aMaxElems = 1U;
+    break;
+  case eCSSKeyword_translate:
+    /* One or two lengths or percents. */
+    variantIndex = eTwoLengthPercents;
+    aMinElems = 1U;
+    aMaxElems = 2U;
+    break;
+  case eCSSKeyword_skew:
+    /* Exactly one angle. */
+    variantIndex = eAngle;
+    aMinElems = 1U;
+    aMaxElems = 1U;
+    break;
+  case eCSSKeyword_scale:
+    /* One or two scale factors. */
+    variantIndex = eTwoNumbers;
+    aMinElems = 1U;
+    aMaxElems = 2U;
+    break;
+  case eCSSKeyword_skewx:
+    /* Exactly one angle. */
+    variantIndex = eAngle;
+    aMinElems = 1U;
+    aMaxElems = 1U;
+    break;
+  case eCSSKeyword_skewy:
+    /* Exactly one angle. */
+    variantIndex = eAngle;
+    aMinElems = 1U;
+    aMaxElems = 1U;
+    break;
+  case eCSSKeyword_matrix:
+    /* Six values, which can be numbers, lengths, or percents. */
+    variantIndex = eMatrix;
+    aMinElems = 6U;
+    aMaxElems = 6U;
+    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!");
+  NS_ASSERTION(variantIndex >= 0, "Invalid variant mask!");
+  NS_ASSERTION(variantIndex < eNumVariantMasks, "Invalid variant mask!");
+#ifdef DEBUG
+  NS_ASSERTION(aMaxElems <= kVariantMaskLengths[variantIndex],
+               "Invalid aMaxElems for this variant mask.");
+#endif
+
+  // Convert the index into a mask.
+  aVariantMask = kVariantMasks[variantIndex];
+
+  return PR_TRUE;
+}
+                                          
+
+/* Reads a single transform function from the tokenizer stream, reporting an
+ * error if something goes wrong.
+ */
+PRBool CSSParserImpl::ReadSingleTransform(nsCSSValueList **& aTail)
+{
+  typedef nsTArray<nsCSSValue>::size_type arrlen_t;
+	
+  if (!GetToken(PR_TRUE))
+    return PR_FALSE;
+  
+  /* Check to make sure that we've read a function. */
+  if (mToken.mType != eCSSToken_Function) {
+    UngetToken();
+    return PR_FALSE;
+  }
+
+  /* Load up the variant mask information for ParseFunction.  If we can't,
+   * abort.
+   */
+  const PRInt32* variantMask;
+  PRUint16 minElems, maxElems;
+  if (!GetFunctionParseInformation(nsCSSKeywords::LookupKeyword(mToken.mIdent),
+                                   minElems, maxElems, variantMask))
+    return PR_FALSE;
+
+  /* Create a cell to populate, fail if we're out of memory. */
+  nsAutoPtr<nsCSSValue> newCell(new nsCSSValue);
+  if (!newCell) {
+    mScanner.SetLowLevelError(NS_ERROR_OUT_OF_MEMORY);
+    return PR_FALSE;
+  }
+
+  /* Try reading things in, failing if we can't */
+  if (!ParseFunction(mToken.mIdent, variantMask, minElems, maxElems, *newCell))
+    return PR_FALSE;
+
+  /* Wrap up our result in an nsCSSValueList cell. */
+  nsAutoPtr<nsCSSValueList> toAppend(new nsCSSValueList);
+  if (!toAppend)
+    return PR_FALSE;
+
+  toAppend->mValue = *newCell;
+  
+  /* Chain the element to the end of the transform list, then update the
+   * list.
+   */
+  *aTail = toAppend.forget();
+  aTail = &(*aTail)->mNext;
+  
+  /* It worked!  Return true. */
+  return PR_TRUE;
+}
+
+/* Parses a -moz-transform property list by continuously reading in properties
+ * and constructing a matrix from it.
+ */
+PRBool CSSParserImpl::ParseMozTransform()
+{
+  mTempData.mDisplay.mTransform = nsnull;
+ 
+  /* First, check to see if this is some sort of keyword - none, inherit,
+   * or initial.
+   */
+  nsCSSValue keywordValue;
+  if (ParseVariant(keywordValue, VARIANT_INHERIT | VARIANT_NONE, nsnull)) {
+    /* Looks like it is.  Make a new value list and fill it in, failing if
+     * we can't.
+     */
+    mTempData.mDisplay.mTransform = new nsCSSValueList;
+    if (!mTempData.mDisplay.mTransform) {
+      mScanner.SetLowLevelError(NS_ERROR_OUT_OF_MEMORY);
+      return PR_FALSE;
+    }
+
+    /* Inform the parser that everything worked. */
+    mTempData.mDisplay.mTransform->mValue = keywordValue;
+    mTempData.SetPropertyBit(eCSSProperty__moz_transform);
+    return PR_TRUE;
+  }
+  
+  /* We will read a nonempty list of transforms.  Thus we'll read in a
+   * transform, then continuously read transforms until we're no longer
+   * able to do so.
+   */
+  nsCSSValueList *transformList = nsnull;
+  nsCSSValueList **tail = &transformList;
+  do {
+    /* Try reading a transform.  If we fail to do so, abort. */
+    if (!ReadSingleTransform(tail)) {
+      delete transformList;
+      return PR_FALSE;
+    }
+  }
+  while (!CheckEndProperty());
+
+  /* Confirm that this is the end of the property and set error code
+   * appropriately otherwise.
+   */
+  if (!ExpectEndProperty()) {
+    delete transformList;
+    return PR_FALSE;
+  }
+  
+  /* Validate our data. */
+  NS_ASSERTION(transformList, "Didn't read any transforms!");
+  
+  mTempData.SetPropertyBit(eCSSProperty__moz_transform);
+  mTempData.mDisplay.mTransform = transformList;
+
+  return PR_TRUE;
+}
+
+PRBool CSSParserImpl::ParseMozTransformOrigin()
+{
+  /* Read in a box position, fail if we can't. */
+  if (!ParseBoxPosition(mTempData.mDisplay.mTransformOrigin))
+    return PR_FALSE;
+
+  /* Set the property bit and return. */
+  mTempData.SetPropertyBit(eCSSProperty__moz_transform_origin);
+  return PR_TRUE;
+}
+
 PRBool
 CSSParserImpl::ParseFamily(nsCSSValue& aValue)
 {
   if (!GetToken(PR_TRUE))
     return PR_FALSE;
 
   if (eCSSToken_Ident == mToken.mType) {
     nsCSSKeyword keyword = nsCSSKeywords::LookupKeyword(mToken.mIdent);
@@ -6818,17 +7243,17 @@ CSSParserImpl::ParseMargin()
                             kMarginSideIDs);
 }
 
 PRBool
 CSSParserImpl::ParseMarks(nsCSSValue& aValue)
 {
   if (ParseVariant(aValue, VARIANT_HOK, nsCSSProps::kPageMarksKTable)) {
     if (eCSSUnit_Enumerated == aValue.GetUnit()) {
-      if (PR_FALSE == ExpectEndProperty()) {
+      if (PR_FALSE == CheckEndProperty()) {
         nsCSSValue  second;
         if (ParseEnum(second, nsCSSProps::kPageMarksKTable)) {
           aValue.SetIntValue(aValue.GetIntValue() | second.GetIntValue(), eCSSUnit_Enumerated);
           return PR_TRUE;
         }
         return PR_FALSE;
       }
     }
@@ -6962,17 +7387,17 @@ CSSParserImpl::ParseQuotes()
         mScanner.SetLowLevelError(NS_ERROR_OUT_OF_MEMORY);
         return PR_FALSE;
       }
       quotes->mXValue = open;
       while (nsnull != quotes) {
         // get mandatory close
         if (ParseVariant(quotes->mYValue, VARIANT_STRING,
                          nsnull)) {
-          if (ExpectEndProperty()) {
+          if (CheckEndProperty()) {
             mTempData.SetPropertyBit(eCSSProperty_quotes);
             mTempData.mContent.mQuotes = quotesHead;
             return PR_TRUE;
           }
           // look for another open
           if (ParseVariant(open, VARIANT_STRING, nsnull)) {
             quotes->mNext = new nsCSSValuePairList();
             quotes = quotes->mNext;
@@ -7258,17 +7683,17 @@ CSSParserImpl::ParseDasharray()
     if (!list) {
       mScanner.SetLowLevelError(NS_ERROR_OUT_OF_MEMORY);
       return PR_FALSE;
     }
 
     list->mValue = value;
 
     for (;;) {
-      if (ExpectEndProperty()) {
+      if (CheckEndProperty()) {
         mTempData.SetPropertyBit(eCSSProperty_stroke_dasharray);
         mTempData.mSVG.mStrokeDasharray = listHead;
         return PR_TRUE;
       }
 
       if (eCSSUnit_Inherit == value.GetUnit() ||
           eCSSUnit_Initial == value.GetUnit() ||
           eCSSUnit_None    == value.GetUnit())
--- a/layout/style/nsCSSPropList.h
+++ b/layout/style/nsCSSPropList.h
@@ -495,16 +495,18 @@ CSS_PROP_BACKENDONLY(speak-punctuation, 
 CSS_PROP_BACKENDONLY(speech-rate, speech_rate, SpeechRate, Aural, mSpeechRate, eCSSType_Value, kSpeechRateKTable)
 CSS_PROP_BACKENDONLY(stress, stress, Stress, Aural, mStress, eCSSType_Value, nsnull)
 CSS_PROP_TABLE(table-layout, table_layout, TableLayout, Table, mLayout, eCSSType_Value, kTableLayoutKTable)
 CSS_PROP_TEXT(text-align, text_align, TextAlign, Text, mTextAlign, eCSSType_Value, kTextAlignKTable)
 CSS_PROP_TEXTRESET(text-decoration, text_decoration, TextDecoration, Text, mDecoration, eCSSType_Value, kTextDecorationKTable)
 CSS_PROP_TEXT(text-indent, text_indent, TextIndent, Text, mTextIndent, eCSSType_Value, nsnull)
 CSS_PROP_TEXT(text-shadow, text_shadow, TextShadow, Text, mTextShadow, eCSSType_ValueList, nsnull)
 CSS_PROP_TEXT(text-transform, text_transform, TextTransform, Text, mTextTransform, eCSSType_Value, kTextTransformKTable)
+CSS_PROP_DISPLAY(-moz-transform, _moz_transform, MozTransform, Display, mTransform, eCSSType_ValueList, kDisplayKTable)
+CSS_PROP_DISPLAY(-moz-transform-origin, _moz_transform_origin, MozTransformOrigin, Display, mTransformOrigin, eCSSType_ValuePair, kBackgroundPositionKTable)
 CSS_PROP_POSITION(top, top, Top, Position, mOffset.mTop, eCSSType_Value, nsnull)
 CSS_PROP_TEXTRESET(unicode-bidi, unicode_bidi, UnicodeBidi, Text, mUnicodeBidi, eCSSType_Value, kUnicodeBidiKTable)
 CSS_PROP_USERINTERFACE(-moz-user-focus, user_focus, MozUserFocus, UserInterface, mUserFocus, eCSSType_Value, kUserFocusKTable) // XXX bug 3935
 CSS_PROP_USERINTERFACE(-moz-user-input, user_input, MozUserInput, UserInterface, mUserInput, eCSSType_Value, kUserInputKTable) // XXX ??? // XXX bug 3935
 CSS_PROP_USERINTERFACE(-moz-user-modify, user_modify, MozUserModify, UserInterface, mUserModify, eCSSType_Value, kUserModifyKTable) // XXX bug 3935
 CSS_PROP_UIRESET(-moz-user-select, user_select, MozUserSelect, UserInterface, mUserSelect, eCSSType_Value, kUserSelectKTable) // XXX bug 3935
 CSS_PROP_TEXTRESET(vertical-align, vertical_align, VerticalAlign, Text, mVerticalAlign, eCSSType_Value, kVerticalAlignKTable)
 CSS_PROP_VISIBILITY(visibility, visibility, Visibility, Display, mVisibility, eCSSType_Value, kVisibilityKTable)  // reflow for collapse
--- a/layout/style/nsCSSStruct.cpp
+++ b/layout/style/nsCSSStruct.cpp
@@ -208,17 +208,18 @@ nsCSSValueListRect::sides[4] = {
   &nsCSSValueListRect::mTop,
   &nsCSSValueListRect::mRight,
   &nsCSSValueListRect::mBottom,
   &nsCSSValueListRect::mLeft,
 };
 
 // --- nsCSSDisplay -----------------
 
-nsCSSDisplay::nsCSSDisplay(void)
+/* During allocation, null-out the transform list. */
+nsCSSDisplay::nsCSSDisplay(void) : mTransform(nsnull)
 {
   MOZ_COUNT_CTOR(nsCSSDisplay);
 }
 
 nsCSSDisplay::~nsCSSDisplay(void)
 {
   MOZ_COUNT_DTOR(nsCSSDisplay);
 }
--- a/layout/style/nsCSSStruct.h
+++ b/layout/style/nsCSSStruct.h
@@ -274,16 +274,18 @@ struct nsCSSDisplay : public nsCSSStruct
   nsCSSValue mPosition;
   nsCSSValue mFloat;
   nsCSSValue mClear;
   nsCSSRect  mClip;
   nsCSSValue mOverflowX;
   nsCSSValue mOverflowY;
   nsCSSValue mVisibility;
   nsCSSValue mOpacity;
+  nsCSSValueList *mTransform; // List of Arrays containing transform information
+  nsCSSValuePair mTransformOrigin;
 
   // temp fix for bug 24000 
   nsCSSValue mBreakBefore;
   nsCSSValue mBreakAfter;
   // end temp fix
 private:
   nsCSSDisplay(const nsCSSDisplay& aOther); // NOT IMPLEMENTED
 };
--- a/layout/style/nsCSSValue.cpp
+++ b/layout/style/nsCSSValue.cpp
@@ -99,17 +99,17 @@ nsCSSValue::nsCSSValue(nscolor aValue)
   : mUnit(eCSSUnit_Color)
 {
   mValue.mColor = aValue;
 }
 
 nsCSSValue::nsCSSValue(nsCSSValue::Array* aValue, nsCSSUnit aUnit)
   : mUnit(aUnit)
 {
-  NS_ASSERTION(eCSSUnit_Array <= aUnit && aUnit <= eCSSUnit_Counters,
+  NS_ASSERTION(eCSSUnit_Array <= aUnit && aUnit <= eCSSUnit_Function,
                "bad unit");
   mValue.mArray = aValue;
   mValue.mArray->AddRef();
 }
 
 nsCSSValue::nsCSSValue(nsCSSValue::URL* aValue)
   : mUnit(eCSSUnit_URL)
 {
@@ -138,17 +138,17 @@ nsCSSValue::nsCSSValue(const nsCSSValue&
     mValue.mString->AddRef();
   }
   else if (eCSSUnit_Integer <= mUnit && mUnit <= eCSSUnit_EnumColor) {
     mValue.mInt = aCopy.mValue.mInt;
   }
   else if (eCSSUnit_Color == mUnit) {
     mValue.mColor = aCopy.mValue.mColor;
   }
-  else if (eCSSUnit_Array <= mUnit && mUnit <= eCSSUnit_Counters) {
+  else if (eCSSUnit_Array <= mUnit && mUnit <= eCSSUnit_Function) {
     mValue.mArray = aCopy.mValue.mArray;
     mValue.mArray->AddRef();
   }
   else if (eCSSUnit_URL == mUnit) {
     mValue.mURL = aCopy.mValue.mURL;
     mValue.mURL->AddRef();
   }
   else if (eCSSUnit_Image == mUnit) {
@@ -180,17 +180,17 @@ PRBool nsCSSValue::operator==(const nsCS
                         GetBufferValue(aOther.mValue.mString)) == 0);
     }
     else if ((eCSSUnit_Integer <= mUnit) && (mUnit <= eCSSUnit_EnumColor)) {
       return mValue.mInt == aOther.mValue.mInt;
     }
     else if (eCSSUnit_Color == mUnit) {
       return mValue.mColor == aOther.mValue.mColor;
     }
-    else if (eCSSUnit_Array <= mUnit && mUnit <= eCSSUnit_Counters) {
+    else if (eCSSUnit_Array <= mUnit && mUnit <= eCSSUnit_Function) {
       return *mValue.mArray == *aOther.mValue.mArray;
     }
     else if (eCSSUnit_URL == mUnit) {
       return *mValue.mURL == *aOther.mValue.mURL;
     }
     else if (eCSSUnit_Image == mUnit) {
       return *mValue.mImage == *aOther.mValue.mImage;
     }
@@ -244,17 +244,17 @@ nscoord nsCSSValue::GetLengthTwips() con
   }
   return 0;
 }
 
 void nsCSSValue::DoReset()
 {
   if (UnitHasStringValue()) {
     mValue.mString->Release();
-  } else if (eCSSUnit_Array <= mUnit && mUnit <= eCSSUnit_Counters) {
+  } else if (eCSSUnit_Array <= mUnit && mUnit <= eCSSUnit_Function) {
     mValue.mArray->Release();
   } else if (eCSSUnit_URL == mUnit) {
     mValue.mURL->Release();
   } else if (eCSSUnit_Image == mUnit) {
     mValue.mImage->Release();
   }
   mUnit = eCSSUnit_Null;
 }
@@ -309,17 +309,17 @@ void nsCSSValue::SetColorValue(nscolor a
 {
   Reset();
   mUnit = eCSSUnit_Color;
   mValue.mColor = aValue;
 }
 
 void nsCSSValue::SetArrayValue(nsCSSValue::Array* aValue, nsCSSUnit aUnit)
 {
-  NS_ASSERTION(eCSSUnit_Array <= aUnit && aUnit <= eCSSUnit_Counters,
+  NS_ASSERTION(eCSSUnit_Array <= aUnit && aUnit <= eCSSUnit_Function,
                "bad unit");
   Reset();
   mUnit = aUnit;
   mValue.mArray = aValue;
   mValue.mArray->AddRef();
 }
 
 void nsCSSValue::SetURLValue(nsCSSValue::URL* aValue)
--- a/layout/style/nsCSSValue.h
+++ b/layout/style/nsCSSValue.h
@@ -66,16 +66,19 @@ enum nsCSSUnit {
                                   //       only in temporary values
   eCSSUnit_String       = 10,     // (PRUnichar*) a string value
   eCSSUnit_Attr         = 11,     // (PRUnichar*) a attr(string) value
   eCSSUnit_Local_Font   = 12,     // (PRUnichar*) a local font name
   eCSSUnit_Font_Format  = 13,     // (PRUnichar*) a font format name
   eCSSUnit_Array        = 20,     // (nsCSSValue::Array*) a list of values
   eCSSUnit_Counter      = 21,     // (nsCSSValue::Array*) a counter(string,[string]) value
   eCSSUnit_Counters     = 22,     // (nsCSSValue::Array*) a counters(string,string[,string]) value
+  eCSSUnit_Function     = 23,     // (nsCSSValue::Array*) a function with parameters.  First elem of array is name,
+                                  //  the rest of the values are arguments.
+
   eCSSUnit_URL          = 30,     // (nsCSSValue::URL*) value
   eCSSUnit_Image        = 31,     // (nsCSSValue::Image*) value
   eCSSUnit_Integer      = 50,     // (int) simple value
   eCSSUnit_Enumerated   = 51,     // (int) value has enumerated meaning
   eCSSUnit_EnumColor    = 80,     // (int) enumerated color (kColorKTable)
   eCSSUnit_Color        = 81,     // (nscolor) an RGBA value
   eCSSUnit_Percent      = 90,     // (float) 1.0 == 100%) value is percentage of something
   eCSSUnit_Number       = 91,     // (float) value is numeric (usually multiplier, different behavior that percent)
@@ -216,17 +219,17 @@ public:
   nscolor GetColorValue() const
   {
     NS_ASSERTION((mUnit == eCSSUnit_Color), "not a color value");
     return mValue.mColor;
   }
 
   Array* GetArrayValue() const
   {
-    NS_ASSERTION(eCSSUnit_Array <= mUnit && mUnit <= eCSSUnit_Counters,
+    NS_ASSERTION(eCSSUnit_Array <= mUnit && mUnit <= eCSSUnit_Function,
                  "not an array value");
     return mValue.mArray;
   }
 
   nsIURI* GetURLValue() const
   {
     NS_ASSERTION(mUnit == eCSSUnit_URL || mUnit == eCSSUnit_Image,
                  "not a URL value");
--- a/layout/style/nsComputedDOMStyle.cpp
+++ b/layout/style/nsComputedDOMStyle.cpp
@@ -44,16 +44,17 @@
 /* DOM object returned from element.getComputedStyle() */
 
 #include "nsComputedDOMStyle.h"
 
 #include "nsDOMError.h"
 #include "nsDOMString.h"
 #include "nsIDOMCSS2Properties.h"
 #include "nsIDOMElement.h"
+#include "nsIDOMCSSPrimitiveValue.h"
 #include "nsStyleContext.h"
 #include "nsIScrollableFrame.h"
 #include "nsContentUtils.h"
 #include "prprf.h"
 
 #include "nsCSSProps.h"
 #include "nsCSSKeywords.h"
 #include "nsDOMCSSRect.h"
@@ -67,16 +68,19 @@
 #include "nsIDocument.h"
 
 #include "nsCSSPseudoElements.h"
 #include "nsStyleSet.h"
 #include "imgIRequest.h"
 #include "nsInspectorCSSUtils.h"
 #include "nsLayoutUtils.h"
 #include "nsFrameManager.h"
+#include "nsCSSKeywords.h"
+#include "nsStyleCoord.h"
+#include "nsDisplayList.h"
 
 #if defined(DEBUG_bzbarsky) || defined(DEBUG_caillon)
 #define DEBUG_ComputedDOMStyle
 #endif
 
 /*
  * This is the implementation of the readonly CSSStyleDeclaration that is
  * returned by the getComputedStyle() function.
@@ -807,16 +811,135 @@ nsComputedDOMStyle::GetCounterIncrement(
     const nsStyleCounterData *data = content->GetCounterIncrementAt(i);
     name->SetString(data->mCounter);
     value->SetNumber(data->mValue); // XXX This should really be integer
   }
 
   return CallQueryInterface(valueList, aValue);
 }
 
+/* Convert the stored representation into a list of two values and then hand
+ * it back.
+ */
+nsresult nsComputedDOMStyle::GetMozTransformOrigin(nsIDOMCSSValue **aValue)
+{
+  /* We need to build up a list of two values.  We'll call them
+   * width and height.
+   */
+  nsAutoPtr<nsROCSSPrimitiveValue> width(GetROCSSPrimitiveValue());
+  nsAutoPtr<nsROCSSPrimitiveValue> height(GetROCSSPrimitiveValue());
+  if (!width || !height)
+    return NS_ERROR_OUT_OF_MEMORY;
+
+  /* Now, get the values. */
+  const nsStyleDisplay* display = GetStyleDisplay();
+  SetValueToCoord(width, display->mTransformOrigin[0],
+                  &nsComputedDOMStyle::GetFrameBoundsWidthForTransform);
+  SetValueToCoord(height, display->mTransformOrigin[1],
+                  &nsComputedDOMStyle::GetFrameBoundsHeightForTransform);
+
+  /* Store things as a value list, fail if we can't get one. */
+  nsAutoPtr<nsDOMCSSValueList> valueList(GetROCSSValueList(PR_FALSE));
+  if (!valueList)
+    return NS_ERROR_OUT_OF_MEMORY;
+
+  /* Chain on width and height, fail if we can't. */
+  if (!valueList->AppendCSSValue(width))
+    return NS_ERROR_OUT_OF_MEMORY;
+  width.forget();
+  if (!valueList->AppendCSSValue(height))
+    return NS_ERROR_OUT_OF_MEMORY;
+  height.forget();
+
+  /* Release the pointer and call query interface!  We're done. */
+  return CallQueryInterface(valueList.forget(), aValue);
+}
+
+/* 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.
+ */
+nsresult nsComputedDOMStyle::GetMozTransform(nsIDOMCSSValue **aValue)
+{
+  static const PRInt32 NUM_FLOATS = 4;
+  
+  /* First, get the display data.  We'll need it. */
+  const nsStyleDisplay* display = GetStyleDisplay();
+  
+  /* If the "no transforms" flag is set, then we should construct a
+   * single-element entry and hand it back.
+   */
+  if (!display->mTransformPresent) {
+    nsROCSSPrimitiveValue *val(GetROCSSPrimitiveValue());
+    if (!val)
+      return NS_ERROR_OUT_OF_MEMORY;
+    
+    /* Set it to "none." */
+    val->SetIdent(eCSSKeyword_none);
+    return CallQueryInterface(val, aValue);
+  }
+  
+  /* Otherwise, we need to compute the current value of the transform matrix,
+   * store it in a string, and hand it back to the caller.
+   */
+  nsAutoString resultString(NS_LITERAL_STRING("matrix("));
+  
+  /* Now, we need to convert the matrix into a string.  We'll start by taking
+   * the first four entries and converting them directly to floating-point
+   * values.
+   */
+  for (PRInt32 index = 0; index < NUM_FLOATS; ++index) {
+    resultString.AppendFloat(display->mTransform.GetMainMatrixEntry(index));
+    resultString.Append(NS_LITERAL_STRING(", "));
+  }
+  
+  /* For the next part, we need to compute the translate values.  This means
+   * that we'll need to get the width and height of this object.
+   */
+  PRInt32 cssPxWidth = 0, cssPxHeight = 0;
+
+  /* Use the inner frame for width and height.  If we fail, assume zero.
+   * TODO: There is no good way for us to represent the case where there's no
+   * frame, which is problematic.  The reason is that when we have percentage
+   * transforms, there are a total of four stored matrix entries that influence
+   * the transform based on the size of the element.  However, this poses a
+   * problem, because only two of these values can be explicitly referenced
+   * using the named transforms.  Until a real solution is found, we'll just
+   * use this approach.
+   */
+  nsRect bounds =
+    (mInnerFrame ? nsDisplayTransform::GetFrameBoundsForTransform(mInnerFrame) :
+     nsRect(0, 0, 0, 0));
+
+  /* Now, compute the dX and dY components by adding the stored coord value
+   * (in CSS pixels) to the translate values.
+   */
+  
+  float deltaX = nsPresContext::AppUnitsToFloatCSSPixels
+    (display->mTransform.GetXTranslation(bounds));
+  float deltaY = nsPresContext::AppUnitsToFloatCSSPixels
+    (display->mTransform.GetYTranslation(bounds));
+     
+
+  /* Append these values! */
+  resultString.AppendFloat(deltaX);
+  resultString.Append(NS_LITERAL_STRING("px, "));
+  resultString.AppendFloat(deltaY);
+  resultString.Append(NS_LITERAL_STRING("px)"));
+
+  /* Create a value to hold our result. */
+  nsROCSSPrimitiveValue* rv(GetROCSSPrimitiveValue());
+
+  if (!rv)
+    return NS_ERROR_OUT_OF_MEMORY;
+
+  rv->SetString(resultString);
+  return CallQueryInterface(rv, aValue);
+}
+
 nsresult
 nsComputedDOMStyle::GetCounterReset(nsIDOMCSSValue** aValue)
 {
   const nsStyleContent *content = GetStyleContent();
 
   if (content->CounterResetCount() == 0) {
     nsROCSSPrimitiveValue *val = GetROCSSPrimitiveValue();
     NS_ENSURE_TRUE(val, NS_ERROR_OUT_OF_MEMORY);
@@ -3296,16 +3419,52 @@ nsComputedDOMStyle::GetFrameBorderRectWi
   }
 
   FlushPendingReflows();
 
   aWidth = mInnerFrame->GetSize().width;
   return PR_TRUE;
 }
 
+PRBool
+nsComputedDOMStyle::GetFrameBoundsWidthForTransform(nscoord& aWidth)
+{
+  // We need a frame to work with.
+  if (!mInnerFrame) {
+    return PR_FALSE;
+  }
+
+  FlushPendingReflows();
+
+  // Check to see that we're transformed.
+  if (!mInnerFrame->GetStyleDisplay()->HasTransform())
+    return PR_FALSE;
+
+  aWidth = nsDisplayTransform::GetFrameBoundsForTransform(mInnerFrame).width;
+  return PR_TRUE;
+}
+
+PRBool
+nsComputedDOMStyle::GetFrameBoundsHeightForTransform(nscoord& aHeight)
+{
+  // We need a frame to work with.
+  if (!mInnerFrame) {
+    return PR_FALSE;
+  }
+
+  FlushPendingReflows();
+
+  // Check to see that we're transformed.
+  if (!mInnerFrame->GetStyleDisplay()->HasTransform())
+    return PR_FALSE;
+
+  aHeight = nsDisplayTransform::GetFrameBoundsForTransform(mInnerFrame).height;
+  return PR_TRUE;
+}
+
 #ifdef MOZ_SVG
 
 nsresult
 nsComputedDOMStyle::GetSVGPaintFor(PRBool aFill,
                                    nsIDOMCSSValue** aValue)
 {
   nsROCSSPrimitiveValue* val = GetROCSSPrimitiveValue();
   NS_ENSURE_TRUE(val, NS_ERROR_OUT_OF_MEMORY);
@@ -4006,16 +4165,18 @@ nsComputedDOMStyle::GetQueryableProperty
     COMPUTED_STYLE_MAP_ENTRY(float_edge,                    FloatEdge),
     COMPUTED_STYLE_MAP_ENTRY(force_broken_image_icon,  ForceBrokenImageIcon),
     COMPUTED_STYLE_MAP_ENTRY(image_region,                  ImageRegion),
     COMPUTED_STYLE_MAP_ENTRY(_moz_outline_radius_bottomLeft, OutlineRadiusBottomLeft),
     COMPUTED_STYLE_MAP_ENTRY(_moz_outline_radius_bottomRight,OutlineRadiusBottomRight),
     COMPUTED_STYLE_MAP_ENTRY(_moz_outline_radius_topLeft,    OutlineRadiusTopLeft),
     COMPUTED_STYLE_MAP_ENTRY(_moz_outline_radius_topRight,   OutlineRadiusTopRight),
     COMPUTED_STYLE_MAP_ENTRY(stack_sizing,                  StackSizing),
+    COMPUTED_STYLE_MAP_ENTRY(_moz_transform,                MozTransform),
+    COMPUTED_STYLE_MAP_ENTRY(_moz_transform_origin,         MozTransformOrigin),
     COMPUTED_STYLE_MAP_ENTRY(user_focus,                    UserFocus),
     COMPUTED_STYLE_MAP_ENTRY(user_input,                    UserInput),
     COMPUTED_STYLE_MAP_ENTRY(user_modify,                   UserModify),
     COMPUTED_STYLE_MAP_ENTRY(user_select,                   UserSelect),
     COMPUTED_STYLE_MAP_ENTRY(word_wrap,                     WordWrap)
 
 #ifdef MOZ_SVG
     ,
--- a/layout/style/nsComputedDOMStyle.h
+++ b/layout/style/nsComputedDOMStyle.h
@@ -266,16 +266,18 @@ private:
   nsresult GetDisplay(nsIDOMCSSValue** aValue);
   nsresult GetPosition(nsIDOMCSSValue** aValue);
   nsresult GetClip(nsIDOMCSSValue** aValue);
   nsresult GetOverflow(nsIDOMCSSValue** aValue);
   nsresult GetOverflowX(nsIDOMCSSValue** aValue);
   nsresult GetOverflowY(nsIDOMCSSValue** aValue);
   nsresult GetPageBreakAfter(nsIDOMCSSValue** aValue);
   nsresult GetPageBreakBefore(nsIDOMCSSValue** aValue);
+  nsresult GetMozTransform(nsIDOMCSSValue** aValue);
+  nsresult GetMozTransformOrigin(nsIDOMCSSValue **aValue);
 
   /* User interface properties */
   nsresult GetCursor(nsIDOMCSSValue** aValue);
   nsresult GetForceBrokenImageIcon(nsIDOMCSSValue** aValue);
   nsresult GetIMEMode(nsIDOMCSSValue** aValue);
   nsresult GetUserFocus(nsIDOMCSSValue** aValue);
   nsresult GetUserInput(nsIDOMCSSValue** aValue);
   nsresult GetUserModify(nsIDOMCSSValue** aValue);
@@ -369,16 +371,18 @@ private:
    * be determined, returns aDefaultValue.
    */
   nscoord StyleCoordToNSCoord(const nsStyleCoord& aCoord,
                               PercentageBaseGetter aPercentageBaseGetter,
                               nscoord aDefaultValue);
 
   PRBool GetCBContentWidth(nscoord& aWidth);
   PRBool GetCBContentHeight(nscoord& aWidth);
+  PRBool GetFrameBoundsWidthForTransform(nscoord &aWidth);
+  PRBool GetFrameBoundsHeightForTransform(nscoord &aHeight);
   PRBool GetFrameBorderRectWidth(nscoord& aWidth);
 
   struct ComputedStyleMapEntry
   {
     // Create a pointer-to-member-function type.
     typedef nsresult (nsComputedDOMStyle::*ComputeMethod)(nsIDOMCSSValue**);
 
     nsCSSProperty mProperty;
--- a/layout/style/nsRuleNode.cpp
+++ b/layout/style/nsRuleNode.cpp
@@ -64,16 +64,18 @@
 #include "nsStyleSet.h"
 #include "nsSize.h"
 #include "imgIRequest.h"
 #include "nsRuleData.h"
 #include "nsILanguageAtomService.h"
 #include "nsIStyleRule.h"
 #include "nsBidiUtils.h"
 #include "nsStyleStructInlines.h"
+#include "nsStyleTransformMatrix.h"
+#include "nsCSSKeywords.h"
 
 /*
  * For storage of an |nsRuleNode|'s children in a PLDHashTable.
  */
 
 struct ChildrenHashEntry : public PLDHashEntryHdr {
   // key is |mRuleNode->GetKey()|
   nsRuleNode *mRuleNode;
@@ -226,26 +228,37 @@ static nscoord CalcLengthWith(const nsCS
     }
     default:
       NS_NOTREACHED("unexpected unit");
       break;
   }
   return 0;
 }
 
-static nscoord CalcLength(const nsCSSValue& aValue,
-                          nsStyleContext* aStyleContext,
-                          nsPresContext* aPresContext,
-                          PRBool& aInherited)
+/* static */ nscoord
+nsRuleNode::CalcLength(const nsCSSValue& aValue,
+                       nsStyleContext* aStyleContext,
+                       nsPresContext* aPresContext,
+                       PRBool& aInherited)
 {
   NS_ASSERTION(aStyleContext, "Must have style data");
 
   return CalcLengthWith(aValue, -1, nsnull, aStyleContext, aPresContext, aInherited);
 }
 
+/* Inline helper function to redirect requests to CalcLength. */
+static inline nscoord CalcLength(const nsCSSValue& aValue,
+                                 nsStyleContext* aStyleContext,
+                                 nsPresContext* aPresContext,
+                                 PRBool& aInherited)
+{
+  return nsRuleNode::CalcLength(aValue, aStyleContext,
+                                aPresContext, aInherited);
+}
+
 /* static */ nscoord
 nsRuleNode::CalcLengthWithInitialFont(nsPresContext* aPresContext,
                                       const nsCSSValue& aValue)
 {
   nsStyleFont defaultFont(aPresContext);
   PRBool inherited;
   return CalcLengthWith(aValue, -1, &defaultFont, nsnull, aPresContext,
                         inherited);
@@ -259,16 +272,17 @@ nsRuleNode::CalcLengthWithInitialFont(ns
 #define SETCOORD_LENGTH                 0x20   // L
 #define SETCOORD_INTEGER                0x40   // I
 #define SETCOORD_ENUMERATED             0x80   // E
 #define SETCOORD_NONE                   0x100  // O
 #define SETCOORD_INITIAL_ZERO           0x200
 #define SETCOORD_INITIAL_AUTO           0x400
 #define SETCOORD_INITIAL_NONE           0x800
 #define SETCOORD_INITIAL_NORMAL         0x1000
+#define SETCOORD_INITIAL_HALF           0x2000
 
 #define SETCOORD_LP     (SETCOORD_LENGTH | SETCOORD_PERCENT)
 #define SETCOORD_LH     (SETCOORD_LENGTH | SETCOORD_INHERIT)
 #define SETCOORD_AH     (SETCOORD_AUTO | SETCOORD_INHERIT)
 #define SETCOORD_LAH    (SETCOORD_AUTO | SETCOORD_LENGTH | SETCOORD_INHERIT)
 #define SETCOORD_LPH    (SETCOORD_LP | SETCOORD_INHERIT)
 #define SETCOORD_LPAH   (SETCOORD_LP | SETCOORD_AH)
 #define SETCOORD_LPEH   (SETCOORD_LP | SETCOORD_ENUMERATED | SETCOORD_INHERIT)
@@ -338,22 +352,50 @@ static PRBool SetCoord(const nsCSSValue&
   else if (((aMask & SETCOORD_INITIAL_NONE) != 0) && 
            (aValue.GetUnit() == eCSSUnit_Initial)) {
     aCoord.SetNoneValue();
   }
   else if (((aMask & SETCOORD_INITIAL_NORMAL) != 0) && 
            (aValue.GetUnit() == eCSSUnit_Initial)) {
     aCoord.SetNormalValue();
   }
+  else if (((aMask & SETCOORD_INITIAL_HALF) != 0) &&
+           (aValue.GetUnit() == eCSSUnit_Initial)) {
+    aCoord.SetPercentValue(0.5f);
+  }
   else {
     result = PR_FALSE;  // didn't set anything
   }
   return result;
 }
 
+/* Given an enumerated value that represents a box position, converts it to
+ * a float representing the percentage of the box it corresponds to.  For
+ * example, "center" becomes 0.5f.
+ *
+ * @param aEnumValue The enumerated value.
+ * @return The float percent it corresponds to.
+ */
+static float GetFloatFromBoxPosition(PRInt32 aEnumValue)
+{
+  switch (aEnumValue) {
+  case NS_STYLE_BG_POSITION_LEFT:
+  case NS_STYLE_BG_POSITION_TOP:
+    return 0.0f;
+  case NS_STYLE_BG_POSITION_RIGHT:
+  case NS_STYLE_BG_POSITION_BOTTOM:
+    return 1.0f;
+  default:
+    NS_NOTREACHED("unexpected value");
+    // fall through
+  case NS_STYLE_BG_POSITION_CENTER:
+    return 0.5f;
+  }
+}
+
 static PRBool SetColor(const nsCSSValue& aValue, const nscolor aParentColor, 
                        nsPresContext* aPresContext, nsStyleContext *aContext,
                        nscolor& aResult, PRBool& aInherited)
 {
   PRBool  result = PR_FALSE;
   nsCSSUnit unit = aValue.GetUnit();
 
   if (eCSSUnit_Color == unit) {
@@ -3073,16 +3115,48 @@ nsRuleNode::ComputeUIResetData(void* aSt
               inherited,
               SETDSC_INTEGER,
               parentUI->mForceBrokenImageIcon,
               0, 0, 0, 0, 0);
 
   COMPUTE_END_RESET(UIReset, ui)
 }
 
+/* Given a -moz-transform token stream, accumulates them into an
+ * nsStyleTransformMatrix
+ *
+ * @param aList The nsCSSValueList of arrays to read into transform functions.
+ * @param aContext The style context to use for unit conversion.
+ * @param aPresContext The presentation context to use for unit conversion
+ * @param aInherited If the value is inherited, this is set to PR_TRUE.
+ * @return An nsStyleTransformMatrix corresponding to the net transform.
+ */
+static nsStyleTransformMatrix ReadTransforms(const nsCSSValueList* aList,
+                                             nsStyleContext* aContext,
+                                             nsPresContext* aPresContext,
+                                             PRBool &aInherited)
+{
+  nsStyleTransformMatrix result;
+
+  for (const nsCSSValueList* curr = aList; curr != nsnull; curr = curr->mNext) {
+    const nsCSSValue &currElem = curr->mValue;
+    NS_ASSERTION(currElem.GetUnit() == eCSSUnit_Function,
+                 "Stream should consist solely of functions!");
+    NS_ASSERTION(currElem.GetArrayValue()->Count() >= 1,
+                 "Incoming function is too short!");
+
+    /* Read in a single transform matrix, then accumulate it with the total. */
+    nsStyleTransformMatrix currMatrix;
+    currMatrix.SetToTransformFunction(currElem.GetArrayValue(), aContext,
+                                      aPresContext);
+    result *= currMatrix;
+  }
+  return result;
+}
+
 const void*
 nsRuleNode::ComputeDisplayData(void* aStartStruct,
                                const nsRuleDataStruct& aData, 
                                nsStyleContext* aContext, 
                                nsRuleNode* aHighestNode,
                                const RuleDetail aRuleDetail, PRBool aInherited)
 {
   COMPUTE_START_RESET(Display, (), display, parentDisplay,
@@ -3325,16 +3399,89 @@ nsRuleNode::ComputeDisplayData(void* aSt
 
       // We can't cache the data in the rule tree since if a more specific
       // rule has 'float: none' we'll end up with the wrong 'display'
       // property.
       inherited = PR_TRUE;
     }
 
   }
+  
+  /* Convert the nsCSSValueList into an nsTArray<nsTransformFunction *>. */
+  const nsCSSValueList *head = displayData.mTransform;
+  
+  if (head != nsnull) {
+    /* There is a chance that we will discover that
+     * the transform property has been set to 'none,' 'initial,' or 'inherit.'
+     * If so, process appropriately.
+     */
+    
+    /* If it's 'none,' indicate that there are no transforms. */
+    if (head->mValue.GetUnit() == eCSSUnit_None)
+      display->mTransformPresent = PR_FALSE;
+    
+    /* If we need to inherit, do so by making a full deep-copy. */
+    else if (head->mValue.GetUnit() == eCSSUnit_Inherit)  {
+      display->mTransformPresent = parentDisplay->mTransformPresent;
+      if (parentDisplay->mTransformPresent)
+        display->mTransform = parentDisplay->mTransform;
+      inherited = PR_TRUE;
+    }
+    /* If it's 'initial', then we reset to empty. */
+    else if (head->mValue.GetUnit() == eCSSUnit_Initial)
+      display->mTransformPresent = PR_FALSE;
+    
+    /* Otherwise, we are looking at a list of CSS tokens.  We'll read each of
+     * them in as an array of nsTransformFunction objects, then will accumulate
+     * them all together to form the final transform matrix.
+     */
+    else {
+ 
+      display->mTransform = 
+        ReadTransforms(head, aContext, mPresContext, inherited);
+
+      /* Make sure to say that this data is valid! */
+      display->mTransformPresent = PR_TRUE;
+    }
+  }
+  
+  /* Convert -moz-transform-origin. */
+  if (displayData.mTransformOrigin.mXValue.GetUnit() != eCSSUnit_Null ||
+      displayData.mTransformOrigin.mXValue.GetUnit() != eCSSUnit_Null) {
+
+    /* If X coordinate is an enumerated type, handle it explicitly. */
+    if (eCSSUnit_Enumerated == displayData.mTransformOrigin.mXValue.GetUnit())
+      display->mTransformOrigin[0].SetPercentValue
+        (GetFloatFromBoxPosition
+         (displayData.mTransformOrigin.mXValue.GetIntValue()));
+    else {
+      /* Convert lengths, percents, and inherit.  Default value is 50%. */
+      PRBool result = SetCoord(displayData.mTransformOrigin.mXValue,
+                               display->mTransformOrigin[0],
+                               parentDisplay->mTransformOrigin[0],
+                               SETCOORD_LPH | SETCOORD_INITIAL_HALF,
+                               aContext, mPresContext, aInherited);
+      NS_ASSERTION(result, "Malformed -moz-transform-origin parse!");
+    }
+
+    /* If Y coordinate is an enumerated type, handle it explicitly. */
+    if (eCSSUnit_Enumerated == displayData.mTransformOrigin.mYValue.GetUnit())
+      display->mTransformOrigin[1].SetPercentValue
+        (GetFloatFromBoxPosition
+         (displayData.mTransformOrigin.mYValue.GetIntValue()));
+    else {
+      /* Convert lengths, percents, initial, inherit. */
+      PRBool result = SetCoord(displayData.mTransformOrigin.mYValue,
+                               display->mTransformOrigin[1],
+                               parentDisplay->mTransformOrigin[1],
+                               SETCOORD_LPH | SETCOORD_INITIAL_HALF,
+                               aContext, mPresContext, aInherited);
+      NS_ASSERTION(result, "Malformed -moz-transform-origin parse!");
+    }
+  }
 
   COMPUTE_END_RESET(Display, display)
 }
 
 const void*
 nsRuleNode::ComputeVisibilityData(void* aStartStruct,
                                   const nsRuleDataStruct& aData, 
                                   nsStyleContext* aContext, 
@@ -3480,30 +3627,19 @@ nsRuleNode::ComputeBackgroundData(void* 
   }
   else if (colorData.mBackPosition.mXValue.IsLengthUnit()) {
     bg->mBackgroundXPosition.mCoord = CalcLength(colorData.mBackPosition.mXValue, 
                                                  aContext, mPresContext, inherited);
     bg->mBackgroundFlags |= NS_STYLE_BG_X_POSITION_LENGTH;
     bg->mBackgroundFlags &= ~NS_STYLE_BG_X_POSITION_PERCENT;
   }
   else if (eCSSUnit_Enumerated == colorData.mBackPosition.mXValue.GetUnit()) {
-    switch (colorData.mBackPosition.mXValue.GetIntValue()) {
-      case NS_STYLE_BG_POSITION_LEFT:
-        bg->mBackgroundXPosition.mFloat = 0.0f;
-        break;
-      case NS_STYLE_BG_POSITION_RIGHT:
-        bg->mBackgroundXPosition.mFloat = 1.0f;
-        break;
-      default:
-        NS_NOTREACHED("unexpected value");
-        // fall through
-      case NS_STYLE_BG_POSITION_CENTER:
-        bg->mBackgroundXPosition.mFloat = 0.5f;
-        break;
-    }
+    bg->mBackgroundXPosition.mFloat =
+      GetFloatFromBoxPosition(colorData.mBackPosition.mXValue.GetIntValue());
+
     bg->mBackgroundFlags |= NS_STYLE_BG_X_POSITION_PERCENT;
     bg->mBackgroundFlags &= ~NS_STYLE_BG_X_POSITION_LENGTH;
   }
   else if (eCSSUnit_Inherit == colorData.mBackPosition.mXValue.GetUnit()) {
     inherited = PR_TRUE;
     bg->mBackgroundXPosition = parentBG->mBackgroundXPosition;
     bg->mBackgroundFlags &= ~(NS_STYLE_BG_X_POSITION_LENGTH | NS_STYLE_BG_X_POSITION_PERCENT);
     bg->mBackgroundFlags |= (parentFlags & (NS_STYLE_BG_X_POSITION_LENGTH | NS_STYLE_BG_X_POSITION_PERCENT));
@@ -3519,30 +3655,19 @@ nsRuleNode::ComputeBackgroundData(void* 
   }
   else if (colorData.mBackPosition.mYValue.IsLengthUnit()) {
     bg->mBackgroundYPosition.mCoord = CalcLength(colorData.mBackPosition.mYValue,
                                                  aContext, mPresContext, inherited);
     bg->mBackgroundFlags |= NS_STYLE_BG_Y_POSITION_LENGTH;
     bg->mBackgroundFlags &= ~NS_STYLE_BG_Y_POSITION_PERCENT;
   }
   else if (eCSSUnit_Enumerated == colorData.mBackPosition.mYValue.GetUnit()) {
-    switch (colorData.mBackPosition.mYValue.GetIntValue()) {
-      case NS_STYLE_BG_POSITION_TOP:
-        bg->mBackgroundYPosition.mFloat = 0.0f;
-        break;
-      case NS_STYLE_BG_POSITION_BOTTOM:
-        bg->mBackgroundYPosition.mFloat = 1.0f;
-        break;
-      default:
-        NS_NOTREACHED("unexpected value");
-        // fall through
-      case NS_STYLE_BG_POSITION_CENTER:
-        bg->mBackgroundYPosition.mFloat = 0.5f;
-        break;
-    }
+    bg->mBackgroundYPosition.mFloat =
+      GetFloatFromBoxPosition(colorData.mBackPosition.mYValue.GetIntValue());
+
     bg->mBackgroundFlags |= NS_STYLE_BG_Y_POSITION_PERCENT;
     bg->mBackgroundFlags &= ~NS_STYLE_BG_Y_POSITION_LENGTH;
   }
   else if (eCSSUnit_Inherit == colorData.mBackPosition.mYValue.GetUnit()) {
     inherited = PR_TRUE;
     bg->mBackgroundYPosition = parentBG->mBackgroundYPosition;
     bg->mBackgroundFlags &= ~(NS_STYLE_BG_Y_POSITION_LENGTH | NS_STYLE_BG_Y_POSITION_PERCENT);
     bg->mBackgroundFlags |= (parentFlags & (NS_STYLE_BG_Y_POSITION_LENGTH | NS_STYLE_BG_Y_POSITION_PERCENT));
--- a/layout/style/nsRuleNode.h
+++ b/layout/style/nsRuleNode.h
@@ -737,11 +737,16 @@ public:
   NS_HIDDEN_(PRBool) Sweep();
 
   static PRBool
     HasAuthorSpecifiedRules(nsStyleContext* aStyleContext, PRUint32 ruleTypeMask);
 
   // Expose this so media queries can use it
   static nscoord CalcLengthWithInitialFont(nsPresContext* aPresContext,
                                            const nsCSSValue& aValue);
+  // Expose this so nsTransformFunctions can use it.
+  static nscoord CalcLength(const nsCSSValue& aValue,
+                            nsStyleContext* aStyleContext,
+                            nsPresContext* aPresContext,
+                            PRBool& aInherited);
 };
 
 #endif
--- a/layout/style/nsStyleStruct.cpp
+++ b/layout/style/nsStyleStruct.cpp
@@ -1231,16 +1231,19 @@ nsStyleDisplay::nsStyleDisplay()
   mBreakType = NS_STYLE_CLEAR_NONE;
   mBreakBefore = PR_FALSE;
   mBreakAfter = PR_FALSE;
   mOverflowX = NS_STYLE_OVERFLOW_VISIBLE;
   mOverflowY = NS_STYLE_OVERFLOW_VISIBLE;
   mClipFlags = NS_STYLE_CLIP_AUTO;
   mClip.SetRect(0,0,0,0);
   mOpacity = 1.0f;
+  mTransformPresent = PR_FALSE; // No transform
+  mTransformOrigin[0].SetPercentValue(0.5f); // Transform is centered on origin
+  mTransformOrigin[1].SetPercentValue(0.5f); 
 }
 
 nsStyleDisplay::nsStyleDisplay(const nsStyleDisplay& aSource)
 {
   mAppearance = aSource.mAppearance;
   mDisplay = aSource.mDisplay;
   mOriginalDisplay = aSource.mOriginalDisplay;
   mBinding = aSource.mBinding;
@@ -1249,16 +1252,25 @@ nsStyleDisplay::nsStyleDisplay(const nsS
   mBreakType = aSource.mBreakType;
   mBreakBefore = aSource.mBreakBefore;
   mBreakAfter = aSource.mBreakAfter;
   mOverflowX = aSource.mOverflowX;
   mOverflowY = aSource.mOverflowY;
   mClipFlags = aSource.mClipFlags;
   mClip = aSource.mClip;
   mOpacity = aSource.mOpacity;
+
+  /* Copy over the transformation information. */
+  mTransformPresent = aSource.mTransformPresent;
+  if (mTransformPresent)
+    mTransform = aSource.mTransform;
+  
+  /* Copy over transform origin. */
+  mTransformOrigin[0] = aSource.mTransformOrigin[0];
+  mTransformOrigin[1] = aSource.mTransformOrigin[1];
 }
 
 nsChangeHint nsStyleDisplay::CalcDifference(const nsStyleDisplay& aOther) const
 {
   nsChangeHint hint = nsChangeHint(0);
 
   if (!EqualURIs(mBinding, aOther.mBinding)
       || mPosition != aOther.mPosition
@@ -1280,16 +1292,42 @@ nsChangeHint nsStyleDisplay::CalcDiffere
       || mBreakBefore != aOther.mBreakBefore
       || mBreakAfter != aOther.mBreakAfter
       || mAppearance != aOther.mAppearance)
     NS_UpdateHint(hint, NS_CombineHint(nsChangeHint_ReflowFrame, nsChangeHint_RepaintFrame));
 
   if (mOpacity != aOther.mOpacity)
     NS_UpdateHint(hint, nsChangeHint_RepaintFrame);
 
+  /* If we've added or removed the transform property, we need to reconstruct the frame to add
+   * or remove the view object, and also to handle abs-pos and fixed-pos containers.
+   */
+  if (mTransformPresent != aOther.mTransformPresent) {
+    NS_UpdateHint(hint, nsChangeHint_ReconstructFrame);
+  }
+  else if (mTransformPresent) {
+    /* Otherwise, if we've kept the property lying around and we already had a
+     * transform, we need to see whether or not we've changed the transform.
+     * If so, we need to do a reflow and a repaint. The reflow is to recompute
+     * the overflow rect (which probably changed if the transform changed)
+     * and to redraw within the bounds of that new overflow rect.
+     */
+    if (mTransform != aOther.mTransform)
+      NS_UpdateHint(hint, NS_CombineHint(nsChangeHint_ReflowFrame,
+                                         nsChangeHint_RepaintFrame));
+    
+    for (PRUint8 index = 0; index < 2; ++index)
+      if (mTransformOrigin[index] != aOther.mTransformOrigin[index]) {
+        NS_UpdateHint(hint, NS_CombineHint(nsChangeHint_ReflowFrame,
+                                           nsChangeHint_RepaintFrame));
+        break;
+      }
+  }
+  
+  
   return hint;
 }
 
 #ifdef DEBUG
 /* static */
 nsChangeHint nsStyleDisplay::MaxDifference()
 {
   // All the parts of FRAMECHANGE are present above in CalcDifference.
--- a/layout/style/nsStyleStruct.h
+++ b/layout/style/nsStyleStruct.h
@@ -57,16 +57,17 @@
 #include "nsChangeHint.h"
 #include "nsPresContext.h"
 #include "nsIPresShell.h"
 #include "nsCOMPtr.h"
 #include "nsCOMArray.h"
 #include "nsIAtom.h"
 #include "nsIURI.h"
 #include "nsCSSValue.h"
+#include "nsStyleTransformMatrix.h"
 
 class nsIFrame;
 class imgIRequest;
 
 // Includes nsStyleStructID.
 #include "nsStyleStructFwd.h"
 
 // Bits for each struct.
@@ -881,17 +882,20 @@ struct nsStyleDisplay {
   PRUint8 mPosition;            // [reset] see nsStyleConsts.h
   PRUint8 mFloats;              // [reset] see nsStyleConsts.h NS_STYLE_FLOAT_*
   PRUint8 mBreakType;           // [reset] see nsStyleConsts.h NS_STYLE_CLEAR_*
   PRPackedBool mBreakBefore;    // [reset] 
   PRPackedBool mBreakAfter;     // [reset] 
   PRUint8 mOverflowX;           // [reset] see nsStyleConsts.h
   PRUint8 mOverflowY;           // [reset] see nsStyleConsts.h
   PRUint8   mClipFlags;         // [reset] see nsStyleConsts.h
-  
+  PRPackedBool mTransformPresent;  // [reset] Whether there is a -moz-transform.
+  nsStyleTransformMatrix mTransform; // [reset] The stored transform matrix
+  nsStyleCoord mTransformOrigin[2]; // [reset] percent, coord.
+
   PRBool IsBlockInside() const {
     return NS_STYLE_DISPLAY_BLOCK == mDisplay ||
            NS_STYLE_DISPLAY_LIST_ITEM == mDisplay ||
            NS_STYLE_DISPLAY_INLINE_BLOCK == mDisplay;
     // Should TABLE_CELL and TABLE_CAPTION go here?  They have
     // block frames nested inside of them.
     // (But please audit all callers before changing.)
   }
@@ -913,33 +917,41 @@ struct nsStyleDisplay {
 
   PRBool IsFloating() const {
     return NS_STYLE_FLOAT_NONE != mFloats;
   }
 
   PRBool IsAbsolutelyPositioned() const {return (NS_STYLE_POSITION_ABSOLUTE == mPosition) ||
                                                 (NS_STYLE_POSITION_FIXED == mPosition);}
 
-  PRBool IsPositioned() const {return IsAbsolutelyPositioned() ||
-                                      (NS_STYLE_POSITION_RELATIVE == mPosition);}
+  /* Returns true if we're positioned or there's a transform in effect. */
+  PRBool IsPositioned() const {
+    return IsAbsolutelyPositioned() ||
+      NS_STYLE_POSITION_RELATIVE == mPosition || mTransformPresent;
+  }
 
   PRBool IsScrollableOverflow() const {
     // mOverflowX and mOverflowY always match when one of them is
     // NS_STYLE_OVERFLOW_VISIBLE or NS_STYLE_OVERFLOW_CLIP.
     return mOverflowX != NS_STYLE_OVERFLOW_VISIBLE &&
            mOverflowX != NS_STYLE_OVERFLOW_CLIP;
   }
 
   // For table elements that don't support scroll frame creation, we
   // support 'overflow: hidden' to mean 'overflow: -moz-hidden-unscrollable'.
   PRBool IsTableClip() const {
     return mOverflowX == NS_STYLE_OVERFLOW_CLIP ||
            (mOverflowX == NS_STYLE_OVERFLOW_HIDDEN &&
             mOverflowY == NS_STYLE_OVERFLOW_HIDDEN);
   }
+
+  /* Returns whether the element has the -moz-transform property. */
+  PRBool HasTransform() const {
+    return mTransformPresent;
+  }
 };
 
 struct nsStyleTable {
   nsStyleTable(void);
   nsStyleTable(const nsStyleTable& aOther);
   ~nsStyleTable(void);
 
   void* operator new(size_t sz, nsPresContext* aContext) CPP_THROW_NEW {
new file mode 100644
--- /dev/null
+++ b/layout/style/nsStyleTransformMatrix.cpp
@@ -0,0 +1,541 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ *   Mozilla Corporation
+ *
+ * Contributor(s):
+ *   Keith Schwarz <kschwarz@mozilla.com> (original author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of the GNU General Public License Version 2 or later (the "GPL"),
+ * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/*
+ * A class used for intermediate representations of the -moz-transform property.
+ */
+
+#include "nsStyleTransformMatrix.h"
+#include "nsAutoPtr.h"
+#include "nsCSSValue.h"
+#include "nsStyleContext.h"
+#include "nsPresContext.h"
+#include "nsRuleNode.h"
+#include "nsCSSKeywords.h"
+#include <math.h>
+
+/* Arguably, this loses precision, but it doesn't hurt! */
+const float kPi = 3.1415926535897932384626433f;
+const float kEpsilon = 0.0001f;
+
+/* Computes tan(theta).  For values of theta such that
+ * tan(theta) is undefined or arbitrarily large, SafeTangent
+ * returns a managably large or small value of the correct sign.
+ */
+static float SafeTangent(float aTheta)
+{
+  /* We'll do this by computing sin and cos theta.  If cos(theta) is
+   * is too close to zero, we'll set it to some arbitrary epsilon value
+   * that avoid float overflow or undefined result.
+   */
+  float sinTheta = sin(aTheta);
+  float cosTheta = cos(aTheta);
+  
+  /* Bound cos(theta) to be in the range [-1, -epsilon) U (epsilon, 1] */
+  if (cosTheta >= 0 && cosTheta < kEpsilon)
+    cosTheta = kEpsilon;
+  else if (cosTheta < 0 && cosTheta >= -kEpsilon)
+    cosTheta = -kEpsilon;
+  
+  return sinTheta / cosTheta;
+}
+
+/* Converts an nsCSSValue containing an angle into an equivalent measure
+ * of radians.
+ */
+static float CSSToRadians(const nsCSSValue &aValue)
+{
+  NS_PRECONDITION(aValue.IsAngularUnit(),
+                  "Expected an angle, but didn't find one!");
+  
+  switch (aValue.GetUnit()) {
+  case eCSSUnit_Degree:
+    /* 360deg = 2pi rad, so deg = pi / 180 rad */
+    return aValue.GetFloatValue() * kPi / 180.0f;
+  case eCSSUnit_Grad:
+    /* 400grad = 2pi rad, so grad = pi / 200 rad */
+    return aValue.GetFloatValue() * kPi / 200.0f;
+  case eCSSUnit_Radian:
+    /* Yay identity transforms! */
+    return aValue.GetFloatValue();
+  default:
+    NS_NOTREACHED("Unexpected angular unit!");
+    return 0.0f;
+  }
+}
+
+/* Constructor sets the data to the identity matrix. */
+nsStyleTransformMatrix::nsStyleTransformMatrix()
+{
+  SetToIdentity();
+}
+
+/* SetToIdentity just fills in the appropriate values. */
+void nsStyleTransformMatrix::SetToIdentity()
+{
+    /* Set the main matrix to the identity. */
+  mMain[0] = 1.0f;
+  mMain[1] = 0.0f;
+  mMain[2] = 0.0f;
+  mMain[3] = 1.0f;
+  mDelta[0] = 0;
+  mDelta[1] = 0;
+
+  /* Both translation matrices are zero. */
+  mX[0] = 0.0f;
+  mX[1] = 0.0f;
+  mY[0] = 0.0f;
+  mY[1] = 0.0f;
+}
+
+/* Adds the constant translation to the scale factor translation components. */
+nscoord nsStyleTransformMatrix::GetXTranslation(const nsRect& aBounds) const
+{
+  return nscoord(aBounds.width * mX[0] + aBounds.height * mY[0]) + mDelta[0];
+}
+nscoord nsStyleTransformMatrix::GetYTranslation(const nsRect& aBounds) const
+{
+  return nscoord(aBounds.width * mX[1] + aBounds.height * mY[1]) + mDelta[1];
+}
+
+/* GetThebesMatrix converts the stored matrix in a few steps. */
+gfxMatrix nsStyleTransformMatrix::GetThebesMatrix(const nsRect& aBounds,
+                                                  PRInt32 aScale) const
+{
+  /* Compute the graphics matrix.  Unfortunately, the gfxMatrix stores entries
+   * as
+   * | a b e |
+   * | c d f |
+   * | 0 0 1 |
+   * But we store the matrix as
+   * | a c e |
+   * | b d f |
+   * | 0 0 1 |
+   * So, we'll have to be a bit clever about how we do the conversion.
+   *
+   * Also, we need to be sure to add to this matrices the following:
+   *
+   * | 0 0 dx1|
+   * | 0 0 dx2| * width
+   * | 0 0   0|
+   *
+   * | 0 0 dy1|
+   * | 0 0 dy2| * height
+   * | 0 0   0|
+   */
+
+  return gfxMatrix(mMain[0], mMain[2], mMain[1], mMain[3],
+                   NSAppUnitsToFloatPixels(GetXTranslation(aBounds), aScale),
+                   NSAppUnitsToFloatPixels(GetYTranslation(aBounds), aScale));
+}
+
+/* Performs the matrix multiplication necessary to multiply the two matrices,
+ * then hands back a reference to ourself.
+ */
+nsStyleTransformMatrix&
+nsStyleTransformMatrix::operator *= (const nsStyleTransformMatrix &aOther)
+{
+  /* We'll buffer all of our results into a temporary storage location
+   * during this operation since we don't want to overwrite the values of
+   * the old matrix with the values of the new.
+   */
+  float newMatrix[4];
+  nscoord newDelta[2];
+  float newX[2];
+  float newY[2];
+  
+  /*  [aOther]    [this]
+   * |a1 c1 e1| |a0 c0 e0|   |a0a1 + b0c1    c0a1 + d0c1     e0a1 + f0c1 + e1|
+   * |b1 d1 f1|x|b0 d0 f0| = |a0b1 + b0d1    c0b1 + d0d1     e0b1 + f0d1 + f1|
+   * |0  0  1 | | 0  0  1|   |          0              0                    1|
+   */
+  newMatrix[0] = mMain[0] * aOther.mMain[0] + mMain[1] * aOther.mMain[2];
+  newMatrix[1] = mMain[0] * aOther.mMain[1] + mMain[1] * aOther.mMain[3];
+  newMatrix[2] = mMain[2] * aOther.mMain[0] + mMain[3] * aOther.mMain[2];
+  newMatrix[3] = mMain[2] * aOther.mMain[1] + mMain[3] * aOther.mMain[3];
+  newDelta[0] =
+    NSCoordMultiply(mDelta[0], aOther.mMain[0]) +
+    NSCoordMultiply(mDelta[1], aOther.mMain[2]) +
+    aOther.mDelta[0];
+  newDelta[1] =
+    NSCoordMultiply(mDelta[0], aOther.mMain[1]) +
+    NSCoordMultiply(mDelta[1], aOther.mMain[3]) +
+    aOther.mDelta[1];
+
+  /* For consistent terminology, let u0, u1, v0, and v1 be the four transform
+   * coordinates from the old matrix, and let x0, x1, y0, and y1 be the four
+   * transform coordinates from the new matrix.  Then the new transform
+   * coordinates are:
+   *
+   * u0' = a1u0 + c1u1 + x0
+   * u1' = b1u0 + d1u1 + x1
+   * v0' = a1v0 + c1v1 + y0
+   * v1' = b1v0 + d1v1 + y1
+   */
+  newX[0] = aOther.mMain[0] * mX[0] + aOther.mMain[2] * mX[1] + aOther.mX[0];
+  newX[1] = aOther.mMain[1] * mX[0] + aOther.mMain[3] * mX[1] + aOther.mX[1];
+  newY[0] = aOther.mMain[0] * mY[0] + aOther.mMain[2] * mY[1] + aOther.mY[0];
+  newY[1] = aOther.mMain[1] * mY[0] + aOther.mMain[3] * mY[1] + aOther.mY[1];
+
+  /* Now, write everything back in. */
+  for (PRInt32 index = 0; index < 4; ++index)
+    mMain[index] = newMatrix[index];
+  for (PRInt32 index = 0; index < 2; ++index) {
+    mDelta[index] = newDelta[index];
+    mX[index] = newX[index];
+    mY[index] = newY[index];
+  }
+
+  /* As promised, return a reference to ourselves. */
+  return *this;
+}
+
+/* op* is implemented in terms of op*=. */
+const nsStyleTransformMatrix
+nsStyleTransformMatrix::operator *(const nsStyleTransformMatrix &aOther) const
+{
+  return nsStyleTransformMatrix(*this) *= aOther;
+}
+
+/* Helper function to fill in an nscoord with the specified nsCSSValue. */
+static void SetCoordToValue(const nsCSSValue &aValue,
+			    nsStyleContext* aContext,
+			    nsPresContext* aPresContext, nscoord &aOut)
+{
+  PRBool unused = PR_FALSE;
+  aOut = nsRuleNode::CalcLength(aValue, aContext, aPresContext, unused);
+
+  NS_POSTCONDITION(!unused, "How did we inherit a value?");
+}
+
+/* Helper function to process a matrix entry. */
+static void ProcessMatrix(float aMain[4], nscoord aDelta[2],
+			  float aX[2], float aY[2],
+			  const nsCSSValue::Array* aData,
+			  nsStyleContext* aContext,
+			  nsPresContext* aPresContext)
+{
+  NS_PRECONDITION(aData->Count() == 7, "Invalid array!");
+
+  /* Take the first four elements out of the array as floats and store
+   * them in aMain.
+   */
+  for (PRUint16 index = 1; index <= 4; ++index)
+    aMain[index - 1] = aData->Item(index).GetFloatValue();
+
+  /* For the fifth element, if it's a percentage, store it in aX[0].
+   * Otherwise, it's a length that needs to go in aDelta[0]
+   */
+  if (aData->Item(5).GetUnit() == eCSSUnit_Percent)
+    aX[0] = aData->Item(5).GetPercentValue();
+  else
+    SetCoordToValue(aData->Item(5), aContext, aPresContext, aDelta[0]);
+
+  /* For the final element, if it's a percentage, store it in aY[1].
+   * Otherwise, it's a length that needs to go in aDelta[1].
+   */
+  if (aData->Item(6).GetUnit() == eCSSUnit_Percent)
+    aY[1] = aData->Item(5).GetPercentValue();
+  else
+    SetCoordToValue(aData->Item(6), aContext, aPresContext, aDelta[1]);
+}
+
+/* Helper function to process a translatex function. */
+static void ProcessTranslateX(nscoord aDelta[2], float aX[2],
+			      const nsCSSValue::Array* aData,
+			      nsStyleContext* aContext,
+			      nsPresContext* aPresContext)
+{
+  NS_PRECONDITION(aData->Count() == 2, "Invalid array!");
+
+  /* There are two cases.  If we have a number, we want our matrix to look
+   * like this:
+   *
+   * |  1  0 dx|
+   * |  0  1  0|
+   * |  0  0  1|
+   * So E = value
+   * 
+   * Otherwise, we might have a percentage, so we want to set the dX component
+   * to the percent.
+   */
+  if (aData->Item(1).GetUnit() != eCSSUnit_Percent)
+    SetCoordToValue(aData->Item(1), aContext, aPresContext, aDelta[0]);
+  else
+    aX[0] = aData->Item(1).GetPercentValue();
+}
+
+/* Helper function to process a translatey function. */
+static void ProcessTranslateY(nscoord aDelta[2], float aY[2],
+			      const nsCSSValue::Array* aData,
+			      nsStyleContext* aContext,
+			      nsPresContext* aPresContext)
+{
+  NS_PRECONDITION(aData->Count() == 2, "Invalid array!");
+
+  /* There are two cases.  If we have a number, we want our matrix to look
+   * like this:
+   *
+   * |  1  0  0|
+   * |  0  1 dy|
+   * |  0  0  1|
+   * So E = value
+   * 
+   * Otherwise, we might have a percentage, so we want to set the dY component
+   * to the percent.
+   */
+  if (aData->Item(1).GetUnit() != eCSSUnit_Percent)
+    SetCoordToValue(aData->Item(1), aContext, aPresContext, aDelta[1]);
+  else
+    aY[1] = aData->Item(1).GetPercentValue();
+}
+
+/* Helper functiont to process a translate function. */
+static void ProcessTranslate(nscoord aDelta[2], float aX[2], float aY[2],
+			     const nsCSSValue::Array* aData,
+			     nsStyleContext* aContext,
+			     nsPresContext* aPresContext)
+{
+  NS_PRECONDITION(aData->Count() == 2 || aData->Count() == 3, "Invalid array!");
+
+  /* There are several cases to consider.
+   * First, we might have one value, or we might have two.  If we have
+   * one, pretend we got two of the same value.
+   * Next, the values might be lengths, or they might be percents.  If they're
+   * percents, store them in the dX and dY components.  Otherwise, store them in
+   * the main matrix.
+   */
+
+  const nsCSSValue &dx = aData->Item(1);
+  const nsCSSValue &dy = (aData->Count() == 2 ? dx : aData->Item(2));
+
+  if (dx.GetUnit() == eCSSUnit_Percent)
+    aX[0] = dx.GetPercentValue();
+  else
+    SetCoordToValue(dx, aContext, aPresContext, aDelta[0]);
+
+  if (dy.GetUnit() == eCSSUnit_Percent)
+    aY[1] = dy.GetPercentValue();
+  else
+    SetCoordToValue(dy, aContext, aPresContext, aDelta[1]); 
+}
+
+/* Helper function to set up a scale matrix. */
+static void ProcessScaleHelper(float aXScale, float aYScale, float aMain[4])
+{
+  /* We want our matrix to look like this:
+   * | dx  0  0|
+   * |  0 dy  0|
+   * |  0  0  1|
+   * So A = value
+   */
+  aMain[0] = aXScale;
+  aMain[3] = aYScale;
+}
+
+/* Process a scalex function. */
+static void ProcessScaleX(float aMain[4], const nsCSSValue::Array* aData)
+{
+  NS_PRECONDITION(aData->Count() == 2, "Bad array!");
+  ProcessScaleHelper(aData->Item(1).GetFloatValue(), 1.0f, aMain);
+}
+
+/* Process a scaley function. */
+static void ProcessScaleY(float aMain[4], const nsCSSValue::Array* aData)
+{
+  NS_PRECONDITION(aData->Count() == 2, "Bad array!");
+  ProcessScaleHelper(1.0f, aData->Item(1).GetFloatValue(), aMain);
+}
+
+/* Process a scale function. */
+static void ProcessScale(float aMain[4], const nsCSSValue::Array* aData)
+{
+  NS_PRECONDITION(aData->Count() == 2 || aData->Count() == 3, "Bad array!");
+  /* We either have one element or two.  If we have one, it's for both X and Y.
+   * Otherwise it's one for each.
+   */
+  const nsCSSValue& scaleX = aData->Item(1);
+  const nsCSSValue& scaleY = (aData->Count() == 2 ? scaleX :
+			      aData->Item(2));
+
+  ProcessScaleHelper(scaleX.GetFloatValue(),
+		     scaleY.GetFloatValue(), aMain);
+}
+
+/* Helper function that, given a set of angles, constructs the appropriate
+ * skew matrix.
+ */
+static void ProcessSkewHelper(float aXAngle, float aYAngle, float aMain[4])
+{
+  /* We want our matrix to look like this:
+   * |  1           tan(ThetaY)  0|
+   * |  tan(ThetaX) 1            0|
+   * |  0           0            1|
+   * However, to avoid infinte values, we'll use the SafeTangent function
+   * instead of the C standard tan function.
+   */
+  aMain[1] = SafeTangent(aXAngle);
+  aMain[2] = SafeTangent(aYAngle);
+}
+
+/* Function that converts a skewx transform into a matrix. */
+static void ProcessSkewX(float aMain[4], const nsCSSValue::Array* aData)
+{
+  NS_ASSERTION(aData->Count() == 2, "Bad array!");
+  ProcessSkewHelper(CSSToRadians(aData->Item(1)), 0.0f, aMain);
+}
+
+/* Function that converts a skewy transform into a matrix. */
+static void ProcessSkewY(float aMain[4], const nsCSSValue::Array* aData)
+{
+  NS_ASSERTION(aData->Count() == 2, "Bad array!");
+  ProcessSkewHelper(0.0f, CSSToRadians(aData->Item(1)), aMain);
+}
+
+/* Function that converts a skew transform into a matrix. */
+static void ProcessSkew(float aMain[4], const nsCSSValue::Array* aData)
+{
+  NS_ASSERTION(aData->Count() == 2 || aData->Count() == 3, "Bad array!");
+  
+  float xSkew = CSSToRadians(aData->Item(1));
+  float ySkew = (aData->Count() == 2 ? xSkew : CSSToRadians(aData->Item(2)));
+
+  ProcessSkewHelper(xSkew, ySkew, aMain);
+}
+
+/* Function that converts a rotate transform into a matrix. */
+static void ProcessRotate(float aMain[4], const nsCSSValue::Array* aData)
+{
+  NS_PRECONDITION(aData->Count() == 2, "Invalid array!");
+
+  /* We want our matrix to look like this:
+   * |  cos(theta)  -sin(theta)  0|
+   * |  sin(theta)   cos(theta)  0|
+   * |           0            0  1|
+   * However, there's a bit of a problem - our coordinate system has Y
+   * increasing downward.  Thus we will rotate by -theta
+   * degrees.  Thus:
+   * A = cos(theta), B = -sin(theta), C = sin(theta), D = cos(theta)
+   * (see http://www.w3.org/TR/SVG/coords.html#RotationDefined)
+   */
+  float theta = CSSToRadians(aData->Item(1));
+  float cosTheta = cos(theta);
+  float sinTheta = sin(theta);
+
+  aMain[0] = cosTheta;
+  aMain[1] = -sinTheta;
+  aMain[2] = sinTheta;
+  aMain[3] = cosTheta;
+}
+
+/**
+ * SetToTransformFunction is essentially a giant switch statement that fans
+ * out to many smaller helper functions.
+ */
+void
+nsStyleTransformMatrix::SetToTransformFunction(const nsCSSValue::Array * aData,
+                                               nsStyleContext* aContext,
+                                               nsPresContext* aPresContext)
+{
+  NS_PRECONDITION(aData, "Why did you want to get data from a null array?");
+  NS_PRECONDITION(aContext, "Need a context for unit conversion!");
+  NS_PRECONDITION(aPresContext, "Need a context for unit conversion!");
+  
+  /* Reset the matrix to the identity so that each subfunction can just
+   * worry about its own components.
+   */
+  SetToIdentity();
+
+  /* Get the keyword for the transform. */
+  nsAutoString keyword;
+  aData->Item(0).GetStringValue(keyword);
+  switch (nsCSSKeywords::LookupKeyword(keyword)) {
+  case eCSSKeyword_translatex:
+    ProcessTranslateX(mDelta, mX, aData, aContext, aPresContext);
+    break;
+  case eCSSKeyword_translatey:
+    ProcessTranslateY(mDelta, mY, aData, aContext, aPresContext);
+    break;
+  case eCSSKeyword_translate:
+    ProcessTranslate(mDelta, mX, mY, aData, aContext, aPresContext);
+    break;
+  case eCSSKeyword_scalex:
+    ProcessScaleX(mMain, aData);
+    break;
+  case eCSSKeyword_scaley:
+    ProcessScaleY(mMain, aData);
+    break;
+  case eCSSKeyword_scale:
+    ProcessScale(mMain, aData);
+    break;
+  case eCSSKeyword_skewx:
+    ProcessSkewX(mMain, aData);
+    break;
+  case eCSSKeyword_skewy:
+    ProcessSkewY(mMain, aData);
+    break;
+  case eCSSKeyword_skew:
+    ProcessSkew(mMain, aData);
+    break;
+  case eCSSKeyword_rotate:
+    ProcessRotate(mMain, aData);
+    break;
+  case eCSSKeyword_matrix:
+    ProcessMatrix(mMain, mDelta, mX, mY, aData, aContext, aPresContext);
+    break;
+  default:
+    NS_NOTREACHED("Unknown transform function!");
+  }
+}
+
+/* Does an element-by-element comparison and returns whether or not the
+ * matrices are equal.
+ */
+PRBool
+nsStyleTransformMatrix::operator ==(const nsStyleTransformMatrix &aOther) const
+{
+  for (PRInt32 index = 0; index < 4; ++index)
+    if (mMain[index] != aOther.mMain[index])
+      return PR_FALSE;
+
+  for (PRInt32 index = 0; index < 2; ++index)
+    if (mDelta[index] != aOther.mDelta[index] ||
+	mX[index] != aOther.mX[index] ||
+	mY[index] != aOther.mY[index])
+      return PR_FALSE;
+
+  return PR_TRUE;
+}
new file mode 100644
--- /dev/null
+++ b/layout/style/nsStyleTransformMatrix.h
@@ -0,0 +1,172 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ *   Mozilla Corporation
+ *
+ * Contributor(s):
+ *   Keith Schwarz <kschwarz@mozilla.com> (original author)
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either of the GNU General Public License Version 2 or later (the "GPL"),
+ * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/*
+ * A class representing three matrices that can be used for style transforms.
+ */
+
+#ifndef nsStyleTransformMatrix_h_
+#define nsStyleTransformMatrix_h_
+
+#include "nsCSSValue.h"
+#include "gfxMatrix.h"
+#include "nsRect.h"
+
+/**
+ * A class representing a style transformation matrix.  The class actually
+ * wraps three different matrices, a constant matrix and two matrices
+ * whose values are scaled by the width and the height of the bounding
+ * rectangle for the object to transform.
+ */
+class nsStyleContext;
+class nsPresContext;
+class nsStyleTransformMatrix
+{
+ public:
+  /**
+   * Constructor sets the matrix to the identity.
+   */
+  nsStyleTransformMatrix();
+
+  /**
+   * Given a frame's bounding rectangle, returns a gfxMatrix
+   * corresponding to the transformation represented by this
+   * matrix.  The transformation takes points in the frame's
+   * local space and converts them to points in the frame's
+   * transformed space.
+   *
+   * @param aBounds The frame's bounding rectangle.
+   * @param aFactor The number of app units per device pixel.
+   * @return A Thebes matrix corresponding to the transform.
+   */
+  gfxMatrix GetThebesMatrix(const nsRect& aBounds, PRInt32 aFactor) const;
+
+  /**
+   * Multiplies this matrix by another matrix.  The multiplication is
+   * a post-multiplication, so A *= B updates this matrix to be BA.
+   *
+   * @param aOther The matrix to multiply this matrix by.
+   * @return A reference to this matrix.
+   */
+  nsStyleTransformMatrix& operator *= (const nsStyleTransformMatrix &aOther);
+
+  /**
+   * Returns a new nsStyleTransformMatrix that's equal to this matrix
+   * post-multiplied with another matrix.
+   *
+   * @param aOther The matrix to multiply this matrix by.
+   * @return A new nsStyleTransformMatrix equal to this matrix postmultiplied
+   *         with the other matrix.
+   */
+  const nsStyleTransformMatrix
+    operator * (const nsStyleTransformMatrix &aOther) const;
+
+  /**
+   * Given an nsCSSValue::Array* containing a -moz-transform function,
+   * updates this matrix to hold the value of that function.
+   *
+   * @param aData The nsCSSValue::Array* containing the transform function.
+   * @param aContext The style context, used for unit conversion.
+   * @param aPresContext The presentation context, used for unit conversion.
+   */
+  void SetToTransformFunction(const nsCSSValue::Array* aData,
+                              nsStyleContext* aContext,
+                              nsPresContext* aPresContext);
+
+  /**
+   * Sets this matrix to be the identity matrix.
+   */
+  void SetToIdentity();
+
+  /**
+   * Returns the value of the entry at the 2x2 submatrix of the
+   * transform matrix that defines the non-affine linear transform.
+   * The order is given as
+   * |elem[0]  elem[2]|
+   * |elem[1]  elem[3]|
+   *
+   * @param aIndex The element index.
+   * @return The value of the element at that index.
+   */
+  float GetMainMatrixEntry(PRInt32 aIndex) const
+  {
+    NS_PRECONDITION(aIndex >= 0 && aIndex < 4, "Index out of bounds!");
+    return mMain[aIndex];
+  }
+
+  /**
+   * Returns the value of the X or Y translation component of the matrix,
+   * given the specified bounds.
+   *
+   * @param aBounds The bounds of the element.
+   * @return The value of the X or Ytranslation component.
+   */
+  nscoord GetXTranslation(const nsRect& aBounds) const;
+  nscoord GetYTranslation(const nsRect& aBounds) const;
+
+  /**
+   * Returns whether the two matrices are equal or not.
+   *
+   * @param aOther The matrix to compare to.
+   * @return Whether the two matrices are equal.
+   */
+  PRBool operator== (const nsStyleTransformMatrix& aOther) const;
+  PRBool operator!= (const nsStyleTransformMatrix& aOther) const
+  {
+    return !(*this == aOther);
+  }
+
+ private:
+  /* The three matrices look like this:
+   * |mMain[0] mMain[2] mDelta[0]|
+   * |mMain[1] mMain[3] mDelta[1]| <-- Constant matrix
+   * |       0        0         1|
+   *
+   * |       0        0     mX[0]|
+   * |       0        0     mX[1]| <-- Scaled by width of element
+   * |       0        0         1|
+   *
+   * |       0        0     mY[0]|
+   * |       0        0     mY[1]| <-- Scaled by height of element
+   * |       0        0         1|
+   */
+  float mMain[4];
+  nscoord  mDelta[2];
+  float mX[2];
+  float mY[2];
+};
+
+#endif
--- a/layout/style/test/property_database.js
+++ b/layout/style/test/property_database.js
@@ -499,16 +499,41 @@ var gCSSProperties = {
 		inherited: false,
 		type: CSS_TYPE_SHORTHAND_AND_LONGHAND,
 		get_computed: logical_box_prop_get_computed,
 		/* no subproperties */
 		initial_values: [ "0", "0px", "0%", "0em", "0ex" ],
 		other_values: [ "1px", "3em" ],
 		invalid_values: []
 	},
+	"-moz-transform": {
+		domProp: "MozTransform",
+		inherited: false,
+		type: CSS_TYPE_LONGHAND,
+		initial_values: [ "none" ],
+		other_values: [ "translatex(1px)", "translatex(4em)", "translatex(-4px)", "translatex(3px)", "translatex(0px) translatex(1px) translatex(2px) translatex(3px) translatex(4px)", "translatey(4em)", "translate(3px)", "translate(10px, -3px)", "rotate(45deg)", "rotate(45grad)", "rotate(45rad)", "rotate(0)", "scalex(10)", "scaley(10)", "scale(10)", "scale(10, 20)", "skewx(30deg)", "skewx(0)", "skewy(0)", "skewx(30grad)", "skewx(30rad)", "skewy(30deg)", "skewy(30grad)", "skewy(30rad)", "matrix(1, 2, 3, 4, 5px, 6em)", "rotate(45deg) scale(2, 1)", "skewx(45deg) skewx(-50grad)", "translate(0, 0) scale(1, 1) skewx(0) skewy(0) matrix(1, 0, 0, 1, 0, 0)", "translatex(50%)", "translatey(50%)", "translate(50%)", "translate(3%, 5px)", "translate(5px, 3%)", "matrix(1, 2, 3, 4, 5px, 6%)", "matrix(1, 2, 3, 4, 5%, 6px)", "matrix(1, 2, 3, 4, 5%, 6%)"],
+		invalid_values: ["1px", "#0000ff", "red", "auto", "translatex(1px 1px)", "translatex(translatex(1px))", "translatex(#0000ff)", "translatex(red)", "translatey()", "matrix(1, 2, 3, 4, 5, 6)", "matrix(1px, 2px, 3px, 4px, 5px, 6px)", "scale(150%)", "skewx(red)", "matrix(1%, 0, 0, 0, 0px, 0px)", "matrix(0, 1%, 2, 3, 4px,5px)", "matrix(0, 1, 2%, 3, 4px, 5px)", "matrix(0, 1, 2, 3%, 4%, 5%)"]
+	},
+	"-moz-transform-origin": {
+		domProp: "MozTransformOrigin",
+		inherited: false,
+		type: CSS_TYPE_LONGHAND,
+		/* no subproperties */
+		prerequisites: { "width": "10px", "height": "10px", "display": "block"},
+		initial_values: [ "50% 50%", "center", "center center" ],
+		other_values: [ "25% 25%", "5px 5px", "20% 3em", "0 0", "0in 1in",
+						"top", "bottom","top left", "top right",
+						"top center", "center left", "center right",
+						"bottom left", "bottom right", "bottom center",
+						"20% center", "5px center", "13in bottom",
+						"left 50px", "right 13%", "center 40px"],
+		invalid_values: ["red", "auto", "none", "0.5 0.5", "40px #0000ff",
+						 "border", "center red", "right diagonal",
+						 "#00ffff bottom"]
+	},
 	"-moz-stack-sizing": {
 		domProp: "MozStackSizing",
 		inherited: false,
 		type: CSS_TYPE_LONGHAND,
 		initial_values: [ "stretch-to-fit" ],
 		other_values: [ "ignore" ],
 		invalid_values: []
 	},
--- a/layout/svg/base/src/nsSVGForeignObjectFrame.cpp
+++ b/layout/svg/base/src/nsSVGForeignObjectFrame.cpp
@@ -300,16 +300,30 @@ nsSVGForeignObjectFrame::TransformPointF
 
   float x = PresContext()->AppUnitsToDevPixels(aIn.x);
   float y = PresContext()->AppUnitsToDevPixels(aIn.y);
   nsSVGUtils::TransformPoint(inverse, &x, &y);
   *aOut = nsPoint(PresContext()->DevPixelsToAppUnits(NSToIntRound(x)),
                   PresContext()->DevPixelsToAppUnits(NSToIntRound(y)));
   return NS_OK;
 }
+
+gfxMatrix
+nsSVGForeignObjectFrame::GetTransformMatrix(nsIFrame **aOutAncestor)
+{
+  NS_PRECONDITION(aOutAncestor, "We need an ancestor to write to!");
+
+  /* Set the ancestor to be the outer frame. */
+  *aOutAncestor = nsSVGUtils::GetOuterSVGFrame(this);
+  NS_ASSERTION(*aOutAncestor, "How did we end up without an outer frame?");
+
+  /* Return the matrix back to the root, factoring in the x and y offsets. */
+  nsCOMPtr<nsIDOMSVGMatrix> matrix = GetTMIncludingOffset();
+  return nsSVGUtils::ConvertSVGMatrixToThebes(matrix);
+}
  
 NS_IMETHODIMP_(nsIFrame*)
 nsSVGForeignObjectFrame::GetFrameForPoint(const nsPoint &aPoint)
 {
   if (IsDisabled())
     return NS_OK;
 
   nsIFrame* kid = GetFirstChild(nsnull);
--- a/layout/svg/base/src/nsSVGForeignObjectFrame.h
+++ b/layout/svg/base/src/nsSVGForeignObjectFrame.h
@@ -76,16 +76,29 @@ public:
   }
 
   NS_IMETHOD Reflow(nsPresContext*           aPresContext,
                     nsHTMLReflowMetrics&     aDesiredSize,
                     const nsHTMLReflowState& aReflowState,
                     nsReflowStatus&          aStatus);
 
   /**
+   * Foreign objects are always transformed.
+   */
+  virtual PRBool IsTransformed() const
+  {
+    return PR_TRUE;
+  }
+
+  /**
+   * Foreign objects can return a transform matrix.
+   */
+  virtual gfxMatrix GetTransformMatrix(nsIFrame **aOutAncestor);
+
+  /**
    * Get the "type" of the frame
    *
    * @see nsGkAtoms::svgForeignObjectFrame
    */
   virtual nsIAtom* GetType() const;
 
   virtual PRBool IsFrameOfType(PRUint32 aFlags) const
   {
--- a/view/public/nsIView.h
+++ b/view/public/nsIView.h
@@ -82,16 +82,21 @@ enum nsViewVisibility {
 // displayed above z-index:auto views if this view 
 // is z-index:auto also
 #define NS_VIEW_FLAG_TOPMOST              0x0010
 
 // If set, the view disowns the widget and leaves it up
 // to other code to destroy it.
 #define NS_VIEW_DISOWNS_WIDGET             0x0020
 
+// If set, the view should always invalidate its frame
+// during a scroll instead of doing a BitBlt.  This bit
+// is propagated down to children.
+#define NS_VIEW_FLAG_INVALIDATE_ON_SCROLL  0x0040
+
 struct nsViewZIndex {
   PRBool mIsAuto;
   PRInt32 mZIndex;
   PRBool mIsTopmost;
   
   nsViewZIndex(PRBool aIsAuto, PRInt32 aZIndex, PRBool aIsTopmost)
     : mIsAuto(aIsAuto), mZIndex(aZIndex), mIsTopmost(aIsTopmost) {}
 };
@@ -310,16 +315,32 @@ public:
   /**
    * If called, will make the view disown the widget and leave it up
    * to other code to destroy it.
    */
   void DisownWidget() {
     mVFlags |= NS_VIEW_DISOWNS_WIDGET;
   }
 
+  /**
+   * If called, will make the view invalidate its frame instead of BitBlitting
+   * it when there's a scroll.
+   */
+  void SetInvalidateFrameOnScroll()
+  {
+    mVFlags |= NS_VIEW_FLAG_INVALIDATE_ON_SCROLL;
+  }
+
+  /**
+   * Returns whether or not we should automatically fail to BitBlt when scrolling.
+   * This is true if either we're marked to have invalidate on scroll or if some
+   * ancestor does.
+   */
+  PRBool NeedsInvalidateFrameOnScroll() const;
+
 #ifdef DEBUG
   /**
    * Output debug info to FILE
    * @param out output file handle
    * @param aIndent indentation depth
    * NOTE: virtual so that debugging tools not linked into gklayout can access it
    */
   virtual void List(FILE* out, PRInt32 aIndent = 0) const;
--- a/view/public/nsIViewObserver.h
+++ b/view/public/nsIViewObserver.h
@@ -42,18 +42,18 @@
 #include "nsEvent.h"
 #include "nsColor.h"
 #include "nsRect.h"
 
 class nsIRenderingContext;
 class nsGUIEvent;
 
 #define NS_IVIEWOBSERVER_IID   \
-{ 0x0f4bc34a, 0xc93b, 0x4699, \
-{ 0xb6, 0xc2, 0xb3, 0xca, 0x9e, 0xe4, 0x6c, 0x95 } }
+{ 0x63ae23ee, 0xe251, 0x4005, \
+{ 0xaf, 0xe4, 0x5b, 0x0f, 0xa1, 0x5a, 0xb4, 0x99 } }
 
 class nsIViewObserver : public nsISupports
 {
 public:
   
   NS_DECLARE_STATIC_IID_ACCESSOR(NS_IVIEWOBSERVER_IID)
 
   /* called when the observer needs to paint. This paints the entire
@@ -109,13 +109,19 @@ public:
   NS_IMETHOD_(PRBool) IsVisible() = 0;
 
   /**
    * Notify the observer that we're about to start painting.  This
    * gives the observer a chance to make some last-minute invalidates
    * and geometry changes if it wants to.
    */
   NS_IMETHOD_(void) WillPaint() = 0;
+
+  /**
+   * Notify the observer that it should invalidate the frame bounds for
+   * the frame associated with this view.
+   */
+  NS_IMETHOD_(void) InvalidateFrameForView(nsIView *aView) = 0;
 };
 
 NS_DEFINE_STATIC_IID_ACCESSOR(nsIViewObserver, NS_IVIEWOBSERVER_IID)
 
 #endif
--- a/view/src/nsScrollPortView.cpp
+++ b/view/src/nsScrollPortView.cpp
@@ -515,16 +515,22 @@ NS_IMETHODIMP nsScrollPortView::CanScrol
   return NS_OK;
 }
 
 void nsScrollPortView::Scroll(nsView *aScrolledView, nsPoint aTwipsDelta, nsPoint aPixDelta,
                               PRInt32 aP2A)
 {
   if (aTwipsDelta.x != 0 || aTwipsDelta.y != 0)
   {
+    /* If we should invalidate our wrapped view, we should do so at this
+     * point.
+     */
+    if (aScrolledView->NeedsInvalidateFrameOnScroll())
+      GetViewManager()->GetViewObserver()->InvalidateFrameForView(aScrolledView);
+    
     nsIWidget *scrollWidget = GetWidget();
     nsRegion updateRegion;
     PRBool canBitBlit = scrollWidget &&
                         ((mScrollProperties & NS_SCROLL_PROPERTY_ALWAYS_BLIT) ||
                          mViewManager->CanScrollWithBitBlt(aScrolledView, aTwipsDelta, &updateRegion));
 
     if (canBitBlit) {
       // We're going to bit-blit.  Let the viewmanager know so it can
--- a/view/src/nsView.cpp
+++ b/view/src/nsView.cpp
@@ -41,17 +41,16 @@
 #include "nsGUIEvent.h"
 #include "nsIDeviceContext.h"
 #include "nsIComponentManager.h"
 #include "nsIScrollableView.h"
 #include "nsGfxCIID.h"
 #include "nsIRegion.h"
 #include "nsIInterfaceRequestor.h"
 
-
 //mmptemp
 
 static nsEventStatus PR_CALLBACK HandleEvent(nsGUIEvent *aEvent);
 
 
 //#define SHOW_VIEW_BORDERS
 //#define HIDE_ALL_WIDGETS
 
@@ -870,8 +869,20 @@ PRBool nsIView::ExternalIsRoot() const
 void
 nsIView::SetDeletionObserver(nsWeakView* aDeletionObserver)
 {
   if (mDeletionObserver && aDeletionObserver) {
     aDeletionObserver->SetPrevious(mDeletionObserver);
   }
   mDeletionObserver = aDeletionObserver;
 }
+
+/* We invalidate the frame on a scroll iff this frame is marked as such or if
+ * some parent is.
+ */
+PRBool nsIView::NeedsInvalidateFrameOnScroll() const
+{
+  for (const nsIView *currView = this; currView != nsnull; currView = currView->GetParent())
+    if (currView->mVFlags & NS_VIEW_FLAG_INVALIDATE_ON_SCROLL)
+      return PR_TRUE;
+  
+  return PR_FALSE;
+}
--- a/view/src/nsViewManager.cpp
+++ b/view/src/nsViewManager.cpp
@@ -118,16 +118,17 @@ static PRBool IsViewVisible(nsView *aVie
   for (nsIView *view = aView; view; view = view->GetParent()) {
     // We don't check widget visibility here because in the future (with
     // the better approach to this that's in attachment 160801 on bug
     // 227361), callers of the equivalent to this function should be able
     // to rely on being notified when the result of this function changes.
     if (view->GetVisibility() == nsViewVisibility_kHide)
       return PR_FALSE;
   }
+
   // Find out if the root view is visible by asking the view observer
   // (this won't be needed anymore if we link view trees across chrome /
   // content boundaries in DocumentViewerImpl::MakeWindow).
   nsIViewObserver* vo = aView->GetViewManager()->GetViewObserver();
   return vo && vo->IsVisible();
 }
 
 void
@@ -724,16 +725,17 @@ nsresult
 nsViewManager::WillBitBlit(nsView* aView, nsPoint aScrollAmount)
 {
   if (!IsRootVM()) {
     RootViewManager()->WillBitBlit(aView, aScrollAmount);
     return NS_OK;
   }
 
   NS_PRECONDITION(aView, "Must have a view");
+  NS_PRECONDITION(!aView->NeedsInvalidateFrameOnScroll(), "We shouldn't be BitBlting.");
   NS_PRECONDITION(aView->HasWidget(), "View must have a widget");
 
   ++mScrollCnt;
   
   // Since the view is actually moving the widget by -aScrollAmount, that's the
   // offset we want to use when accumulating dirty rects.
   AccumulateIntersectionsIntoDirtyRegion(aView, GetRootView(), -aScrollAmount);
   return NS_OK;