Bug 1846853 - Clamp negative outline-offset per spec r=emilio

A negative outline-offset value should be clamped per spec as to stop it
from becoming invisible with large negative values.

Spec ref: https://www.w3.org/TR/css-ui-4/#outline-offset

Also added a new WPT reftest for this case as no tests existed prior,
updated related WPT expectations, and updated a few old layout reftests.

Differential Revision: https://phabricator.services.mozilla.com/D185357
This commit is contained in:
CanadaHonk 2023-08-23 11:35:01 +00:00
Родитель 846812dc70
Коммит e115104963
13 изменённых файлов: 73 добавлений и 24 удалений

Просмотреть файл

@ -9880,16 +9880,17 @@ static void ComputeAndIncludeOutlineArea(nsIFrame* aFrame,
innerRect); innerRect);
} }
const nscoord offset = outline->mOutlineOffset.ToAppUnits();
nsRect outerRect(innerRect); nsRect outerRect(innerRect);
outerRect.Inflate(outline->EffectiveOffsetFor(outerRect));
if (outline->mOutlineStyle.IsAuto()) { if (outline->mOutlineStyle.IsAuto()) {
nsPresContext* pc = aFrame->PresContext(); nsPresContext* pc = aFrame->PresContext();
outerRect.Inflate(offset);
pc->Theme()->GetWidgetOverflow(pc->DeviceContext(), aFrame, pc->Theme()->GetWidgetOverflow(pc->DeviceContext(), aFrame,
StyleAppearance::FocusOutline, &outerRect); StyleAppearance::FocusOutline, &outerRect);
} else { } else {
nscoord width = outline->GetOutlineWidth(); const nscoord width = outline->GetOutlineWidth();
outerRect.Inflate(width + offset); outerRect.Inflate(width);
} }
nsRect& vo = aOverflowAreas.InkOverflow(); nsRect& vo = aOverflowAreas.InkOverflow();

Просмотреть файл

@ -962,9 +962,10 @@ nsCSSRendering::CreateBorderRendererForNonThemedOutline(
return Nothing(); return Nothing();
} }
const nscoord offset = ourOutline->mOutlineOffset.ToAppUnits();
nsRect innerRect = aInnerRect; nsRect innerRect = aInnerRect;
innerRect.Inflate(offset);
const nsSize effectiveOffset = ourOutline->EffectiveOffsetFor(innerRect);
innerRect.Inflate(effectiveOffset);
// If the dirty rect is completely inside the border area (e.g., only the // If the dirty rect is completely inside the border area (e.g., only the
// content is being painted), then we can skip out now // content is being painted), then we can skip out now
@ -975,7 +976,7 @@ nsCSSRendering::CreateBorderRendererForNonThemedOutline(
return Nothing(); return Nothing();
} }
nscoord width = ourOutline->GetOutlineWidth(); const nscoord width = ourOutline->GetOutlineWidth();
StyleBorderStyle outlineStyle; StyleBorderStyle outlineStyle;
// Themed outlines are handled by our callers, if supported. // Themed outlines are handled by our callers, if supported.
@ -1009,10 +1010,13 @@ nsCSSRendering::CreateBorderRendererForNonThemedOutline(
RectCornerRadii innerRadii; RectCornerRadii innerRadii;
ComputePixelRadii(twipsRadii, oneDevPixel, &innerRadii); ComputePixelRadii(twipsRadii, oneDevPixel, &innerRadii);
Float devPixelOffset = aPresContext->AppUnitsToFloatDevPixels(offset); const auto devPxOffset = LayoutDeviceSize::FromAppUnits(
const Float widths[4] = { effectiveOffset, aPresContext->AppUnitsPerDevPixel());
outlineWidths[0] + devPixelOffset, outlineWidths[1] + devPixelOffset,
outlineWidths[2] + devPixelOffset, outlineWidths[3] + devPixelOffset}; const Float widths[4] = {outlineWidths[0] + devPxOffset.Height(),
outlineWidths[1] + devPxOffset.Width(),
outlineWidths[2] + devPxOffset.Height(),
outlineWidths[3] + devPxOffset.Width()};
nsCSSBorderRenderer::ComputeOuterRadii(innerRadii, widths, &outlineRadii); nsCSSBorderRenderer::ComputeOuterRadii(innerRadii, widths, &outlineRadii);
} }

Просмотреть файл

@ -4015,7 +4015,7 @@ void nsDisplayOutline::Paint(nsDisplayListBuilder* aBuilder, gfxContext* aCtx) {
nsRect rect = GetInnerRect() + ToReferenceFrame(); nsRect rect = GetInnerRect() + ToReferenceFrame();
nsPresContext* pc = mFrame->PresContext(); nsPresContext* pc = mFrame->PresContext();
if (IsThemedOutline()) { if (IsThemedOutline()) {
rect.Inflate(mFrame->StyleOutline()->mOutlineOffset.ToAppUnits()); rect.Inflate(mFrame->StyleOutline()->EffectiveOffsetFor(rect));
pc->Theme()->DrawWidgetBackground(aCtx, mFrame, pc->Theme()->DrawWidgetBackground(aCtx, mFrame,
StyleAppearance::FocusOutline, rect, StyleAppearance::FocusOutline, rect,
GetPaintRect(aBuilder, aCtx)); GetPaintRect(aBuilder, aCtx));
@ -4044,7 +4044,7 @@ bool nsDisplayOutline::CreateWebRenderCommands(
nsPresContext* pc = mFrame->PresContext(); nsPresContext* pc = mFrame->PresContext();
nsRect rect = GetInnerRect() + ToReferenceFrame(); nsRect rect = GetInnerRect() + ToReferenceFrame();
if (IsThemedOutline()) { if (IsThemedOutline()) {
rect.Inflate(mFrame->StyleOutline()->mOutlineOffset.ToAppUnits()); rect.Inflate(mFrame->StyleOutline()->EffectiveOffsetFor(rect));
return pc->Theme()->CreateWebRenderCommandsForWidget( return pc->Theme()->CreateWebRenderCommandsForWidget(
aBuilder, aResources, aSc, aManager, mFrame, aBuilder, aResources, aSc, aManager, mFrame,
StyleAppearance::FocusOutline, rect); StyleAppearance::FocusOutline, rect);

Просмотреть файл

@ -8,7 +8,6 @@
<style type="text/css"> <style type="text/css">
* { -moz-outline-offset: -2px; outline-offset: -2px; } * { -moz-outline-offset: -2px; outline-offset: -2px; }
body > span { outline: 1px dotted black; }
/* can't compare to border combined with negative margin because of /* can't compare to border combined with negative margin because of
margin collapsing */ margin collapsing */
body > div { display: block; outline: 1px dotted black; width: 15em; } body > div { display: block; outline: 1px dotted black; width: 15em; }
@ -17,7 +16,7 @@
</head> </head>
<body> <body>
<span></span><div>text<br>text<br>text</div> <div>text<br>text<br>text</div>
</body> </body>
</html> </html>

Просмотреть файл

@ -19,7 +19,7 @@
</head> </head>
<body> <body>
<span></span><div>text</div><span>Some words that must be wider than 10em</span><div>text</div> <div>text</div><span>Some words that must be wider than 10em</span><div>text</div>
</body> </body>
</html> </html>

Просмотреть файл

@ -585,6 +585,18 @@ nsChangeHint nsStyleOutline::CalcDifference(
return nsChangeHint(0); return nsChangeHint(0);
} }
nsSize nsStyleOutline::EffectiveOffsetFor(const nsRect& aRect) const {
const nscoord offset = mOutlineOffset.ToAppUnits();
if (offset >= 0) {
// Fast path for non-negative offset values
return nsSize(offset, offset);
}
return nsSize(std::max(offset, -(aRect.Width() / 2)),
std::max(offset, -(aRect.Height() / 2)));
}
// -------------------- // --------------------
// nsStyleList // nsStyleList
// //

Просмотреть файл

@ -617,6 +617,8 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleOutline {
return false; return false;
} }
nsSize EffectiveOffsetFor(const nsRect& aRect) const;
protected: protected:
// The actual value of outline-width is the computed value (an absolute // The actual value of outline-width is the computed value (an absolute
// length, forced to zero when outline-style is none) rounded to device // length, forced to zero when outline-style is none) rounded to device

Просмотреть файл

@ -1,2 +0,0 @@
[outline-013.html]
expected: FAIL

Просмотреть файл

@ -1,2 +0,0 @@
[outline-014.html]
expected: FAIL

Просмотреть файл

@ -1,2 +0,0 @@
[outline-015.html]
expected: FAIL

Просмотреть файл

@ -1,2 +0,0 @@
[outline-016.html]
expected: FAIL

Просмотреть файл

@ -0,0 +1,23 @@
<!DOCTYPE html>
<style>
div {
border: 2px solid black;
padding: 5px 0; /* No horizontal padding as outline-offset is not affected by it and span simulates it */
font-size: 16px;
}
span {
display: block;
/* 3/6px offset for border */
height: 2px;
width: calc(100% - 20em + 6px);
margin: -1px calc(10em - 3px); /* -1px vertical to remove height of span from div height */
background: red;
}
</style>
<p>PASS if there is a thin red line in the middle of the box.</p>
<div>
<span></span>
</div>

Просмотреть файл

@ -0,0 +1,16 @@
<!DOCTYPE html>
<link rel="help" href="https://www.w3.org/TR/css-ui-4/#outline-offset">
<link rel="match" href="negative-outline-offset-ref.html">
<style>
div {
outline: 1px solid red;
outline-offset: -10em;
border: 2px solid black;
padding: 5px;
font-size: 16px;
}
</style>
<p>PASS if there is a thin red line in the middle of the box.</p>
<div></div>