Bug 1049256, part 1 - Convert SVG hit-testing to act on an SVG user space point instead of calling nsSVGUtils::GetCanvasTM(). r=longsonr

This commit is contained in:
Jonathan Watt 2014-08-07 08:09:31 +01:00
parent 37d322f441
commit 5b5a18e51d
19 changed files with 206 additions and 135 deletions

View File

@ -3113,8 +3113,12 @@ nsDisplaySVGText::HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
SVGTextFrame *frame = static_cast<SVGTextFrame*>(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 <text> 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();

View File

@ -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,

View File

@ -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;

View File

@ -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<nsSVGElement*>(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<SVGClipPathElement*>(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)

View File

@ -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

View File

@ -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),

View File

@ -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;

View File

@ -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<nsSVGElement*>(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

View File

@ -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;

View File

@ -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<SVGImageElement*>(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 <image> 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 <image> 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 <image> element's own bounds here.
}
return nsSVGImageFrameBase::GetFrameForPoint(aPoint);
return this;
}
nsIAtom *

View File

@ -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<nsSVGElement*>(mContent);
nsSVGContainerFrame *parent = static_cast<nsSVGContainerFrame*>(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<nsSVGElement*>(mContent)->
GetAnimatedLengthValues(&clip.x, &clip.y,
&clip.width, &clip.height, nullptr);
if (!clip.Contains(ToPoint(aPoint))) {
return nullptr;
}
}

View File

@ -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,

View File

@ -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

View File

@ -521,20 +521,24 @@ nsDisplayOuterSVG::HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames)
{
nsSVGOuterSVGFrame *outerSVGFrame = static_cast<nsSVGOuterSVGFrame*>(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<nsSVGOuterSVGAnonChildFrame*>(
outerSVGFrame->GetFirstPrincipalChild());
nsIFrame* frame = nsSVGUtils::HitTestChildren(
anonKid, rectCenter + outerSVGFrame->GetPosition() -
outerSVGFrame->GetContentRect().TopLeft());
nsIFrame* frame =
nsSVGUtils::HitTestChildren(anonKid, svgViewportRelativePoint);
if (frame) {
aOutFrames->AppendElement(frame);
}

View File

@ -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<nsSVGPathGeometryFrame*>(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<PathBuilder> 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<PathBuilder> 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))

View File

@ -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;

View File

@ -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<const nsSVGElement*>(mContent)->
PrependLocalTransformsTo(gfxMatrix(), nsSVGElement::eChildToUserSpace);
m = static_cast<const nsSVGElement*>(kid->GetContent())->
PrependLocalTransformsTo(m, nsSVGElement::eUserSpaceToParent);
if (!m.IsIdentity()) {
if (!m.Invert()) {
return nullptr;
}
point = m.Transform(point);
}
return svgFrame->GetFrameForPoint(point);
}
return nullptr;

View File

@ -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<const nsSVGElement*>(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<const nsSVGElement*>(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<const nsSVGElement*>(content)->
PrependLocalTransformsTo(gfxMatrix(),
nsSVGElement::eUserSpaceToParent);
if (!m.IsIdentity()) {
if (!m.Invert()) {
continue;
}
p = m.Transform(p);
}
}
result = SVGFrame->GetFrameForPoint(p);
if (result)
break;
}

View File

@ -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 <svg>).
*/
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