Bug 999964 part 1 - Patch for SVG 2 getBBox method; r=longsonr, r=bz
authorShigeyuki Tsukihashi <shigeyuki.tsukihashi@going.co.jp>
Tue, 13 May 2014 10:24:35 +0900
changeset 184077 e56e0f49a088640c4f98e9f267b3199e4e248789
parent 184076 6c446b233036afc69b6590133328a117edbfbd8a
child 184078 dcbcf74976277493bc36b43f420bd3e897da3fdd
push id26810
push usercbook@mozilla.com
push dateWed, 21 May 2014 11:46:36 +0000
treeherdermozilla-central@50fb8c4db2fd [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerslongsonr, bz
bugs999964
milestone32.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 999964 part 1 - Patch for SVG 2 getBBox method; r=longsonr, r=bz
content/svg/content/src/SVGTransformableElement.cpp
content/svg/content/src/SVGTransformableElement.h
dom/webidl/SVGGraphicsElement.webidl
layout/svg/nsSVGClipPathFrame.cpp
layout/svg/nsSVGClipPathFrame.h
layout/svg/nsSVGUtils.cpp
layout/svg/nsSVGUtils.h
modules/libpref/src/init/all.js
--- a/content/svg/content/src/SVGTransformableElement.cpp
+++ b/content/svg/content/src/SVGTransformableElement.cpp
@@ -1,15 +1,16 @@
 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "gfx2DGlue.h"
 #include "mozilla/dom/SVGAnimatedTransformList.h"
+#include "mozilla/dom/SVGGraphicsElementBinding.h"
 #include "mozilla/dom/SVGTransformableElement.h"
 #include "mozilla/dom/SVGMatrix.h"
 #include "mozilla/dom/SVGSVGElement.h"
 #include "nsContentUtils.h"
 #include "nsIDOMMutationEvent.h"
 #include "nsIFrame.h"
 #include "nsISVGChildFrame.h"
 #include "mozilla/dom/SVGRect.h"
@@ -177,32 +178,56 @@ SVGTransformableElement::GetNearestViewp
 
 nsSVGElement*
 SVGTransformableElement::GetFarthestViewportElement()
 {
   return SVGContentUtils::GetOuterSVGElement(this);
 }
 
 already_AddRefed<SVGIRect>
-SVGTransformableElement::GetBBox(ErrorResult& rv)
+SVGTransformableElement::GetBBox(const SVGBoundingBoxOptions& aOptions, 
+                                 ErrorResult& rv)
 {
   nsIFrame* frame = GetPrimaryFrame(Flush_Layout);
 
   if (!frame || (frame->GetStateBits() & NS_FRAME_IS_NONDISPLAY)) {
     rv.Throw(NS_ERROR_FAILURE);
     return nullptr;
   }
-
   nsISVGChildFrame* svgframe = do_QueryFrame(frame);
   if (!svgframe) {
     rv.Throw(NS_ERROR_NOT_IMPLEMENTED); // XXX: outer svg
     return nullptr;
   }
 
-  return NS_NewSVGRect(this, ToRect(nsSVGUtils::GetBBox(frame)));
+  if (!NS_SVGNewGetBBoxEnabled()) {
+    return NS_NewSVGRect(this, ToRect(nsSVGUtils::GetBBox(frame)));
+  } else {
+    uint32_t aFlags = 0;
+    if (aOptions.mFill) {
+      aFlags |= nsSVGUtils::eBBoxIncludeFill;
+    }
+    if (aOptions.mStroke) {
+      aFlags |= nsSVGUtils::eBBoxIncludeStroke;
+    }
+    if (aOptions.mMarkers) {
+      aFlags |= nsSVGUtils::eBBoxIncludeMarkers;
+    }
+    if (aOptions.mClipped) {
+      aFlags |= nsSVGUtils::eBBoxIncludeClipped;
+    }
+    if (aFlags == 0) {
+      return NS_NewSVGRect(this,0,0,0,0);
+    }
+    if (aFlags == nsSVGUtils::eBBoxIncludeMarkers || 
+        aFlags == nsSVGUtils::eBBoxIncludeClipped) {
+      aFlags |= nsSVGUtils::eBBoxIncludeFill;
+    }
+    return NS_NewSVGRect(this, ToRect(nsSVGUtils::GetBBox(frame, aFlags)));
+  }
 }
 
 already_AddRefed<SVGMatrix>
 SVGTransformableElement::GetCTM()
 {
   nsIDocument* currentDoc = GetCurrentDoc();
   if (currentDoc) {
     // Flush all pending notifications so that our frames are up to date
--- a/content/svg/content/src/SVGTransformableElement.h
+++ b/content/svg/content/src/SVGTransformableElement.h
@@ -14,31 +14,33 @@
 
 namespace mozilla {
 namespace dom {
 
 class SVGAnimatedTransformList;
 class SVGGraphicsElement;
 class SVGMatrix;
 class SVGIRect;
+class SVGBoundingBoxOptions;
 
 class SVGTransformableElement : public nsSVGElement
 {
 public:
   SVGTransformableElement(already_AddRefed<nsINodeInfo>& aNodeInfo)
     : nsSVGElement(aNodeInfo) {}
   virtual ~SVGTransformableElement() {}
 
   virtual nsresult Clone(nsINodeInfo *aNodeInfo, nsINode **aResult) const MOZ_OVERRIDE = 0;
 
   // WebIDL
   already_AddRefed<SVGAnimatedTransformList> Transform();
   nsSVGElement* GetNearestViewportElement();
   nsSVGElement* GetFarthestViewportElement();
-  already_AddRefed<SVGIRect> GetBBox(ErrorResult& rv);
+  already_AddRefed<SVGIRect> GetBBox(const SVGBoundingBoxOptions& aOptions, 
+                                     ErrorResult& rv);
   already_AddRefed<SVGMatrix> GetCTM();
   already_AddRefed<SVGMatrix> GetScreenCTM();
   already_AddRefed<SVGMatrix> GetTransformToElement(SVGGraphicsElement& aElement,
                                                     ErrorResult& rv);
 
   // nsIContent interface
   NS_IMETHOD_(bool) IsAttributeMapped(const nsIAtom* aAttribute) const MOZ_OVERRIDE;
 
--- a/dom/webidl/SVGGraphicsElement.webidl
+++ b/dom/webidl/SVGGraphicsElement.webidl
@@ -5,24 +5,31 @@
  *
  * The origin of this IDL file is
  * http://www.w3.org/TR/SVG2/
  *
  * Copyright © 2012 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C
  * liability, trademark and document use rules apply.
  */
 
+dictionary SVGBoundingBoxOptions {
+  boolean fill = true;
+  boolean stroke = false;
+  boolean markers = false;
+  boolean clipped = false;
+};
+
 interface SVGGraphicsElement : SVGElement {
   readonly attribute SVGAnimatedTransformList transform;
 
   readonly attribute SVGElement? nearestViewportElement;
   readonly attribute SVGElement? farthestViewportElement;
 
   [NewObject, Throws]
-  SVGRect getBBox();
+  SVGRect getBBox(optional SVGBoundingBoxOptions aOptions);
   // Not implemented
   // SVGRect getStrokeBBox();
   SVGMatrix? getCTM();
   SVGMatrix? getScreenCTM();
   [Throws]
   SVGMatrix getTransformToElement(SVGGraphicsElement element);
 };
 
--- a/layout/svg/nsSVGClipPathFrame.cpp
+++ b/layout/svg/nsSVGClipPathFrame.cpp
@@ -320,8 +320,50 @@ nsSVGClipPathFrame::GetCanvasTM(uint32_t
   gfxMatrix tm =
     content->PrependLocalTransformsTo(mClipParentMatrix ?
                                       *mClipParentMatrix : gfxMatrix());
 
   return nsSVGUtils::AdjustMatrixForUnits(tm,
                                           &content->mEnumAttributes[SVGClipPathElement::CLIPPATHUNITS],
                                           mClipParent);
 }
+
+SVGBBox
+nsSVGClipPathFrame::GetBBoxForClipPathFrame(const SVGBBox &aBBox, 
+                                            const gfxMatrix &aMatrix)
+{
+  nsIContent* node = GetContent()->GetFirstChild();
+  SVGBBox unionBBox, tmpBBox;
+  for (; node; node = node->GetNextSibling()) {
+    nsIFrame *frame = 
+      static_cast<nsSVGElement*>(node)->GetPrimaryFrame();
+    if (frame) {
+      nsISVGChildFrame *svg = do_QueryFrame(frame);
+      if (svg) {
+        tmpBBox = svg->GetBBoxContribution(mozilla::gfx::ToMatrix(aMatrix), 
+                                         nsSVGUtils::eBBoxIncludeFill);
+        nsSVGEffects::EffectProperties effectProperties =
+                              nsSVGEffects::GetEffectProperties(frame);
+        bool isOK = true;
+        nsSVGClipPathFrame *clipPathFrame = 
+                              effectProperties.GetClipPathFrame(&isOK);
+        if (clipPathFrame && isOK) {
+          tmpBBox = clipPathFrame->GetBBoxForClipPathFrame(tmpBBox, aMatrix);
+        } 
+        tmpBBox.Intersect(aBBox);
+        unionBBox.UnionEdges(tmpBBox);
+      }
+    }
+  }
+  nsSVGEffects::EffectProperties props = 
+    nsSVGEffects::GetEffectProperties(this);    
+  if (props.mClipPath) {
+    bool isOK = true;
+    nsSVGClipPathFrame *clipPathFrame = props.GetClipPathFrame(&isOK);
+    if (clipPathFrame && isOK) {
+      tmpBBox = clipPathFrame->GetBBoxForClipPathFrame(aBBox, aMatrix);                                                       
+      unionBBox.Intersect(tmpBBox);
+    } else if (!isOK) {
+      unionBBox = SVGBBox();
+    }
+  }
+  return unionBBox;
+}
--- a/layout/svg/nsSVGClipPathFrame.h
+++ b/layout/svg/nsSVGClipPathFrame.h
@@ -70,16 +70,19 @@ public:
 
 #ifdef DEBUG_FRAME_DUMP
   virtual nsresult GetFrameName(nsAString& aResult) const MOZ_OVERRIDE
   {
     return MakeFrameName(NS_LITERAL_STRING("SVGClipPath"), aResult);
   }
 #endif
 
+  SVGBBox 
+  GetBBoxForClipPathFrame(const SVGBBox &aBBox, const gfxMatrix &aMatrix);
+
  private:
   // A helper class to allow us to paint clip paths safely. The helper
   // automatically sets and clears the mInUse flag on the clip path frame
   // (to prevent nasty reference loops). It's easy to mess this up
   // and break things, so this helper makes the code far more robust.
   class MOZ_STACK_CLASS AutoClipPathReferencer
   {
   public:
--- a/layout/svg/nsSVGUtils.cpp
+++ b/layout/svg/nsSVGUtils.cpp
@@ -37,44 +37,53 @@
 #include "nsSVGFilterPaintCallback.h"
 #include "nsSVGForeignObjectFrame.h"
 #include "gfxSVGGlyphs.h"
 #include "nsSVGInnerSVGFrame.h"
 #include "nsSVGIntegrationUtils.h"
 #include "nsSVGLength2.h"
 #include "nsSVGMaskFrame.h"
 #include "nsSVGOuterSVGFrame.h"
+#include "mozilla/dom/SVGClipPathElement.h"
 #include "mozilla/dom/SVGPathElement.h"
 #include "nsSVGPathGeometryElement.h"
 #include "nsSVGPathGeometryFrame.h"
 #include "nsSVGPaintServerFrame.h"
 #include "mozilla/dom/SVGSVGElement.h"
 #include "nsTextFrame.h"
 #include "SVGContentUtils.h"
 #include "mozilla/unused.h"
 
 using namespace mozilla;
 using namespace mozilla::dom;
 using namespace mozilla::gfx;
 
 static bool sSVGDisplayListHitTestingEnabled;
 static bool sSVGDisplayListPaintingEnabled;
+static bool sSVGNewGetBBoxEnabled;
 
 bool
 NS_SVGDisplayListHitTestingEnabled()
 {
   return sSVGDisplayListHitTestingEnabled;
 }
 
 bool
 NS_SVGDisplayListPaintingEnabled()
 {
   return sSVGDisplayListPaintingEnabled;
 }
 
+bool
+NS_SVGNewGetBBoxEnabled()
+{
+  return sSVGNewGetBBoxEnabled;
+}
+
+
 // we only take the address of this:
 static mozilla::gfx::UserDataKey sSVGAutoRenderStateKey;
 
 SVGAutoRenderState::SVGAutoRenderState(nsRenderingContext *aContext,
                                        RenderMode aMode
                                        MOZ_GUARD_OBJECT_NOTIFIER_PARAM_IN_IMPL)
   : mContext(aContext)
   , mOriginalRenderState(nullptr)
@@ -125,16 +134,19 @@ SVGAutoRenderState::IsPaintingToWindow(n
 void
 nsSVGUtils::Init()
 {
   Preferences::AddBoolVarCache(&sSVGDisplayListHitTestingEnabled,
                                "svg.display-lists.hit-testing.enabled");
 
   Preferences::AddBoolVarCache(&sSVGDisplayListPaintingEnabled,
                                "svg.display-lists.painting.enabled");
+
+  Preferences::AddBoolVarCache(&sSVGNewGetBBoxEnabled,
+                               "svg.new-getBBox.enabled");
 }
 
 nsSVGDisplayContainerFrame*
 nsSVGUtils::GetNearestSVGViewport(nsIFrame *aFrame)
 {
   NS_ASSERTION(aFrame->IsFrameOfType(nsIFrame::eSVG), "SVG frame expected");
   if (aFrame->GetType() == nsGkAtoms::svgOuterSVGFrame) {
     return nullptr;
@@ -880,28 +892,85 @@ nsSVGUtils::GetBBox(nsIFrame *aFrame, ui
       svg = do_QueryFrame(ancestor);
     }
     nsIContent* content = aFrame->GetContent();
     if (content->IsSVG() &&
         !static_cast<const nsSVGElement*>(content)->HasValidDimensions()) {
       return bbox;
     }
     gfxMatrix matrix;
-    if (aFrame->GetType() == nsGkAtoms::svgForeignObjectFrame) {
+    if (aFrame->GetType() == nsGkAtoms::svgForeignObjectFrame ||
+        aFrame->GetType() == nsGkAtoms::svgUseFrame) {
       // The spec says getBBox "Returns the tight bounding box in *current user
       // space*". So we should really be doing this for all elements, but that
       // needs investigation to check that we won't break too much content.
       // NOTE: When changing this to apply to other frame types, make sure to
       // also update nsSVGUtils::FrameSpaceInCSSPxToUserSpaceOffset.
       NS_ABORT_IF_FALSE(content->IsSVG(), "bad cast");
       nsSVGElement *element = static_cast<nsSVGElement*>(content);
       matrix = element->PrependLocalTransformsTo(matrix,
                           nsSVGElement::eChildToUserSpace);
     }
-    return svg->GetBBoxContribution(ToMatrix(matrix), aFlags).ToThebesRect();
+    bbox = svg->GetBBoxContribution(ToMatrix(matrix), aFlags).ToThebesRect();
+    // Account for 'clipped'.
+    if (aFlags & nsSVGUtils::eBBoxIncludeClipped) {
+      gfxRect clipRect(0, 0, 0, 0);
+      float x, y, width, height;
+      gfxMatrix tm;
+      gfxRect fillBBox = 
+        svg->GetBBoxContribution(ToMatrix(tm), 
+                                 nsSVGUtils::eBBoxIncludeFill).ToThebesRect();
+      x = fillBBox.x;
+      y = fillBBox.y;
+      width = fillBBox.width;
+      height = fillBBox.height;
+      bool hasClip = aFrame->StyleDisplay()->IsScrollableOverflow();
+      if (hasClip) {
+        clipRect = 
+          nsSVGUtils::GetClipRectForFrame(aFrame, x, y, width, height);
+          if (aFrame->GetType() == nsGkAtoms::svgForeignObjectFrame ||
+              aFrame->GetType() == nsGkAtoms::svgUseFrame) {
+            clipRect = matrix.TransformBounds(clipRect);
+          }
+      }
+      nsSVGEffects::EffectProperties effectProperties =
+        nsSVGEffects::GetEffectProperties(aFrame);
+      bool isOK = true;
+      nsSVGClipPathFrame *clipPathFrame = 
+        effectProperties.GetClipPathFrame(&isOK);
+      if (clipPathFrame && isOK) {
+        SVGClipPathElement *clipContent = 
+          static_cast<SVGClipPathElement*>(clipPathFrame->GetContent());
+        nsRefPtr<SVGAnimatedEnumeration> units = clipContent->ClipPathUnits();
+        if (units->AnimVal() == SVG_UNIT_TYPE_OBJECTBOUNDINGBOX) {
+          matrix = gfxMatrix().Scale(width, height) *
+                      gfxMatrix().Translate(gfxPoint(x, y)) *
+                      matrix;
+        } else if (aFrame->GetType() == nsGkAtoms::svgForeignObjectFrame) {
+          matrix.Reset();
+        }
+        bbox = 
+          clipPathFrame->GetBBoxForClipPathFrame(bbox, matrix).ToThebesRect();
+        if (hasClip) {
+          bbox = bbox.Intersect(clipRect);
+        }
+      } else {
+        if (!isOK) {
+          bbox = gfxRect(0, 0, 0, 0);
+        } else {
+          if (hasClip) {
+            bbox = bbox.Intersect(clipRect);
+          }
+        }
+      }
+      if (bbox.IsEmpty()) {
+        bbox = gfxRect(0, 0, 0, 0);
+      }
+    }
+    return bbox;
   }
   return nsSVGIntegrationUtils::GetSVGBBoxForNonSVGFrame(aFrame);
 }
 
 gfxPoint
 nsSVGUtils::FrameSpaceInCSSPxToUserSpaceOffset(nsIFrame *aFrame)
 {
   if (!(aFrame->GetStateBits() & NS_FRAME_SVG_LAYOUT)) {
@@ -914,17 +983,18 @@ nsSVGUtils::FrameSpaceInCSSPxToUserSpace
   if (aFrame->IsFrameOfType(nsIFrame::eSVGGeometry) ||
       aFrame->IsSVGText()) {
     return nsLayoutUtils::RectToGfxRect(aFrame->GetRect(),
                                          nsPresContext::AppUnitsPerCSSPixel()).TopLeft();
   }
 
   // For foreignObject frames, nsSVGUtils::GetBBox applies their local
   // transform, so we need to do the same here.
-  if (aFrame->GetType() == nsGkAtoms::svgForeignObjectFrame) {
+  if (aFrame->GetType() == nsGkAtoms::svgForeignObjectFrame ||
+      aFrame->GetType() == nsGkAtoms::svgUseFrame) {
     gfxMatrix transform = static_cast<nsSVGElement*>(aFrame->GetContent())->
         PrependLocalTransformsTo(gfxMatrix(),
                                  nsSVGElement::eChildToUserSpace);
     NS_ASSERTION(!transform.HasNonTranslation(), "we're relying on this being an offset-only transform");
     return transform.GetTranslation();
   }
 
   return gfxPoint();
--- a/layout/svg/nsSVGUtils.h
+++ b/layout/svg/nsSVGUtils.h
@@ -32,16 +32,17 @@ class gfxPattern;
 class nsFrameList;
 class nsIContent;
 class nsIDocument;
 class nsIFrame;
 class nsPresContext;
 class nsRenderingContext;
 class nsStyleContext;
 class nsStyleCoord;
+class nsSVGClipPathFrame;
 class nsSVGDisplayContainerFrame;
 class nsSVGElement;
 class nsSVGEnum;
 class nsSVGLength2;
 class nsSVGOuterSVGFrame;
 class nsSVGPathGeometryFrame;
 class nsTextFrame;
 class gfxTextContextPaint;
@@ -71,16 +72,17 @@ class SourceSurface;
 
 #define SVG_HIT_TEST_FILL        0x01
 #define SVG_HIT_TEST_STROKE      0x02
 #define SVG_HIT_TEST_CHECK_MRECT 0x04
 
 
 bool NS_SVGDisplayListHitTestingEnabled();
 bool NS_SVGDisplayListPaintingEnabled();
+bool NS_SVGNewGetBBoxEnabled();
 
 /**
  * Sometimes we need to distinguish between an empty box and a box
  * that contains an element that has no size e.g. a point at the origin.
  */
 class SVGBBox {
   typedef mozilla::gfx::Rect Rect;
 
@@ -105,16 +107,29 @@ public:
   void UnionEdges(const SVGBBox& aSVGBBox) {
     if (aSVGBBox.mIsEmpty) {
       return;
     }
     mBBox = mIsEmpty ? aSVGBBox.mBBox : mBBox.UnionEdges(aSVGBBox.mBBox);
     mIsEmpty = false;
   }
 
+  void Intersect(const SVGBBox& aSVGBBox) {
+    if (!mIsEmpty && !aSVGBBox.mIsEmpty) {
+      mBBox = mBBox.Intersect(aSVGBBox.mBBox);
+      if (mBBox.IsEmpty()) {
+        mIsEmpty = true;
+        mBBox = Rect(0, 0, 0, 0);
+      }
+    } else {
+      mIsEmpty = true;
+      mBBox = Rect(0, 0, 0, 0);
+    }
+  }
+
 private:
   Rect mBBox;
   bool mIsEmpty;
 };
 
 // GRRR WINDOWS HATE HATE HATE
 #undef CLIP_MASK
 
@@ -391,17 +406,18 @@ public:
                        nsSVGEnum *aUnits,
                        nsIFrame *aFrame);
 
   enum BBoxFlags {
     eBBoxIncludeFill           = 1 << 0,
     eBBoxIncludeFillGeometry   = 1 << 1,
     eBBoxIncludeStroke         = 1 << 2,
     eBBoxIncludeStrokeGeometry = 1 << 3,
-    eBBoxIncludeMarkers        = 1 << 4
+    eBBoxIncludeMarkers        = 1 << 4,
+    eBBoxIncludeClipped        = 1 << 5
   };
   /**
    * Get the SVG bbox (the SVG spec's simplified idea of bounds) of aFrame in
    * aFrame's userspace.
    */
   static gfxRect GetBBox(nsIFrame *aFrame,
                          uint32_t aFlags = eBBoxIncludeFillGeometry);
 
--- a/modules/libpref/src/init/all.js
+++ b/modules/libpref/src/init/all.js
@@ -2083,16 +2083,23 @@ pref("svg.marker-improvements.enabled", 
 #endif
 
 #ifdef RELEASE_BUILD
 pref("svg.svg-iframe.enabled", false);
 #else
 pref("svg.svg-iframe.enabled", false);
 #endif
 
+// Is support for the new getBBox method from SVG 2 enabled?  
+// See https://svgwg.org/svg2-draft/single-page.html#types-SVGBoundingBoxOptions
+#ifdef RELEASE_BUILD
+pref("svg.new-getBBox.enabled", false);
+#else
+pref("svg.new-getBBox.enabled", true);
+#endif
 
 // Default font types and sizes by locale
 pref("font.default.ar", "sans-serif");
 pref("font.minimum-size.ar", 0);
 pref("font.size.variable.ar", 16);
 pref("font.size.fixed.ar", 13);
 
 pref("font.default.el", "serif");