diff --git a/components/layout/display_list/mod.rs b/components/layout/display_list/mod.rs index 0659a8b3b3b..543ebb64ae2 100644 --- a/components/layout/display_list/mod.rs +++ b/components/layout/display_list/mod.rs @@ -1100,12 +1100,7 @@ impl<'a> BuilderForBoxFragment<'a> { fn build(&mut self, builder: &mut DisplayListBuilder, section: StackingContextSection) { if self.is_hit_test_for_scrollable_overflow { - self.build_hit_test( - builder, - self.fragment - .reachable_scrollable_overflow_region() - .to_webrender(), - ); + self.build_hit_test(builder, self.fragment.scrollable_overflow().to_webrender()); return; } diff --git a/components/layout/display_list/stacking_context.rs b/components/layout/display_list/stacking_context.rs index 41ce5351aef..ab2205d8ba9 100644 --- a/components/layout/display_list/stacking_context.rs +++ b/components/layout/display_list/stacking_context.rs @@ -1472,12 +1472,10 @@ impl BoxFragment { y: overflow.y.into(), }; - let content_rect = self.reachable_scrollable_overflow_region().to_webrender(); - let scroll_tree_node_id = stacking_context_tree.define_scroll_frame( parent_scroll_node_id, external_id, - content_rect, + self.scrollable_overflow().to_webrender(), scroll_frame_rect, sensitivity, ); diff --git a/components/layout/fragment_tree/box_fragment.rs b/components/layout/fragment_tree/box_fragment.rs index eb63038b7d7..2bddc99fca9 100644 --- a/components/layout/fragment_tree/box_fragment.rs +++ b/components/layout/fragment_tree/box_fragment.rs @@ -88,7 +88,10 @@ pub(crate) struct BoxFragment { block_margins_collapsed_with_children: Option>, - /// The scrollable overflow of this box fragment. + /// The scrollable overflow of this box fragment in the same coordiante system as + /// [`Self::content_rect`] ie a rectangle within the parent fragment's content + /// rectangle. This does not take into account any transforms this fragment applies. + /// This is handled when calling [`Self::scrollable_overflow_for_parent`]. scrollable_overflow: Option>, /// The resolved box insets if this box is `position: sticky`. These are calculated @@ -202,19 +205,57 @@ impl BoxFragment { .expect("Should only call `scrollable_overflow()` after calculating overflow") } + /// This is an implementation of: + /// - . + /// - pub(crate) fn calculate_scrollable_overflow(&mut self) { - let scrollable_overflow_from_children = self - .children - .iter() - .fold(PhysicalRect::zero(), |acc, child| { - acc.union(&child.calculate_scrollable_overflow_for_parent()) - }); let physical_padding_rect = self.padding_rect(); let content_origin = self.content_rect.origin.to_vector(); - self.scrollable_overflow = Some( - physical_padding_rect - .union(&scrollable_overflow_from_children.translate(content_origin)), - ); + + // > The scrollable overflow area is the union of: + // > * The scroll container’s own padding box. + // > * All line boxes directly contained by the scroll container. + // > * The border boxes of all boxes for which it is the containing block and + // > whose border boxes are positioned not wholly in the unreachable + // > scrollable overflow region, accounting for transforms by projecting + // > each box onto the plane of the element that establishes its 3D + // > rendering context. + // > * The margin areas of grid item and flex item boxes for which the box + // > establishes a containing block. + // > * The scrollable overflow areas of all of the above boxes (including zero-area + // > boxes and accounting for transforms as described above), provided they + // > themselves have overflow: visible (i.e. do not themselves trap the overflow) + // > and that scrollable overflow is not already clipped (e.g. by the clip property + // > or the contain property). + // > * Additional padding added to the scrollable overflow rectangle as necessary + // to enable scroll positions that satisfy the requirements of both place-content: + // start and place-content: end alignment. + // + // TODO(mrobinson): Below we are handling the border box and the scrollable + // overflow together, but from the specification it seems that if the border + // box of an item is in the "wholly unreachable scrollable overflow region", but + // its scrollable overflow is not, it should also be excluded. + let scrollable_overflow = self + .children + .iter() + .fold(physical_padding_rect, |acc, child| { + let scrollable_overflow_from_child = child + .calculate_scrollable_overflow_for_parent() + .translate(content_origin); + + // Note that this doesn't just exclude scrollable overflow outside the + // wholly unrechable scrollable overflow area, but also clips it. This + // makes the resulting value more like the "scroll area" rather than the + // "scrollable overflow." + let scrollable_overflow_from_child = self + .clip_wholly_unreachable_scrollable_overflow( + scrollable_overflow_from_child, + physical_padding_rect, + ); + acc.union(&scrollable_overflow_from_child) + }); + + self.scrollable_overflow = Some(scrollable_overflow) } pub(crate) fn set_containing_block(&mut self, containing_block: &PhysicalRect) { @@ -281,11 +322,6 @@ impl BoxFragment { } pub(crate) fn scrollable_overflow_for_parent(&self) -> PhysicalRect { - // TODO: Properly handle absolutely positioned fragments. - if self.style.get_box().position.is_absolutely_positioned() { - return PhysicalRect::zero(); - } - let mut overflow = self.border_rect(); if !self.style.establishes_scroll_container(self.base.flags) { // https://www.w3.org/TR/css-overflow-3/#scrollable @@ -308,45 +344,46 @@ impl BoxFragment { } } + if !self + .style + .has_effective_transform_or_perspective(self.base.flags) + { + return overflow; + } + // // > ...accounting for transforms by projecting each box onto the plane of // > the element that establishes its 3D rendering context. [CSS3-TRANSFORMS] // Both boxes and its scrollable overflow (if it is included) should be transformed accordingly. // - // TODO(stevennovaryo): We are supposed to handle perspective transform and 3d context, but it is yet to happen. - if self - .style - .has_effective_transform_or_perspective(self.base.flags) - { - if let Some(transform) = - self.calculate_transform_matrix(&self.border_rect().to_untyped()) - { - if let Some(transformed_overflow_box) = - transform.outer_transformed_rect(&overflow.to_webrender().to_rect()) - { - overflow = - f32_rect_to_au_rect(transformed_overflow_box.to_untyped()).cast_unit(); - } - } - } - - overflow + // TODO(stevennovaryo): We are supposed to handle perspective transform and 3d + // contexts, but it is yet to happen. + self.calculate_transform_matrix(&self.border_rect().to_untyped()) + .and_then(|transform| { + transform.outer_transformed_rect(&overflow.to_webrender().to_rect()) + }) + .map(|transformed_rect| f32_rect_to_au_rect(transformed_rect.to_untyped()).cast_unit()) + .unwrap_or(overflow) } - /// - /// > area beyond the scroll origin in either axis is considered the unreachable scrollable overflow region - /// - /// Return the clipped the scrollable overflow based on its scroll origin, determined by overflow direction. - /// For an element, the clip rect is the padding rect and for viewport, it is the initial containing block. - pub(crate) fn clip_unreachable_scrollable_overflow_region( + /// Return the clipped the scrollable overflow based on its scroll origin, determined + /// by overflow direction. For an element, the clip rect is the padding rect and for + /// viewport, it is the initial containing block. + pub(crate) fn clip_wholly_unreachable_scrollable_overflow( &self, scrollable_overflow: PhysicalRect, clipping_rect: PhysicalRect, ) -> PhysicalRect { + // From : + // > Unless otherwise adjusted (e.g. by content alignment [css-align-3]), the area + // > beyond the scroll origin in either axis is considered the unreachable scrollable + // > overflow region: content rendered here is not accessible to the reader, see § 2.2 + // > Scrollable Overflow. A scroll container is said to be scrolled to its scroll + // > origin when its scroll origin coincides with the corresponding corner of its + // > scrollport. This scroll position, the scroll origin position, usually, but not + // > always, coincides with the initial scroll position. let scrolling_direction = self.style.overflow_direction(); - let mut scrollable_overflow_box = scrollable_overflow.to_box2d(); let mut clipping_box = clipping_rect.to_box2d(); - if scrolling_direction.rightward { clipping_box.max.x = MAX_AU; } else { @@ -359,7 +396,9 @@ impl BoxFragment { clipping_box.min.y = MIN_AU; } - scrollable_overflow_box = scrollable_overflow_box.intersection_unchecked(&clipping_box); + let scrollable_overflow_box = scrollable_overflow + .to_box2d() + .intersection_unchecked(&clipping_box); match scrollable_overflow_box.is_negative() { true => PhysicalRect::zero(), @@ -367,18 +406,6 @@ impl BoxFragment { } } - /// - /// > area beyond the scroll origin in either axis is considered the unreachable scrollable overflow region - /// - /// Return the clipped the scrollable overflow based on its scroll origin, determined by overflow direction. - /// This will coincides with the scrollport if the fragment is a scroll container. - pub(crate) fn reachable_scrollable_overflow_region(&self) -> PhysicalRect { - self.clip_unreachable_scrollable_overflow_region( - self.scrollable_overflow(), - self.padding_rect(), - ) - } - pub(crate) fn calculate_resolved_insets_if_positioned(&self) -> PhysicalSides { let position = self.style.get_box().position; debug_assert_ne!( diff --git a/components/layout/fragment_tree/fragment.rs b/components/layout/fragment_tree/fragment.rs index 10338c78743..00efe9ac64b 100644 --- a/components/layout/fragment_tree/fragment.rs +++ b/components/layout/fragment_tree/fragment.rs @@ -162,7 +162,7 @@ impl Fragment { } } - pub fn unclipped_scrolling_area(&self) -> PhysicalRect { + pub(crate) fn scrolling_area(&self) -> PhysicalRect { match self { Fragment::Box(fragment) | Fragment::Float(fragment) => { let fragment = fragment.borrow(); @@ -172,17 +172,6 @@ impl Fragment { } } - pub fn scrolling_area(&self) -> PhysicalRect { - match self { - Fragment::Box(fragment) | Fragment::Float(fragment) => { - let fragment = fragment.borrow(); - fragment - .offset_by_containing_block(&fragment.reachable_scrollable_overflow_region()) - }, - _ => self.scrollable_overflow_for_parent(), - } - } - pub(crate) fn scrollable_overflow_for_parent(&self) -> PhysicalRect { match self { Fragment::Box(fragment) | Fragment::Float(fragment) => { diff --git a/components/layout/fragment_tree/fragment_tree.rs b/components/layout/fragment_tree/fragment_tree.rs index b59ace43aa6..bab28ccc4de 100644 --- a/components/layout/fragment_tree/fragment_tree.rs +++ b/components/layout/fragment_tree/fragment_tree.rs @@ -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 . 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 + // . + 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( @@ -141,29 +153,6 @@ impl FragmentTree { .find_map(|child| child.find(&info, 0, &mut process_func)) } - /// - /// - /// Scrolling area for a viewport that is clipped according to overflow direction of root element. - pub fn get_scrolling_area_for_viewport(&self) -> PhysicalRect { - 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 `` element's [`Fragment`], if it exists in this [`FragmentTree`]. pub(crate) fn body_fragment(&self) -> Option> { fn find_body(children: &[Fragment]) -> Option> { diff --git a/components/layout/query.rs b/components/layout/query.rs index 7f304ff2da1..137b6fc382d 100644 --- a/components/layout/query.rs +++ b/components/layout/query.rs @@ -92,7 +92,7 @@ pub fn process_node_scroll_area_request( .first() .map(Fragment::scrolling_area) .unwrap_or_default(), - None => tree.get_scrolling_area_for_viewport(), + None => tree.scrollable_overflow(), }; Rect::new( diff --git a/tests/wpt/meta/css/css-flexbox/negative-overflow.html.ini b/tests/wpt/meta/css/css-flexbox/negative-overflow.html.ini index e483189946d..9881852c542 100644 --- a/tests/wpt/meta/css/css-flexbox/negative-overflow.html.ini +++ b/tests/wpt/meta/css/css-flexbox/negative-overflow.html.ini @@ -5,9 +5,6 @@ [.flexbox 11] expected: FAIL - [.flexbox 2] - expected: FAIL - [.flexbox 8] expected: FAIL diff --git a/tests/wpt/meta/css/css-masking/clip-path/clip-path-fixed-scroll.html.ini b/tests/wpt/meta/css/css-masking/clip-path/clip-path-fixed-scroll.html.ini new file mode 100644 index 00000000000..7addfba3787 --- /dev/null +++ b/tests/wpt/meta/css/css-masking/clip-path/clip-path-fixed-scroll.html.ini @@ -0,0 +1,2 @@ +[clip-path-fixed-scroll.html] + expected: FAIL diff --git a/tests/wpt/meta/css/css-overflow/overflow-outside-padding.html.ini b/tests/wpt/meta/css/css-overflow/overflow-outside-padding.html.ini new file mode 100644 index 00000000000..58ca44deaae --- /dev/null +++ b/tests/wpt/meta/css/css-overflow/overflow-outside-padding.html.ini @@ -0,0 +1,3 @@ +[overflow-outside-padding.html] + [#target did not trigger scroll overflow] + expected: FAIL diff --git a/tests/wpt/meta/css/css-overflow/scrollable-overflow-transform-005.tentative.html.ini b/tests/wpt/meta/css/css-overflow/scrollable-overflow-transform-005.tentative.html.ini deleted file mode 100644 index 901184b8ed2..00000000000 --- a/tests/wpt/meta/css/css-overflow/scrollable-overflow-transform-005.tentative.html.ini +++ /dev/null @@ -1,3 +0,0 @@ -[scrollable-overflow-transform-005.tentative.html] - [.container 6] - expected: FAIL diff --git a/tests/wpt/meta/css/css-overflow/scrollable-overflow-transform-009.html.ini b/tests/wpt/meta/css/css-overflow/scrollable-overflow-transform-009.html.ini deleted file mode 100644 index cd8e986f634..00000000000 --- a/tests/wpt/meta/css/css-overflow/scrollable-overflow-transform-009.html.ini +++ /dev/null @@ -1,6 +0,0 @@ -[scrollable-overflow-transform-009.html] - [.container 2] - expected: FAIL - - [.container 3] - expected: FAIL diff --git a/tests/wpt/meta/css/css-position/position-absolute-under-non-containing-stacking-context.html.ini b/tests/wpt/meta/css/css-position/position-absolute-under-non-containing-stacking-context.html.ini deleted file mode 100644 index ac4f8b57db3..00000000000 --- a/tests/wpt/meta/css/css-position/position-absolute-under-non-containing-stacking-context.html.ini +++ /dev/null @@ -1,2 +0,0 @@ -[position-absolute-under-non-containing-stacking-context.html] - expected: FAIL diff --git a/tests/wpt/meta/css/css-position/positon-absolute-scrollable-overflow-001.html.ini b/tests/wpt/meta/css/css-position/positon-absolute-scrollable-overflow-001.html.ini index 85b4aad53ef..ed66f92a6e9 100644 --- a/tests/wpt/meta/css/css-position/positon-absolute-scrollable-overflow-001.html.ini +++ b/tests/wpt/meta/css/css-position/positon-absolute-scrollable-overflow-001.html.ini @@ -11,9 +11,6 @@ [.containing-block 4] expected: FAIL - [.containing-block 7] - expected: FAIL - [.containing-block 8] expected: FAIL @@ -25,6 +22,3 @@ [.containing-block 11] expected: FAIL - - [.containing-block 14] - expected: FAIL diff --git a/tests/wpt/meta/css/cssom-view/scrollTo-zoom.html.ini b/tests/wpt/meta/css/cssom-view/scrollTo-zoom.html.ini deleted file mode 100644 index c833ebfe21f..00000000000 --- a/tests/wpt/meta/css/cssom-view/scrollTo-zoom.html.ini +++ /dev/null @@ -1,2 +0,0 @@ -[scrollTo-zoom.html] - expected: FAIL