tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

SVGIntegrationUtils.cpp (48914B)


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