Bug 505115 - Part 14b - Layout support for -moz-transform-style. r=roc
authorMatt Woodrow <mwoodrow@mozilla.com>
Sat, 27 Aug 2011 12:01:46 +1200
changeset 77286 364548e43748b8f9c9fb30312f2fddf2bc5c84a8
parent 77285 14eb4130996517df8cf6f2301d5eba2f8677305f
child 77287 1de7e151b2ad93b5c1261053bb8fd36a00a83f7d
push id78
push userclegnitto@mozilla.com
push dateFri, 16 Dec 2011 17:32:24 +0000
treeherdermozilla-release@79d24e644fdd [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersroc
bugs505115
milestone9.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 505115 - Part 14b - Layout support for -moz-transform-style. r=roc
layout/base/nsDisplayList.cpp
layout/base/nsDisplayList.h
layout/base/nsLayoutDebugger.cpp
layout/generic/nsFrame.cpp
layout/generic/nsFrame.h
layout/generic/nsIFrame.h
layout/style/nsStyleStruct.h
--- a/layout/base/nsDisplayList.cpp
+++ b/layout/base/nsDisplayList.cpp
@@ -757,25 +757,43 @@ static PRBool IsContentLEQ(nsDisplayItem
   // These GetUnderlyingFrame calls return non-null because we're only used
   // in sorting
   return nsLayoutUtils::CompareTreePosition(
       aItem1->GetUnderlyingFrame()->GetContent(),
       aItem2->GetUnderlyingFrame()->GetContent(),
       static_cast<nsIContent*>(aClosure)) <= 0;
 }
 
+static PRBool IsZPositionLEQ(nsDisplayItem* aItem1, nsDisplayItem* aItem2,
+                             void* aClosure) {
+  if (!aItem1->GetUnderlyingFrame()->Preserves3D() ||
+      !aItem1->GetUnderlyingFrame()->Preserves3D()) {
+    return IsContentLEQ(aItem1, aItem2, aClosure);
+  }
+
+  nsIFrame* ancestor;
+  gfx3DMatrix matrix1 = aItem1->GetUnderlyingFrame()->GetTransformMatrix(&ancestor);
+  gfx3DMatrix matrix2 = aItem2->GetUnderlyingFrame()->GetTransformMatrix(&ancestor);
+
+  if (matrix1._43 == matrix2._43) {
+    return IsContentLEQ(aItem1, aItem2, aClosure);
+  }
+
+  return matrix1._43 < matrix2._43;
+}
+
 static PRBool IsZOrderLEQ(nsDisplayItem* aItem1, nsDisplayItem* aItem2,
                           void* aClosure) {
   // These GetUnderlyingFrame calls return non-null because we're only used
   // in sorting.  Note that we can't just take the difference of the two
   // z-indices here, because that might overflow a 32-bit int.
   PRInt32 index1 = nsLayoutUtils::GetZIndex(aItem1->GetUnderlyingFrame());
   PRInt32 index2 = nsLayoutUtils::GetZIndex(aItem2->GetUnderlyingFrame());
   if (index1 == index2)
-    return IsContentLEQ(aItem1, aItem2, aClosure);
+    return IsZPositionLEQ(aItem1, aItem2, aClosure);
   return index1 < index2;
 }
 
 void nsDisplayList::ExplodeAnonymousChildLists(nsDisplayListBuilder* aBuilder) {
   // See if there's anything to do
   PRBool anyAnonymousItems = PR_FALSE;
   nsDisplayItem* i;
   for (i = GetBottom(); i != nsnull; i = i->GetAbove()) {
@@ -812,16 +830,21 @@ void nsDisplayList::SortByZOrder(nsDispl
   Sort(aBuilder, IsZOrderLEQ, aCommonAncestor);
 }
 
 void nsDisplayList::SortByContentOrder(nsDisplayListBuilder* aBuilder,
                                        nsIContent* aCommonAncestor) {
   Sort(aBuilder, IsContentLEQ, aCommonAncestor);
 }
 
+void nsDisplayList::SortByZPosition(nsDisplayListBuilder* aBuilder,
+                                    nsIContent* aCommonAncestor) {
+  Sort(aBuilder, IsZPositionLEQ, aCommonAncestor);
+}
+
 void nsDisplayList::Sort(nsDisplayListBuilder* aBuilder,
                          SortLEQ aCmp, void* aClosure) {
   ExplodeAnonymousChildLists(aBuilder);
   ::Sort(this, Count(), aCmp, aClosure);
 }
 
 PRBool nsDisplayItem::RecomputeVisibility(nsDisplayListBuilder* aBuilder,
                                           nsRegion* aVisibleRegion) {
@@ -2414,21 +2437,31 @@ gfxPoint3D GetDeltaToMozPerspectiveOrigi
 /* 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.
  */
 gfx3DMatrix
 nsDisplayTransform::GetResultingTransformMatrix(const nsIFrame* aFrame,
                                                 const nsPoint &aOrigin,
                                                 float aFactor,
-                                                const nsRect* aBoundsOverride)
+                                                const nsRect* aBoundsOverride,
+                                                nsIFrame** aOutAncestor)
 {
   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!");
+
+  if (aOutAncestor) {
+      *aOutAncestor = nsLayoutUtils::GetCrossDocParentFrame(aFrame);
+  }
+
+  /* Preserve-3d can cause frames without a transform to get an nsDisplayTransform created, we should
+   * use our parent's transform here.
+   */
+  if (!aFrame->GetStyleDisplay()->HasTransform()) {
+    return GetResultingTransformMatrix(aFrame->GetParent(), aOrigin - aFrame->GetPosition(), aFactor, nsnull, aOutAncestor);
+  }
 
   /* Account for the -moz-transform-origin property by translating the
    * coordinate space to the new origin.
    */
   gfxPoint3D toMozOrigin = GetDeltaToMozTransformOrigin(aFrame, aFactor, aBoundsOverride);
   gfxPoint3D toPerspectiveOrigin = GetDeltaToMozPerspectiveOrigin(aFrame, aFactor, aBoundsOverride);
   gfxPoint3D newOrigin = gfxPoint3D(NSAppUnitsToFloatPixels(aOrigin.x, aFactor),
                                     NSAppUnitsToFloatPixels(aOrigin.y, aFactor),
@@ -2438,35 +2471,53 @@ nsDisplayTransform::GetResultingTransfor
    * bounds of the frame.
    */
   const nsStyleDisplay* disp = aFrame->GetStyleDisplay();
   nsRect bounds = (aBoundsOverride ? *aBoundsOverride :
                    nsDisplayTransform::GetFrameBoundsForTransform(aFrame));
 
   /* Get the matrix, then change its basis to factor in the origin. */
   PRBool dummy;
-  gfx3DMatrix result =
-    nsStyleTransformMatrix::ReadTransforms(disp->mSpecifiedTransform,
-                                           aFrame->GetStyleContext(),
-                                           aFrame->PresContext(),
-                                           dummy, bounds, aFactor);
+  gfx3DMatrix result;
+  /* Transformed frames always have a transform, or are preserving 3d (and might still have perspective!) */
+  if (disp->mSpecifiedTransform) {
+    result = nsStyleTransformMatrix::ReadTransforms(disp->mSpecifiedTransform,
+                                                    aFrame->GetStyleContext(),
+                                                    aFrame->PresContext(),
+                                                    dummy, bounds, aFactor);
+  } else {
+     NS_ASSERTION(aFrame->Preserves3DChildren(),
+                  "If we don't have a transform, then we must be at least preserving transforms of our children");
+  }
 
   const nsStyleDisplay* parentDisp = nsnull;
   if (aFrame->GetParent()) {
     parentDisp = aFrame->GetParent()->GetStyleDisplay();
   }
   if (nsLayoutUtils::Are3DTransformsEnabled() &&
       parentDisp && parentDisp->mChildPerspective.GetUnit() == eStyleUnit_Coord &&
       parentDisp->mChildPerspective.GetCoordValue() > 0.0) {
     gfx3DMatrix perspective;
     perspective._34 =
       -1.0 / NSAppUnitsToFloatPixels(parentDisp->mChildPerspective.GetCoordValue(),
                                      aFactor);
     result = result * nsLayoutUtils::ChangeMatrixBasis(toPerspectiveOrigin, perspective);
   }
+
+  if (aFrame->Preserves3D() && nsLayoutUtils::Are3DTransformsEnabled()) {
+      // Include the transform set on our parent
+      NS_ASSERTION(aFrame->GetParent() &&
+                   aFrame->GetParent()->IsTransformed() &&
+                   aFrame->GetParent()->Preserves3DChildren(),
+                   "Preserve3D mismatch!");
+      gfx3DMatrix parent = GetResultingTransformMatrix(aFrame->GetParent(), aOrigin - aFrame->GetPosition(),
+                                                       aFactor, nsnull, aOutAncestor);
+      return nsLayoutUtils::ChangeMatrixBasis(newOrigin + toMozOrigin, result) * parent;
+  }
+
   return nsLayoutUtils::ChangeMatrixBasis
     (newOrigin + toMozOrigin, result);
 }
 
 const gfx3DMatrix&
 nsDisplayTransform::GetTransform(float aFactor)
 {
   if (mTransform.IsIdentity() || mCachedFactor != aFactor) {
@@ -2734,51 +2785,44 @@ nsDisplayTransform::TryMerge(nsDisplayLi
  *         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::TransformRectOut(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::MatrixTransformRectOut
     (aUntransformedBounds,
      GetResultingTransformMatrix(aFrame, aOrigin, factor, aBoundsOverride),
      factor);
 }
 
 PRBool nsDisplayTransform::UntransformRect(const nsRect &aUntransformedBounds,
                                            const nsIFrame* aFrame,
                                            const nsPoint &aOrigin,
                                            nsRect* aOutRect)
 {
   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();
   gfx3DMatrix matrix = GetResultingTransformMatrix(aFrame, aOrigin, factor, nsnull);
   if (matrix.IsSingular())
     return PR_FALSE;
--- a/layout/base/nsDisplayList.h
+++ b/layout/base/nsDisplayList.h
@@ -1004,16 +1004,18 @@ public:
    * GetUnderlyingFrame() on each item. z-index is ignored.
    * @param aCommonAncestor a common ancestor of all the content elements
    * associated with the display items, for speeding up tree order
    * checks, or nsnull if not known; it's only a hint, if it is not an
    * ancestor of some elements, then we lose performance but not correctness
    */
   void SortByContentOrder(nsDisplayListBuilder* aBuilder, nsIContent* aCommonAncestor);
 
+  void SortByZPosition(nsDisplayListBuilder* aBuilder, nsIContent* aCommonAncestor);
+
   /**
    * Generic stable sort. Take care, because some of the items might be nsDisplayLists
    * themselves.
    * aCmp(item1, item2) should return true if item1 <= item2. We sort the items
    * into increasing order.
    */
   typedef PRBool (* SortLEQ)(nsDisplayItem* aItem1, nsDisplayItem* aItem2,
                              void* aClosure);
@@ -2075,16 +2077,23 @@ public:
    */
   nsDisplayTransform(nsDisplayListBuilder* aBuilder, nsIFrame *aFrame,
                      nsDisplayList *aList) :
     nsDisplayItem(aBuilder, aFrame), mStoredList(aBuilder, aFrame, aList)
   {
     MOZ_COUNT_CTOR(nsDisplayTransform);
   }
 
+  nsDisplayTransform(nsDisplayListBuilder* aBuilder, nsIFrame *aFrame,
+                     nsDisplayItem *aItem) :
+  nsDisplayItem(aBuilder, aFrame), mStoredList(aBuilder, aFrame, aItem)
+  {
+    MOZ_COUNT_CTOR(nsDisplayTransform);
+  }
+
 #ifdef NS_BUILD_REFCNT_LOGGING
   virtual ~nsDisplayTransform()
   {
     MOZ_COUNT_DTOR(nsDisplayTransform);
   }
 #endif
 
   NS_DISPLAY_DECL_NAME("nsDisplayTransform", TYPE_TRANSFORM);
@@ -2179,21 +2188,22 @@ public:
    *        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 gfx3DMatrix GetResultingTransformMatrix(const nsIFrame* aFrame,
                                                  const nsPoint& aOrigin,
                                                  float aFactor,
-                                                 const nsRect* aBoundsOverride = nsnull);
+                                                 const nsRect* aBoundsOverride = nsnull,
+                                                 nsIFrame** aOutAncestor = nsnull);
 
 private:
   nsDisplayWrapList mStoredList;
-  gfx3DMatrix mTransform;
+  gfx3DMatrix mTransform;
   float mCachedFactor;
 };
 
 /**
  * This class adds basic support for limiting the rendering to the part inside
  * the specified edges.  It's a base class for the display item classes that
  * does the actual work.  The two members, mLeftEdge and mRightEdge, are
  * relative to the edges of the frame's scrollable overflow rectangle and is
--- a/layout/base/nsLayoutDebugger.cpp
+++ b/layout/base/nsLayoutDebugger.cpp
@@ -174,23 +174,23 @@ PrintDisplayListTo(nsDisplayListBuilder*
       }
       default:
         break;
     }
     nscolor color;
     nsRect vis = i->GetVisibleRect();
     nsDisplayList* list = i->GetList();
     nsRegion opaque;
+    if (i->GetType() == nsDisplayItem::TYPE_TRANSFORM) {
+        nsDisplayTransform* t = static_cast<nsDisplayTransform*>(i);
+        list = t->GetStoredList()->GetList();
+    }
     if (!list || list->DidComputeVisibility()) {
       opaque = i->GetOpaqueRegion(aBuilder);
     }
-    if (i->GetType() == nsDisplayItem::TYPE_TRANSFORM) {
-      nsDisplayTransform* t = static_cast<nsDisplayTransform*>(i);
-      list = t->GetStoredList()->GetList();
-    }
     fprintf(aOutput, "%s %p(%s) (%d,%d,%d,%d)(%d,%d,%d,%d)%s%s",
             i->Name(), (void*)f, NS_ConvertUTF16toUTF8(fName).get(),
             rect.x, rect.y, rect.width, rect.height,
             vis.x, vis.y, vis.width, vis.height,
             opaque.IsEmpty() ? "" : " opaque",
             i->IsUniform(aBuilder, &color) ? " uniform" : "");
     if (f) {
       PRUint32 key = i->GetPerFrameKey();
--- a/layout/generic/nsFrame.cpp
+++ b/layout/generic/nsFrame.cpp
@@ -249,16 +249,26 @@ nsFrame::RootFrameList(nsPresContext* aP
     nsIFrame* frame = shell->FrameManager()->GetRootFrame();
     if(frame) {
       frame->List(out, aIndent);
     }
   }
 }
 #endif
 
+static PRBool ApplyOverflowClipping(nsDisplayListBuilder* aBuilder,
+                                    const nsIFrame* aFrame,
+                                    const nsStyleDisplay* aDisp, 
+                                    nsRect* aRect);
+
+static PRBool ApplyAbsPosClipping(nsDisplayListBuilder* aBuilder,
+                                  const nsStyleDisplay* aDisp, 
+                                  const nsIFrame* aFrame,
+                                  nsRect* aRect);
+
 void
 NS_MergeReflowStatusInto(nsReflowStatus* aPrimary, nsReflowStatus aSecondary)
 {
   *aPrimary |= aSecondary &
     (NS_FRAME_NOT_COMPLETE | NS_FRAME_OVERFLOW_INCOMPLETE |
      NS_FRAME_TRUNCATED | NS_FRAME_REFLOW_NEXTINFLOW);
   if (*aPrimary & NS_FRAME_NOT_COMPLETE) {
     *aPrimary &= ~NS_FRAME_OVERFLOW_INCOMPLETE;
@@ -757,16 +767,35 @@ nsIFrame::GetPaddingRect() const
 
 PRBool
 nsIFrame::IsTransformed() const
 {
   return (mState & NS_FRAME_MAY_BE_TRANSFORMED) &&
     GetStyleDisplay()->HasTransform();
 }
 
+PRBool
+nsIFrame::Preserves3DChildren() const
+{
+  return GetStyleDisplay()->mTransformStyle == NS_STYLE_TRANSFORM_STYLE_PRESERVE_3D && IsTransformed();
+}
+
+PRBool
+nsIFrame::Preserves3D() const
+{
+  if (!GetParent() || !GetParent()->Preserves3DChildren() || !IsTransformed()) {
+    return PR_FALSE;
+  }
+
+  nsRect temp;
+  return (!ApplyOverflowClipping(nsnull, this, GetStyleDisplay(), &temp) &&
+          !ApplyAbsPosClipping(nsnull, GetStyleDisplay(), this, &temp) &&
+          !nsSVGIntegrationUtils::UsingEffectsForFrame(this));
+}
+
 nsRect
 nsIFrame::GetContentRectRelativeToSelf() const
 {
   nsMargin bp(GetUsedBorderAndPadding());
   ApplySkipSides(bp);
   nsRect r(0, 0, mRect.width, mRect.height);
   r.Deflate(bp);
   return r;
@@ -1200,17 +1229,17 @@ nsFrame::DisplayBorderBackgroundOutline(
     NS_ENSURE_SUCCESS(rv, rv);
   }
 
   return DisplayOutlineUnconditional(aBuilder, aLists);
 }
 
 PRBool
 nsIFrame::GetAbsPosClipRect(const nsStyleDisplay* aDisp, nsRect* aRect,
-                            const nsSize& aSize)
+                            const nsSize& aSize) const
 {
   NS_PRECONDITION(aRect, "Must have aRect out parameter");
 
   if (!aDisp->IsAbsolutelyPositioned() ||
       !(aDisp->mClipFlags & NS_STYLE_CLIP_RECT))
     return PR_FALSE;
 
   *aRect = aDisp->mClip;
@@ -1219,30 +1248,32 @@ nsIFrame::GetAbsPosClipRect(const nsStyl
   }
   if (NS_STYLE_CLIP_BOTTOM_AUTO & aDisp->mClipFlags) {
     aRect->height = aSize.height - aRect->y;
   }
   return PR_TRUE;
 }
 
 static PRBool ApplyAbsPosClipping(nsDisplayListBuilder* aBuilder,
-                                  const nsStyleDisplay* aDisp, nsIFrame* aFrame,
+                                  const nsStyleDisplay* aDisp, const nsIFrame* aFrame,
                                   nsRect* aRect) {
   if (!aFrame->GetAbsPosClipRect(aDisp, aRect, aFrame->GetSize()))
     return PR_FALSE;
 
-  *aRect += aBuilder->ToReferenceFrame(aFrame);
+  if (aBuilder) {
+    *aRect += aBuilder->ToReferenceFrame(aFrame);
+  }
   return PR_TRUE;
 }
 
 /**
  * Returns PR_TRUE if aFrame is overflow:hidden and we should interpret
  * that as -moz-hidden-unscrollable.
  */
-static inline PRBool ApplyOverflowHiddenClipping(nsIFrame* aFrame,
+static inline PRBool ApplyOverflowHiddenClipping(const nsIFrame* aFrame,
                                                  const nsStyleDisplay* aDisp)
 {
   if (aDisp->mOverflowX != NS_STYLE_OVERFLOW_HIDDEN)
     return PR_FALSE;
     
   nsIAtom* type = aFrame->GetType();
   // REVIEW: these are the frame types that call IsTableClip and set up
   // clipping. Actually there were also table rows and the inner table frame
@@ -1251,17 +1282,17 @@ static inline PRBool ApplyOverflowHidden
   // but we should actually clip at tableFrame (as per discussion with Hixie and
   // bz).
   return type == nsGkAtoms::tableFrame ||
        type == nsGkAtoms::tableCellFrame ||
        type == nsGkAtoms::bcTableCellFrame;
 }
 
 static PRBool ApplyOverflowClipping(nsDisplayListBuilder* aBuilder,
-                                    nsIFrame* aFrame,
+                                    const nsIFrame* aFrame,
                                     const nsStyleDisplay* aDisp, nsRect* aRect) {
   // REVIEW: from nsContainerFrame.cpp SyncFrameViewGeometryDependentProperties,
   // except that that function used the border-edge for
   // -moz-hidden-unscrollable which I don't think is correct... Also I've
   // changed -moz-hidden-unscrollable to apply to any kind of frame.
 
   // Only -moz-hidden-unscrollable is handled here (and 'hidden' for table
   // frames, and any non-visible value for blocks in a paginated context).
@@ -1271,18 +1302,20 @@ static PRBool ApplyOverflowClipping(nsDi
     PRBool clip = aDisp->mOverflowX == NS_STYLE_OVERFLOW_CLIP;
     if (!clip)
       return PR_FALSE;
     // We allow -moz-hidden-unscrollable to apply to any kind of frame. This
     // is required by comboboxes which make their display text (an inline frame)
     // have clipping.
   }
   
-  *aRect = aFrame->GetPaddingRect() - aFrame->GetPosition() +
-    aBuilder->ToReferenceFrame(aFrame);
+  *aRect = aFrame->GetPaddingRect() - aFrame->GetPosition();
+  if (aBuilder) {
+    *aRect += aBuilder->ToReferenceFrame(aFrame);
+  }
   return PR_TRUE;
 }
 
 class nsOverflowClipWrapper : public nsDisplayWrapper
 {
 public:
   /**
    * Create a wrapper to apply overflow clipping for aContainer.
@@ -1422,16 +1455,59 @@ DisplayDebugBorders(nsDisplayListBuilder
       aFrame->PresContext()->PresShell()->GetDrawEventTargetFrame() == aFrame) {
     aLists.Outlines()->AppendNewToTop(new (aBuilder)
         nsDisplayGeneric(aBuilder, aFrame, PaintEventTargetBorder, "EventTargetBorder",
                          nsDisplayItem::TYPE_EVENT_TARGET_BORDER));
   }
 }
 #endif
 
+static nsresult
+WrapPreserve3DList(nsIFrame *aFrame, nsDisplayListBuilder *aBuilder, nsDisplayList *aList)
+{
+  nsresult rv = NS_OK;
+  nsDisplayList newList;
+  while (nsDisplayItem *item = aList->RemoveBottom()) {
+    if (item->GetUnderlyingFrame() && item->GetUnderlyingFrame()->GetParent()->Preserves3DChildren()) {
+      switch (item->GetType()) {
+        case nsDisplayItem::TYPE_TRANSFORM: {
+          // The child transform frame should always preserve 3d. In the cases where preserve-3d is disabled
+          // such as clipping, this would be wrapped in a clip display object, and we wouldn't reach this point.
+          NS_ASSERTION(item->GetUnderlyingFrame()->Preserves3D(), "Child transform frame must preserve 3d!");
+          break;
+        }
+        case nsDisplayItem::TYPE_WRAP_LIST: {
+          nsDisplayWrapList *list = static_cast<nsDisplayWrapList*>(item);
+          rv = WrapPreserve3DList(aFrame, aBuilder, list->GetList());
+          break;
+        }
+        case nsDisplayItem::TYPE_OPACITY: {
+          nsDisplayOpacity *opacity = static_cast<nsDisplayOpacity*>(item);
+          rv = WrapPreserve3DList(aFrame, aBuilder, opacity->GetList());
+          break;
+        }
+        default: {
+          item = new (aBuilder) nsDisplayTransform(aBuilder, item->GetUnderlyingFrame(), item);
+          break;
+        }
+      } 
+    } else {
+      item = new (aBuilder) nsDisplayTransform(aBuilder, item->GetUnderlyingFrame(), item);
+    }
+ 
+    if (NS_FAILED(rv) || !item)
+      return rv;
+
+    newList.AppendToTop(item);
+  }
+
+  aList->AppendToTop(&newList);
+  return NS_OK;
+}
+
 nsresult
 nsIFrame::BuildDisplayListForStackingContext(nsDisplayListBuilder* aBuilder,
                                              const nsRect&         aDirtyRect,
                                              nsDisplayList*        aList) {
   if (GetStateBits() & NS_FRAME_TOO_DEEP_IN_FRAME_TREE)
     return NS_OK;
 
   // Replaced elements have their visibility handled here, because
@@ -1452,17 +1528,17 @@ nsIFrame::BuildDisplayListForStackingCon
 
   PRBool inTransform = aBuilder->IsInTransform();
   /* 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()) {
     /* If we have a complex transform, just grab the entire overflow rect instead. */
-    if (!nsDisplayTransform::UntransformRect(dirtyRect, this, nsPoint(0, 0), &dirtyRect)) {
+    if (Preserves3DChildren() || !nsDisplayTransform::UntransformRect(dirtyRect, this, nsPoint(0, 0), &dirtyRect)) {
       dirtyRect = GetVisualOverflowRectRelativeToSelf();
     }
     inTransform = PR_TRUE;
   }
 
   if (applyAbsPosClipping) {
     dirtyRect.IntersectRect(dirtyRect,
                             absPosClip - aBuilder->ToReferenceFrame(this));
@@ -1573,25 +1649,46 @@ nsIFrame::BuildDisplayListForStackingCon
    */
   if (disp->mOpacity < 1.0f && !resultList.IsEmpty()) {
     rv = resultList.AppendNewToTop(
         new (aBuilder) nsDisplayOpacity(aBuilder, this, &resultList));
     if (NS_FAILED(rv))
       return rv;
   }
 
-  /* If we're going to apply a transformation, wrap everything in an
-   * nsDisplayTransform. If there's nothing in the list, don't add anything.
+  /* If we're going to apply a transformation and don't have preserve-3d set, wrap 
+   * everything in an nsDisplayTransform. If there's nothing in the list, don't add 
+   * anything.
+   *
+   * For the preserve-3d case we want to individually wrap every child in the list with
+   * a separate nsDisplayTransform instead. When the child is already an nsDisplayTransform,
+   * we can skip this step, as the computed transform will already include our own.
+   *
+   * We also traverse into sublists created by nsDisplayWrapList or nsDisplayOpacity, so that
+   * we find all the correct children.
    */
   if ((mState & NS_FRAME_MAY_BE_TRANSFORMED) &&
       disp->HasTransform() && !resultList.IsEmpty()) {
-    rv = resultList.AppendNewToTop(
+    if (Preserves3DChildren()) {
+      rv = WrapPreserve3DList(this, aBuilder, &resultList);
+      if (NS_FAILED(rv))
+        return rv;
+
+      if (resultList.Count() > 1) {
+        rv = resultList.AppendNewToTop(
+          new (aBuilder) nsDisplayWrapList(aBuilder, this, &resultList));
+        if (NS_FAILED(rv))
+          return rv;
+      }
+    } else {
+      rv = resultList.AppendNewToTop(
         new (aBuilder) nsDisplayTransform(aBuilder, this, &resultList));
-    if (NS_FAILED(rv))
-      return rv;
+      if (NS_FAILED(rv))
+        return rv;
+    }
   }
 
   aList->AppendToTop(&resultList);
   return rv;
 }
 
 nsresult
 nsIFrame::BuildDisplayListForChild(nsDisplayListBuilder*   aBuilder,
@@ -4151,18 +4248,35 @@ nsIFrame::InvalidateInternalAfterResize(
    * 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.
    *
+   * When we are preserving-3d, we can have arbitrary hierarchies of preserved 3d
+   * children. The computed transform on these children is relative to the root
+   * transform object in the hierarchy, not necessarily their direct ancestor.
+   * In this case we transform by the child's transform, and mark the rectangle
+   * as being transformed until it is passed up to the root of the hierarchy.
+   *
    * See bug #452496 for more details.
    */
+
+  // Check the transformed flags and remove it
+  PRBool rectIsTransformed = (aFlags & INVALIDATE_ALREADY_TRANSFORMED);
+  if (!Preserves3D()) {
+    // We only want to remove the flag if we aren't preserving 3d. Otherwise
+    // the rect will already have been transformed into the root preserve-3d
+    // frame coordinate space, and we should continue passing it up without
+    // further transforms.
+    aFlags &= ~INVALIDATE_ALREADY_TRANSFORMED;
+  }
+
   if ((mState & NS_FRAME_HAS_CONTAINER_LAYER) &&
       !(aFlags & INVALIDATE_NO_THEBES_LAYERS)) {
     // XXX for now I'm going to assume this is in the local coordinate space
     // This only matters for frames with transforms and retained layers,
     // which can't happen right now since transforms trigger fallback
     // rendering and the display items that trigger layers are nested inside
     // the nsDisplayTransform
     // XXX need to set INVALIDATE_NO_THEBES_LAYERS for certain kinds of
@@ -4170,20 +4284,28 @@ nsIFrame::InvalidateInternalAfterResize(
     FrameLayerBuilder::InvalidateThebesLayerContents(this,
         aDamageRect + nsPoint(aX, aY));
     // Don't need to invalidate any more Thebes layers
     aFlags |= INVALIDATE_NO_THEBES_LAYERS;
     if (aFlags & INVALIDATE_ONLY_THEBES_LAYERS) {
       return;
     }
   }
-  if (IsTransformed()) {
+  if (IsTransformed() && !rectIsTransformed) {
     nsRect newDamageRect;
     newDamageRect.UnionRect(nsDisplayTransform::TransformRectOut
                             (aDamageRect, this, nsPoint(-aX, -aY)), aDamageRect);
+
+    // If we are preserving 3d, then our computed transform includes that of any
+    // ancestor frames that also preserve 3d. Mark the rectangle as already being
+    // transformed into the parent's coordinate space.
+    if (Preserves3D()) {
+      aFlags |= INVALIDATE_ALREADY_TRANSFORMED;
+    }
+
     GetParent()->
       InvalidateInternal(newDamageRect, aX + mRect.x, aY + mRect.y, this,
                          aFlags);
   }
   else 
     GetParent()->
       InvalidateInternal(aDamageRect, aX + mRect.x, aY + mRect.y, this, aFlags);
 }
@@ -4207,44 +4329,41 @@ nsIFrame::InvalidateInternal(const nsRec
   InvalidateInternalAfterResize(aDamageRect, aX, aY, aFlags);
 }
 
 gfx3DMatrix
 nsIFrame::GetTransformMatrix(nsIFrame **aOutAncestor)
 {
   NS_PRECONDITION(aOutAncestor, "Need a place to put the ancestor!");
 
-  /* If 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 = GetOffsetToCrossDoc(*aOutAncestor);
+    NS_ASSERTION(nsLayoutUtils::GetCrossDocParentFrame(this), "Cannot transform the viewport frame!");
     PRInt32 scaleFactor = PresContext()->AppUnitsPerDevPixel();
 
     gfx3DMatrix result =
       nsDisplayTransform::GetResultingTransformMatrix(this, nsPoint(0, 0),
-                                                      scaleFactor);
+                                                      scaleFactor, nsnull, aOutAncestor);
+    nsPoint delta = GetOffsetToCrossDoc(*aOutAncestor);
     /* Combine the raw transform with a translation to our parent. */
     result *= gfx3DMatrix::Translation
       (NSAppUnitsToFloatPixels(delta.x, scaleFactor),
        NSAppUnitsToFloatPixels(delta.y, scaleFactor),
        0.0f);
     return result;
   }
   
+  *aOutAncestor = nsLayoutUtils::GetCrossDocParentFrame(this);
+  
   /* 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.
    */
@@ -6261,19 +6380,23 @@ nsIFrame::FinishAndStoreOverflow(nsOverf
     Properties().Set(nsIFrame::PreTransformBBoxProperty(),
                      new nsRect(aOverflowAreas.VisualOverflow()));
     /* 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);
     // Transform affects both overflow areas.
-    NS_FOR_FRAME_OVERFLOW_TYPES(otype) {
-      nsRect& o = aOverflowAreas.Overflow(otype);
-      o = nsDisplayTransform::TransformRect(o, this, nsPoint(0, 0), &newBounds);
+    if (!Preserves3DChildren()) {
+      NS_FOR_FRAME_OVERFLOW_TYPES(otype) {
+        nsRect& o = aOverflowAreas.Overflow(otype);
+       o = nsDisplayTransform::TransformRect(o, this, nsPoint(0, 0), &newBounds);
+      }
+    } else {
+      ComputePreserve3DChildrenOverflow(aOverflowAreas, newBounds);
     }
   }
 
   PRBool visualOverflowChanged =
     !GetVisualOverflowRect().IsEqualInterior(aOverflowAreas.VisualOverflow());
 
   if (aOverflowAreas != nsOverflowAreas(bounds, bounds)) {
     SetOverflowAreas(aOverflowAreas);
@@ -6321,16 +6444,47 @@ nsIFrame::FinishAndStoreOverflow(nsOverf
       // that we didn't reach here.
       InvalidateLayer(aOverflowAreas.VisualOverflow(),
                       nsDisplayItem::TYPE_TRANSFORM);
     }
   }
 }
 
 void
+nsIFrame::ComputePreserve3DChildrenOverflow(nsOverflowAreas& aOverflowAreas, const nsRect& aBounds)
+{
+  // When we are preserving 3d we need to iterate over all children separately.
+  // If the child also preserves 3d then their overflow will already been in our
+  // coordinate space, otherwise we need to transform.
+  nsRect childVisual;
+  nsRect childScrollable;
+  nsIFrame::ChildListIterator lists(this);
+  for (; !lists.IsDone(); lists.Next()) {
+    nsFrameList::Enumerator childFrames(lists.CurrentList());
+    for (; !childFrames.AtEnd(); childFrames.Next()) {
+      nsIFrame* child = childFrames.get();
+      if (child->Preserves3D()) {
+        childVisual = childVisual.Union(child->GetVisualOverflowRect());
+        childScrollable = childScrollable.Union(child->GetScrollableOverflowRect());
+      } else {
+        childVisual = 
+          childVisual.Union(nsDisplayTransform::TransformRect(child->GetVisualOverflowRect(), 
+                            this, nsPoint(0,0), &aBounds));
+        childScrollable = 
+          childScrollable.Union(nsDisplayTransform::TransformRect(child->GetScrollableOverflowRect(), 
+                                this, nsPoint(0,0), &aBounds));
+      }
+    }
+  }
+
+  aOverflowAreas.Overflow(eVisualOverflow) = childVisual;
+  aOverflowAreas.Overflow(eScrollableOverflow) = childScrollable;
+}
+
+void
 nsFrame::ConsiderChildOverflow(nsOverflowAreas& aOverflowAreas,
                                nsIFrame* aChildFrame)
 {
   const nsStyleDisplay* disp = GetStyleDisplay();
   // check here also for hidden as table frames (table, tr and td) currently 
   // don't wrap their content into a scrollable frame if overflow is specified
   // FIXME: Why do we check this here rather than in
   // FinishAndStoreOverflow (where we check NS_STYLE_OVERFLOW_CLIP)?
--- a/layout/generic/nsFrame.h
+++ b/layout/generic/nsFrame.h
@@ -572,17 +572,17 @@ public:
   // frame.
   // If aScrollLock is true, don't break outside scrollframes when looking for a
   // containing block frame.
   static PRInt32 GetLineNumber(nsIFrame *aFrame,
                                PRBool aLockScroll,
                                nsIFrame** aContainingBlock = nsnull);
 
   // test whether aFrame should apply paginated overflow clipping.
-  static PRBool ApplyPaginatedOverflowClipping(nsIFrame* aFrame)
+  static PRBool ApplyPaginatedOverflowClipping(const nsIFrame* aFrame)
   {
     // If we're paginated and a block, and have NS_BLOCK_CLIP_PAGINATED_OVERFLOW
     // set, then we want to clip our overflow.
     return
       aFrame->PresContext()->IsPaginated() &&
       aFrame->GetType() == nsGkAtoms::blockFrame &&
       (aFrame->GetStateBits() & NS_BLOCK_CLIP_PAGINATED_OVERFLOW) != 0;
   }
--- a/layout/generic/nsIFrame.h
+++ b/layout/generic/nsIFrame.h
@@ -1200,16 +1200,31 @@ public:
 
   /**
    * 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;
 
   /**
+   * Returns whether this frame will attempt to preserve the 3d transforms of its
+   * children. This is a direct indicator of -moz-transform-style: preserve-3d.
+   */
+  PRBool Preserves3DChildren() const;
+
+  /**
+   * Returns whether this frame has a parent that Preserves3DChildren() and
+   * can respect this. Returns false if the frame is clipped.
+   */
+  PRBool Preserves3D() const;
+
+  // Calculate the overflow size of all child frames, taking preserve-3d into account
+  void ComputePreserve3DChildrenOverflow(nsOverflowAreas& aOverflowAreas, const nsRect& aBounds);
+
+  /**
    * Event handling of GUI events.
    *
    * @param   aEvent event structure describing the type of event and rge widget
    *            where the event originated
    *          The |point| member of this is in the coordinate system of the
    *          view returned by GetOffsetFromView.
    * @param   aEventStatus a return value indicating whether the event was handled
    *            and whether default processing should be done
@@ -2111,17 +2126,18 @@ public:
     INVALIDATE_CROSS_DOC = 0x02,
     INVALIDATE_REASON_SCROLL_BLIT = 0x04,
     INVALIDATE_REASON_SCROLL_REPAINT = 0x08,
     INVALIDATE_REASON_MASK = INVALIDATE_REASON_SCROLL_BLIT |
                              INVALIDATE_REASON_SCROLL_REPAINT,
     INVALIDATE_NO_THEBES_LAYERS = 0x10,
     INVALIDATE_ONLY_THEBES_LAYERS = 0x20,
     INVALIDATE_EXCLUDE_CURRENT_PAINT = 0x40,
-    INVALIDATE_NO_UPDATE_LAYER_TREE = 0x80
+    INVALIDATE_NO_UPDATE_LAYER_TREE = 0x80,
+    INVALIDATE_ALREADY_TRANSFORMED = 0x100
   };
   virtual void InvalidateInternal(const nsRect& aDamageRect,
                                   nscoord aOffsetX, nscoord aOffsetY,
                                   nsIFrame* aForChild, PRUint32 aFlags);
 
   /**
    * 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
@@ -2498,17 +2514,17 @@ NS_PTR_TO_INT32(frame->Properties().Get(
 
   /**
    * Returns PR_TRUE if the frame is absolutely positioned and has a clip
    * rect set via the 'clip' property. If true, then we also set aRect
    * to the computed clip rect coordinates relative to this frame's origin.
    * aRect must not be null!
    */
   PRBool GetAbsPosClipRect(const nsStyleDisplay* aDisp, nsRect* aRect,
-                           const nsSize& aSize);
+                           const nsSize& aSize) const;
 
   /**
    * Check if this frame is focusable and in the current tab order.
    * Tabbable is indicated by a nonnegative tabindex & is a subset of focusable.
    * For example, only the selected radio button in a group is in the 
    * tab order, unless the radio group has no selection in which case
    * all of the visible, non-disabled radio buttons in the group are 
    * in the tab order. On the other hand, all of the visible, non-disabled 
--- a/layout/style/nsStyleStruct.h
+++ b/layout/style/nsStyleStruct.h
@@ -1610,17 +1610,17 @@ struct nsStyleDisplay {
   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 mSpecifiedTransform != nsnull;
+    return mSpecifiedTransform != nsnull || mTransformStyle == NS_STYLE_TRANSFORM_STYLE_PRESERVE_3D;
   }
 };
 
 struct nsStyleTable {
   nsStyleTable(void);
   nsStyleTable(const nsStyleTable& aOther);
   ~nsStyleTable(void);