layout: Unify scrollable overflow calculation and include position: absolute (#37475)

Previously, layout was handling scrollable overflow and srolling area
calculation separately, only excluding the "unreachable scrollable
overflow region" at the last step. In addition, `position: absolute` was
not included in scrollable overflow calculation.

This change combines the two concepts into a single scrollable overflow
calculation and starts taking into account `position: absolute`.
Finally, `BoxFragment::scrollable_overflow_for_parent` is converted to
use early returns which reduces the amount of indentation.

Fixes #35928.
Fixes #37204.
Testing: This causes some WPT test to pass, but also two to start
failing:
- `/css/css-masking/clip-path/clip-path-fixed-scroll.html`: This seems
to fail
because script is scrolling past the boundaries of the document. This is
a
failure that was uncovered by the fixed element now being added to the
   page's scroll area.
- `/css/css-overflow/overflow-outside-padding.html`: One test has
started to fail
here because now the absolutely positioned element is included in the
scroll area,
and I think there is an issue with how we are placing RTL items with
negative margins.

Signed-off-by: Martin Robinson <mrobinson@igalia.com>
This commit is contained in:
Martin Robinson 2025-06-16 13:30:31 +02:00 committed by GitHub
parent 29e618dcf7
commit 0f61361e27
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 123 additions and 142 deletions

View file

@ -102,29 +102,41 @@ impl FragmentTree {
.expect("Should only call `scrollable_overflow()` after calculating overflow")
}
/// Calculate the scrollable overflow / scrolling area for this [`FragmentTree`] according
/// to <https://drafts.csswg.org/cssom-view/#scrolling-area>.
pub(crate) fn calculate_scrollable_overflow(&self) {
self.scrollable_overflow
.set(Some(self.root_fragments.iter().fold(
PhysicalRect::zero(),
|acc, child| {
let child_overflow = child.calculate_scrollable_overflow_for_parent();
let scrollable_overflow = || {
let Some(first_root_fragment) = self.root_fragments.first() else {
return self.initial_containing_block;
};
// https://drafts.csswg.org/css-overflow/#scrolling-direction
// We want to clip scrollable overflow on box-start and inline-start
// sides of the scroll container.
//
// FIXME(mrobinson, bug 25564): This should take into account writing
// mode.
let child_overflow = PhysicalRect::new(
euclid::Point2D::zero(),
euclid::Size2D::new(
child_overflow.size.width + child_overflow.origin.x,
child_overflow.size.height + child_overflow.origin.y,
),
);
acc.union(&child_overflow)
let scrollable_overflow = self.root_fragments.iter().fold(
self.initial_containing_block,
|overflow, fragment| {
fragment
.calculate_scrollable_overflow_for_parent()
.union(&overflow)
},
)));
);
// Assuming that the first fragment is the root element, ensure that
// scrollable overflow that is unreachable is not included in the final
// rectangle. See
// <https://drafts.csswg.org/css-overflow/#scrolling-direction>.
let first_root_fragment = match first_root_fragment {
Fragment::Box(fragment) | Fragment::Float(fragment) => fragment.borrow(),
_ => return scrollable_overflow,
};
if !first_root_fragment.is_root_element() {
return scrollable_overflow;
}
first_root_fragment.clip_wholly_unreachable_scrollable_overflow(
scrollable_overflow,
self.initial_containing_block,
)
};
self.scrollable_overflow.set(Some(scrollable_overflow()))
}
pub(crate) fn find<T>(
@ -141,29 +153,6 @@ impl FragmentTree {
.find_map(|child| child.find(&info, 0, &mut process_func))
}
/// <https://drafts.csswg.org/cssom-view/#scrolling-area>
///
/// Scrolling area for a viewport that is clipped according to overflow direction of root element.
pub fn get_scrolling_area_for_viewport(&self) -> PhysicalRect<Au> {
let mut scroll_area = self.initial_containing_block;
if let Some(root_fragment) = self.root_fragments.first() {
for fragment in self.root_fragments.iter() {
scroll_area = fragment.unclipped_scrolling_area().union(&scroll_area);
}
match root_fragment {
Fragment::Box(fragment) | Fragment::Float(fragment) => fragment
.borrow()
.clip_unreachable_scrollable_overflow_region(
scroll_area,
self.initial_containing_block,
),
_ => scroll_area,
}
} else {
scroll_area
}
}
/// Find the `<body>` element's [`Fragment`], if it exists in this [`FragmentTree`].
pub(crate) fn body_fragment(&self) -> Option<ArcRefCell<BoxFragment>> {
fn find_body(children: &[Fragment]) -> Option<ArcRefCell<BoxFragment>> {