Bug 1323962 - Should support CSS transform for clip-path r=longsonr
authorviolet <violet.bugreport@gmail.com>
Mon, 22 Apr 2019 23:59:38 +0000
changeset 470453 29ad5178399d2ae1743ff24838818e4f77ccfe39
parent 470452 6ab85d4927b51b449d4d3c8ef3be3679fd2f04e7
child 470454 03ea838144b57003bee53950ae138e23bf0f8066
push id35906
push useraciure@mozilla.com
push dateTue, 23 Apr 2019 22:14:56 +0000
treeherdermozilla-central@0ce3633f8b80 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerslongsonr
bugs1323962
milestone68.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 1323962 - Should support CSS transform for clip-path r=longsonr <clipPath> can itself have SVG transform, thus we need to override nsIFrame::IsSVGTransformed() method so that layout code will be aware of the SVG transform. The remaining is similar to <mask> Differential Revision: https://phabricator.services.mozilla.com/D28272
layout/reftests/svg/reftest.list
layout/reftests/svg/test_bug1323962-3-ref.html
layout/reftests/svg/test_bug1323962-3.html
layout/svg/nsSVGClipPathFrame.cpp
layout/svg/nsSVGClipPathFrame.h
layout/svg/nsSVGUtils.cpp
testing/web-platform/meta/css/css-masking/clip-path-svg-content/clip-path-css-transform-001.svg.ini
testing/web-platform/meta/css/css-masking/clip-path-svg-content/clip-path-css-transform-002.svg.ini
testing/web-platform/meta/css/css-masking/clip-path-svg-content/clip-path-css-transform-003.svg.ini
testing/web-platform/meta/css/css-masking/clip-path-svg-content/clip-path-css-transform-004.svg.ini
--- a/layout/reftests/svg/reftest.list
+++ b/layout/reftests/svg/reftest.list
@@ -141,16 +141,17 @@ fuzzy-if(d2d||skiaContent,0-1,0-10000) =
 == dynamic-marker-01.svg pass.svg
 == dynamic-marker-02.svg dynamic-marker-02-ref.svg
 == dynamic-marker-03.svg pass.svg
 == dynamic-mask-01.svg pass.svg
 == dynamic-mask-contents-01.svg pass.svg
 == dynamic-mask-pre-effects-bbox.html dynamic-mask-pre-effects-bbox-ref.html
 == test_bug1323962.html test_bug1323962-ref.html
 == test_bug1323962-2.html test_bug1323962-2-ref.html
+== test_bug1323962-3.html test_bug1323962-3-ref.html
 == dynamic-opacity-property-01.svg pass.svg
 == dynamic-pattern-01.svg pass.svg
 == dynamic-pattern-02.svg pass.svg
 == dynamic-pattern-contents-01.svg pass.svg
 == dynamic-pattern-contents-02.svg pass.svg
 == dynamic-rect-01.svg dynamic-rect-01-ref.svg
 fuzzy-if(d2d&&layersGPUAccelerated,0-3,0-1200) == dynamic-rect-02.svg dynamic-rect-02-ref.svg # bug 776038 for Win7, Win8
 == dynamic-rect-03.svg dynamic-rect-03-ref.svg
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/test_bug1323962-3-ref.html
@@ -0,0 +1,18 @@
+<svg style="width: 400px; height: 400px; border: 1px solid red">
+  <defs>
+    <clipPath id="clipPath1">
+      <rect id="rec1" x="15px" y="15px" width="40px" height="40px" />
+      <rect id="rec2" width="20px" height="10px" />
+    </clipPath>
+    <clipPath id="clipPath2">
+      <rect id="rec3" x="100px" y="100px" width="40px" height="40px" />
+    </clipPath>
+    <clipPath id="clipPath3" transform="translate(100,100)">
+      <rect id="rec4" x="200px" y="200px" width="40px" height="40px" />
+    </clipPath>
+  </defs>
+
+  <circle cx="25px" cy="25px" r="20px" fill="skyblue" clip-path="url(#clipPath1)" />
+  <circle cx="125px" cy="125px" r="20px" fill="skyblue" clip-path="url(#clipPath2)" />
+  <circle cx="225px" cy="225px" r="20px" fill="skyblue" clip-path="url(#clipPath3)" />
+</svg>
new file mode 100644
--- /dev/null
+++ b/layout/reftests/svg/test_bug1323962-3.html
@@ -0,0 +1,29 @@
+<style>
+#rec1 {
+  transform: translate(15px,15px) scale(2,4);
+}
+#rec3 {
+  transform: translate(100px,100px) scale(2,4);
+}
+#clipPath3 {
+  transform: translate(100px,100px);
+}
+</style>
+<svg style="width: 400px; height: 400px; border: 1px solid red">
+  <defs>
+    <clipPath id="clipPath1">
+      <rect id="rec1" width="20px" height="10px" />
+      <rect id="rec2" width="20px" height="10px" />
+    </clipPath>
+    <clipPath id="clipPath2">
+      <rect id="rec3" width="20px" height="10px" />
+    </clipPath>
+    <clipPath id="clipPath3">
+      <rect id="rec4" x="200px" y="200px" width="40px" height="40px" />
+    </clipPath>
+  </defs>
+
+  <circle cx="25px" cy="25px" r="20px" fill="skyblue" clip-path="url(#clipPath1)" />
+  <circle cx="125px" cy="125px" r="20px" fill="skyblue" clip-path="url(#clipPath2)" />
+  <circle cx="225px" cy="225px" r="20px" fill="skyblue" clip-path="url(#clipPath3)" />
+</svg>
--- a/layout/svg/nsSVGClipPathFrame.cpp
+++ b/layout/svg/nsSVGClipPathFrame.cpp
@@ -52,18 +52,21 @@ void nsSVGClipPathFrame::ApplyClipPath(g
   nsSVGDisplayableFrame* singleClipPathChild = nullptr;
   IsTrivial(&singleClipPathChild);
 
   if (singleClipPathChild) {
     SVGGeometryFrame* pathFrame = do_QueryFrame(singleClipPathChild);
     if (pathFrame && pathFrame->StyleVisibility()->IsVisible()) {
       SVGGeometryElement* pathElement =
           static_cast<SVGGeometryElement*>(pathFrame->GetContent());
-      gfxMatrix toChildsUserSpace = pathElement->PrependLocalTransformsTo(
-          GetClipPathTransform(aClippedFrame) * aMatrix, eUserSpaceToParent);
+
+      gfxMatrix toChildsUserSpace =
+          nsSVGUtils::GetTransformMatrixInUserSpace(pathFrame, this) *
+          (GetClipPathTransform(aClippedFrame) * aMatrix);
+
       gfxMatrix newMatrix = aContext.CurrentMatrixDouble()
                                 .PreMultiply(toChildsUserSpace)
                                 .NudgeToIntegers();
       if (!newMatrix.IsSingular()) {
         aContext.SetMatrixDouble(newMatrix);
         FillRule clipRule =
             nsSVGUtils::ToFillRule(pathFrame->StyleSVG()->mClipRule);
         clipPath = pathElement->GetOrBuildPath(drawTarget, clipRule);
@@ -212,18 +215,18 @@ void nsSVGClipPathFrame::PaintFrameIntoM
     // blend using |maskSurface|.
   }
 
   gfxMatrix toChildsUserSpace = mMatrixForChildren;
   nsIFrame* child = do_QueryFrame(frame);
   nsIContent* childContent = child->GetContent();
   if (childContent->IsSVGElement()) {
     toChildsUserSpace =
-        static_cast<const SVGElement*>(childContent)
-            ->PrependLocalTransformsTo(mMatrixForChildren, eUserSpaceToParent);
+        nsSVGUtils::GetTransformMatrixInUserSpace(child, child->GetParent()) *
+        mMatrixForChildren;
   }
 
   // clipPath does not result in any image rendering, so we just use a dummy
   // imgDrawingParams instead of requiring our caller to pass one.
   image::imgDrawingParams imgParams;
 
   // Our children have NS_STATE_SVG_CLIPPATH_CHILD set on them, and
   // SVGGeometryFrame::Render checks for that state bit and paints
@@ -293,19 +296,18 @@ bool nsSVGClipPathFrame::PointIsInsideCl
       !clipPathFrame->PointIsInsideClipPath(aClippedFrame, aPoint)) {
     return false;
   }
 
   for (nsIFrame* kid = mFrames.FirstChild(); kid; kid = kid->GetNextSibling()) {
     nsSVGDisplayableFrame* SVGFrame = do_QueryFrame(kid);
     if (SVGFrame) {
       gfxPoint pointForChild = point;
-      gfxMatrix m =
-          static_cast<SVGElement*>(kid->GetContent())
-              ->PrependLocalTransformsTo(gfxMatrix(), eUserSpaceToParent);
+
+      gfxMatrix m = nsSVGUtils::GetTransformMatrixInUserSpace(kid, this);
       if (!m.IsIdentity()) {
         if (!m.Invert()) {
           return false;
         }
         pointForChild = m.TransformPoint(point);
       }
       if (SVGFrame->GetFrameForPoint(pointForChild)) {
         return true;
@@ -421,17 +423,19 @@ void nsSVGClipPathFrame::Init(nsIContent
   nsSVGContainerFrame::Init(aContent, aParent, aPrevInFlow);
 }
 
 gfxMatrix nsSVGClipPathFrame::GetCanvasTM() { return mMatrixForChildren; }
 
 gfxMatrix nsSVGClipPathFrame::GetClipPathTransform(nsIFrame* aClippedFrame) {
   SVGClipPathElement* content = static_cast<SVGClipPathElement*>(GetContent());
 
-  gfxMatrix tm = content->PrependLocalTransformsTo(gfxMatrix());
+  gfxMatrix tm =
+      content->PrependLocalTransformsTo({}, eChildToUserSpace) *
+      nsSVGUtils::GetTransformMatrixInUserSpace(this, this->GetParent());
 
   SVGAnimatedEnumeration* clipPathUnits =
       &content->mEnumAttributes[SVGClipPathElement::CLIPPATHUNITS];
 
   uint32_t flags = nsSVGUtils::eBBoxIncludeFillGeometry |
                    (aClippedFrame->StyleBorder()->mBoxDecorationBreak ==
                             StyleBoxDecorationBreak::Clone
                         ? nsSVGUtils::eIncludeOnlyCurrentFrameForNonSVGElement
@@ -455,17 +459,17 @@ SVGBBox nsSVGClipPathFrame::GetBBoxForCl
   SVGBBox unionBBox, tmpBBox;
   for (; node; node = node->GetNextSibling()) {
     SVGElement* svgNode = static_cast<SVGElement*>(node);
     nsIFrame* frame = svgNode->GetPrimaryFrame();
     if (frame) {
       nsSVGDisplayableFrame* svg = do_QueryFrame(frame);
       if (svg) {
         gfxMatrix matrix =
-            svgNode->PrependLocalTransformsTo(aMatrix, eUserSpaceToParent);
+            nsSVGUtils::GetTransformMatrixInUserSpace(frame, this) * aMatrix;
         tmpBBox = svg->GetBBoxContribution(mozilla::gfx::ToMatrix(matrix),
                                            nsSVGUtils::eBBoxIncludeFill);
         nsSVGClipPathFrame* clipPathFrame;
         if (SVGObserverUtils::GetAndObserveClipPath(frame, &clipPathFrame) !=
                 SVGObserverUtils::eHasRefsSomeInvalid &&
             clipPathFrame) {
           tmpBBox =
               clipPathFrame->GetBBoxForClipPathFrame(tmpBBox, aMatrix, aFlags);
@@ -480,8 +484,24 @@ SVGBBox nsSVGClipPathFrame::GetBBoxForCl
 
   if (clipPathThatClipsClipPath) {
     tmpBBox = clipPathThatClipsClipPath->GetBBoxForClipPathFrame(aBBox, aMatrix,
                                                                  aFlags);
     unionBBox.Intersect(tmpBBox);
   }
   return unionBBox;
 }
+
+bool nsSVGClipPathFrame::IsSVGTransformed(Matrix* aOwnTransforms,
+                                          Matrix* aFromParentTransforms) const {
+  auto e = static_cast<SVGElement const*>(GetContent());
+  Matrix m = ToMatrix(e->PrependLocalTransformsTo({}, eUserSpaceToParent));
+
+  if (m.IsIdentity()) {
+    return false;
+  }
+
+  if (aOwnTransforms) {
+    *aOwnTransforms = m;
+  }
+
+  return true;
+}
--- a/layout/svg/nsSVGClipPathFrame.h
+++ b/layout/svg/nsSVGClipPathFrame.h
@@ -37,16 +37,19 @@ class nsSVGClipPathFrame final : public 
 
  public:
   NS_DECL_FRAMEARENA_HELPERS(nsSVGClipPathFrame)
 
   // nsIFrame methods:
   virtual void BuildDisplayList(nsDisplayListBuilder* aBuilder,
                                 const nsDisplayListSet& aLists) override {}
 
+  virtual bool IsSVGTransformed(Matrix* aOwnTransforms,
+                                Matrix* aFromParentTransforms) const override;
+
   // nsSVGClipPathFrame methods:
 
   /**
    * Applies the clipPath by pushing a clip path onto the DrawTarget.
    *
    * This method must only be used if IsTrivial() returns true, otherwise use
    * GetClipMask.
    *
--- a/layout/svg/nsSVGUtils.cpp
+++ b/layout/svg/nsSVGUtils.cpp
@@ -1087,18 +1087,21 @@ gfxRect nsSVGUtils::GetBBox(nsIFrame* aF
         SVGClipPathElement* clipContent =
             static_cast<SVGClipPathElement*>(clipPathFrame->GetContent());
         if (clipContent->IsUnitsObjectBoundingBox()) {
           matrix.PreTranslate(gfxPoint(x, y));
           matrix.PreScale(width, height);
         } else if (aFrame->IsSVGForeignObjectFrame()) {
           matrix = gfxMatrix();
         }
-        matrix =
-            clipContent->PrependLocalTransformsTo(matrix, eUserSpaceToParent);
+
+        matrix = nsSVGUtils::GetTransformMatrixInUserSpace(
+                     clipPathFrame, clipPathFrame->GetParent()) *
+                 matrix;
+
         bbox = clipPathFrame->GetBBoxForClipPathFrame(bbox, matrix, aFlags)
                    .ToThebesRect();
       }
 
       if (hasClip) {
         bbox = bbox.Intersect(clipRect);
       }
 
deleted file mode 100644
--- a/testing/web-platform/meta/css/css-masking/clip-path-svg-content/clip-path-css-transform-001.svg.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[clip-path-css-transform-001.svg]
-  expected: FAIL
deleted file mode 100644
--- a/testing/web-platform/meta/css/css-masking/clip-path-svg-content/clip-path-css-transform-002.svg.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[clip-path-css-transform-002.svg]
-  expected: FAIL
deleted file mode 100644
--- a/testing/web-platform/meta/css/css-masking/clip-path-svg-content/clip-path-css-transform-003.svg.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[clip-path-css-transform-003.svg]
-  expected: FAIL
deleted file mode 100644
--- a/testing/web-platform/meta/css/css-masking/clip-path-svg-content/clip-path-css-transform-004.svg.ini
+++ /dev/null
@@ -1,2 +0,0 @@
-[clip-path-css-transform-004.svg]
-  expected: FAIL