Bug 923193. Make transform-origin on SVG elements use the SVG bbox as the reference rectangle. r=heycam
authorRobert O'Callahan <robert@ocallahan.org>
Thu, 03 Oct 2013 08:34:24 -0400
changeset 164930 a417424f9213c410910cbd85fd41d3ddb3162eed
parent 164929 9c8ab7e9ae419e7dec2025c2584eeb565b17accc
child 164931 45d9e6cd34736aadeac40925170bca7e9b42b3bd
push id3066
push userakeybl@mozilla.com
push dateMon, 09 Dec 2013 19:58:46 +0000
treeherdermozilla-beta@a31a0dce83aa [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersheycam
bugs923193
milestone27.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 923193. Make transform-origin on SVG elements use the SVG bbox as the reference rectangle. r=heycam
layout/base/nsDisplayList.cpp
layout/reftests/transform/reftest.list
layout/reftests/transform/transform-origin-svg-1-ref.svg
layout/reftests/transform/transform-origin-svg-1a.svg
layout/reftests/transform/transform-origin-svg-1b.svg
layout/reftests/transform/transform-origin-svg-2-ref.svg
layout/reftests/transform/transform-origin-svg-2a.svg
layout/reftests/transform/transform-origin-svg-2b.svg
layout/svg/nsSVGTextFrame2.cpp
layout/svg/svg.css
--- a/layout/base/nsDisplayList.cpp
+++ b/layout/base/nsDisplayList.cpp
@@ -3824,35 +3824,37 @@ bool nsDisplayZoom::ComputeVisibility(ns
 #ifndef UNIFIED_CONTINUATIONS
 
 nsRect
 nsDisplayTransform::GetFrameBoundsForTransform(const nsIFrame* aFrame)
 {
   NS_PRECONDITION(aFrame, "Can't get the bounds of a nonexistent frame!");
 
   if (aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT) {
-    // TODO: SVG needs to define what percentage translations resolve against.
-    return nsRect();
+    gfxRect bbox = nsSVGUtils::GetBBox(const_cast<nsIFrame*>(aFrame));
+    return nsLayoutUtils::RoundGfxRectToAppRect(bbox,
+      aFrame->PresContext()->AppUnitsPerCSSPixel()) - aFrame->GetPosition();
   }
 
   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;
 
   if (aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT) {
-    // TODO: SVG needs to define what percentage translations resolve against.
-    return result;
+    gfxRect bbox = nsSVGUtils::GetBBox(const_cast<nsIFrame*>(aFrame));
+    return nsLayoutUtils::RoundGfxRectToAppRect(bbox,
+      aFrame->PresContext()->AppUnitsPerCSSPixel()) - aFrame->GetPosition();
   }
 
   /* Iterate through the continuation list, unioning together all the
    * bounding rects.
    */
   for (const nsIFrame *currFrame = aFrame->FirstContinuation();
        currFrame != nullptr;
        currFrame = currFrame->GetNextContinuation())
@@ -3916,57 +3918,55 @@ nsDisplayTransform::GetDeltaToMozTransfo
    * 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->StyleDisplay();
   nsRect boundingRect = (aBoundsOverride ? *aBoundsOverride :
                          nsDisplayTransform::GetFrameBoundsForTransform(aFrame));
 
   /* Allows us to access named variables by index. */
-  float coords[3];
-  const nscoord* dimensions[2] =
-    {&boundingRect.width, &boundingRect.height};
+  float coords[2];
+  nscoord boundingOffsets[2] = {boundingRect.x, boundingRect.y};
+  nscoord boundingDimensions[2] = {boundingRect.width, boundingRect.height};
+  nscoord frameOffsets[2] = {aFrame->GetPosition().x, aFrame->GetPosition().y};
 
   for (uint8_t index = 0; index < 2; ++index) {
     /* If the -moz-transform-origin specifies a percentage, take the percentage
      * of the size of the box.
      */
     const nsStyleCoord &coord = display->mTransformOrigin[index];
-    if (coord.GetUnit() == eStyleUnit_Calc) {
-      const nsStyleCoord::Calc *calc = coord.GetCalcValue();
+    if (coord.GetUnit() == eStyleUnit_Percent) {
       coords[index] =
-        NSAppUnitsToFloatPixels(*dimensions[index], aAppUnitsPerPixel) *
-          calc->mPercent +
-        NSAppUnitsToFloatPixels(calc->mLength, aAppUnitsPerPixel);
-    } else if (coord.GetUnit() == eStyleUnit_Percent) {
-      coords[index] =
-        NSAppUnitsToFloatPixels(*dimensions[index], aAppUnitsPerPixel) *
-        coord.GetPercentValue();
+        NSAppUnitsToFloatPixels(boundingDimensions[index], aAppUnitsPerPixel) *
+          coord.GetPercentValue() +
+        NSAppUnitsToFloatPixels(boundingOffsets[index], aAppUnitsPerPixel);
     } else {
-      NS_ABORT_IF_FALSE(coord.GetUnit() == eStyleUnit_Coord, "unexpected unit");
-      coords[index] =
-        NSAppUnitsToFloatPixels(coord.GetCoordValue(), aAppUnitsPerPixel);
+      if (coord.GetUnit() == eStyleUnit_Calc) {
+        const nsStyleCoord::Calc *calc = coord.GetCalcValue();
+        coords[index] =
+          NSAppUnitsToFloatPixels(boundingDimensions[index], aAppUnitsPerPixel) *
+            calc->mPercent +
+          NSAppUnitsToFloatPixels(calc->mLength, aAppUnitsPerPixel);
+      } else {
+        NS_ABORT_IF_FALSE(coord.GetUnit() == eStyleUnit_Coord, "unexpected unit");
+        coords[index] =
+          NSAppUnitsToFloatPixels(coord.GetCoordValue(), aAppUnitsPerPixel);
+      }
+      if (aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT) {
+        // <length> values represent offsets from the origin of the SVG element's
+        // user space, not the top left of its border-box, so we must
+        // convert them to be relative to the border-box.
+        coords[index] -= NSAppUnitsToFloatPixels(frameOffsets[index], aAppUnitsPerPixel);
+      }
     }
-    if ((aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT) &&
-        coord.GetUnit() != eStyleUnit_Percent) {
-      // <length> values represent offsets from the origin of the SVG element's
-      // user space, not the top left of its bounds, so we must adjust for that:
-      nscoord offset =
-        (index == 0) ? aFrame->GetPosition().x : aFrame->GetPosition().y;
-      coords[index] -= NSAppUnitsToFloatPixels(offset, aAppUnitsPerPixel);
-    }
-  }
-
-  coords[2] = NSAppUnitsToFloatPixels(display->mTransformOrigin[2].GetCoordValue(),
-                                      aAppUnitsPerPixel);
-  /* Adjust based on the origin of the rectangle. */
-  coords[0] += NSAppUnitsToFloatPixels(boundingRect.x, aAppUnitsPerPixel);
-  coords[1] += NSAppUnitsToFloatPixels(boundingRect.y, aAppUnitsPerPixel);
-
-  return gfxPoint3D(coords[0], coords[1], coords[2]);
+  }
+
+  return gfxPoint3D(coords[0], coords[1],
+                    NSAppUnitsToFloatPixels(display->mTransformOrigin[2].GetCoordValue(),
+                                            aAppUnitsPerPixel));
 }
 
 /* Returns the delta specified by the -moz-perspective-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 perspective origin. This function is
  * called off the main thread.
  */
 /* static */ gfxPoint3D
--- a/layout/reftests/transform/reftest.list
+++ b/layout/reftests/transform/reftest.list
@@ -119,8 +119,12 @@ skip-if(B2G) fails-if(Android) fuzzy-if(
 # Bug 722777
 == table-1a.html table-1-ref.html
 == table-1b.html table-1-ref.html
 == table-1c.html table-1-ref.html
 == table-2a.html table-2-ref.html
 == table-2b.html table-2-ref.html
 # Bug 722463
 == inline-1a.html inline-1-ref.html
+== transform-origin-svg-1a.svg transform-origin-svg-1-ref.svg
+== transform-origin-svg-1b.svg transform-origin-svg-1-ref.svg
+== transform-origin-svg-2a.svg transform-origin-svg-2-ref.svg
+== transform-origin-svg-2b.svg transform-origin-svg-2-ref.svg
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/transform-origin-svg-1-ref.svg
@@ -0,0 +1,3 @@
+<svg xmlns='http://www.w3.org/2000/svg'>
+<rect x='40' y='140' width='100' height='100' fill='lime'/>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/transform-origin-svg-1a.svg
@@ -0,0 +1,6 @@
+<svg xmlns='http://www.w3.org/2000/svg'>
+<g transform="translate(30,30)">
+  <rect x='10' y='10' width='100' height='100' fill='lime'
+        style="transform:rotate(90deg); transform-origin:left bottom;"/>
+</g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/transform-origin-svg-1b.svg
@@ -0,0 +1,7 @@
+<svg xmlns='http://www.w3.org/2000/svg'>
+<g transform="translate(30,30)">
+  <rect x='10' y='10' width='100' height='100' fill='lime'
+        style="transform:rotate(90deg); transform-origin:10px 110px;
+               -webkit-transform:rotate(90deg); -webkit-transform-origin:10px 110px;"/>
+</g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/transform-origin-svg-2-ref.svg
@@ -0,0 +1,3 @@
+<svg xmlns='http://www.w3.org/2000/svg'>
+<rect x='40' y='140' width='100' height='100' fill='lime' stroke-width='20' stroke='blue'/>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/transform-origin-svg-2a.svg
@@ -0,0 +1,6 @@
+<svg xmlns='http://www.w3.org/2000/svg'>
+<g transform="translate(30,30)">
+  <rect x='10' y='10' width='100' height='100' fill='lime' stroke-width='20' stroke='blue'
+        style="transform:rotate(90deg); transform-origin:left bottom;"/>
+</g>
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/transform/transform-origin-svg-2b.svg
@@ -0,0 +1,7 @@
+<svg xmlns='http://www.w3.org/2000/svg'>
+<g transform="translate(30,30)">
+  <rect x='10' y='10' width='100' height='100' fill='lime' stroke-width='20' stroke='blue'
+        style="transform:rotate(90deg); transform-origin:10px 110px;
+               -webkit-transform:rotate(90deg); -webkit-transform-origin:10px 110px;"/>
+</g>
+</svg>
--- a/layout/svg/nsSVGTextFrame2.cpp
+++ b/layout/svg/nsSVGTextFrame2.cpp
@@ -3682,24 +3682,26 @@ nsSVGTextFrame2::ReflowSVG()
 
   if (mState & NS_FRAME_FIRST_REFLOW) {
     // Make sure we have our filter property (if any) before calling
     // FinishAndStoreOverflow (subsequent filter changes are handled off
     // nsChangeHint_UpdateEffects):
     nsSVGEffects::UpdateEffects(this);
   }
 
+  // Now unset the various reflow bits. Do this before calling
+  // FinishAndStoreOverflow since FinishAndStoreOverflow can require glyph
+  // positions (to resolve transform-origin).
+  mState &= ~(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY |
+              NS_FRAME_HAS_DIRTY_CHILDREN);
+
   nsRect overflow = nsRect(nsPoint(0,0), mRect.Size());
   nsOverflowAreas overflowAreas(overflow, overflow);
   FinishAndStoreOverflow(overflowAreas, mRect.Size());
 
-  // Now unset the various reflow bits:
-  mState &= ~(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY |
-              NS_FRAME_HAS_DIRTY_CHILDREN);
-
   // XXX nsSVGContainerFrame::ReflowSVG only looks at its nsISVGChildFrame
   // children, and calls ConsiderChildOverflow on them.  Does it matter
   // that ConsiderChildOverflow won't be called on our children?
   nsSVGTextFrame2Base::ReflowSVG();
 }
 
 /**
  * Converts nsSVGUtils::eBBox* flags into TextRenderedRun flags appropriate
@@ -3724,19 +3726,32 @@ TextRenderedRunFlagsForBBoxContribution(
 }
 
 SVGBBox
 nsSVGTextFrame2::GetBBoxContribution(const gfxMatrix &aToBBoxUserspace,
                                      uint32_t aFlags)
 {
   NS_ASSERTION(GetFirstPrincipalChild(), "must have a child frame");
 
+  SVGBBox bbox;
+  nsIFrame* kid = GetFirstPrincipalChild();
+  if (kid && NS_SUBTREE_DIRTY(kid)) {
+    // Return an empty bbox if our kid's subtree is dirty. This may be called
+    // in that situation, e.g. when we're building a display list after an
+    // interrupted reflow. This can also be called during reflow before we've
+    // been reflowed, e.g. if an earlier sibling is calling FinishAndStoreOverflow and
+    // needs our parent's perspective matrix, which depends on the SVG bbox
+    // contribution of this frame. In the latter situation, when all siblings have
+    // been reflowed, the parent will compute its perspective and rerun
+    // FinishAndStoreOverflow for all its children.
+    return bbox;
+  }
+
   UpdateGlyphPositioning();
 
-  SVGBBox bbox;
   nsPresContext* presContext = PresContext();
 
   TextRenderedRunIterator it(this);
   for (TextRenderedRun run = it.Current(); run.mFrame; run = it.Next()) {
     uint32_t flags = TextRenderedRunFlagsForBBoxContribution(run, aFlags);
     SVGBBox bboxForRun =
       run.GetUserSpaceRect(presContext, flags, &aToBBoxUserspace);
     bbox.UnionEdges(bboxForRun);
--- a/layout/svg/svg.css
+++ b/layout/svg/svg.css
@@ -46,17 +46,17 @@ foreignObject {
 
 *|*::-moz-svg-foreign-content {
   display: block !important;
   /* We need to be an absolute and fixed container */
   transform: translate(0) !important;
   text-indent: 0;
 }
 
-/* Set |transform-origin:0% 0%;| for all SVG elements except outer-<svg>,
+/* Set |transform-origin:0 0;| for all SVG elements except outer-<svg>,
    noting that 'svg' as a child of 'foreignObject' counts as outer-<svg>.
 */
 *:not(svg),
 *:not(foreignObject) > svg {
   transform-origin:0 0;
 }
 
 *|*::-moz-svg-text {