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 184131 e56e0f49a088640c4f98e9f267b3199e4e248789
parent 184130 6c446b233036afc69b6590133328a117edbfbd8a
child 184132 dcbcf74976277493bc36b43f420bd3e897da3fdd
push id6897
push userryanvm@gmail.com
push dateWed, 21 May 2014 12:56:42 +0000
treeherderfx-team@a971ac323875 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerslongsonr, bz
bugs999964
milestone32.0a1
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");