Skip to content

Commit

Permalink
Bug 1870200 - Break the cyclic dependency if any of the desendants us…
Browse files Browse the repository at this point in the history
…es non-scaling-stroke. r=emilio

While we are computing the transform-box:stroke-box, for CSS Transforms or
Motion path Transforms, we have to avoid the cyclic dependency not only
for the SVG geometry frame itself, but also for the desendants of
the SVG container frame. Therefore, we just compute its fill-box (i.e.
make |getStroke| be false).

w3c/csswg-drafts#9640

Differential Revision: https://phabricator.services.mozilla.com/D203184
  • Loading branch information
BorisChiou committed Mar 4, 2024
1 parent fc2c64f commit c24d294
Show file tree
Hide file tree
Showing 7 changed files with 78 additions and 17 deletions.
8 changes: 5 additions & 3 deletions layout/base/MotionPathUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -111,9 +111,11 @@ CSSCoord MotionPathUtils::GetRayContainReferenceSize(nsIFrame* aFrame) {
const auto size = CSSSize::FromAppUnits(
(aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)
? nsLayoutUtils::ComputeSVGReferenceRect(
aFrame, aFrame->StyleSVGReset()->HasNonScalingStroke()
? StyleGeometryBox::FillBox
: StyleGeometryBox::StrokeBox)
aFrame,
aFrame->StyleSVGReset()->HasNonScalingStroke()
? StyleGeometryBox::FillBox
: StyleGeometryBox::StrokeBox,
nsLayoutUtils::MayHaveNonScalingStrokeCyclicDependency::Yes)
: nsLayoutUtils::ComputeHTMLReferenceRect(
aFrame, StyleGeometryBox::BorderBox))
.Size());
Expand Down
14 changes: 9 additions & 5 deletions layout/base/nsLayoutUtils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9492,8 +9492,9 @@ nsRect nsLayoutUtils::ComputeSVGOriginBox(SVGViewportElement* aElement) {
}

/* static */
nsRect nsLayoutUtils::ComputeSVGReferenceRect(nsIFrame* aFrame,
StyleGeometryBox aGeometryBox) {
nsRect nsLayoutUtils::ComputeSVGReferenceRect(
nsIFrame* aFrame, StyleGeometryBox aGeometryBox,
MayHaveNonScalingStrokeCyclicDependency aMayHaveCyclicDependency) {
MOZ_ASSERT(aFrame->GetContent()->IsSVGElement());
nsRect r;

Expand All @@ -9502,9 +9503,12 @@ nsRect nsLayoutUtils::ComputeSVGReferenceRect(nsIFrame* aFrame,
// XXX Bug 1299876
// The size of stroke-box is not correct if this graphic element has
// specific stroke-linejoin or stroke-linecap.
gfxRect bbox =
SVGUtils::GetBBox(aFrame, SVGUtils::eBBoxIncludeFillGeometry |
SVGUtils::eBBoxIncludeStroke);
const uint32_t flags = SVGUtils::eBBoxIncludeFillGeometry |
SVGUtils::eBBoxIncludeStroke |
(bool(aMayHaveCyclicDependency)
? SVGUtils::eAvoidCycleIfNonScalingStroke
: 0);
gfxRect bbox = SVGUtils::GetBBox(aFrame, flags);
r = nsLayoutUtils::RoundGfxRectToAppRect(bbox, AppUnitsPerCSSPixel());
break;
}
Expand Down
8 changes: 7 additions & 1 deletion layout/base/nsLayoutUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -2913,7 +2913,13 @@ class nsLayoutUtils {

// Compute the geometry box for SVG layout. The caller should map the CSS box
// into the proper SVG box.
static nsRect ComputeSVGReferenceRect(nsIFrame*, StyleGeometryBox);
// |aMayHaveCyclicDependency| is used for stroke-box to avoid the cyclic
// dependency if any of its descendants uses non-scaling-stroke.
enum class MayHaveNonScalingStrokeCyclicDependency : bool { No, Yes };
static nsRect ComputeSVGReferenceRect(
nsIFrame*, StyleGeometryBox,
MayHaveNonScalingStrokeCyclicDependency =
MayHaveNonScalingStrokeCyclicDependency::No);

// Compute the geometry box for CSS layout. The caller should map the SVG box
// into the proper CSS box.
Expand Down
3 changes: 2 additions & 1 deletion layout/style/nsStyleTransformMatrix.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,8 @@ static nsRect GetSVGBox(const nsIFrame* aFrame) {
// FIXME: Bug 1849054. We may have to update
// SVGGeometryFrame::GetBBoxContribution() to get tighter stroke bounds.
nsRect strokeBox = nsLayoutUtils::ComputeSVGReferenceRect(
const_cast<nsIFrame*>(aFrame), StyleGeometryBox::StrokeBox);
const_cast<nsIFrame*>(aFrame), StyleGeometryBox::StrokeBox,
nsLayoutUtils::MayHaveNonScalingStrokeCyclicDependency::Yes);
// The |nsIFrame::mRect| includes markers, so we have to compute the
// offsets without markers.
return nsRect{strokeBox.x - aFrame->GetPosition().x,
Expand Down
32 changes: 25 additions & 7 deletions layout/svg/SVGGeometryFrame.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -358,13 +358,31 @@ SVGBBox SVGGeometryFrame::GetBBoxContribution(const Matrix& aToBBoxUserspace,

SVGGeometryElement* element = static_cast<SVGGeometryElement*>(GetContent());

bool getFill = (aFlags & SVGUtils::eBBoxIncludeFillGeometry) ||
((aFlags & SVGUtils::eBBoxIncludeFill) &&
!StyleSVG()->mFill.kind.IsNone());

bool getStroke =
(aFlags & SVGUtils::eBBoxIncludeStrokeGeometry) ||
((aFlags & SVGUtils::eBBoxIncludeStroke) && SVGUtils::HasStroke(this));
const bool getFill = (aFlags & SVGUtils::eBBoxIncludeFillGeometry) ||
((aFlags & SVGUtils::eBBoxIncludeFill) &&
!StyleSVG()->mFill.kind.IsNone());

const bool getStroke =
((aFlags & SVGUtils::eBBoxIncludeStrokeGeometry) ||
((aFlags & SVGUtils::eBBoxIncludeStroke) &&
SVGUtils::HasStroke(this))) &&
// If this frame has non-scaling-stroke and we would like to compute its
// stroke, it may cause a potential cyclical dependency if the caller is
// for transform. In this case, we have to fall back to fill-box, so make
// |getStroke| be false.
// https://github.com/w3c/csswg-drafts/issues/9640
//
// Note:
// 1. We don't care about the computation of the markers below in this
// function because we know the callers don't set
// SVGUtils::eBBoxIncludeMarkers.
// See nsStyleTransformMatrix::GetSVGBox() and
// MotionPathUtils::GetRayContainReferenceSize() for more details.
// 2. We have to break the dependency here *again* because the geometry
// frame may be in the subtree of a SVGContainerFrame, which may not
// set non-scaling-stroke.
!(StyleSVGReset()->HasNonScalingStroke() &&
(aFlags & SVGUtils::eAvoidCycleIfNonScalingStroke));

SVGContentUtils::AutoStrokeOptions strokeOptions;
if (getStroke) {
Expand Down
6 changes: 6 additions & 0 deletions layout/svg/SVGUtils.h
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,12 @@ class SVGUtils final {
// For a frame with a clip-path, if this flag is set then the result
// will not be clipped to the bbox of the content inside the clip-path.
eDoNotClipToBBoxOfContentInsideClipPath = 1 << 10,
// For some cases, e.g. when using transform-box: stroke-box, we may have
// the cyclical dependency if any of the elements in the subtree has
// non-scaling-stroke. In this case, we should break it and use
// transform-box:fill-box instead.
// https://github.com/w3c/csswg-drafts/issues/9640
eAvoidCycleIfNonScalingStroke = 1 << 11,
};
/**
* This function in primarily for implementing the SVG DOM function getBBox()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<!DOCTYPE html>
<title>transform-box: border-box, stroke with vector-effect: non-scaling-stroke</title>
<link rel="match" href="reference/svgbox-rect-ref.html">
<link rel="help" href="https://drafts.csswg.org/css-transforms-1/#transform-box">
<link rel="help" href="https://svgwg.org/svg2-draft/coords.html#VectorEffects">
<link rel="help" href="https://github.com/w3c/csswg-drafts/issues/9640">
<meta name="assert" content="The used value of transform-box is fill-box on SVG with non-scaling-stroke"/>
<style>
#container {
fill: green;
stroke: black;
stroke-width: 20;
transform-box: border-box;
transform: scale(0.5) translateY(-100%);
}
#inner {
vector-effect: non-scaling-stroke;
}
</style>
<svg width="400" height="300">
<a id="container">
<rect id="inner" width="100" height="200" x="50" y="180"/>
</a>
</svg>

0 comments on commit c24d294

Please sign in to comment.