Bug 1769512 - Implement overflow-clip-margin: <length>. r=jwatt

Differential Revision: https://phabricator.services.mozilla.com/D146432
This commit is contained in:
Emilio Cobos Álvarez 2022-05-23 07:21:43 +00:00
parent 3d7fc1d193
commit 99908fb968
21 changed files with 194 additions and 90 deletions

View file

@ -404,5 +404,5 @@ exports.ANIMATION_TYPE_FOR_LONGHANDS = [
],
["shadow", new Set(["box-shadow", "text-shadow"])],
["paintServer", new Set(["fill", "stroke"])],
["length", new Set(["font-size", "outline-offset", "outline-width"])],
["length", new Set(["font-size", "outline-offset", "outline-width", "overflow-clip-box"])],
];

View file

@ -3114,6 +3114,7 @@ exports.CSS_PROPERTIES = {
"margin-right",
"margin-bottom",
"margin-left",
"overflow-clip-margin",
"scroll-margin-top",
"scroll-margin-right",
"scroll-margin-bottom",
@ -9063,6 +9064,20 @@ exports.CSS_PROPERTIES = {
"visible"
]
},
"overflow-clip-margin": {
"isInherited": false,
"subproperties": [
"overflow-clip-margin"
],
"supports": [],
"values": [
"inherit",
"initial",
"revert",
"revert-layer",
"unset"
]
},
"overflow-inline": {
"isInherited": false,
"subproperties": [

View file

@ -11,6 +11,26 @@
namespace mozilla {
/* static */
void OverflowAreas::ApplyOverflowClippingOnRect(nsRect& aOverflowRect,
const nsRect& aBounds,
PhysicalAxes aClipAxes,
const nsSize& aOverflowMargin) {
auto inflatedBounds = aBounds;
inflatedBounds.Inflate(aOverflowMargin);
auto clip = aOverflowRect;
if (aClipAxes & PhysicalAxes::Vertical) {
clip.y = inflatedBounds.y;
clip.height = inflatedBounds.height;
}
if (aClipAxes & PhysicalAxes::Horizontal) {
clip.x = inflatedBounds.x;
clip.width = inflatedBounds.width;
}
aOverflowRect = aOverflowRect.Intersect(clip);
}
void OverflowAreas::UnionWith(const OverflowAreas& aOther) {
InkOverflow().UnionRect(InkOverflow(), aOther.InkOverflow());
ScrollableOverflow().UnionRect(ScrollableOverflow(),

View file

@ -79,6 +79,23 @@ struct OverflowAreas {
// Mutates |this| by setting both overflow areas to |aRect|.
void SetAllTo(const nsRect& aRect);
// Applies overflow clipping (for e.g. overflow: clip) as needed to both our
// overflow rects.
void ApplyClipping(const nsRect& aBounds, PhysicalAxes aClipAxes,
const nsSize& aOverflowMargin) {
ApplyOverflowClippingOnRect(InkOverflow(), aBounds, aClipAxes,
aOverflowMargin);
ApplyOverflowClippingOnRect(ScrollableOverflow(), aBounds, aClipAxes,
aOverflowMargin);
}
// Applies the overflow clipping to a given overflow rect, given the frame
// bounds, and the physical axes on which to apply the overflow clip.
static void ApplyOverflowClippingOnRect(nsRect& aOverflowRect,
const nsRect& aBounds,
PhysicalAxes aClipAxes,
const nsSize& aOverflowMargin);
private:
nsRect mInk;
nsRect mScrollable;

View file

@ -71,6 +71,15 @@ enum LogicalCorner {
// Physical axis constants.
enum PhysicalAxis { eAxisVertical = 0x0, eAxisHorizontal = 0x1 };
// Represents zero or more physical axes.
enum class PhysicalAxes : uint8_t {
None = 0x0,
Horizontal = 0x1,
Vertical = 0x2,
Both = Horizontal | Vertical,
};
MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(PhysicalAxes)
inline LogicalAxis GetOrthogonalAxis(LogicalAxis aAxis) {
return aAxis == eLogicalAxisBlock ? eLogicalAxisInline : eLogicalAxisBlock;
}

View file

@ -2139,35 +2139,46 @@ void nsBlockFrame::ComputeOverflowAreas(OverflowAreas& aOverflowAreas,
const nsStyleDisplay* aDisplay) const {
// XXX_perf: This can be done incrementally. It is currently one of
// the things that makes incremental reflow O(N^2).
if (ShouldApplyOverflowClipping(aDisplay) != PhysicalAxes::Both) {
for (const auto& line : Lines()) {
if (aDisplay->IsContainLayout()) {
// If we have layout containment, we should only consider our child's
// ink overflow, leaving the scrollable regions of the parent
// unaffected.
// Note: scrollable overflow is a subset of ink overflow,
// so this has the same affect as unioning the child's visual and
// scrollable overflow with its parent's ink overflow.
nsRect childVisualRect = line.InkOverflowRect();
OverflowAreas childVisualArea =
OverflowAreas(childVisualRect, nsRect());
aOverflowAreas.UnionWith(childVisualArea);
} else {
aOverflowAreas.UnionWith(line.GetOverflowAreas());
}
}
auto overflowClipAxes = ShouldApplyOverflowClipping(aDisplay);
auto overflowClipMargin = OverflowClipMargin(overflowClipAxes);
if (overflowClipAxes == PhysicalAxes::Both &&
overflowClipMargin == nsSize()) {
return;
}
// Factor an outside ::marker in; normally the ::marker will be factored
// into the line-box's overflow areas. However, if the line is a block
// line then it won't; if there are no lines, it won't. So just
// factor it in anyway (it can't hurt if it was already done).
// XXXldb Can we just fix GetOverflowArea instead?
if (nsIFrame* outsideMarker = GetOutsideMarker()) {
aOverflowAreas.UnionAllWith(outsideMarker->GetRect());
}
// We rely here on our caller having called SetOverflowAreasToDesiredBounds().
nsRect frameBounds = aOverflowAreas.ScrollableOverflow();
ConsiderBlockEndEdgeOfChildren(aOverflowAreas, aBEndEdgeOfChildren,
aDisplay);
for (const auto& line : Lines()) {
if (aDisplay->IsContainLayout()) {
// If we have layout containment, we should only consider our child's
// ink overflow, leaving the scrollable regions of the parent
// unaffected.
// Note: scrollable overflow is a subset of ink overflow,
// so this has the same affect as unioning the child's visual and
// scrollable overflow with its parent's ink overflow.
nsRect childVisualRect = line.InkOverflowRect();
OverflowAreas childVisualArea = OverflowAreas(childVisualRect, nsRect());
aOverflowAreas.UnionWith(childVisualArea);
} else {
aOverflowAreas.UnionWith(line.GetOverflowAreas());
}
}
// Factor an outside ::marker in; normally the ::marker will be factored
// into the line-box's overflow areas. However, if the line is a block
// line then it won't; if there are no lines, it won't. So just
// factor it in anyway (it can't hurt if it was already done).
// XXXldb Can we just fix GetOverflowArea instead?
if (nsIFrame* outsideMarker = GetOutsideMarker()) {
aOverflowAreas.UnionAllWith(outsideMarker->GetRect());
}
ConsiderBlockEndEdgeOfChildren(aOverflowAreas, aBEndEdgeOfChildren, aDisplay);
if (overflowClipAxes != PhysicalAxes::None) {
aOverflowAreas.ApplyClipping(frameBounds, overflowClipAxes,
overflowClipMargin);
}
#ifdef NOISY_OVERFLOW_AREAS

View file

@ -2699,18 +2699,24 @@ static void ApplyOverflowClipping(
bool cbV = (wm.IsVertical() ? disp->mOverflowClipBoxInline
: disp->mOverflowClipBoxBlock) ==
StyleOverflowClipBox::ContentBox;
nsMargin bp = aFrame->GetUsedPadding();
nsMargin boxMargin = -aFrame->GetUsedPadding();
if (!cbH) {
bp.left = bp.right = nscoord(0);
boxMargin.left = boxMargin.right = nscoord(0);
}
if (!cbV) {
bp.top = bp.bottom = nscoord(0);
boxMargin.top = boxMargin.bottom = nscoord(0);
}
bp += aFrame->GetUsedBorder();
bp.ApplySkipSides(aFrame->GetSkipSides());
auto clipMargin = aFrame->OverflowClipMargin(aClipAxes);
boxMargin -= aFrame->GetUsedBorder();
boxMargin += nsMargin(clipMargin.height, clipMargin.width, clipMargin.height,
clipMargin.width);
boxMargin.ApplySkipSides(aFrame->GetSkipSides());
nsRect rect(nsPoint(0, 0), aFrame->GetSize());
rect.Deflate(bp);
rect.Inflate(boxMargin);
if (MOZ_UNLIKELY(!(aClipAxes & nsIFrame::PhysicalAxes::Horizontal))) {
// NOTE(mats) We shouldn't be clipping at all in this dimension really,
// but clipping in just one axis isn't supported by our GFX APIs so we
@ -2726,11 +2732,30 @@ static void ApplyOverflowClipping(
rect.height = o.height;
}
clipRect = rect + aBuilder->ToReferenceFrame(aFrame);
haveRadii = aFrame->GetBoxBorderRadii(radii, -bp);
haveRadii = aFrame->GetBoxBorderRadii(radii, boxMargin);
aClipState.ClipContainingBlockDescendantsExtra(clipRect,
haveRadii ? radii : nullptr);
}
nsSize nsIFrame::OverflowClipMargin(PhysicalAxes aClipAxes) const {
nsSize result;
if (aClipAxes == PhysicalAxes::None) {
return result;
}
const auto& margin = StyleMargin()->mOverflowClipMargin;
if (margin.IsZero()) {
return result;
}
nscoord marginAu = margin.ToAppUnits();
if (aClipAxes & PhysicalAxes::Horizontal) {
result.width = marginAu;
}
if (aClipAxes & PhysicalAxes::Vertical) {
result.height = marginAu;
}
return result;
}
#ifdef DEBUG
static void PaintDebugBorder(nsIFrame* aFrame, DrawTarget* aDrawTarget,
const nsRect& aDirtyRect, nsPoint aPt) {
@ -9548,14 +9573,19 @@ static nsRect ComputeOutlineInnerRect(
}
const nsStyleDisplay* disp = aFrame->StyleDisplay();
LayoutFrameType fType = aFrame->Type();
auto overflowClipAxes = aFrame->ShouldApplyOverflowClipping(disp);
if (overflowClipAxes == nsIFrame::PhysicalAxes::Both ||
fType == LayoutFrameType::Scroll ||
if (fType == LayoutFrameType::Scroll ||
fType == LayoutFrameType::ListControl ||
fType == LayoutFrameType::SVGOuterSVG) {
return u;
}
auto overflowClipAxes = aFrame->ShouldApplyOverflowClipping(disp);
auto overflowClipMargin = aFrame->OverflowClipMargin(overflowClipAxes);
if (overflowClipAxes == nsIFrame::PhysicalAxes::Both &&
overflowClipMargin == nsSize()) {
return u;
}
const nsStyleEffects* effects = aFrame->StyleEffects();
Maybe<nsRect> clipPropClipRect =
aFrame->GetClipPropClipRect(disp, effects, bounds.Size());
@ -9619,15 +9649,10 @@ static nsRect ComputeOutlineInnerRect(
}
}
if (overflowClipAxes & nsIFrame::PhysicalAxes::Vertical) {
u.y = bounds.y;
u.height = bounds.height;
if (overflowClipAxes != nsIFrame::PhysicalAxes::None) {
OverflowAreas::ApplyOverflowClippingOnRect(u, bounds, overflowClipAxes,
overflowClipMargin);
}
if (overflowClipAxes & nsIFrame::PhysicalAxes::Horizontal) {
u.x = bounds.x;
u.width = bounds.width;
}
return u;
}
@ -9811,27 +9836,6 @@ bool nsIFrame::FinishAndStoreOverflow(OverflowAreas& aOverflowAreas,
"Computed overflow area must contain frame bounds");
}
// If we clip our children, clear accumulated overflow area in the affected
// dimension(s). The children are actually clipped to the padding-box, but
// since the overflow area should include the entire border-box, just set it
// to the border-box size here.
if (overflowClipAxes != PhysicalAxes::None) {
nsRect& ink = aOverflowAreas.InkOverflow();
nsRect& scrollable = aOverflowAreas.ScrollableOverflow();
if (overflowClipAxes & PhysicalAxes::Vertical) {
ink.y = bounds.y;
scrollable.y = bounds.y;
ink.height = bounds.height;
scrollable.height = bounds.height;
}
if (overflowClipAxes & PhysicalAxes::Horizontal) {
ink.x = bounds.x;
scrollable.x = bounds.x;
ink.width = bounds.width;
scrollable.width = bounds.width;
}
}
// Overflow area must always include the frame's top-left and bottom-right,
// even if the frame rect is empty (so we can scroll to those positions).
// Pending a real fix for bug 426879, don't do this for inline frames
@ -9846,6 +9850,15 @@ bool nsIFrame::FinishAndStoreOverflow(OverflowAreas& aOverflowAreas,
}
}
// If we clip our children, clear accumulated overflow area in the affected
// dimension(s). The children are actually clipped to the padding-box, but
// since the overflow area should include the entire border-box, just set it
// to the border-box size here.
if (overflowClipAxes != PhysicalAxes::None) {
aOverflowAreas.ApplyClipping(bounds, overflowClipAxes,
OverflowClipMargin(overflowClipAxes));
}
// Note that StyleOverflow::Clip doesn't clip the frame
// background, so we add theme background overflow here so it's not clipped.
if (!::IsXULBoxWrapped(this) && IsThemed(disp)) {

View file

@ -2935,17 +2935,11 @@ class nsIFrame : public nsQueryFrame {
*/
virtual void UnionChildOverflow(mozilla::OverflowAreas& aOverflowAreas);
// Represents zero or more physical axes.
enum class PhysicalAxes : uint8_t {
None = 0x0,
Horizontal = 0x1,
Vertical = 0x2,
Both = Horizontal | Vertical,
};
// Returns the applicable overflow-clip-margin values.
using PhysicalAxes = mozilla::PhysicalAxes;
/**
* Returns true if this frame should apply overflow clipping.
*/
nsSize OverflowClipMargin(PhysicalAxes aClipAxes) const;
// Returns the axes on which this frame should apply overflow clipping.
PhysicalAxes ShouldApplyOverflowClipping(const nsStyleDisplay* aDisp) const;
/**
@ -5555,7 +5549,6 @@ class nsIFrame : public nsQueryFrame {
#endif
};
MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(nsIFrame::PhysicalAxes)
MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(nsIFrame::ReflowChildFlags)
//----------------------------------------------------------------------

View file

@ -299,12 +299,15 @@ static StyleRect<T> StyleRectWithAllSides(const T& aSide) {
nsStyleMargin::nsStyleMargin(const Document& aDocument)
: mMargin(StyleRectWithAllSides(
LengthPercentageOrAuto::LengthPercentage(LengthPercentage::Zero()))),
mScrollMargin(StyleRectWithAllSides(StyleLength{0.})) {
mScrollMargin(StyleRectWithAllSides(StyleLength{0.})),
mOverflowClipMargin(StyleLength::Zero()) {
MOZ_COUNT_CTOR(nsStyleMargin);
}
nsStyleMargin::nsStyleMargin(const nsStyleMargin& aSrc)
: mMargin(aSrc.mMargin), mScrollMargin(aSrc.mScrollMargin) {
: mMargin(aSrc.mMargin),
mScrollMargin(aSrc.mScrollMargin),
mOverflowClipMargin(aSrc.mOverflowClipMargin) {
MOZ_COUNT_CTOR(nsStyleMargin);
}
@ -324,6 +327,10 @@ nsChangeHint nsStyleMargin::CalcDifference(
hint |= nsChangeHint_NeutralChange;
}
if (mOverflowClipMargin != aNewData.mOverflowClipMargin) {
hint |= nsChangeHint_UpdateOverflow | nsChangeHint_RepaintFrame;
}
return hint;
}

View file

@ -426,6 +426,9 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleMargin {
mozilla::StyleRect<mozilla::LengthPercentageOrAuto> mMargin;
mozilla::StyleRect<mozilla::StyleLength> mScrollMargin;
// TODO: Add support for overflow-clip-margin: <visual-box> and maybe
// per-axis/side clipping, see https://github.com/w3c/csswg-drafts/issues/7245
mozilla::StyleLength mOverflowClipMargin;
};
struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStylePadding {

View file

@ -6887,6 +6887,14 @@ var gCSSProperties = {
other_values: ["auto", "scroll", "hidden", "clip"],
invalid_values: [],
},
"overflow-clip-margin": {
domProp: "overflowClipMargin",
inherited: false,
type: CSS_TYPE_LONGHAND,
initial_values: ["0px"],
other_values: ["1px", "2em", "calc(10px + 1vh)"],
invalid_values: ["-10px"],
},
padding: {
domProp: "padding",
inherited: false,

View file

@ -28,6 +28,16 @@
)}
% endfor
${helpers.predefined_type(
"overflow-clip-margin",
"Length",
"computed::Length::zero()",
parse_method="parse_non_negative",
engines="gecko",
spec="https://drafts.csswg.org/css-overflow/#propdef-overflow-clip-margin",
animation_value_type="ComputedValue",
)}
% for side in ALL_SIDES:
${helpers.predefined_type(
"scroll-margin-%s" % side[0],

View file

@ -1,2 +0,0 @@
[overflow-clip-margin-001.html]
expected: FAIL

View file

@ -1,2 +0,0 @@
[overflow-clip-margin-002.html]
expected: FAIL

View file

@ -1,2 +0,0 @@
[overflow-clip-margin-004.html]
expected: FAIL

View file

@ -1,2 +0,0 @@
[overflow-clip-margin-005.html]
expected: FAIL

View file

@ -1,2 +0,0 @@
[overflow-clip-margin-invalidation.html]
expected: FAIL

View file

@ -8,6 +8,8 @@
overflow: auto;
width: 100px;
height: 100px;
/* Avoids some fuzz on scrollbar corners */
scrollbar-color: blue blue;
}
.child {
position: relative;

View file

@ -9,6 +9,8 @@
overflow: auto;
width: 100px;
height: 100px;
/* Avoids some fuzz on scrollbar corners */
scrollbar-color: blue blue;
}
.parent {
width: 100px;

View file

@ -8,6 +8,8 @@
overflow: auto;
width: 100px;
height: 100px;
/* Avoids some fuzz on scrollbar corners */
scrollbar-color: blue blue;
}
.child {
position: relative;

View file

@ -9,6 +9,8 @@
overflow: auto;
width: 100px;
height: 100px;
/* Avoids some fuzz on scrollbar corners */
scrollbar-color: blue blue;
}
.parent {
width: 100px;