layout: Make Fragment hold ArcRefCell inside (#34923)

Push the interior mutability into enum variants of `Fragment`, so that
they can be cloned. This saves memory in the `Fragment` tree as the
`Fragment` enum is now a relatively wee 16 bytes and the interior parts
can be a variety of sizes. Before, every `Fragment` was the size of the
biggest kind (`BoxFragment` - 248 bytes).

This a step on the way toward incremental layout.

Signed-off-by: Martin Robinson <mrobinson@igalia.com>
Co-authored-by: Oriol Brufau <obrufau@igalia.com>
This commit is contained in:
Martin Robinson 2025-01-13 10:59:59 +01:00 committed by GitHub
parent c936dd6c4e
commit de780dcde4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
18 changed files with 257 additions and 233 deletions

View file

@ -13,7 +13,7 @@ use crate::layout_debug::DebugId;
/// This data structure stores fields that are common to all non-base
/// Fragment types and should generally be the first member of all
/// concrete fragments.
#[derive(Debug, Serialize)]
#[derive(Clone, Debug, Serialize)]
pub(crate) struct BaseFragment {
/// A tag which identifies the DOM node and pseudo element of this
/// Fragment's content. If this fragment isn't related to any DOM

View file

@ -3,6 +3,7 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use app_units::Au;
use atomic_refcell::AtomicRefCell;
use base::print_tree::PrintTree;
use serde::Serialize;
use servo_arc::Arc as ServoArc;
@ -13,7 +14,6 @@ use style::properties::ComputedValues;
use style::Zero;
use super::{BaseFragment, BaseFragmentInfo, CollapsedBlockMargins, Fragment};
use crate::cell::ArcRefCell;
use crate::formatting_contexts::Baselines;
use crate::fragment_tree::FragmentFlags;
use crate::geom::{
@ -53,7 +53,7 @@ pub(crate) struct BoxFragment {
#[serde(skip_serializing)]
pub style: ServoArc<ComputedValues>,
pub children: Vec<ArcRefCell<Fragment>>,
pub children: Vec<Fragment>,
/// The content rect of this fragment in the parent fragment's content rectangle. This
/// does not include padding, border, or margin -- it only includes content.
@ -84,7 +84,8 @@ pub(crate) struct BoxFragment {
/// The resolved box insets if this box is `position: sticky`. These are calculated
/// during stacking context tree construction because they rely on the size of the
/// scroll container.
pub(crate) resolved_sticky_insets: Option<PhysicalSides<AuOrAuto>>,
#[serde(skip_serializing)]
pub(crate) resolved_sticky_insets: AtomicRefCell<Option<PhysicalSides<AuOrAuto>>>,
#[serde(skip_serializing)]
pub background_mode: BackgroundMode,
@ -115,7 +116,7 @@ impl BoxFragment {
BoxFragment {
base: base_fragment_info.into(),
style,
children: children.into_iter().map(ArcRefCell::new).collect(),
children,
content_rect,
padding,
border,
@ -124,7 +125,7 @@ impl BoxFragment {
baselines: Baselines::default(),
block_margins_collapsed_with_children,
scrollable_overflow_from_children,
resolved_sticky_insets: None,
resolved_sticky_insets: AtomicRefCell::default(),
background_mode: BackgroundMode::Normal,
detailed_layout_info: None,
}
@ -234,7 +235,7 @@ impl BoxFragment {
));
for child in &self.children {
child.borrow().print(tree);
child.print(tree);
}
tree.end_level();
}
@ -278,7 +279,7 @@ impl BoxFragment {
"Should not call this method on statically positioned box."
);
if let Some(resolved_sticky_insets) = self.resolved_sticky_insets {
if let Some(resolved_sticky_insets) = *self.resolved_sticky_insets.borrow() {
return resolved_sticky_insets;
}

View file

@ -52,7 +52,7 @@ pub(crate) struct ContainingBlockManager<'a, T> {
impl<'a, T> ContainingBlockManager<'a, T> {
pub(crate) fn get_containing_block_for_fragment(&self, fragment: &Fragment) -> &T {
if let Fragment::Box(box_fragment) = fragment {
match box_fragment.style.clone_position() {
match box_fragment.borrow().style.clone_position() {
ComputedPosition::Fixed => self.for_absolute_and_fixed_descendants,
ComputedPosition::Absolute => self
.for_absolute_descendants

View file

@ -23,16 +23,16 @@ use crate::cell::ArcRefCell;
use crate::geom::{LogicalSides, PhysicalRect};
use crate::style_ext::ComputedValuesExt;
#[derive(Serialize)]
#[derive(Clone, Serialize)]
pub(crate) enum Fragment {
Box(BoxFragment),
Box(ArcRefCell<BoxFragment>),
/// Floating content. A floated fragment is very similar to a normal
/// [BoxFragment] but it isn't positioned using normal in block flow
/// positioning rules (margin collapse, etc). Instead, they are laid
/// out by the [crate::flow::float::SequentialLayoutState] of their
/// float containing block formatting context.
Float(BoxFragment),
Positioning(PositioningFragment),
Float(ArcRefCell<BoxFragment>),
Positioning(ArcRefCell<PositioningFragment>),
/// Absolute and fixed position fragments are hoisted up so that they
/// are children of the BoxFragment that establishes their containing
/// blocks, so that they can be laid out properly. When this happens
@ -41,9 +41,9 @@ pub(crate) enum Fragment {
/// regard to their original tree order during stacking context tree /
/// display list construction.
AbsoluteOrFixedPositioned(ArcRefCell<HoistedSharedFragment>),
Text(TextFragment),
Image(ImageFragment),
IFrame(IFrameFragment),
Text(ArcRefCell<TextFragment>),
Image(ArcRefCell<ImageFragment>),
IFrame(ArcRefCell<IFrameFragment>),
}
#[derive(Serialize)]
@ -99,26 +99,26 @@ pub(crate) struct IFrameFragment {
}
impl Fragment {
pub fn base(&self) -> Option<&BaseFragment> {
pub fn base(&self) -> Option<BaseFragment> {
Some(match self {
Fragment::Box(fragment) => &fragment.base,
Fragment::Text(fragment) => &fragment.base,
Fragment::Box(fragment) => fragment.borrow().base.clone(),
Fragment::Text(fragment) => fragment.borrow().base.clone(),
Fragment::AbsoluteOrFixedPositioned(_) => return None,
Fragment::Positioning(fragment) => &fragment.base,
Fragment::Image(fragment) => &fragment.base,
Fragment::IFrame(fragment) => &fragment.base,
Fragment::Float(fragment) => &fragment.base,
Fragment::Positioning(fragment) => fragment.borrow().base.clone(),
Fragment::Image(fragment) => fragment.borrow().base.clone(),
Fragment::IFrame(fragment) => fragment.borrow().base.clone(),
Fragment::Float(fragment) => fragment.borrow().base.clone(),
})
}
pub(crate) fn content_rect_mut(&mut self) -> Option<&mut PhysicalRect<Au>> {
pub(crate) fn mutate_content_rect(&mut self, callback: impl FnOnce(&mut PhysicalRect<Au>)) {
match self {
Fragment::Box(box_fragment) | Fragment::Float(box_fragment) => {
Some(&mut box_fragment.content_rect)
callback(&mut box_fragment.borrow_mut().content_rect)
},
Fragment::Positioning(_) | Fragment::AbsoluteOrFixedPositioned(_) => None,
Fragment::Text(text_fragment) => Some(&mut text_fragment.rect),
Fragment::Image(image_fragment) => Some(&mut image_fragment.rect),
Fragment::IFrame(iframe_fragment) => Some(&mut iframe_fragment.rect),
Fragment::Positioning(_) | Fragment::AbsoluteOrFixedPositioned(_) => {},
Fragment::Text(text_fragment) => callback(&mut text_fragment.borrow_mut().rect),
Fragment::Image(image_fragment) => callback(&mut image_fragment.borrow_mut().rect),
Fragment::IFrame(iframe_fragment) => callback(&mut iframe_fragment.borrow_mut().rect),
}
}
@ -128,25 +128,26 @@ impl Fragment {
pub fn print(&self, tree: &mut PrintTree) {
match self {
Fragment::Box(fragment) => fragment.print(tree),
Fragment::Box(fragment) => fragment.borrow().print(tree),
Fragment::Float(fragment) => {
tree.new_level("Float".to_string());
fragment.print(tree);
fragment.borrow().print(tree);
tree.end_level();
},
Fragment::AbsoluteOrFixedPositioned(_) => {
tree.add_item("AbsoluteOrFixedPositioned".to_string());
},
Fragment::Positioning(fragment) => fragment.print(tree),
Fragment::Text(fragment) => fragment.print(tree),
Fragment::Image(fragment) => fragment.print(tree),
Fragment::IFrame(fragment) => fragment.print(tree),
Fragment::Positioning(fragment) => fragment.borrow().print(tree),
Fragment::Text(fragment) => fragment.borrow().print(tree),
Fragment::Image(fragment) => fragment.borrow().print(tree),
Fragment::IFrame(fragment) => fragment.borrow().print(tree),
}
}
pub fn scrolling_area(&self, containing_block: &PhysicalRect<Au>) -> PhysicalRect<Au> {
match self {
Fragment::Box(fragment) | Fragment::Float(fragment) => fragment
.borrow()
.scrollable_overflow()
.translate(containing_block.origin.to_vector()),
_ => self.scrollable_overflow(),
@ -156,13 +157,13 @@ impl Fragment {
pub fn scrollable_overflow(&self) -> PhysicalRect<Au> {
match self {
Fragment::Box(fragment) | Fragment::Float(fragment) => {
fragment.scrollable_overflow_for_parent()
fragment.borrow().scrollable_overflow_for_parent()
},
Fragment::AbsoluteOrFixedPositioned(_) => PhysicalRect::zero(),
Fragment::Positioning(fragment) => fragment.scrollable_overflow,
Fragment::Text(fragment) => fragment.rect,
Fragment::Image(fragment) => fragment.rect,
Fragment::IFrame(fragment) => fragment.rect,
Fragment::Positioning(fragment) => fragment.borrow().scrollable_overflow,
Fragment::Text(fragment) => fragment.borrow().rect,
Fragment::Image(fragment) => fragment.borrow().rect,
Fragment::IFrame(fragment) => fragment.borrow().rect,
}
}
@ -179,6 +180,7 @@ impl Fragment {
match self {
Fragment::Box(fragment) | Fragment::Float(fragment) => {
let fragment = fragment.borrow();
let content_rect = fragment
.content_rect
.translate(containing_block.origin.to_vector());
@ -202,15 +204,16 @@ impl Fragment {
fragment
.children
.iter()
.find_map(|child| child.borrow().find(&new_manager, level + 1, process_func))
.find_map(|child| child.find(&new_manager, level + 1, process_func))
},
Fragment::Positioning(fragment) => {
let fragment = fragment.borrow();
let content_rect = fragment.rect.translate(containing_block.origin.to_vector());
let new_manager = manager.new_for_non_absolute_descendants(&content_rect);
fragment
.children
.iter()
.find_map(|child| child.borrow().find(&new_manager, level + 1, process_func))
.find_map(|child| child.find(&new_manager, level + 1, process_func))
},
_ => None,
}

View file

@ -13,7 +13,6 @@ use webrender_api::units;
use webrender_traits::display_list::ScrollSensitivity;
use super::{ContainingBlockManager, Fragment, Tag};
use crate::cell::ArcRefCell;
use crate::display_list::StackingContext;
use crate::flow::CanvasBackground;
use crate::geom::PhysicalRect;
@ -28,7 +27,7 @@ pub struct FragmentTree {
/// * The first fragment is generated by the root element.
/// * There may be additional fragments generated by positioned boxes
/// that have the initial containing block.
pub(crate) root_fragments: Vec<ArcRefCell<Fragment>>,
pub(crate) root_fragments: Vec<Fragment>,
/// The scrollable overflow rectangle for the entire tree
/// <https://drafts.csswg.org/css-overflow/#scrollable>
@ -63,7 +62,7 @@ impl FragmentTree {
pub fn print(&self) {
let mut print_tree = PrintTree::new("Fragment Tree".to_string());
for fragment in &self.root_fragments {
fragment.borrow().print(&mut print_tree);
fragment.print(&mut print_tree);
}
}
@ -85,7 +84,7 @@ impl FragmentTree {
};
self.root_fragments
.iter()
.find_map(|child| child.borrow().find(&info, 0, &mut process_func))
.find_map(|child| child.find(&info, 0, &mut process_func))
}
pub fn remove_nodes_in_fragment_tree_from_set(&self, set: &mut FxHashSet<AnimationSetKey>) {
@ -110,9 +109,11 @@ impl FragmentTree {
}
let fragment_relative_rect = match fragment {
Fragment::Box(fragment) | Fragment::Float(fragment) => fragment.border_rect(),
Fragment::Positioning(fragment) => fragment.rect,
Fragment::Text(fragment) => fragment.rect,
Fragment::Box(fragment) | Fragment::Float(fragment) => {
fragment.borrow().border_rect()
},
Fragment::Positioning(fragment) => fragment.borrow().rect,
Fragment::Text(fragment) => fragment.borrow().rect,
Fragment::AbsoluteOrFixedPositioned(_) |
Fragment::Image(_) |
Fragment::IFrame(_) => return None,
@ -140,6 +141,7 @@ impl FragmentTree {
// CSS layout box is inline, return zero." For this check we
// also explicitly ignore the list item portion of the display
// style.
let fragment = fragment.borrow();
if fragment.is_inline_box() {
return Some(Rect::zero());
}
@ -151,7 +153,7 @@ impl FragmentTree {
Size2D::new(padding_rect.size.width, padding_rect.size.height),
)
},
Fragment::Positioning(fragment) => fragment.rect.cast_unit(),
Fragment::Positioning(fragment) => fragment.borrow().rect.cast_unit(),
_ => return None,
};
@ -168,7 +170,6 @@ impl FragmentTree {
let mut scroll_area = self.initial_containing_block;
for fragment in self.root_fragments.iter() {
scroll_area = fragment
.borrow()
.scrolling_area(&self.initial_containing_block)
.union(&scroll_area);
}

View file

@ -8,7 +8,6 @@ use style::logical_geometry::WritingMode;
use style::values::specified::align::AlignFlags;
use super::Fragment;
use crate::cell::ArcRefCell;
use crate::geom::{LogicalVec2, PhysicalRect, PhysicalVec};
/// A reference to a Fragment which is shared between `HoistedAbsolutelyPositionedBox`
@ -16,7 +15,7 @@ use crate::geom::{LogicalVec2, PhysicalRect, PhysicalVec};
/// This will be used later in order to paint this hoisted box in tree order.
#[derive(Serialize)]
pub(crate) struct HoistedSharedFragment {
pub fragment: Option<ArcRefCell<Fragment>>,
pub fragment: Option<Fragment>,
/// The "static-position rect" of this absolutely positioned box. This is defined by the
/// layout mode from which the box originates.
///

View file

@ -19,7 +19,7 @@ use crate::geom::PhysicalRect;
pub(crate) struct PositioningFragment {
pub base: BaseFragment,
pub rect: PhysicalRect<Au>,
pub children: Vec<ArcRefCell<Fragment>>,
pub children: Vec<Fragment>,
/// The scrollable overflow of this anonymous fragment's children.
pub scrollable_overflow: PhysicalRect<Au>,
@ -29,7 +29,7 @@ pub(crate) struct PositioningFragment {
}
impl PositioningFragment {
pub fn new_anonymous(rect: PhysicalRect<Au>, children: Vec<Fragment>) -> Self {
pub fn new_anonymous(rect: PhysicalRect<Au>, children: Vec<Fragment>) -> ArcRefCell<Self> {
Self::new_with_base_fragment(BaseFragment::anonymous(), None, rect, children)
}
@ -37,7 +37,7 @@ impl PositioningFragment {
base_fragment_info: BaseFragmentInfo,
rect: PhysicalRect<Au>,
style: ServoArc<ComputedValues>,
) -> Self {
) -> ArcRefCell<Self> {
Self::new_with_base_fragment(base_fragment_info.into(), Some(style), rect, Vec::new())
}
@ -46,7 +46,7 @@ impl PositioningFragment {
style: Option<ServoArc<ComputedValues>>,
rect: PhysicalRect<Au>,
children: Vec<Fragment>,
) -> Self {
) -> ArcRefCell<Self> {
let content_origin = rect.origin;
let scrollable_overflow = children.iter().fold(PhysicalRect::zero(), |acc, child| {
acc.union(
@ -55,13 +55,13 @@ impl PositioningFragment {
.translate(content_origin.to_vector()),
)
});
PositioningFragment {
ArcRefCell::new(PositioningFragment {
base,
style,
rect,
children: children.into_iter().map(ArcRefCell::new).collect(),
children,
scrollable_overflow,
}
})
}
pub fn print(&self, tree: &mut PrintTree) {
@ -74,7 +74,7 @@ impl PositioningFragment {
));
for child in &self.children {
child.borrow().print(tree);
child.print(tree);
}
tree.end_level();
}