commit c95e29f3587bee6bfadd7547605b98edc2e86487 parent 8770c1b7c83c701cd0409778790ddc2dd9d805a1 Author: Emilio Cobos Álvarez <emilio@crisal.io> Date: Mon, 8 Dec 2025 13:11:22 +0000 Bug 1999100 - Don't clip overflow by default on replaced images. r=jwatt This implements overflow-clip-box: <visual-box>, and uses it to allow overflow on replaced elements. Differential Revision: https://phabricator.services.mozilla.com/D274759 Diffstat:
36 files changed, 277 insertions(+), 254 deletions(-)
diff --git a/dom/base/DOMIntersectionObserver.cpp b/dom/base/DOMIntersectionObserver.cpp @@ -839,7 +839,8 @@ IntersectionOutput DOMIntersectionObserver::Intersect( if (!clipAxes.isEmpty()) { targetRectRelativeToTarget = OverflowAreas::GetOverflowClipRect( targetRectRelativeToTarget, targetRectRelativeToTarget, clipAxes, - aTargetFrame->OverflowClipMargin(clipAxes)); + aTargetFrame->OverflowClipMargin(clipAxes, + /* aAllowNegative = */ false)); } } diff --git a/layout/generic/ReflowOutput.cpp b/layout/generic/ReflowOutput.cpp @@ -26,7 +26,7 @@ static bool IsValidOverflowRect(const nsRect& aRect) { nsRect OverflowAreas::GetOverflowClipRect(const nsRect& aRectToClip, const nsRect& aBounds, PhysicalAxes aClipAxes, - const nsSize& aOverflowMargin) { + const nsMargin& aOverflowMargin) { auto inflatedBounds = aBounds; inflatedBounds.Inflate(aOverflowMargin); auto clip = aRectToClip; @@ -42,10 +42,9 @@ nsRect OverflowAreas::GetOverflowClipRect(const nsRect& aRectToClip, } /* static */ -void OverflowAreas::ApplyOverflowClippingOnRect(nsRect& aOverflowRect, - const nsRect& aBounds, - PhysicalAxes aClipAxes, - const nsSize& aOverflowMargin) { +void OverflowAreas::ApplyOverflowClippingOnRect( + nsRect& aOverflowRect, const nsRect& aBounds, PhysicalAxes aClipAxes, + const nsMargin& aOverflowMargin) { aOverflowRect = aOverflowRect.Intersect( GetOverflowClipRect(aOverflowRect, aBounds, aClipAxes, aOverflowMargin)); } diff --git a/layout/generic/ReflowOutput.h b/layout/generic/ReflowOutput.h @@ -80,7 +80,7 @@ struct OverflowAreas { // 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) { + const nsMargin& aOverflowMargin) { ApplyOverflowClippingOnRect(InkOverflow(), aBounds, aClipAxes, aOverflowMargin); ApplyOverflowClippingOnRect(ScrollableOverflow(), aBounds, aClipAxes, @@ -92,14 +92,14 @@ struct OverflowAreas { static nsRect GetOverflowClipRect(const nsRect& aRectToClip, const nsRect& aBounds, PhysicalAxes aClipAxes, - const nsSize& aOverflowMargin); + const nsMargin& 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); + const nsMargin& aOverflowMargin); private: nsRect mInk; diff --git a/layout/generic/nsBlockFrame.cpp b/layout/generic/nsBlockFrame.cpp @@ -2371,8 +2371,9 @@ void nsBlockFrame::ComputeOverflowAreas(OverflowAreas& aOverflowAreas, // XXX_perf: This can be done incrementally. It is currently one of // the things that makes incremental reflow O(N^2). auto overflowClipAxes = ShouldApplyOverflowClipping(aDisplay); - auto overflowClipMargin = OverflowClipMargin(overflowClipAxes); - if (overflowClipAxes == kPhysicalAxesBoth && overflowClipMargin == nsSize()) { + auto overflowClipMargin = + OverflowClipMargin(overflowClipAxes, /* aAllowNegative = */ false); + if (overflowClipAxes == kPhysicalAxesBoth && overflowClipMargin.IsAllZero()) { return; } diff --git a/layout/generic/nsHTMLCanvasFrame.cpp b/layout/generic/nsHTMLCanvasFrame.cpp @@ -463,13 +463,20 @@ void nsHTMLCanvasFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, return; } - uint32_t clipFlags = - nsStyleUtil::ObjectPropsMightCauseOverflow(StylePosition()) - ? 0 - : DisplayListClipState::ASSUME_DRAWING_RESTRICTED_TO_CONTENT_RECT; - - DisplayListClipState::AutoClipContainingBlockDescendantsToContentBox clip( - aBuilder, this, clipFlags); + DisplayListClipState::AutoSaveRestore clipState(aBuilder); + auto clipAxes = ShouldApplyOverflowClipping(StyleDisplay()); + if (!clipAxes.isEmpty()) { + nsRect clipRect; + nsRectCornerRadii radii; + bool haveRadii = + ComputeOverflowClipRectRelativeToSelf(clipAxes, clipRect, radii); + if (haveRadii || + nsStyleUtil::ObjectPropsMightCauseOverflow(StylePosition())) { + clipState.ClipContainingBlockDescendants( + clipRect + aBuilder->ToReferenceFrame(this), + haveRadii ? &radii : nullptr); + } + } aLists.Content()->AppendNewToTop<nsDisplayCanvas>(aBuilder, this); } diff --git a/layout/generic/nsIFrame.cpp b/layout/generic/nsIFrame.cpp @@ -2763,6 +2763,19 @@ static void ApplyOverflowClipping( haveRadii ? &radii : nullptr); } +static Sides ToSkipSides(PhysicalAxes aClipAxes) { + SideBits result{}; + if (!aClipAxes.contains(PhysicalAxis::Vertical)) { + result |= SideBits::eTop; + result |= SideBits::eBottom; + } + if (!aClipAxes.contains(PhysicalAxis::Horizontal)) { + result |= SideBits::eLeft; + result |= SideBits::eRight; + } + return Sides(result); +} + bool nsIFrame::ComputeOverflowClipRectRelativeToSelf( const PhysicalAxes aClipAxes, nsRect& aOutRect, nsRectCornerRadii& aOutRadii) const { @@ -2772,12 +2785,8 @@ bool nsIFrame::ComputeOverflowClipRectRelativeToSelf( // comboboxes which make their display text (an inline frame) have clipping. MOZ_ASSERT(!aClipAxes.isEmpty()); MOZ_ASSERT(ShouldApplyOverflowClipping(StyleDisplay()) == aClipAxes); - // Only deflate the padding if we clip to the content-box in that axis. - nsMargin boxMargin = -GetUsedBorder(); - auto clipMargin = OverflowClipMargin(aClipAxes); - boxMargin += nsMargin(clipMargin.height, clipMargin.width, clipMargin.height, - clipMargin.width); - boxMargin.ApplySkipSides(GetSkipSides()); + auto boxMargin = OverflowClipMargin(aClipAxes, /* aAllowNegative = */ true); + boxMargin.ApplySkipSides(GetSkipSides() | ToSkipSides(aClipAxes)); aOutRect = nsRect(nsPoint(), GetSize()); aOutRect.Inflate(boxMargin); @@ -2802,21 +2811,32 @@ bool nsIFrame::ComputeOverflowClipRectRelativeToSelf( return true; } -nsSize nsIFrame::OverflowClipMargin(PhysicalAxes aClipAxes) const { - nsSize result; +nsMargin nsIFrame::OverflowClipMargin(PhysicalAxes aClipAxes, + bool aAllowNegative) const { + nsMargin result; if (aClipAxes.isEmpty()) { return result; } const auto& margin = StyleMargin()->mOverflowClipMargin; - if (margin.IsZero()) { + if (!aAllowNegative && margin.offset.IsZero()) { return result; } - nscoord marginAu = margin.ToAppUnits(); - if (aClipAxes.contains(PhysicalAxis::Horizontal)) { - result.width = marginAu; + switch (margin.visual_box) { + case StyleOverflowClipMarginBox::BorderBox: + break; + case StyleOverflowClipMarginBox::PaddingBox: + result = -GetUsedBorder(); + break; + case StyleOverflowClipMarginBox::ContentBox: + result = -GetUsedBorderAndPadding(); + break; + } + if (!margin.offset.IsZero()) { + nscoord marginAu = margin.offset.ToAppUnits(); + result += nsMargin(marginAu, marginAu, marginAu, marginAu); } - if (aClipAxes.contains(PhysicalAxis::Vertical)) { - result.height = marginAu; + if (!aAllowNegative) { + result.EnsureAtLeast(nsMargin()); } return result; } @@ -10716,8 +10736,9 @@ static nsRect ComputeOutlineInnerRect( } auto overflowClipAxes = aFrame->ShouldApplyOverflowClipping(disp); - auto overflowClipMargin = aFrame->OverflowClipMargin(overflowClipAxes); - if (overflowClipAxes == kPhysicalAxesBoth && overflowClipMargin == nsSize()) { + auto overflowClipMargin = aFrame->OverflowClipMargin( + overflowClipAxes, /* aAllowNegative = */ false); + if (overflowClipAxes == kPhysicalAxesBoth && overflowClipMargin.IsAllZero()) { return u; } @@ -10996,8 +11017,9 @@ bool nsIFrame::FinishAndStoreOverflow(OverflowAreas& aOverflowAreas, // since the overflow area should include the entire border-box, just set it // to the border-box size here. if (!overflowClipAxes.isEmpty()) { - aOverflowAreas.ApplyClipping(bounds, overflowClipAxes, - OverflowClipMargin(overflowClipAxes)); + aOverflowAreas.ApplyClipping( + bounds, overflowClipAxes, + OverflowClipMargin(overflowClipAxes, /* aAllowNegative = */ false)); } ComputeAndIncludeOutlineArea(this, aOverflowAreas, aNewSize); @@ -12264,9 +12286,8 @@ PhysicalAxes nsIFrame::ShouldApplyOverflowClipping( return kPhysicalAxesBoth; } - // and overflow:hidden that we should interpret as clip - if (aDisp->mOverflowX == StyleOverflow::Hidden && - aDisp->mOverflowY == StyleOverflow::Hidden) { + // and overflow: scrollable that we should interpret as clip + if (aDisp->IsScrollableOverflow()) { // REVIEW: these are the frame types that set up clipping. LayoutFrameType type = Type(); switch (type) { @@ -12280,10 +12301,20 @@ PhysicalAxes nsIFrame::ShouldApplyOverflowClipping( case LayoutFrameType::SVGInnerSVG: case LayoutFrameType::SVGOuterSVG: case LayoutFrameType::SVGSymbol: - case LayoutFrameType::Table: - case LayoutFrameType::TableCell: case LayoutFrameType::Image: + case LayoutFrameType::TableCell: return kPhysicalAxesBoth; + case LayoutFrameType::Table: + // Tables, for legacy reasons only clip when hidden in both directions, + // and treat all other scrollable overflow values as `visible`. + // This is (somewhat, since other browsers make this change at + // computed-value time, see bug 1918789) interoperable css2-era + // behavior. We might be able to change this, but not today. + // See layout/reftests/table-overflow/bug785684-x.html + return aDisp->mOverflowX == StyleOverflow::Hidden && + aDisp->mOverflowY == StyleOverflow::Hidden + ? kPhysicalAxesBoth + : PhysicalAxes(); case LayoutFrameType::TextInput: // It has an anonymous scroll container frame that handles any overflow. return PhysicalAxes(); @@ -12294,8 +12325,8 @@ PhysicalAxes nsIFrame::ShouldApplyOverflowClipping( // clip overflow:clip, except for nsListControlFrame which is // a ScrollContainerFrame sub-class. - if (MOZ_UNLIKELY((aDisp->mOverflowX == mozilla::StyleOverflow::Clip || - aDisp->mOverflowY == mozilla::StyleOverflow::Clip) && + if (MOZ_UNLIKELY((aDisp->mOverflowX == StyleOverflow::Clip || + aDisp->mOverflowY == StyleOverflow::Clip) && !IsListControlFrame())) { // FIXME: we could use GetViewportScrollStylesOverrideElement() here instead // if that worked correctly in a print context. (see bug 1654667) @@ -12303,10 +12334,10 @@ PhysicalAxes nsIFrame::ShouldApplyOverflowClipping( if (!element || !PresContext()->ElementWouldPropagateScrollStyles(*element)) { PhysicalAxes axes; - if (aDisp->mOverflowX == mozilla::StyleOverflow::Clip) { + if (aDisp->mOverflowX == StyleOverflow::Clip) { axes += PhysicalAxis::Horizontal; } - if (aDisp->mOverflowY == mozilla::StyleOverflow::Clip) { + if (aDisp->mOverflowY == StyleOverflow::Clip) { axes += PhysicalAxis::Vertical; } return axes; diff --git a/layout/generic/nsIFrame.h b/layout/generic/nsIFrame.h @@ -3223,8 +3223,12 @@ class nsIFrame : public nsQueryFrame { const mozilla::PhysicalAxes aClipAxes, nsRect& aOutRect, nsRectCornerRadii& aOutRadii) const; - // Returns the applicable overflow-clip-margin values. - nsSize OverflowClipMargin(mozilla::PhysicalAxes aClipAxes) const; + // Returns the applicable overflow-clip-margin values relative to our + // border-box. If aAllowNegative is false, prevents us from returning margins + // that are less than zero. This is useful for overflow computation (where you + // don't want the box to shrink). + nsMargin OverflowClipMargin(mozilla::PhysicalAxes aClipAxes, + bool aAllowNegative = true) const; // Returns the axes on which this frame should apply overflow clipping. mozilla::PhysicalAxes ShouldApplyOverflowClipping( diff --git a/layout/generic/nsImageFrame.cpp b/layout/generic/nsImageFrame.cpp @@ -2647,7 +2647,6 @@ void nsImageFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, } DisplayListClipState::AutoSaveRestore clipState(aBuilder); - const bool isViewTransition = mKind == Kind::ViewTransition; auto clipAxes = ShouldApplyOverflowClipping(StyleDisplay()); if (!clipAxes.isEmpty()) { nsRect clipRect; @@ -2657,15 +2656,6 @@ void nsImageFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, clipState.ClipContainingBlockDescendants( clipRect + aBuilder->ToReferenceFrame(this), haveRadii ? &radii : nullptr); - } else if (!isViewTransition) { - // Allow overflow by default for view transitions, but not for other image - // types, for historical reasons. - uint32_t clipFlags = - nsStyleUtil::ObjectPropsMightCauseOverflow(StylePosition()) - ? 0 - : DisplayListClipState::ASSUME_DRAWING_RESTRICTED_TO_CONTENT_RECT; - clipState.ClipContainingBlockDescendantsToContentBox(aBuilder, this, - clipFlags); } if (!mComputedSize.IsEmpty()) { @@ -2674,6 +2664,7 @@ void nsImageFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, nsCOMPtr<imgIRequest> currentRequest = GetCurrentRequest(); + const bool isViewTransition = mKind == Kind::ViewTransition; const bool isImageFromStyle = mKind != Kind::ImageLoadingContent && mKind != Kind::XULImage && !isViewTransition; const bool drawAltFeedback = [&] { diff --git a/layout/generic/nsVideoFrame.cpp b/layout/generic/nsVideoFrame.cpp @@ -697,20 +697,26 @@ void nsVideoFrame::BuildDisplayList(nsDisplayListBuilder* aBuilder, const bool shouldDisplayPoster = ShouldDisplayPoster(); - // NOTE: If we're displaying a poster image (instead of video data), we can - // trust the nsImageFrame to constrain its drawing to its content rect - // (which happens to be the same as our content rect). - uint32_t clipFlags; - if (shouldDisplayPoster || - !nsStyleUtil::ObjectPropsMightCauseOverflow(StylePosition())) { - clipFlags = DisplayListClipState::ASSUME_DRAWING_RESTRICTED_TO_CONTENT_RECT; - } else { - clipFlags = 0; + DisplayListClipState::AutoSaveRestore clipState(aBuilder); + auto clipAxes = ShouldApplyOverflowClipping(StyleDisplay()); + if (!clipAxes.isEmpty()) { + nsRect clipRect; + nsRectCornerRadii radii; + const bool haveRadii = + ComputeOverflowClipRectRelativeToSelf(clipAxes, clipRect, radii); + // NOTE: If we're displaying a poster image (instead of video data), we can + // trust the nsImageFrame to constrain its drawing to its content rect + // (which happens to be the same as our content rect). + const bool canOverflowWithoutRadii = + !shouldDisplayPoster && + nsStyleUtil::ObjectPropsMightCauseOverflow(StylePosition()); + if (haveRadii || canOverflowWithoutRadii) { + clipState.ClipContainingBlockDescendants( + clipRect + aBuilder->ToReferenceFrame(this), + haveRadii ? &radii : nullptr); + } } - DisplayListClipState::AutoClipContainingBlockDescendantsToContentBox clip( - aBuilder, this, clipFlags); - if (HasVideoElement() && !shouldDisplayPoster) { aLists.Content()->AppendNewToTop<nsDisplayVideo>(aBuilder, this); } diff --git a/layout/style/ServoBindings.toml b/layout/style/ServoBindings.toml @@ -434,7 +434,7 @@ cbindgen-types = [ { gecko = "StyleViewTransitionClass", servo = "crate::values::computed::ViewTransitionClass" }, { gecko = "StyleViewTransitionName", servo = "crate::values::computed::ViewTransitionName" }, { gecko = "StyleResize", servo = "crate::values::computed::Resize" }, - { gecko = "StyleOverflowClipBox", servo = "crate::values::computed::OverflowClipBox" }, + { gecko = "StyleOverflowClipMargin", servo = "crate::values::computed::OverflowClipMargin" }, { gecko = "StyleFloat", servo = "crate::values::computed::Float" }, { gecko = "StyleClear", servo = "crate::values::computed::Clear" }, { gecko = "StyleOverscrollBehavior", servo = "crate::values::computed::OverscrollBehavior" }, diff --git a/layout/style/nsStyleStruct.cpp b/layout/style/nsStyleStruct.cpp @@ -323,7 +323,8 @@ nsStyleMargin::nsStyleMargin() : mMargin(StyleRectWithAllSides( StyleMargin::LengthPercentage(LengthPercentage::Zero()))), mScrollMargin(StyleRectWithAllSides(StyleLength{0.})), - mOverflowClipMargin(StyleLength::Zero()) { + mOverflowClipMargin( + {StyleLength::Zero(), StyleOverflowClipMarginBox::PaddingBox}) { MOZ_COUNT_CTOR(nsStyleMargin); } diff --git a/layout/style/nsStyleStruct.h b/layout/style/nsStyleStruct.h @@ -494,9 +494,9 @@ struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStyleMargin { mozilla::StyleRect<mozilla::StyleMargin> 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; + // TODO: Add support per-axis/side clipping, see + // https://github.com/w3c/csswg-drafts/issues/7245 + mozilla::StyleOverflowClipMargin mOverflowClipMargin; }; struct MOZ_NEEDS_MEMMOVABLE_MEMBERS nsStylePadding { diff --git a/layout/style/res/html.css b/layout/style/res/html.css @@ -670,6 +670,23 @@ object[usemap] { color: blue; } +/* https://drafts.csswg.org/css-overflow-4/#overflow-control: + * + * Host languages should define UA style sheet rules that apply a default value + * of clip to such elements and set their overflow-clip-margin to content-box. + * + * NOTE(emilio): This rule is very hand-wavy, for now the + * <embed>/<iframe>/<object> cases are !important to match Blink, but see + * https://github.com/whatwg/html/issues/11986 for some discussion about making + * it explicit. + */ +canvas, +img, +video { + overflow-clip-margin: content-box; + overflow: clip; +} + frameset { display: block !important; overflow: clip; diff --git a/servo/components/style/properties/data.py b/servo/components/style/properties/data.py @@ -629,7 +629,6 @@ class Longhand(Property): "OutlineStyle", "Overflow", "OverflowAnchor", - "OverflowClipBox", "OverflowWrap", "OverscrollBehavior", "PageOrientation", diff --git a/servo/components/style/properties/longhands/margin.mako.rs b/servo/components/style/properties/longhands/margin.mako.rs @@ -30,9 +30,8 @@ ${helpers.predefined_type( "overflow-clip-margin", - "Length", - "computed::Length::zero()", - parse_method="parse_non_negative", + "OverflowClipMargin", + "computed::OverflowClipMargin::zero()", engines="gecko servo", spec="https://drafts.csswg.org/css-overflow/#propdef-overflow-clip-margin", affects="overflow", diff --git a/servo/components/style/values/computed/box.rs b/servo/components/style/values/computed/box.rs @@ -8,7 +8,8 @@ use crate::values::animated::{Animate, Procedure, ToAnimatedValue}; use crate::values::computed::length::{LengthPercentage, NonNegativeLength}; use crate::values::computed::{Context, Integer, Number, ToComputedValue}; use crate::values::generics::box_::{ - GenericContainIntrinsicSize, GenericLineClamp, GenericPerspective, GenericVerticalAlign, + GenericContainIntrinsicSize, GenericLineClamp, GenericOverflowClipMargin, GenericPerspective, + GenericVerticalAlign, }; use crate::values::specified::box_ as specified; use std::fmt; @@ -16,15 +17,17 @@ use style_traits::{CssWriter, ToCss}; pub use crate::values::specified::box_::{ Appearance, BaselineSource, BreakBetween, BreakWithin, Clear, Contain, ContainerName, - ContainerType, ContentVisibility, Display, Float, Overflow, OverflowAnchor, OverflowClipBox, - OverscrollBehavior, PositionProperty, ScrollSnapAlign, ScrollSnapAxis, ScrollSnapStop, - ScrollSnapStrictness, ScrollSnapType, ScrollbarGutter, TouchAction, WillChange, - WritingModeProperty, + ContainerType, ContentVisibility, Display, Float, Overflow, OverflowAnchor, OverscrollBehavior, + PositionProperty, ScrollSnapAlign, ScrollSnapAxis, ScrollSnapStop, ScrollSnapStrictness, + ScrollSnapType, ScrollbarGutter, TouchAction, WillChange, WritingModeProperty, }; /// A computed value for the `vertical-align` property. pub type VerticalAlign = GenericVerticalAlign<LengthPercentage>; +/// A computed value for the `overflow-clip-margin` property. +pub type OverflowClipMargin = GenericOverflowClipMargin<NonNegativeLength>; + /// A computed value for the `contain-intrinsic-size` property. pub type ContainIntrinsicSize = GenericContainIntrinsicSize<NonNegativeLength>; diff --git a/servo/components/style/values/computed/mod.rs b/servo/components/style/values/computed/mod.rs @@ -55,7 +55,7 @@ pub use self::border::{ pub use self::box_::{ Appearance, BaselineSource, BreakBetween, BreakWithin, Clear, Contain, ContainIntrinsicSize, ContainerName, ContainerType, ContentVisibility, Display, Float, LineClamp, Overflow, - OverflowAnchor, OverflowClipBox, OverscrollBehavior, Perspective, PositionProperty, Resize, + OverflowAnchor, OverflowClipMargin, OverscrollBehavior, Perspective, PositionProperty, Resize, ScrollSnapAlign, ScrollSnapAxis, ScrollSnapStop, ScrollSnapStrictness, ScrollSnapType, ScrollbarGutter, TouchAction, VerticalAlign, WillChange, WritingModeProperty, Zoom, }; diff --git a/servo/components/style/values/generics/box.rs b/servo/components/style/values/generics/box.rs @@ -5,6 +5,7 @@ //! Generic types for box properties. use crate::values::animated::ToAnimatedZero; +use crate::Zero; use std::fmt::{self, Write}; use style_traits::{CssWriter, ToCss}; @@ -155,7 +156,7 @@ pub struct GenericLineClamp<I>(pub I); pub use self::GenericLineClamp as LineClamp; -impl<I: crate::Zero> LineClamp<I> { +impl<I: Zero> LineClamp<I> { /// Returns the `none` value. pub fn none() -> Self { Self(crate::Zero::zero()) @@ -167,7 +168,7 @@ impl<I: crate::Zero> LineClamp<I> { } } -impl<I: crate::Zero + ToCss> ToCss for LineClamp<I> { +impl<I: Zero + ToCss> ToCss for LineClamp<I> { fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result where W: Write, @@ -246,3 +247,87 @@ impl PositionProperty { matches!(self, Self::Absolute | Self::Fixed) } } + +/// https://drafts.csswg.org/css-overflow-4/#overflow-clip-margin's <visual-box>. Note that the +/// spec has special behavior for the omitted keyword, but that's rather odd, see: +/// https://github.com/w3c/csswg-drafts/issues/13185 +#[allow(missing_docs)] +#[derive( + Clone, + ComputeSquaredDistance, + Copy, + Debug, + Eq, + MallocSizeOf, + PartialEq, + Parse, + SpecifiedValueInfo, + ToAnimatedValue, + ToComputedValue, + ToCss, + ToResolvedValue, + ToShmem, + ToTyped, +)] +#[repr(u8)] +pub enum OverflowClipMarginBox { + ContentBox, + PaddingBox, + BorderBox, +} + +/// https://drafts.csswg.org/css-overflow-4/#overflow-clip-margin +#[derive( + Animate, + Clone, + ComputeSquaredDistance, + Copy, + Debug, + Eq, + MallocSizeOf, + PartialEq, + SpecifiedValueInfo, + ToAnimatedValue, + ToComputedValue, + ToAnimatedZero, + ToResolvedValue, + ToShmem, + ToTyped, +)] +#[repr(C)] +pub struct GenericOverflowClipMargin<L> { + /// The offset of the clip. + pub offset: L, + /// The box that we're clipping to. + #[animation(constant)] + pub visual_box: OverflowClipMarginBox, +} + +pub use self::GenericOverflowClipMargin as OverflowClipMargin; + +impl<L: Zero> GenericOverflowClipMargin<L> { + /// Returns the `none` value. + pub fn zero() -> Self { + Self { + offset: Zero::zero(), + visual_box: OverflowClipMarginBox::PaddingBox, + } + } +} + +impl<L: Zero + ToCss> ToCss for OverflowClipMargin<L> { + fn to_css<W>(&self, dest: &mut CssWriter<W>) -> fmt::Result + where + W: Write, + { + if self.visual_box == OverflowClipMarginBox::PaddingBox { + return self.offset.to_css(dest); + } + self.visual_box.to_css(dest)?; + if !self.offset.is_zero() { + dest.write_char(' ')?; + self.offset.to_css(dest)?; + } + Ok(()) + } +} diff --git a/servo/components/style/values/specified/box.rs b/servo/components/style/values/specified/box.rs @@ -8,8 +8,8 @@ pub use crate::logical_geometry::WritingModeProperty; use crate::parser::{Parse, ParserContext}; use crate::properties::{LonghandId, PropertyDeclarationId, PropertyId}; use crate::values::generics::box_::{ - GenericContainIntrinsicSize, GenericLineClamp, GenericPerspective, GenericVerticalAlign, - VerticalAlignKeyword, + GenericContainIntrinsicSize, GenericLineClamp, GenericOverflowClipMargin, GenericPerspective, + GenericVerticalAlign, OverflowClipMarginBox, VerticalAlignKeyword, }; use crate::values::specified::length::{LengthPercentage, NonNegativeLength}; use crate::values::specified::{AllowQuirks, Integer, NonNegativeNumberOrPercentage}; @@ -30,6 +30,42 @@ fn grid_enabled() -> bool { style_config::get_bool("layout.grid.enabled") } +/// The specified value of `overflow-clip-margin`. +pub type OverflowClipMargin = GenericOverflowClipMargin<NonNegativeLength>; + +impl Parse for OverflowClipMargin { + // <visual-box> || <length [0,∞]> + fn parse<'i>( + context: &ParserContext, + input: &mut Parser<'i, '_>, + ) -> Result<Self, ParseError<'i>> { + use crate::Zero; + let mut offset = None; + let mut visual_box = None; + loop { + if offset.is_none() { + offset = input + .try_parse(|i| NonNegativeLength::parse(context, i)) + .ok(); + } + if visual_box.is_none() { + visual_box = input.try_parse(OverflowClipMarginBox::parse).ok(); + if visual_box.is_some() { + continue; + } + } + break; + } + if offset.is_none() && visual_box.is_none() { + return Err(input.new_custom_error(StyleParseErrorKind::UnspecifiedError)); + } + Ok(Self { + offset: offset.unwrap_or_else(NonNegativeLength::zero), + visual_box: visual_box.unwrap_or(OverflowClipMarginBox::PaddingBox), + }) + } +} + /// Defines an element’s display type, which consists of /// the two basic qualities of how an element generates boxes /// <https://drafts.csswg.org/css-display/#propdef-display> @@ -903,29 +939,6 @@ pub enum OverflowAnchor { None, } -#[allow(missing_docs)] -#[cfg_attr(feature = "servo", derive(Deserialize, Serialize))] -#[derive( - Clone, - Copy, - Debug, - Eq, - MallocSizeOf, - Parse, - PartialEq, - SpecifiedValueInfo, - ToComputedValue, - ToCss, - ToResolvedValue, - ToShmem, - ToTyped, -)] -#[repr(u8)] -pub enum OverflowClipBox { - PaddingBox, - ContentBox, -} - #[derive( Clone, Debug, diff --git a/servo/components/style/values/specified/mod.rs b/servo/components/style/values/specified/mod.rs @@ -25,7 +25,7 @@ use std::ops::Add; use style_traits::values::specified::AllowedNumericType; use style_traits::{CssWriter, ParseError, SpecifiedValueInfo, StyleParseErrorKind, ToCss}; -pub use self::align::{ContentDistribution, ItemPlacement, SelfAlignment, JustifyItems}; +pub use self::align::{ContentDistribution, ItemPlacement, JustifyItems, SelfAlignment}; pub use self::angle::{AllowUnitlessZeroAngle, Angle}; pub use self::animation::{ AnimationComposition, AnimationDirection, AnimationDuration, AnimationFillMode, @@ -43,7 +43,7 @@ pub use self::border::{ pub use self::box_::{ Appearance, BaselineSource, BreakBetween, BreakWithin, Clear, Contain, ContainIntrinsicSize, ContainerName, ContainerType, ContentVisibility, Display, Float, LineClamp, Overflow, - OverflowAnchor, OverflowClipBox, OverscrollBehavior, Perspective, PositionProperty, Resize, + OverflowAnchor, OverflowClipMargin, OverscrollBehavior, Perspective, PositionProperty, Resize, ScrollSnapAlign, ScrollSnapAxis, ScrollSnapStop, ScrollSnapStrictness, ScrollSnapType, ScrollbarGutter, TouchAction, VerticalAlign, WillChange, WillChangeBits, WritingModeProperty, Zoom, diff --git a/servo/ports/geckolib/cbindgen.toml b/servo/ports/geckolib/cbindgen.toml @@ -180,7 +180,7 @@ include = [ "TransitionBehavior", "ViewTimelineInset", "OverflowAnchor", - "OverflowClipBox", + "OverflowClipMargin", "Resize", "Overflow", "LengthPercentage", diff --git a/testing/web-platform/meta/css/css-overflow/overflow-canvas.html.ini b/testing/web-platform/meta/css/css-overflow/overflow-canvas.html.ini @@ -1,2 +0,0 @@ -[overflow-canvas.html] - expected: FAIL diff --git a/testing/web-platform/meta/css/css-overflow/overflow-clip-margin-mul-column-content-box.html.ini b/testing/web-platform/meta/css/css-overflow/overflow-clip-margin-mul-column-content-box.html.ini @@ -1,2 +0,0 @@ -[overflow-clip-margin-mul-column-content-box.html] - expected: FAIL diff --git a/testing/web-platform/meta/css/css-overflow/overflow-clip-margin-visual-box-and-value.html.ini b/testing/web-platform/meta/css/css-overflow/overflow-clip-margin-visual-box-and-value.html.ini @@ -1,2 +0,0 @@ -[overflow-clip-margin-visual-box-and-value.html] - expected: FAIL diff --git a/testing/web-platform/meta/css/css-overflow/overflow-clip-margin-visual-box.html.ini b/testing/web-platform/meta/css/css-overflow/overflow-clip-margin-visual-box.html.ini @@ -1,2 +0,0 @@ -[overflow-clip-margin-visual-box.html] - expected: FAIL diff --git a/testing/web-platform/meta/css/css-overflow/overflow-clip-scroll-size.html.ini b/testing/web-platform/meta/css/css-overflow/overflow-clip-scroll-size.html.ini @@ -1,5 +0,0 @@ -[overflow-clip-scroll-size.html] - expected: - if (os == "android") and fission: [OK, TIMEOUT] - [scroll size should take into account border size and overflow-clip-margin] - expected: FAIL diff --git a/testing/web-platform/meta/css/css-overflow/overflow-img-border-radius.html.ini b/testing/web-platform/meta/css/css-overflow/overflow-img-border-radius.html.ini @@ -1,2 +0,0 @@ -[overflow-img-border-radius.html] - expected: FAIL diff --git a/testing/web-platform/meta/css/css-overflow/overflow-img-object-position.html.ini b/testing/web-platform/meta/css/css-overflow/overflow-img-object-position.html.ini @@ -1,2 +0,0 @@ -[overflow-img-object-position.html] - expected: FAIL diff --git a/testing/web-platform/meta/css/css-overflow/overflow-img-svg.html.ini b/testing/web-platform/meta/css/css-overflow/overflow-img-svg.html.ini @@ -1,2 +0,0 @@ -[overflow-img-svg.html] - expected: FAIL diff --git a/testing/web-platform/meta/css/css-overflow/overflow-img.html.ini b/testing/web-platform/meta/css/css-overflow/overflow-img.html.ini @@ -1,2 +0,0 @@ -[overflow-img.html] - expected: FAIL diff --git a/testing/web-platform/meta/css/css-overflow/overflow-video.html.ini b/testing/web-platform/meta/css/css-overflow/overflow-video.html.ini @@ -1,2 +0,0 @@ -[overflow-video.html] - expected: FAIL diff --git a/testing/web-platform/meta/css/css-overflow/parsing/overflow-clip-margin-computed.html.ini b/testing/web-platform/meta/css/css-overflow/parsing/overflow-clip-margin-computed.html.ini @@ -1,48 +0,0 @@ -[overflow-clip-margin-computed.html] - [Property overflow-clip-margin value '0px'] - expected: - if not debug and (os == "mac"): [PASS, FAIL] - if not debug and (os == "linux"): [PASS, FAIL] - if not debug and (os == "android"): [PASS, FAIL] - - [Property overflow-clip-margin value '10px'] - expected: - if not debug and (os == "mac"): [PASS, FAIL] - if not debug and (os == "android"): [PASS, FAIL] - if not debug and (os == "linux"): [PASS, FAIL] - - [Property overflow-clip-margin value 'content-box'] - expected: FAIL - - [Property overflow-clip-margin value 'content-box 0px'] - expected: FAIL - - [Property overflow-clip-margin value 'content-box 10px'] - expected: FAIL - - [Property overflow-clip-margin value '10px content-box'] - expected: FAIL - - [Property overflow-clip-margin value 'padding-box'] - expected: FAIL - - [Property overflow-clip-margin value 'padding-box 0px'] - expected: FAIL - - [Property overflow-clip-margin value 'padding-box 10px'] - expected: FAIL - - [Property overflow-clip-margin value '10px padding-box'] - expected: FAIL - - [Property overflow-clip-margin value 'border-box'] - expected: FAIL - - [Property overflow-clip-margin value 'border-box 0px'] - expected: FAIL - - [Property overflow-clip-margin value 'border-box 10px'] - expected: FAIL - - [Property overflow-clip-margin value '10px border-box'] - expected: FAIL diff --git a/testing/web-platform/meta/css/css-overflow/parsing/overflow-clip-margin.html.ini b/testing/web-platform/meta/css/css-overflow/parsing/overflow-clip-margin.html.ini @@ -1,44 +0,0 @@ -[overflow-clip-margin.html] - expected: - if (os == "android") and fission: [OK, TIMEOUT] - [e.style['overflow-clip-margin'\] = "10px" should set the property value] - expected: - if not debug and (os == "android"): [PASS, FAIL] - if not debug and (os == "mac"): [PASS, FAIL] - if not debug and (os == "linux"): [PASS, FAIL] - - [e.style['overflow-clip-margin'\] = "content-box" should set the property value] - expected: FAIL - - [e.style['overflow-clip-margin'\] = "content-box 10px" should set the property value] - expected: FAIL - - [e.style['overflow-clip-margin'\] = "10px content-box" should set the property value] - expected: FAIL - - [e.style['overflow-clip-margin'\] = "0px content-box" should set the property value] - expected: FAIL - - [e.style['overflow-clip-margin'\] = "padding-box" should set the property value] - expected: FAIL - - [e.style['overflow-clip-margin'\] = "padding-box 0px" should set the property value] - expected: FAIL - - [e.style['overflow-clip-margin'\] = "padding-box 10px" should set the property value] - expected: FAIL - - [e.style['overflow-clip-margin'\] = "10px padding-box" should set the property value] - expected: FAIL - - [e.style['overflow-clip-margin'\] = "border-box" should set the property value] - expected: FAIL - - [e.style['overflow-clip-margin'\] = "border-box 0px" should set the property value] - expected: FAIL - - [e.style['overflow-clip-margin'\] = "border-box 10px" should set the property value] - expected: FAIL - - [e.style['overflow-clip-margin'\] = "10px border-box" should set the property value] - expected: FAIL diff --git a/testing/web-platform/meta/css/css-page/background-image-only-for-print.html.ini b/testing/web-platform/meta/css/css-page/background-image-only-for-print.html.ini @@ -1,4 +0,0 @@ -[background-image-only-for-print.html] - expected: - if os == "mac": PASS - FAIL diff --git a/testing/web-platform/meta/css/css-typed-om/the-stylepropertymap/properties/overflow-clip-margin.html.ini b/testing/web-platform/meta/css/css-typed-om/the-stylepropertymap/properties/overflow-clip-margin.html.ini @@ -1,21 +0,0 @@ -[overflow-clip-margin.html] - ['overflow-clip-margin' does not support 'border-box'] - expected: FAIL - - ['overflow-clip-margin' does not support 'content-box'] - expected: FAIL - - ['overflow-clip-margin' does not support 'padding-box'] - expected: FAIL - - ['overflow-clip-margin' does not support 'padding-box 10px'] - expected: FAIL - - ['overflow-clip-margin' does not support '10px content-box'] - expected: FAIL - - ['overflow-clip-margin' does not support '0px'] - expected: FAIL - - ['overflow-clip-margin' does not support '10px'] - expected: FAIL diff --git a/toolkit/content/xul.css b/toolkit/content/xul.css @@ -638,6 +638,12 @@ findbar { contain: inline-size; } +/* Matches legacy behavior and <html:img> */ +image { + overflow-clip-margin: content-box; + overflow: clip; +} + /* Some elements that in HTML blocks should be inline-level by default */ button, image {