Bug 1748835 [wpt PR 32273] - Avoid reftest-wait timeout if ::target-text is unsupport...
[gecko.git] / layout / svg / SVGIntegrationUtils.cpp
blob2dbd1f61270011dea0d361e710a6eaafe3ac4f29
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/. */
7 // Main header first:
8 #include "SVGIntegrationUtils.h"
10 // Keep others in (case-insensitive) order:
11 #include "gfxDrawable.h"
13 #include "Layers.h"
14 #include "nsCSSAnonBoxes.h"
15 #include "nsCSSRendering.h"
16 #include "nsDisplayList.h"
17 #include "nsLayoutUtils.h"
18 #include "gfxContext.h"
19 #include "SVGPaintServerFrame.h"
20 #include "mozilla/gfx/Point.h"
21 #include "mozilla/gfx/gfxVars.h"
22 #include "mozilla/CSSClipPathInstance.h"
23 #include "mozilla/FilterInstance.h"
24 #include "mozilla/StaticPrefs_layers.h"
25 #include "mozilla/SVGClipPathFrame.h"
26 #include "mozilla/SVGObserverUtils.h"
27 #include "mozilla/SVGMaskFrame.h"
28 #include "mozilla/SVGUtils.h"
29 #include "mozilla/Unused.h"
30 #include "mozilla/dom/SVGElement.h"
32 using namespace mozilla::dom;
33 using namespace mozilla::layers;
34 using namespace mozilla::gfx;
35 using namespace mozilla::image;
37 namespace mozilla {
39 /**
40 * This class is used to get the pre-effects ink overflow rect of a frame,
41 * or, in the case of a frame with continuations, to collect the union of the
42 * pre-effects ink overflow rects of all the continuations. The result is
43 * relative to the origin (top left corner of the border box) of the frame, or,
44 * if the frame has continuations, the origin of the _first_ continuation.
46 class PreEffectsInkOverflowCollector : public nsLayoutUtils::BoxCallback {
47 public:
48 /**
49 * If the pre-effects ink overflow rect of the frame being examined
50 * happens to be known, it can be passed in as aCurrentFrame and its
51 * pre-effects ink overflow rect can be passed in as
52 * aCurrentFrameOverflowArea. This is just an optimization to save a
53 * frame property lookup - these arguments are optional.
55 PreEffectsInkOverflowCollector(nsIFrame* aFirstContinuation,
56 nsIFrame* aCurrentFrame,
57 const nsRect& aCurrentFrameOverflowArea,
58 bool aInReflow)
59 : mFirstContinuation(aFirstContinuation),
60 mCurrentFrame(aCurrentFrame),
61 mCurrentFrameOverflowArea(aCurrentFrameOverflowArea),
62 mInReflow(aInReflow) {
63 NS_ASSERTION(!mFirstContinuation->GetPrevContinuation(),
64 "We want the first continuation here");
67 virtual void AddBox(nsIFrame* aFrame) override {
68 nsRect overflow = (aFrame == mCurrentFrame)
69 ? mCurrentFrameOverflowArea
70 : PreEffectsInkOverflowRect(aFrame, mInReflow);
71 mResult.UnionRect(mResult,
72 overflow + aFrame->GetOffsetTo(mFirstContinuation));
75 nsRect GetResult() const { return mResult; }
77 private:
78 static nsRect PreEffectsInkOverflowRect(nsIFrame* aFrame, bool aInReflow) {
79 nsRect* r = aFrame->GetProperty(nsIFrame::PreEffectsBBoxProperty());
80 if (r) {
81 return *r;
84 #ifdef DEBUG
85 // Having PreTransformOverflowAreasProperty cached means
86 // InkOverflowRect() will return post-effect rect, which is not what
87 // we want. This function intentional reports pre-effect rect. But it does
88 // not matter if there is no SVG effect on this frame, since no effect
89 // means post-effect rect matches pre-effect rect.
91 // This function may be called during reflow or painting. We should only
92 // do this check in painting process since the PreEffectsBBoxProperty of
93 // continuations are not set correctly while reflowing.
94 if (SVGIntegrationUtils::UsingOverflowAffectingEffects(aFrame) &&
95 !aInReflow) {
96 OverflowAreas* preTransformOverflows =
97 aFrame->GetProperty(nsIFrame::PreTransformOverflowAreasProperty());
99 MOZ_ASSERT(!preTransformOverflows,
100 "InkOverflowRect() won't return the pre-effects rect!");
102 #endif
103 return aFrame->InkOverflowRectRelativeToSelf();
106 nsIFrame* mFirstContinuation;
107 nsIFrame* mCurrentFrame;
108 const nsRect& mCurrentFrameOverflowArea;
109 nsRect mResult;
110 bool mInReflow;
114 * Gets the union of the pre-effects ink overflow rects of all of a frame's
115 * continuations, in "user space".
117 static nsRect GetPreEffectsInkOverflowUnion(
118 nsIFrame* aFirstContinuation, nsIFrame* aCurrentFrame,
119 const nsRect& aCurrentFramePreEffectsOverflow,
120 const nsPoint& aFirstContinuationToUserSpace, bool aInReflow) {
121 NS_ASSERTION(!aFirstContinuation->GetPrevContinuation(),
122 "Need first continuation here");
123 PreEffectsInkOverflowCollector collector(aFirstContinuation, aCurrentFrame,
124 aCurrentFramePreEffectsOverflow,
125 aInReflow);
126 // Compute union of all overflow areas relative to aFirstContinuation:
127 nsLayoutUtils::GetAllInFlowBoxes(aFirstContinuation, &collector);
128 // Return the result in user space:
129 return collector.GetResult() + aFirstContinuationToUserSpace;
133 * Gets the pre-effects ink overflow rect of aCurrentFrame in "user space".
135 static nsRect GetPreEffectsInkOverflow(
136 nsIFrame* aFirstContinuation, nsIFrame* aCurrentFrame,
137 const nsPoint& aFirstContinuationToUserSpace) {
138 NS_ASSERTION(!aFirstContinuation->GetPrevContinuation(),
139 "Need first continuation here");
140 PreEffectsInkOverflowCollector collector(aFirstContinuation, nullptr,
141 nsRect(), false);
142 // Compute overflow areas of current frame relative to aFirstContinuation:
143 nsLayoutUtils::AddBoxesForFrame(aCurrentFrame, &collector);
144 // Return the result in user space:
145 return collector.GetResult() + aFirstContinuationToUserSpace;
148 bool SVGIntegrationUtils::UsingOverflowAffectingEffects(
149 const nsIFrame* aFrame) {
150 // Currently overflow don't take account of SVG or other non-absolute
151 // positioned clipping, or masking.
152 return aFrame->StyleEffects()->HasFilters();
155 bool SVGIntegrationUtils::UsingEffectsForFrame(const nsIFrame* aFrame) {
156 // Even when SVG display lists are disabled, returning true for SVG frames
157 // does not adversely affect any of our callers. Therefore we don't bother
158 // checking the SDL prefs here, since we don't know if we're being called for
159 // painting or hit-testing anyway.
160 const nsStyleSVGReset* style = aFrame->StyleSVGReset();
161 const nsStyleEffects* effects = aFrame->StyleEffects();
162 // TODO(cbrewster): remove backdrop-filter from this list once it is supported
163 // in preserve-3d cases.
164 return effects->HasFilters() || effects->HasBackdropFilters() ||
165 style->HasClipPath() || style->HasMask();
168 bool SVGIntegrationUtils::UsingMaskOrClipPathForFrame(const nsIFrame* aFrame) {
169 const nsStyleSVGReset* style = aFrame->StyleSVGReset();
170 return style->HasClipPath() || style->HasMask();
173 bool SVGIntegrationUtils::UsingSimpleClipPathForFrame(const nsIFrame* aFrame) {
174 const nsStyleSVGReset* style = aFrame->StyleSVGReset();
175 if (!style->HasClipPath() || style->HasMask()) {
176 return false;
179 const auto& clipPath = style->mClipPath;
180 if (!clipPath.IsShape()) {
181 return false;
184 return !clipPath.AsShape()._0->IsPolygon();
187 nsPoint SVGIntegrationUtils::GetOffsetToBoundingBox(nsIFrame* aFrame) {
188 if (aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
189 // Do NOT call GetAllInFlowRectsUnion for SVG - it will get the
190 // covered region relative to the SVGOuterSVGFrame, which is absolutely
191 // not what we want. SVG frames are always in user space, so they have
192 // no offset adjustment to make.
193 return nsPoint();
196 // The GetAllInFlowRectsUnion() call gets the union of the frame border-box
197 // rects over all continuations, relative to the origin (top-left of the
198 // border box) of its second argument (here, aFrame, the first continuation).
199 return -nsLayoutUtils::GetAllInFlowRectsUnion(aFrame, aFrame).TopLeft();
202 struct EffectOffsets {
203 // The offset between the reference frame and the bounding box of the
204 // target frame in app unit.
205 nsPoint offsetToBoundingBox;
206 // The offset between the reference frame and the bounding box of the
207 // target frame in app unit.
208 nsPoint offsetToUserSpace;
209 // The offset between the reference frame and the bounding box of the
210 // target frame in device unit.
211 gfxPoint offsetToUserSpaceInDevPx;
214 static EffectOffsets ComputeEffectOffset(
215 nsIFrame* aFrame, const SVGIntegrationUtils::PaintFramesParams& aParams) {
216 EffectOffsets result;
218 result.offsetToBoundingBox =
219 aParams.builder->ToReferenceFrame(aFrame) -
220 SVGIntegrationUtils::GetOffsetToBoundingBox(aFrame);
221 if (!aFrame->IsFrameOfType(nsIFrame::eSVG)) {
222 /* Snap the offset if the reference frame is not a SVG frame,
223 * since other frames will be snapped to pixel when rendering. */
224 result.offsetToBoundingBox =
225 nsPoint(aFrame->PresContext()->RoundAppUnitsToNearestDevPixels(
226 result.offsetToBoundingBox.x),
227 aFrame->PresContext()->RoundAppUnitsToNearestDevPixels(
228 result.offsetToBoundingBox.y));
231 // After applying only "aOffsetToBoundingBox", aParams.ctx would have its
232 // origin at the top left corner of frame's bounding box (over all
233 // continuations).
234 // However, SVG painting needs the origin to be located at the origin of the
235 // SVG frame's "user space", i.e. the space in which, for example, the
236 // frame's BBox lives.
237 // SVG geometry frames and foreignObject frames apply their own offsets, so
238 // their position is relative to their user space. So for these frame types,
239 // if we want aParams.ctx to be in user space, we first need to subtract the
240 // frame's position so that SVG painting can later add it again and the
241 // frame is painted in the right place.
242 gfxPoint toUserSpaceGfx =
243 SVGUtils::FrameSpaceInCSSPxToUserSpaceOffset(aFrame);
244 nsPoint toUserSpace =
245 nsPoint(nsPresContext::CSSPixelsToAppUnits(float(toUserSpaceGfx.x)),
246 nsPresContext::CSSPixelsToAppUnits(float(toUserSpaceGfx.y)));
248 result.offsetToUserSpace = result.offsetToBoundingBox - toUserSpace;
250 #ifdef DEBUG
251 bool hasSVGLayout = aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT);
252 NS_ASSERTION(
253 hasSVGLayout || result.offsetToBoundingBox == result.offsetToUserSpace,
254 "For non-SVG frames there shouldn't be any additional offset");
255 #endif
257 result.offsetToUserSpaceInDevPx = nsLayoutUtils::PointToGfxPoint(
258 result.offsetToUserSpace, aFrame->PresContext()->AppUnitsPerDevPixel());
260 return result;
264 * Setup transform matrix of a gfx context by a specific frame. Move the
265 * origin of aParams.ctx to the user space of aFrame.
267 static EffectOffsets MoveContextOriginToUserSpace(
268 nsIFrame* aFrame, const SVGIntegrationUtils::PaintFramesParams& aParams) {
269 EffectOffsets offset = ComputeEffectOffset(aFrame, aParams);
271 aParams.ctx.SetMatrixDouble(aParams.ctx.CurrentMatrixDouble().PreTranslate(
272 offset.offsetToUserSpaceInDevPx));
274 return offset;
277 gfxPoint SVGIntegrationUtils::GetOffsetToUserSpaceInDevPx(
278 nsIFrame* aFrame, const PaintFramesParams& aParams) {
279 nsIFrame* firstFrame =
280 nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
281 EffectOffsets offset = ComputeEffectOffset(firstFrame, aParams);
282 return offset.offsetToUserSpaceInDevPx;
285 /* static */
286 nsSize SVGIntegrationUtils::GetContinuationUnionSize(nsIFrame* aNonSVGFrame) {
287 NS_ASSERTION(!aNonSVGFrame->IsFrameOfType(nsIFrame::eSVG),
288 "SVG frames should not get here");
289 nsIFrame* firstFrame =
290 nsLayoutUtils::FirstContinuationOrIBSplitSibling(aNonSVGFrame);
291 return nsLayoutUtils::GetAllInFlowRectsUnion(firstFrame, firstFrame).Size();
294 /* static */ gfx::Size SVGIntegrationUtils::GetSVGCoordContextForNonSVGFrame(
295 nsIFrame* aNonSVGFrame) {
296 NS_ASSERTION(!aNonSVGFrame->IsFrameOfType(nsIFrame::eSVG),
297 "SVG frames should not get here");
298 nsIFrame* firstFrame =
299 nsLayoutUtils::FirstContinuationOrIBSplitSibling(aNonSVGFrame);
300 nsRect r = nsLayoutUtils::GetAllInFlowRectsUnion(firstFrame, firstFrame);
301 return gfx::Size(nsPresContext::AppUnitsToFloatCSSPixels(r.width),
302 nsPresContext::AppUnitsToFloatCSSPixels(r.height));
305 gfxRect SVGIntegrationUtils::GetSVGBBoxForNonSVGFrame(
306 nsIFrame* aNonSVGFrame, bool aUnionContinuations) {
307 // Except for SVGOuterSVGFrame, we shouldn't be getting here with SVG
308 // frames at all. This function is for elements that are laid out using the
309 // CSS box model rules.
310 NS_ASSERTION(!aNonSVGFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT),
311 "Frames with SVG layout should not get here");
312 MOZ_ASSERT(!aNonSVGFrame->IsFrameOfType(nsIFrame::eSVG) ||
313 aNonSVGFrame->IsSVGOuterSVGFrame());
315 nsIFrame* firstFrame =
316 nsLayoutUtils::FirstContinuationOrIBSplitSibling(aNonSVGFrame);
317 // 'r' is in "user space":
318 nsRect r = (aUnionContinuations)
319 ? GetPreEffectsInkOverflowUnion(
320 firstFrame, nullptr, nsRect(),
321 GetOffsetToBoundingBox(firstFrame), false)
322 : GetPreEffectsInkOverflow(firstFrame, aNonSVGFrame,
323 GetOffsetToBoundingBox(firstFrame));
325 return nsLayoutUtils::RectToGfxRect(r, AppUnitsPerCSSPixel());
328 // XXX Since we're called during reflow, this method is broken for frames with
329 // continuations. When we're called for a frame with continuations, we're
330 // called for each continuation in turn as it's reflowed. However, it isn't
331 // until the last continuation is reflowed that this method's
332 // GetOffsetToBoundingBox() and GetPreEffectsInkOverflowUnion() calls will
333 // obtain valid border boxes for all the continuations. As a result, we'll
334 // end up returning bogus post-filter ink overflow rects for all the prior
335 // continuations. Unfortunately, by the time the last continuation is
336 // reflowed, it's too late to go back and set and propagate the overflow
337 // rects on the previous continuations.
339 // The reason that we need to pass an override bbox to
340 // GetPreEffectsInkOverflowUnion rather than just letting it call into our
341 // GetSVGBBoxForNonSVGFrame method is because we get called by
342 // ComputeEffectsRect when it has been called with
343 // aStoreRectProperties set to false. In this case the pre-effects visual
344 // overflow rect that it has been passed may be different to that stored on
345 // aFrame, resulting in a different bbox.
347 // XXXjwatt The pre-effects ink overflow rect passed to
348 // ComputeEffectsRect won't include continuation overflows, so
349 // for frames with continuation the following filter analysis will likely end
350 // up being carried out with a bbox created as if the frame didn't have
351 // continuations.
353 // XXXjwatt Using aPreEffectsOverflowRect to create the bbox isn't really right
354 // for SVG frames, since for SVG frames the SVG spec defines the bbox to be
355 // something quite different to the pre-effects ink overflow rect. However,
356 // we're essentially calculating an invalidation area here, and using the
357 // pre-effects overflow rect will actually overestimate that area which, while
358 // being a bit wasteful, isn't otherwise a problem.
360 nsRect SVGIntegrationUtils::ComputePostEffectsInkOverflowRect(
361 nsIFrame* aFrame, const nsRect& aPreEffectsOverflowRect) {
362 MOZ_ASSERT(!aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT),
363 "Don't call this on SVG child frames");
365 MOZ_ASSERT(aFrame->StyleEffects()->HasFilters(),
366 "We should only be called if the frame is filtered, since filters "
367 "are the only effect that affects overflow.");
369 nsIFrame* firstFrame =
370 nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
371 // Note: we do not return here for eHasNoRefs since we must still handle any
372 // CSS filter functions.
373 // TODO: We currently pass nullptr instead of an nsTArray* here, but we
374 // actually should get the filter frames and then pass them into
375 // GetPostFilterBounds below! See bug 1494263.
376 // TODO: we should really return an empty rect for eHasRefsSomeInvalid since
377 // in that case we disable painting of the element.
378 if (SVGObserverUtils::GetAndObserveFilters(firstFrame, nullptr) ==
379 SVGObserverUtils::eHasRefsSomeInvalid) {
380 return aPreEffectsOverflowRect;
383 // Create an override bbox - see comment above:
384 nsPoint firstFrameToBoundingBox = GetOffsetToBoundingBox(firstFrame);
385 // overrideBBox is in "user space", in _CSS_ pixels:
386 // XXX Why are we rounding out to pixel boundaries? We don't do that in
387 // GetSVGBBoxForNonSVGFrame, and it doesn't appear to be necessary.
388 gfxRect overrideBBox = nsLayoutUtils::RectToGfxRect(
389 GetPreEffectsInkOverflowUnion(firstFrame, aFrame, aPreEffectsOverflowRect,
390 firstFrameToBoundingBox, true),
391 AppUnitsPerCSSPixel());
392 overrideBBox.RoundOut();
394 nsRect overflowRect =
395 FilterInstance::GetPostFilterBounds(firstFrame, &overrideBBox);
397 // Return overflowRect relative to aFrame, rather than "user space":
398 return overflowRect -
399 (aFrame->GetOffsetTo(firstFrame) + firstFrameToBoundingBox);
402 nsIntRegion SVGIntegrationUtils::AdjustInvalidAreaForSVGEffects(
403 nsIFrame* aFrame, const nsPoint& aToReferenceFrame,
404 const nsIntRegion& aInvalidRegion) {
405 if (aInvalidRegion.IsEmpty()) {
406 return nsIntRect();
409 nsIFrame* firstFrame =
410 nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
412 // If we have any filters to observe then we should have started doing that
413 // during reflow/ComputeFrameEffectsRect, so we use GetFiltersIfObserving
414 // here to avoid needless work (or masking bugs by setting up observers at
415 // the wrong time).
416 if (!aFrame->StyleEffects()->HasFilters() ||
417 SVGObserverUtils::GetFiltersIfObserving(firstFrame, nullptr) ==
418 SVGObserverUtils::eHasRefsSomeInvalid) {
419 return aInvalidRegion;
422 int32_t appUnitsPerDevPixel = aFrame->PresContext()->AppUnitsPerDevPixel();
424 // Convert aInvalidRegion into bounding box frame space in app units:
425 nsPoint toBoundingBox =
426 aFrame->GetOffsetTo(firstFrame) + GetOffsetToBoundingBox(firstFrame);
427 // The initial rect was relative to the reference frame, so we need to
428 // remove that offset to get a rect relative to the current frame.
429 toBoundingBox -= aToReferenceFrame;
430 nsRegion preEffectsRegion =
431 aInvalidRegion.ToAppUnits(appUnitsPerDevPixel).MovedBy(toBoundingBox);
433 // Adjust the dirty area for effects, and shift it back to being relative to
434 // the reference frame.
435 nsRegion result =
436 FilterInstance::GetPostFilterDirtyArea(firstFrame, preEffectsRegion)
437 .MovedBy(-toBoundingBox);
438 // Return the result, in pixels relative to the reference frame.
439 return result.ToOutsidePixels(appUnitsPerDevPixel);
442 nsRect SVGIntegrationUtils::GetRequiredSourceForInvalidArea(
443 nsIFrame* aFrame, const nsRect& aDirtyRect) {
444 nsIFrame* firstFrame =
445 nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
447 // If we have any filters to observe then we should have started doing that
448 // during reflow/ComputeFrameEffectsRect, so we use GetFiltersIfObserving
449 // here to avoid needless work (or masking bugs by setting up observers at
450 // the wrong time).
451 if (!aFrame->StyleEffects()->HasFilters() ||
452 SVGObserverUtils::GetFiltersIfObserving(firstFrame, nullptr) ==
453 SVGObserverUtils::eHasRefsSomeInvalid) {
454 return aDirtyRect;
457 // Convert aDirtyRect into "user space" in app units:
458 nsPoint toUserSpace =
459 aFrame->GetOffsetTo(firstFrame) + GetOffsetToBoundingBox(firstFrame);
460 nsRect postEffectsRect = aDirtyRect + toUserSpace;
462 // Return ther result, relative to aFrame, not in user space:
463 return FilterInstance::GetPreFilterNeededArea(firstFrame, postEffectsRect)
464 .GetBounds() -
465 toUserSpace;
468 bool SVGIntegrationUtils::HitTestFrameForEffects(nsIFrame* aFrame,
469 const nsPoint& aPt) {
470 nsIFrame* firstFrame =
471 nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
472 // Convert aPt to user space:
473 nsPoint toUserSpace;
474 if (aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
475 // XXXmstange Isn't this wrong for svg:use and innerSVG frames?
476 toUserSpace = aFrame->GetPosition();
477 } else {
478 toUserSpace =
479 aFrame->GetOffsetTo(firstFrame) + GetOffsetToBoundingBox(firstFrame);
481 nsPoint pt = aPt + toUserSpace;
482 gfxPoint userSpacePt = gfxPoint(pt.x, pt.y) / AppUnitsPerCSSPixel();
483 return SVGUtils::HitTestClip(firstFrame, userSpacePt);
486 using PaintFramesParams = SVGIntegrationUtils::PaintFramesParams;
489 * Paint css-positioned-mask onto a given target(aMaskDT).
490 * Return value indicates if mask is complete.
492 static bool PaintMaskSurface(const PaintFramesParams& aParams,
493 DrawTarget* aMaskDT, float aOpacity,
494 ComputedStyle* aSC,
495 const nsTArray<SVGMaskFrame*>& aMaskFrames,
496 const Matrix& aMaskSurfaceMatrix,
497 const nsPoint& aOffsetToUserSpace) {
498 MOZ_ASSERT(aMaskFrames.Length() > 0);
499 MOZ_ASSERT(aMaskDT->GetFormat() == SurfaceFormat::A8);
500 MOZ_ASSERT(aOpacity == 1.0 || aMaskFrames.Length() == 1);
502 const nsStyleSVGReset* svgReset = aSC->StyleSVGReset();
503 gfxMatrix cssPxToDevPxMatrix = SVGUtils::GetCSSPxToDevPxMatrix(aParams.frame);
505 nsPresContext* presContext = aParams.frame->PresContext();
506 gfxPoint devPixelOffsetToUserSpace = nsLayoutUtils::PointToGfxPoint(
507 aOffsetToUserSpace, presContext->AppUnitsPerDevPixel());
509 RefPtr<gfxContext> maskContext =
510 gfxContext::CreatePreservingTransformOrNull(aMaskDT);
511 MOZ_ASSERT(maskContext);
513 bool isMaskComplete = true;
515 // Multiple SVG masks interleave with image mask. Paint each layer onto
516 // aMaskDT one at a time.
517 for (int i = aMaskFrames.Length() - 1; i >= 0; i--) {
518 SVGMaskFrame* maskFrame = aMaskFrames[i];
519 CompositionOp compositionOp =
520 (i == int(aMaskFrames.Length() - 1))
521 ? CompositionOp::OP_OVER
522 : nsCSSRendering::GetGFXCompositeMode(
523 svgReset->mMask.mLayers[i].mComposite);
525 // maskFrame != nullptr means we get a SVG mask.
526 // maskFrame == nullptr means we get an image mask.
527 if (maskFrame) {
528 SVGMaskFrame::MaskParams params(
529 maskContext, aParams.frame, cssPxToDevPxMatrix, aOpacity,
530 svgReset->mMask.mLayers[i].mMaskMode, aParams.imgParams);
531 RefPtr<SourceSurface> svgMask = maskFrame->GetMaskForMaskedFrame(params);
532 if (svgMask) {
533 Matrix tmp = aMaskDT->GetTransform();
534 aMaskDT->SetTransform(Matrix());
535 aMaskDT->MaskSurface(ColorPattern(DeviceColor(0.0, 0.0, 0.0, 1.0)),
536 svgMask, Point(0, 0),
537 DrawOptions(1.0, compositionOp));
538 aMaskDT->SetTransform(tmp);
540 } else if (svgReset->mMask.mLayers[i].mImage.IsResolved()) {
541 gfxContextMatrixAutoSaveRestore matRestore(maskContext);
543 maskContext->Multiply(gfxMatrix::Translation(-devPixelOffsetToUserSpace));
544 nsCSSRendering::PaintBGParams params =
545 nsCSSRendering::PaintBGParams::ForSingleLayer(
546 *presContext, aParams.dirtyRect, aParams.borderArea,
547 aParams.frame,
548 aParams.builder->GetBackgroundPaintFlags() |
549 nsCSSRendering::PAINTBG_MASK_IMAGE,
550 i, compositionOp, aOpacity);
552 aParams.imgParams.result &= nsCSSRendering::PaintStyleImageLayerWithSC(
553 params, *maskContext, aSC, *aParams.frame->StyleBorder());
554 } else {
555 isMaskComplete = false;
559 return isMaskComplete;
562 struct MaskPaintResult {
563 RefPtr<SourceSurface> maskSurface;
564 Matrix maskTransform;
565 bool transparentBlackMask;
566 bool opacityApplied;
568 MaskPaintResult() : transparentBlackMask(false), opacityApplied(false) {}
571 static MaskPaintResult CreateAndPaintMaskSurface(
572 const PaintFramesParams& aParams, float aOpacity, ComputedStyle* aSC,
573 const nsTArray<SVGMaskFrame*>& aMaskFrames,
574 const nsPoint& aOffsetToUserSpace) {
575 const nsStyleSVGReset* svgReset = aSC->StyleSVGReset();
576 MOZ_ASSERT(aMaskFrames.Length() > 0);
577 MaskPaintResult paintResult;
579 gfxContext& ctx = aParams.ctx;
581 // Optimization for single SVG mask.
582 if (((aMaskFrames.Length() == 1) && aMaskFrames[0])) {
583 gfxMatrix cssPxToDevPxMatrix =
584 SVGUtils::GetCSSPxToDevPxMatrix(aParams.frame);
585 paintResult.opacityApplied = true;
586 SVGMaskFrame::MaskParams params(
587 &ctx, aParams.frame, cssPxToDevPxMatrix, aOpacity,
588 svgReset->mMask.mLayers[0].mMaskMode, aParams.imgParams);
589 paintResult.maskSurface = aMaskFrames[0]->GetMaskForMaskedFrame(params);
590 paintResult.maskTransform = ctx.CurrentMatrix();
591 paintResult.maskTransform.Invert();
592 if (!paintResult.maskSurface) {
593 paintResult.transparentBlackMask = true;
596 return paintResult;
599 const Rect& maskSurfaceRect = aParams.maskRect.valueOr(Rect());
600 if (aParams.maskRect.isSome() && maskSurfaceRect.IsEmpty()) {
601 // XXX: Is this ever true?
602 paintResult.transparentBlackMask = true;
603 return paintResult;
606 RefPtr<DrawTarget> maskDT = ctx.GetDrawTarget()->CreateClippedDrawTarget(
607 maskSurfaceRect, SurfaceFormat::A8);
608 if (!maskDT || !maskDT->IsValid()) {
609 return paintResult;
612 // We can paint mask along with opacity only if
613 // 1. There is only one mask, or
614 // 2. No overlap among masks.
615 // Collision detect in #2 is not that trivial, we only accept #1 here.
616 paintResult.opacityApplied = (aMaskFrames.Length() == 1);
618 // Set context's matrix on maskContext, offset by the maskSurfaceRect's
619 // position. This makes sure that we combine the masks in device space.
620 Matrix maskSurfaceMatrix = ctx.CurrentMatrix();
622 bool isMaskComplete = PaintMaskSurface(
623 aParams, maskDT, paintResult.opacityApplied ? aOpacity : 1.0, aSC,
624 aMaskFrames, maskSurfaceMatrix, aOffsetToUserSpace);
626 if (!isMaskComplete ||
627 (aParams.imgParams.result != ImgDrawResult::SUCCESS &&
628 aParams.imgParams.result != ImgDrawResult::SUCCESS_NOT_COMPLETE &&
629 aParams.imgParams.result != ImgDrawResult::WRONG_SIZE)) {
630 // Now we know the status of mask resource since we used it while painting.
631 // According to the return value of PaintMaskSurface, we know whether mask
632 // resource is resolvable or not.
634 // For a HTML doc:
635 // According to css-masking spec, always create a mask surface when
636 // we have any item in maskFrame even if all of those items are
637 // non-resolvable <mask-sources> or <images>.
638 // Set paintResult.transparentBlackMask as true, the caller should stop
639 // painting masked content as if this mask is a transparent black one.
640 // For a SVG doc:
641 // SVG 1.1 say that if we fail to resolve a mask, we should draw the
642 // object unmasked.
643 // Left paintResult.maskSurface empty, the caller should paint all
644 // masked content as if this mask is an opaque white one(no mask).
645 paintResult.transparentBlackMask =
646 !aParams.frame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT);
648 MOZ_ASSERT(!paintResult.maskSurface);
649 return paintResult;
652 paintResult.maskTransform = maskSurfaceMatrix;
653 if (!paintResult.maskTransform.Invert()) {
654 return paintResult;
657 paintResult.maskSurface = maskDT->Snapshot();
658 return paintResult;
661 static bool ValidateSVGFrame(nsIFrame* aFrame) {
662 #ifdef DEBUG
663 NS_ASSERTION(!aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT) ||
664 (NS_SVGDisplayListPaintingEnabled() &&
665 !aFrame->HasAnyStateBits(NS_FRAME_IS_NONDISPLAY)),
666 "Should not use SVGIntegrationUtils on this SVG frame");
667 #endif
669 bool hasSVGLayout = aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT);
670 if (hasSVGLayout) {
671 #ifdef DEBUG
672 ISVGDisplayableFrame* svgFrame = do_QueryFrame(aFrame);
673 MOZ_ASSERT(svgFrame && aFrame->GetContent()->IsSVGElement(),
674 "A non-SVG frame carries NS_FRAME_SVG_LAYOUT flag?");
675 #endif
677 const nsIContent* content = aFrame->GetContent();
678 if (!static_cast<const SVGElement*>(content)->HasValidDimensions()) {
679 // The SVG spec says not to draw _anything_
680 return false;
684 return true;
687 class AutoPopGroup {
688 public:
689 AutoPopGroup() : mContext(nullptr) {}
691 ~AutoPopGroup() {
692 if (mContext) {
693 mContext->PopGroupAndBlend();
697 void SetContext(gfxContext* aContext) { mContext = aContext; }
699 private:
700 gfxContext* mContext;
703 bool SVGIntegrationUtils::PaintMask(const PaintFramesParams& aParams,
704 bool& aOutIsMaskComplete) {
705 aOutIsMaskComplete = true;
707 SVGUtils::MaskUsage maskUsage;
708 SVGUtils::DetermineMaskUsage(aParams.frame, aParams.handleOpacity, maskUsage);
709 if (!maskUsage.shouldDoSomething()) {
710 return false;
713 nsIFrame* frame = aParams.frame;
714 if (!ValidateSVGFrame(frame)) {
715 return false;
718 gfxContext& ctx = aParams.ctx;
719 RefPtr<DrawTarget> maskTarget = ctx.GetDrawTarget();
721 if (maskUsage.shouldGenerateMaskLayer &&
722 (maskUsage.shouldGenerateClipMaskLayer ||
723 maskUsage.shouldApplyClipPath)) {
724 // We will paint both mask of positioned mask and clip-path into
725 // maskTarget.
727 // Create one extra draw target for drawing positioned mask, so that we do
728 // not have to copy the content of maskTarget before painting
729 // clip-path into it.
730 if (!maskTarget->CanCreateSimilarDrawTarget(maskTarget->GetSize(),
731 SurfaceFormat::A8)) {
732 return false;
734 maskTarget = maskTarget->CreateSimilarDrawTarget(maskTarget->GetSize(),
735 SurfaceFormat::A8);
738 nsIFrame* firstFrame =
739 nsLayoutUtils::FirstContinuationOrIBSplitSibling(frame);
740 nsTArray<SVGMaskFrame*> maskFrames;
741 // XXX check return value?
742 SVGObserverUtils::GetAndObserveMasks(firstFrame, &maskFrames);
744 AutoPopGroup autoPop;
745 bool shouldPushOpacity =
746 (maskUsage.opacity != 1.0) && (maskFrames.Length() != 1);
747 if (shouldPushOpacity) {
748 ctx.PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, maskUsage.opacity);
749 autoPop.SetContext(&ctx);
752 gfxContextMatrixAutoSaveRestore matSR;
754 // Paint clip-path-basic-shape onto ctx
755 gfxContextAutoSaveRestore basicShapeSR;
756 if (maskUsage.shouldApplyBasicShapeOrPath) {
757 matSR.SetContext(&ctx);
759 MoveContextOriginToUserSpace(firstFrame, aParams);
761 basicShapeSR.SetContext(&ctx);
762 CSSClipPathInstance::ApplyBasicShapeOrPathClip(
763 ctx, frame, SVGUtils::GetCSSPxToDevPxMatrix(frame));
764 if (!maskUsage.shouldGenerateMaskLayer) {
765 // Only have basic-shape clip-path effect. Fill clipped region by
766 // opaque white.
767 ctx.SetDeviceColor(DeviceColor::MaskOpaqueWhite());
768 ctx.Fill();
770 return true;
774 // Paint mask onto ctx.
775 if (maskUsage.shouldGenerateMaskLayer) {
776 matSR.Restore();
777 matSR.SetContext(&ctx);
779 EffectOffsets offsets = MoveContextOriginToUserSpace(frame, aParams);
780 aOutIsMaskComplete = PaintMaskSurface(
781 aParams, maskTarget, shouldPushOpacity ? 1.0 : maskUsage.opacity,
782 firstFrame->Style(), maskFrames, ctx.CurrentMatrix(),
783 offsets.offsetToUserSpace);
786 // Paint clip-path onto ctx.
787 if (maskUsage.shouldGenerateClipMaskLayer || maskUsage.shouldApplyClipPath) {
788 matSR.Restore();
789 matSR.SetContext(&ctx);
791 MoveContextOriginToUserSpace(firstFrame, aParams);
792 Matrix clipMaskTransform;
793 gfxMatrix cssPxToDevPxMatrix = SVGUtils::GetCSSPxToDevPxMatrix(frame);
795 SVGClipPathFrame* clipPathFrame;
796 // XXX check return value?
797 SVGObserverUtils::GetAndObserveClipPath(firstFrame, &clipPathFrame);
798 RefPtr<SourceSurface> maskSurface =
799 maskUsage.shouldGenerateMaskLayer ? maskTarget->Snapshot() : nullptr;
800 clipPathFrame->PaintClipMask(ctx, frame, cssPxToDevPxMatrix, maskSurface,
801 ctx.CurrentMatrix());
804 return true;
807 template <class T>
808 void PaintMaskAndClipPathInternal(const PaintFramesParams& aParams,
809 const T& aPaintChild) {
810 MOZ_ASSERT(SVGIntegrationUtils::UsingMaskOrClipPathForFrame(aParams.frame),
811 "Should not use this method when no mask or clipPath effect"
812 "on this frame");
814 /* SVG defines the following rendering model:
816 * 1. Render geometry
817 * 2. Apply filter
818 * 3. Apply clipping, masking, group opacity
820 * We handle #3 here and perform a couple of optimizations:
822 * + Use cairo's clipPath when representable natively (single object
823 * clip region).
825 * + Merge opacity and masking if both used together.
827 nsIFrame* frame = aParams.frame;
828 if (!ValidateSVGFrame(frame)) {
829 return;
832 SVGUtils::MaskUsage maskUsage;
833 SVGUtils::DetermineMaskUsage(aParams.frame, aParams.handleOpacity, maskUsage);
835 if (maskUsage.opacity == 0.0f) {
836 return;
839 gfxContext& context = aParams.ctx;
840 gfxContextMatrixAutoSaveRestore matrixAutoSaveRestore(&context);
842 nsIFrame* firstFrame =
843 nsLayoutUtils::FirstContinuationOrIBSplitSibling(frame);
845 SVGClipPathFrame* clipPathFrame;
846 // XXX check return value?
847 SVGObserverUtils::GetAndObserveClipPath(firstFrame, &clipPathFrame);
849 nsTArray<SVGMaskFrame*> maskFrames;
850 // XXX check return value?
851 SVGObserverUtils::GetAndObserveMasks(firstFrame, &maskFrames);
853 gfxMatrix cssPxToDevPxMatrix = SVGUtils::GetCSSPxToDevPxMatrix(frame);
855 bool shouldGenerateMask =
856 (maskUsage.opacity != 1.0f || maskUsage.shouldGenerateClipMaskLayer ||
857 maskUsage.shouldGenerateMaskLayer);
858 bool shouldPushMask = false;
860 /* Check if we need to do additional operations on this child's
861 * rendering, which necessitates rendering into another surface. */
862 if (shouldGenerateMask) {
863 gfxContextMatrixAutoSaveRestore matSR;
865 Matrix maskTransform;
866 RefPtr<SourceSurface> maskSurface;
867 bool opacityApplied = false;
869 if (maskUsage.shouldGenerateMaskLayer) {
870 matSR.SetContext(&context);
872 // For css-mask, we want to generate a mask for each continuation frame,
873 // so we setup context matrix by the position of the current frame,
874 // instead of the first continuation frame.
875 EffectOffsets offsets = MoveContextOriginToUserSpace(frame, aParams);
876 MaskPaintResult paintResult = CreateAndPaintMaskSurface(
877 aParams, maskUsage.opacity, firstFrame->Style(), maskFrames,
878 offsets.offsetToUserSpace);
880 if (paintResult.transparentBlackMask) {
881 return;
884 maskSurface = paintResult.maskSurface;
885 if (maskSurface) {
886 shouldPushMask = true;
887 // We want the mask to be untransformed so use the inverse of the
888 // current transform as the maskTransform to compensate.
889 maskTransform = context.CurrentMatrix();
890 maskTransform.Invert();
892 opacityApplied = paintResult.opacityApplied;
896 if (maskUsage.shouldGenerateClipMaskLayer) {
897 matSR.Restore();
898 matSR.SetContext(&context);
900 MoveContextOriginToUserSpace(firstFrame, aParams);
901 RefPtr<SourceSurface> clipMaskSurface = clipPathFrame->GetClipMask(
902 context, frame, cssPxToDevPxMatrix, maskSurface, maskTransform);
904 if (clipMaskSurface) {
905 maskSurface = clipMaskSurface;
906 // We want the mask to be untransformed so use the inverse of the
907 // current transform as the maskTransform to compensate.
908 maskTransform = context.CurrentMatrix();
909 maskTransform.Invert();
910 } else {
911 // Either entire surface is clipped out, or gfx buffer allocation
912 // failure in SVGClipPathFrame::GetClipMask.
913 return;
916 shouldPushMask = true;
919 // opacity != 1.0f.
920 if (!maskUsage.shouldGenerateClipMaskLayer &&
921 !maskUsage.shouldGenerateMaskLayer) {
922 MOZ_ASSERT(maskUsage.opacity != 1.0f);
924 matSR.SetContext(&context);
925 MoveContextOriginToUserSpace(firstFrame, aParams);
926 shouldPushMask = true;
929 if (shouldPushMask) {
930 context.PushGroupForBlendBack(gfxContentType::COLOR_ALPHA,
931 opacityApplied ? 1.0 : maskUsage.opacity,
932 maskSurface, maskTransform);
936 /* If this frame has only a trivial clipPath, set up cairo's clipping now so
937 * we can just do normal painting and get it clipped appropriately.
939 if (maskUsage.shouldApplyClipPath || maskUsage.shouldApplyBasicShapeOrPath) {
940 gfxContextMatrixAutoSaveRestore matSR(&context);
942 MoveContextOriginToUserSpace(firstFrame, aParams);
944 MOZ_ASSERT(!maskUsage.shouldApplyClipPath ||
945 !maskUsage.shouldApplyBasicShapeOrPath);
946 if (maskUsage.shouldApplyClipPath) {
947 clipPathFrame->ApplyClipPath(context, frame, cssPxToDevPxMatrix);
948 } else {
949 CSSClipPathInstance::ApplyBasicShapeOrPathClip(context, frame,
950 cssPxToDevPxMatrix);
954 /* Paint the child */
955 context.SetMatrix(matrixAutoSaveRestore.Matrix());
956 aPaintChild();
958 if (StaticPrefs::layers_draw_mask_debug()) {
959 gfxContextAutoSaveRestore saver(&context);
961 context.NewPath();
962 gfxRect drawingRect = nsLayoutUtils::RectToGfxRect(
963 aParams.borderArea, frame->PresContext()->AppUnitsPerDevPixel());
964 context.SnappedRectangle(drawingRect);
965 sRGBColor overlayColor(0.0f, 0.0f, 0.0f, 0.8f);
966 if (maskUsage.shouldGenerateMaskLayer) {
967 overlayColor.r = 1.0f; // red represents css positioned mask.
969 if (maskUsage.shouldApplyClipPath ||
970 maskUsage.shouldGenerateClipMaskLayer) {
971 overlayColor.g = 1.0f; // green represents clip-path:<clip-source>.
973 if (maskUsage.shouldApplyBasicShapeOrPath) {
974 overlayColor.b = 1.0f; // blue represents
975 // clip-path:<basic-shape>||<geometry-box>.
978 context.SetColor(overlayColor);
979 context.Fill();
982 if (maskUsage.shouldApplyClipPath || maskUsage.shouldApplyBasicShapeOrPath) {
983 context.PopClip();
986 if (shouldPushMask) {
987 context.PopGroupAndBlend();
991 void SVGIntegrationUtils::PaintMaskAndClipPath(
992 const PaintFramesParams& aParams,
993 const std::function<void()>& aPaintChild) {
994 PaintMaskAndClipPathInternal(aParams, aPaintChild);
997 void SVGIntegrationUtils::PaintFilter(const PaintFramesParams& aParams,
998 const SVGFilterPaintCallback& aCallback) {
999 MOZ_ASSERT(!aParams.builder->IsForGenerateGlyphMask(),
1000 "Filter effect is discarded while generating glyph mask.");
1001 MOZ_ASSERT(aParams.frame->StyleEffects()->HasFilters(),
1002 "Should not use this method when no filter effect on this frame");
1004 nsIFrame* frame = aParams.frame;
1005 if (!ValidateSVGFrame(frame)) {
1006 return;
1009 float opacity = SVGUtils::ComputeOpacity(frame, aParams.handleOpacity);
1010 if (opacity == 0.0f) {
1011 return;
1014 /* Properties are added lazily and may have been removed by a restyle,
1015 so make sure all applicable ones are set again. */
1016 nsIFrame* firstFrame =
1017 nsLayoutUtils::FirstContinuationOrIBSplitSibling(frame);
1018 // Note: we do not return here for eHasNoRefs since we must still handle any
1019 // CSS filter functions.
1020 // TODO: We currently pass nullptr instead of an nsTArray* here, but we
1021 // actually should get the filter frames and then pass them into
1022 // PaintFilteredFrame below! See bug 1494263.
1023 // XXX: Do we need to check for eHasRefsSomeInvalid here given that
1024 // nsDisplayFilter::BuildLayer returns nullptr for eHasRefsSomeInvalid?
1025 // Or can we just assert !eHasRefsSomeInvalid?
1026 if (SVGObserverUtils::GetAndObserveFilters(firstFrame, nullptr) ==
1027 SVGObserverUtils::eHasRefsSomeInvalid) {
1028 return;
1031 gfxContext& context = aParams.ctx;
1033 gfxContextAutoSaveRestore autoSR(&context);
1034 EffectOffsets offsets = MoveContextOriginToUserSpace(firstFrame, aParams);
1036 /* Paint the child and apply filters */
1037 nsRegion dirtyRegion = aParams.dirtyRect - offsets.offsetToBoundingBox;
1039 FilterInstance::PaintFilteredFrame(frame, &context, aCallback, &dirtyRegion,
1040 aParams.imgParams, opacity);
1043 bool SVGIntegrationUtils::CreateWebRenderCSSFilters(
1044 Span<const StyleFilter> aFilters, nsIFrame* aFrame,
1045 WrFiltersHolder& aWrFilters) {
1046 // All CSS filters are supported by WebRender. SVG filters are not fully
1047 // supported, those use NS_STYLE_FILTER_URL and are handled separately.
1049 // If there are too many filters to render, then just pretend that we
1050 // succeeded, and don't render any of them.
1051 if (aFilters.Length() >
1052 StaticPrefs::gfx_webrender_max_filter_ops_per_chain()) {
1053 return true;
1055 aWrFilters.filters.SetCapacity(aFilters.Length());
1056 auto& wrFilters = aWrFilters.filters;
1057 for (const StyleFilter& filter : aFilters) {
1058 switch (filter.tag) {
1059 case StyleFilter::Tag::Brightness:
1060 wrFilters.AppendElement(
1061 wr::FilterOp::Brightness(filter.AsBrightness()));
1062 break;
1063 case StyleFilter::Tag::Contrast:
1064 wrFilters.AppendElement(wr::FilterOp::Contrast(filter.AsContrast()));
1065 break;
1066 case StyleFilter::Tag::Grayscale:
1067 wrFilters.AppendElement(wr::FilterOp::Grayscale(filter.AsGrayscale()));
1068 break;
1069 case StyleFilter::Tag::Invert:
1070 wrFilters.AppendElement(wr::FilterOp::Invert(filter.AsInvert()));
1071 break;
1072 case StyleFilter::Tag::Opacity: {
1073 float opacity = filter.AsOpacity();
1074 wrFilters.AppendElement(wr::FilterOp::Opacity(
1075 wr::PropertyBinding<float>::Value(opacity), opacity));
1076 break;
1078 case StyleFilter::Tag::Saturate:
1079 wrFilters.AppendElement(wr::FilterOp::Saturate(filter.AsSaturate()));
1080 break;
1081 case StyleFilter::Tag::Sepia:
1082 wrFilters.AppendElement(wr::FilterOp::Sepia(filter.AsSepia()));
1083 break;
1084 case StyleFilter::Tag::HueRotate: {
1085 wrFilters.AppendElement(
1086 wr::FilterOp::HueRotate(filter.AsHueRotate().ToDegrees()));
1087 break;
1089 case StyleFilter::Tag::Blur: {
1090 // TODO(emilio): we should go directly from css pixels -> device pixels.
1091 float appUnitsPerDevPixel =
1092 aFrame->PresContext()->AppUnitsPerDevPixel();
1093 float radius = NSAppUnitsToFloatPixels(filter.AsBlur().ToAppUnits(),
1094 appUnitsPerDevPixel);
1095 wrFilters.AppendElement(wr::FilterOp::Blur(radius, radius));
1096 break;
1098 case StyleFilter::Tag::DropShadow: {
1099 float appUnitsPerDevPixel =
1100 aFrame->PresContext()->AppUnitsPerDevPixel();
1101 const StyleSimpleShadow& shadow = filter.AsDropShadow();
1102 nscolor color = shadow.color.CalcColor(aFrame);
1104 wr::Shadow wrShadow;
1105 wrShadow.offset = {
1106 NSAppUnitsToFloatPixels(shadow.horizontal.ToAppUnits(),
1107 appUnitsPerDevPixel),
1108 NSAppUnitsToFloatPixels(shadow.vertical.ToAppUnits(),
1109 appUnitsPerDevPixel)};
1110 wrShadow.blur_radius = NSAppUnitsToFloatPixels(shadow.blur.ToAppUnits(),
1111 appUnitsPerDevPixel);
1112 wrShadow.color = {NS_GET_R(color) / 255.0f, NS_GET_G(color) / 255.0f,
1113 NS_GET_B(color) / 255.0f, NS_GET_A(color) / 255.0f};
1114 wrFilters.AppendElement(wr::FilterOp::DropShadow(wrShadow));
1115 break;
1117 default:
1118 return false;
1122 return true;
1125 bool SVGIntegrationUtils::BuildWebRenderFilters(
1126 nsIFrame* aFilteredFrame, Span<const StyleFilter> aFilters,
1127 WrFiltersHolder& aWrFilters, Maybe<nsRect>& aPostFilterClip,
1128 bool& aInitialized) {
1129 return FilterInstance::BuildWebRenderFilters(
1130 aFilteredFrame, aFilters, aWrFilters, aPostFilterClip, aInitialized);
1133 bool SVGIntegrationUtils::CanCreateWebRenderFiltersForFrame(nsIFrame* aFrame) {
1134 WrFiltersHolder wrFilters;
1135 Maybe<nsRect> filterClip;
1136 auto filterChain = aFrame->StyleEffects()->mFilters.AsSpan();
1137 bool initialized = true;
1138 return CreateWebRenderCSSFilters(filterChain, aFrame, wrFilters) ||
1139 BuildWebRenderFilters(aFrame, filterChain, wrFilters, filterClip,
1140 initialized);
1143 bool SVGIntegrationUtils::UsesSVGEffectsNotSupportedInCompositor(
1144 nsIFrame* aFrame) {
1145 // WebRender supports masks / clip-paths and some filters in the compositor.
1146 // Non-WebRender doesn't support any SVG effects in the compositor.
1147 if (aFrame->StyleEffects()->HasFilters()) {
1148 return !gfx::gfxVars::UseWebRender() ||
1149 !SVGIntegrationUtils::CanCreateWebRenderFiltersForFrame(aFrame);
1151 if (SVGIntegrationUtils::UsingMaskOrClipPathForFrame(aFrame)) {
1152 return !gfx::gfxVars::UseWebRender();
1154 return false;
1157 class PaintFrameCallback : public gfxDrawingCallback {
1158 public:
1159 PaintFrameCallback(nsIFrame* aFrame, const nsSize aPaintServerSize,
1160 const IntSize aRenderSize, uint32_t aFlags)
1161 : mFrame(aFrame),
1162 mPaintServerSize(aPaintServerSize),
1163 mRenderSize(aRenderSize),
1164 mFlags(aFlags) {}
1165 virtual bool operator()(gfxContext* aContext, const gfxRect& aFillRect,
1166 const SamplingFilter aSamplingFilter,
1167 const gfxMatrix& aTransform) override;
1169 private:
1170 nsIFrame* mFrame;
1171 nsSize mPaintServerSize;
1172 IntSize mRenderSize;
1173 uint32_t mFlags;
1176 bool PaintFrameCallback::operator()(gfxContext* aContext,
1177 const gfxRect& aFillRect,
1178 const SamplingFilter aSamplingFilter,
1179 const gfxMatrix& aTransform) {
1180 if (mFrame->HasAnyStateBits(NS_FRAME_DRAWING_AS_PAINTSERVER)) {
1181 return false;
1184 AutoSetRestorePaintServerState paintServer(mFrame);
1186 aContext->Save();
1188 // Clip to aFillRect so that we don't paint outside.
1189 aContext->NewPath();
1190 aContext->Rectangle(aFillRect);
1191 aContext->Clip();
1193 gfxMatrix invmatrix = aTransform;
1194 if (!invmatrix.Invert()) {
1195 return false;
1197 aContext->Multiply(invmatrix);
1199 // nsLayoutUtils::PaintFrame will anchor its painting at mFrame. But we want
1200 // to have it anchored at the top left corner of the bounding box of all of
1201 // mFrame's continuations. So we add a translation transform.
1202 int32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
1203 nsPoint offset = SVGIntegrationUtils::GetOffsetToBoundingBox(mFrame);
1204 gfxPoint devPxOffset = gfxPoint(offset.x, offset.y) / appUnitsPerDevPixel;
1205 aContext->Multiply(gfxMatrix::Translation(devPxOffset));
1207 gfxSize paintServerSize =
1208 gfxSize(mPaintServerSize.width, mPaintServerSize.height) /
1209 mFrame->PresContext()->AppUnitsPerDevPixel();
1211 // nsLayoutUtils::PaintFrame wants to render with paintServerSize, but we
1212 // want it to render with mRenderSize, so we need to set up a scale transform.
1213 gfxFloat scaleX = mRenderSize.width / paintServerSize.width;
1214 gfxFloat scaleY = mRenderSize.height / paintServerSize.height;
1215 aContext->Multiply(gfxMatrix::Scaling(scaleX, scaleY));
1217 // Draw.
1218 nsRect dirty(-offset.x, -offset.y, mPaintServerSize.width,
1219 mPaintServerSize.height);
1221 using PaintFrameFlags = nsLayoutUtils::PaintFrameFlags;
1222 PaintFrameFlags flags = PaintFrameFlags::InTransform;
1223 if (mFlags & SVGIntegrationUtils::FLAG_SYNC_DECODE_IMAGES) {
1224 flags |= PaintFrameFlags::SyncDecodeImages;
1226 nsLayoutUtils::PaintFrame(aContext, mFrame, dirty, NS_RGBA(0, 0, 0, 0),
1227 nsDisplayListBuilderMode::Painting, flags);
1229 nsIFrame* currentFrame = mFrame;
1230 while ((currentFrame = currentFrame->GetNextContinuation()) != nullptr) {
1231 offset = currentFrame->GetOffsetToCrossDoc(mFrame);
1232 devPxOffset = gfxPoint(offset.x, offset.y) / appUnitsPerDevPixel;
1234 aContext->Save();
1235 aContext->Multiply(gfxMatrix::Scaling(1 / scaleX, 1 / scaleY));
1236 aContext->Multiply(gfxMatrix::Translation(devPxOffset));
1237 aContext->Multiply(gfxMatrix::Scaling(scaleX, scaleY));
1239 nsLayoutUtils::PaintFrame(aContext, currentFrame, dirty - offset,
1240 NS_RGBA(0, 0, 0, 0),
1241 nsDisplayListBuilderMode::Painting, flags);
1243 aContext->Restore();
1246 aContext->Restore();
1248 return true;
1251 /* static */
1252 already_AddRefed<gfxDrawable> SVGIntegrationUtils::DrawableFromPaintServer(
1253 nsIFrame* aFrame, nsIFrame* aTarget, const nsSize& aPaintServerSize,
1254 const IntSize& aRenderSize, const DrawTarget* aDrawTarget,
1255 const gfxMatrix& aContextMatrix, uint32_t aFlags) {
1256 // aPaintServerSize is the size that would be filled when using
1257 // background-repeat:no-repeat and background-size:auto. For normal background
1258 // images, this would be the intrinsic size of the image; for gradients and
1259 // patterns this would be the whole target frame fill area.
1260 // aRenderSize is what we will be actually filling after accounting for
1261 // background-size.
1262 if (SVGPaintServerFrame* server = do_QueryFrame(aFrame)) {
1263 // aFrame is either a pattern or a gradient. These fill the whole target
1264 // frame by default, so aPaintServerSize is the whole target background fill
1265 // area.
1266 gfxRect overrideBounds(0, 0, aPaintServerSize.width,
1267 aPaintServerSize.height);
1268 overrideBounds.Scale(1.0 / aFrame->PresContext()->AppUnitsPerDevPixel());
1269 imgDrawingParams imgParams(aFlags);
1270 RefPtr<gfxPattern> pattern = server->GetPaintServerPattern(
1271 aTarget, aDrawTarget, aContextMatrix, &nsStyleSVG::mFill, 1.0,
1272 imgParams, &overrideBounds);
1274 if (!pattern) {
1275 return nullptr;
1278 // pattern is now set up to fill aPaintServerSize. But we want it to
1279 // fill aRenderSize, so we need to add a scaling transform.
1280 // We couldn't just have set overrideBounds to aRenderSize - it would have
1281 // worked for gradients, but for patterns it would result in a different
1282 // pattern size.
1283 gfxFloat scaleX = overrideBounds.Width() / aRenderSize.width;
1284 gfxFloat scaleY = overrideBounds.Height() / aRenderSize.height;
1285 gfxMatrix scaleMatrix = gfxMatrix::Scaling(scaleX, scaleY);
1286 pattern->SetMatrix(scaleMatrix * pattern->GetMatrix());
1287 RefPtr<gfxDrawable> drawable = new gfxPatternDrawable(pattern, aRenderSize);
1288 return drawable.forget();
1291 if (aFrame->IsFrameOfType(nsIFrame::eSVG) &&
1292 !static_cast<ISVGDisplayableFrame*>(do_QueryFrame(aFrame))) {
1293 MOZ_ASSERT_UNREACHABLE(
1294 "We should prevent painting of unpaintable SVG "
1295 "before we get here");
1296 return nullptr;
1299 // We don't want to paint into a surface as long as we don't need to, so we
1300 // set up a drawing callback.
1301 RefPtr<gfxDrawingCallback> cb =
1302 new PaintFrameCallback(aFrame, aPaintServerSize, aRenderSize, aFlags);
1303 RefPtr<gfxDrawable> drawable = new gfxCallbackDrawable(cb, aRenderSize);
1304 return drawable.forget();
1307 } // namespace mozilla