Tue, 06 Jan 2015 21:39:09 +0100
Conditionally force memory storage according to privacy.thirdparty.isolate;
This solves Tor bug #9701, complying with disk avoidance documented in
https://www.torproject.org/projects/torbrowser/design/#disk-avoidance.
michael@0 | 1 | /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ |
michael@0 | 2 | /* This Source Code Form is subject to the terms of the Mozilla Public |
michael@0 | 3 | * License, v. 2.0. If a copy of the MPL was not distributed with this |
michael@0 | 4 | * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
michael@0 | 5 | |
michael@0 | 6 | // Main header first: |
michael@0 | 7 | #include "nsSVGPathGeometryFrame.h" |
michael@0 | 8 | |
michael@0 | 9 | // Keep others in (case-insensitive) order: |
michael@0 | 10 | #include "gfxContext.h" |
michael@0 | 11 | #include "gfxPlatform.h" |
michael@0 | 12 | #include "gfxSVGGlyphs.h" |
michael@0 | 13 | #include "nsDisplayList.h" |
michael@0 | 14 | #include "nsGkAtoms.h" |
michael@0 | 15 | #include "nsRenderingContext.h" |
michael@0 | 16 | #include "nsSVGEffects.h" |
michael@0 | 17 | #include "nsSVGIntegrationUtils.h" |
michael@0 | 18 | #include "nsSVGMarkerFrame.h" |
michael@0 | 19 | #include "nsSVGPathGeometryElement.h" |
michael@0 | 20 | #include "nsSVGUtils.h" |
michael@0 | 21 | #include "mozilla/ArrayUtils.h" |
michael@0 | 22 | #include "SVGAnimatedTransformList.h" |
michael@0 | 23 | #include "SVGGraphicsElement.h" |
michael@0 | 24 | |
michael@0 | 25 | using namespace mozilla; |
michael@0 | 26 | using namespace mozilla::gfx; |
michael@0 | 27 | |
michael@0 | 28 | //---------------------------------------------------------------------- |
michael@0 | 29 | // Implementation |
michael@0 | 30 | |
michael@0 | 31 | nsIFrame* |
michael@0 | 32 | NS_NewSVGPathGeometryFrame(nsIPresShell* aPresShell, |
michael@0 | 33 | nsStyleContext* aContext) |
michael@0 | 34 | { |
michael@0 | 35 | return new (aPresShell) nsSVGPathGeometryFrame(aContext); |
michael@0 | 36 | } |
michael@0 | 37 | |
michael@0 | 38 | NS_IMPL_FRAMEARENA_HELPERS(nsSVGPathGeometryFrame) |
michael@0 | 39 | |
michael@0 | 40 | //---------------------------------------------------------------------- |
michael@0 | 41 | // nsQueryFrame methods |
michael@0 | 42 | |
michael@0 | 43 | NS_QUERYFRAME_HEAD(nsSVGPathGeometryFrame) |
michael@0 | 44 | NS_QUERYFRAME_ENTRY(nsISVGChildFrame) |
michael@0 | 45 | NS_QUERYFRAME_ENTRY(nsSVGPathGeometryFrame) |
michael@0 | 46 | NS_QUERYFRAME_TAIL_INHERITING(nsSVGPathGeometryFrameBase) |
michael@0 | 47 | |
michael@0 | 48 | //---------------------------------------------------------------------- |
michael@0 | 49 | // Display list item: |
michael@0 | 50 | |
michael@0 | 51 | class nsDisplaySVGPathGeometry : public nsDisplayItem { |
michael@0 | 52 | public: |
michael@0 | 53 | nsDisplaySVGPathGeometry(nsDisplayListBuilder* aBuilder, |
michael@0 | 54 | nsSVGPathGeometryFrame* aFrame) |
michael@0 | 55 | : nsDisplayItem(aBuilder, aFrame) |
michael@0 | 56 | { |
michael@0 | 57 | MOZ_COUNT_CTOR(nsDisplaySVGPathGeometry); |
michael@0 | 58 | NS_ABORT_IF_FALSE(aFrame, "Must have a frame!"); |
michael@0 | 59 | } |
michael@0 | 60 | #ifdef NS_BUILD_REFCNT_LOGGING |
michael@0 | 61 | virtual ~nsDisplaySVGPathGeometry() { |
michael@0 | 62 | MOZ_COUNT_DTOR(nsDisplaySVGPathGeometry); |
michael@0 | 63 | } |
michael@0 | 64 | #endif |
michael@0 | 65 | |
michael@0 | 66 | NS_DISPLAY_DECL_NAME("nsDisplaySVGPathGeometry", TYPE_SVG_PATH_GEOMETRY) |
michael@0 | 67 | |
michael@0 | 68 | virtual void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, |
michael@0 | 69 | HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames); |
michael@0 | 70 | virtual void Paint(nsDisplayListBuilder* aBuilder, |
michael@0 | 71 | nsRenderingContext* aCtx); |
michael@0 | 72 | }; |
michael@0 | 73 | |
michael@0 | 74 | void |
michael@0 | 75 | nsDisplaySVGPathGeometry::HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect, |
michael@0 | 76 | HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames) |
michael@0 | 77 | { |
michael@0 | 78 | nsSVGPathGeometryFrame *frame = static_cast<nsSVGPathGeometryFrame*>(mFrame); |
michael@0 | 79 | nsPoint pointRelativeToReferenceFrame = aRect.Center(); |
michael@0 | 80 | // ToReferenceFrame() includes frame->GetPosition(), our user space position. |
michael@0 | 81 | nsPoint userSpacePt = pointRelativeToReferenceFrame - |
michael@0 | 82 | (ToReferenceFrame() - frame->GetPosition()); |
michael@0 | 83 | if (frame->GetFrameForPoint(userSpacePt)) { |
michael@0 | 84 | aOutFrames->AppendElement(frame); |
michael@0 | 85 | } |
michael@0 | 86 | } |
michael@0 | 87 | |
michael@0 | 88 | void |
michael@0 | 89 | nsDisplaySVGPathGeometry::Paint(nsDisplayListBuilder* aBuilder, |
michael@0 | 90 | nsRenderingContext* aCtx) |
michael@0 | 91 | { |
michael@0 | 92 | // ToReferenceFrame includes our mRect offset, but painting takes |
michael@0 | 93 | // account of that too. To avoid double counting, we subtract that |
michael@0 | 94 | // here. |
michael@0 | 95 | nsPoint offset = ToReferenceFrame() - mFrame->GetPosition(); |
michael@0 | 96 | |
michael@0 | 97 | aCtx->PushState(); |
michael@0 | 98 | aCtx->Translate(offset); |
michael@0 | 99 | static_cast<nsSVGPathGeometryFrame*>(mFrame)->PaintSVG(aCtx, nullptr); |
michael@0 | 100 | aCtx->PopState(); |
michael@0 | 101 | } |
michael@0 | 102 | |
michael@0 | 103 | //---------------------------------------------------------------------- |
michael@0 | 104 | // nsIFrame methods |
michael@0 | 105 | |
michael@0 | 106 | void |
michael@0 | 107 | nsSVGPathGeometryFrame::Init(nsIContent* aContent, |
michael@0 | 108 | nsIFrame* aParent, |
michael@0 | 109 | nsIFrame* aPrevInFlow) |
michael@0 | 110 | { |
michael@0 | 111 | AddStateBits(aParent->GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD); |
michael@0 | 112 | nsSVGPathGeometryFrameBase::Init(aContent, aParent, aPrevInFlow); |
michael@0 | 113 | } |
michael@0 | 114 | |
michael@0 | 115 | nsresult |
michael@0 | 116 | nsSVGPathGeometryFrame::AttributeChanged(int32_t aNameSpaceID, |
michael@0 | 117 | nsIAtom* aAttribute, |
michael@0 | 118 | int32_t aModType) |
michael@0 | 119 | { |
michael@0 | 120 | // We don't invalidate for transform changes (the layers code does that). |
michael@0 | 121 | // Also note that SVGTransformableElement::GetAttributeChangeHint will |
michael@0 | 122 | // return nsChangeHint_UpdateOverflow for "transform" attribute changes |
michael@0 | 123 | // and cause DoApplyRenderingChangeToTree to make the SchedulePaint call. |
michael@0 | 124 | |
michael@0 | 125 | if (aNameSpaceID == kNameSpaceID_None && |
michael@0 | 126 | (static_cast<nsSVGPathGeometryElement*> |
michael@0 | 127 | (mContent)->AttributeDefinesGeometry(aAttribute))) { |
michael@0 | 128 | nsSVGEffects::InvalidateRenderingObservers(this); |
michael@0 | 129 | nsSVGUtils::ScheduleReflowSVG(this); |
michael@0 | 130 | } |
michael@0 | 131 | return NS_OK; |
michael@0 | 132 | } |
michael@0 | 133 | |
michael@0 | 134 | /* virtual */ void |
michael@0 | 135 | nsSVGPathGeometryFrame::DidSetStyleContext(nsStyleContext* aOldStyleContext) |
michael@0 | 136 | { |
michael@0 | 137 | nsSVGPathGeometryFrameBase::DidSetStyleContext(aOldStyleContext); |
michael@0 | 138 | |
michael@0 | 139 | if (aOldStyleContext) { |
michael@0 | 140 | float oldOpacity = aOldStyleContext->PeekStyleDisplay()->mOpacity; |
michael@0 | 141 | float newOpacity = StyleDisplay()->mOpacity; |
michael@0 | 142 | if (newOpacity != oldOpacity && |
michael@0 | 143 | nsSVGUtils::CanOptimizeOpacity(this)) { |
michael@0 | 144 | // nsIFrame::BuildDisplayListForStackingContext() is not going to create an |
michael@0 | 145 | // nsDisplayOpacity display list item, so DLBI won't invalidate for us. |
michael@0 | 146 | InvalidateFrame(); |
michael@0 | 147 | } |
michael@0 | 148 | } |
michael@0 | 149 | } |
michael@0 | 150 | |
michael@0 | 151 | nsIAtom * |
michael@0 | 152 | nsSVGPathGeometryFrame::GetType() const |
michael@0 | 153 | { |
michael@0 | 154 | return nsGkAtoms::svgPathGeometryFrame; |
michael@0 | 155 | } |
michael@0 | 156 | |
michael@0 | 157 | bool |
michael@0 | 158 | nsSVGPathGeometryFrame::IsSVGTransformed(gfx::Matrix *aOwnTransform, |
michael@0 | 159 | gfx::Matrix *aFromParentTransform) const |
michael@0 | 160 | { |
michael@0 | 161 | bool foundTransform = false; |
michael@0 | 162 | |
michael@0 | 163 | // Check if our parent has children-only transforms: |
michael@0 | 164 | nsIFrame *parent = GetParent(); |
michael@0 | 165 | if (parent && |
michael@0 | 166 | parent->IsFrameOfType(nsIFrame::eSVG | nsIFrame::eSVGContainer)) { |
michael@0 | 167 | foundTransform = static_cast<nsSVGContainerFrame*>(parent)-> |
michael@0 | 168 | HasChildrenOnlyTransform(aFromParentTransform); |
michael@0 | 169 | } |
michael@0 | 170 | |
michael@0 | 171 | nsSVGElement *content = static_cast<nsSVGElement*>(mContent); |
michael@0 | 172 | nsSVGAnimatedTransformList* transformList = |
michael@0 | 173 | content->GetAnimatedTransformList(); |
michael@0 | 174 | if ((transformList && transformList->HasTransform()) || |
michael@0 | 175 | content->GetAnimateMotionTransform()) { |
michael@0 | 176 | if (aOwnTransform) { |
michael@0 | 177 | *aOwnTransform = gfx::ToMatrix(content->PrependLocalTransformsTo(gfxMatrix(), |
michael@0 | 178 | nsSVGElement::eUserSpaceToParent)); |
michael@0 | 179 | } |
michael@0 | 180 | foundTransform = true; |
michael@0 | 181 | } |
michael@0 | 182 | return foundTransform; |
michael@0 | 183 | } |
michael@0 | 184 | |
michael@0 | 185 | void |
michael@0 | 186 | nsSVGPathGeometryFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, |
michael@0 | 187 | const nsRect& aDirtyRect, |
michael@0 | 188 | const nsDisplayListSet& aLists) |
michael@0 | 189 | { |
michael@0 | 190 | if (!static_cast<const nsSVGElement*>(mContent)->HasValidDimensions()) { |
michael@0 | 191 | return; |
michael@0 | 192 | } |
michael@0 | 193 | aLists.Content()->AppendNewToTop( |
michael@0 | 194 | new (aBuilder) nsDisplaySVGPathGeometry(aBuilder, this)); |
michael@0 | 195 | } |
michael@0 | 196 | |
michael@0 | 197 | //---------------------------------------------------------------------- |
michael@0 | 198 | // nsISVGChildFrame methods |
michael@0 | 199 | |
michael@0 | 200 | nsresult |
michael@0 | 201 | nsSVGPathGeometryFrame::PaintSVG(nsRenderingContext *aContext, |
michael@0 | 202 | const nsIntRect *aDirtyRect, |
michael@0 | 203 | nsIFrame* aTransformRoot) |
michael@0 | 204 | { |
michael@0 | 205 | if (!StyleVisibility()->IsVisible()) |
michael@0 | 206 | return NS_OK; |
michael@0 | 207 | |
michael@0 | 208 | uint32_t paintOrder = StyleSVG()->mPaintOrder; |
michael@0 | 209 | if (paintOrder == NS_STYLE_PAINT_ORDER_NORMAL) { |
michael@0 | 210 | Render(aContext, eRenderFill | eRenderStroke, aTransformRoot); |
michael@0 | 211 | PaintMarkers(aContext); |
michael@0 | 212 | } else { |
michael@0 | 213 | while (paintOrder) { |
michael@0 | 214 | uint32_t component = |
michael@0 | 215 | paintOrder & ((1 << NS_STYLE_PAINT_ORDER_BITWIDTH) - 1); |
michael@0 | 216 | switch (component) { |
michael@0 | 217 | case NS_STYLE_PAINT_ORDER_FILL: |
michael@0 | 218 | Render(aContext, eRenderFill, aTransformRoot); |
michael@0 | 219 | break; |
michael@0 | 220 | case NS_STYLE_PAINT_ORDER_STROKE: |
michael@0 | 221 | Render(aContext, eRenderStroke, aTransformRoot); |
michael@0 | 222 | break; |
michael@0 | 223 | case NS_STYLE_PAINT_ORDER_MARKERS: |
michael@0 | 224 | PaintMarkers(aContext); |
michael@0 | 225 | break; |
michael@0 | 226 | } |
michael@0 | 227 | paintOrder >>= NS_STYLE_PAINT_ORDER_BITWIDTH; |
michael@0 | 228 | } |
michael@0 | 229 | } |
michael@0 | 230 | |
michael@0 | 231 | return NS_OK; |
michael@0 | 232 | } |
michael@0 | 233 | |
michael@0 | 234 | nsIFrame* |
michael@0 | 235 | nsSVGPathGeometryFrame::GetFrameForPoint(const nsPoint &aPoint) |
michael@0 | 236 | { |
michael@0 | 237 | gfxMatrix canvasTM = GetCanvasTM(FOR_HIT_TESTING); |
michael@0 | 238 | if (canvasTM.IsSingular()) { |
michael@0 | 239 | return nullptr; |
michael@0 | 240 | } |
michael@0 | 241 | uint16_t fillRule, hitTestFlags; |
michael@0 | 242 | if (GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD) { |
michael@0 | 243 | hitTestFlags = SVG_HIT_TEST_FILL; |
michael@0 | 244 | fillRule = StyleSVG()->mClipRule; |
michael@0 | 245 | } else { |
michael@0 | 246 | hitTestFlags = GetHitTestFlags(); |
michael@0 | 247 | // XXX once bug 614732 is fixed, aPoint won't need any conversion in order |
michael@0 | 248 | // to compare it with mRect. |
michael@0 | 249 | nsPoint point = |
michael@0 | 250 | nsSVGUtils::TransformOuterSVGPointToChildFrame(aPoint, canvasTM, PresContext()); |
michael@0 | 251 | if (!hitTestFlags || ((hitTestFlags & SVG_HIT_TEST_CHECK_MRECT) && |
michael@0 | 252 | !mRect.Contains(point))) |
michael@0 | 253 | return nullptr; |
michael@0 | 254 | fillRule = StyleSVG()->mFillRule; |
michael@0 | 255 | } |
michael@0 | 256 | |
michael@0 | 257 | bool isHit = false; |
michael@0 | 258 | |
michael@0 | 259 | nsRefPtr<gfxContext> tmpCtx = |
michael@0 | 260 | new gfxContext(gfxPlatform::GetPlatform()->ScreenReferenceSurface()); |
michael@0 | 261 | |
michael@0 | 262 | GeneratePath(tmpCtx, ToMatrix(canvasTM)); |
michael@0 | 263 | gfxPoint userSpacePoint = |
michael@0 | 264 | tmpCtx->DeviceToUser(gfxPoint(PresContext()->AppUnitsToGfxUnits(aPoint.x), |
michael@0 | 265 | PresContext()->AppUnitsToGfxUnits(aPoint.y))); |
michael@0 | 266 | |
michael@0 | 267 | if (fillRule == NS_STYLE_FILL_RULE_EVENODD) |
michael@0 | 268 | tmpCtx->SetFillRule(gfxContext::FILL_RULE_EVEN_ODD); |
michael@0 | 269 | else |
michael@0 | 270 | tmpCtx->SetFillRule(gfxContext::FILL_RULE_WINDING); |
michael@0 | 271 | |
michael@0 | 272 | if (hitTestFlags & SVG_HIT_TEST_FILL) |
michael@0 | 273 | isHit = tmpCtx->PointInFill(userSpacePoint); |
michael@0 | 274 | if (!isHit && (hitTestFlags & SVG_HIT_TEST_STROKE)) { |
michael@0 | 275 | nsSVGUtils::SetupCairoStrokeGeometry(this, tmpCtx); |
michael@0 | 276 | // tmpCtx's matrix may have transformed by SetupCairoStrokeGeometry |
michael@0 | 277 | // if there is a non-scaling stroke. We need to transform userSpacePoint |
michael@0 | 278 | // so that everything is using the same co-ordinate system. |
michael@0 | 279 | userSpacePoint = |
michael@0 | 280 | nsSVGUtils::GetStrokeTransform(this).Invert().Transform(userSpacePoint); |
michael@0 | 281 | isHit = tmpCtx->PointInStroke(userSpacePoint); |
michael@0 | 282 | } |
michael@0 | 283 | |
michael@0 | 284 | if (isHit && nsSVGUtils::HitTestClip(this, aPoint)) |
michael@0 | 285 | return this; |
michael@0 | 286 | |
michael@0 | 287 | return nullptr; |
michael@0 | 288 | } |
michael@0 | 289 | |
michael@0 | 290 | nsRect |
michael@0 | 291 | nsSVGPathGeometryFrame::GetCoveredRegion() |
michael@0 | 292 | { |
michael@0 | 293 | return nsSVGUtils::TransformFrameRectToOuterSVG( |
michael@0 | 294 | mRect, GetCanvasTM(FOR_OUTERSVG_TM), PresContext()); |
michael@0 | 295 | } |
michael@0 | 296 | |
michael@0 | 297 | void |
michael@0 | 298 | nsSVGPathGeometryFrame::ReflowSVG() |
michael@0 | 299 | { |
michael@0 | 300 | NS_ASSERTION(nsSVGUtils::OuterSVGIsCallingReflowSVG(this), |
michael@0 | 301 | "This call is probably a wasteful mistake"); |
michael@0 | 302 | |
michael@0 | 303 | NS_ABORT_IF_FALSE(!(GetStateBits() & NS_FRAME_IS_NONDISPLAY), |
michael@0 | 304 | "ReflowSVG mechanism not designed for this"); |
michael@0 | 305 | |
michael@0 | 306 | if (!nsSVGUtils::NeedsReflowSVG(this)) { |
michael@0 | 307 | return; |
michael@0 | 308 | } |
michael@0 | 309 | |
michael@0 | 310 | uint32_t flags = nsSVGUtils::eBBoxIncludeFill | |
michael@0 | 311 | nsSVGUtils::eBBoxIncludeStroke | |
michael@0 | 312 | nsSVGUtils::eBBoxIncludeMarkers; |
michael@0 | 313 | // Our "visual" overflow rect needs to be valid for building display lists |
michael@0 | 314 | // for hit testing, which means that for certain values of 'pointer-events' |
michael@0 | 315 | // it needs to include the geometry of the fill or stroke even when the fill/ |
michael@0 | 316 | // stroke don't actually render (e.g. when stroke="none" or |
michael@0 | 317 | // stroke-opacity="0"). GetHitTestFlags() accounts for 'pointer-events'. |
michael@0 | 318 | uint16_t hitTestFlags = GetHitTestFlags(); |
michael@0 | 319 | if ((hitTestFlags & SVG_HIT_TEST_FILL)) { |
michael@0 | 320 | flags |= nsSVGUtils::eBBoxIncludeFillGeometry; |
michael@0 | 321 | } |
michael@0 | 322 | if ((hitTestFlags & SVG_HIT_TEST_STROKE)) { |
michael@0 | 323 | flags |= nsSVGUtils::eBBoxIncludeStrokeGeometry; |
michael@0 | 324 | } |
michael@0 | 325 | |
michael@0 | 326 | // We'd like to just pass the identity matrix to GetBBoxContribution, but if |
michael@0 | 327 | // this frame's user space size is _very_ large/small then the extents we |
michael@0 | 328 | // obtain below might have overflowed or otherwise be broken. This would |
michael@0 | 329 | // cause us to end up with a broken mRect and visual overflow rect and break |
michael@0 | 330 | // painting of this frame. This is particularly noticeable if the transforms |
michael@0 | 331 | // between us and our nsSVGOuterSVGFrame scale this frame to a reasonable |
michael@0 | 332 | // size. To avoid this we sadly have to do extra work to account for the |
michael@0 | 333 | // transforms between us and our nsSVGOuterSVGFrame, even though the |
michael@0 | 334 | // overwhelming number of SVGs will never have this problem. |
michael@0 | 335 | // XXX Will Azure eventually save us from having to do this? |
michael@0 | 336 | gfxSize scaleFactors = GetCanvasTM(FOR_OUTERSVG_TM).ScaleFactors(true); |
michael@0 | 337 | bool applyScaling = fabs(scaleFactors.width) >= 1e-6 && |
michael@0 | 338 | fabs(scaleFactors.height) >= 1e-6; |
michael@0 | 339 | gfx::Matrix scaling; |
michael@0 | 340 | if (applyScaling) { |
michael@0 | 341 | scaling.Scale(scaleFactors.width, scaleFactors.height); |
michael@0 | 342 | } |
michael@0 | 343 | gfxRect extent = GetBBoxContribution(scaling, flags).ToThebesRect(); |
michael@0 | 344 | if (applyScaling) { |
michael@0 | 345 | extent.Scale(1 / scaleFactors.width, 1 / scaleFactors.height); |
michael@0 | 346 | } |
michael@0 | 347 | mRect = nsLayoutUtils::RoundGfxRectToAppRect(extent, |
michael@0 | 348 | PresContext()->AppUnitsPerCSSPixel()); |
michael@0 | 349 | |
michael@0 | 350 | if (mState & NS_FRAME_FIRST_REFLOW) { |
michael@0 | 351 | // Make sure we have our filter property (if any) before calling |
michael@0 | 352 | // FinishAndStoreOverflow (subsequent filter changes are handled off |
michael@0 | 353 | // nsChangeHint_UpdateEffects): |
michael@0 | 354 | nsSVGEffects::UpdateEffects(this); |
michael@0 | 355 | } |
michael@0 | 356 | |
michael@0 | 357 | nsRect overflow = nsRect(nsPoint(0,0), mRect.Size()); |
michael@0 | 358 | nsOverflowAreas overflowAreas(overflow, overflow); |
michael@0 | 359 | FinishAndStoreOverflow(overflowAreas, mRect.Size()); |
michael@0 | 360 | |
michael@0 | 361 | mState &= ~(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY | |
michael@0 | 362 | NS_FRAME_HAS_DIRTY_CHILDREN); |
michael@0 | 363 | |
michael@0 | 364 | // Invalidate, but only if this is not our first reflow (since if it is our |
michael@0 | 365 | // first reflow then we haven't had our first paint yet). |
michael@0 | 366 | if (!(GetParent()->GetStateBits() & NS_FRAME_FIRST_REFLOW)) { |
michael@0 | 367 | InvalidateFrame(); |
michael@0 | 368 | } |
michael@0 | 369 | } |
michael@0 | 370 | |
michael@0 | 371 | void |
michael@0 | 372 | nsSVGPathGeometryFrame::NotifySVGChanged(uint32_t aFlags) |
michael@0 | 373 | { |
michael@0 | 374 | NS_ABORT_IF_FALSE(aFlags & (TRANSFORM_CHANGED | COORD_CONTEXT_CHANGED), |
michael@0 | 375 | "Invalidation logic may need adjusting"); |
michael@0 | 376 | |
michael@0 | 377 | // Changes to our ancestors may affect how we render when we are rendered as |
michael@0 | 378 | // part of our ancestor (specifically, if our coordinate context changes size |
michael@0 | 379 | // and we have percentage lengths defining our geometry, then we need to be |
michael@0 | 380 | // reflowed). However, ancestor changes cannot affect how we render when we |
michael@0 | 381 | // are rendered as part of any rendering observers that we may have. |
michael@0 | 382 | // Therefore no need to notify rendering observers here. |
michael@0 | 383 | |
michael@0 | 384 | // Don't try to be too smart trying to avoid the ScheduleReflowSVG calls |
michael@0 | 385 | // for the stroke properties examined below. Checking HasStroke() is not |
michael@0 | 386 | // enough, since what we care about is whether we include the stroke in our |
michael@0 | 387 | // overflow rects or not, and we sometimes deliberately include stroke |
michael@0 | 388 | // when it's not visible. See the complexities of GetBBoxContribution. |
michael@0 | 389 | |
michael@0 | 390 | if (aFlags & COORD_CONTEXT_CHANGED) { |
michael@0 | 391 | // Stroke currently contributes to our mRect, which is why we have to take |
michael@0 | 392 | // account of stroke-width here. Note that we do not need to take account |
michael@0 | 393 | // of stroke-dashoffset since, although that can have a percentage value |
michael@0 | 394 | // that is resolved against our coordinate context, it does not affect our |
michael@0 | 395 | // mRect. |
michael@0 | 396 | if (static_cast<nsSVGPathGeometryElement*>(mContent)->GeometryDependsOnCoordCtx() || |
michael@0 | 397 | StyleSVG()->mStrokeWidth.HasPercent()) { |
michael@0 | 398 | nsSVGUtils::ScheduleReflowSVG(this); |
michael@0 | 399 | } |
michael@0 | 400 | } |
michael@0 | 401 | |
michael@0 | 402 | if ((aFlags & TRANSFORM_CHANGED) && |
michael@0 | 403 | StyleSVGReset()->mVectorEffect == |
michael@0 | 404 | NS_STYLE_VECTOR_EFFECT_NON_SCALING_STROKE) { |
michael@0 | 405 | // Stroke currently contributes to our mRect, and our stroke depends on |
michael@0 | 406 | // the transform to our outer-<svg> if |vector-effect:non-scaling-stroke|. |
michael@0 | 407 | nsSVGUtils::ScheduleReflowSVG(this); |
michael@0 | 408 | } |
michael@0 | 409 | } |
michael@0 | 410 | |
michael@0 | 411 | SVGBBox |
michael@0 | 412 | nsSVGPathGeometryFrame::GetBBoxContribution(const Matrix &aToBBoxUserspace, |
michael@0 | 413 | uint32_t aFlags) |
michael@0 | 414 | { |
michael@0 | 415 | SVGBBox bbox; |
michael@0 | 416 | |
michael@0 | 417 | if (aToBBoxUserspace.IsSingular()) { |
michael@0 | 418 | // XXX ReportToConsole |
michael@0 | 419 | return bbox; |
michael@0 | 420 | } |
michael@0 | 421 | |
michael@0 | 422 | nsRefPtr<gfxContext> tmpCtx = |
michael@0 | 423 | new gfxContext(gfxPlatform::GetPlatform()->ScreenReferenceSurface()); |
michael@0 | 424 | |
michael@0 | 425 | GeneratePath(tmpCtx, aToBBoxUserspace); |
michael@0 | 426 | tmpCtx->IdentityMatrix(); |
michael@0 | 427 | |
michael@0 | 428 | // Be careful when replacing the following logic to get the fill and stroke |
michael@0 | 429 | // extents independently (instead of computing the stroke extents from the |
michael@0 | 430 | // path extents). You may think that you can just use the stroke extents if |
michael@0 | 431 | // there is both a fill and a stroke. In reality it's necessary to calculate |
michael@0 | 432 | // both the fill and stroke extents, and take the union of the two. There are |
michael@0 | 433 | // two reasons for this: |
michael@0 | 434 | // |
michael@0 | 435 | // # Due to stroke dashing, in certain cases the fill extents could actually |
michael@0 | 436 | // extend outside the stroke extents. |
michael@0 | 437 | // # If the stroke is very thin, cairo won't paint any stroke, and so the |
michael@0 | 438 | // stroke bounds that it will return will be empty. |
michael@0 | 439 | |
michael@0 | 440 | gfxRect pathExtents = tmpCtx->GetUserPathExtent(); |
michael@0 | 441 | |
michael@0 | 442 | // Account for fill: |
michael@0 | 443 | if ((aFlags & nsSVGUtils::eBBoxIncludeFillGeometry) || |
michael@0 | 444 | ((aFlags & nsSVGUtils::eBBoxIncludeFill) && |
michael@0 | 445 | StyleSVG()->mFill.mType != eStyleSVGPaintType_None)) { |
michael@0 | 446 | bbox = pathExtents; |
michael@0 | 447 | } |
michael@0 | 448 | |
michael@0 | 449 | // Account for stroke: |
michael@0 | 450 | if ((aFlags & nsSVGUtils::eBBoxIncludeStrokeGeometry) || |
michael@0 | 451 | ((aFlags & nsSVGUtils::eBBoxIncludeStroke) && |
michael@0 | 452 | nsSVGUtils::HasStroke(this))) { |
michael@0 | 453 | // We can't use tmpCtx->GetUserStrokeExtent() since it doesn't work for |
michael@0 | 454 | // device space extents. Instead we approximate the stroke extents from |
michael@0 | 455 | // pathExtents using PathExtentsToMaxStrokeExtents. |
michael@0 | 456 | if (pathExtents.Width() <= 0 && pathExtents.Height() <= 0) { |
michael@0 | 457 | // We have a zero length path, but it may still have non-empty stroke |
michael@0 | 458 | // bounds depending on the value of stroke-linecap. We need to fix up |
michael@0 | 459 | // pathExtents before it can be used with PathExtentsToMaxStrokeExtents |
michael@0 | 460 | // though, because if pathExtents is empty, its position will not have |
michael@0 | 461 | // been set. Happily we can use tmpCtx->GetUserStrokeExtent() to find |
michael@0 | 462 | // the center point of the extents even though it gets the extents wrong. |
michael@0 | 463 | nsSVGUtils::SetupCairoStrokeBBoxGeometry(this, tmpCtx); |
michael@0 | 464 | pathExtents.MoveTo(tmpCtx->GetUserStrokeExtent().Center()); |
michael@0 | 465 | pathExtents.SizeTo(0, 0); |
michael@0 | 466 | } |
michael@0 | 467 | bbox.UnionEdges(nsSVGUtils::PathExtentsToMaxStrokeExtents(pathExtents, |
michael@0 | 468 | this, |
michael@0 | 469 | ThebesMatrix(aToBBoxUserspace))); |
michael@0 | 470 | } |
michael@0 | 471 | |
michael@0 | 472 | // Account for markers: |
michael@0 | 473 | if ((aFlags & nsSVGUtils::eBBoxIncludeMarkers) != 0 && |
michael@0 | 474 | static_cast<nsSVGPathGeometryElement*>(mContent)->IsMarkable()) { |
michael@0 | 475 | |
michael@0 | 476 | float strokeWidth = nsSVGUtils::GetStrokeWidth(this); |
michael@0 | 477 | MarkerProperties properties = GetMarkerProperties(this); |
michael@0 | 478 | |
michael@0 | 479 | if (properties.MarkersExist()) { |
michael@0 | 480 | nsTArray<nsSVGMark> marks; |
michael@0 | 481 | static_cast<nsSVGPathGeometryElement*>(mContent)->GetMarkPoints(&marks); |
michael@0 | 482 | uint32_t num = marks.Length(); |
michael@0 | 483 | |
michael@0 | 484 | // These are in the same order as the nsSVGMark::Type constants. |
michael@0 | 485 | nsSVGMarkerFrame* markerFrames[] = { |
michael@0 | 486 | properties.GetMarkerStartFrame(), |
michael@0 | 487 | properties.GetMarkerMidFrame(), |
michael@0 | 488 | properties.GetMarkerEndFrame(), |
michael@0 | 489 | }; |
michael@0 | 490 | PR_STATIC_ASSERT(MOZ_ARRAY_LENGTH(markerFrames) == nsSVGMark::eTypeCount); |
michael@0 | 491 | |
michael@0 | 492 | for (uint32_t i = 0; i < num; i++) { |
michael@0 | 493 | nsSVGMark& mark = marks[i]; |
michael@0 | 494 | nsSVGMarkerFrame* frame = markerFrames[mark.type]; |
michael@0 | 495 | if (frame) { |
michael@0 | 496 | SVGBBox mbbox = |
michael@0 | 497 | frame->GetMarkBBoxContribution(aToBBoxUserspace, aFlags, this, |
michael@0 | 498 | &marks[i], strokeWidth); |
michael@0 | 499 | bbox.UnionEdges(mbbox); |
michael@0 | 500 | } |
michael@0 | 501 | } |
michael@0 | 502 | } |
michael@0 | 503 | } |
michael@0 | 504 | |
michael@0 | 505 | return bbox; |
michael@0 | 506 | } |
michael@0 | 507 | |
michael@0 | 508 | //---------------------------------------------------------------------- |
michael@0 | 509 | // nsSVGPathGeometryFrame methods: |
michael@0 | 510 | |
michael@0 | 511 | gfxMatrix |
michael@0 | 512 | nsSVGPathGeometryFrame::GetCanvasTM(uint32_t aFor, nsIFrame* aTransformRoot) |
michael@0 | 513 | { |
michael@0 | 514 | if (!(GetStateBits() & NS_FRAME_IS_NONDISPLAY) && !aTransformRoot) { |
michael@0 | 515 | if ((aFor == FOR_PAINTING && NS_SVGDisplayListPaintingEnabled()) || |
michael@0 | 516 | (aFor == FOR_HIT_TESTING && NS_SVGDisplayListHitTestingEnabled())) { |
michael@0 | 517 | return nsSVGIntegrationUtils::GetCSSPxToDevPxMatrix(this); |
michael@0 | 518 | } |
michael@0 | 519 | } |
michael@0 | 520 | |
michael@0 | 521 | NS_ASSERTION(mParent, "null parent"); |
michael@0 | 522 | |
michael@0 | 523 | nsSVGContainerFrame *parent = static_cast<nsSVGContainerFrame*>(mParent); |
michael@0 | 524 | dom::SVGGraphicsElement *content = static_cast<dom::SVGGraphicsElement*>(mContent); |
michael@0 | 525 | |
michael@0 | 526 | return content->PrependLocalTransformsTo( |
michael@0 | 527 | this == aTransformRoot ? gfxMatrix() : |
michael@0 | 528 | parent->GetCanvasTM(aFor, aTransformRoot)); |
michael@0 | 529 | } |
michael@0 | 530 | |
michael@0 | 531 | nsSVGPathGeometryFrame::MarkerProperties |
michael@0 | 532 | nsSVGPathGeometryFrame::GetMarkerProperties(nsSVGPathGeometryFrame *aFrame) |
michael@0 | 533 | { |
michael@0 | 534 | NS_ASSERTION(!aFrame->GetPrevContinuation(), "aFrame should be first continuation"); |
michael@0 | 535 | |
michael@0 | 536 | MarkerProperties result; |
michael@0 | 537 | const nsStyleSVG *style = aFrame->StyleSVG(); |
michael@0 | 538 | result.mMarkerStart = |
michael@0 | 539 | nsSVGEffects::GetMarkerProperty(style->mMarkerStart, aFrame, |
michael@0 | 540 | nsSVGEffects::MarkerBeginProperty()); |
michael@0 | 541 | result.mMarkerMid = |
michael@0 | 542 | nsSVGEffects::GetMarkerProperty(style->mMarkerMid, aFrame, |
michael@0 | 543 | nsSVGEffects::MarkerMiddleProperty()); |
michael@0 | 544 | result.mMarkerEnd = |
michael@0 | 545 | nsSVGEffects::GetMarkerProperty(style->mMarkerEnd, aFrame, |
michael@0 | 546 | nsSVGEffects::MarkerEndProperty()); |
michael@0 | 547 | return result; |
michael@0 | 548 | } |
michael@0 | 549 | |
michael@0 | 550 | nsSVGMarkerFrame * |
michael@0 | 551 | nsSVGPathGeometryFrame::MarkerProperties::GetMarkerStartFrame() |
michael@0 | 552 | { |
michael@0 | 553 | if (!mMarkerStart) |
michael@0 | 554 | return nullptr; |
michael@0 | 555 | return static_cast<nsSVGMarkerFrame *> |
michael@0 | 556 | (mMarkerStart->GetReferencedFrame(nsGkAtoms::svgMarkerFrame, nullptr)); |
michael@0 | 557 | } |
michael@0 | 558 | |
michael@0 | 559 | nsSVGMarkerFrame * |
michael@0 | 560 | nsSVGPathGeometryFrame::MarkerProperties::GetMarkerMidFrame() |
michael@0 | 561 | { |
michael@0 | 562 | if (!mMarkerMid) |
michael@0 | 563 | return nullptr; |
michael@0 | 564 | return static_cast<nsSVGMarkerFrame *> |
michael@0 | 565 | (mMarkerMid->GetReferencedFrame(nsGkAtoms::svgMarkerFrame, nullptr)); |
michael@0 | 566 | } |
michael@0 | 567 | |
michael@0 | 568 | nsSVGMarkerFrame * |
michael@0 | 569 | nsSVGPathGeometryFrame::MarkerProperties::GetMarkerEndFrame() |
michael@0 | 570 | { |
michael@0 | 571 | if (!mMarkerEnd) |
michael@0 | 572 | return nullptr; |
michael@0 | 573 | return static_cast<nsSVGMarkerFrame *> |
michael@0 | 574 | (mMarkerEnd->GetReferencedFrame(nsGkAtoms::svgMarkerFrame, nullptr)); |
michael@0 | 575 | } |
michael@0 | 576 | |
michael@0 | 577 | void |
michael@0 | 578 | nsSVGPathGeometryFrame::Render(nsRenderingContext *aContext, |
michael@0 | 579 | uint32_t aRenderComponents, |
michael@0 | 580 | nsIFrame* aTransformRoot) |
michael@0 | 581 | { |
michael@0 | 582 | gfxContext *gfx = aContext->ThebesContext(); |
michael@0 | 583 | |
michael@0 | 584 | uint16_t renderMode = SVGAutoRenderState::GetRenderMode(aContext); |
michael@0 | 585 | |
michael@0 | 586 | switch (StyleSVG()->mShapeRendering) { |
michael@0 | 587 | case NS_STYLE_SHAPE_RENDERING_OPTIMIZESPEED: |
michael@0 | 588 | case NS_STYLE_SHAPE_RENDERING_CRISPEDGES: |
michael@0 | 589 | gfx->SetAntialiasMode(gfxContext::MODE_ALIASED); |
michael@0 | 590 | break; |
michael@0 | 591 | default: |
michael@0 | 592 | gfx->SetAntialiasMode(gfxContext::MODE_COVERAGE); |
michael@0 | 593 | break; |
michael@0 | 594 | } |
michael@0 | 595 | |
michael@0 | 596 | if (renderMode != SVGAutoRenderState::NORMAL) { |
michael@0 | 597 | NS_ABORT_IF_FALSE(renderMode == SVGAutoRenderState::CLIP || |
michael@0 | 598 | renderMode == SVGAutoRenderState::CLIP_MASK, |
michael@0 | 599 | "Unknown render mode"); |
michael@0 | 600 | |
michael@0 | 601 | // In the case that |renderMode == SVGAutoRenderState::CLIP| then we don't |
michael@0 | 602 | // use the path we generate here until further up the call stack when |
michael@0 | 603 | // nsSVGClipPathFrame::Clip calls gfxContext::Clip. That's a problem for |
michael@0 | 604 | // Moz2D which emits paths in user space (unlike cairo which emits paths in |
michael@0 | 605 | // device space). gfxContext has hacks to deal with code changing the |
michael@0 | 606 | // transform then using the current path when it is backed by Moz2D, but |
michael@0 | 607 | // Moz2D itself does not since that would fundamentally go against its API. |
michael@0 | 608 | // Therefore we do not want to Save()/Restore() the gfxContext here in the |
michael@0 | 609 | // SVGAutoRenderState::CLIP case since that would block us from killing off |
michael@0 | 610 | // gfxContext and using Moz2D directly. Not bothering to Save()/Restore() |
michael@0 | 611 | // is actually okay, since we know that doesn't matter in the |
michael@0 | 612 | // SVGAutoRenderState::CLIP case (at least for the current implementation). |
michael@0 | 613 | gfxContextMatrixAutoSaveRestore autoSaveRestore; |
michael@0 | 614 | // For now revent back to doing the save even for CLIP to fix bug 959128. |
michael@0 | 615 | // Undo in bug 987193. |
michael@0 | 616 | //if (renderMode != SVGAutoRenderState::CLIP) { |
michael@0 | 617 | autoSaveRestore.SetContext(gfx); |
michael@0 | 618 | //} |
michael@0 | 619 | |
michael@0 | 620 | GeneratePath(gfx, ToMatrix(GetCanvasTM(FOR_PAINTING, aTransformRoot))); |
michael@0 | 621 | |
michael@0 | 622 | // We used to call gfx->Restore() here, since for the |
michael@0 | 623 | // SVGAutoRenderState::CLIP case it is important to leave the fill rule |
michael@0 | 624 | // that we set below untouched so that the value is still set when return |
michael@0 | 625 | // to gfxContext::Clip() further up the call stack. Since we no longer |
michael@0 | 626 | // call gfx->Save() in the SVGAutoRenderState::CLIP case we don't need to |
michael@0 | 627 | // worry that autoSaveRestore will delay the Restore() call for the |
michael@0 | 628 | // CLIP_MASK case until we exit this function. |
michael@0 | 629 | |
michael@0 | 630 | gfxContext::FillRule oldFillRull = gfx->CurrentFillRule(); |
michael@0 | 631 | |
michael@0 | 632 | if (StyleSVG()->mClipRule == NS_STYLE_FILL_RULE_EVENODD) |
michael@0 | 633 | gfx->SetFillRule(gfxContext::FILL_RULE_EVEN_ODD); |
michael@0 | 634 | else |
michael@0 | 635 | gfx->SetFillRule(gfxContext::FILL_RULE_WINDING); |
michael@0 | 636 | |
michael@0 | 637 | if (renderMode == SVGAutoRenderState::CLIP_MASK) { |
michael@0 | 638 | gfx->SetColor(gfxRGBA(1.0f, 1.0f, 1.0f, 1.0f)); |
michael@0 | 639 | gfx->Fill(); |
michael@0 | 640 | gfx->SetFillRule(oldFillRull); // restore, but only for CLIP_MASK |
michael@0 | 641 | gfx->NewPath(); |
michael@0 | 642 | } |
michael@0 | 643 | |
michael@0 | 644 | return; |
michael@0 | 645 | } |
michael@0 | 646 | |
michael@0 | 647 | gfxContextAutoSaveRestore autoSaveRestore(gfx); |
michael@0 | 648 | |
michael@0 | 649 | GeneratePath(gfx, ToMatrix(GetCanvasTM(FOR_PAINTING, aTransformRoot))); |
michael@0 | 650 | |
michael@0 | 651 | gfxTextContextPaint *contextPaint = |
michael@0 | 652 | (gfxTextContextPaint*)aContext->GetUserData(&gfxTextContextPaint::sUserDataKey); |
michael@0 | 653 | |
michael@0 | 654 | if ((aRenderComponents & eRenderFill) && |
michael@0 | 655 | nsSVGUtils::SetupCairoFillPaint(this, gfx, contextPaint)) { |
michael@0 | 656 | gfx->Fill(); |
michael@0 | 657 | } |
michael@0 | 658 | |
michael@0 | 659 | if ((aRenderComponents & eRenderStroke) && |
michael@0 | 660 | nsSVGUtils::SetupCairoStroke(this, gfx, contextPaint)) { |
michael@0 | 661 | gfx->Stroke(); |
michael@0 | 662 | } |
michael@0 | 663 | |
michael@0 | 664 | gfx->NewPath(); |
michael@0 | 665 | } |
michael@0 | 666 | |
michael@0 | 667 | void |
michael@0 | 668 | nsSVGPathGeometryFrame::GeneratePath(gfxContext* aContext, |
michael@0 | 669 | const Matrix &aTransform) |
michael@0 | 670 | { |
michael@0 | 671 | if (aTransform.IsSingular()) { |
michael@0 | 672 | aContext->IdentityMatrix(); |
michael@0 | 673 | aContext->NewPath(); |
michael@0 | 674 | return; |
michael@0 | 675 | } |
michael@0 | 676 | |
michael@0 | 677 | aContext->MultiplyAndNudgeToIntegers(ThebesMatrix(aTransform)); |
michael@0 | 678 | |
michael@0 | 679 | // Hack to let SVGPathData::ConstructPath know if we have square caps: |
michael@0 | 680 | const nsStyleSVG* style = StyleSVG(); |
michael@0 | 681 | if (style->mStrokeLinecap == NS_STYLE_STROKE_LINECAP_SQUARE) { |
michael@0 | 682 | aContext->SetLineCap(gfxContext::LINE_CAP_SQUARE); |
michael@0 | 683 | } |
michael@0 | 684 | |
michael@0 | 685 | aContext->NewPath(); |
michael@0 | 686 | static_cast<nsSVGPathGeometryElement*>(mContent)->ConstructPath(aContext); |
michael@0 | 687 | } |
michael@0 | 688 | |
michael@0 | 689 | void |
michael@0 | 690 | nsSVGPathGeometryFrame::PaintMarkers(nsRenderingContext* aContext) |
michael@0 | 691 | { |
michael@0 | 692 | gfxTextContextPaint *contextPaint = |
michael@0 | 693 | (gfxTextContextPaint*)aContext->GetUserData(&gfxTextContextPaint::sUserDataKey); |
michael@0 | 694 | |
michael@0 | 695 | if (static_cast<nsSVGPathGeometryElement*>(mContent)->IsMarkable()) { |
michael@0 | 696 | MarkerProperties properties = GetMarkerProperties(this); |
michael@0 | 697 | |
michael@0 | 698 | if (properties.MarkersExist()) { |
michael@0 | 699 | float strokeWidth = nsSVGUtils::GetStrokeWidth(this, contextPaint); |
michael@0 | 700 | |
michael@0 | 701 | nsTArray<nsSVGMark> marks; |
michael@0 | 702 | static_cast<nsSVGPathGeometryElement*> |
michael@0 | 703 | (mContent)->GetMarkPoints(&marks); |
michael@0 | 704 | |
michael@0 | 705 | uint32_t num = marks.Length(); |
michael@0 | 706 | if (num) { |
michael@0 | 707 | // These are in the same order as the nsSVGMark::Type constants. |
michael@0 | 708 | nsSVGMarkerFrame* markerFrames[] = { |
michael@0 | 709 | properties.GetMarkerStartFrame(), |
michael@0 | 710 | properties.GetMarkerMidFrame(), |
michael@0 | 711 | properties.GetMarkerEndFrame(), |
michael@0 | 712 | }; |
michael@0 | 713 | PR_STATIC_ASSERT(MOZ_ARRAY_LENGTH(markerFrames) == nsSVGMark::eTypeCount); |
michael@0 | 714 | |
michael@0 | 715 | for (uint32_t i = 0; i < num; i++) { |
michael@0 | 716 | nsSVGMark& mark = marks[i]; |
michael@0 | 717 | nsSVGMarkerFrame* frame = markerFrames[mark.type]; |
michael@0 | 718 | if (frame) { |
michael@0 | 719 | frame->PaintMark(aContext, this, &mark, strokeWidth); |
michael@0 | 720 | } |
michael@0 | 721 | } |
michael@0 | 722 | } |
michael@0 | 723 | } |
michael@0 | 724 | } |
michael@0 | 725 | } |
michael@0 | 726 | |
michael@0 | 727 | uint16_t |
michael@0 | 728 | nsSVGPathGeometryFrame::GetHitTestFlags() |
michael@0 | 729 | { |
michael@0 | 730 | return nsSVGUtils::GetGeometryHitTestFlags(this); |
michael@0 | 731 | } |