Bug 1852323 - Part 2: Fix the mapping of StyleGeometryBox for mask-clip. r=emilio

Also, drop `nsLayoutUtils::ComputeGeometryBox()` and avoid doing mapping
in `ComputeHTMLReferenceRect()` and `ComputeSVGReferenceRect()`. The
caller should handle it properly because each property uses its own mapping
between CSS box and SVG box.

Note:
1. mask-clip-3.html is copied from mask-clip-1.html but just replace
   css boxes with svg boxes.
2. mask-clip-4.html is copied from mask-clip-2.html but just replace
   svg boxed with css boxes.

Differential Revision: https://phabricator.services.mozilla.com/D188316
This commit is contained in:
Boris Chiou 2023-10-10 22:00:32 +00:00
parent 5213031e3e
commit 192403f967
9 changed files with 276 additions and 79 deletions

View File

@ -39,13 +39,34 @@ CSSPoint MotionPathUtils::ComputeAnchorPointAdjustment(const nsIFrame& aFrame) {
}
if (aFrame.IsFrameOfType(nsIFrame::eSVGContainer)) {
nsRect boxRect = nsLayoutUtils::ComputeGeometryBox(
nsRect boxRect = nsLayoutUtils::ComputeSVGReferenceRect(
const_cast<nsIFrame*>(&aFrame), StyleGeometryBox::FillBox);
return CSSPoint::FromAppUnits(boxRect.TopLeft());
}
return CSSPoint::FromAppUnits(aFrame.GetPosition());
}
// Convert the StyleCoordBox into the StyleGeometryBox in CSS layout.
// https://drafts.csswg.org/css-box-4/#keywords
static StyleGeometryBox CoordBoxToGeometryBoxInCSSLayout(
StyleCoordBox aCoordBox) {
switch (aCoordBox) {
case StyleCoordBox::ContentBox:
return StyleGeometryBox::ContentBox;
case StyleCoordBox::PaddingBox:
return StyleGeometryBox::PaddingBox;
case StyleCoordBox::BorderBox:
return StyleGeometryBox::BorderBox;
case StyleCoordBox::FillBox:
return StyleGeometryBox::ContentBox;
case StyleCoordBox::StrokeBox:
case StyleCoordBox::ViewBox:
return StyleGeometryBox::BorderBox;
}
MOZ_ASSERT_UNREACHABLE("Unknown coord-box type");
return StyleGeometryBox::BorderBox;
}
/* static */
const nsIFrame* MotionPathUtils::GetOffsetPathReferenceBox(
const nsIFrame* aFrame, nsRect& aOutputRect) {
@ -67,7 +88,7 @@ const nsIFrame* MotionPathUtils::GetOffsetPathReferenceBox(
? offsetPath.AsCoordBox()
: offsetPath.AsOffsetPath().coord_box;
aOutputRect = nsLayoutUtils::ComputeHTMLReferenceRect(
containingBlock, nsLayoutUtils::CoordBoxToGeometryBox(coordBox));
containingBlock, CoordBoxToGeometryBoxInCSSLayout(coordBox));
return containingBlock;
}
@ -78,15 +99,14 @@ CSSCoord MotionPathUtils::GetRayContainReferenceSize(nsIFrame* aFrame) {
// https://drafts.fxtf.org/motion-1/#valdef-ray-contain
//
// Note: Per the spec, border-box is treated as stroke-box in the SVG context,
// Also, SVGUtils::GetBBox() may cache the box via the frame property, so we
// have to do const-casting.
// https://drafts.csswg.org/css-box-4/#valdef-box-border-box
CSSSize size = CSSSize::FromAppUnits(
nsLayoutUtils::ComputeGeometryBox(aFrame,
// StrokeBox and BorderBox are in the
// same switch case for CSS context.
StyleGeometryBox::StrokeBox)
.Size());
const auto size =
CSSSize::FromAppUnits((aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)
? nsLayoutUtils::ComputeSVGReferenceRect(
aFrame, StyleGeometryBox::StrokeBox)
: nsLayoutUtils::ComputeHTMLReferenceRect(
aFrame, StyleGeometryBox::BorderBox))
.Size());
return std::max(size.width, size.height);
}

View File

@ -9603,18 +9603,13 @@ nsRect nsLayoutUtils::ComputeSVGOriginBox(SVGViewportElement* aElement) {
nsPresContext::CSSPixelsToAppUnits(viewportSize.height));
}
static nsRect ComputeSVGReferenceRect(nsIFrame* aFrame,
StyleGeometryBox aGeometryBox) {
/* static */
nsRect nsLayoutUtils::ComputeSVGReferenceRect(nsIFrame* aFrame,
StyleGeometryBox aGeometryBox) {
MOZ_ASSERT(aFrame->GetContent()->IsSVGElement());
nsRect r;
// For SVG elements without associated CSS layout box, the used value for
// content-box and padding-box is fill-box and for
// border-box and margin-box is stroke-box.
switch (aGeometryBox) {
case StyleGeometryBox::NoBox:
case StyleGeometryBox::BorderBox:
case StyleGeometryBox::MarginBox:
case StyleGeometryBox::StrokeBox: {
// XXX Bug 1299876
// The size of stroke-box is not correct if this graphic element has
@ -9635,8 +9630,6 @@ static nsRect ComputeSVGReferenceRect(nsIFrame* aFrame,
r = nsLayoutUtils::ComputeSVGOriginBox(viewportElement);
break;
}
case StyleGeometryBox::ContentBox:
case StyleGeometryBox::PaddingBox:
case StyleGeometryBox::FillBox: {
gfxRect bbox =
SVGUtils::GetBBox(aFrame, SVGUtils::eBBoxIncludeFillGeometry);
@ -9644,10 +9637,7 @@ static nsRect ComputeSVGReferenceRect(nsIFrame* aFrame,
break;
}
default: {
MOZ_ASSERT_UNREACHABLE("unknown StyleGeometryBox type");
gfxRect bbox =
SVGUtils::GetBBox(aFrame, SVGUtils::eBBoxIncludeFillGeometry);
r = nsLayoutUtils::RoundGfxRectToAppRect(bbox, AppUnitsPerCSSPixel());
MOZ_ASSERT_UNREACHABLE("unsupported SVG box");
break;
}
}
@ -9660,8 +9650,6 @@ nsRect nsLayoutUtils::ComputeHTMLReferenceRect(const nsIFrame* aFrame,
StyleGeometryBox aGeometryBox) {
nsRect r;
// For elements with associated CSS layout box, the used value for fill-box,
// stroke-box and view-box is border-box.
switch (aGeometryBox) {
case StyleGeometryBox::ContentBox:
r = aFrame->GetContentRectRelativeToSelf();
@ -9672,53 +9660,17 @@ nsRect nsLayoutUtils::ComputeHTMLReferenceRect(const nsIFrame* aFrame,
case StyleGeometryBox::MarginBox:
r = aFrame->GetMarginRectRelativeToSelf();
break;
case StyleGeometryBox::NoBox:
case StyleGeometryBox::BorderBox:
case StyleGeometryBox::FillBox:
case StyleGeometryBox::StrokeBox:
case StyleGeometryBox::ViewBox:
r = aFrame->GetRectRelativeToSelf();
break;
default:
MOZ_ASSERT_UNREACHABLE("unknown StyleGeometryBox type");
r = aFrame->GetRectRelativeToSelf();
MOZ_ASSERT_UNREACHABLE("unsupported CSS box");
break;
}
return r;
}
/* static */
StyleGeometryBox nsLayoutUtils::CoordBoxToGeometryBox(StyleCoordBox aCoordBox) {
switch (aCoordBox) {
case StyleCoordBox::ContentBox:
return StyleGeometryBox::ContentBox;
case StyleCoordBox::PaddingBox:
return StyleGeometryBox::PaddingBox;
case StyleCoordBox::BorderBox:
return StyleGeometryBox::BorderBox;
case StyleCoordBox::FillBox:
return StyleGeometryBox::FillBox;
case StyleCoordBox::StrokeBox:
return StyleGeometryBox::StrokeBox;
case StyleCoordBox::ViewBox:
return StyleGeometryBox::ViewBox;
}
MOZ_ASSERT_UNREACHABLE("Unknown coord-box type");
return StyleGeometryBox::BorderBox;
}
/* static */
nsRect nsLayoutUtils::ComputeGeometryBox(nsIFrame* aFrame,
StyleGeometryBox aGeometryBox) {
// We use ComputeSVGReferenceRect for all SVG elements, except <svg>
// element, which does have an associated CSS layout box. In this case we
// should still use ComputeHTMLReferenceRect for region computing.
return aFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)
? ComputeSVGReferenceRect(aFrame, aGeometryBox)
: ComputeHTMLReferenceRect(aFrame, aGeometryBox);
}
static StyleGeometryBox ShapeBoxToGeometryBox(const StyleShapeBox& aBox) {
switch (aBox) {
case StyleShapeBox::BorderBox:

View File

@ -2958,12 +2958,14 @@ class nsLayoutUtils {
static nsRect ComputeSVGOriginBox(mozilla::dom::SVGViewportElement*);
// Compute the geometry box for SVG layout. The caller should map the CSS box
// into the proper SVG box.
static nsRect ComputeSVGReferenceRect(nsIFrame*, StyleGeometryBox);
// Compute the geometry box for CSS layout. The caller should map the SVG box
// into the proper CSS box.
static nsRect ComputeHTMLReferenceRect(const nsIFrame*, StyleGeometryBox);
static StyleGeometryBox CoordBoxToGeometryBox(mozilla::StyleCoordBox);
static nsRect ComputeGeometryBox(nsIFrame*, StyleGeometryBox);
static nsRect ComputeClipPathGeometryBox(
nsIFrame*, const mozilla::StyleShapeGeometryBox&);

View File

@ -2029,17 +2029,19 @@ static bool IsHTMLStyleGeometryBox(StyleGeometryBox aBox) {
aBox == StyleGeometryBox::MarginBox);
}
static StyleGeometryBox ComputeBoxValue(nsIFrame* aForFrame,
StyleGeometryBox aBox) {
static StyleGeometryBox ComputeBoxValueForOrigin(nsIFrame* aForFrame,
StyleGeometryBox aBox) {
// The mapping for mask-origin is from
// https://drafts.fxtf.org/css-masking/#the-mask-origin
if (!aForFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
// For elements with associated CSS layout box, the values fill-box,
// stroke-box and view-box compute to the initial value of mask-clip.
// stroke-box and view-box compute to the initial value of mask-origin.
if (IsSVGStyleGeometryBox(aBox)) {
return StyleGeometryBox::BorderBox;
}
} else {
// For SVG elements without associated CSS layout box, the values
// content-box, padding-box, border-box and margin-box compute to fill-box.
// content-box, padding-box, border-box compute to fill-box.
if (IsHTMLStyleGeometryBox(aBox)) {
return StyleGeometryBox::FillBox;
}
@ -2048,6 +2050,40 @@ static StyleGeometryBox ComputeBoxValue(nsIFrame* aForFrame,
return aBox;
}
static StyleGeometryBox ComputeBoxValueForClip(const nsIFrame* aForFrame,
StyleGeometryBox aBox) {
// The mapping for mask-clip is from
// https://drafts.fxtf.org/css-masking/#the-mask-clip
if (aForFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT)) {
// For SVG elements without associated CSS layout box, the used values for
// content-box and padding-box compute to fill-box and for border-box and
// margin-box compute to stroke-box.
switch (aBox) {
case StyleGeometryBox::ContentBox:
case StyleGeometryBox::PaddingBox:
return StyleGeometryBox::FillBox;
case StyleGeometryBox::BorderBox:
case StyleGeometryBox::MarginBox:
return StyleGeometryBox::StrokeBox;
default:
return aBox;
}
}
// For elements with associated CSS layout box, the used values for fill-box
// compute to content-box and for stroke-box and view-box compute to
// border-box.
switch (aBox) {
case StyleGeometryBox::FillBox:
return StyleGeometryBox::ContentBox;
case StyleGeometryBox::StrokeBox:
case StyleGeometryBox::ViewBox:
return StyleGeometryBox::BorderBox;
default:
return aBox;
}
}
bool nsCSSRendering::ImageLayerClipState::IsValid() const {
// mDirtyRectInDevPx comes from mDirtyRectInAppUnits. mDirtyRectInAppUnits
// can not be empty if mDirtyRectInDevPx is not.
@ -2069,16 +2105,17 @@ void nsCSSRendering::GetImageLayerClip(
const nsRect& aCallerDirtyRect, bool aWillPaintBorder,
nscoord aAppUnitsPerPixel,
/* out */ ImageLayerClipState* aClipState) {
StyleGeometryBox layerClip = ComputeBoxValue(aForFrame, aLayer.mClip);
StyleGeometryBox layerClip = ComputeBoxValueForClip(aForFrame, aLayer.mClip);
if (IsSVGStyleGeometryBox(layerClip)) {
MOZ_ASSERT(aForFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT));
// The coordinate space of clipArea is svg user space.
nsRect clipArea = nsLayoutUtils::ComputeGeometryBox(aForFrame, layerClip);
nsRect clipArea =
nsLayoutUtils::ComputeSVGReferenceRect(aForFrame, layerClip);
nsRect strokeBox = (layerClip == StyleGeometryBox::StrokeBox)
? clipArea
: nsLayoutUtils::ComputeGeometryBox(
: nsLayoutUtils::ComputeSVGReferenceRect(
aForFrame, StyleGeometryBox::StrokeBox);
nsRect clipAreaRelativeToStrokeBox = clipArea - strokeBox.TopLeft();
@ -2708,17 +2745,19 @@ nsRect nsCSSRendering::ComputeImageLayerPositioningArea(
// may need it to compute the effective image size for a CSS gradient.
nsRect positionArea;
StyleGeometryBox layerOrigin = ComputeBoxValue(aForFrame, aLayer.mOrigin);
StyleGeometryBox layerOrigin =
ComputeBoxValueForOrigin(aForFrame, aLayer.mOrigin);
if (IsSVGStyleGeometryBox(layerOrigin)) {
MOZ_ASSERT(aForFrame->HasAnyStateBits(NS_FRAME_SVG_LAYOUT));
*aAttachedToFrame = aForFrame;
positionArea = nsLayoutUtils::ComputeGeometryBox(aForFrame, layerOrigin);
positionArea =
nsLayoutUtils::ComputeSVGReferenceRect(aForFrame, layerOrigin);
nsPoint toStrokeBoxOffset = nsPoint(0, 0);
if (layerOrigin != StyleGeometryBox::StrokeBox) {
nsRect strokeBox = nsLayoutUtils::ComputeGeometryBox(
nsRect strokeBox = nsLayoutUtils::ComputeSVGReferenceRect(
aForFrame, StyleGeometryBox::StrokeBox);
toStrokeBoxOffset = positionArea.TopLeft() - strokeBox.TopLeft();
}

View File

@ -57,7 +57,7 @@ static nsRect GetSVGBox(const nsIFrame* aFrame) {
case StyleTransformBox::FillBox: {
// Percentages in transforms resolve against the SVG bbox, and the
// transform is relative to the top-left of the SVG bbox.
nsRect bboxInAppUnits = nsLayoutUtils::ComputeGeometryBox(
nsRect bboxInAppUnits = nsLayoutUtils::ComputeSVGReferenceRect(
const_cast<nsIFrame*>(aFrame), StyleGeometryBox::FillBox);
// The mRect of an SVG nsIFrame is its user space bounds *including*
// stroke and markers, whereas bboxInAppUnits is its user space bounds
@ -86,7 +86,7 @@ static nsRect GetSVGBox(const nsIFrame* aFrame) {
// have simple bounds.
// FIXME: Bug 1849054. We may have to update
// SVGGeometryFrame::GetBBoxContribution() to get tighter stroke bounds.
nsRect strokeBox = nsLayoutUtils::ComputeGeometryBox(
nsRect strokeBox = nsLayoutUtils::ComputeSVGReferenceRect(
const_cast<nsIFrame*>(aFrame), StyleGeometryBox::StrokeBox);
// The |nsIFrame::mRect| includes markers, so we have to compute the
// offsets without markers.

View File

@ -0,0 +1,41 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>CSS mask-clip reference</title>
<link rel="author" title="Mozilla" href="https://www.mozilla.org">
<style type="text/css">
div {
position: absolute;
top: 10px;
background-color: purple;
}
div.border {
left: 10px;
margin: 1px 4px;
width: 60px;
height: 25px;
}
div.border2 {
left: 110px;
margin: 1px 4px;
width: 60px;
height: 25px;
}
div.content {
left: 210px;
margin: 15px 13px;
width: 40px;
height: 11px;
}
</style>
</head>
<body>
<div class="color border"></div>
<div class="color border2"></div>
<div class="color content"></div>
</body>
</html>

View File

@ -0,0 +1,56 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>CSS Masking: mask-clip: clip mask image</title>
<link rel="author" title="Mozilla" href="https://www.mozilla.org">
<link rel="help" href="https://drafts.fxtf.org/css-masking-1/#the-mask-clip">
<link rel="match" href="mask-clip-3-ref.html">
<meta name="assert" content="fill-box, stroke-box, view-box of mask-clip should clip to the appropriate boxes.">
<style type="text/css">
div.outer {
/*
* content box: 40 x 20
* padding box: 52 x 38
* border box: 60 x 50
* margin box: 66 x 54
*/
background-color: purple;
position: absolute;
top: 10px;
margin: 1px 2px 3px 4px;
border: solid transparent;
border-width: 8px 2px 4px 6px;
padding: 6px 9px 12px 3px;
width: 40px;
height: 20px;
}
div.mask {
mask-size: 100% 100%;
mask-origin: border-box;
mask-image: url(support/transparent-100x50-blue-100x50.svg);
}
div.stroke {
left: 10px;
mask-clip: stroke-box; /* should be the same as border-box */
}
div.view {
left: 110px;
mask-clip: view-box; /* should be the same as border-box */
}
div.fill {
left: 210px;
mask-clip: fill-box; /* should be the same as content-box */
}
</style>
</head>
<body>
<div class="outer mask stroke"></div>
<div class="outer mask view"></div>
<div class="outer mask fill"></div>
</body>
</html>

View File

@ -0,0 +1,31 @@
<!DOCTYPE HTML>
<html>
<head>
<meta charset="utf-8">
<title>CSS mask-clip reference</title>
<link rel="author" title="Mozilla" href="https://www.mozilla.org">
<style type="text/css">
svg {
position: absolute;
top: 10px;
border: 1px solid black;
}
</style>
</head>
<body>
<svg width="200" height="200" style="left: 10px;">
<rect x="50" y="50" width="50" height="50" fill="blue"/>
</svg>
<svg width="200" height="200" style="left: 220px;">
<rect x="50" y="50" width="50" height="50" fill="blue"/>
</svg>
<svg width="200" height="200" style="left: 10px; top: 220px;">
<rect x="50" y="50" width="50" height="50" fill="green"/>
<rect x="60" y="60" width="40" height="40" fill="blue"/>
</svg>
<svg width="200" height="200" style="left: 220px; top: 220px;">
<rect x="50" y="50" width="50" height="50" fill="green"/>
<rect x="60" y="60" width="40" height="40" fill="blue"/>
</svg>
</body>
</html>

View File

@ -0,0 +1,56 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>CSS Masking: mask-clip: clip mask image</title>
<link rel="author" title="Mozilla" href="https://www.mozilla.org">
<link rel="help" href="https://drafts.fxtf.org/css-masking-1/#the-mask-clip">
<link rel="match" href="mask-clip-4-ref.html">
<meta name="assert" content="content-box, padding-box, border-box, and
magrin-box values of mask-clip should clip to the appropriate boxes.">
<style type="text/css">
svg {
position: absolute;
top: 10px;
border: 1px solid black;
}
rect.mask {
fill: blue;
mask-origin: fill-box;
mask-repeat: no-repeat;
mask-image: url(support/50x50-opaque-blue.svg);
}
rect.content {
mask-clip: content-box; /* should be the same as fill-box */
}
rect.padding {
mask-clip: padding-box; /* should be the same as fill-box */
}
rect.border {
mask-clip: border-box; /* should be the same as stroke-box */
}
rect.margin {
mask-clip: margin-box; /* should be the same as stroke-box */
}
</style>
</head>
<body>
<svg width="200" height="200" style="left: 10px;">
<rect class="content mask" x="50" y="50" width="150" height="150"/>
</svg>
<svg width="200" height="200" style="left: 220px;">
<rect class="padding mask" x="50" y="50" width="150" height="150"/>
</svg>
<svg width="200" height="200" style="left: 10px; top: 220px;">
<rect class="border mask" x="50" y="50" width="100" height="100" stroke="green" stroke-width="20"/>
</svg>
<svg width="200" height="200" style="left: 220px; top: 220px;">
<rect class="margin mask" x="50" y="50" width="100" height="100" stroke="green" stroke-width="20"/>
</svg>
</body>
</html>