diff --git a/Cargo.lock b/Cargo.lock index aa4ff7c1d7f..1590681f08a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6613,7 +6613,7 @@ dependencies = [ [[package]] name = "selectors" version = "0.28.0" -source = "git+https://github.com/servo/stylo?branch=2025-05-01#610fcfb48987d75b7d1d53887882fcdb244d74ee" +source = "git+https://github.com/servo/stylo?branch=2025-05-01#945b70e9a1984cd44ee56b7a674c302b19a4f620" dependencies = [ "bitflags 2.9.1", "cssparser", @@ -6908,7 +6908,7 @@ dependencies = [ [[package]] name = "servo_arc" version = "0.4.1" -source = "git+https://github.com/servo/stylo?branch=2025-05-01#610fcfb48987d75b7d1d53887882fcdb244d74ee" +source = "git+https://github.com/servo/stylo?branch=2025-05-01#945b70e9a1984cd44ee56b7a674c302b19a4f620" dependencies = [ "serde", "stable_deref_trait", @@ -7369,7 +7369,7 @@ dependencies = [ [[package]] name = "stylo" version = "0.3.0" -source = "git+https://github.com/servo/stylo?branch=2025-05-01#610fcfb48987d75b7d1d53887882fcdb244d74ee" +source = "git+https://github.com/servo/stylo?branch=2025-05-01#945b70e9a1984cd44ee56b7a674c302b19a4f620" dependencies = [ "app_units", "arrayvec", @@ -7427,7 +7427,7 @@ dependencies = [ [[package]] name = "stylo_atoms" version = "0.3.0" -source = "git+https://github.com/servo/stylo?branch=2025-05-01#610fcfb48987d75b7d1d53887882fcdb244d74ee" +source = "git+https://github.com/servo/stylo?branch=2025-05-01#945b70e9a1984cd44ee56b7a674c302b19a4f620" dependencies = [ "string_cache", "string_cache_codegen", @@ -7436,12 +7436,12 @@ dependencies = [ [[package]] name = "stylo_config" version = "0.3.0" -source = "git+https://github.com/servo/stylo?branch=2025-05-01#610fcfb48987d75b7d1d53887882fcdb244d74ee" +source = "git+https://github.com/servo/stylo?branch=2025-05-01#945b70e9a1984cd44ee56b7a674c302b19a4f620" [[package]] name = "stylo_derive" version = "0.3.0" -source = "git+https://github.com/servo/stylo?branch=2025-05-01#610fcfb48987d75b7d1d53887882fcdb244d74ee" +source = "git+https://github.com/servo/stylo?branch=2025-05-01#945b70e9a1984cd44ee56b7a674c302b19a4f620" dependencies = [ "darling", "proc-macro2", @@ -7453,7 +7453,7 @@ dependencies = [ [[package]] name = "stylo_dom" version = "0.3.0" -source = "git+https://github.com/servo/stylo?branch=2025-05-01#610fcfb48987d75b7d1d53887882fcdb244d74ee" +source = "git+https://github.com/servo/stylo?branch=2025-05-01#945b70e9a1984cd44ee56b7a674c302b19a4f620" dependencies = [ "bitflags 2.9.1", "stylo_malloc_size_of", @@ -7462,7 +7462,7 @@ dependencies = [ [[package]] name = "stylo_malloc_size_of" version = "0.3.0" -source = "git+https://github.com/servo/stylo?branch=2025-05-01#610fcfb48987d75b7d1d53887882fcdb244d74ee" +source = "git+https://github.com/servo/stylo?branch=2025-05-01#945b70e9a1984cd44ee56b7a674c302b19a4f620" dependencies = [ "app_units", "cssparser", @@ -7479,12 +7479,12 @@ dependencies = [ [[package]] name = "stylo_static_prefs" version = "0.3.0" -source = "git+https://github.com/servo/stylo?branch=2025-05-01#610fcfb48987d75b7d1d53887882fcdb244d74ee" +source = "git+https://github.com/servo/stylo?branch=2025-05-01#945b70e9a1984cd44ee56b7a674c302b19a4f620" [[package]] name = "stylo_traits" version = "0.3.0" -source = "git+https://github.com/servo/stylo?branch=2025-05-01#610fcfb48987d75b7d1d53887882fcdb244d74ee" +source = "git+https://github.com/servo/stylo?branch=2025-05-01#945b70e9a1984cd44ee56b7a674c302b19a4f620" dependencies = [ "app_units", "bitflags 2.9.1", @@ -7893,7 +7893,7 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "to_shmem" version = "0.2.0" -source = "git+https://github.com/servo/stylo?branch=2025-05-01#610fcfb48987d75b7d1d53887882fcdb244d74ee" +source = "git+https://github.com/servo/stylo?branch=2025-05-01#945b70e9a1984cd44ee56b7a674c302b19a4f620" dependencies = [ "cssparser", "servo_arc", @@ -7906,7 +7906,7 @@ dependencies = [ [[package]] name = "to_shmem_derive" version = "0.1.0" -source = "git+https://github.com/servo/stylo?branch=2025-05-01#610fcfb48987d75b7d1d53887882fcdb244d74ee" +source = "git+https://github.com/servo/stylo?branch=2025-05-01#945b70e9a1984cd44ee56b7a674c302b19a4f620" dependencies = [ "darling", "proc-macro2", diff --git a/components/layout/display_list/stacking_context.rs b/components/layout/display_list/stacking_context.rs index 66d8421e5f7..17e8f2ef4fd 100644 --- a/components/layout/display_list/stacking_context.rs +++ b/components/layout/display_list/stacking_context.rs @@ -618,7 +618,7 @@ impl StackingContext { // If it’s larger, we also want to paint areas reachable after scrolling. let painting_area = fragment_tree .initial_containing_block - .union(&fragment_tree.scrollable_overflow) + .union(&fragment_tree.scrollable_overflow()) .to_webrender(); let background_color = @@ -1346,7 +1346,7 @@ impl BoxFragment { let position = self.style.get_box().position; // https://drafts.csswg.org/css2/#clipping // The clip property applies only to absolutely positioned elements - if position != ComputedPosition::Absolute && position != ComputedPosition::Fixed { + if !position.is_absolutely_positioned() { return None; } diff --git a/components/layout/flow/root.rs b/components/layout/flow/root.rs index fb9884a4f01..fe98dbc3156 100644 --- a/components/layout/flow/root.rs +++ b/components/layout/flow/root.rs @@ -29,7 +29,7 @@ use crate::flow::inline::InlineItem; use crate::flow::{BlockContainer, BlockFormattingContext, BlockLevelBox}; use crate::formatting_contexts::IndependentFormattingContext; use crate::fragment_tree::FragmentTree; -use crate::geom::{LogicalVec2, PhysicalRect, PhysicalSize}; +use crate::geom::{LogicalVec2, PhysicalSize}; use crate::positioned::{AbsolutelyPositionedBox, PositioningContext}; use crate::replaced::ReplacedContents; use crate::style_ext::{Display, DisplayGeneratingBox, DisplayInside}; @@ -392,31 +392,9 @@ impl BoxTree { &mut root_fragments, ); - let scrollable_overflow = root_fragments - .iter() - .fold(PhysicalRect::zero(), |acc, child| { - let child_overflow = child.scrollable_overflow_for_parent(); - - // 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) - }); - FragmentTree::new( layout_context, root_fragments, - scrollable_overflow, physical_containing_block, self.viewport_scroll_sensitivity, ) diff --git a/components/layout/fragment_tree/box_fragment.rs b/components/layout/fragment_tree/box_fragment.rs index b7c3a2a3524..eb63038b7d7 100644 --- a/components/layout/fragment_tree/box_fragment.rs +++ b/components/layout/fragment_tree/box_fragment.rs @@ -89,7 +89,7 @@ pub(crate) struct BoxFragment { block_margins_collapsed_with_children: Option>, /// The scrollable overflow of this box fragment. - pub scrollable_overflow_from_children: PhysicalRect, + scrollable_overflow: Option>, /// The resolved box insets if this box is `position: sticky`. These are calculated /// during `StackingContextTree` construction because they rely on the size of the @@ -114,11 +114,6 @@ impl BoxFragment { margin: PhysicalSides, clearance: Option, ) -> BoxFragment { - let scrollable_overflow_from_children = - children.iter().fold(PhysicalRect::zero(), |acc, child| { - acc.union(&child.scrollable_overflow_for_parent()) - }); - BoxFragment { base: base_fragment_info.into(), style, @@ -131,7 +126,7 @@ impl BoxFragment { clearance, baselines: Baselines::default(), block_margins_collapsed_with_children: None, - scrollable_overflow_from_children, + scrollable_overflow: None, resolved_sticky_insets: AtomicRefCell::default(), background_mode: BackgroundMode::Normal, specific_layout_info: None, @@ -203,13 +198,23 @@ impl BoxFragment { /// Get the scrollable overflow for this [`BoxFragment`] relative to its /// containing block. pub fn scrollable_overflow(&self) -> PhysicalRect { + self.scrollable_overflow + .expect("Should only call `scrollable_overflow()` after calculating overflow") + } + + 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(); - physical_padding_rect.union( - &self - .scrollable_overflow_from_children - .translate(content_origin), - ) + self.scrollable_overflow = Some( + physical_padding_rect + .union(&scrollable_overflow_from_children.translate(content_origin)), + ); } pub(crate) fn set_containing_block(&mut self, containing_block: &PhysicalRect) { @@ -275,7 +280,12 @@ impl BoxFragment { tree.end_level(); } - pub fn scrollable_overflow_for_parent(&self) -> PhysicalRect { + 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 @@ -328,7 +338,7 @@ impl BoxFragment { /// /// 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 fn clip_unreachable_scrollable_overflow_region( + pub(crate) fn clip_unreachable_scrollable_overflow_region( &self, scrollable_overflow: PhysicalRect, clipping_rect: PhysicalRect, @@ -362,7 +372,7 @@ impl BoxFragment { /// /// 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 fn reachable_scrollable_overflow_region(&self) -> PhysicalRect { + pub(crate) fn reachable_scrollable_overflow_region(&self) -> PhysicalRect { self.clip_unreachable_scrollable_overflow_region( self.scrollable_overflow(), self.padding_rect(), @@ -421,9 +431,7 @@ impl BoxFragment { return convert_to_au_or_auto(PhysicalSides::new(top, right, bottom, left)); } - debug_assert!( - position == ComputedPosition::Fixed || position == ComputedPosition::Absolute - ); + debug_assert!(position.is_absolutely_positioned()); let margin_rect = self.margin_rect(); let (top, bottom) = match (&insets.top, &insets.bottom) { diff --git a/components/layout/fragment_tree/fragment.rs b/components/layout/fragment_tree/fragment.rs index c81fd59e36b..10338c78743 100644 --- a/components/layout/fragment_tree/fragment.rs +++ b/components/layout/fragment_tree/fragment.rs @@ -183,19 +183,36 @@ impl Fragment { } } - pub fn scrollable_overflow_for_parent(&self) -> PhysicalRect { + pub(crate) fn scrollable_overflow_for_parent(&self) -> PhysicalRect { match self { Fragment::Box(fragment) | Fragment::Float(fragment) => { - fragment.borrow().scrollable_overflow_for_parent() + return fragment.borrow().scrollable_overflow_for_parent(); }, Fragment::AbsoluteOrFixedPositioned(_) => PhysicalRect::zero(), - Fragment::Positioning(fragment) => fragment.borrow().scrollable_overflow, + Fragment::Positioning(fragment) => fragment.borrow().scrollable_overflow_for_parent(), Fragment::Text(fragment) => fragment.borrow().rect, Fragment::Image(fragment) => fragment.borrow().rect, Fragment::IFrame(fragment) => fragment.borrow().rect, } } + pub(crate) fn calculate_scrollable_overflow_for_parent(&self) -> PhysicalRect { + self.calculate_scrollable_overflow(); + self.scrollable_overflow_for_parent() + } + + pub(crate) fn calculate_scrollable_overflow(&self) { + match self { + Fragment::Box(fragment) | Fragment::Float(fragment) => { + fragment.borrow_mut().calculate_scrollable_overflow() + }, + Fragment::Positioning(fragment) => { + fragment.borrow_mut().calculate_scrollable_overflow() + }, + _ => {}, + } + } + pub(crate) fn cumulative_border_box_rect(&self) -> Option> { 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 ba03a72ac21..b59ace43aa6 100644 --- a/components/layout/fragment_tree/fragment_tree.rs +++ b/components/layout/fragment_tree/fragment_tree.rs @@ -2,14 +2,14 @@ * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ +use std::cell::Cell; + use app_units::Au; use base::print_tree::PrintTree; use compositing_traits::display_list::AxesScrollSensitivity; -use euclid::default::Size2D; use fxhash::FxHashSet; use malloc_size_of_derive::MallocSizeOf; use style::animation::AnimationSetKey; -use webrender_api::units; use super::{BoxFragment, ContainingBlockManager, Fragment}; use crate::ArcRefCell; @@ -30,7 +30,7 @@ pub struct FragmentTree { /// The scrollable overflow rectangle for the entire tree /// - pub(crate) scrollable_overflow: PhysicalRect, + scrollable_overflow: Cell>>, /// The containing block used in the layout of this fragment tree. pub(crate) initial_containing_block: PhysicalRect, @@ -43,13 +43,12 @@ impl FragmentTree { pub(crate) fn new( layout_context: &LayoutContext, root_fragments: Vec, - scrollable_overflow: PhysicalRect, initial_containing_block: PhysicalRect, viewport_scroll_sensitivity: AxesScrollSensitivity, ) -> Self { let fragment_tree = Self { root_fragments, - scrollable_overflow, + scrollable_overflow: Cell::default(), initial_containing_block, viewport_scroll_sensitivity, }; @@ -97,11 +96,35 @@ impl FragmentTree { } } - pub fn scrollable_overflow(&self) -> units::LayoutSize { - units::LayoutSize::from_untyped(Size2D::new( - self.scrollable_overflow.size.width.to_f32_px(), - self.scrollable_overflow.size.height.to_f32_px(), - )) + pub(crate) fn scrollable_overflow(&self) -> PhysicalRect { + self.scrollable_overflow + .get() + .expect("Should only call `scrollable_overflow()` after calculating overflow") + } + + 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(); + + // 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) + }, + ))); } pub(crate) fn find( diff --git a/components/layout/fragment_tree/positioning_fragment.rs b/components/layout/fragment_tree/positioning_fragment.rs index e45a6137bff..5547a9d86a1 100644 --- a/components/layout/fragment_tree/positioning_fragment.rs +++ b/components/layout/fragment_tree/positioning_fragment.rs @@ -22,7 +22,7 @@ pub(crate) struct PositioningFragment { pub children: Vec, /// The scrollable overflow of this anonymous fragment's children. - pub scrollable_overflow: PhysicalRect, + scrollable_overflow: Option>, /// The style of the fragment. pub style: ServoArc, @@ -55,20 +55,12 @@ impl PositioningFragment { rect: PhysicalRect, children: Vec, ) -> ArcRefCell { - let content_origin = rect.origin; - let scrollable_overflow = children.iter().fold(PhysicalRect::zero(), |acc, child| { - acc.union( - &child - .scrollable_overflow_for_parent() - .translate(content_origin.to_vector()), - ) - }); ArcRefCell::new(PositioningFragment { base, style, rect, children, - scrollable_overflow, + scrollable_overflow: None, cumulative_containing_block_rect: PhysicalRect::zero(), }) } @@ -81,6 +73,25 @@ impl PositioningFragment { rect.translate(self.cumulative_containing_block_rect.origin.to_vector()) } + pub(crate) fn calculate_scrollable_overflow(&mut self) { + self.scrollable_overflow = Some(self.children.iter().fold( + PhysicalRect::zero(), + |acc, child| { + acc.union( + &child + .calculate_scrollable_overflow_for_parent() + .translate(self.rect.origin.to_vector()), + ) + }, + )); + } + + pub(crate) fn scrollable_overflow_for_parent(&self) -> PhysicalRect { + self.scrollable_overflow.expect( + "Should only call `scrollable_overflow_for_parent()` after calculating overflow", + ) + } + pub fn print(&self, tree: &mut PrintTree) { tree.new_level(format!( "PositioningFragment\ diff --git a/components/layout/layout_impl.rs b/components/layout/layout_impl.rs index 54edc215389..0fbc8395c35 100644 --- a/components/layout/layout_impl.rs +++ b/components/layout/layout_impl.rs @@ -8,6 +8,7 @@ use std::cell::{Cell, RefCell}; use std::collections::HashMap; use std::fmt::Debug; use std::process; +use std::rc::Rc; use std::sync::{Arc, LazyLock}; use app_units::Au; @@ -142,7 +143,7 @@ pub struct LayoutThread { box_tree: RefCell>>, /// The fragment tree. - fragment_tree: RefCell>>, + fragment_tree: RefCell>>, /// The [`StackingContextTree`] cached from previous layouts. stacking_context_tree: RefCell>, @@ -656,7 +657,7 @@ impl LayoutThread { &mut layout_context, viewport_changed, ); - + self.calculate_overflow(damage); self.build_stacking_context_tree(&reflow_request, damage); self.build_display_list(&reflow_request, &mut layout_context); @@ -773,8 +774,11 @@ impl LayoutThread { driver::traverse_dom(&recalc_style_traversal, token, rayon_pool).as_node(); let root_node = root_element.as_node(); - let damage = compute_damage_and_repair_style(layout_context.shared_context(), root_node); - if !viewport_changed && !damage.contains(RestyleDamage::REBUILD_BOX) { + let mut damage = + compute_damage_and_repair_style(layout_context.shared_context(), root_node); + if viewport_changed { + damage = RestyleDamage::REBUILD_BOX; + } else if !damage.contains(RestyleDamage::REBUILD_BOX) { layout_context.style_context.stylist.rule_tree().maybe_gc(); return damage; } @@ -802,15 +806,12 @@ impl LayoutThread { .unwrap() .layout(recalc_style_traversal.context(), viewport_size) }; - let fragment_tree = Arc::new(if let Some(pool) = rayon_pool { + let fragment_tree = Rc::new(if let Some(pool) = rayon_pool { pool.install(run_layout) } else { run_layout() }); - if self.debug.dump_flow_tree { - fragment_tree.print(); - } *self.fragment_tree.borrow_mut() = Some(fragment_tree); // The FragmentTree has been updated, so any existing StackingContext tree that layout @@ -837,6 +838,19 @@ impl LayoutThread { damage } + fn calculate_overflow(&self, damage: RestyleDamage) { + if !damage.contains(RestyleDamage::RECALCULATE_OVERFLOW) { + return; + } + + if let Some(fragment_tree) = &*self.fragment_tree.borrow() { + fragment_tree.calculate_scrollable_overflow(); + if self.debug.dump_flow_tree { + fragment_tree.print(); + } + } + } + fn build_stacking_context_tree(&self, reflow_request: &ReflowRequest, damage: RestyleDamage) { if !reflow_request.reflow_goal.needs_display_list() && !reflow_request.reflow_goal.needs_display() @@ -858,13 +872,19 @@ impl LayoutThread { viewport_size.height.to_f32_px(), ); + let scrollable_overflow = fragment_tree.scrollable_overflow(); + let scrollable_overflow = LayoutSize::from_untyped(Size2D::new( + scrollable_overflow.size.width.to_f32_px(), + scrollable_overflow.size.height.to_f32_px(), + )); + // Build the StackingContextTree. This turns the `FragmentTree` into a // tree of fragments in CSS painting order and also creates all // applicable spatial and clip nodes. *self.stacking_context_tree.borrow_mut() = Some(StackingContextTree::new( fragment_tree, viewport_size, - fragment_tree.scrollable_overflow(), + scrollable_overflow, self.id.into(), fragment_tree.viewport_scroll_sensitivity, self.first_reflow.get(), diff --git a/components/layout/positioned.rs b/components/layout/positioned.rs index 6280864d533..186fc78e3bf 100644 --- a/components/layout/positioned.rs +++ b/components/layout/positioned.rs @@ -305,10 +305,7 @@ impl PositioningContext { } pub(crate) fn push(&mut self, hoisted_box: HoistedAbsolutelyPositionedBox) { - debug_assert!(matches!( - hoisted_box.position(), - Position::Absolute | Position::Fixed - )); + debug_assert!(hoisted_box.position().is_absolutely_positioned()); self.absolutes.push(hoisted_box); } @@ -380,7 +377,7 @@ impl HoistedAbsolutelyPositionedBox { .context .style() .clone_position(); - assert!(position == Position::Fixed || position == Position::Absolute); + assert!(position.is_absolutely_positioned()); position } diff --git a/components/layout/query.rs b/components/layout/query.rs index ca9db9ceaf1..7f304ff2da1 100644 --- a/components/layout/query.rs +++ b/components/layout/query.rs @@ -3,7 +3,7 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ //! Utilities for querying the layout, as needed by layout. -use std::sync::Arc; +use std::rc::Rc; use app_units::Au; use euclid::default::{Point2D, Rect}; @@ -80,7 +80,7 @@ pub fn process_client_rect_request(node: ServoLayoutNode<'_>) -> Rect { /// pub fn process_node_scroll_area_request( requested_node: Option>, - fragment_tree: Option>, + fragment_tree: Option>, ) -> Rect { let Some(tree) = fragment_tree else { return Rect::zero(); 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 deleted file mode 100644 index 7addfba3787..00000000000 --- a/tests/wpt/meta/css/css-masking/clip-path/clip-path-fixed-scroll.html.ini +++ /dev/null @@ -1,2 +0,0 @@ -[clip-path-fixed-scroll.html] - expected: FAIL