From 5b5a18e51d4e47ac18d3bf91975906c327b3119d Mon Sep 17 00:00:00 2001 From: Jonathan Watt Date: Thu, 7 Aug 2014 08:09:31 +0100 Subject: [PATCH] Bug 1049256, part 1 - Convert SVG hit-testing to act on an SVG user space point instead of calling nsSVGUtils::GetCanvasTM(). r=longsonr --- layout/svg/SVGTextFrame.cpp | 18 ++++----- layout/svg/SVGTextFrame.h | 2 +- layout/svg/nsISVGChildFrame.h | 12 +++--- layout/svg/nsSVGClipPathFrame.cpp | 53 ++++++++++++++++++-------- layout/svg/nsSVGClipPathFrame.h | 15 ++++++-- layout/svg/nsSVGContainerFrame.cpp | 2 +- layout/svg/nsSVGContainerFrame.h | 2 +- layout/svg/nsSVGForeignObjectFrame.cpp | 25 ++++-------- layout/svg/nsSVGForeignObjectFrame.h | 2 +- layout/svg/nsSVGImageFrame.cpp | 44 ++++++++++++--------- layout/svg/nsSVGInnerSVGFrame.cpp | 19 ++++----- layout/svg/nsSVGInnerSVGFrame.h | 2 +- layout/svg/nsSVGIntegrationUtils.cpp | 4 +- layout/svg/nsSVGOuterSVGFrame.cpp | 22 ++++++----- layout/svg/nsSVGPathGeometryFrame.cpp | 48 ++++++++++------------- layout/svg/nsSVGPathGeometryFrame.h | 2 +- layout/svg/nsSVGSwitchFrame.cpp | 19 +++++++-- layout/svg/nsSVGUtils.cpp | 39 ++++++++++++++++--- layout/svg/nsSVGUtils.h | 11 ++++-- 19 files changed, 206 insertions(+), 135 deletions(-) diff --git a/layout/svg/SVGTextFrame.cpp b/layout/svg/SVGTextFrame.cpp index e36b97e2584..6c8508ee625 100644 --- a/layout/svg/SVGTextFrame.cpp +++ b/layout/svg/SVGTextFrame.cpp @@ -3113,8 +3113,12 @@ nsDisplaySVGText::HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, SVGTextFrame *frame = static_cast(mFrame); nsPoint pointRelativeToReferenceFrame = aRect.Center(); // ToReferenceFrame() includes frame->GetPosition(), our user space position. - nsPoint userSpacePt = pointRelativeToReferenceFrame - - (ToReferenceFrame() - frame->GetPosition()); + nsPoint userSpacePtInAppUnits = pointRelativeToReferenceFrame - + (ToReferenceFrame() - frame->GetPosition()); + + gfxPoint userSpacePt = + gfxPoint(userSpacePtInAppUnits.x, userSpacePtInAppUnits.y) / + frame->PresContext()->AppUnitsPerCSSPixel(); nsIFrame* target = frame->GetFrameForPoint(userSpacePt); if (target) { @@ -3690,7 +3694,7 @@ SVGTextFrame::PaintSVG(nsRenderingContext* aContext, } nsIFrame* -SVGTextFrame::GetFrameForPoint(const nsPoint& aPoint) +SVGTextFrame::GetFrameForPoint(const gfxPoint& aPoint) { NS_ASSERTION(GetFirstPrincipalChild(), "must have a child frame"); @@ -3713,9 +3717,6 @@ SVGTextFrame::GetFrameForPoint(const nsPoint& aPoint) nsPresContext* presContext = PresContext(); - gfxPoint pointInOuterSVGUserUnits = - gfxPoint(aPoint.x, aPoint.y) / PresContext()->AppUnitsPerCSSPixel(); - // Ideally we'd iterate backwards so that we can just return the first frame // that is under aPoint. In practice this will rarely matter though since it // is rare for text in/under an SVG element to overlap (i.e. the first @@ -3729,13 +3730,12 @@ SVGTextFrame::GetFrameForPoint(const nsPoint& aPoint) continue; } - gfxMatrix m = run.GetTransformFromRunUserSpaceToUserSpace(presContext) * - GetCanvasTM(FOR_HIT_TESTING); + gfxMatrix m = run.GetTransformFromRunUserSpaceToUserSpace(presContext); if (!m.Invert()) { return nullptr; } - gfxPoint pointInRunUserSpace = m.Transform(pointInOuterSVGUserUnits); + gfxPoint pointInRunUserSpace = m.Transform(aPoint); gfxRect frameRect = run.GetRunUserSpaceRect(presContext, TextRenderedRun::eIncludeFill | TextRenderedRun::eIncludeStroke).ToThebesRect(); diff --git a/layout/svg/SVGTextFrame.h b/layout/svg/SVGTextFrame.h index a868bbc71fc..e1fda40673c 100644 --- a/layout/svg/SVGTextFrame.h +++ b/layout/svg/SVGTextFrame.h @@ -319,7 +319,7 @@ public: virtual nsresult PaintSVG(nsRenderingContext* aContext, const nsIntRect* aDirtyRect, nsIFrame* aTransformRoot = nullptr) MOZ_OVERRIDE; - virtual nsIFrame* GetFrameForPoint(const nsPoint& aPoint) MOZ_OVERRIDE; + virtual nsIFrame* GetFrameForPoint(const gfxPoint& aPoint) MOZ_OVERRIDE; virtual void ReflowSVG() MOZ_OVERRIDE; virtual nsRect GetCoveredRegion() MOZ_OVERRIDE; virtual SVGBBox GetBBoxContribution(const Matrix& aToBBoxUserspace, diff --git a/layout/svg/nsISVGChildFrame.h b/layout/svg/nsISVGChildFrame.h index 65ec1c62f7b..73da2447496 100644 --- a/layout/svg/nsISVGChildFrame.h +++ b/layout/svg/nsISVGChildFrame.h @@ -62,11 +62,13 @@ public: const nsIntRect *aDirtyRect, nsIFrame* aTransformRoot = nullptr) = 0; - // Check if this frame or children contain the given point, - // specified in app units relative to the origin of the outer - // svg frame (origin ill-defined in the case of borders - bug - // 290770). See bug 290852 for foreignObject complications. - virtual nsIFrame* GetFrameForPoint(const nsPoint &aPoint)=0; + /** + * Returns the frame that should handle pointer events at aPoint. aPoint is + * expected to be in the SVG user space of the frame on which this method is + * called. The frame returned may be the frame on which this method is + * called, any of its descendants or else nullptr. + */ + virtual nsIFrame* GetFrameForPoint(const gfxPoint& aPoint) = 0; // Get bounds in our nsSVGOuterSVGFrame's coordinates space (in app units) virtual nsRect GetCoveredRegion()=0; diff --git a/layout/svg/nsSVGClipPathFrame.cpp b/layout/svg/nsSVGClipPathFrame.cpp index 52499545607..0e0f02b8f99 100644 --- a/layout/svg/nsSVGClipPathFrame.cpp +++ b/layout/svg/nsSVGClipPathFrame.cpp @@ -157,9 +157,8 @@ nsSVGClipPathFrame::ClipPaint(nsRenderingContext* aContext, } bool -nsSVGClipPathFrame::ClipHitTest(nsIFrame* aParent, - const gfxMatrix &aMatrix, - const nsPoint &aPoint) +nsSVGClipPathFrame::PointIsInsideClipPath(nsIFrame* aClippedFrame, + const gfxPoint &aPoint) { // If the flag is set when we get here, it means this clipPath frame // has already been used in hit testing against the current clip, @@ -170,29 +169,40 @@ nsSVGClipPathFrame::ClipHitTest(nsIFrame* aParent, } AutoClipPathReferencer clipRef(this); - mClipParent = aParent; - if (mClipParentMatrix) { - *mClipParentMatrix = aMatrix; - } else { - mClipParentMatrix = new gfxMatrix(aMatrix); + gfxMatrix matrix = GetClipPathTransform(aClippedFrame); + if (!matrix.Invert()) { + return false; } + gfxPoint point = matrix.Transform(aPoint); + // clipPath elements can themselves be clipped by a different clip path. In + // that case the other clip path further clips away the element that is being + // clipped by the original clipPath. If this clipPath is being clipped by a + // different clip path we need to check if it prevents the original element + // from recieving events at aPoint: nsSVGClipPathFrame *clipPathFrame = nsSVGEffects::GetEffectProperties(this).GetClipPathFrame(nullptr); - if (clipPathFrame && !clipPathFrame->ClipHitTest(aParent, aMatrix, aPoint)) + if (clipPathFrame && + !clipPathFrame->PointIsInsideClipPath(aClippedFrame, aPoint)) { return false; + } for (nsIFrame* kid = mFrames.FirstChild(); kid; kid = kid->GetNextSibling()) { nsISVGChildFrame* SVGFrame = do_QueryFrame(kid); if (SVGFrame) { - // Notify the child frame that we may be working with a - // different transform, so it can update its covered region - // (used to shortcut hit testing). - SVGFrame->NotifySVGChanged(nsISVGChildFrame::TRANSFORM_CHANGED); - - if (SVGFrame->GetFrameForPoint(aPoint)) + gfxPoint pointForChild = point; + gfxMatrix m = static_cast(kid->GetContent())-> + PrependLocalTransformsTo(gfxMatrix(), nsSVGElement::eUserSpaceToParent); + if (!m.IsIdentity()) { + if (!m.Invert()) { + return false; + } + pointForChild = m.Transform(point); + } + if (SVGFrame->GetFrameForPoint(pointForChild)) { return true; + } } } return false; @@ -326,6 +336,19 @@ nsSVGClipPathFrame::GetCanvasTM(uint32_t aFor, nsIFrame* aTransformRoot) mClipParent); } +gfxMatrix +nsSVGClipPathFrame::GetClipPathTransform(nsIFrame* aClippedFrame) +{ + SVGClipPathElement *content = static_cast(mContent); + + gfxMatrix tm = content->PrependLocalTransformsTo(gfxMatrix()); + + nsSVGEnum* clipPathUnits = + &content->mEnumAttributes[SVGClipPathElement::CLIPPATHUNITS]; + + return nsSVGUtils::AdjustMatrixForUnits(tm, clipPathUnits, aClippedFrame); +} + SVGBBox nsSVGClipPathFrame::GetBBoxForClipPathFrame(const SVGBBox &aBBox, const gfxMatrix &aMatrix) diff --git a/layout/svg/nsSVGClipPathFrame.h b/layout/svg/nsSVGClipPathFrame.h index 74cbbb00437..602bcbc48c1 100644 --- a/layout/svg/nsSVGClipPathFrame.h +++ b/layout/svg/nsSVGClipPathFrame.h @@ -41,9 +41,10 @@ public: nsIFrame* aParent, const gfxMatrix &aMatrix); - bool ClipHitTest(nsIFrame* aParent, - const gfxMatrix &aMatrix, - const nsPoint &aPoint); + /** + * aPoint is expected to be in aClippedFrame's SVG user space. + */ + bool PointIsInsideClipPath(nsIFrame* aClippedFrame, const gfxPoint &aPoint); // Check if this clipPath is made up of more than one geometry object. // If so, the clipping API in cairo isn't enough and we need to use @@ -78,6 +79,14 @@ public: SVGBBox GetBBoxForClipPathFrame(const SVGBBox &aBBox, const gfxMatrix &aMatrix); + /** + * If the clipPath element transforms its children due to + * clipPathUnits="objectBoundingBox" being set on it and/or due to the + * 'transform' attribute being set on it, this function returns the resulting + * transform. + */ + gfxMatrix GetClipPathTransform(nsIFrame* aClippedFrame); + 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 diff --git a/layout/svg/nsSVGContainerFrame.cpp b/layout/svg/nsSVGContainerFrame.cpp index 6d6c0c8bd07..b9811d9320b 100644 --- a/layout/svg/nsSVGContainerFrame.cpp +++ b/layout/svg/nsSVGContainerFrame.cpp @@ -271,7 +271,7 @@ nsSVGDisplayContainerFrame::PaintSVG(nsRenderingContext* aContext, } nsIFrame* -nsSVGDisplayContainerFrame::GetFrameForPoint(const nsPoint &aPoint) +nsSVGDisplayContainerFrame::GetFrameForPoint(const gfxPoint& aPoint) { NS_ASSERTION(!NS_SVGDisplayListHitTestingEnabled() || (mState & NS_FRAME_IS_NONDISPLAY), diff --git a/layout/svg/nsSVGContainerFrame.h b/layout/svg/nsSVGContainerFrame.h index 3d166a34f83..29f3aeb7821 100644 --- a/layout/svg/nsSVGContainerFrame.h +++ b/layout/svg/nsSVGContainerFrame.h @@ -148,7 +148,7 @@ public: virtual nsresult PaintSVG(nsRenderingContext* aContext, const nsIntRect *aDirtyRect, nsIFrame* aTransformRoot = nullptr) MOZ_OVERRIDE; - virtual nsIFrame* GetFrameForPoint(const nsPoint &aPoint) MOZ_OVERRIDE; + virtual nsIFrame* GetFrameForPoint(const gfxPoint& aPoint) MOZ_OVERRIDE; virtual nsRect GetCoveredRegion() MOZ_OVERRIDE; virtual void ReflowSVG() MOZ_OVERRIDE; virtual void NotifySVGChanged(uint32_t aFlags) MOZ_OVERRIDE; diff --git a/layout/svg/nsSVGForeignObjectFrame.cpp b/layout/svg/nsSVGForeignObjectFrame.cpp index e16899e2034..bbe906a696b 100644 --- a/layout/svg/nsSVGForeignObjectFrame.cpp +++ b/layout/svg/nsSVGForeignObjectFrame.cpp @@ -280,7 +280,7 @@ nsSVGForeignObjectFrame::PaintSVG(nsRenderingContext *aContext, } nsIFrame* -nsSVGForeignObjectFrame::GetFrameForPoint(const nsPoint &aPoint) +nsSVGForeignObjectFrame::GetFrameForPoint(const gfxPoint& aPoint) { NS_ASSERTION(!NS_SVGDisplayListHitTestingEnabled() || (mState & NS_FRAME_IS_NONDISPLAY), @@ -298,29 +298,18 @@ nsSVGForeignObjectFrame::GetFrameForPoint(const nsPoint &aPoint) static_cast(mContent)-> GetAnimatedLengthValues(&x, &y, &width, &height, nullptr); - gfxMatrix tm = GetCanvasTM(FOR_HIT_TESTING); - if (!tm.Invert()) { + if (!gfxRect(x, y, width, height).Contains(aPoint) || + !nsSVGUtils::HitTestClip(this, aPoint)) { return nullptr; } - // Convert aPoint from app units in canvas space to user space: + // Convert the point to app units relative to the top-left corner of the + // viewport that's established by the foreignObject element: - gfxPoint pt = gfxPoint(aPoint.x, aPoint.y) / PresContext()->AppUnitsPerCSSPixel(); - pt = tm.Transform(pt); - - if (!gfxRect(0.0f, 0.0f, width, height).Contains(pt)) - return nullptr; - - // Convert pt to app units in *local* space: - - pt = pt * nsPresContext::AppUnitsPerCSSPixel(); + gfxPoint pt = (aPoint + gfxPoint(x, y)) * nsPresContext::AppUnitsPerCSSPixel(); nsPoint point = nsPoint(NSToIntRound(pt.x), NSToIntRound(pt.y)); - nsIFrame *frame = nsLayoutUtils::GetFrameForPoint(kid, point); - if (frame && nsSVGUtils::HitTestClip(this, aPoint)) - return frame; - - return nullptr; + return nsLayoutUtils::GetFrameForPoint(kid, point); } nsRect diff --git a/layout/svg/nsSVGForeignObjectFrame.h b/layout/svg/nsSVGForeignObjectFrame.h index ec584213de3..22d75cd9d35 100644 --- a/layout/svg/nsSVGForeignObjectFrame.h +++ b/layout/svg/nsSVGForeignObjectFrame.h @@ -79,7 +79,7 @@ public: virtual nsresult PaintSVG(nsRenderingContext *aContext, const nsIntRect *aDirtyRect, nsIFrame* aTransformRoot = nullptr) MOZ_OVERRIDE; - virtual nsIFrame* GetFrameForPoint(const nsPoint &aPoint) MOZ_OVERRIDE; + virtual nsIFrame* GetFrameForPoint(const gfxPoint& aPoint) MOZ_OVERRIDE; virtual nsRect GetCoveredRegion() MOZ_OVERRIDE; virtual void ReflowSVG() MOZ_OVERRIDE; virtual void NotifySVGChanged(uint32_t aFlags) MOZ_OVERRIDE; diff --git a/layout/svg/nsSVGImageFrame.cpp b/layout/svg/nsSVGImageFrame.cpp index 6558898b6c4..d403cdacddf 100644 --- a/layout/svg/nsSVGImageFrame.cpp +++ b/layout/svg/nsSVGImageFrame.cpp @@ -65,7 +65,7 @@ public: virtual nsresult PaintSVG(nsRenderingContext *aContext, const nsIntRect *aDirtyRect, nsIFrame* aTransformRoot) MOZ_OVERRIDE; - virtual nsIFrame* GetFrameForPoint(const nsPoint &aPoint) MOZ_OVERRIDE; + virtual nsIFrame* GetFrameForPoint(const gfxPoint& aPoint) MOZ_OVERRIDE; virtual void ReflowSVG() MOZ_OVERRIDE; // nsSVGPathGeometryFrame methods: @@ -418,13 +418,25 @@ nsSVGImageFrame::PaintSVG(nsRenderingContext *aContext, } nsIFrame* -nsSVGImageFrame::GetFrameForPoint(const nsPoint &aPoint) +nsSVGImageFrame::GetFrameForPoint(const gfxPoint& aPoint) { + Rect rect; + SVGImageElement *element = static_cast(mContent); + element->GetAnimatedLengthValues(&rect.x, &rect.y, + &rect.width, &rect.height, nullptr); + + if (!rect.Contains(ToPoint(aPoint))) { + return nullptr; + } + // Special case for raster images -- we only want to accept points that fall - // in the underlying image's (transformed) native bounds. That region - // doesn't necessarily map to our element's [x,y,width,height]. So, - // we have to look up the native image size & our image transform in order - // to filter out points that fall outside that area. + // in the underlying image's (scaled to fit) native bounds. That region + // doesn't necessarily map to our element's [x,y,width,height] if the + // raster image's aspect ratio is being preserved. We have to look up the + // native image size & our viewBox transform in order to filter out points + // that fall outside that area. (This special case doesn't apply to vector + // images because they don't limit their drawing to explicit "native + // bounds" -- they have an infinite canvas on which to place content.) if (StyleDisplay()->IsScrollableOverflow() && mImageContainer) { if (mImageContainer->GetType() == imgIContainer::TYPE_RASTER) { int32_t nativeWidth, nativeHeight; @@ -433,23 +445,19 @@ nsSVGImageFrame::GetFrameForPoint(const nsPoint &aPoint) nativeWidth == 0 || nativeHeight == 0) { return nullptr; } - - if (!nsSVGUtils::HitTestRect( - GetRasterImageTransform(nativeWidth, nativeHeight, - FOR_HIT_TESTING), - 0, 0, nativeWidth, nativeHeight, - PresContext()->AppUnitsToFloatCSSPixels(aPoint.x), - PresContext()->AppUnitsToFloatCSSPixels(aPoint.y))) { + Matrix viewBoxTM = + SVGContentUtils::GetViewBoxTransform(rect.width, rect.height, + 0, 0, nativeWidth, nativeHeight, + element->mPreserveAspectRatio); + if (!nsSVGUtils::HitTestRect(viewBoxTM, + 0, 0, nativeWidth, nativeHeight, + aPoint.x - rect.x, aPoint.y - rect.y)) { return nullptr; } } - // The special case above doesn't apply to vector images, because they - // don't limit their drawing to explicit "native bounds" -- they have - // an infinite canvas on which to place content. So it's reasonable to - // just fall back on our element's own bounds here. } - return nsSVGImageFrameBase::GetFrameForPoint(aPoint); + return this; } nsIAtom * diff --git a/layout/svg/nsSVGInnerSVGFrame.cpp b/layout/svg/nsSVGInnerSVGFrame.cpp index 3fcdf31dad7..a385e72ea38 100644 --- a/layout/svg/nsSVGInnerSVGFrame.cpp +++ b/layout/svg/nsSVGInnerSVGFrame.cpp @@ -7,6 +7,7 @@ #include "nsSVGInnerSVGFrame.h" // Keep others in (case-insensitive) order: +#include "gfx2DGlue.h" #include "gfxContext.h" #include "nsIFrame.h" #include "nsISVGChildFrame.h" @@ -18,6 +19,7 @@ using namespace mozilla; using namespace mozilla::dom; +using namespace mozilla::gfx; nsIFrame* NS_NewSVGInnerSVGFrame(nsIPresShell* aPresShell, nsStyleContext* aContext) @@ -252,7 +254,7 @@ nsSVGInnerSVGFrame::AttributeChanged(int32_t aNameSpaceID, } nsIFrame* -nsSVGInnerSVGFrame::GetFrameForPoint(const nsPoint &aPoint) +nsSVGInnerSVGFrame::GetFrameForPoint(const gfxPoint& aPoint) { NS_ASSERTION(!NS_SVGDisplayListHitTestingEnabled() || (mState & NS_FRAME_IS_NONDISPLAY), @@ -260,16 +262,11 @@ nsSVGInnerSVGFrame::GetFrameForPoint(const nsPoint &aPoint) "SVG should take this code path"); if (StyleDisplay()->IsScrollableOverflow()) { - nsSVGElement *content = static_cast(mContent); - nsSVGContainerFrame *parent = static_cast(GetParent()); - - float clipX, clipY, clipWidth, clipHeight; - content->GetAnimatedLengthValues(&clipX, &clipY, &clipWidth, &clipHeight, nullptr); - - if (!nsSVGUtils::HitTestRect(gfx::ToMatrix(parent->GetCanvasTM(FOR_HIT_TESTING)), - clipX, clipY, clipWidth, clipHeight, - PresContext()->AppUnitsToDevPixels(aPoint.x), - PresContext()->AppUnitsToDevPixels(aPoint.y))) { + Rect clip; + static_cast(mContent)-> + GetAnimatedLengthValues(&clip.x, &clip.y, + &clip.width, &clip.height, nullptr); + if (!clip.Contains(ToPoint(aPoint))) { return nullptr; } } diff --git a/layout/svg/nsSVGInnerSVGFrame.h b/layout/svg/nsSVGInnerSVGFrame.h index 678f4e46ce8..aae6fb191d8 100644 --- a/layout/svg/nsSVGInnerSVGFrame.h +++ b/layout/svg/nsSVGInnerSVGFrame.h @@ -59,7 +59,7 @@ public: virtual nsRect GetCoveredRegion() MOZ_OVERRIDE; virtual void ReflowSVG() MOZ_OVERRIDE; virtual void NotifySVGChanged(uint32_t aFlags) MOZ_OVERRIDE; - virtual nsIFrame* GetFrameForPoint(const nsPoint &aPoint) MOZ_OVERRIDE; + virtual nsIFrame* GetFrameForPoint(const gfxPoint& aPoint) MOZ_OVERRIDE; // nsSVGContainerFrame methods: virtual gfxMatrix GetCanvasTM(uint32_t aFor, diff --git a/layout/svg/nsSVGIntegrationUtils.cpp b/layout/svg/nsSVGIntegrationUtils.cpp index ee45abc0d37..e5bd8df6bc3 100644 --- a/layout/svg/nsSVGIntegrationUtils.cpp +++ b/layout/svg/nsSVGIntegrationUtils.cpp @@ -366,7 +366,9 @@ nsSVGIntegrationUtils::HitTestFrameForEffects(nsIFrame* aFrame, const nsPoint& a aFrame->GetOffsetTo(firstFrame) + GetOffsetToBoundingBox(firstFrame); } nsPoint pt = aPt + toUserSpace; - return nsSVGUtils::HitTestClip(firstFrame, pt); + gfxPoint userSpacePt = + gfxPoint(pt.x, pt.y) / aFrame->PresContext()->AppUnitsPerCSSPixel(); + return nsSVGUtils::HitTestClip(firstFrame, userSpacePt); } class RegularFramePaintCallback : public nsSVGFilterPaintCallback diff --git a/layout/svg/nsSVGOuterSVGFrame.cpp b/layout/svg/nsSVGOuterSVGFrame.cpp index 3aeb428d832..accb33844a7 100644 --- a/layout/svg/nsSVGOuterSVGFrame.cpp +++ b/layout/svg/nsSVGOuterSVGFrame.cpp @@ -521,20 +521,24 @@ nsDisplayOuterSVG::HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, HitTestState* aState, nsTArray *aOutFrames) { nsSVGOuterSVGFrame *outerSVGFrame = static_cast(mFrame); - nsRect rectAtOrigin = aRect - ToReferenceFrame(); - nsRect thisRect(nsPoint(0,0), outerSVGFrame->GetSize()); - if (!thisRect.Intersects(rectAtOrigin)) - return; - nsPoint rectCenter(rectAtOrigin.x + rectAtOrigin.width / 2, - rectAtOrigin.y + rectAtOrigin.height / 2); + nsPoint refFrameToContentBox = + ToReferenceFrame() + outerSVGFrame->GetContentRectRelativeToSelf().TopLeft(); + + nsPoint pointRelativeToContentBox = + nsPoint(aRect.x + aRect.width / 2, aRect.y + aRect.height / 2) - + refFrameToContentBox; + + gfxPoint svgViewportRelativePoint = + gfxPoint(pointRelativeToContentBox.x, pointRelativeToContentBox.y) / + outerSVGFrame->PresContext()->AppUnitsPerCSSPixel(); nsSVGOuterSVGAnonChildFrame *anonKid = static_cast( outerSVGFrame->GetFirstPrincipalChild()); - nsIFrame* frame = nsSVGUtils::HitTestChildren( - anonKid, rectCenter + outerSVGFrame->GetPosition() - - outerSVGFrame->GetContentRect().TopLeft()); + + nsIFrame* frame = + nsSVGUtils::HitTestChildren(anonKid, svgViewportRelativePoint); if (frame) { aOutFrames->AppendElement(frame); } diff --git a/layout/svg/nsSVGPathGeometryFrame.cpp b/layout/svg/nsSVGPathGeometryFrame.cpp index 2995d2ebc82..3955a7ea949 100644 --- a/layout/svg/nsSVGPathGeometryFrame.cpp +++ b/layout/svg/nsSVGPathGeometryFrame.cpp @@ -15,6 +15,7 @@ #include "mozilla/RefPtr.h" #include "nsDisplayList.h" #include "nsGkAtoms.h" +#include "nsLayoutUtils.h" #include "nsRenderingContext.h" #include "nsSVGEffects.h" #include "nsSVGIntegrationUtils.h" @@ -82,8 +83,11 @@ nsDisplaySVGPathGeometry::HitTest(nsDisplayListBuilder* aBuilder, const nsRect& nsSVGPathGeometryFrame *frame = static_cast(mFrame); nsPoint pointRelativeToReferenceFrame = aRect.Center(); // ToReferenceFrame() includes frame->GetPosition(), our user space position. - nsPoint userSpacePt = pointRelativeToReferenceFrame - - (ToReferenceFrame() - frame->GetPosition()); + nsPoint userSpacePtInAppUnits = pointRelativeToReferenceFrame - + (ToReferenceFrame() - frame->GetPosition()); + gfxPoint userSpacePt = + gfxPoint(userSpacePtInAppUnits.x, userSpacePtInAppUnits.y) / + frame->PresContext()->AppUnitsPerCSSPixel(); if (frame->GetFrameForPoint(userSpacePt)) { aOutFrames->AppendElement(frame); } @@ -236,12 +240,8 @@ nsSVGPathGeometryFrame::PaintSVG(nsRenderingContext *aContext, } nsIFrame* -nsSVGPathGeometryFrame::GetFrameForPoint(const nsPoint &aPoint) +nsSVGPathGeometryFrame::GetFrameForPoint(const gfxPoint& aPoint) { - gfxMatrix hitTestingTM = GetCanvasTM(FOR_HIT_TESTING); - if (hitTestingTM.IsSingular()) { - return nullptr; - } FillRule fillRule; uint16_t hitTestFlags; if (GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD) { @@ -250,12 +250,16 @@ nsSVGPathGeometryFrame::GetFrameForPoint(const nsPoint &aPoint) ? FillRule::FILL_WINDING : FillRule::FILL_EVEN_ODD; } else { hitTestFlags = GetHitTestFlags(); - nsPoint point = - nsSVGUtils::TransformOuterSVGPointToChildFrame(aPoint, hitTestingTM, PresContext()); - if (!hitTestFlags || ((hitTestFlags & SVG_HIT_TEST_CHECK_MRECT) && - !mRect.Contains(point))) { + if (!hitTestFlags) { return nullptr; } + if (hitTestFlags & SVG_HIT_TEST_CHECK_MRECT) { + gfxRect rect = + nsLayoutUtils::RectToGfxRect(mRect, PresContext()->AppUnitsPerCSSPixel()); + if (!rect.Contains(aPoint)) { + return nullptr; + } + } fillRule = StyleSVG()->mFillRule == NS_STYLE_FILL_RULE_NONZERO ? FillRule::FILL_WINDING : FillRule::FILL_EVEN_ODD; } @@ -277,26 +281,14 @@ nsSVGPathGeometryFrame::GetFrameForPoint(const nsPoint &aPoint) return nullptr; // no path, so we don't paint anything that can be hit } - if (!hitTestingTM.IsIdentity()) { - // We'll only get here if we don't have a nsDisplayItem that has called us - // (for example, if we're a NS_FRAME_IS_NONDISPLAY frame under a clipPath). - RefPtr builder = - path->TransformedCopyToBuilder(ToMatrix(hitTestingTM), fillRule); - path = builder->Finish(); - } - - int32_t appUnitsPerCSSPx = PresContext()->AppUnitsPerCSSPixel(); - Point userSpacePoint = Point(Float(aPoint.x) / appUnitsPerCSSPx, - Float(aPoint.y) / appUnitsPerCSSPx); - if (hitTestFlags & SVG_HIT_TEST_FILL) { - isHit = path->ContainsPoint(userSpacePoint, Matrix()); + isHit = path->ContainsPoint(ToPoint(aPoint), Matrix()); } if (!isHit && (hitTestFlags & SVG_HIT_TEST_STROKE)) { + Point point = ToPoint(aPoint); SVGContentUtils::AutoStrokeOptions stroke; SVGContentUtils::GetStrokeOptions(&stroke, content, StyleContext(), nullptr); - Matrix nonScalingStrokeMatrix = - ToMatrix(nsSVGUtils::GetStrokeTransform(this)); + Matrix nonScalingStrokeMatrix = ToMatrix(nsSVGUtils::GetStrokeTransform(this)); if (!nonScalingStrokeMatrix.IsIdentity()) { // We need to transform the path back into the appropriate ancestor // coordinate system in order for non-scaled stroke to be correct. @@ -305,12 +297,12 @@ nsSVGPathGeometryFrame::GetFrameForPoint(const nsPoint &aPoint) if (!nonScalingStrokeMatrix.Invert()) { return nullptr; } - userSpacePoint = ToMatrix(hitTestingTM) * nonScalingStrokeMatrix * userSpacePoint; + point = nonScalingStrokeMatrix * point; RefPtr builder = path->TransformedCopyToBuilder(nonScalingStrokeMatrix, fillRule); path = builder->Finish(); } - isHit = path->StrokeContainsPoint(stroke, userSpacePoint, Matrix()); + isHit = path->StrokeContainsPoint(stroke, point, Matrix()); } if (isHit && nsSVGUtils::HitTestClip(this, aPoint)) diff --git a/layout/svg/nsSVGPathGeometryFrame.h b/layout/svg/nsSVGPathGeometryFrame.h index 14f64e472c8..75774ef743b 100644 --- a/layout/svg/nsSVGPathGeometryFrame.h +++ b/layout/svg/nsSVGPathGeometryFrame.h @@ -96,7 +96,7 @@ protected: virtual nsresult PaintSVG(nsRenderingContext *aContext, const nsIntRect *aDirtyRect, nsIFrame* aTransformRoot = nullptr) MOZ_OVERRIDE; - virtual nsIFrame* GetFrameForPoint(const nsPoint &aPoint) MOZ_OVERRIDE; + virtual nsIFrame* GetFrameForPoint(const gfxPoint& aPoint) MOZ_OVERRIDE; virtual nsRect GetCoveredRegion() MOZ_OVERRIDE; virtual void ReflowSVG() MOZ_OVERRIDE; virtual void NotifySVGChanged(uint32_t aFlags) MOZ_OVERRIDE; diff --git a/layout/svg/nsSVGSwitchFrame.cpp b/layout/svg/nsSVGSwitchFrame.cpp index 88ebd3e5fca..8aba5942e2a 100644 --- a/layout/svg/nsSVGSwitchFrame.cpp +++ b/layout/svg/nsSVGSwitchFrame.cpp @@ -55,7 +55,7 @@ public: virtual nsresult PaintSVG(nsRenderingContext* aContext, const nsIntRect *aDirtyRect, nsIFrame* aTransformRoot) MOZ_OVERRIDE; - nsIFrame* GetFrameForPoint(const nsPoint &aPoint) MOZ_OVERRIDE; + nsIFrame* GetFrameForPoint(const gfxPoint& aPoint) MOZ_OVERRIDE; nsRect GetCoveredRegion() MOZ_OVERRIDE; virtual void ReflowSVG() MOZ_OVERRIDE; virtual SVGBBox GetBBoxContribution(const Matrix &aToBBoxUserspace, @@ -128,7 +128,7 @@ nsSVGSwitchFrame::PaintSVG(nsRenderingContext* aContext, nsIFrame* -nsSVGSwitchFrame::GetFrameForPoint(const nsPoint &aPoint) +nsSVGSwitchFrame::GetFrameForPoint(const gfxPoint& aPoint) { NS_ASSERTION(!NS_SVGDisplayListHitTestingEnabled() || (mState & NS_FRAME_IS_NONDISPLAY), @@ -138,7 +138,20 @@ nsSVGSwitchFrame::GetFrameForPoint(const nsPoint &aPoint) nsIFrame *kid = GetActiveChildFrame(); nsISVGChildFrame* svgFrame = do_QueryFrame(kid); if (svgFrame) { - return svgFrame->GetFrameForPoint(aPoint); + // Transform the point from our SVG user space to our child's. + gfxPoint point = aPoint; + gfxMatrix m = + static_cast(mContent)-> + PrependLocalTransformsTo(gfxMatrix(), nsSVGElement::eChildToUserSpace); + m = static_cast(kid->GetContent())-> + PrependLocalTransformsTo(m, nsSVGElement::eUserSpaceToParent); + if (!m.IsIdentity()) { + if (!m.Invert()) { + return nullptr; + } + point = m.Transform(point); + } + return svgFrame->GetFrameForPoint(point); } return nullptr; diff --git a/layout/svg/nsSVGUtils.cpp b/layout/svg/nsSVGUtils.cpp index a65ff4afc7c..854b4be7b5a 100644 --- a/layout/svg/nsSVGUtils.cpp +++ b/layout/svg/nsSVGUtils.cpp @@ -694,7 +694,7 @@ nsSVGUtils::PaintFrameWithEffects(nsRenderingContext *aContext, } bool -nsSVGUtils::HitTestClip(nsIFrame *aFrame, const nsPoint &aPoint) +nsSVGUtils::HitTestClip(nsIFrame *aFrame, const gfxPoint &aPoint) { nsSVGEffects::EffectProperties props = nsSVGEffects::GetEffectProperties(aFrame); @@ -713,13 +713,28 @@ nsSVGUtils::HitTestClip(nsIFrame *aFrame, const nsPoint &aPoint) return true; } - return clipPathFrame->ClipHitTest(aFrame, GetCanvasTM(aFrame, - nsISVGChildFrame::FOR_HIT_TESTING), aPoint); + return clipPathFrame->PointIsInsideClipPath(aFrame, aPoint); } nsIFrame * -nsSVGUtils::HitTestChildren(nsIFrame *aFrame, const nsPoint &aPoint) +nsSVGUtils::HitTestChildren(nsSVGDisplayContainerFrame* aFrame, + const gfxPoint& aPoint) { + // First we transform aPoint into the coordinate space established by aFrame + // for its children (e.g. take account of any 'viewBox' attribute): + gfxPoint point = aPoint; + if (aFrame->GetContent()->IsSVG()) { // must check before cast + gfxMatrix m = static_cast(aFrame->GetContent())-> + PrependLocalTransformsTo(gfxMatrix(), + nsSVGElement::eChildToUserSpace); + if (!m.IsIdentity()) { + if (!m.Invert()) { + return nullptr; + } + point = m.Transform(point); + } + } + // Traverse the list in reverse order, so that if we get a hit we know that's // the topmost frame that intersects the point; then we can just return it. nsIFrame* result = nullptr; @@ -733,7 +748,21 @@ nsSVGUtils::HitTestChildren(nsIFrame *aFrame, const nsPoint &aPoint) !static_cast(content)->HasValidDimensions()) { continue; } - result = SVGFrame->GetFrameForPoint(aPoint); + // GetFrameForPoint() expects a point in its frame's SVG user space, so + // we need to convert to that space: + gfxPoint p = point; + if (content->IsSVG()) { // must check before cast + gfxMatrix m = static_cast(content)-> + PrependLocalTransformsTo(gfxMatrix(), + nsSVGElement::eUserSpaceToParent); + if (!m.IsIdentity()) { + if (!m.Invert()) { + continue; + } + p = m.Transform(p); + } + } + result = SVGFrame->GetFrameForPoint(p); if (result) break; } diff --git a/layout/svg/nsSVGUtils.h b/layout/svg/nsSVGUtils.h index 4671ac78682..11e0dc3543b 100644 --- a/layout/svg/nsSVGUtils.h +++ b/layout/svg/nsSVGUtils.h @@ -304,12 +304,15 @@ public: /* Hit testing - check if point hits the clipPath of indicated * frame. Returns true if no clipPath set. */ static bool - HitTestClip(nsIFrame *aFrame, const nsPoint &aPoint); + HitTestClip(nsIFrame *aFrame, const gfxPoint &aPoint); - /* Hit testing - check if point hits any children of frame. */ - + /** + * Hit testing - check if point hits any children of aFrame. aPoint is + * expected to be in the coordinate space established by aFrame for its + * children (e.g. the space established by the 'viewBox' attribute on ). + */ static nsIFrame * - HitTestChildren(nsIFrame *aFrame, const nsPoint &aPoint); + HitTestChildren(nsSVGDisplayContainerFrame *aFrame, const gfxPoint &aPoint); /* * Returns the CanvasTM of the indicated frame, whether it's a