layout/
layout_box_base.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5use std::fmt::{Debug, Formatter};
6use std::sync::atomic::{AtomicBool, Ordering};
7
8use app_units::Au;
9use atomic_refcell::AtomicRefCell;
10use euclid::Point2D;
11use layout_api::LayoutDamage;
12use malloc_size_of_derive::MallocSizeOf;
13use servo_arc::Arc;
14use style::computed_values::position::T as Position;
15use style::logical_geometry::WritingMode;
16use style::properties::ComputedValues;
17use style::values::specified::align::AlignFlags;
18use style_traits::CSSPixel;
19
20use crate::context::LayoutContext;
21use crate::dom::{LayoutBox, WeakLayoutBox};
22use crate::flow::CollapsibleWithParentStartMargin;
23use crate::formatting_contexts::Baselines;
24use crate::fragment_tree::{
25    BaseFragmentInfo, BoxFragment, CollapsedBlockMargins, Fragment, FragmentStatus,
26    SpecificLayoutInfo,
27};
28use crate::geom::LogicalSides1D;
29use crate::positioned::{PositioningContext, relative_adjustement};
30use crate::sizing::{ComputeInlineContentSizes, InlineContentSizesResult, SizeConstraint};
31use crate::{ArcRefCell, ConstraintSpace, ContainingBlock, ContainingBlockSize};
32
33/// A box tree node that handles containing information about style and the original DOM
34/// node or pseudo-element that it is based on. This also handles caching of layout values
35/// such as the inline content sizes to avoid recalculating these values during layout
36/// passes.
37///
38/// In the future, this will hold layout results to support incremental layout.
39#[derive(MallocSizeOf)]
40pub(crate) struct LayoutBoxBase {
41    pub base_fragment_info: BaseFragmentInfo,
42    pub style: Arc<ComputedValues>,
43    pub cached_inline_content_size:
44        AtomicRefCell<Option<Box<(SizeConstraint, InlineContentSizesResult)>>>,
45    pub outer_inline_content_sizes_depend_on_content: AtomicBool,
46    pub cached_layout_result: AtomicRefCell<Option<LayoutResultAndInputs>>,
47    pub fragments: AtomicRefCell<Vec<Fragment>>,
48    pub parent_box: Option<WeakLayoutBox>,
49}
50
51impl LayoutBoxBase {
52    pub(crate) fn new(base_fragment_info: BaseFragmentInfo, style: Arc<ComputedValues>) -> Self {
53        Self {
54            base_fragment_info,
55            style,
56            cached_inline_content_size: AtomicRefCell::default(),
57            outer_inline_content_sizes_depend_on_content: AtomicBool::new(true),
58            cached_layout_result: AtomicRefCell::default(),
59            fragments: AtomicRefCell::default(),
60            parent_box: None,
61        }
62    }
63
64    /// Get the inline content sizes of a box tree node that extends this [`LayoutBoxBase`], fetch
65    /// the result from a cache when possible.
66    pub(crate) fn inline_content_sizes(
67        &self,
68        layout_context: &LayoutContext,
69        constraint_space: &ConstraintSpace,
70        layout_box: &impl ComputeInlineContentSizes,
71    ) -> InlineContentSizesResult {
72        let mut cache = self.cached_inline_content_size.borrow_mut();
73        if let Some(cached_inline_content_size) = cache.as_ref() {
74            let (previous_cb_block_size, result) = **cached_inline_content_size;
75            if !result.depends_on_block_constraints ||
76                previous_cb_block_size == constraint_space.block_size
77            {
78                return result;
79            }
80            // TODO: Should we keep multiple caches for various block sizes?
81        }
82
83        let result =
84            layout_box.compute_inline_content_sizes_with_fixup(layout_context, constraint_space);
85        *cache = Some(Box::new((constraint_space.block_size, result)));
86        result
87    }
88
89    pub(crate) fn fragments(&self) -> Vec<Fragment> {
90        self.fragments.borrow().clone()
91    }
92
93    pub(crate) fn add_fragment(&self, fragment: Fragment) {
94        self.fragments.borrow_mut().push(fragment);
95    }
96
97    pub(crate) fn set_fragment(&self, fragment: Fragment) {
98        *self.fragments.borrow_mut() = vec![fragment];
99    }
100
101    pub(crate) fn clear_fragments(&self) {
102        self.fragments.borrow_mut().clear();
103    }
104
105    pub(crate) fn clear_fragments_and_fragment_cache(&self) {
106        self.fragments.borrow_mut().clear();
107        *self.cached_layout_result.borrow_mut() = None;
108    }
109
110    pub(crate) fn repair_style(&mut self, new_style: &Arc<ComputedValues>) {
111        self.style = new_style.clone();
112        for fragment in self.fragments.borrow_mut().iter_mut() {
113            if let Some(mut base) = fragment.base_mut() {
114                base.repair_style(new_style);
115            }
116        }
117    }
118
119    #[expect(unused)]
120    pub(crate) fn parent_box(&self) -> Option<LayoutBox> {
121        self.parent_box.as_ref().and_then(WeakLayoutBox::upgrade)
122    }
123
124    pub(crate) fn add_damage(
125        &self,
126        element_damage: LayoutDamage,
127        damage_from_children: LayoutDamage,
128    ) -> LayoutDamage {
129        self.clear_fragments_and_fragment_cache();
130
131        if !element_damage.is_empty() ||
132            damage_from_children.contains(LayoutDamage::RECOMPUTE_INLINE_CONTENT_SIZES)
133        {
134            *self.cached_inline_content_size.borrow_mut() = None;
135        }
136
137        let mut damage_for_parent = element_damage | damage_from_children;
138
139        // When a block container has a mix of inline-level and block-level contents, the
140        // inline-level ones are wrapped inside an anonymous block associated with the
141        // block container. The anonymous block has an `auto` size, so its intrinsic
142        // contribution depends on content, but it can't affect the intrinsic size of
143        // ancestors if the block container is sized extrinsically.
144        //
145        // If the intrinsic contributions of this node depend on content, we will need to
146        // clear the cached intrinsic sizes of the parent. But if the contributions are
147        // purely extrinsic, then the intrinsic sizes of the ancestors won't be affected,
148        // and we can keep the cache.
149        damage_for_parent.set(
150            LayoutDamage::RECOMPUTE_INLINE_CONTENT_SIZES,
151            !element_damage.is_empty() ||
152                (!self.base_fragment_info.is_anonymous() &&
153                    self.outer_inline_content_sizes_depend_on_content
154                        .load(Ordering::Relaxed)),
155        );
156
157        damage_for_parent
158    }
159
160    pub(crate) fn cached_same_formatting_context_block_if_applicable(
161        &self,
162        containing_block: &ContainingBlock,
163        collapsible_with_parent_start_margin: Option<CollapsibleWithParentStartMargin>,
164        ignore_block_margins_for_stretch: LogicalSides1D<bool>,
165        has_inline_parent: bool,
166    ) -> Option<ArcRefCell<BoxFragment>> {
167        let mut cached_layout_result = self.cached_layout_result.borrow_mut();
168        let Some(LayoutResultAndInputs::SameFormattingContextBlock(result)) =
169            &mut *cached_layout_result
170        else {
171            return None;
172        };
173
174        if result.containing_block_size != containing_block.size ||
175            result.containing_block_writing_mode != containing_block.style.writing_mode ||
176            result.containing_block_justify_items !=
177                containing_block.style.clone_justify_items().computed.0.0 ||
178            result.collapsible_with_parent_start_margin != collapsible_with_parent_start_margin ||
179            result.ignore_block_margins_for_stretch != ignore_block_margins_for_stretch ||
180            result.has_inline_parent != has_inline_parent
181        {
182            return None;
183        }
184
185        let fragment = result.result.fragment.clone();
186        {
187            let mut borrowed_fragment = fragment.borrow_mut();
188
189            // Ideally when the final position doesn't change, this wouldn't be set, but we have
190            // no way currently to track whether the final position wil differ from the one set in
191            // the cached fragment. Final positioning is done in the containing block and depends
192            // on things like margins and the size of siblings.
193            borrowed_fragment.base.status = FragmentStatus::PositionMaybeChanged;
194
195            borrowed_fragment.base.rect.origin = result.result.original_offset;
196            if self.style.clone_position() == Position::Relative {
197                borrowed_fragment.base.rect.origin +=
198                    relative_adjustement(&self.style, containing_block)
199                        .to_physical_vector(containing_block.style.writing_mode)
200            }
201        }
202
203        Some(fragment)
204    }
205
206    pub(crate) fn cache_same_formatting_context_block_layout(
207        &self,
208        containing_block: &ContainingBlock,
209        collapsible_with_parent_start_margin: Option<CollapsibleWithParentStartMargin>,
210        ignore_block_margins_for_stretch: LogicalSides1D<bool>,
211        has_inline_parent: bool,
212        fragment: ArcRefCell<BoxFragment>,
213    ) {
214        let mut original_offset;
215        {
216            let borrowed_fragment = fragment.borrow();
217            original_offset = borrowed_fragment.content_rect().origin;
218            if self.style.clone_position() == Position::Relative {
219                original_offset -= relative_adjustement(&self.style, containing_block)
220                    .to_physical_vector(containing_block.style.writing_mode)
221            }
222        }
223
224        *self.cached_layout_result.borrow_mut() =
225            Some(LayoutResultAndInputs::SameFormattingContextBlock(Box::new(
226                SameFormattingContextBlockLayoutResultAndInputs {
227                    result: SameFormattingContextBlockLayoutResult {
228                        fragment,
229                        original_offset,
230                    },
231                    containing_block_size: containing_block.size.clone(),
232                    containing_block_writing_mode: containing_block.style.writing_mode,
233                    containing_block_justify_items: containing_block
234                        .style
235                        .clone_justify_items()
236                        .computed
237                        .0
238                        .0,
239                    collapsible_with_parent_start_margin,
240                    ignore_block_margins_for_stretch,
241                    has_inline_parent,
242                },
243            )));
244    }
245}
246
247impl Debug for LayoutBoxBase {
248    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
249        f.debug_struct("LayoutBoxBase").finish()
250    }
251}
252
253#[derive(MallocSizeOf)]
254pub(crate) enum LayoutResultAndInputs {
255    IndependentFormattingContext(Box<IndependentFormattingContextLayoutResultAndInputs>),
256    SameFormattingContextBlock(Box<SameFormattingContextBlockLayoutResultAndInputs>),
257}
258
259#[derive(Clone, MallocSizeOf)]
260pub(crate) struct IndependentFormattingContextLayoutResult {
261    pub fragments: Vec<Fragment>,
262
263    /// <https://drafts.csswg.org/css2/visudet.html#root-height>
264    pub content_block_size: Au,
265
266    /// If this layout is for a block container, this tracks the collapsable size
267    /// of start and end margins and whether or not the block container collapsed through.
268    pub collapsible_margins_in_children: CollapsedBlockMargins,
269
270    /// The contents of a table may force it to become wider than what we would expect
271    /// from 'width' and 'min-width'. This is the resulting inline content size,
272    /// or None for non-table layouts.
273    pub content_inline_size_for_table: Option<Au>,
274
275    /// The offset of the last inflow baseline of this layout in the content area, if
276    /// there was one. This is used to propagate baselines to the ancestors of `display:
277    /// inline-block`.
278    pub baselines: Baselines,
279
280    /// Whether or not this layout depends on the containing block size.
281    pub depends_on_block_constraints: bool,
282
283    /// Additional information of this layout that could be used by Javascripts and devtools.
284    pub specific_layout_info: Option<SpecificLayoutInfo>,
285}
286
287/// A collection of layout inputs and a cached layout result for an IndependentFormattingContext for
288/// use in [`LayoutBoxBase`].
289#[derive(MallocSizeOf)]
290pub(crate) struct IndependentFormattingContextLayoutResultAndInputs {
291    /// The [`IndependentFormattingContextLayoutResult`] for this layout.
292    pub result: IndependentFormattingContextLayoutResult,
293
294    /// The [`ContainingBlockSize`] to use for this box's contents, but not
295    /// for the box itself.
296    pub containing_block_for_children_size: ContainingBlockSize,
297
298    /// A [`PositioningContext`] holding absolutely-positioned descendants
299    /// collected during the layout of this box.
300    pub positioning_context: PositioningContext,
301}
302
303#[derive(Clone, MallocSizeOf)]
304pub(crate) struct SameFormattingContextBlockLayoutResult {
305    pub fragment: ArcRefCell<BoxFragment>,
306    original_offset: Point2D<Au, CSSPixel>,
307}
308
309/// A collection of layout inputs and a cached layout result for a SameFormattingContextBlock for
310/// use in [`LayoutBoxBase`].
311#[derive(MallocSizeOf)]
312pub(crate) struct SameFormattingContextBlockLayoutResultAndInputs {
313    pub result: SameFormattingContextBlockLayoutResult,
314    /// The [`ContainingBlockSize`] used when this block was laid out.
315    pub containing_block_size: ContainingBlockSize,
316    /// The containing block's [`WritingMode`]  used when this block was laid out.
317    pub containing_block_writing_mode: WritingMode,
318    /// The containing block's `justify-items` [`AlignFlags`] used when this block was laid out.
319    pub containing_block_justify_items: AlignFlags,
320    /// Whether or not the margin in this block was collapsible with the parent's start margin
321    /// when this block was laid out.
322    collapsible_with_parent_start_margin: Option<CollapsibleWithParentStartMargin>,
323    /// Whether or not block margins were ignored for stretch when this block was laid out.
324    ignore_block_margins_for_stretch: LogicalSides1D<bool>,
325    /// Whether or not this block had an inline parent.
326    has_inline_parent: bool,
327}