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