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 "gfxDrawable.h"
12
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"
31
32 using namespace mozilla::dom;
33 using namespace mozilla::layers;
34 using namespace mozilla::gfx;
35 using namespace mozilla::image;
36
37 namespace mozilla {
38
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.
45 */
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.
54 */
PreEffectsInkOverflowCollector(nsIFrame * aFirstContinuation,nsIFrame * aCurrentFrame,const nsRect & aCurrentFrameOverflowArea,bool aInReflow)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");
65 }
66
AddBox(nsIFrame * aFrame)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));
73 }
74
GetResult() const75 nsRect GetResult() const { return mResult; }
76
77 private:
PreEffectsInkOverflowRect(nsIFrame * aFrame,bool aInReflow)78 static nsRect PreEffectsInkOverflowRect(nsIFrame* aFrame, bool aInReflow) {
79 nsRect* r = aFrame->GetProperty(nsIFrame::PreEffectsBBoxProperty());
80 if (r) {
81 return *r;
82 }
83
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.
90 //
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());
98
99 MOZ_ASSERT(!preTransformOverflows,
100 "InkOverflowRect() won't return the pre-effects rect!");
101 }
102 #endif
103 return aFrame->InkOverflowRectRelativeToSelf();
104 }
105
106 nsIFrame* mFirstContinuation;
107 nsIFrame* mCurrentFrame;
108 const nsRect& mCurrentFrameOverflowArea;
109 nsRect mResult;
110 bool mInReflow;
111 };
112
113 /**
114 * Gets the union of the pre-effects ink overflow rects of all of a frame's
115 * continuations, in "user space".
116 */
GetPreEffectsInkOverflowUnion(nsIFrame * aFirstContinuation,nsIFrame * aCurrentFrame,const nsRect & aCurrentFramePreEffectsOverflow,const nsPoint & aFirstContinuationToUserSpace,bool aInReflow)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;
130 }
131
132 /**
133 * Gets the pre-effects ink overflow rect of aCurrentFrame in "user space".
134 */
GetPreEffectsInkOverflow(nsIFrame * aFirstContinuation,nsIFrame * aCurrentFrame,const nsPoint & aFirstContinuationToUserSpace)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;
146 }
147
UsingOverflowAffectingEffects(const nsIFrame * aFrame)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();
153 }
154
UsingEffectsForFrame(const nsIFrame * aFrame)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();
166 }
167
UsingMaskOrClipPathForFrame(const nsIFrame * aFrame)168 bool SVGIntegrationUtils::UsingMaskOrClipPathForFrame(const nsIFrame* aFrame) {
169 const nsStyleSVGReset* style = aFrame->StyleSVGReset();
170 return style->HasClipPath() || style->HasMask();
171 }
172
UsingSimpleClipPathForFrame(const nsIFrame * aFrame)173 bool SVGIntegrationUtils::UsingSimpleClipPathForFrame(const nsIFrame* aFrame) {
174 const nsStyleSVGReset* style = aFrame->StyleSVGReset();
175 if (!style->HasClipPath() || style->HasMask()) {
176 return false;
177 }
178
179 const auto& clipPath = style->mClipPath;
180 if (!clipPath.IsShape()) {
181 return false;
182 }
183
184 return !clipPath.AsShape()._0->IsPolygon();
185 }
186
GetOffsetToBoundingBox(nsIFrame * aFrame)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();
194 }
195
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();
200 }
201
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;
212 };
213
ComputeEffectOffset(nsIFrame * aFrame,const SVGIntegrationUtils::PaintFramesParams & aParams)214 static EffectOffsets ComputeEffectOffset(
215 nsIFrame* aFrame, const SVGIntegrationUtils::PaintFramesParams& aParams) {
216 EffectOffsets result;
217
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));
229 }
230
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)));
247
248 result.offsetToUserSpace = result.offsetToBoundingBox - toUserSpace;
249
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
256
257 result.offsetToUserSpaceInDevPx = nsLayoutUtils::PointToGfxPoint(
258 result.offsetToUserSpace, aFrame->PresContext()->AppUnitsPerDevPixel());
259
260 return result;
261 }
262
263 /**
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.
266 */
MoveContextOriginToUserSpace(nsIFrame * aFrame,const SVGIntegrationUtils::PaintFramesParams & aParams)267 static EffectOffsets MoveContextOriginToUserSpace(
268 nsIFrame* aFrame, const SVGIntegrationUtils::PaintFramesParams& aParams) {
269 EffectOffsets offset = ComputeEffectOffset(aFrame, aParams);
270
271 aParams.ctx.SetMatrixDouble(aParams.ctx.CurrentMatrixDouble().PreTranslate(
272 offset.offsetToUserSpaceInDevPx));
273
274 return offset;
275 }
276
GetOffsetToUserSpaceInDevPx(nsIFrame * aFrame,const PaintFramesParams & aParams)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;
283 }
284
285 /* static */
GetContinuationUnionSize(nsIFrame * aNonSVGFrame)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();
292 }
293
GetSVGCoordContextForNonSVGFrame(nsIFrame * aNonSVGFrame)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));
303 }
304
GetSVGBBoxForNonSVGFrame(nsIFrame * aNonSVGFrame,bool aUnionContinuations)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());
314
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));
324
325 return nsLayoutUtils::RectToGfxRect(r, AppUnitsPerCSSPixel());
326 }
327
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.
338 //
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.
346 //
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.
352 //
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.
359 //
ComputePostEffectsInkOverflowRect(nsIFrame * aFrame,const nsRect & aPreEffectsOverflowRect)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");
364
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.");
368
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;
381 }
382
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();
393
394 nsRect overflowRect =
395 FilterInstance::GetPostFilterBounds(firstFrame, &overrideBBox);
396
397 // Return overflowRect relative to aFrame, rather than "user space":
398 return overflowRect -
399 (aFrame->GetOffsetTo(firstFrame) + firstFrameToBoundingBox);
400 }
401
AdjustInvalidAreaForSVGEffects(nsIFrame * aFrame,const nsPoint & aToReferenceFrame,const nsIntRegion & aInvalidRegion)402 nsIntRegion SVGIntegrationUtils::AdjustInvalidAreaForSVGEffects(
403 nsIFrame* aFrame, const nsPoint& aToReferenceFrame,
404 const nsIntRegion& aInvalidRegion) {
405 if (aInvalidRegion.IsEmpty()) {
406 return nsIntRect();
407 }
408
409 nsIFrame* firstFrame =
410 nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
411
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;
420 }
421
422 int32_t appUnitsPerDevPixel = aFrame->PresContext()->AppUnitsPerDevPixel();
423
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);
432
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);
440 }
441
GetRequiredSourceForInvalidArea(nsIFrame * aFrame,const nsRect & aDirtyRect)442 nsRect SVGIntegrationUtils::GetRequiredSourceForInvalidArea(
443 nsIFrame* aFrame, const nsRect& aDirtyRect) {
444 nsIFrame* firstFrame =
445 nsLayoutUtils::FirstContinuationOrIBSplitSibling(aFrame);
446
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;
455 }
456
457 // Convert aDirtyRect into "user space" in app units:
458 nsPoint toUserSpace =
459 aFrame->GetOffsetTo(firstFrame) + GetOffsetToBoundingBox(firstFrame);
460 nsRect postEffectsRect = aDirtyRect + toUserSpace;
461
462 // Return ther result, relative to aFrame, not in user space:
463 return FilterInstance::GetPreFilterNeededArea(firstFrame, postEffectsRect)
464 .GetBounds() -
465 toUserSpace;
466 }
467
HitTestFrameForEffects(nsIFrame * aFrame,const nsPoint & aPt)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);
480 }
481 nsPoint pt = aPt + toUserSpace;
482 gfxPoint userSpacePt = gfxPoint(pt.x, pt.y) / AppUnitsPerCSSPixel();
483 return SVGUtils::HitTestClip(firstFrame, userSpacePt);
484 }
485
486 using PaintFramesParams = SVGIntegrationUtils::PaintFramesParams;
487
488 /**
489 * Paint css-positioned-mask onto a given target(aMaskDT).
490 * Return value indicates if mask is complete.
491 */
PaintMaskSurface(const PaintFramesParams & aParams,DrawTarget * aMaskDT,float aOpacity,ComputedStyle * aSC,const nsTArray<SVGMaskFrame * > & aMaskFrames,const nsPoint & aOffsetToUserSpace)492 static bool PaintMaskSurface(const PaintFramesParams& aParams,
493 DrawTarget* aMaskDT, float aOpacity,
494 ComputedStyle* aSC,
495 const nsTArray<SVGMaskFrame*>& aMaskFrames,
496 const nsPoint& aOffsetToUserSpace) {
497 MOZ_ASSERT(aMaskFrames.Length() > 0);
498 MOZ_ASSERT(aMaskDT->GetFormat() == SurfaceFormat::A8);
499 MOZ_ASSERT(aOpacity == 1.0 || aMaskFrames.Length() == 1);
500
501 const nsStyleSVGReset* svgReset = aSC->StyleSVGReset();
502 gfxMatrix cssPxToDevPxMatrix = SVGUtils::GetCSSPxToDevPxMatrix(aParams.frame);
503
504 nsPresContext* presContext = aParams.frame->PresContext();
505 gfxPoint devPixelOffsetToUserSpace = nsLayoutUtils::PointToGfxPoint(
506 aOffsetToUserSpace, presContext->AppUnitsPerDevPixel());
507
508 RefPtr<gfxContext> maskContext =
509 gfxContext::CreatePreservingTransformOrNull(aMaskDT);
510 MOZ_ASSERT(maskContext);
511
512 bool isMaskComplete = true;
513
514 // Multiple SVG masks interleave with image mask. Paint each layer onto
515 // aMaskDT one at a time.
516 for (int i = aMaskFrames.Length() - 1; i >= 0; i--) {
517 SVGMaskFrame* maskFrame = aMaskFrames[i];
518 CompositionOp compositionOp =
519 (i == int(aMaskFrames.Length() - 1))
520 ? CompositionOp::OP_OVER
521 : nsCSSRendering::GetGFXCompositeMode(
522 svgReset->mMask.mLayers[i].mComposite);
523
524 // maskFrame != nullptr means we get a SVG mask.
525 // maskFrame == nullptr means we get an image mask.
526 if (maskFrame) {
527 SVGMaskFrame::MaskParams params(
528 maskContext->GetDrawTarget(), aParams.frame, cssPxToDevPxMatrix,
529 aOpacity, svgReset->mMask.mLayers[i].mMaskMode, aParams.imgParams);
530 RefPtr<SourceSurface> svgMask = maskFrame->GetMaskForMaskedFrame(params);
531 if (svgMask) {
532 Matrix tmp = aMaskDT->GetTransform();
533 aMaskDT->SetTransform(Matrix());
534 aMaskDT->MaskSurface(ColorPattern(DeviceColor(0.0, 0.0, 0.0, 1.0)),
535 svgMask, Point(0, 0),
536 DrawOptions(1.0, compositionOp));
537 aMaskDT->SetTransform(tmp);
538 }
539 } else if (svgReset->mMask.mLayers[i].mImage.IsResolved()) {
540 gfxContextMatrixAutoSaveRestore matRestore(maskContext);
541
542 maskContext->Multiply(gfxMatrix::Translation(-devPixelOffsetToUserSpace));
543 nsCSSRendering::PaintBGParams params =
544 nsCSSRendering::PaintBGParams::ForSingleLayer(
545 *presContext, aParams.dirtyRect, aParams.borderArea,
546 aParams.frame,
547 aParams.builder->GetBackgroundPaintFlags() |
548 nsCSSRendering::PAINTBG_MASK_IMAGE,
549 i, compositionOp, aOpacity);
550
551 aParams.imgParams.result &= nsCSSRendering::PaintStyleImageLayerWithSC(
552 params, *maskContext, aSC, *aParams.frame->StyleBorder());
553 } else {
554 isMaskComplete = false;
555 }
556 }
557
558 return isMaskComplete;
559 }
560
561 struct MaskPaintResult {
562 RefPtr<SourceSurface> maskSurface;
563 Matrix maskTransform;
564 bool transparentBlackMask;
565 bool opacityApplied;
566
MaskPaintResultmozilla::MaskPaintResult567 MaskPaintResult() : transparentBlackMask(false), opacityApplied(false) {}
568 };
569
CreateAndPaintMaskSurface(const PaintFramesParams & aParams,float aOpacity,ComputedStyle * aSC,const nsTArray<SVGMaskFrame * > & aMaskFrames,const nsPoint & aOffsetToUserSpace)570 static MaskPaintResult CreateAndPaintMaskSurface(
571 const PaintFramesParams& aParams, float aOpacity, ComputedStyle* aSC,
572 const nsTArray<SVGMaskFrame*>& aMaskFrames,
573 const nsPoint& aOffsetToUserSpace) {
574 const nsStyleSVGReset* svgReset = aSC->StyleSVGReset();
575 MOZ_ASSERT(aMaskFrames.Length() > 0);
576 MaskPaintResult paintResult;
577
578 gfxContext& ctx = aParams.ctx;
579
580 // Optimization for single SVG mask.
581 if (((aMaskFrames.Length() == 1) && aMaskFrames[0])) {
582 gfxMatrix cssPxToDevPxMatrix =
583 SVGUtils::GetCSSPxToDevPxMatrix(aParams.frame);
584 paintResult.opacityApplied = true;
585 SVGMaskFrame::MaskParams params(
586 ctx.GetDrawTarget(), aParams.frame, cssPxToDevPxMatrix, aOpacity,
587 svgReset->mMask.mLayers[0].mMaskMode, aParams.imgParams);
588 paintResult.maskSurface = aMaskFrames[0]->GetMaskForMaskedFrame(params);
589 paintResult.maskTransform = ctx.CurrentMatrix();
590 paintResult.maskTransform.Invert();
591 if (!paintResult.maskSurface) {
592 paintResult.transparentBlackMask = true;
593 }
594
595 return paintResult;
596 }
597
598 const LayoutDeviceRect& maskSurfaceRect =
599 aParams.maskRect.valueOr(LayoutDeviceRect());
600 if (aParams.maskRect.isSome() && maskSurfaceRect.IsEmpty()) {
601 // XXX: Is this ever true?
602 paintResult.transparentBlackMask = true;
603 return paintResult;
604 }
605
606 RefPtr<DrawTarget> maskDT = ctx.GetDrawTarget()->CreateClippedDrawTarget(
607 maskSurfaceRect.ToUnknownRect(), SurfaceFormat::A8);
608 if (!maskDT || !maskDT->IsValid()) {
609 return paintResult;
610 }
611
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);
617
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();
621
622 bool isMaskComplete = PaintMaskSurface(
623 aParams, maskDT, paintResult.opacityApplied ? aOpacity : 1.0, aSC,
624 aMaskFrames, aOffsetToUserSpace);
625
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.
633 //
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);
647
648 MOZ_ASSERT(!paintResult.maskSurface);
649 return paintResult;
650 }
651
652 paintResult.maskTransform = maskSurfaceMatrix;
653 if (!paintResult.maskTransform.Invert()) {
654 return paintResult;
655 }
656
657 paintResult.maskSurface = maskDT->Snapshot();
658 return paintResult;
659 }
660
ValidateSVGFrame(nsIFrame * aFrame)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
668
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
676
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;
681 }
682 }
683
684 return true;
685 }
686
687 class AutoPopGroup {
688 public:
AutoPopGroup()689 AutoPopGroup() : mContext(nullptr) {}
690
~AutoPopGroup()691 ~AutoPopGroup() {
692 if (mContext) {
693 mContext->PopGroupAndBlend();
694 }
695 }
696
SetContext(gfxContext * aContext)697 void SetContext(gfxContext* aContext) { mContext = aContext; }
698
699 private:
700 gfxContext* mContext;
701 };
702
PaintMask(const PaintFramesParams & aParams,bool & aOutIsMaskComplete)703 bool SVGIntegrationUtils::PaintMask(const PaintFramesParams& aParams,
704 bool& aOutIsMaskComplete) {
705 aOutIsMaskComplete = true;
706
707 SVGUtils::MaskUsage maskUsage;
708 SVGUtils::DetermineMaskUsage(aParams.frame, aParams.handleOpacity, maskUsage);
709 if (!maskUsage.shouldDoSomething()) {
710 return false;
711 }
712
713 nsIFrame* frame = aParams.frame;
714 if (!ValidateSVGFrame(frame)) {
715 return false;
716 }
717
718 gfxContext& ctx = aParams.ctx;
719 RefPtr<DrawTarget> maskTarget = ctx.GetDrawTarget();
720
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.
726 //
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 maskTarget = maskTarget->CreateClippedDrawTarget(Rect(), SurfaceFormat::A8);
731 }
732
733 nsIFrame* firstFrame =
734 nsLayoutUtils::FirstContinuationOrIBSplitSibling(frame);
735 nsTArray<SVGMaskFrame*> maskFrames;
736 // XXX check return value?
737 SVGObserverUtils::GetAndObserveMasks(firstFrame, &maskFrames);
738
739 AutoPopGroup autoPop;
740 bool shouldPushOpacity =
741 (maskUsage.opacity != 1.0) && (maskFrames.Length() != 1);
742 if (shouldPushOpacity) {
743 ctx.PushGroupForBlendBack(gfxContentType::COLOR_ALPHA, maskUsage.opacity);
744 autoPop.SetContext(&ctx);
745 }
746
747 gfxContextMatrixAutoSaveRestore matSR;
748
749 // Paint clip-path-basic-shape onto ctx
750 gfxContextAutoSaveRestore basicShapeSR;
751 if (maskUsage.shouldApplyBasicShapeOrPath) {
752 matSR.SetContext(&ctx);
753
754 MoveContextOriginToUserSpace(firstFrame, aParams);
755
756 basicShapeSR.SetContext(&ctx);
757 gfxMatrix mat = SVGUtils::GetCSSPxToDevPxMatrix(frame);
758 if (!maskUsage.shouldGenerateMaskLayer) {
759 // Only have basic-shape clip-path effect. Fill clipped region by
760 // opaque white.
761 ctx.SetDeviceColor(DeviceColor::MaskOpaqueWhite());
762 RefPtr<Path> path = CSSClipPathInstance::CreateClipPathForFrame(
763 ctx.GetDrawTarget(), frame, mat);
764 ctx.SetPath(path);
765 ctx.Fill();
766
767 return true;
768 }
769 CSSClipPathInstance::ApplyBasicShapeOrPathClip(ctx, frame, mat);
770 }
771
772 // Paint mask into maskTarget.
773 if (maskUsage.shouldGenerateMaskLayer) {
774 matSR.Restore();
775 matSR.SetContext(&ctx);
776
777 EffectOffsets offsets = ComputeEffectOffset(frame, aParams);
778 maskTarget->SetTransform(maskTarget->GetTransform().PreTranslate(
779 ToPoint(offsets.offsetToUserSpaceInDevPx)));
780 aOutIsMaskComplete = PaintMaskSurface(
781 aParams, maskTarget, shouldPushOpacity ? 1.0 : maskUsage.opacity,
782 firstFrame->Style(), maskFrames, offsets.offsetToUserSpace);
783 }
784
785 // Paint clip-path onto ctx.
786 if (maskUsage.shouldGenerateClipMaskLayer || maskUsage.shouldApplyClipPath) {
787 matSR.Restore();
788 matSR.SetContext(&ctx);
789
790 MoveContextOriginToUserSpace(firstFrame, aParams);
791 Matrix clipMaskTransform;
792 gfxMatrix cssPxToDevPxMatrix = SVGUtils::GetCSSPxToDevPxMatrix(frame);
793
794 SVGClipPathFrame* clipPathFrame;
795 // XXX check return value?
796 SVGObserverUtils::GetAndObserveClipPath(firstFrame, &clipPathFrame);
797 RefPtr<SourceSurface> maskSurface =
798 maskUsage.shouldGenerateMaskLayer ? maskTarget->Snapshot() : nullptr;
799 clipPathFrame->PaintClipMask(ctx, frame, cssPxToDevPxMatrix, maskSurface);
800 }
801
802 return true;
803 }
804
805 template <class T>
PaintMaskAndClipPathInternal(const PaintFramesParams & aParams,const T & aPaintChild)806 void PaintMaskAndClipPathInternal(const PaintFramesParams& aParams,
807 const T& aPaintChild) {
808 MOZ_ASSERT(SVGIntegrationUtils::UsingMaskOrClipPathForFrame(aParams.frame),
809 "Should not use this method when no mask or clipPath effect"
810 "on this frame");
811
812 /* SVG defines the following rendering model:
813 *
814 * 1. Render geometry
815 * 2. Apply filter
816 * 3. Apply clipping, masking, group opacity
817 *
818 * We handle #3 here and perform a couple of optimizations:
819 *
820 * + Use cairo's clipPath when representable natively (single object
821 * clip region).
822 *
823 * + Merge opacity and masking if both used together.
824 */
825 nsIFrame* frame = aParams.frame;
826 if (!ValidateSVGFrame(frame)) {
827 return;
828 }
829
830 SVGUtils::MaskUsage maskUsage;
831 SVGUtils::DetermineMaskUsage(aParams.frame, aParams.handleOpacity, maskUsage);
832
833 if (maskUsage.opacity == 0.0f) {
834 return;
835 }
836
837 gfxContext& context = aParams.ctx;
838 gfxContextMatrixAutoSaveRestore matrixAutoSaveRestore(&context);
839
840 nsIFrame* firstFrame =
841 nsLayoutUtils::FirstContinuationOrIBSplitSibling(frame);
842
843 SVGClipPathFrame* clipPathFrame;
844 // XXX check return value?
845 SVGObserverUtils::GetAndObserveClipPath(firstFrame, &clipPathFrame);
846
847 nsTArray<SVGMaskFrame*> maskFrames;
848 // XXX check return value?
849 SVGObserverUtils::GetAndObserveMasks(firstFrame, &maskFrames);
850
851 gfxMatrix cssPxToDevPxMatrix = SVGUtils::GetCSSPxToDevPxMatrix(frame);
852
853 bool shouldGenerateMask =
854 (maskUsage.opacity != 1.0f || maskUsage.shouldGenerateClipMaskLayer ||
855 maskUsage.shouldGenerateMaskLayer);
856 bool shouldPushMask = false;
857
858 /* Check if we need to do additional operations on this child's
859 * rendering, which necessitates rendering into another surface. */
860 if (shouldGenerateMask) {
861 gfxContextMatrixAutoSaveRestore matSR;
862
863 RefPtr<SourceSurface> maskSurface;
864 bool opacityApplied = false;
865
866 if (maskUsage.shouldGenerateMaskLayer) {
867 matSR.SetContext(&context);
868
869 // For css-mask, we want to generate a mask for each continuation frame,
870 // so we setup context matrix by the position of the current frame,
871 // instead of the first continuation frame.
872 EffectOffsets offsets = MoveContextOriginToUserSpace(frame, aParams);
873 MaskPaintResult paintResult = CreateAndPaintMaskSurface(
874 aParams, maskUsage.opacity, firstFrame->Style(), maskFrames,
875 offsets.offsetToUserSpace);
876
877 if (paintResult.transparentBlackMask) {
878 return;
879 }
880
881 maskSurface = paintResult.maskSurface;
882 if (maskSurface) {
883 shouldPushMask = true;
884
885 opacityApplied = paintResult.opacityApplied;
886 }
887 }
888
889 if (maskUsage.shouldGenerateClipMaskLayer) {
890 matSR.Restore();
891 matSR.SetContext(&context);
892
893 MoveContextOriginToUserSpace(firstFrame, aParams);
894 RefPtr<SourceSurface> clipMaskSurface = clipPathFrame->GetClipMask(
895 context, frame, cssPxToDevPxMatrix, maskSurface);
896
897 if (clipMaskSurface) {
898 maskSurface = clipMaskSurface;
899 } else {
900 // Either entire surface is clipped out, or gfx buffer allocation
901 // failure in SVGClipPathFrame::GetClipMask.
902 return;
903 }
904
905 shouldPushMask = true;
906 }
907
908 // opacity != 1.0f.
909 if (!maskUsage.shouldGenerateClipMaskLayer &&
910 !maskUsage.shouldGenerateMaskLayer) {
911 MOZ_ASSERT(maskUsage.opacity != 1.0f);
912
913 matSR.SetContext(&context);
914 MoveContextOriginToUserSpace(firstFrame, aParams);
915 shouldPushMask = true;
916 }
917
918 if (shouldPushMask) {
919 // We want the mask to be untransformed so use the inverse of the
920 // current transform as the maskTransform to compensate.
921 Matrix maskTransform = context.CurrentMatrix();
922 maskTransform.Invert();
923
924 context.PushGroupForBlendBack(gfxContentType::COLOR_ALPHA,
925 opacityApplied ? 1.0 : maskUsage.opacity,
926 maskSurface, maskTransform);
927 }
928 }
929
930 /* If this frame has only a trivial clipPath, set up cairo's clipping now so
931 * we can just do normal painting and get it clipped appropriately.
932 */
933 if (maskUsage.shouldApplyClipPath || maskUsage.shouldApplyBasicShapeOrPath) {
934 gfxContextMatrixAutoSaveRestore matSR(&context);
935
936 MoveContextOriginToUserSpace(firstFrame, aParams);
937
938 MOZ_ASSERT(!maskUsage.shouldApplyClipPath ||
939 !maskUsage.shouldApplyBasicShapeOrPath);
940 if (maskUsage.shouldApplyClipPath) {
941 clipPathFrame->ApplyClipPath(context, frame, cssPxToDevPxMatrix);
942 } else {
943 CSSClipPathInstance::ApplyBasicShapeOrPathClip(context, frame,
944 cssPxToDevPxMatrix);
945 }
946 }
947
948 /* Paint the child */
949 context.SetMatrix(matrixAutoSaveRestore.Matrix());
950 aPaintChild();
951
952 if (StaticPrefs::layers_draw_mask_debug()) {
953 gfxContextAutoSaveRestore saver(&context);
954
955 context.NewPath();
956 gfxRect drawingRect = nsLayoutUtils::RectToGfxRect(
957 aParams.borderArea, frame->PresContext()->AppUnitsPerDevPixel());
958 context.SnappedRectangle(drawingRect);
959 sRGBColor overlayColor(0.0f, 0.0f, 0.0f, 0.8f);
960 if (maskUsage.shouldGenerateMaskLayer) {
961 overlayColor.r = 1.0f; // red represents css positioned mask.
962 }
963 if (maskUsage.shouldApplyClipPath ||
964 maskUsage.shouldGenerateClipMaskLayer) {
965 overlayColor.g = 1.0f; // green represents clip-path:<clip-source>.
966 }
967 if (maskUsage.shouldApplyBasicShapeOrPath) {
968 overlayColor.b = 1.0f; // blue represents
969 // clip-path:<basic-shape>||<geometry-box>.
970 }
971
972 context.SetColor(overlayColor);
973 context.Fill();
974 }
975
976 if (maskUsage.shouldApplyClipPath || maskUsage.shouldApplyBasicShapeOrPath) {
977 context.PopClip();
978 }
979
980 if (shouldPushMask) {
981 context.PopGroupAndBlend();
982 }
983 }
984
PaintMaskAndClipPath(const PaintFramesParams & aParams,const std::function<void ()> & aPaintChild)985 void SVGIntegrationUtils::PaintMaskAndClipPath(
986 const PaintFramesParams& aParams,
987 const std::function<void()>& aPaintChild) {
988 PaintMaskAndClipPathInternal(aParams, aPaintChild);
989 }
990
PaintFilter(const PaintFramesParams & aParams,const SVGFilterPaintCallback & aCallback)991 void SVGIntegrationUtils::PaintFilter(const PaintFramesParams& aParams,
992 const SVGFilterPaintCallback& aCallback) {
993 MOZ_ASSERT(!aParams.builder->IsForGenerateGlyphMask(),
994 "Filter effect is discarded while generating glyph mask.");
995 MOZ_ASSERT(aParams.frame->StyleEffects()->HasFilters(),
996 "Should not use this method when no filter effect on this frame");
997
998 nsIFrame* frame = aParams.frame;
999 if (!ValidateSVGFrame(frame)) {
1000 return;
1001 }
1002
1003 float opacity = SVGUtils::ComputeOpacity(frame, aParams.handleOpacity);
1004 if (opacity == 0.0f) {
1005 return;
1006 }
1007
1008 /* Properties are added lazily and may have been removed by a restyle,
1009 so make sure all applicable ones are set again. */
1010 nsIFrame* firstFrame =
1011 nsLayoutUtils::FirstContinuationOrIBSplitSibling(frame);
1012 // Note: we do not return here for eHasNoRefs since we must still handle any
1013 // CSS filter functions.
1014 // TODO: We currently pass nullptr instead of an nsTArray* here, but we
1015 // actually should get the filter frames and then pass them into
1016 // PaintFilteredFrame below! See bug 1494263.
1017 // XXX: Do we need to check for eHasRefsSomeInvalid here given that
1018 // nsDisplayFilter::BuildLayer returns nullptr for eHasRefsSomeInvalid?
1019 // Or can we just assert !eHasRefsSomeInvalid?
1020 if (SVGObserverUtils::GetAndObserveFilters(firstFrame, nullptr) ==
1021 SVGObserverUtils::eHasRefsSomeInvalid) {
1022 return;
1023 }
1024
1025 gfxContext& context = aParams.ctx;
1026
1027 gfxContextAutoSaveRestore autoSR(&context);
1028 EffectOffsets offsets = MoveContextOriginToUserSpace(firstFrame, aParams);
1029
1030 /* Paint the child and apply filters */
1031 nsRegion dirtyRegion = aParams.dirtyRect - offsets.offsetToBoundingBox;
1032
1033 FilterInstance::PaintFilteredFrame(frame, &context, aCallback, &dirtyRegion,
1034 aParams.imgParams, opacity);
1035 }
1036
CreateWebRenderCSSFilters(Span<const StyleFilter> aFilters,nsIFrame * aFrame,WrFiltersHolder & aWrFilters)1037 bool SVGIntegrationUtils::CreateWebRenderCSSFilters(
1038 Span<const StyleFilter> aFilters, nsIFrame* aFrame,
1039 WrFiltersHolder& aWrFilters) {
1040 // All CSS filters are supported by WebRender. SVG filters are not fully
1041 // supported, those use NS_STYLE_FILTER_URL and are handled separately.
1042
1043 // If there are too many filters to render, then just pretend that we
1044 // succeeded, and don't render any of them.
1045 if (aFilters.Length() >
1046 StaticPrefs::gfx_webrender_max_filter_ops_per_chain()) {
1047 return true;
1048 }
1049 aWrFilters.filters.SetCapacity(aFilters.Length());
1050 auto& wrFilters = aWrFilters.filters;
1051 for (const StyleFilter& filter : aFilters) {
1052 switch (filter.tag) {
1053 case StyleFilter::Tag::Brightness:
1054 wrFilters.AppendElement(
1055 wr::FilterOp::Brightness(filter.AsBrightness()));
1056 break;
1057 case StyleFilter::Tag::Contrast:
1058 wrFilters.AppendElement(wr::FilterOp::Contrast(filter.AsContrast()));
1059 break;
1060 case StyleFilter::Tag::Grayscale:
1061 wrFilters.AppendElement(wr::FilterOp::Grayscale(filter.AsGrayscale()));
1062 break;
1063 case StyleFilter::Tag::Invert:
1064 wrFilters.AppendElement(wr::FilterOp::Invert(filter.AsInvert()));
1065 break;
1066 case StyleFilter::Tag::Opacity: {
1067 float opacity = filter.AsOpacity();
1068 wrFilters.AppendElement(wr::FilterOp::Opacity(
1069 wr::PropertyBinding<float>::Value(opacity), opacity));
1070 break;
1071 }
1072 case StyleFilter::Tag::Saturate:
1073 wrFilters.AppendElement(wr::FilterOp::Saturate(filter.AsSaturate()));
1074 break;
1075 case StyleFilter::Tag::Sepia:
1076 wrFilters.AppendElement(wr::FilterOp::Sepia(filter.AsSepia()));
1077 break;
1078 case StyleFilter::Tag::HueRotate: {
1079 wrFilters.AppendElement(
1080 wr::FilterOp::HueRotate(filter.AsHueRotate().ToDegrees()));
1081 break;
1082 }
1083 case StyleFilter::Tag::Blur: {
1084 // TODO(emilio): we should go directly from css pixels -> device pixels.
1085 float appUnitsPerDevPixel =
1086 aFrame->PresContext()->AppUnitsPerDevPixel();
1087 float radius = NSAppUnitsToFloatPixels(filter.AsBlur().ToAppUnits(),
1088 appUnitsPerDevPixel);
1089 wrFilters.AppendElement(wr::FilterOp::Blur(radius, radius));
1090 break;
1091 }
1092 case StyleFilter::Tag::DropShadow: {
1093 float appUnitsPerDevPixel =
1094 aFrame->PresContext()->AppUnitsPerDevPixel();
1095 const StyleSimpleShadow& shadow = filter.AsDropShadow();
1096 nscolor color = shadow.color.CalcColor(aFrame);
1097
1098 wr::Shadow wrShadow;
1099 wrShadow.offset = {
1100 NSAppUnitsToFloatPixels(shadow.horizontal.ToAppUnits(),
1101 appUnitsPerDevPixel),
1102 NSAppUnitsToFloatPixels(shadow.vertical.ToAppUnits(),
1103 appUnitsPerDevPixel)};
1104 wrShadow.blur_radius = NSAppUnitsToFloatPixels(shadow.blur.ToAppUnits(),
1105 appUnitsPerDevPixel);
1106 wrShadow.color = {NS_GET_R(color) / 255.0f, NS_GET_G(color) / 255.0f,
1107 NS_GET_B(color) / 255.0f, NS_GET_A(color) / 255.0f};
1108 wrFilters.AppendElement(wr::FilterOp::DropShadow(wrShadow));
1109 break;
1110 }
1111 default:
1112 return false;
1113 }
1114 }
1115
1116 return true;
1117 }
1118
BuildWebRenderFilters(nsIFrame * aFilteredFrame,Span<const StyleFilter> aFilters,WrFiltersHolder & aWrFilters,Maybe<nsRect> & aPostFilterClip,bool & aInitialized)1119 bool SVGIntegrationUtils::BuildWebRenderFilters(
1120 nsIFrame* aFilteredFrame, Span<const StyleFilter> aFilters,
1121 WrFiltersHolder& aWrFilters, Maybe<nsRect>& aPostFilterClip,
1122 bool& aInitialized) {
1123 return FilterInstance::BuildWebRenderFilters(
1124 aFilteredFrame, aFilters, aWrFilters, aPostFilterClip, aInitialized);
1125 }
1126
CanCreateWebRenderFiltersForFrame(nsIFrame * aFrame)1127 bool SVGIntegrationUtils::CanCreateWebRenderFiltersForFrame(nsIFrame* aFrame) {
1128 WrFiltersHolder wrFilters;
1129 Maybe<nsRect> filterClip;
1130 auto filterChain = aFrame->StyleEffects()->mFilters.AsSpan();
1131 bool initialized = true;
1132 return CreateWebRenderCSSFilters(filterChain, aFrame, wrFilters) ||
1133 BuildWebRenderFilters(aFrame, filterChain, wrFilters, filterClip,
1134 initialized);
1135 }
1136
UsesSVGEffectsNotSupportedInCompositor(nsIFrame * aFrame)1137 bool SVGIntegrationUtils::UsesSVGEffectsNotSupportedInCompositor(
1138 nsIFrame* aFrame) {
1139 // WebRender supports masks / clip-paths and some filters in the compositor.
1140 // Non-WebRender doesn't support any SVG effects in the compositor.
1141 if (aFrame->StyleEffects()->HasFilters()) {
1142 return !gfx::gfxVars::UseWebRender() ||
1143 !SVGIntegrationUtils::CanCreateWebRenderFiltersForFrame(aFrame);
1144 }
1145 if (SVGIntegrationUtils::UsingMaskOrClipPathForFrame(aFrame)) {
1146 return !gfx::gfxVars::UseWebRender();
1147 }
1148 return false;
1149 }
1150
1151 class PaintFrameCallback : public gfxDrawingCallback {
1152 public:
PaintFrameCallback(nsIFrame * aFrame,const nsSize aPaintServerSize,const IntSize aRenderSize,uint32_t aFlags)1153 PaintFrameCallback(nsIFrame* aFrame, const nsSize aPaintServerSize,
1154 const IntSize aRenderSize, uint32_t aFlags)
1155 : mFrame(aFrame),
1156 mPaintServerSize(aPaintServerSize),
1157 mRenderSize(aRenderSize),
1158 mFlags(aFlags) {}
1159 virtual bool operator()(gfxContext* aContext, const gfxRect& aFillRect,
1160 const SamplingFilter aSamplingFilter,
1161 const gfxMatrix& aTransform) override;
1162
1163 private:
1164 nsIFrame* mFrame;
1165 nsSize mPaintServerSize;
1166 IntSize mRenderSize;
1167 uint32_t mFlags;
1168 };
1169
operator ()(gfxContext * aContext,const gfxRect & aFillRect,const SamplingFilter aSamplingFilter,const gfxMatrix & aTransform)1170 bool PaintFrameCallback::operator()(gfxContext* aContext,
1171 const gfxRect& aFillRect,
1172 const SamplingFilter aSamplingFilter,
1173 const gfxMatrix& aTransform) {
1174 if (mFrame->HasAnyStateBits(NS_FRAME_DRAWING_AS_PAINTSERVER)) {
1175 return false;
1176 }
1177
1178 AutoSetRestorePaintServerState paintServer(mFrame);
1179
1180 aContext->Save();
1181
1182 // Clip to aFillRect so that we don't paint outside.
1183 aContext->Clip(aFillRect);
1184
1185 gfxMatrix invmatrix = aTransform;
1186 if (!invmatrix.Invert()) {
1187 return false;
1188 }
1189 aContext->Multiply(invmatrix);
1190
1191 // nsLayoutUtils::PaintFrame will anchor its painting at mFrame. But we want
1192 // to have it anchored at the top left corner of the bounding box of all of
1193 // mFrame's continuations. So we add a translation transform.
1194 int32_t appUnitsPerDevPixel = mFrame->PresContext()->AppUnitsPerDevPixel();
1195 nsPoint offset = SVGIntegrationUtils::GetOffsetToBoundingBox(mFrame);
1196 gfxPoint devPxOffset = gfxPoint(offset.x, offset.y) / appUnitsPerDevPixel;
1197 aContext->Multiply(gfxMatrix::Translation(devPxOffset));
1198
1199 gfxSize paintServerSize =
1200 gfxSize(mPaintServerSize.width, mPaintServerSize.height) /
1201 mFrame->PresContext()->AppUnitsPerDevPixel();
1202
1203 // nsLayoutUtils::PaintFrame wants to render with paintServerSize, but we
1204 // want it to render with mRenderSize, so we need to set up a scale transform.
1205 gfxFloat scaleX = mRenderSize.width / paintServerSize.width;
1206 gfxFloat scaleY = mRenderSize.height / paintServerSize.height;
1207 aContext->Multiply(gfxMatrix::Scaling(scaleX, scaleY));
1208
1209 // Draw.
1210 nsRect dirty(-offset.x, -offset.y, mPaintServerSize.width,
1211 mPaintServerSize.height);
1212
1213 using PaintFrameFlags = nsLayoutUtils::PaintFrameFlags;
1214 PaintFrameFlags flags = PaintFrameFlags::InTransform;
1215 if (mFlags & SVGIntegrationUtils::FLAG_SYNC_DECODE_IMAGES) {
1216 flags |= PaintFrameFlags::SyncDecodeImages;
1217 }
1218 nsLayoutUtils::PaintFrame(aContext, mFrame, dirty, NS_RGBA(0, 0, 0, 0),
1219 nsDisplayListBuilderMode::Painting, flags);
1220
1221 nsIFrame* currentFrame = mFrame;
1222 while ((currentFrame = currentFrame->GetNextContinuation()) != nullptr) {
1223 offset = currentFrame->GetOffsetToCrossDoc(mFrame);
1224 devPxOffset = gfxPoint(offset.x, offset.y) / appUnitsPerDevPixel;
1225
1226 aContext->Save();
1227 aContext->Multiply(gfxMatrix::Scaling(1 / scaleX, 1 / scaleY));
1228 aContext->Multiply(gfxMatrix::Translation(devPxOffset));
1229 aContext->Multiply(gfxMatrix::Scaling(scaleX, scaleY));
1230
1231 nsLayoutUtils::PaintFrame(aContext, currentFrame, dirty - offset,
1232 NS_RGBA(0, 0, 0, 0),
1233 nsDisplayListBuilderMode::Painting, flags);
1234
1235 aContext->Restore();
1236 }
1237
1238 aContext->Restore();
1239
1240 return true;
1241 }
1242
1243 /* static */
DrawableFromPaintServer(nsIFrame * aFrame,nsIFrame * aTarget,const nsSize & aPaintServerSize,const IntSize & aRenderSize,const DrawTarget * aDrawTarget,const gfxMatrix & aContextMatrix,uint32_t aFlags)1244 already_AddRefed<gfxDrawable> SVGIntegrationUtils::DrawableFromPaintServer(
1245 nsIFrame* aFrame, nsIFrame* aTarget, const nsSize& aPaintServerSize,
1246 const IntSize& aRenderSize, const DrawTarget* aDrawTarget,
1247 const gfxMatrix& aContextMatrix, uint32_t aFlags) {
1248 // aPaintServerSize is the size that would be filled when using
1249 // background-repeat:no-repeat and background-size:auto. For normal background
1250 // images, this would be the intrinsic size of the image; for gradients and
1251 // patterns this would be the whole target frame fill area.
1252 // aRenderSize is what we will be actually filling after accounting for
1253 // background-size.
1254 if (SVGPaintServerFrame* server = do_QueryFrame(aFrame)) {
1255 // aFrame is either a pattern or a gradient. These fill the whole target
1256 // frame by default, so aPaintServerSize is the whole target background fill
1257 // area.
1258 gfxRect overrideBounds(0, 0, aPaintServerSize.width,
1259 aPaintServerSize.height);
1260 overrideBounds.Scale(1.0 / aFrame->PresContext()->AppUnitsPerDevPixel());
1261 imgDrawingParams imgParams(aFlags);
1262 RefPtr<gfxPattern> pattern = server->GetPaintServerPattern(
1263 aTarget, aDrawTarget, aContextMatrix, &nsStyleSVG::mFill, 1.0,
1264 imgParams, &overrideBounds);
1265
1266 if (!pattern) {
1267 return nullptr;
1268 }
1269
1270 // pattern is now set up to fill aPaintServerSize. But we want it to
1271 // fill aRenderSize, so we need to add a scaling transform.
1272 // We couldn't just have set overrideBounds to aRenderSize - it would have
1273 // worked for gradients, but for patterns it would result in a different
1274 // pattern size.
1275 gfxFloat scaleX = overrideBounds.Width() / aRenderSize.width;
1276 gfxFloat scaleY = overrideBounds.Height() / aRenderSize.height;
1277 gfxMatrix scaleMatrix = gfxMatrix::Scaling(scaleX, scaleY);
1278 pattern->SetMatrix(scaleMatrix * pattern->GetMatrix());
1279 RefPtr<gfxDrawable> drawable = new gfxPatternDrawable(pattern, aRenderSize);
1280 return drawable.forget();
1281 }
1282
1283 if (aFrame->IsFrameOfType(nsIFrame::eSVG) &&
1284 !static_cast<ISVGDisplayableFrame*>(do_QueryFrame(aFrame))) {
1285 MOZ_ASSERT_UNREACHABLE(
1286 "We should prevent painting of unpaintable SVG "
1287 "before we get here");
1288 return nullptr;
1289 }
1290
1291 // We don't want to paint into a surface as long as we don't need to, so we
1292 // set up a drawing callback.
1293 RefPtr<gfxDrawingCallback> cb =
1294 new PaintFrameCallback(aFrame, aPaintServerSize, aRenderSize, aFlags);
1295 RefPtr<gfxDrawable> drawable = new gfxCallbackDrawable(cb, aRenderSize);
1296 return drawable.forget();
1297 }
1298
1299 } // namespace mozilla
1300