SVGIntegrationUtils.cpp (48914B)
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ 2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */ 3 /* This Source Code Form is subject to the terms of the Mozilla Public 4 * License, v. 2.0. If a copy of the MPL was not distributed with this 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 6 7 // Main header first: 8 #include "SVGIntegrationUtils.h" 9 10 // Keep others in (case-insensitive) order: 11 #include "SVGPaintServerFrame.h" 12 #include "gfxContext.h" 13 #include "gfxDrawable.h" 14 #include "mozilla/CSSClipPathInstance.h" 15 #include "mozilla/FilterInstance.h" 16 #include "mozilla/SVGClipPathFrame.h" 17 #include "mozilla/SVGMaskFrame.h" 18 #include "mozilla/SVGObserverUtils.h" 19 #include "mozilla/SVGUtils.h" 20 #include "mozilla/StaticPrefs_layers.h" 21 #include "mozilla/dom/SVGElement.h" 22 #include "mozilla/gfx/Point.h" 23 #include "nsCSSAnonBoxes.h" 24 #include "nsCSSRendering.h" 25 #include "nsDisplayList.h" 26 #include "nsLayoutUtils.h" 27 28 using namespace mozilla::dom; 29 using namespace mozilla::layers; 30 using namespace mozilla::gfx; 31 using namespace mozilla::image; 32 33 namespace mozilla { 34 35 /** 36 * This class is used to get the pre-effects ink overflow rect of a frame, 37 * or, in the case of a frame with continuations, to collect the union of the 38 * pre-effects ink overflow rects of all the continuations. The result is 39 * relative to the origin (top left corner of the border box) of the frame, or, 40 * if the frame has continuations, the origin of the _first_ continuation. 41 */ 42 class PreEffectsInkOverflowCollector : public nsLayoutUtils::BoxCallback { 43 public: 44 /** 45 * If the pre-effects ink overflow rect of the frame being examined 46 * happens to be known, it can be passed in as aCurrentFrame and its 47 * pre-effects ink overflow rect can be passed in as 48 * aCurrentFrameOverflowArea. This is just an optimization to save a 49 * frame property lookup - these arguments are optional. 50 */ 51 PreEffectsInkOverflowCollector(nsIFrame* aFirstContinuation, 52 nsIFrame* aCurrentFrame, 53 const nsRect& aCurrentFrameOverflowArea, 54 bool aInReflow) 55 : mFirstContinuation(aFirstContinuation), 56 mCurrentFrame(aCurrentFrame), 57 mCurrentFrameOverflowArea(aCurrentFrameOverflowArea), 58 mInReflow(aInReflow) { 59 NS_ASSERTION(!mFirstContinuation->GetPrevContinuation(), 60 "We want the first continuation here"); 61 } 62 63 void AddBox(nsIFrame* aFrame) override { 64 nsRect overflow = (aFrame == mCurrentFrame) 65 ? mCurrentFrameOverflowArea 66 : PreEffectsInkOverflowRect(aFrame, mInReflow); 67 mResult.UnionRect(mResult, 68 overflow + aFrame->GetOffsetTo(mFirstContinuation)); 69 } 70 71 nsRect GetResult() const { return mResult; } 72 73 private: 74 static nsRect PreEffectsInkOverflowRect(nsIFrame* aFrame, bool aInReflow) { 75 nsRect* r = aFrame->GetProperty(nsIFrame::PreEffectsBBoxProperty()); 76 if (r) { 77 return *r; 78 } 79 80 #ifdef DEBUG 81 // Having PreTransformOverflowAreasProperty cached means 82 // InkOverflowRect() will return post-effect rect, which is not what 83 // we want. This function intentional reports pre-effect rect. But it does 84 // not matter if there is no SVG effect on this frame, since no effect 85 // means post-effect rect matches pre-effect rect. 86 // 87 // This function may be called during reflow or painting. We should only 88 // do this check in painting process since the PreEffectsBBoxProperty of 89 // continuations are not set correctly while reflowing. 90 if (SVGIntegrationUtils::UsingOverflowAffectingEffects(aFrame) && 91 !aInReflow) { 92 OverflowAreas* preTransformOverflows = 93 aFrame->GetProperty(nsIFrame::PreTransformOverflowAreasProperty()); 94 95 MOZ_ASSERT(!preTransformOverflows, 96 "InkOverflowRect() won't return the pre-effects rect!"); 97 } 98 #endif 99 return aFrame->InkOverflowRectRelativeToSelf(); 100 } 101 102 nsIFrame* mFirstContinuation; 103 nsIFrame* mCurrentFrame; 104 const nsRect& mCurrentFrameOverflowArea; 105 nsRect mResult; 106 bool mInReflow; 107 }; 108 109 /** 110 * Gets the union of the pre-effects ink overflow rects of all of a frame's 111 * continuations, in "user space". 112 */ 113 static nsRect GetPreEffectsInkOverflowUnion( 114 nsIFrame* aFirstContinuation, nsIFrame* aCurrentFrame, 115 const nsRect& aCurrentFramePreEffectsOverflow, 116 const nsPoint& aFirstContinuationToUserSpace, bool aInReflow) { 117 NS_ASSERTION(!aFirstContinuation->GetPrevContinuation(), 118 "Need first continuation here"); 119 PreEffectsInkOverflowCollector collector(aFirstContinuation, aCurrentFrame, 120 aCurrentFramePreEffectsOverflow, 121 aInReflow); 122 // Compute union of all overflow areas relative to aFirstContinuation: 123 nsLayoutUtils::GetAllInFlowBoxes(aFirstContinuation, &collector); 124 // Return the result in user space: 125 return collector.GetResult() + aFirstContinuationToUserSpace; 126 } 127 128 /** 129 * Gets the pre-effects ink overflow rect of aCurrentFrame in "user space". 130 */ 131 static nsRect GetPreEffectsInkOverflow( 132 nsIFrame* aFirstContinuation, nsIFrame* aCurrentFrame, 133 const nsPoint& aFirstContinuationToUserSpace) { 134 NS_ASSERTION(!aFirstContinuation->GetPrevContinuation(), 135 "Need first continuation here"); 136 PreEffectsInkOverflowCollector collector(aFirstContinuation, nullptr, 137 nsRect(), false); 138 // Compute overflow areas of current frame relative to aFirstContinuation: 139 nsLayoutUtils::AddBoxesForFrame(aCurrentFrame, &collector); 140 // Return the result in user space: 141 return collector.GetResult() + aFirstContinuationToUserSpace; 142 } 143 144 bool SVGIntegrationUtils::UsingOverflowAffectingEffects( 145 const nsIFrame* aFrame) { 146 // Currently overflow don't take account of SVG or other non-absolute 147 // positioned clipping, or masking. 148 return aFrame->StyleEffects()->HasFilters(); 149 } 150 151 bool SVGIntegrationUtils::UsingEffectsForFrame(const nsIFrame* aFrame) { 152 // Even when SVG display lists are disabled, returning true for SVG frames 153 // does not adversely affect any of our callers. Therefore we don't bother 154 // checking the SDL prefs here, since we don't know if we're being called for 155 // painting or hit-testing anyway. 156 const nsStyleSVGReset* style = aFrame->StyleSVGReset(); 157 const nsStyleEffects* effects = aFrame->StyleEffects(); 158 // TODO(cbrewster): remove backdrop-filter from this list once it is supported 159 // in preserve-3d cases. 160 return effects->HasFilters() || effects->HasBackdropFilters() || 161 style->HasClipPath() || style->HasMask(); 162 } 163 164 nsPoint SVGIntegrationUtils::GetOffsetToBoundingBox(nsIFrame* aFrame) { 165 if (aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) { 166 // Do NOT call GetAllInFlowRectsUnion for SVG - it will get the 167 // covered region relative to the SVGOuterSVGFrame, which is absolutely 168 // not what we want. SVG frames are always in user space, so they have 169 // no offset adjustment to make. 170 return nsPoint(); 171 } 172 173 // The GetAllInFlowRectsUnion() call gets the union of the frame border-box 174 // rects over all continuations, relative to the origin (top-left of the 175 // border box) of its second argument (here, aFrame, the first continuation). 176 return -nsLayoutUtils::GetAllInFlowRectsUnion(aFrame, aFrame).TopLeft(); 177 } 178 179 struct EffectOffsets { 180 // The offset between the reference frame and the bounding box of the 181 // target frame in app unit. 182 nsPoint offsetToBoundingBox; 183 // The offset between the reference frame and the bounding box of the 184 // target frame in app unit. 185 nsPoint offsetToUserSpace; 186 // The offset between the reference frame and the bounding box of the 187 // target frame in device unit. 188 gfxPoint offsetToUserSpaceInDevPx; 189 }; 190 191 static EffectOffsets ComputeEffectOffset( 192 nsIFrame* aFrame, const SVGIntegrationUtils::PaintFramesParams& aParams) { 193 EffectOffsets result; 194 195 result.offsetToBoundingBox = 196 aParams.builder->ToReferenceFrame(aFrame) - 197 SVGIntegrationUtils::GetOffsetToBoundingBox(aFrame); 198 if (!aFrame->IsSVGFrame()) { 199 /* Snap the offset if the reference frame is not a SVG frame, 200 * since other frames will be snapped to pixel when rendering. */ 201 result.offsetToBoundingBox = 202 nsPoint(aFrame->PresContext()->RoundAppUnitsToNearestDevPixels( 203 result.offsetToBoundingBox.x), 204 aFrame->PresContext()->RoundAppUnitsToNearestDevPixels( 205 result.offsetToBoundingBox.y)); 206 } 207 208 // After applying only "aOffsetToBoundingBox", aParams.ctx would have its 209 // origin at the top left corner of frame's bounding box (over all 210 // continuations). 211 // However, SVG painting needs the origin to be located at the origin of the 212 // SVG frame's "user space", i.e. the space in which, for example, the 213 // frame's BBox lives. 214 // SVG geometry frames and foreignObject frames apply their own offsets, so 215 // their position is relative to their user space. So for these frame types, 216 // if we want aParams.ctx to be in user space, we first need to subtract the 217 // frame's position so that SVG painting can later add it again and the 218 // frame is painted in the right place. 219 gfxPoint toUserSpaceGfx = 220 SVGUtils::FrameSpaceInCSSPxToUserSpaceOffset(aFrame); 221 nsPoint toUserSpace = 222 nsPoint(nsPresContext::CSSPixelsToAppUnits(float(toUserSpaceGfx.x)), 223 nsPresContext::CSSPixelsToAppUnits(float(toUserSpaceGfx.y))); 224 225 result.offsetToUserSpace = result.offsetToBoundingBox - toUserSpace; 226 227 #ifdef DEBUG 228 bool hasSVGLayout = aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT); 229 NS_ASSERTION( 230 hasSVGLayout || result.offsetToBoundingBox == result.offsetToUserSpace, 231 "For non-SVG frames there shouldn't be any additional offset"); 232 #endif 233 234 result.offsetToUserSpaceInDevPx = nsLayoutUtils::PointToGfxPoint( 235 result.offsetToUserSpace, aFrame->PresContext()->AppUnitsPerDevPixel()); 236 237 return result; 238 } 239 240 /** 241 * Setup transform matrix of a gfx context by a specific frame. Move the 242 * origin of aParams.ctx to the user space of aFrame. 243 */ 244 static EffectOffsets MoveContextOriginToUserSpace( 245 nsIFrame* aFrame, const SVGIntegrationUtils::PaintFramesParams& aParams) { 246 EffectOffsets offset = ComputeEffectOffset(aFrame, aParams); 247 248 aParams.ctx.SetMatrixDouble(aParams.ctx.CurrentMatrixDouble().PreTranslate( 249 offset.offsetToUserSpaceInDevPx)); 250 251 return offset; 252 } 253 254 gfxPoint SVGIntegrationUtils::GetOffsetToUserSpaceInDevPx( 255 nsIFrame* aFrame, const PaintFramesParams& aParams) { 256 nsIFrame* firstFrame = 257 nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame); 258 EffectOffsets offset = ComputeEffectOffset(firstFrame, aParams); 259 return offset.offsetToUserSpaceInDevPx; 260 } 261 262 /* static */ 263 nsSize SVGIntegrationUtils::GetContinuationUnionSize(nsIFrame* aNonSVGFrame) { 264 NS_ASSERTION(!aNonSVGFrame->IsSVGFrame(), "SVG frames should not get here"); 265 nsIFrame* firstFrame = 266 nsLayoutUtils::FirstContinuationOrIBSplitSibling(aNonSVGFrame); 267 return nsLayoutUtils::GetAllInFlowRectsUnion(firstFrame, firstFrame).Size(); 268 } 269 270 /* static */ gfx::Size SVGIntegrationUtils::GetSVGCoordContextForNonSVGFrame( 271 nsIFrame* aNonSVGFrame) { 272 NS_ASSERTION(!aNonSVGFrame->IsSVGFrame(), "SVG frames should not get here"); 273 nsIFrame* firstFrame = 274 nsLayoutUtils::FirstContinuationOrIBSplitSibling(aNonSVGFrame); 275 nsRect r = nsLayoutUtils::GetAllInFlowRectsUnion(firstFrame, firstFrame); 276 return gfx::Size(nsPresContext::AppUnitsToFloatCSSPixels(r.width), 277 nsPresContext::AppUnitsToFloatCSSPixels(r.height)); 278 } 279 280 gfxRect SVGIntegrationUtils::GetSVGBBoxForNonSVGFrame( 281 nsIFrame* aNonSVGFrame, bool aUnionContinuations) { 282 // Except for SVGOuterSVGFrame, we shouldn't be getting here with SVG 283 // frames at all. This function is for elements that are laid out using the 284 // CSS box model rules. 285 NS_ASSERTION(!aNonSVGFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT), 286 "Frames with SVG layout should not get here"); 287 288 nsIFrame* firstFrame = 289 nsLayoutUtils::FirstContinuationOrIBSplitSibling(aNonSVGFrame); 290 // 'r' is in "user space": 291 nsRect r = (aUnionContinuations) 292 ? GetPreEffectsInkOverflowUnion( 293 firstFrame, nullptr, nsRect(), 294 GetOffsetToBoundingBox(firstFrame), false) 295 : GetPreEffectsInkOverflow(firstFrame, aNonSVGFrame, 296 GetOffsetToBoundingBox(firstFrame)); 297 298 return nsLayoutUtils::RectToGfxRect(r, AppUnitsPerCSSPixel()); 299 } 300 301 // XXX Since we're called during reflow, this method is broken for frames with 302 // continuations. When we're called for a frame with continuations, we're 303 // called for each continuation in turn as it's reflowed. However, it isn't 304 // until the last continuation is reflowed that this method's 305 // GetOffsetToBoundingBox() and GetPreEffectsInkOverflowUnion() calls will 306 // obtain valid border boxes for all the continuations. As a result, we'll 307 // end up returning bogus post-filter ink overflow rects for all the prior 308 // continuations. Unfortunately, by the time the last continuation is 309 // reflowed, it's too late to go back and set and propagate the overflow 310 // rects on the previous continuations. 311 // 312 // The reason that we need to pass an override bbox to 313 // GetPreEffectsInkOverflowUnion rather than just letting it call into our 314 // GetSVGBBoxForNonSVGFrame method is because we get called by 315 // ComputeEffectsRect when it has been called with 316 // aStoreRectProperties set to false. In this case the pre-effects visual 317 // overflow rect that it has been passed may be different to that stored on 318 // aFrame, resulting in a different bbox. 319 // 320 // XXXjwatt The pre-effects ink overflow rect passed to 321 // ComputeEffectsRect won't include continuation overflows, so 322 // for frames with continuation the following filter analysis will likely end 323 // up being carried out with a bbox created as if the frame didn't have 324 // continuations. 325 // 326 // XXXjwatt Using aPreEffectsOverflowRect to create the bbox isn't really right 327 // for SVG frames, since for SVG frames the SVG spec defines the bbox to be 328 // something quite different to the pre-effects ink overflow rect. However, 329 // we're essentially calculating an invalidation area here, and using the 330 // pre-effects overflow rect will actually overestimate that area which, while 331 // being a bit wasteful, isn't otherwise a problem. 332 // 333 nsRect SVGIntegrationUtils::ComputePostEffectsInkOverflowRect( 334 nsIFrame* aFrame, const nsRect& aPreEffectsOverflowRect) { 335 MOZ_ASSERT(!aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT), 336 "Don't call this on SVG child frames"); 337 338 MOZ_ASSERT(aFrame->StyleEffects()->HasFilters(), 339 "We should only be called if the frame is filtered, since filters " 340 "are the only effect that affects overflow."); 341 342 nsIFrame* firstFrame = 343 nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame); 344 // Note: we do not return here for eHasNoRefs since we must still handle any 345 // CSS filter functions. 346 // TODO: we should really return an empty rect for eHasRefsSomeInvalid since 347 // in that case we disable painting of the element. 348 nsTArray<SVGFilterFrame*> filterFrames; 349 if (SVGObserverUtils::GetAndObserveFilters(firstFrame, &filterFrames) == 350 SVGObserverUtils::eHasRefsSomeInvalid) { 351 return aPreEffectsOverflowRect; 352 } 353 354 // Create an override bbox - see comment above: 355 nsPoint firstFrameToBoundingBox = GetOffsetToBoundingBox(firstFrame); 356 // overrideBBox is in "user space", in _CSS_ pixels: 357 // XXX Why are we rounding out to pixel boundaries? We don't do that in 358 // GetSVGBBoxForNonSVGFrame, and it doesn't appear to be necessary. 359 gfxRect overrideBBox = nsLayoutUtils::RectToGfxRect( 360 GetPreEffectsInkOverflowUnion(firstFrame, aFrame, aPreEffectsOverflowRect, 361 firstFrameToBoundingBox, true), 362 AppUnitsPerCSSPixel()); 363 overrideBBox.RoundOut(); 364 365 Maybe<nsRect> overflowRect = FilterInstance::GetPostFilterBounds( 366 firstFrame, filterFrames, &overrideBBox); 367 if (!overflowRect) { 368 return aPreEffectsOverflowRect; 369 } 370 371 // Return overflowRect relative to aFrame, rather than "user space": 372 return overflowRect.value() - 373 (aFrame->GetOffsetTo(firstFrame) + firstFrameToBoundingBox); 374 } 375 376 nsRect SVGIntegrationUtils::GetRequiredSourceForInvalidArea( 377 nsIFrame* aFrame, const nsRect& aDirtyRect) { 378 nsIFrame* firstFrame = 379 nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame); 380 381 // If we have any filters to observe then we should have started doing that 382 // during reflow/ComputeFrameEffectsRect, so we use GetFiltersIfObserving 383 // here to avoid needless work (or masking bugs by setting up observers at 384 // the wrong time). 385 nsTArray<SVGFilterFrame*> filterFrames; 386 if (!aFrame->StyleEffects()->HasFilters() || 387 SVGObserverUtils::GetFiltersIfObserving(firstFrame, &filterFrames) == 388 SVGObserverUtils::eHasRefsSomeInvalid) { 389 return aDirtyRect; 390 } 391 392 // Convert aDirtyRect into "user space" in app units: 393 nsPoint toUserSpace = 394 aFrame->GetOffsetTo(firstFrame) + GetOffsetToBoundingBox(firstFrame); 395 nsRect postEffectsRect = aDirtyRect + toUserSpace; 396 397 // Return ther result, relative to aFrame, not in user space: 398 return FilterInstance::GetPreFilterNeededArea(firstFrame, filterFrames, 399 postEffectsRect) 400 .GetBounds() - 401 toUserSpace; 402 } 403 404 bool SVGIntegrationUtils::HitTestFrameForEffects(nsIFrame* aFrame, 405 const nsPoint& aPt) { 406 nsIFrame* firstFrame = 407 nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame); 408 // Convert aPt to user space: 409 nsPoint toUserSpace; 410 if (aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) { 411 // XXXmstange Isn't this wrong for svg:use and innerSVG frames? 412 toUserSpace = aFrame->GetPosition(); 413 } else { 414 toUserSpace = 415 aFrame->GetOffsetTo(firstFrame) + GetOffsetToBoundingBox(firstFrame); 416 } 417 nsPoint pt = aPt + toUserSpace; 418 gfxPoint userSpacePt = gfxPoint(pt.x, pt.y) / AppUnitsPerCSSPixel(); 419 return SVGUtils::HitTestClip(firstFrame, userSpacePt); 420 } 421 422 using PaintFramesParams = SVGIntegrationUtils::PaintFramesParams; 423 424 /** 425 * Paint css-positioned-mask onto a given target(aMaskDT). 426 * Return value indicates if mask is complete. 427 */ 428 static bool PaintMaskSurface(const PaintFramesParams& aParams, 429 DrawTarget* aMaskDT, float aOpacity, 430 const ComputedStyle* aSC, 431 const nsTArray<SVGMaskFrame*>& aMaskFrames, 432 const nsPoint& aOffsetToUserSpace) { 433 MOZ_ASSERT(!aMaskFrames.IsEmpty()); 434 MOZ_ASSERT(aMaskDT->GetFormat() == SurfaceFormat::A8); 435 MOZ_ASSERT(aOpacity == 1.0 || aMaskFrames.Length() == 1); 436 437 const nsStyleSVGReset* svgReset = aSC->StyleSVGReset(); 438 gfxMatrix cssPxToDevPxMatrix = SVGUtils::GetCSSPxToDevPxMatrix(aParams.frame); 439 440 nsPresContext* presContext = aParams.frame->PresContext(); 441 gfxPoint devPixelOffsetToUserSpace = nsLayoutUtils::PointToGfxPoint( 442 aOffsetToUserSpace, presContext->AppUnitsPerDevPixel()); 443 444 gfxContext maskContext(aMaskDT, /* aPreserveTransform */ true); 445 446 bool isMaskComplete = true; 447 448 // Multiple SVG masks interleave with image mask. Paint each layer onto 449 // aMaskDT one at a time. 450 for (int i = aMaskFrames.Length() - 1; i >= 0; i--) { 451 SVGMaskFrame* maskFrame = aMaskFrames[i]; 452 CompositionOp compositionOp = 453 (i == int(aMaskFrames.Length() - 1)) 454 ? CompositionOp::OP_OVER 455 : nsCSSRendering::GetGFXCompositeMode( 456 svgReset->mMask.mLayers[i].mComposite); 457 458 // maskFrame != nullptr means we get a SVG mask. 459 // maskFrame == nullptr means we get an image mask. 460 if (maskFrame) { 461 SVGMaskFrame::MaskParams params( 462 maskContext.GetDrawTarget(), aParams.frame, cssPxToDevPxMatrix, 463 aOpacity, svgReset->mMask.mLayers[i].mMaskMode, aParams.imgParams); 464 RefPtr<SourceSurface> svgMask = maskFrame->GetMaskForMaskedFrame(params); 465 if (svgMask) { 466 Matrix tmp = aMaskDT->GetTransform(); 467 aMaskDT->SetTransform(Matrix()); 468 aMaskDT->MaskSurface(ColorPattern(DeviceColor(0.0, 0.0, 0.0, 1.0)), 469 svgMask, Point(0, 0), 470 DrawOptions(1.0, compositionOp)); 471 aMaskDT->SetTransform(tmp); 472 } 473 } else if (svgReset->mMask.mLayers[i].mImage.IsResolved()) { 474 gfxContextMatrixAutoSaveRestore matRestore(&maskContext); 475 476 maskContext.Multiply(gfxMatrix::Translation(-devPixelOffsetToUserSpace)); 477 nsCSSRendering::PaintBGParams params = 478 nsCSSRendering::PaintBGParams::ForSingleLayer( 479 *presContext, aParams.dirtyRect, aParams.borderArea, 480 aParams.frame, 481 aParams.builder->GetBackgroundPaintFlags() | 482 nsCSSRendering::PAINTBG_MASK_IMAGE, 483 i, compositionOp, aOpacity); 484 485 aParams.imgParams.result &= nsCSSRendering::PaintStyleImageLayerWithSC( 486 params, maskContext, aSC, *aParams.frame->StyleBorder()); 487 } else { 488 isMaskComplete = false; 489 } 490 } 491 492 return isMaskComplete; 493 } 494 495 struct MaskPaintResult { 496 RefPtr<SourceSurface> maskSurface; 497 Matrix maskTransform; 498 bool transparentBlackMask; 499 bool opacityApplied; 500 501 MaskPaintResult() : transparentBlackMask(false), opacityApplied(false) {} 502 }; 503 504 static MaskPaintResult CreateAndPaintMaskSurface( 505 const PaintFramesParams& aParams, float aOpacity, const ComputedStyle* aSC, 506 const nsTArray<SVGMaskFrame*>& aMaskFrames, 507 const nsPoint& aOffsetToUserSpace) { 508 const nsStyleSVGReset* svgReset = aSC->StyleSVGReset(); 509 MOZ_ASSERT(!aMaskFrames.IsEmpty()); 510 MaskPaintResult paintResult; 511 512 gfxContext& ctx = aParams.ctx; 513 514 // Optimization for single SVG mask. 515 if (aMaskFrames.Length() == 1 && aMaskFrames[0]) { 516 gfxMatrix cssPxToDevPxMatrix = 517 SVGUtils::GetCSSPxToDevPxMatrix(aParams.frame); 518 paintResult.opacityApplied = true; 519 SVGMaskFrame::MaskParams params( 520 ctx.GetDrawTarget(), aParams.frame, cssPxToDevPxMatrix, aOpacity, 521 svgReset->mMask.mLayers[0].mMaskMode, aParams.imgParams); 522 paintResult.maskSurface = aMaskFrames[0]->GetMaskForMaskedFrame(params); 523 paintResult.maskTransform = ctx.CurrentMatrix(); 524 paintResult.maskTransform.Invert(); 525 if (!paintResult.maskSurface) { 526 paintResult.transparentBlackMask = true; 527 } 528 529 return paintResult; 530 } 531 532 const LayoutDeviceRect& maskSurfaceRect = 533 aParams.maskRect.valueOr(LayoutDeviceRect()); 534 if (aParams.maskRect.isSome() && maskSurfaceRect.IsEmpty()) { 535 // XXX: Is this ever true? 536 paintResult.transparentBlackMask = true; 537 return paintResult; 538 } 539 540 RefPtr<DrawTarget> maskDT = ctx.GetDrawTarget()->CreateClippedDrawTarget( 541 maskSurfaceRect.ToUnknownRect(), SurfaceFormat::A8); 542 if (!maskDT || !maskDT->IsValid()) { 543 return paintResult; 544 } 545 546 // We can paint mask along with opacity only if 547 // 1. There is only one mask, or 548 // 2. No overlap among masks. 549 // Collision detect in #2 is not that trivial, we only accept #1 here. 550 paintResult.opacityApplied = (aMaskFrames.Length() == 1); 551 552 // Set context's matrix on maskContext, offset by the maskSurfaceRect's 553 // position. This makes sure that we combine the masks in device space. 554 Matrix maskSurfaceMatrix = ctx.CurrentMatrix(); 555 556 bool isMaskComplete = PaintMaskSurface( 557 aParams, maskDT, paintResult.opacityApplied ? aOpacity : 1.0, aSC, 558 aMaskFrames, aOffsetToUserSpace); 559 560 if (!isMaskComplete || 561 (aParams.imgParams.result != ImgDrawResult::SUCCESS && 562 aParams.imgParams.result != ImgDrawResult::SUCCESS_NOT_COMPLETE && 563 aParams.imgParams.result != ImgDrawResult::WRONG_SIZE)) { 564 // Now we know the status of mask resource since we used it while painting. 565 // According to the return value of PaintMaskSurface, we know whether mask 566 // resource is resolvable or not. 567 // 568 // For a HTML doc: 569 // According to css-masking spec, always create a mask surface when 570 // we have any item in maskFrame even if all of those items are 571 // non-resolvable <mask-sources> or <images>. 572 // Set paintResult.transparentBlackMask as true, the caller should stop 573 // painting masked content as if this mask is a transparent black one. 574 // For a SVG doc: 575 // SVG 1.1 say that if we fail to resolve a mask, we should draw the 576 // object unmasked. 577 // Left paintResult.maskSurface empty, the caller should paint all 578 // masked content as if this mask is an opaque white one(no mask). 579 paintResult.transparentBlackMask = 580 !aParams.frame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT); 581 582 MOZ_ASSERT(!paintResult.maskSurface); 583 return paintResult; 584 } 585 586 paintResult.maskTransform = maskSurfaceMatrix; 587 if (!paintResult.maskTransform.Invert()) { 588 return paintResult; 589 } 590 591 paintResult.maskSurface = maskDT->Snapshot(); 592 return paintResult; 593 } 594 595 static bool ValidateSVGFrame(nsIFrame* aFrame) { 596 NS_ASSERTION( 597 !aFrame->HasAllStateBits(NS_FRAME_SVG_LAYOUT | NS_FRAME_IS_NONDISPLAY), 598 "Should not use SVGIntegrationUtils on this SVG frame"); 599 600 if (aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) { 601 #ifdef DEBUG 602 ISVGDisplayableFrame* svgFrame = do_QueryFrame(aFrame); 603 MOZ_ASSERT(svgFrame && aFrame->GetContent()->IsSVGElement(), 604 "A non-SVG frame carries NS_FRAME_SVG_LAYOUT flag?"); 605 #endif 606 607 const nsIContent* content = aFrame->GetContent(); 608 if (!static_cast<const SVGElement*>(content)->HasValidDimensions()) { 609 // The SVG spec says not to draw _anything_ 610 return false; 611 } 612 } 613 614 return true; 615 } 616 617 bool SVGIntegrationUtils::PaintMask(const PaintFramesParams& aParams, 618 bool& aOutIsMaskComplete) { 619 aOutIsMaskComplete = true; 620 621 SVGUtils::MaskUsage maskUsage = 622 SVGUtils::DetermineMaskUsage(aParams.frame, aParams.handleOpacity); 623 if (!maskUsage.ShouldDoSomething()) { 624 return false; 625 } 626 627 nsIFrame* frame = aParams.frame; 628 if (!ValidateSVGFrame(frame)) { 629 return false; 630 } 631 632 gfxContext& ctx = aParams.ctx; 633 RefPtr<DrawTarget> maskTarget = ctx.GetDrawTarget(); 634 635 if (maskUsage.ShouldGenerateMaskLayer() && maskUsage.HasSVGClip()) { 636 // We will paint both mask of positioned mask and clip-path into 637 // maskTarget. 638 // 639 // Create one extra draw target for drawing positioned mask, so that we do 640 // not have to copy the content of maskTarget before painting 641 // clip-path into it. 642 maskTarget = maskTarget->CreateClippedDrawTarget(Rect(), SurfaceFormat::A8); 643 } 644 645 nsIFrame* firstFrame = 646 nsLayoutUtils::FirstContinuationOrIBSplitSibling(frame); 647 nsTArray<SVGMaskFrame*> maskFrames; 648 // XXX check return value? 649 SVGObserverUtils::GetAndObserveMasks(firstFrame, &maskFrames); 650 651 gfxGroupForBlendAutoSaveRestore autoPop(&ctx); 652 bool shouldPushOpacity = !maskUsage.IsOpaque() && maskFrames.Length() != 1; 653 if (shouldPushOpacity) { 654 autoPop.PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, 655 maskUsage.Opacity()); 656 } 657 658 gfxContextMatrixAutoSaveRestore matSR; 659 660 // Paint clip-path-basic-shape onto ctx 661 gfxContextAutoSaveRestore basicShapeSR; 662 if (maskUsage.ShouldApplyBasicShapeOrPath()) { 663 matSR.SetContext(&ctx); 664 665 MoveContextOriginToUserSpace(firstFrame, aParams); 666 667 basicShapeSR.SetContext(&ctx); 668 gfxMatrix mat = SVGUtils::GetCSSPxToDevPxMatrix(frame); 669 if (!maskUsage.ShouldGenerateMaskLayer()) { 670 // Only have basic-shape clip-path effect. Fill clipped region by 671 // opaque white. 672 ctx.SetDeviceColor(DeviceColor::MaskOpaqueWhite()); 673 RefPtr<Path> path = CSSClipPathInstance::CreateClipPathForFrame( 674 ctx.GetDrawTarget(), frame, mat); 675 if (path) { 676 ctx.SetPath(path); 677 ctx.Fill(); 678 } 679 680 return true; 681 } 682 CSSClipPathInstance::ApplyBasicShapeOrPathClip(ctx, frame, mat); 683 } 684 685 // Paint mask into maskTarget. 686 if (maskUsage.ShouldGenerateMaskLayer()) { 687 matSR.Restore(); 688 matSR.SetContext(&ctx); 689 690 EffectOffsets offsets = ComputeEffectOffset(frame, aParams); 691 maskTarget->SetTransform(maskTarget->GetTransform().PreTranslate( 692 ToPoint(offsets.offsetToUserSpaceInDevPx))); 693 aOutIsMaskComplete = PaintMaskSurface( 694 aParams, maskTarget, shouldPushOpacity ? 1.0f : maskUsage.Opacity(), 695 firstFrame->Style(), maskFrames, offsets.offsetToUserSpace); 696 } 697 698 // Paint clip-path onto ctx. 699 if (maskUsage.HasSVGClip()) { 700 matSR.Restore(); 701 matSR.SetContext(&ctx); 702 703 MoveContextOriginToUserSpace(firstFrame, aParams); 704 Matrix clipMaskTransform; 705 gfxMatrix cssPxToDevPxMatrix = SVGUtils::GetCSSPxToDevPxMatrix(frame); 706 707 SVGClipPathFrame* clipPathFrame; 708 // XXX check return value? 709 SVGObserverUtils::GetAndObserveClipPath(firstFrame, &clipPathFrame); 710 RefPtr<SourceSurface> maskSurface = 711 maskUsage.ShouldGenerateMaskLayer() ? maskTarget->Snapshot() : nullptr; 712 clipPathFrame->PaintClipMask(ctx, frame, cssPxToDevPxMatrix, maskSurface); 713 } 714 715 return true; 716 } 717 718 template <class T> 719 void PaintMaskAndClipPathInternal(const PaintFramesParams& aParams, 720 const T& aPaintChild) { 721 #ifdef DEBUG 722 const nsStyleSVGReset* style = aParams.frame->StyleSVGReset(); 723 MOZ_ASSERT(style->HasClipPath() || style->HasMask(), 724 "Should not use this method when no mask or clipPath effect" 725 "on this frame"); 726 #endif 727 728 /* SVG defines the following rendering model: 729 * 730 * 1. Render geometry 731 * 2. Apply filter 732 * 3. Apply clipping, masking, group opacity 733 * 734 * We handle #3 here and perform a couple of optimizations: 735 * 736 * + Use cairo's clipPath when representable natively (single object 737 * clip region). 738 * 739 * + Merge opacity and masking if both used together. 740 */ 741 nsIFrame* frame = aParams.frame; 742 if (!ValidateSVGFrame(frame)) { 743 return; 744 } 745 746 SVGUtils::MaskUsage maskUsage = 747 SVGUtils::DetermineMaskUsage(aParams.frame, aParams.handleOpacity); 748 749 if (maskUsage.IsTransparent()) { 750 return; 751 } 752 753 gfxContext& context = aParams.ctx; 754 gfxContextMatrixAutoSaveRestore matrixAutoSaveRestore(&context); 755 756 nsIFrame* firstFrame = 757 nsLayoutUtils::FirstContinuationOrIBSplitSibling(frame); 758 759 SVGClipPathFrame* clipPathFrame; 760 // XXX check return value? 761 SVGObserverUtils::GetAndObserveClipPath(firstFrame, &clipPathFrame); 762 763 nsTArray<SVGMaskFrame*> maskFrames; 764 // XXX check return value? 765 SVGObserverUtils::GetAndObserveMasks(firstFrame, &maskFrames); 766 767 gfxMatrix cssPxToDevPxMatrix = SVGUtils::GetCSSPxToDevPxMatrix(frame); 768 769 bool shouldPushMask = false; 770 771 gfxGroupForBlendAutoSaveRestore autoGroupForBlend(&context); 772 773 /* Check if we need to do additional operations on this child's 774 * rendering, which necessitates rendering into another surface. */ 775 if (maskUsage.ShouldGenerateMask()) { 776 gfxContextMatrixAutoSaveRestore matSR; 777 778 RefPtr<SourceSurface> maskSurface; 779 bool opacityApplied = false; 780 781 if (maskUsage.ShouldGenerateMaskLayer()) { 782 matSR.SetContext(&context); 783 784 // For css-mask, we want to generate a mask for each continuation frame, 785 // so we setup context matrix by the position of the current frame, 786 // instead of the first continuation frame. 787 EffectOffsets offsets = MoveContextOriginToUserSpace(frame, aParams); 788 MaskPaintResult paintResult = CreateAndPaintMaskSurface( 789 aParams, maskUsage.Opacity(), firstFrame->Style(), maskFrames, 790 offsets.offsetToUserSpace); 791 792 if (paintResult.transparentBlackMask) { 793 return; 794 } 795 796 maskSurface = paintResult.maskSurface; 797 if (maskSurface) { 798 shouldPushMask = true; 799 800 opacityApplied = paintResult.opacityApplied; 801 } 802 } 803 804 if (maskUsage.ShouldGenerateClipMaskLayer()) { 805 matSR.Restore(); 806 matSR.SetContext(&context); 807 808 MoveContextOriginToUserSpace(firstFrame, aParams); 809 RefPtr<SourceSurface> clipMaskSurface = clipPathFrame->GetClipMask( 810 context, frame, cssPxToDevPxMatrix, maskSurface); 811 812 if (clipMaskSurface) { 813 maskSurface = clipMaskSurface; 814 } else { 815 // Either entire surface is clipped out, or gfx buffer allocation 816 // failure in SVGClipPathFrame::GetClipMask. 817 return; 818 } 819 820 shouldPushMask = true; 821 } 822 823 // opacity != 1.0f. 824 if (!maskUsage.ShouldGenerateLayer()) { 825 MOZ_ASSERT(!maskUsage.IsOpaque()); 826 827 matSR.SetContext(&context); 828 MoveContextOriginToUserSpace(firstFrame, aParams); 829 shouldPushMask = true; 830 } 831 832 if (shouldPushMask) { 833 // We want the mask to be untransformed so use the inverse of the 834 // current transform as the maskTransform to compensate. 835 Matrix maskTransform = context.CurrentMatrix(); 836 maskTransform.Invert(); 837 838 autoGroupForBlend.PushGroupForBlendBack( 839 gfxContentType::COLOR_ALPHA, 840 opacityApplied ? 1.0f : maskUsage.Opacity(), maskSurface, 841 maskTransform); 842 } 843 } 844 845 /* If this frame has only a trivial clipPath, set up cairo's clipping now so 846 * we can just do normal painting and get it clipped appropriately. 847 */ 848 if (maskUsage.ShouldApplyClipPath() || 849 maskUsage.ShouldApplyBasicShapeOrPath()) { 850 gfxContextMatrixAutoSaveRestore matSR(&context); 851 852 MoveContextOriginToUserSpace(firstFrame, aParams); 853 854 MOZ_ASSERT(!maskUsage.ShouldApplyClipPath() || 855 !maskUsage.ShouldApplyBasicShapeOrPath()); 856 if (maskUsage.ShouldApplyClipPath()) { 857 clipPathFrame->ApplyClipPath(context, frame, cssPxToDevPxMatrix); 858 } else { 859 CSSClipPathInstance::ApplyBasicShapeOrPathClip(context, frame, 860 cssPxToDevPxMatrix); 861 } 862 } 863 864 /* Paint the child */ 865 context.SetMatrix(matrixAutoSaveRestore.Matrix()); 866 aPaintChild(); 867 868 if (StaticPrefs::layers_draw_mask_debug()) { 869 gfxContextAutoSaveRestore saver(&context); 870 871 context.NewPath(); 872 gfxRect drawingRect = nsLayoutUtils::RectToGfxRect( 873 aParams.borderArea, frame->PresContext()->AppUnitsPerDevPixel()); 874 context.SnappedRectangle(drawingRect); 875 sRGBColor overlayColor(0.0f, 0.0f, 0.0f, 0.8f); 876 if (maskUsage.ShouldGenerateMaskLayer()) { 877 overlayColor.r = 1.0f; // red represents css positioned mask. 878 } 879 if (maskUsage.HasSVGClip()) { 880 overlayColor.g = 1.0f; // green represents clip-path:<clip-source>. 881 } 882 if (maskUsage.ShouldApplyBasicShapeOrPath()) { 883 overlayColor.b = 1.0f; // blue represents 884 // clip-path:<basic-shape>||<geometry-box>. 885 } 886 887 context.SetColor(overlayColor); 888 context.Fill(); 889 } 890 891 if (maskUsage.ShouldApplyClipPath() || 892 maskUsage.ShouldApplyBasicShapeOrPath()) { 893 context.PopClip(); 894 } 895 } 896 897 void SVGIntegrationUtils::PaintMaskAndClipPath( 898 const PaintFramesParams& aParams, 899 const std::function<void()>& aPaintChild) { 900 PaintMaskAndClipPathInternal(aParams, aPaintChild); 901 } 902 903 void SVGIntegrationUtils::PaintFilter(const PaintFramesParams& aParams, 904 Span<const StyleFilter> aFilters, 905 const SVGFilterPaintCallback& aCallback) { 906 MOZ_ASSERT(!aParams.builder->IsForGenerateGlyphMask(), 907 "Filter effect is discarded while generating glyph mask."); 908 MOZ_ASSERT(!aFilters.IsEmpty(), 909 "Should not use this method when no filter effect on this frame"); 910 911 nsIFrame* frame = aParams.frame; 912 if (!ValidateSVGFrame(frame)) { 913 return; 914 } 915 916 float opacity = SVGUtils::ComputeOpacity(frame, aParams.handleOpacity); 917 if (opacity == 0.0f) { 918 return; 919 } 920 921 // Properties are added lazily and may have been removed by a restyle, so make 922 // sure all applicable ones are set again. 923 nsIFrame* firstFrame = 924 nsLayoutUtils::FirstContinuationOrIBSplitSibling(frame); 925 // Note: we do not return here for eHasNoRefs since we must still handle any 926 // CSS filter functions. 927 // XXX: Do we need to check for eHasRefsSomeInvalid here given that 928 // nsDisplayFilter::BuildLayer returns nullptr for eHasRefsSomeInvalid? 929 // Or can we just assert !eHasRefsSomeInvalid? 930 nsTArray<SVGFilterFrame*> filterFrames; 931 if (SVGObserverUtils::GetAndObserveFilters(firstFrame, &filterFrames) == 932 SVGObserverUtils::eHasRefsSomeInvalid) { 933 aCallback(aParams.ctx, aParams.imgParams, nullptr, nullptr); 934 return; 935 } 936 937 gfxContext& context = aParams.ctx; 938 939 gfxContextAutoSaveRestore autoSR(&context); 940 EffectOffsets offsets = MoveContextOriginToUserSpace(firstFrame, aParams); 941 942 /* Paint the child and apply filters */ 943 nsRegion dirtyRegion = aParams.dirtyRect - offsets.offsetToBoundingBox; 944 945 FilterInstance::PaintFilteredFrame(frame, aFilters, filterFrames, &context, 946 aCallback, &dirtyRegion, aParams.imgParams, 947 opacity); 948 } 949 950 WrFiltersStatus SVGIntegrationUtils::CreateWebRenderCSSFilters( 951 Span<const StyleFilter> aFilters, nsIFrame* aFrame, 952 WrFiltersHolder& aWrFilters) { 953 // Check if prefs are set to convert the CSS filters to SVG filters and use 954 // the new WebRender SVG filter rendering, rather than the existing CSS filter 955 // support 956 if (StaticPrefs::gfx_webrender_svg_filter_effects() && 957 StaticPrefs:: 958 gfx_webrender_svg_filter_effects_also_convert_css_filters()) { 959 return WrFiltersStatus::BLOB_FALLBACK; 960 } 961 // All CSS filters are supported by WebRender. SVG filters are not fully 962 // supported, those use NS_STYLE_FILTER_URL and are handled separately. 963 964 // If there are too many filters to render, then just pretend that we 965 // succeeded, and don't render any of them. 966 if (aFilters.Length() > 967 StaticPrefs::gfx_webrender_max_filter_ops_per_chain()) { 968 return WrFiltersStatus::DISABLED_FOR_PERFORMANCE; 969 } 970 // Track status so we can do cleanup if unsupported filters are found. 971 WrFiltersStatus status = WrFiltersStatus::CHAIN; 972 aWrFilters.filters.SetCapacity(aFilters.Length()); 973 auto& wrFilters = aWrFilters.filters; 974 for (const StyleFilter& filter : aFilters) { 975 switch (filter.tag) { 976 case StyleFilter::Tag::Brightness: 977 wrFilters.AppendElement( 978 wr::FilterOp::Brightness(filter.AsBrightness())); 979 break; 980 case StyleFilter::Tag::Contrast: 981 wrFilters.AppendElement(wr::FilterOp::Contrast(filter.AsContrast())); 982 break; 983 case StyleFilter::Tag::Grayscale: 984 wrFilters.AppendElement(wr::FilterOp::Grayscale(filter.AsGrayscale())); 985 break; 986 case StyleFilter::Tag::Invert: 987 wrFilters.AppendElement(wr::FilterOp::Invert(filter.AsInvert())); 988 break; 989 case StyleFilter::Tag::Opacity: { 990 float opacity = filter.AsOpacity(); 991 wrFilters.AppendElement(wr::FilterOp::Opacity( 992 wr::PropertyBinding<float>::Value(opacity), opacity)); 993 break; 994 } 995 case StyleFilter::Tag::Saturate: 996 wrFilters.AppendElement(wr::FilterOp::Saturate(filter.AsSaturate())); 997 break; 998 case StyleFilter::Tag::Sepia: 999 wrFilters.AppendElement(wr::FilterOp::Sepia(filter.AsSepia())); 1000 break; 1001 case StyleFilter::Tag::HueRotate: { 1002 wrFilters.AppendElement( 1003 wr::FilterOp::HueRotate(filter.AsHueRotate().ToDegrees())); 1004 break; 1005 } 1006 case StyleFilter::Tag::Blur: { 1007 // TODO(emilio): we should go directly from css pixels -> device pixels. 1008 float appUnitsPerDevPixel = 1009 aFrame->PresContext()->AppUnitsPerDevPixel(); 1010 float radius = NSAppUnitsToFloatPixels(filter.AsBlur().ToAppUnits(), 1011 appUnitsPerDevPixel); 1012 wrFilters.AppendElement(wr::FilterOp::Blur(radius, radius)); 1013 break; 1014 } 1015 case StyleFilter::Tag::DropShadow: { 1016 float appUnitsPerDevPixel = 1017 aFrame->PresContext()->AppUnitsPerDevPixel(); 1018 const StyleSimpleShadow& shadow = filter.AsDropShadow(); 1019 nscolor color = shadow.color.CalcColor(aFrame); 1020 1021 wr::Shadow wrShadow; 1022 wrShadow.offset = { 1023 NSAppUnitsToFloatPixels(shadow.horizontal.ToAppUnits(), 1024 appUnitsPerDevPixel), 1025 NSAppUnitsToFloatPixels(shadow.vertical.ToAppUnits(), 1026 appUnitsPerDevPixel)}; 1027 wrShadow.blur_radius = NSAppUnitsToFloatPixels(shadow.blur.ToAppUnits(), 1028 appUnitsPerDevPixel); 1029 wrShadow.color = {NS_GET_R(color) / 255.0f, NS_GET_G(color) / 255.0f, 1030 NS_GET_B(color) / 255.0f, NS_GET_A(color) / 255.0f}; 1031 wrFilters.AppendElement(wr::FilterOp::DropShadow(wrShadow)); 1032 break; 1033 } 1034 default: 1035 status = WrFiltersStatus::BLOB_FALLBACK; 1036 break; 1037 } 1038 if (status != WrFiltersStatus::CHAIN) { 1039 break; 1040 } 1041 } 1042 if (status != WrFiltersStatus::CHAIN) { 1043 // Clean up the filters holder if we can't render filters this way. 1044 aWrFilters = {}; 1045 } 1046 return status; 1047 } 1048 1049 WrFiltersStatus SVGIntegrationUtils::BuildWebRenderFilters( 1050 nsIFrame* aFilteredFrame, Span<const StyleFilter> aFilters, 1051 StyleFilterType aStyleFilterType, WrFiltersHolder& aWrFilters, 1052 const nsPoint& aOffsetForSVGFilters) { 1053 return FilterInstance::BuildWebRenderFilters(aFilteredFrame, aFilters, 1054 aStyleFilterType, aWrFilters, 1055 aOffsetForSVGFilters); 1056 } 1057 1058 bool SVGIntegrationUtils::CanCreateWebRenderFiltersForFrame(nsIFrame* aFrame) { 1059 WrFiltersHolder wrFilters; 1060 auto filterChain = aFrame->StyleEffects()->mFilters.AsSpan(); 1061 WrFiltersStatus status = 1062 CreateWebRenderCSSFilters(filterChain, aFrame, wrFilters); 1063 if (status == WrFiltersStatus::BLOB_FALLBACK) { 1064 status = BuildWebRenderFilters(aFrame, filterChain, StyleFilterType::Filter, 1065 wrFilters, nsPoint()); 1066 } 1067 return status == WrFiltersStatus::CHAIN || status == WrFiltersStatus::SVGFE; 1068 } 1069 1070 bool SVGIntegrationUtils::UsesSVGEffectsNotSupportedInCompositor( 1071 nsIFrame* aFrame) { 1072 // WebRender supports masks / clip-paths and some filters in the compositor. 1073 if (aFrame->StyleEffects()->HasFilters()) { 1074 return !SVGIntegrationUtils::CanCreateWebRenderFiltersForFrame(aFrame); 1075 } 1076 return false; 1077 } 1078 1079 class PaintFrameCallback : public gfxDrawingCallback { 1080 public: 1081 PaintFrameCallback(nsIFrame* aFrame, const nsSize aPaintServerSize, 1082 const IntSize aRenderSize, 1083 SVGIntegrationUtils::DecodeFlags aFlags) 1084 : mFrame(aFrame), 1085 mPaintServerSize(aPaintServerSize), 1086 mRenderSize(aRenderSize), 1087 mFlags(aFlags) {} 1088 virtual bool operator()(gfxContext* aContext, const gfxRect& aFillRect, 1089 const SamplingFilter aSamplingFilter, 1090 const gfxMatrix& aTransform) override; 1091 1092 private: 1093 nsIFrame* mFrame; 1094 nsSize mPaintServerSize; 1095 IntSize mRenderSize; 1096 SVGIntegrationUtils::DecodeFlags mFlags; 1097 }; 1098 1099 bool PaintFrameCallback::operator()(gfxContext* aContext, 1100 const gfxRect& aFillRect, 1101 const SamplingFilter aSamplingFilter, 1102 const gfxMatrix& aTransform) { 1103 if (mFrame->HasAnyStateBits(NS_FRAME_DRAWING_AS_PAINTSERVER)) { 1104 return false; 1105 } 1106 1107 AutoSetRestorePaintServerState paintServer(mFrame); 1108 1109 aContext->Save(); 1110 1111 // Clip to aFillRect so that we don't paint outside. 1112 aContext->Clip(aFillRect); 1113 1114 gfxMatrix invmatrix = aTransform; 1115 if (!invmatrix.Invert()) { 1116 return false; 1117 } 1118 aContext->Multiply(invmatrix); 1119 1120 // nsLayoutUtils::PaintFrame will anchor its painting at mFrame. But we want 1121 // to have it anchored at the top left corner of the bounding box of all of 1122 // mFrame's continuations. So we add a translation transform. 1123 int32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel(); 1124 nsPoint offset = SVGIntegrationUtils::GetOffsetToBoundingBox(mFrame); 1125 gfxPoint devPxOffset = gfxPoint(offset.x, offset.y) / appUnitsPerDevPixel; 1126 aContext->Multiply(gfxMatrix::Translation(devPxOffset)); 1127 1128 gfxSize paintServerSize = 1129 gfxSize(mPaintServerSize.width, mPaintServerSize.height) / 1130 mFrame->PresContext()->AppUnitsPerDevPixel(); 1131 1132 // nsLayoutUtils::PaintFrame wants to render with paintServerSize, but we 1133 // want it to render with mRenderSize, so we need to set up a scale transform. 1134 gfxFloat scaleX = mRenderSize.width / paintServerSize.width; 1135 gfxFloat scaleY = mRenderSize.height / paintServerSize.height; 1136 aContext->Multiply(gfxMatrix::Scaling(scaleX, scaleY)); 1137 1138 // Draw. 1139 nsRect dirty(-offset.x, -offset.y, mPaintServerSize.width, 1140 mPaintServerSize.height); 1141 1142 using PaintFrameFlags = nsLayoutUtils::PaintFrameFlags; 1143 PaintFrameFlags flags = PaintFrameFlags::InTransform; 1144 if (mFlags.contains(SVGIntegrationUtils::DecodeFlag::SyncDecodeImages)) { 1145 flags |= PaintFrameFlags::SyncDecodeImages; 1146 } 1147 nsLayoutUtils::PaintFrame(aContext, mFrame, dirty, NS_RGBA(0, 0, 0, 0), 1148 nsDisplayListBuilderMode::Painting, flags); 1149 1150 nsIFrame* currentFrame = mFrame; 1151 while ((currentFrame = currentFrame->GetNextContinuation()) != nullptr) { 1152 offset = currentFrame->GetOffsetToCrossDoc(mFrame); 1153 devPxOffset = gfxPoint(offset.x, offset.y) / appUnitsPerDevPixel; 1154 1155 aContext->Save(); 1156 aContext->Multiply(gfxMatrix::Scaling(1 / scaleX, 1 / scaleY)); 1157 aContext->Multiply(gfxMatrix::Translation(devPxOffset)); 1158 aContext->Multiply(gfxMatrix::Scaling(scaleX, scaleY)); 1159 1160 nsLayoutUtils::PaintFrame(aContext, currentFrame, dirty - offset, 1161 NS_RGBA(0, 0, 0, 0), 1162 nsDisplayListBuilderMode::Painting, flags); 1163 1164 aContext->Restore(); 1165 } 1166 1167 aContext->Restore(); 1168 1169 return true; 1170 } 1171 1172 /* static */ 1173 already_AddRefed<gfxDrawable> SVGIntegrationUtils::DrawableFromPaintServer( 1174 nsIFrame* aFrame, nsIFrame* aTarget, const nsSize& aPaintServerSize, 1175 const IntSize& aRenderSize, const DrawTarget* aDrawTarget, 1176 const gfxMatrix& aContextMatrix, DecodeFlags aFlags) { 1177 // aPaintServerSize is the size that would be filled when using 1178 // background-repeat:no-repeat and background-size:auto. For normal background 1179 // images, this would be the intrinsic size of the image; for gradients and 1180 // patterns this would be the whole target frame fill area. 1181 // aRenderSize is what we will be actually filling after accounting for 1182 // background-size. 1183 if (SVGPaintServerFrame* server = do_QueryFrame(aFrame)) { 1184 // aFrame is either a pattern or a gradient. These fill the whole target 1185 // frame by default, so aPaintServerSize is the whole target background fill 1186 // area. 1187 gfxRect overrideBounds(0, 0, aPaintServerSize.width, 1188 aPaintServerSize.height); 1189 overrideBounds.Scale(1.0 / aFrame->PresContext()->AppUnitsPerDevPixel()); 1190 uint32_t imgFlags = imgIContainer::FLAG_ASYNC_NOTIFY; 1191 if (aFlags.contains(DecodeFlag::SyncDecodeImages)) { 1192 imgFlags |= imgIContainer::FLAG_SYNC_DECODE; 1193 } 1194 imgDrawingParams imgParams(imgFlags); 1195 RefPtr<gfxPattern> pattern = server->GetPaintServerPattern( 1196 aTarget, aDrawTarget, aContextMatrix, &nsStyleSVG::mFill, 1.0, 1197 imgParams, &overrideBounds); 1198 1199 if (!pattern) { 1200 return nullptr; 1201 } 1202 1203 // pattern is now set up to fill aPaintServerSize. But we want it to 1204 // fill aRenderSize, so we need to add a scaling transform. 1205 // We couldn't just have set overrideBounds to aRenderSize - it would have 1206 // worked for gradients, but for patterns it would result in a different 1207 // pattern size. 1208 gfxFloat scaleX = overrideBounds.Width() / aRenderSize.width; 1209 gfxFloat scaleY = overrideBounds.Height() / aRenderSize.height; 1210 gfxMatrix scaleMatrix = gfxMatrix::Scaling(scaleX, scaleY); 1211 pattern->SetMatrix(scaleMatrix * pattern->GetMatrix()); 1212 return do_AddRef(new gfxPatternDrawable(pattern, aRenderSize)); 1213 } 1214 1215 if (aFrame->IsSVGFrame() && 1216 !static_cast<ISVGDisplayableFrame*>(do_QueryFrame(aFrame))) { 1217 MOZ_ASSERT_UNREACHABLE( 1218 "We should prevent painting of unpaintable SVG " 1219 "before we get here"); 1220 return nullptr; 1221 } 1222 1223 // We don't want to paint into a surface as long as we don't need to, so we 1224 // set up a drawing callback. 1225 RefPtr<gfxDrawingCallback> cb = 1226 new PaintFrameCallback(aFrame, aPaintServerSize, aRenderSize, aFlags); 1227 return do_AddRef(new gfxCallbackDrawable(cb, aRenderSize)); 1228 } 1229 1230 } // namespace mozilla