Bug 504622 part 1. Rewrite fieldset border drawing to just clip to the area outside the legend instead of doing it in pieces with different clip rects. r=mattwoodrow,dbaron

This change will allow the border drawing code to deal with the following
changes, which will make us no longer force the fieldset to be wider than the
legend.  Without this patch, allowing the fieldset to be narrower than the
legend causes the vertical inline-start-side and inline-end-side borders of the
fieldset to paint under the legend, because the current code only modifies the
painting of the block-start-side border (the one the legend is positioned on).

This does change behavior in one situation, which the new tests test.  For
relatively positioned legends, we used to use the original vertical location but
the positioned horizontal location of the legend to decide which parts of the
border to not paint.  In the new setup, we use the original location for both.
I did check that this new behavior matches Chrome and Safari.  Edge seems to
have our old behavior.
This commit is contained in:
Boris Zbarsky
2016-11-29 15:52:30 -05:00
parent 08cb8f45ba
commit 0f9915799f
13 changed files with 164 additions and 49 deletions

View File

@@ -110,6 +110,7 @@ public:
virtual void ComputeInvalidationRegion(nsDisplayListBuilder* aBuilder,
const nsDisplayItemGeometry* aGeometry,
nsRegion *aInvalidRegion) override;
virtual nsRect GetBounds(nsDisplayListBuilder* aBuilder, bool* aSnap) override;
NS_DISPLAY_DECL_NAME("FieldSetBorderBackground", TYPE_FIELDSET_BORDER_BACKGROUND)
};
@@ -155,6 +156,19 @@ nsDisplayFieldSetBorderBackground::ComputeInvalidationRegion(nsDisplayListBuilde
nsDisplayItem::ComputeInvalidationRegion(aBuilder, aGeometry, aInvalidRegion);
}
nsRect
nsDisplayFieldSetBorderBackground::GetBounds(nsDisplayListBuilder* aBuilder,
bool* aSnap)
{
// Just go ahead and claim our frame's overflow rect as the bounds, because we
// may have border-image-outset or other features that cause borders to extend
// outside the border rect. We could try to duplicate all the complexity
// nsDisplayBorder has here, but keeping things in sync would be a pain, and
// this code is not typically performance-sensitive.
*aSnap = false;
return Frame()->GetVisualOverflowRectRelativeToSelf() + ToReferenceFrame();
}
void
nsFieldSetFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder,
const nsRect& aDirtyRect,
@@ -217,14 +231,12 @@ nsFieldSetFrame::PaintBorder(
nsPoint aPt,
const nsRect& aDirtyRect)
{
// if the border is smaller than the legend. Move the border down
// to be centered on the legend.
// If the border is smaller than the legend, move the border down
// to be centered on the legend. We call VisualBorderRectRelativeToSelf() to
// compute the border positioning.
// FIXME: This means border-radius clamping is incorrect; we should
// override nsIFrame::GetBorderRadii.
WritingMode wm = GetWritingMode();
nsRect rect = VisualBorderRectRelativeToSelf();
nscoord off = wm.IsVertical() ? rect.x : rect.y;
rect += aPt;
nsRect rect = VisualBorderRectRelativeToSelf() + aPt;
nsPresContext* presContext = PresContext();
PaintBorderFlags borderFlags = aBuilder->ShouldSyncDecodeImages()
@@ -237,55 +249,39 @@ nsFieldSetFrame::PaintBorder(
this, rect);
if (nsIFrame* legend = GetLegend()) {
Side legendSide = wm.PhysicalSide(eLogicalSideBStart);
nscoord legendBorderWidth =
StyleBorder()->GetComputedBorderWidth(legendSide);
// We want to avoid drawing our border under the legend, so clip out the
// legend while drawing our border. We don't want to use mLegendRect here,
// because we do want to draw our border under the legend's inline-start and
// -end margins. And we use GetNormalRect(), not GetRect(), because we do
// not want relative positioning applied to the legend to change how our
// border looks.
nsRect legendRect = legend->GetNormalRect() + aPt;
// Use the rect of the legend frame, not mLegendRect, so we draw our
// border under the legend's inline-start and -end margins.
LogicalRect legendRect(wm, legend->GetRect() + aPt, rect.Size());
// Compute clipRect using logical coordinates, so that the legend space
// will be clipped out of the appropriate physical side depending on mode.
LogicalRect clipRect = LogicalRect(wm, rect, rect.Size());
DrawTarget* drawTarget = aRenderingContext.GetDrawTarget();
gfxContext* gfx = aRenderingContext.ThebesContext();
// We set up a clip path which has our rect clockwise and the legend rect
// counterclockwise, with FILL_WINDING as the fill rule. That will allow us
// to paint within our rect but outside the legend rect. For "our rect" we
// use our visual overflow rect (relative to ourselves, so it's not affected
// by transforms), because we can have borders sticking outside our border
// box (e.g. due to border-image-outset).
RefPtr<PathBuilder> pathBuilder =
drawTarget->CreatePathBuilder(FillRule::FILL_WINDING);
int32_t appUnitsPerDevPixel = presContext->AppUnitsPerDevPixel();
AppendRectToPath(pathBuilder,
NSRectToSnappedRect(GetVisualOverflowRectRelativeToSelf() + aPt,
appUnitsPerDevPixel,
*drawTarget),
true);
AppendRectToPath(pathBuilder,
NSRectToSnappedRect(legendRect, appUnitsPerDevPixel,
*drawTarget),
false);
RefPtr<Path> clipPath = pathBuilder->Finish();
// draw inline-start portion of the block-start side of the border
clipRect.ISize(wm) = legendRect.IStart(wm) - clipRect.IStart(wm);
clipRect.BSize(wm) = legendBorderWidth;
gfxContext* gfx = aRenderingContext.ThebesContext();
gfx->Save();
gfx->Clip(NSRectToSnappedRect(clipRect.GetPhysicalRect(wm, rect.Size()),
appUnitsPerDevPixel, *drawTarget));
result &=
nsCSSRendering::PaintBorder(presContext, aRenderingContext, this,
aDirtyRect, rect, mStyleContext, borderFlags);
gfx->Restore();
// draw inline-end portion of the block-start side of the border
clipRect = LogicalRect(wm, rect, rect.Size());
clipRect.ISize(wm) = clipRect.IEnd(wm) - legendRect.IEnd(wm);
clipRect.IStart(wm) = legendRect.IEnd(wm);
clipRect.BSize(wm) = legendBorderWidth;
gfx->Save();
gfx->Clip(NSRectToSnappedRect(clipRect.GetPhysicalRect(wm, rect.Size()),
appUnitsPerDevPixel, *drawTarget));
result &=
nsCSSRendering::PaintBorder(presContext, aRenderingContext, this,
aDirtyRect, rect, mStyleContext, borderFlags);
gfx->Restore();
// draw remainder of the border (omitting the block-start side)
clipRect = LogicalRect(wm, rect, rect.Size());
clipRect.BStart(wm) += legendBorderWidth;
clipRect.BSize(wm) = BSize(wm) - (off + legendBorderWidth);
gfx->Save();
gfx->Clip(NSRectToSnappedRect(clipRect.GetPhysicalRect(wm, rect.Size()),
appUnitsPerDevPixel, *drawTarget));
gfx->Clip(clipPath);
result &=
nsCSSRendering::PaintBorder(presContext, aRenderingContext, this,
aDirtyRect, rect, mStyleContext, borderFlags);