tor-browser

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

SVGGeometryFrame.cpp (31217B)


      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 "SVGGeometryFrame.h"
      9 
     10 // Keep others in (case-insensitive) order:
     11 #include "SVGAnimatedTransformList.h"
     12 #include "SVGMarkerFrame.h"
     13 #include "gfx2DGlue.h"
     14 #include "gfxContext.h"
     15 #include "gfxPlatform.h"
     16 #include "gfxUtils.h"
     17 #include "mozilla/PresShell.h"
     18 #include "mozilla/RefPtr.h"
     19 #include "mozilla/SVGContentUtils.h"
     20 #include "mozilla/SVGContextPaint.h"
     21 #include "mozilla/SVGObserverUtils.h"
     22 #include "mozilla/SVGUtils.h"
     23 #include "mozilla/StaticPrefs_svg.h"
     24 #include "mozilla/dom/SVGGeometryElement.h"
     25 #include "mozilla/dom/SVGGraphicsElement.h"
     26 #include "mozilla/gfx/2D.h"
     27 #include "mozilla/gfx/Helpers.h"
     28 #include "nsGkAtoms.h"
     29 #include "nsLayoutUtils.h"
     30 
     31 using namespace mozilla::dom;
     32 using namespace mozilla::gfx;
     33 using namespace mozilla::image;
     34 
     35 //----------------------------------------------------------------------
     36 // Implementation
     37 
     38 nsIFrame* NS_NewSVGGeometryFrame(mozilla::PresShell* aPresShell,
     39                                 mozilla::ComputedStyle* aStyle) {
     40  return new (aPresShell)
     41      mozilla::SVGGeometryFrame(aStyle, aPresShell->GetPresContext());
     42 }
     43 
     44 namespace mozilla {
     45 
     46 NS_IMPL_FRAMEARENA_HELPERS(SVGGeometryFrame)
     47 
     48 //----------------------------------------------------------------------
     49 // nsQueryFrame methods
     50 
     51 NS_QUERYFRAME_HEAD(SVGGeometryFrame)
     52  NS_QUERYFRAME_ENTRY(ISVGDisplayableFrame)
     53  NS_QUERYFRAME_ENTRY(SVGGeometryFrame)
     54 NS_QUERYFRAME_TAIL_INHERITING(nsIFrame)
     55 
     56 //----------------------------------------------------------------------
     57 // nsIFrame methods
     58 
     59 void SVGGeometryFrame::Init(nsIContent* aContent, nsContainerFrame* aParent,
     60                            nsIFrame* aPrevInFlow) {
     61  AddStateBits(aParent->GetStateBits() & NS_STATE_SVG_CLIPPATH_CHILD);
     62  nsIFrame::Init(aContent, aParent, aPrevInFlow);
     63 }
     64 
     65 nsresult SVGGeometryFrame::AttributeChanged(int32_t aNameSpaceID,
     66                                            nsAtom* aAttribute, AttrModType) {
     67  // We don't invalidate for transform changes (the layers code does that).
     68  // Also note that SVGTransformableElement::GetAttributeChangeHint will
     69  // return nsChangeHint_UpdateOverflow for "transform" attribute changes
     70  // and cause DoApplyRenderingChangeToTree to make the SchedulePaint call.
     71 
     72  if (aNameSpaceID == kNameSpaceID_None &&
     73      (static_cast<SVGGeometryElement*>(GetContent())
     74           ->AttributeDefinesGeometry(aAttribute))) {
     75    nsLayoutUtils::PostRestyleEvent(mContent->AsElement(), RestyleHint{0},
     76                                    nsChangeHint_InvalidateRenderingObservers);
     77    SVGUtils::ScheduleReflowSVG(this);
     78  }
     79  return NS_OK;
     80 }
     81 
     82 /* virtual */
     83 void SVGGeometryFrame::DidSetComputedStyle(ComputedStyle* aOldComputedStyle) {
     84  nsIFrame::DidSetComputedStyle(aOldComputedStyle);
     85  if (StyleSVGReset()->HasNonScalingStroke() &&
     86      (!aOldComputedStyle ||
     87       !aOldComputedStyle->StyleSVGReset()->HasNonScalingStroke())) {
     88    SVGUtils::UpdateNonScalingStrokeStateBit(this);
     89  }
     90  auto* element = static_cast<SVGGeometryElement*>(GetContent());
     91  if (!aOldComputedStyle) {
     92    element->ClearAnyCachedPath();
     93    return;
     94  }
     95 
     96  const auto* oldStyleSVG = aOldComputedStyle->StyleSVG();
     97  if (!SVGContentUtils::ShapeTypeHasNoCorners(GetContent())) {
     98    if (StyleSVG()->mStrokeLinecap != oldStyleSVG->mStrokeLinecap &&
     99        element->IsSVGElement(nsGkAtoms::path)) {
    100      // If the stroke-linecap changes to or from "butt" then our element
    101      // needs to update its cached Moz2D Path, since SVGPathData::BuildPath
    102      // decides whether or not to insert little lines into the path for zero
    103      // length subpaths base on that property.
    104      element->ClearAnyCachedPath();
    105    } else if (HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD)) {
    106      if (StyleSVG()->mClipRule != oldStyleSVG->mClipRule) {
    107        // Moz2D Path objects are fill-rule specific.
    108        // For clipPath we use clip-rule as the path's fill-rule.
    109        element->ClearAnyCachedPath();
    110      }
    111    } else if (StyleSVG()->mFillRule != oldStyleSVG->mFillRule) {
    112      // Moz2D Path objects are fill-rule specific.
    113      element->ClearAnyCachedPath();
    114    }
    115  }
    116 
    117  if (StyleDisplay()->CalcTransformPropertyDifference(
    118          *aOldComputedStyle->StyleDisplay())) {
    119    NotifySVGChanged(ChangeFlag::TransformChanged);
    120  }
    121 
    122  if (element->IsGeometryChangedViaCSS(*Style(), *aOldComputedStyle) ||
    123      aOldComputedStyle->EffectiveZoom() != Style()->EffectiveZoom()) {
    124    element->ClearAnyCachedPath();
    125    SVGObserverUtils::InvalidateRenderingObservers(this);
    126  }
    127 }
    128 
    129 bool SVGGeometryFrame::DoGetParentSVGTransforms(
    130    gfx::Matrix* aFromParentTransform) const {
    131  return SVGUtils::GetParentSVGTransforms(this, aFromParentTransform);
    132 }
    133 
    134 void SVGGeometryFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
    135                                        const nsDisplayListSet& aLists) {
    136  if (!static_cast<const SVGElement*>(GetContent())->HasValidDimensions()) {
    137    return;
    138  }
    139 
    140  if (aBuilder->IsForPainting()) {
    141    if (!IsVisibleForPainting()) {
    142      return;
    143    }
    144    if (StyleEffects()->IsTransparent() && SVGUtils::CanOptimizeOpacity(this)) {
    145      return;
    146    }
    147    const auto* styleSVG = StyleSVG();
    148    if (styleSVG->mFill.kind.IsNone() && styleSVG->mStroke.kind.IsNone() &&
    149        !styleSVG->HasMarker()) {
    150      return;
    151    }
    152 
    153    aBuilder->BuildCompositorHitTestInfoIfNeeded(this,
    154                                                 aLists.BorderBackground());
    155  }
    156 
    157  DisplayOutline(aBuilder, aLists);
    158  aLists.Content()->AppendNewToTop<DisplaySVGGeometry>(aBuilder, this);
    159 }
    160 
    161 //----------------------------------------------------------------------
    162 // ISVGDisplayableFrame methods
    163 
    164 void SVGGeometryFrame::PaintSVG(gfxContext& aContext,
    165                                const gfxMatrix& aTransform,
    166                                imgDrawingParams& aImgParams) {
    167  if (!StyleVisibility()->IsVisible()) {
    168    return;
    169  }
    170 
    171  // Matrix to the geometry's user space:
    172  gfxMatrix newMatrix =
    173      aContext.CurrentMatrixDouble().PreMultiply(aTransform).NudgeToIntegers();
    174  if (newMatrix.IsSingular()) {
    175    return;
    176  }
    177 
    178  uint32_t paintOrder = StyleSVG()->mPaintOrder;
    179  if (!paintOrder) {
    180    Render(&aContext, RenderFlags(RenderFlag::Fill, RenderFlag::Stroke),
    181           newMatrix, aImgParams);
    182    PaintMarkers(aContext, aTransform, aImgParams);
    183  } else {
    184    while (paintOrder) {
    185      auto component = StylePaintOrder(paintOrder & kPaintOrderMask);
    186      switch (component) {
    187        case StylePaintOrder::Fill:
    188          Render(&aContext, RenderFlag::Fill, newMatrix, aImgParams);
    189          break;
    190        case StylePaintOrder::Stroke:
    191          Render(&aContext, RenderFlag::Stroke, newMatrix, aImgParams);
    192          break;
    193        case StylePaintOrder::Markers:
    194          PaintMarkers(aContext, aTransform, aImgParams);
    195          break;
    196        default:
    197          MOZ_FALLTHROUGH_ASSERT("Unknown paint-order variant, how?");
    198        case StylePaintOrder::Normal:
    199          break;
    200      }
    201      paintOrder >>= kPaintOrderShift;
    202    }
    203  }
    204 }
    205 
    206 nsIFrame* SVGGeometryFrame::GetFrameForPoint(const gfxPoint& aPoint) {
    207  FillRule fillRule;
    208  uint16_t hitTestFlags;
    209  if (HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD)) {
    210    hitTestFlags = SVG_HIT_TEST_FILL;
    211    fillRule = SVGUtils::ToFillRule(StyleSVG()->mClipRule);
    212  } else {
    213    hitTestFlags = SVGUtils::GetGeometryHitTestFlags(this);
    214    if (!hitTestFlags) {
    215      return nullptr;
    216    }
    217    fillRule = SVGUtils::ToFillRule(StyleSVG()->mFillRule);
    218  }
    219 
    220  bool isHit = false;
    221 
    222  SVGGeometryElement* content = static_cast<SVGGeometryElement*>(GetContent());
    223 
    224  // Using ScreenReferenceDrawTarget() opens us to Moz2D backend specific hit-
    225  // testing bugs. Maybe we should use a BackendType::CAIRO DT for hit-testing
    226  // so that we get more consistent/backwards compatible results?
    227  RefPtr<DrawTarget> drawTarget =
    228      gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
    229  RefPtr<Path> path = content->GetOrBuildPath(drawTarget, fillRule);
    230  if (!path) {
    231    return nullptr;  // no path, so we don't paint anything that can be hit
    232  }
    233 
    234  if (hitTestFlags & SVG_HIT_TEST_FILL) {
    235    isHit = path->ContainsPoint(ToPoint(aPoint), {});
    236  }
    237  if (!isHit && (hitTestFlags & SVG_HIT_TEST_STROKE)) {
    238    Point point = ToPoint(aPoint);
    239    SVGContentUtils::AutoStrokeOptions stroke;
    240    SVGContentUtils::GetStrokeOptions(&stroke, content, Style(), nullptr);
    241    gfxMatrix userToOuterSVG;
    242    if (SVGUtils::GetNonScalingStrokeTransform(this, &userToOuterSVG)) {
    243      // We need to transform the path back into the appropriate ancestor
    244      // coordinate system in order for non-scaled stroke to be correct.
    245      // Naturally we also need to transform the point into the same
    246      // coordinate system in order to hit-test against the path.
    247      point = ToMatrix(userToOuterSVG).TransformPoint(point);
    248      Path::TransformAndSetFillRule(path, ToMatrix(userToOuterSVG), fillRule);
    249    }
    250    isHit = path->StrokeContainsPoint(stroke, point, {});
    251  }
    252 
    253  if (isHit && SVGUtils::HitTestClip(this, aPoint)) {
    254    return this;
    255  }
    256 
    257  return nullptr;
    258 }
    259 
    260 void SVGGeometryFrame::ReflowSVG() {
    261  NS_ASSERTION(SVGUtils::OuterSVGIsCallingReflowSVG(this),
    262               "This call is probably a wasteful mistake");
    263 
    264  MOZ_ASSERT(!HasAnyStateBits(NS_FRAME_IS_NONDISPLAY),
    265             "ReflowSVG mechanism not designed for this");
    266 
    267  if (!SVGUtils::NeedsReflowSVG(this)) {
    268    return;
    269  }
    270 
    271  uint32_t flags = SVGUtils::eBBoxIncludeFillGeometry |
    272                   SVGUtils::eBBoxIncludeStroke | SVGUtils::eBBoxIncludeMarkers;
    273 
    274  // Our "visual" overflow rect needs to be valid for building display lists
    275  // for hit testing, which means that for certain values of 'pointer-events'
    276  // it needs to include the geometry of the fill or stroke even when the fill/
    277  // stroke don't actually render (e.g. when stroke="none" or
    278  // stroke-opacity="0"). GetGeometryHitTestFlags() accounts for
    279  // 'pointer-events'.
    280  uint16_t hitTestFlags = SVGUtils::GetGeometryHitTestFlags(this);
    281  if (hitTestFlags & SVG_HIT_TEST_FILL) {
    282    flags |= SVGUtils::eBBoxIncludeFillGeometry;
    283  }
    284  if (hitTestFlags & SVG_HIT_TEST_STROKE) {
    285    flags |= SVGUtils::eBBoxIncludeStrokeGeometry;
    286  }
    287 
    288  SVGBBox extent = GetBBoxContribution({}, flags).ToThebesRect();
    289  mRect = nsLayoutUtils::RoundGfxRectToAppRect((const Rect&)extent,
    290                                               AppUnitsPerCSSPixel());
    291 
    292  if (HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
    293    // Make sure we have our filter property (if any) before calling
    294    // FinishAndStoreOverflow (subsequent filter changes are handled off
    295    // nsChangeHint_UpdateEffects):
    296    SVGObserverUtils::UpdateEffects(this);
    297  }
    298 
    299  nsRect overflow = nsRect(nsPoint(0, 0), mRect.Size());
    300  OverflowAreas overflowAreas(overflow, overflow);
    301  FinishAndStoreOverflow(overflowAreas, mRect.Size());
    302 
    303  RemoveStateBits(NS_FRAME_FIRST_REFLOW | NS_FRAME_IS_DIRTY |
    304                  NS_FRAME_HAS_DIRTY_CHILDREN);
    305 
    306  // Invalidate, but only if this is not our first reflow (since if it is our
    307  // first reflow then we haven't had our first paint yet).
    308  if (!GetParent()->HasAnyStateBits(NS_FRAME_FIRST_REFLOW)) {
    309    InvalidateFrame();
    310  }
    311 }
    312 
    313 void SVGGeometryFrame::NotifySVGChanged(ChangeFlags aFlags) {
    314  MOZ_ASSERT(aFlags.contains(ChangeFlag::TransformChanged) ||
    315                 aFlags.contains(ChangeFlag::CoordContextChanged),
    316             "Invalidation logic may need adjusting");
    317 
    318  // Changes to our ancestors may affect how we render when we are rendered as
    319  // part of our ancestor (specifically, if our coordinate context changes size
    320  // and we have percentage lengths defining our geometry, then we need to be
    321  // reflowed). However, ancestor changes cannot affect how we render when we
    322  // are rendered as part of any rendering observers that we may have.
    323  // Therefore no need to notify rendering observers here.
    324 
    325  // Don't try to be too smart trying to avoid the ScheduleReflowSVG calls
    326  // for the stroke properties examined below. Checking HasStroke() is not
    327  // enough, since what we care about is whether we include the stroke in our
    328  // overflow rects or not, and we sometimes deliberately include stroke
    329  // when it's not visible. See the complexities of GetBBoxContribution.
    330 
    331  if (aFlags.contains(ChangeFlag::CoordContextChanged)) {
    332    auto* geom = static_cast<SVGGeometryElement*>(GetContent());
    333    // Stroke currently contributes to our mRect, which is why we have to take
    334    // account of stroke-width here. Note that we do not need to take account
    335    // of stroke-dashoffset since, although that can have a percentage value
    336    // that is resolved against our coordinate context, it does not affect our
    337    // mRect.
    338    const auto& strokeWidth = StyleSVG()->mStrokeWidth;
    339    if (geom->GeometryDependsOnCoordCtx() ||
    340        (strokeWidth.IsLengthPercentage() &&
    341         strokeWidth.AsLengthPercentage().HasPercent())) {
    342      geom->ClearAnyCachedPath();
    343      SVGUtils::ScheduleReflowSVG(this);
    344    }
    345  }
    346 
    347  if (aFlags.contains(ChangeFlag::TransformChanged) &&
    348      StyleSVGReset()->HasNonScalingStroke()) {
    349    // Stroke currently contributes to our mRect, and our stroke depends on
    350    // the transform to our outer-<svg> if |vector-effect:non-scaling-stroke|.
    351    SVGUtils::ScheduleReflowSVG(this);
    352  }
    353 }
    354 
    355 SVGBBox SVGGeometryFrame::GetBBoxContribution(const Matrix& aToBBoxUserspace,
    356                                              uint32_t aFlags) {
    357  SVGBBox bbox;
    358 
    359  if (aToBBoxUserspace.IsSingular()) {
    360    // XXX ReportToConsole
    361    return bbox;
    362  }
    363 
    364  if ((aFlags & SVGUtils::eForGetClientRects) &&
    365      aToBBoxUserspace.PreservesAxisAlignedRectangles()) {
    366    if (!mRect.IsEmpty()) {
    367      Rect rect = NSRectToRect(mRect, AppUnitsPerCSSPixel());
    368      bbox = aToBBoxUserspace.TransformBounds(rect);
    369    }
    370    return bbox;
    371  }
    372 
    373  SVGGeometryElement* element = static_cast<SVGGeometryElement*>(GetContent());
    374 
    375  const bool getFill = (aFlags & SVGUtils::eBBoxIncludeFillGeometry) ||
    376                       ((aFlags & SVGUtils::eBBoxIncludeFill) &&
    377                        !StyleSVG()->mFill.kind.IsNone());
    378 
    379  const bool getStroke =
    380      ((aFlags & SVGUtils::eBBoxIncludeStrokeGeometry) ||
    381       ((aFlags & SVGUtils::eBBoxIncludeStroke) &&
    382        SVGUtils::HasStroke(this))) &&
    383      // If this frame has non-scaling-stroke and we would like to compute its
    384      // stroke, it may cause a potential cyclical dependency if the caller is
    385      // for transform. In this case, we have to fall back to fill-box, so make
    386      // |getStroke| be false.
    387      // https://github.com/w3c/csswg-drafts/issues/9640
    388      //
    389      // Note:
    390      // 1. We don't care about the computation of the markers below in this
    391      //    function because we know the callers don't set
    392      //    SVGUtils::eBBoxIncludeMarkers.
    393      //    See nsStyleTransformMatrix::GetSVGBox() and
    394      //    MotionPathUtils::GetRayContainReferenceSize() for more details.
    395      // 2. We have to break the dependency here *again* because the geometry
    396      //    frame may be in the subtree of a SVGContainerFrame, which may not
    397      //    set non-scaling-stroke.
    398      !(StyleSVGReset()->HasNonScalingStroke() &&
    399        (aFlags & SVGUtils::eAvoidCycleIfNonScalingStroke));
    400 
    401  SVGContentUtils::AutoStrokeOptions strokeOptions;
    402  if (getStroke) {
    403    SVGContentUtils::GetStrokeOptions(
    404        &strokeOptions, element, Style(), nullptr,
    405        SVGContentUtils::StrokeOptionFlag::IgnoreStrokeDashing);
    406  } else {
    407    // Override the default line width of 1.f so that when we call
    408    // GetGeometryBounds below the result doesn't include stroke bounds.
    409    strokeOptions.mLineWidth = 0.f;
    410  }
    411 
    412  Rect simpleBounds;
    413  bool gotSimpleBounds = false;
    414  gfxMatrix userToOuterSVG;
    415  if (getStroke &&
    416      SVGUtils::GetNonScalingStrokeTransform(this, &userToOuterSVG)) {
    417    Matrix moz2dUserToOuterSVG = ToMatrix(userToOuterSVG);
    418    if (moz2dUserToOuterSVG.IsSingular()) {
    419      return bbox;
    420    }
    421    gotSimpleBounds = element->GetGeometryBounds(
    422        &simpleBounds, strokeOptions, aToBBoxUserspace, &moz2dUserToOuterSVG);
    423  } else if (getFill || getStroke) {
    424    gotSimpleBounds = element->GetGeometryBounds(&simpleBounds, strokeOptions,
    425                                                 aToBBoxUserspace);
    426  }
    427 
    428  if (gotSimpleBounds) {
    429    bbox = simpleBounds;
    430  } else {
    431    RefPtr<Path> pathInBBoxSpace;
    432    RefPtr<Path> pathInUserSpace;
    433    if (getFill || getStroke) {
    434      // Get the bounds using a Moz2D Path object (more expensive):
    435      RefPtr<DrawTarget> tmpDT;
    436      tmpDT = gfxPlatform::GetPlatform()->ScreenReferenceDrawTarget();
    437 
    438      FillRule fillRule = SVGUtils::ToFillRule(
    439          HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD) ? StyleSVG()->mClipRule
    440                                                       : StyleSVG()->mFillRule);
    441      pathInUserSpace = element->GetOrBuildPath(tmpDT, fillRule);
    442      if (!pathInUserSpace) {
    443        return bbox;
    444      }
    445      if (aToBBoxUserspace.IsIdentity()) {
    446        pathInBBoxSpace = pathInUserSpace;
    447      } else {
    448        RefPtr<PathBuilder> builder = pathInUserSpace->TransformedCopyToBuilder(
    449            aToBBoxUserspace, fillRule);
    450        pathInBBoxSpace = builder->Finish();
    451        if (!pathInBBoxSpace) {
    452          return bbox;
    453        }
    454      }
    455    }
    456 
    457    // Account for fill:
    458    if (getFill && !getStroke) {
    459      Rect pathBBoxExtents = pathInBBoxSpace->GetBounds();
    460      if (!pathBBoxExtents.IsFinite()) {
    461        // This can happen in the case that we only have a move-to command in
    462        // the path commands, in which case we know nothing gets rendered.
    463        return bbox;
    464      }
    465      bbox = pathBBoxExtents;
    466    }
    467 
    468    // Account for stroke:
    469    if (getStroke) {
    470      // Be careful when replacing the following logic to get the fill and
    471      // stroke extents independently.
    472      // You may think that you can just use the stroke extents if
    473      // there is both a fill and a stroke. In reality it may be necessary to
    474      // calculate both the fill and stroke extents.
    475      // There are two reasons for this:
    476      //
    477      // # Due to stroke dashing, in certain cases the fill extents could
    478      //   actually extend outside the stroke extents.
    479      // # If the stroke is very thin, cairo won't paint any stroke, and so the
    480      //   stroke bounds that it will return will be empty.
    481 
    482      Rect strokeBBoxExtents;
    483      if (StaticPrefs::svg_Moz2D_strokeBounds_enabled()) {
    484        gfxMatrix userToOuterSVG;
    485        if (SVGUtils::GetNonScalingStrokeTransform(this, &userToOuterSVG)) {
    486          Matrix outerSVGToUser = ToMatrix(userToOuterSVG);
    487          outerSVGToUser.Invert();
    488          Matrix outerSVGToBBox = aToBBoxUserspace * outerSVGToUser;
    489          RefPtr<PathBuilder> builder =
    490              pathInUserSpace->TransformedCopyToBuilder(
    491                  ToMatrix(userToOuterSVG));
    492          RefPtr<Path> pathInOuterSVGSpace = builder->Finish();
    493          strokeBBoxExtents = pathInOuterSVGSpace->GetStrokedBounds(
    494              strokeOptions, outerSVGToBBox);
    495        } else {
    496          strokeBBoxExtents = pathInUserSpace->GetStrokedBounds(
    497              strokeOptions, aToBBoxUserspace);
    498        }
    499        if (strokeBBoxExtents.IsEmpty() && getFill) {
    500          strokeBBoxExtents = pathInBBoxSpace->GetBounds();
    501          if (!strokeBBoxExtents.IsFinite()) {
    502            return bbox;
    503          }
    504        }
    505      } else {
    506        Rect pathBBoxExtents = pathInBBoxSpace->GetBounds();
    507        if (!pathBBoxExtents.IsFinite()) {
    508          return bbox;
    509        }
    510        strokeBBoxExtents = ToRect(SVGUtils::PathExtentsToMaxStrokeExtents(
    511            ThebesRect(pathBBoxExtents), this, ThebesMatrix(aToBBoxUserspace)));
    512      }
    513      MOZ_ASSERT(strokeBBoxExtents.IsFinite(), "bbox is about to go bad");
    514      bbox.UnionEdges(strokeBBoxExtents);
    515    }
    516  }
    517 
    518  // Account for markers:
    519  if ((aFlags & SVGUtils::eBBoxIncludeMarkers) && element->IsMarkable()) {
    520    SVGMarkerFrame* markerFrames[SVGMark::eTypeCount];
    521    if (SVGObserverUtils::GetAndObserveMarkers(this, &markerFrames)) {
    522      nsTArray<SVGMark> marks;
    523      element->GetMarkPoints(&marks);
    524      if (uint32_t num = marks.Length()) {
    525        float strokeWidth = SVGUtils::GetStrokeWidth(this);
    526        for (uint32_t i = 0; i < num; i++) {
    527          const SVGMark& mark = marks[i];
    528          SVGMarkerFrame* frame = markerFrames[mark.type];
    529          if (frame) {
    530            SVGBBox mbbox = frame->GetMarkBBoxContribution(
    531                aToBBoxUserspace, aFlags, this, mark, strokeWidth);
    532            MOZ_ASSERT(mbbox.IsFinite(), "bbox is about to go bad");
    533            bbox.UnionEdges(mbbox);
    534          }
    535        }
    536      }
    537    }
    538  }
    539 
    540  return bbox;
    541 }
    542 
    543 //----------------------------------------------------------------------
    544 // SVGGeometryFrame methods:
    545 
    546 gfxMatrix SVGGeometryFrame::GetCanvasTM() {
    547  NS_ASSERTION(GetParent(), "null parent");
    548 
    549  auto* parent = static_cast<SVGContainerFrame*>(GetParent());
    550  auto* content = static_cast<SVGGraphicsElement*>(GetContent());
    551  return content->ChildToUserSpaceTransform() * parent->GetCanvasTM();
    552 }
    553 
    554 void SVGGeometryFrame::Render(gfxContext* aContext,
    555                              RenderFlags aRenderComponents,
    556                              const gfxMatrix& aTransform,
    557                              imgDrawingParams& aImgParams) {
    558  MOZ_ASSERT(!aTransform.IsSingular());
    559 
    560  DrawTarget* drawTarget = aContext->GetDrawTarget();
    561 
    562  MOZ_ASSERT(drawTarget);
    563  if (!drawTarget->IsValid()) {
    564    return;
    565  }
    566 
    567  FillRule fillRule = SVGUtils::ToFillRule(
    568      HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD) ? StyleSVG()->mClipRule
    569                                                   : StyleSVG()->mFillRule);
    570 
    571  SVGGeometryElement* element = static_cast<SVGGeometryElement*>(GetContent());
    572 
    573  AntialiasMode aaMode = SVGUtils::ToAntialiasMode(StyleSVG()->mShapeRendering);
    574 
    575  // We wait as late as possible before setting the transform so that we don't
    576  // set it unnecessarily if we return early (it's an expensive operation for
    577  // some backends).
    578  gfxContextMatrixAutoSaveRestore autoRestoreTransform(aContext);
    579  aContext->SetMatrixDouble(aTransform);
    580 
    581  if (HasAnyStateBits(NS_STATE_SVG_CLIPPATH_CHILD)) {
    582    // We don't complicate this code with GetAsSimplePath since the cost of
    583    // masking will dwarf Path creation overhead anyway.
    584    RefPtr<Path> path = element->GetOrBuildPath(drawTarget, fillRule);
    585    if (path) {
    586      ColorPattern white(ToDeviceColor(sRGBColor(1.0f, 1.0f, 1.0f, 1.0f)));
    587      drawTarget->Fill(path, white,
    588                       DrawOptions(1.0f, CompositionOp::OP_OVER, aaMode));
    589    }
    590    return;
    591  }
    592 
    593  SVGGeometryElement::SimplePath simplePath;
    594  RefPtr<Path> path;
    595 
    596  element->GetAsSimplePath(&simplePath);
    597  if (!simplePath.IsPath()) {
    598    path = element->GetOrBuildPath(drawTarget, fillRule);
    599    if (!path) {
    600      return;
    601    }
    602  }
    603 
    604  SVGContextPaint* contextPaint =
    605      SVGContextPaint::GetContextPaint(GetContent());
    606 
    607  if (aRenderComponents.contains(RenderFlag::Fill)) {
    608    GeneralPattern fillPattern;
    609    SVGUtils::MakeFillPatternFor(this, aContext, &fillPattern, aImgParams,
    610                                 contextPaint);
    611 
    612    if (fillPattern.GetPattern()) {
    613      DrawOptions drawOptions(1.0f, CompositionOp::OP_OVER, aaMode);
    614      if (simplePath.IsRect()) {
    615        drawTarget->FillRect(simplePath.AsRect(), fillPattern, drawOptions);
    616      } else if (path) {
    617        drawTarget->Fill(path, fillPattern, drawOptions);
    618      }
    619    }
    620  }
    621 
    622  if (aRenderComponents.contains(RenderFlag::Stroke) &&
    623      SVGUtils::HasStroke(this, contextPaint)) {
    624    // Account for vector-effect:non-scaling-stroke:
    625    gfxMatrix userToOuterSVG;
    626    if (SVGUtils::GetNonScalingStrokeTransform(this, &userToOuterSVG)) {
    627      // A simple Rect can't be transformed with rotate/skew, so let's switch
    628      // to using a real path:
    629      if (!path) {
    630        path = element->GetOrBuildPath(drawTarget, fillRule);
    631        if (!path) {
    632          return;
    633        }
    634        simplePath.Reset();
    635      }
    636      // We need to transform the path back into the appropriate ancestor
    637      // coordinate system, and paint it it that coordinate system, in order
    638      // for non-scaled stroke to paint correctly.
    639      gfxMatrix outerSVGToUser = userToOuterSVG;
    640      outerSVGToUser.Invert();
    641      aContext->Multiply(outerSVGToUser);
    642      Path::TransformAndSetFillRule(path, ToMatrix(userToOuterSVG), fillRule);
    643    }
    644    GeneralPattern strokePattern;
    645    SVGUtils::MakeStrokePatternFor(this, aContext, &strokePattern, aImgParams,
    646                                   contextPaint);
    647 
    648    if (strokePattern.GetPattern()) {
    649      SVGContentUtils::AutoStrokeOptions strokeOptions;
    650      SVGContentUtils::GetStrokeOptions(&strokeOptions,
    651                                        static_cast<SVGElement*>(GetContent()),
    652                                        Style(), contextPaint);
    653      // GetStrokeOptions may set the line width to zero as an optimization
    654      if (strokeOptions.mLineWidth <= 0) {
    655        return;
    656      }
    657      DrawOptions drawOptions(1.0f, CompositionOp::OP_OVER, aaMode);
    658      if (simplePath.IsRect()) {
    659        drawTarget->StrokeRect(simplePath.AsRect(), strokePattern,
    660                               strokeOptions, drawOptions);
    661      } else if (simplePath.IsLine()) {
    662        drawTarget->StrokeLine(simplePath.Point1(), simplePath.Point2(),
    663                               strokePattern, strokeOptions, drawOptions);
    664      } else {
    665        drawTarget->Stroke(path, strokePattern, strokeOptions, drawOptions);
    666      }
    667    }
    668  }
    669 }
    670 
    671 bool SVGGeometryFrame::IsInvisible() const {
    672  if (!StyleVisibility()->IsVisible()) {
    673    return true;
    674  }
    675 
    676  // Anything below will round to zero later down the pipeline.
    677  constexpr float opacity_threshold = 1.0 / 128.0;
    678 
    679  if (StyleEffects()->mOpacity <= opacity_threshold &&
    680      SVGUtils::CanOptimizeOpacity(this)) {
    681    return true;
    682  }
    683 
    684  const nsStyleSVG* style = StyleSVG();
    685  SVGContextPaint* contextPaint =
    686      SVGContextPaint::GetContextPaint(GetContent());
    687 
    688  if (!style->mFill.kind.IsNone()) {
    689    float opacity = SVGUtils::GetOpacity(style->mFillOpacity, contextPaint);
    690    if (opacity > opacity_threshold) {
    691      return false;
    692    }
    693  }
    694 
    695  if (!style->mStroke.kind.IsNone()) {
    696    float opacity = SVGUtils::GetOpacity(style->mStrokeOpacity, contextPaint);
    697    if (opacity > opacity_threshold) {
    698      return false;
    699    }
    700  }
    701 
    702  if (style->HasMarker()) {
    703    return false;
    704  }
    705 
    706  return true;
    707 }
    708 
    709 bool SVGGeometryFrame::CreateWebRenderCommands(
    710    mozilla::wr::DisplayListBuilder& aBuilder,
    711    mozilla::wr::IpcResourceUpdateQueue& aResources,
    712    const mozilla::layers::StackingContextHelper& aSc,
    713    mozilla::layers::RenderRootStateManager* aManager,
    714    nsDisplayListBuilder* aDisplayListBuilder, DisplaySVGGeometry* aItem,
    715    bool aDryRun) {
    716  MOZ_ASSERT(StyleVisibility()->IsVisible());
    717 
    718  SVGGeometryElement* element = static_cast<SVGGeometryElement*>(GetContent());
    719 
    720  SVGGeometryElement::SimplePath simplePath;
    721  element->GetAsSimplePath(&simplePath);
    722 
    723  if (!simplePath.IsRect()) {
    724    return false;
    725  }
    726 
    727  const nsStyleSVG* style = StyleSVG();
    728  MOZ_ASSERT(style);
    729 
    730  if (!style->mFill.kind.IsColor()) {
    731    return false;
    732  }
    733 
    734  switch (style->mFill.kind.tag) {
    735    case StyleSVGPaintKind::Tag::Color:
    736      break;
    737    default:
    738      return false;
    739  }
    740 
    741  if (!style->mStroke.kind.IsNone()) {
    742    return false;
    743  }
    744 
    745  if (StyleEffects()->HasMixBlendMode()) {
    746    // FIXME: not implemented
    747    return false;
    748  }
    749 
    750  if (style->HasMarker() && element->IsMarkable()) {
    751    // Markers aren't suppported yet.
    752    return false;
    753  }
    754 
    755  if (!aDryRun) {
    756    auto appUnitsPerDevPx = PresContext()->AppUnitsPerDevPixel();
    757    float scale = (float)AppUnitsPerCSSPixel() / (float)appUnitsPerDevPx;
    758 
    759    auto rect = simplePath.AsRect();
    760    rect.Scale(scale);
    761 
    762    auto offset = LayoutDevicePoint::FromAppUnits(
    763        aItem->ToReferenceFrame() - GetPosition(), appUnitsPerDevPx);
    764    rect.MoveBy(offset.x, offset.y);
    765 
    766    auto wrRect = wr::ToLayoutRect(rect);
    767 
    768    SVGContextPaint* contextPaint =
    769        SVGContextPaint::GetContextPaint(GetContent());
    770    // At the moment this code path doesn't support strokes so it fine to
    771    // combine the rectangle's opacity (which has to be applied on the result)
    772    // of (filling + stroking) with the fill opacity.
    773 
    774    float elemOpacity = 1.0f;
    775    if (SVGUtils::CanOptimizeOpacity(this)) {
    776      elemOpacity = StyleEffects()->mOpacity;
    777    }
    778 
    779    float fillOpacity = SVGUtils::GetOpacity(style->mFillOpacity, contextPaint);
    780    float opacity = elemOpacity * fillOpacity;
    781 
    782    auto color = wr::ToColorF(
    783        ToDeviceColor(StyleSVG()->mFill.kind.AsColor().CalcColor(this)));
    784    color.a *= opacity;
    785    aBuilder.PushRect(wrRect, wrRect, !aItem->BackfaceIsHidden(), true, false,
    786                      color);
    787  }
    788 
    789  return true;
    790 }
    791 
    792 void SVGGeometryFrame::PaintMarkers(gfxContext& aContext,
    793                                    const gfxMatrix& aTransform,
    794                                    imgDrawingParams& aImgParams) {
    795  auto* element = static_cast<SVGGeometryElement*>(GetContent());
    796  if (!element->IsMarkable()) {
    797    return;
    798  }
    799  SVGMarkerFrame* markerFrames[SVGMark::eTypeCount];
    800  if (!SVGObserverUtils::GetAndObserveMarkers(this, &markerFrames)) {
    801    return;
    802  }
    803  nsTArray<SVGMark> marks;
    804  element->GetMarkPoints(&marks);
    805  if (marks.IsEmpty()) {
    806    return;
    807  }
    808  float strokeWidth = GetStrokeWidthForMarkers();
    809  for (const SVGMark& mark : marks) {
    810    if (auto* frame = markerFrames[mark.type]) {
    811      frame->PaintMark(aContext, aTransform, this, mark, strokeWidth,
    812                       aImgParams);
    813    }
    814  }
    815 }
    816 
    817 float SVGGeometryFrame::GetStrokeWidthForMarkers() {
    818  float strokeWidth = SVGUtils::GetStrokeWidth(
    819      this, SVGContextPaint::GetContextPaint(GetContent()));
    820  gfxMatrix userToOuterSVG;
    821  if (SVGUtils::GetNonScalingStrokeTransform(this, &userToOuterSVG)) {
    822    // We're not interested in any translation here so we can treat this as
    823    // Singular Value Decomposition (SVD) of a 2 x 2 matrix. That would give us
    824    // sx and sy values as the X and Y scales. The value we want is the XY
    825    // scale i.e. the normalised hypotenuse, which is sqrt(sx^2 + sy^2) /
    826    // sqrt(2). If we use the formulae from
    827    // https://scicomp.stackexchange.com/a/14103, we discover that the
    828    // normalised hypotenuse is simply the square root of the sum of the squares
    829    // of all the 2D matrix elements divided by sqrt(2).
    830    //
    831    // Note that this may need adjusting to support 3D transforms properly.
    832 
    833    strokeWidth /= float(sqrt(userToOuterSVG._11 * userToOuterSVG._11 +
    834                              userToOuterSVG._12 * userToOuterSVG._12 +
    835                              userToOuterSVG._21 * userToOuterSVG._21 +
    836                              userToOuterSVG._22 * userToOuterSVG._22) /
    837                         M_SQRT2);
    838  }
    839  return strokeWidth;
    840 }
    841 
    842 }  // namespace mozilla